diff --git a/CMakeLists.txt b/CMakeLists.txt index 734dd1a01..abc5aa96b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,10 +87,16 @@ option(SRB2_CONFIG_PROFILEMODE "Compile for profiling (GCC only)." OFF) option(SRB2_CONFIG_TRACY "Compile with Tracy profiling enabled" OFF) option(SRB2_CONFIG_ASAN "Compile with AddressSanitizer (libasan)." OFF) option(SRB2_CONFIG_UBSAN "Compile with UndefinedBehaviorSanitizer (libubsan)." OFF) +option(SRB2_CONFIG_NOVERIFYIWADS "Compile with IWAD verification turned off." OFF) set(SRB2_CONFIG_ASSET_DIRECTORY "" CACHE PATH "Path to directory that contains all asset files for the installer. If set, assets will be part of installation and cpack.") option(SRB2_CONFIG_LTO "Enable link time optimizations, improves performance at the cost of longer link times." ON) option(SRB2_CONFIG_TIDY "Enable clang tiny, checks compiled code for issues at the cost of longer compile times." OFF) +if(SRB2_CONFIG_NOVERIFYIWADS) + target_compile_definitions(SRB2SDL2 PRIVATE -DNOVERIFYIWADS) +endif() + + if(SRB2_CONFIG_ENABLE_TESTS) # https://github.com/catchorg/Catch2 CPMAddPackage( diff --git a/extras/Items.md b/extras/Items.md new file mode 100644 index 000000000..94b174af5 --- /dev/null +++ b/extras/Items.md @@ -0,0 +1,64 @@ +# Items + +BlanKart features its own collection of both new items and toggleable alternate takes on existing SRB2Kart v1 items. Most new items also appear in Ring Racers, but function significantly differently. This wiki page will cover all necessary information for these items. + +## Alternate Items + +Alternative items are secondary versions of existing SRB2Kart items that **reinterpret them in (mostly) unique ways**. As BlanKart develops further, more alternate items may appear. + +To toggle on/off these alternate versions, you can do so with the following commands: + +- ``altitem_invincibility`` (for Invincibility) +- ``altitem_shrink`` (for Shrink) + +### Alt. Invincibility (Power: Occupies Slot) +Invincibility, in its normal form, is a power item that appears in the lower ranks, letting players cut through offroad and spin out opponents with prejudice. **Alt. Invincibility** works significantly different, trading its aggression for speed and optimized recovery. + +To begin, Alt. Invincibility's item odds work as an inversion of the Self-Propelled Bomb's: Like how the S.P.B. only appears when first is extremely far ahead of the pack, Alt. Invincibility only reveals itself to players who are **extremely far behind the pack**. In some extreme cases, Alt. Invincibilty will override the race-start cooldown to power-up players already left in the dust. + +Unlike the standard Invincibility, **you can't damage players with Alt. Invincibility**, instead bumping them like you would normally. + +Its time limit (and power) is directly tied to your distance from the "cluster player", the player closest to the largest collection on the map of (losing) players. If you notice yourself falling super far behind on the minimap, you're likely to roll Alt. Invincibility. **The further the distance, the stronger your invincibility**, and the faster you'll go. Think of it like a non-autopilot version of Mario Kart's Bullet Bill, or Sonic Racing's Drill Wisp. _Make huge comebacks, and don't lose hope!_ + +Do note, though: Alt. Invincibility is designed as a "gap closer", and not traditional catchup. Once you pass the cluster player, the timer **decreases at an exponentially fast rate**; don't count on it to let you steal wins from the leader! + +As you run out of power (your Invincibility drops below 5 seconds), **offroad will gradually begin to affect you again**. A warning signal usually sounds as your invincibility is about to run out, as well. Be careful when you see the rainbow color begin to fade! + +If you want something else, or your invincibility is running low, hold ITEM to cancel your Invincibility; the same as you would Grow. + +### Alt. Shrink (Power) +Shrink, in its normal form, functions much like Mario Kart's Lightning item. Most servers keep it off due to how frequent and disruptive it is to races. + +**Alt. Shrink** forgoes that and turns it into an inversion of Grow! In its alternate form, Shrink appears as a **midpack power item**, shrinking you to a dimunitive size and increasing your acceleration and top speed, whilist **doubling the amount of nearly every item** you roll while under its effects. With some luck, you can roll impressive items and put pressure on first! + +Be careful, though: running into anyone while in this state **will flip you over** and significantly cut your speed. Nimble dodging is required to make the most of this item! + +## New Items + +### Bubble Shield (Defense: Occupies Slot) +This is an item that appears around the upper-midpack of any race. This item's focus is **defense**. + +When a player holds onto this shield, they can inflate the shield to protect themselves from items. +As the shield is used more, and takes damage from items and player bumps, it **cracks more and more until finally shattering**. Some item interactions, like a snipe from a Banana or Orbinaut, make it **shatter right away**! + +Hold down ITEM to inflate the shield. Once you let go, it slowly deflates. As long as it's in an inflated state--be it inflating or deflating--you can **protect yourself from items** without spinning out. +While in an _uninflated_ state, however, the Bubble Shield can let you **nullify only one item hit**, shattering itself in the process. + +If you hold ITEM for too long, you'll **overinflate the shield**, causing it to shatter, no matter the amount of health it still had. That's not all bad, though; you'll get a nice speed boost, and be temporarily invulnerable to hazards and items; proximity mines are no match! + +The Bubble Shield also makes your bumps stronger: if you bump into another player, you won't rebound, but they will. If your shield isn't inflated while bumping other players, it'll **take severe damage** from the bump. Careful play is key with this shield! + +### Flame Shield (Offense+Boost: Occupies Slot) +This is an item that appears around the lower-midpack of any race. + +The Flame Shield can be used by holding down ITEM. + +When in use, a **Flamometer** appears next to your character. The Flamometer shows how much you're charging the shield, and how much fuel you have left. +The fuel gauge is constantly depleting once the Flame Shield is first used. As you hold ITEM; you'll increase the Flame Shield's boost power, but it'll **use up whatever fuel you have** at the moment. + +If you overcharge the Flame Shield, the Flamometer **catches fire**, and you'll gain no extra boost power, but can more easily cut through offroad. If you pace yourself, you can make this item last very long. + +If you run into players while charging your Flame Shield, **you'll flip them over**, making them lose a significant amount of speed. You however, rush right through them. + +### Land Mine (Drop Behind) +This is an item exclusive to first. Drop a discreet mine onto the track; anyone who runs into it **flips over and loses speed**. diff --git a/src/Sourcefile b/src/Sourcefile index 192774079..a60766335 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -132,7 +132,7 @@ k_bot.cpp k_botitem.cpp k_botsearch.cpp k_cluster.cpp -k_odds.c +k_items.c k_grandprix.c k_boss.c k_hud.c diff --git a/src/command.h b/src/command.h index b6e99ebf2..345be77df 100644 --- a/src/command.h +++ b/src/command.h @@ -188,9 +188,6 @@ extern CV_PossibleValue_t CV_Natural[]; #define KARTGP_MASTER 4 // Not a speed setting, gives hard speed with maxed out bots #define KARTGP_NIGHTMARE 5 // Not a speed setting, gives expert speed with maxed out bots extern CV_PossibleValue_t kartspeed_cons_t[], gpdifficulty_cons_t[]; -// Invincibility types. -#define KARTINVIN_LEGACY 0 -#define KARTINVIN_ALTERN 1 extern consvar_t cv_execversion; diff --git a/src/d_main.cpp b/src/d_main.cpp index 949c574a3..1d824b2d6 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -94,7 +94,7 @@ #define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291 #define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b #define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9 -#define ASSET_HASH_MAIN_PK3 0x26b5dde340bc9059 +#define ASSET_HASH_MAIN_PK3 0x9dc33fb314952e03 #define ASSET_HASH_MAPPATCH_PK3 0xd4d4ce4a090d5473 #define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461 #ifdef USE_PATCH_FILE @@ -1861,7 +1861,7 @@ void D_SRB2Main(void) if (WADNAMECHECK(word)) { if (!(pstartmap = wadnamemap)) - I_Error("Bad '%s' level warp; pstartmap (%d) != wadnamemap (%d)).\n" + I_Error("Bad '%s' level warp; pstartmap (%d) != wadnamemap (%d).\n" #if defined (_WIN32) "Are you using MSDOS 8.3 filenames in Zone Builder?\n" "\n" diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 0a97a853e..0ee09b735 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -68,6 +68,7 @@ #include "m_perfstats.h" #include "g_party.h" #include "k_specialstage.h" +#include "k_items.h" #define CV_RESTRICT CV_NETVAR @@ -166,7 +167,6 @@ static void KartItemLitter_OnChange(void); static void KartItemPush_OnChange(void); static void KartAntiBump_OnChange(void); static void KartItemBreaker_OnChange(void); -static void KartInvinType_OnChange(void); static void KartBumpSpark_OnChange(void); static void Schedule_OnChange(void); @@ -383,35 +383,6 @@ consvar_t cv_joyscale[MAXSPLITSCREENPLAYERS] = { //Alam: Dummy for save #endif // SRB2kart -consvar_t cv_sneaker = CVAR_INIT ("sneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_rocketsneaker = CVAR_INIT ("rocketsneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_invincibility = CVAR_INIT ("invincibility", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_banana = CVAR_INIT ("banana", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_eggmanmonitor = CVAR_INIT ("eggmanmonitor", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_orbinaut = CVAR_INIT ("orbinaut", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_jawz = CVAR_INIT ("jawz", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_mine = CVAR_INIT ("mine", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_ballhog = CVAR_INIT ("ballhog", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_selfpropelledbomb = CVAR_INIT ("selfpropelledbomb", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_grow = CVAR_INIT ("grow", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_shrink = CVAR_INIT ("shrink", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_thundershield = CVAR_INIT ("thundershield", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_hyudoro = CVAR_INIT ("hyudoro", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_pogospring = CVAR_INIT ("pogospring", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_kitchensink = CVAR_INIT ("kitchensink", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_superring = CVAR_INIT ("superring", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_landmine = CVAR_INIT ("landmine", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_bubbleshield = CVAR_INIT ("bubbleshield", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_flameshield = CVAR_INIT ("flameshield", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); - -consvar_t cv_dualsneaker = CVAR_INIT ("dualsneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_triplesneaker = CVAR_INIT ("triplesneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_triplebanana = CVAR_INIT ("triplebanana", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_decabanana = CVAR_INIT ("decabanana", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_tripleorbinaut = CVAR_INIT ("tripleorbinaut", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_quadorbinaut = CVAR_INIT ("quadorbinaut", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -consvar_t cv_dualjawz = CVAR_INIT ("dualjawz", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); - static CV_PossibleValue_t kartminimap_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}}; consvar_t cv_kartminimap = CVAR_INIT ("kartminimap", "4", CV_SAVE, kartminimap_cons_t, NULL); consvar_t cv_kartcheck = CVAR_INIT ("kartcheck", "Yes", CV_SAVE, CV_YesNo, NULL); @@ -499,6 +470,10 @@ consvar_t cv_kartstacking_grow_speedboost = CVAR_INIT ("vanillaboost_grow_speedb consvar_t cv_kartstacking_grow_accelboost = CVAR_INIT ("vanillaboost_grow_accelboost", "0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_grow_stackable = CVAR_INIT ("vanillaboost_grow_stackable", "Off", CV_NETVAR|CV_GUARD, CV_OnOff, NULL); +consvar_t cv_kartstacking_altshrink_speedboost = CVAR_INIT ("vanillaboost_altshrink_speedboost", "0.50", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); +consvar_t cv_kartstacking_altshrink_accelboost = CVAR_INIT ("vanillaboost_altshrink_accelboost", "0.15", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); +consvar_t cv_kartstacking_altshrink_stackable = CVAR_INIT ("vanillaboost_altshrink_stackable", "On", CV_NETVAR|CV_GUARD, CV_OnOff, NULL); + consvar_t cv_kartstacking_bubble_speedboost = CVAR_INIT ("vanillaboost_bubble_speedboost", "0.35", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_bubble_accelboost = CVAR_INIT ("vanillaboost_bubble_accelboost", "8.0", CV_NETVAR|CV_CHEAT|CV_FLOAT|CV_GUARD, CV_Unsigned, NULL); consvar_t cv_kartstacking_bubble_stackable = CVAR_INIT ("vanillaboost_bubble_stackable", "Off", CV_NETVAR|CV_GUARD, CV_OnOff, NULL); @@ -588,7 +563,7 @@ consvar_t cv_kartantibump = CVAR_INIT ("kartantibump", "0", CV_NETVAR|CV_CALL|CV // Odds distancing #define MAXODDSDIST ((INT32_MAX / FRACUNIT) / 2) static CV_PossibleValue_t distvar_cons_t[] = {{1, "MIN"}, {MAXODDSDIST, "MAX"}, {0, NULL}}; -consvar_t cv_kartoddsdist = CVAR_INIT ("kartoddsdist", "987", CV_NETVAR|CV_CHEAT|CV_GUARD, distvar_cons_t, NULL); +consvar_t cv_kartoddsdist = CVAR_INIT ("kartoddsdist", "1480", CV_NETVAR|CV_CHEAT|CV_GUARD, distvar_cons_t, NULL); consvar_t cv_kartlegacyoddsdist = CVAR_INIT ("kartlegacyoddsdist", "1050", CV_NETVAR|CV_CHEAT|CV_GUARD, distvar_cons_t, NULL); // SPB distance @@ -597,9 +572,12 @@ consvar_t cv_kartspbdist = CVAR_INIT ("kartspbdist", "4432", CV_NETVAR|CV_CHEAT| consvar_t cv_kartlegacyspbdist = CVAR_INIT ("kartlegacyspbdist", "2216", CV_NETVAR|CV_CHEAT|CV_GUARD, spbdist_cons_t, NULL); #undef MAXODDSDIST -// Invincibility modifiers -static CV_PossibleValue_t invintype_cons_t[] = {{0, "Legacy"}, {1, "Alternative"}, {0, NULL}}; -consvar_t cv_kartinvintype = CVAR_INIT ("kartinvintype", "Legacy", CV_NETVAR|CV_CALL, invintype_cons_t, KartInvinType_OnChange); +// SPB Rush toggle; toggles on/off the more aggressive "pressure" odds when an SPB is active +consvar_t cv_kartspbrush = CVAR_INIT ("kartspbrush", "On", CV_NETVAR, CV_OnOff, NULL); + +// Time limit for Alt. Shrink +static CV_PossibleValue_t altshrinktime_cons_t[] = {{0, "MIN"}, {(INT16_MAX / TICRATE), "MAX"}, {0, NULL}}; +consvar_t cv_kartaltshrinktime = CVAR_INIT ("kartaltshrinktime", "14", CV_NETVAR|CV_CHEAT|CV_GUARD, altshrinktime_cons_t, NULL); // How far the player must be from the cluster to begin frequently rolling Invincibility. static CV_PossibleValue_t invindist_cons_t[] = {{1, "MIN"}, {32000, "MAX"}, {0, NULL}}; @@ -621,23 +599,7 @@ consvar_t cv_kartflame_offroadburn = CVAR_INIT ("kartflame_offroadburn", "Off", static CV_PossibleValue_t kartairsquish_cons_t[] = {{1, "Squish"}, {2, "Flip-over"}, {0, "None"}, {0, NULL}}; consvar_t cv_kartairsquish = CVAR_INIT ("kartairsquish", "None", CV_NETVAR, kartairsquish_cons_t, NULL); -static CV_PossibleValue_t kartdebugitem_cons_t[] = -{ -#define FOREACH( name, n ) { n, #name } - KART_ITEM_ITERATOR, -#undef FOREACH - {0} -}; -consvar_t cv_kartdebugitem = CVAR_INIT ("kartdebugitem", "NONE", CV_NETVAR|CV_CHEAT, kartdebugitem_cons_t, NULL); -static CV_PossibleValue_t kartdebugamount_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}}; -consvar_t cv_kartdebugamount = CVAR_INIT ("kartdebugamount", "1", CV_NETVAR|CV_CHEAT, kartdebugamount_cons_t, NULL); consvar_t cv_kartdebugshrink = CVAR_INIT ("kartdebugshrink", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); -#ifdef DEVELOP -#define VALUE "Yes" -#else -#define VALUE "No" -#endif -#undef VALUE consvar_t cv_kartdebugdistribution = CVAR_INIT ("kartdebugdistribution", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); consvar_t cv_kartdebughuddrop = CVAR_INIT ("kartdebughuddrop", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); @@ -1274,6 +1236,7 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_growmusic); CV_RegisterVar(&cv_supermusic); + CV_RegisterVar(&cv_altshrinkmusic); CV_RegisterVar(&cv_songcredits); CV_RegisterVar(&cv_tutorialprompt); @@ -1340,6 +1303,7 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_invincmusicfade); CV_RegisterVar(&cv_growmusicfade); + CV_RegisterVar(&cv_altshrinkmusicfade); CV_RegisterVar(&cv_resetspecialmusic); @@ -6220,16 +6184,15 @@ static void Got_Cheat(UINT8 **cp, INT32 playernum) } case CHEAT_GIVEITEM: { - SINT8 item = READSINT8(*cp); + UINT8 item = READUINT8(*cp); UINT8 amt = READUINT8(*cp); - item = max(item, KITEM_SAD); - item = min(item, NUMKARTITEMS - 1); + item = min(item, numkartitems - 1); K_StripItems(player); // Cancel roulette if rolling - player->itemroulette = KROULETTE_DISABLED; + player->itemroulette = 0; player->itemtype = item; player->itemamount = amt; @@ -6240,10 +6203,7 @@ static void Got_Cheat(UINT8 **cp, INT32 playernum) } else { - // FIXME: we should have actual KITEM_ name array - const char *itemname = cv_kartdebugitem.PossibleValue[1 + item].strvalue; - - CV_CheaterWarning(playernum, va("give item %s x%d", itemname, amt)); + CV_CheaterWarning(playernum, va("give item %s x%d", item < 0 ? "SAD" : DEH_KartItemName(item), amt)); } break; } @@ -6528,8 +6488,6 @@ static void Command_KartGiveItem_f(void) const char *name; INT32 item; - const char * str; - int i; ac = COM_Argc(); @@ -6541,7 +6499,7 @@ static void Command_KartGiveItem_f(void) } else { - item = NUMKARTITEMS; + item = numkartitems; name = COM_Argv(1); @@ -6557,18 +6515,18 @@ static void Command_KartGiveItem_f(void) CONS_Printf("\x83" "Autocomplete:\n"); /* then do very loose partial matching */ - for (i = 0; ( str = kartdebugitem_cons_t[i].strvalue ); ++i) + for (i = 0; i < numkartitems; i++) { - if (strcasestr(str, name) != NULL) + if (strcasestr(DEH_KartItemName(i), name) != NULL) { - CONS_Printf("\x83\t%s\n", str); - item = kartdebugitem_cons_t[i].value; + CONS_Printf("\x83\t%s\n", DEH_KartItemName(i)); + item = i; } } } } - if (item < NUMKARTITEMS) + if (item < numkartitems) { INT32 amt; @@ -7816,24 +7774,6 @@ static void KartItemBreaker_OnChange(void) } } -static void KartInvinType_OnChange(void) -{ - if (K_CanChangeRules(false) == false) - { - return; - } - - if (leveltime < starttime) - { - CONS_Printf(M_GetText("Invincibility type has been changed to \"%s\".\n"), cv_kartinvintype.string); - invintype = (UINT8)cv_kartinvintype.value; - } - else - { - CONS_Printf(M_GetText("Invincibility type will be changed to \"%s\" next round.\n"), cv_kartinvintype.string); - } -} - static void KartBumpSpark_OnChange(void) { if (K_CanChangeRules(false) == false) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index e8e270783..3aa6c8495 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -72,38 +72,6 @@ extern consvar_t cv_spectatorreentry, cv_antigrief; extern consvar_t cv_debugtraversemax; #endif -// SRB2kart items -extern consvar_t - cv_sneaker, - cv_rocketsneaker, - cv_invincibility, - cv_banana, - cv_eggmanmonitor, - cv_orbinaut, - cv_jawz, - cv_mine, - cv_ballhog, - cv_selfpropelledbomb, - cv_grow, - cv_shrink, - cv_thundershield, - cv_hyudoro, - cv_pogospring, - cv_kitchensink, - cv_superring, - cv_landmine, - cv_bubbleshield, - cv_flameshield; - -extern consvar_t - cv_dualsneaker, - cv_triplesneaker, - cv_triplebanana, - cv_decabanana, - cv_tripleorbinaut, - cv_quadorbinaut, - cv_dualjawz; - extern consvar_t cv_kartminimap; extern consvar_t cv_kartcheck; extern consvar_t cv_kartinvinsfx; @@ -160,6 +128,11 @@ extern consvar_t cv_kartstacking_bubble_speedboost; extern consvar_t cv_kartstacking_bubble_accelboost; extern consvar_t cv_kartstacking_bubble_stackable; +extern consvar_t cv_kartaltshrinktime; +extern consvar_t cv_kartstacking_altshrink_speedboost; +extern consvar_t cv_kartstacking_altshrink_accelboost; +extern consvar_t cv_kartstacking_altshrink_stackable; + extern consvar_t cv_kartstacking_start_speedboost; extern consvar_t cv_kartstacking_start_accelboost; extern consvar_t cv_kartstacking_start_stackable; @@ -202,7 +175,6 @@ extern consvar_t cv_kartexplosion_limitlifetime; extern consvar_t cv_kartexplosion_limitlifetime_cap; extern consvar_t cv_kartslipdash; extern consvar_t cv_kartslopeboost; -extern consvar_t cv_kartinvintype; extern consvar_t cv_kartinvindist; extern consvar_t cv_kartinvindistmul; extern consvar_t cv_kartinvin_maxtime; @@ -230,6 +202,8 @@ extern consvar_t cv_kartlegacyoddsdist; extern consvar_t cv_kartspbdist; extern consvar_t cv_kartlegacyspbdist; +extern consvar_t cv_kartspbrush; + extern consvar_t cv_encorevotes; extern consvar_t cv_votetime; diff --git a/src/d_player.h b/src/d_player.h index ded27e845..b828778cd 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -58,14 +58,6 @@ typedef enum PST_REBORN } playerstate_t; -typedef enum -{ - IF_USERINGS = 1, // Have to be not holding the item button to change from using rings to using items (or vice versa) - prevents weirdness - IF_ITEMOUT = 1<<1, // Are you holding an item out? - IF_EGGMANOUT = 1<<2, // Eggman mark held, separate from IF_ITEMOUT so it doesn't stop you from getting items - IF_HOLDREADY = 1<<3, // Hold button-style item is ready to activate -} itemflags_t; - // // Player internal flags // @@ -137,71 +129,13 @@ typedef enum CR_ZOOMTUBE, } carrytype_t; // carry -/* -To use: #define FOREACH( name, number ) -Do with it whatever you want. -Run this macro, then #undef FOREACH afterward -*/ -#define KART_ITEM_ITERATOR \ - FOREACH (SAD, -1),\ - FOREACH (NONE, 0),\ - FOREACH (SNEAKER, 1),\ - FOREACH (ROCKETSNEAKER, 2),\ - FOREACH (INVINCIBILITY, 3),\ - FOREACH (BANANA, 4),\ - FOREACH (EGGMAN, 5),\ - FOREACH (ORBINAUT, 6),\ - FOREACH (JAWZ, 7),\ - FOREACH (MINE, 8),\ - FOREACH (BALLHOG, 9),\ - FOREACH (SPB, 10),\ - FOREACH (GROW, 11),\ - FOREACH (SHRINK, 12),\ - FOREACH (THUNDERSHIELD, 13),\ - FOREACH (HYUDORO, 14),\ - FOREACH (POGOSPRING, 15),\ - FOREACH (KITCHENSINK, 16),\ - FOREACH (SUPERRING, 17),\ - FOREACH (LANDMINE, 18),\ - FOREACH (BUBBLESHIELD, 19),\ - FOREACH (FLAMESHIELD, 20) - typedef enum { -#define FOREACH( name, n ) KITEM_ ## name = n - KART_ITEM_ITERATOR, -#undef FOREACH - - NUMKARTITEMS, - - // Additional roulette numbers, only used for K_KartGetItemResult - KRITEM_DUALSNEAKER = NUMKARTITEMS, - KRITEM_TRIPLESNEAKER, - KRITEM_TRIPLEBANANA, - KRITEM_TENFOLDBANANA, - KRITEM_TRIPLEORBINAUT, - KRITEM_QUADORBINAUT, - KRITEM_DUALJAWZ, - - NUMKARTRESULTS -} kartitems_t; - -typedef enum { - KROULETTE_DISABLED, - KROULETTE_ACTIVE, -} kartroulette_t; - -typedef enum { - KROULETTETYPE_NORMAL, - KROULETTETYPE_KARMA, - KROULETTETYPE_EGGMAN, -} kartroulettetype_t; - -typedef enum { - KITEMBLINKMODE_NORMAL, - KITEMBLINKMODE_MASHED, - KITEMBLINKMODE_KARMA, -} kartitemblinkmode_t; + IF_USERINGS = 1, // Have to be not holding the item button to change from using rings to using items (or vice versa) - prevents weirdness + IF_ITEMOUT = 1<<1, // Are you holding an item out? + IF_EGGMANOUT = 1<<2, // Eggman mark held, separate from IF_ITEMOUT so it doesn't stop you from getting items + IF_HOLDREADY = 1<<3, // Hold button-style item is ready to activate +} itemflags_t; typedef enum { @@ -660,20 +594,24 @@ struct player_t UINT16 draftleeway; // Leniency timer before removing draft power SINT8 lastdraft; // (-1 to 15) - Last player being drafted - UINT8 tripwireState; // see tripwirestate_t - UINT8 tripwirePass; // see tripwirepass_t + UINT8 tripwireState; // see tripwirestate_t + UINT8 tripwirePass; // see tripwirepass_t UINT16 tripwireLeniency; // When reaching a state that lets you go thru tripwire, you get an extra second leniency after it ends to still go through it. UINT8 tripwireReboundDelay; // When failing Tripwire, brieftly lock out speed-based tripwire pass (anti-cheese) - UINT16 itemroulette; // Used for the roulette when deciding what item to give you (was "pw_kartitem") - UINT16 previtemroulette; // Used determining when to give rings after hitting item boxes + UINT16 itemroulette; // Used for the roulette when deciding what item to give you (was "pw_kartitem") + UINT16 previtemroulette; // Used determining when to give rings after hitting item boxes UINT16 itemblink; // Item flashing after roulette, serves as a mashing indicator. Also prevents item from being stolen. UINT16 itemblinkmode; // Type of flashing: 0 = white (normal), 1 = red (mashing), 2 = rainbow (enhanced items) - UINT8 roulettetype; // Used for the roulette, for deciding type (0 = normal, 1 = better, 2 = eggman mark) + UINT8 roulettetype; // Used for the roulette, for deciding type (0 = normal, 1 = better, 2 = eggman mark) // Item held stuff - SINT8 itemtype; // KITEM_ constant for item number - UINT8 itemamount; // Amount of said item + UINT8 itemtype; // KITEM_ constant for item number + UINT8 itemamount; // Amount of said item + itemflags_t itemflags; // holds IF_flags (see itemstate_t) + tic_t itemusecooldown; // timer for which the player is locked out of using an item or mashing the item roulette (implementation of xItemLib setPlayerItemCooldown) + tic_t itemusecooldownmax; // most recent highest value for itemusecooldown, mainly used for HUD rendering currently + SINT8 throwdir; // Held dir of controls; 1 = forward, 0 = none, -1 = backward (was "player->heldDir") UINT8 sadtimer; // How long you've been sad @@ -724,13 +662,14 @@ struct player_t fixed_t slopeaccel; // Handle slopeboost accel INT16 growshrinktimer; // > 0 = Big, < 0 = small + INT16 altshrinktimeshit; // (Alt. Shrink) How many times were you flipped over while shrunk? INT16 growcancel; // Duration of grow canceling INT16 squishedtimer; // Duration of being squished UINT16 rocketsneakertimer; // Rocket Sneaker duration timer - UINT16 invincibilitytimer; // Invincibility timer - UINT16 maxinvincibilitytime; // (Alternate) Initial time for Invincibility, used for the item bar. + UINT16 invincibilitytimer; // Invincibility timer + UINT16 maxinvincibilitytime; // (Alternate) Initial time for Invincibility, used for the item bar. UINT16 invincibilitybottleneck; // (Alternate) Prevents breakaways by gradienting towards a heavier decrement. INT16 invincibilitycancel; // (Alternate) Duration of Invincibility canceling. UINT8 invincibilitywarning; // (Alternate) "Timer warning" boolean to signal Alt. Invin. is running out. @@ -836,8 +775,6 @@ struct player_t UINT8 typing_duration; // How long since resumed timer UINT8 kickstartaccel; - - UINT8 itemflags; // holds IF_flags (see itemstate_t) fixed_t outrun; // Milky Way road effect UINT8 outruntime; // Used to bypass the speed cap for fall off diff --git a/src/deh_lua.c b/src/deh_lua.c index 790fc9777..4c6dbf4d0 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -439,6 +439,26 @@ static int ScanConstants(lua_State *L, boolean mathlib, const char *word) } return luaL_error(L, "karthud '%s' could not be found.\n", word); } + else if (fastncmp("KITEM_",word,6)) { + p = word+6; + if (fastcmp(p, "LIGHTNINGSHIELD")) + { + lua_pushinteger(L, KITEM_THUNDERSHIELD); + return 1; + } + if (fastcmp(p, "SAD")) + { + lua_pushinteger(L, MAXKARTITEMS); + return 1; + } + i = DEH_FindKartItem(p); + if (i != MAXKARTITEMS) + { + CacheAndPushConstant(L, word, i); + return 1; + } + return luaL_error(L, "kartitem '%s' could not be found.\n", word); + } else if (fastncmp("SKINCOLOR_",word,10)) { p = word+10; i = DEH_FindSkincolor(p); diff --git a/src/deh_lua.h b/src/deh_lua.h index 7b767f017..ed97a1418 100644 --- a/src/deh_lua.h +++ b/src/deh_lua.h @@ -27,6 +27,7 @@ extern "C" { #include "fastcmp.h" #include "lua_script.h" #include "lua_libs.h" +#include "k_items.h" #include "dehacked.h" #include "deh_tables.h" diff --git a/src/deh_soc.c b/src/deh_soc.c index dff0fe6de..c128b1691 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -53,6 +53,7 @@ #include "filesrch.h" // refreshdirmenu #include "k_follower.h" #include "doomstat.h" // MAXMUSNAMES +#include "k_items.h" // Loops through every constant and operation in word and performs its calculations, returning the final value. fixed_t get_number(const char *word) @@ -3694,7 +3695,7 @@ void readfollower(MYFILE *f) char *s; char *word, *word2, dname[SKINNAMESIZE+1]; char *tmp; - char testname[SKINNAMESIZE]; + char testname[SKINNAMESIZE+1]; boolean nameset; INT32 fallbackstate = 0; @@ -4035,6 +4036,211 @@ void readweather(MYFILE *f, INT32 num) Z_Free(s); } +#define WARN(str, ...) deh_warning("KartItem %s: " str, strbuf_get(kartitemnames, item->info.nameofs), __VA_ARGS__) +#define WARN0(str) deh_warning("KartItem %s: " str, strbuf_get(kartitemnames, item->info.nameofs)) +void readkartitem(MYFILE *f, kartitem_t *item) +{ + char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); + char *word = s; + char *word2; + char *tmp; + INT32 i; + + do if (myfgets(s, MAXLINELEN, f)) + { + if (s[0] == '\n') + break; + + // First remove trailing newline, if there is one + tmp = strchr(s, '\n'); + if (tmp) + *tmp = '\0'; + + tmp = strchr(s, '#'); + if (tmp) + *tmp = '\0'; + if (s == tmp) + continue; // Skip comment lines, but don't break. + + // Get the part before the " = " + tmp = strchr(s, '='); + if (tmp) + *(tmp-1) = '\0'; + else + break; + strupr(word); + + // Now get the part after + word2 = (tmp += 2); + strupr(word2); + + if (fastcmp(word, "BIGPATCHES") || fastcmp(word, "SMALLPATCHES")) + { + kartitemgraphics_t *graphics = &item->graphics[word[0] == 'B' ? 0 : 1]; + + if (graphics == &item->graphics[0] && item->spritedef.numframes > 0) + { + Z_Free(item->spritedef.spriteframes); + memset(&item->spritedef, 0, sizeof(item->spritedef)); + } + for (i = 0; i < graphics->numpatches; i++) + { + Z_Free(graphics->patchnames[i]); + Patch_Free(graphics->patches[i]); + graphics->patches[i] = NULL; + } + + tmp = strtok(word2,","); + for (graphics->numpatches = 0; graphics->numpatches < MAXITEMPATCHES;) + { + graphics->patchnames[graphics->numpatches++] = Z_StrDup(tmp); + if ((tmp = strtok(NULL,",")) == NULL) + break; + } + } + else if (fastcmp(word, "FLAGS") || fastcmp(word, "FLAGSALT")) + { + item->flags[word[5] == 'A' ? 1 : 0] = get_number(word2); + } + else + WARN("unknown word '%s'", word); + } + while (!myfeof(f)); // finish when the line is empty + + Z_Free(s); +} +#undef WARN +#undef WARN0 + +#define WARN(str, ...) deh_warning("KartResult %s: " str, result->cvar->name, __VA_ARGS__) +#define WARN0(str) deh_warning("KartResult %s: " str, result->cvar->name) +void readkartresult(MYFILE *f, kartresult_t *result) +{ + char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); + char *word = s; + char *word2; + char *tmp; + + do if (myfgets(s, MAXLINELEN, f)) + { + if (s[0] == '\n') + break; + + // First remove trailing newline, if there is one + tmp = strchr(s, '\n'); + if (tmp) + *tmp = '\0'; + + tmp = strchr(s, '#'); + if (tmp) + *tmp = '\0'; + if (s == tmp) + continue; // Skip comment lines, but don't break. + + // Get the part before the " = " + tmp = strchr(s, '='); + if (tmp) + *(tmp-1) = '\0'; + else + break; + strupr(word); + + // Now get the part after + word2 = tmp + 2; + //strupr(word2); + + if (fastcmp(word, "TYPE")) + { + if (result->isalt) + { + WARN0("can't set type for alternate result"); + continue; + } + result->type = get_kartitem(word2); + } + else if (fastcmp(word, "AMOUNT")) + { + if (result->isalt) + { + WARN0("can't set amount for alternate result"); + continue; + } + result->amount = get_number(word2); + } + else if (fastcmp(word, "ISALT")) + { + result->isalt = (boolean)(atoi(word2) || word2[0] == 'T' || word2[0] == 'Y'); + } + else if (fastcmp(word, "FLAGS")) + { + result->flags = get_number(word2); + } + else if (fastncmp(word, "ODDS", 4)) + { + UINT8 oddstable; + if (fastcmp(word+4, "RACE")) + oddstable = ODDS_RACE; + else if (fastcmp(word+4, "BATTLE")) + oddstable = ODDS_BATTLE; + else if (fastcmp(word+4, "SPECIAL")) + oddstable = ODDS_SPECIAL; + else + { + WARN("unknown word '%s'", word); + continue; + } + + UINT8 *odds = result->odds[oddstable]; + memset(odds, 0, sizeof(*odds)*MAXODDS); + tmp = strtok(word2,","); + for (UINT8 i = 0; i <= MAXODDS; i++) + { + if (i == oddstablemax[oddstable]) + { + WARN("odds table %s is limited to %d odds", word+4, oddstablemax[oddstable]); + continue; + } + odds[i] = atoi(tmp); + if ((tmp = strtok(NULL,",")) == NULL) + break; + } + } + else if (fastncmp(word, "UNIQUEODDS", 10)) + { + UINT8 oddstable; + if (fastcmp(word+10, "RACE")) + oddstable = ODDS_RACE; + else if (fastcmp(word+10, "BATTLE")) + oddstable = ODDS_BATTLE; + else if (fastcmp(word+10, "SPECIAL")) + oddstable = ODDS_SPECIAL; + + else + { + WARN("unknown word '%s'", word); + continue; + } + + result->unique_odds[oddstable] = get_useoddsfunc(word2); + } + else if (fastcmp(word, "COOLDOWNTIME")) + { + result->basecooldown = atoi(word2) * TICRATE; + } + else if (fastcmp(word, "DISPLAYNAME")) + { + result->displayname = Z_StrDup(word2); + } + else + WARN("unknown word '%s'", word); + } + while (!myfeof(f)); // finish when the line is empty + + Z_Free(s); +} +#undef WARN +#undef WARN0 + // // // @@ -4192,6 +4398,29 @@ preciptype_t get_precip(const char *word) return PRECIP_NONE; } +kartitemtype_e get_kartitem(const char *word) +{ // Returns the value of KITEM_ enumerations + kartitemtype_e i; + if (fastncmp("KITEM_",word,6)) + word += 6; // take off the KITEM_ + i = DEH_FindKartItem(word); + if (i == MAXKARTITEMS) + deh_warning("Couldn't find kartitem named 'KITEM_%s'", word); + return i; +} + +useoddsfunc_f *get_useoddsfunc(const char *word) +{ // Returns the value of KO_ enumerations + size_t i; + if (fastncmp("KO_",word,3)) + word += 3; // take off the KO_ + for (i = 0; USEODDS_FUNCS[i].name; i++) + if (fasticmp(word, USEODDS_FUNCS[i].name)) + return USEODDS_FUNCS[i].func; + deh_warning("Couldn't find odds function named 'KO_%s'",word); + return NULL; +} + /// \todo Make ANY of this completely over-the-top math craziness obey the order of operations. static fixed_t op_mul(fixed_t a, fixed_t b) { return a*b; } static fixed_t op_div(fixed_t a, fixed_t b) { return a/b; } diff --git a/src/deh_soc.h b/src/deh_soc.h index 8dc8882e8..143c21a3d 100644 --- a/src/deh_soc.h +++ b/src/deh_soc.h @@ -36,6 +36,7 @@ #include "fastcmp.h" #include "lua_script.h" // Reluctantly included for LUA_EvalMath #include "d_clisrv.h" +#include "k_items.h" #ifdef HWRENDER #include "hardware/hw_light.h" @@ -62,6 +63,8 @@ menudrawer_f *get_menudrawer(const char *word); //INT16 get_gametype(const char *word); //powertype_t get_power(const char *word); skincolornum_t get_skincolor(const char *word); +kartitemtype_e get_kartitem(const char *word); +useoddsfunc_f *get_useoddsfunc(const char *word); void readwipes(MYFILE *f); void readmaincfg(MYFILE *f); @@ -91,6 +94,8 @@ void readcupheader(MYFILE *f, cupheader_t *cup); void readfollower(MYFILE *f); preciptype_t get_precip(const char *word); void readweather(MYFILE *f, INT32 num); +void readkartitem(MYFILE *f, kartitem_t *item); +void readkartresult(MYFILE *f, kartresult_t *result); #ifdef __cplusplus } // extern "C" diff --git a/src/deh_tables.c b/src/deh_tables.c index 3f75781ab..59087ad1c 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -40,6 +40,7 @@ strbuf_t *statenames; strbuf_t *mobjnames; strbuf_t *skincolornames; strbuf_t *menunames; +strbuf_t *kartitemnames; UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite freeslot in use! I would use ceil() here if I could, but it only saves 1 byte of memory anyway. const char *DEH_MobjtypeName(mobjtype_t i) @@ -62,6 +63,11 @@ const char *DEH_MenutypeName(menutype_t i) return strbuf_get(menunames, menudefs[i].info.nameofs); } +const char *DEH_KartItemName(kartitemtype_e i) +{ + return strbuf_get(kartitemnames, kartitems[i].info.nameofs); +} + mobjtype_t DEH_FindMobjtype(const char *word) { mobjtype_t i; @@ -114,6 +120,19 @@ menutype_t DEH_FindMenutype(const char *word) return MAXMENUTYPES; } +kartitemtype_e DEH_FindKartItem(const char *word) +{ + kartitemtype_e i; + UINT32 hash = HASH32(word, strlen(word)); + for (i = 0; i < numkartitems; i++) { + if (hash != kartitems[i].info.namehash) + continue; + if (fastcmp(word, DEH_KartItemName(i))) + return i; + } + return MAXKARTITEMS; +} + struct flickytypes_s FLICKYTYPES[] = { {"BLUEBIRD", MT_FLICKY_01}, // Flicky (Flicky) {"RABBIT", MT_FLICKY_02}, // Pocky (1) @@ -796,6 +815,7 @@ struct menu_routine_s const MENU_ROUTINES[] = { { "RESETCONTROLS", &MR_ResetControls }, { "CHANGECONTROL", &MR_ChangeControl }, { "ASSIGNJOYSTICK", &MR_AssignJoystick }, + { "SETUPMONITORTOGGLES", &MR_SetupMonitorToggles }, { "HANDLEMONITORTOGGLES", &MR_HandleMonitorToggles }, { "RESTARTAUDIO", &MR_RestartAudio }, { "ENTERVIEWSERVER", &MR_EnterViewServer }, @@ -837,6 +857,12 @@ struct menu_drawer_s const MENU_DRAWERS[] = { { NULL, NULL } }; +struct odds_func_s const USEODDS_FUNCS[] = { + { "ALTINVINODDS", &KO_AltInvinOdds }, + { "SPBRACEODDS", &KO_SPBRaceOdds }, + { NULL, NULL } +}; + struct int_const_s const INT_CONST[] = { // If a mod removes some variables here, // please leave the names in-tact and just set @@ -1512,21 +1538,6 @@ struct int_const_s const INT_CONST[] = { {"BASEVIDWIDTH", BASEVIDWIDTH}, {"BASEVIDHEIGHT", BASEVIDHEIGHT}, - // SRB2Kart - // kartitems_t -#define FOREACH( name, n ) { TOSTR (KITEM_ ## name), KITEM_ ## name } - KART_ITEM_ITERATOR, // Actual items (can be set for k_itemtype) -#undef FOREACH - {"NUMKARTITEMS",NUMKARTITEMS}, - {"KRITEM_DUALSNEAKER",KRITEM_DUALSNEAKER}, // Additional roulette IDs (not usable for much in Lua besides K_GetItemPatch) - {"KRITEM_TRIPLESNEAKER",KRITEM_TRIPLESNEAKER}, - {"KRITEM_TRIPLEBANANA",KRITEM_TRIPLEBANANA}, - {"KRITEM_TENFOLDBANANA",KRITEM_TENFOLDBANANA}, - {"KRITEM_TRIPLEORBINAUT",KRITEM_TRIPLEORBINAUT}, - {"KRITEM_QUADORBINAUT",KRITEM_QUADORBINAUT}, - {"KRITEM_DUALJAWZ",KRITEM_DUALJAWZ}, - {"NUMKARTRESULTS",NUMKARTRESULTS}, - // kartshields_t {"KSHIELD_NONE",KSHIELD_NONE}, {"KSHIELD_THUNDER",KSHIELD_THUNDER}, @@ -1535,22 +1546,34 @@ struct int_const_s const INT_CONST[] = { {"KSHIELD_FLAME",KSHIELD_FLAME}, {"NUMKARTSHIELDS",NUMKARTSHIELDS}, - // kartitems_t - {"KITEM_LIGHTNINGSHIELD",KITEM_THUNDERSHIELD}, - - // kartroulette_t - {"KROULETTE_DISABLED",KROULETTE_DISABLED}, - {"KROULETTE_ACTIVE",KROULETTE_ACTIVE}, - - // kartroulettetype_t + // kartroulettetype_e {"KROULETTETYPE_NORMAL",KROULETTETYPE_NORMAL}, {"KROULETTETYPE_KARMA",KROULETTETYPE_KARMA}, {"KROULETTETYPE_EGGMAN",KROULETTETYPE_EGGMAN}, - // kartitemblinkmode_t - {"KITEMBLINKMODE_NORMAL",KITEMBLINKMODE_NORMAL}, - {"KITEMBLINKMODE_MASHED",KITEMBLINKMODE_MASHED}, - {"KITEMBLINKMODE_KARMA",KITEMBLINKMODE_KARMA}, + // kartitemblink_e + {"KITEMBLINK_NORMAL",KITEMBLINK_NORMAL}, + {"KITEMBLINK_MASHED",KITEMBLINK_MASHED}, + {"KITEMBLINK_KARMA",KITEMBLINK_KARMA}, + {"KITEMBLINK_DEBUG",KITEMBLINK_DEBUG}, + + // kartitemflags_e + {"KIF_UNIQUESLOT",KIF_UNIQUESLOT}, + {"KIF_UNIQUEDROP",KIF_UNIQUEDROP}, + {"KIF_UNIQUE",KIF_UNIQUE}, + {"KIF_ANIMATED",KIF_ANIMATED}, + {"KIF_DARKBG",KIF_DARKBG}, + {"KIF_COLPATCH2PLAYER",KIF_COLPATCH2PLAYER}, + {"KIF_HIDEFROMROULETTE",KIF_HIDEFROMROULETTE}, + + // kartresultflags_e + {"KRF_INDIRECTITEM",KRF_INDIRECTITEM}, + {"KRF_POWERITEM",KRF_POWERITEM}, + {"KRF_COOLDOWNONSTART",KRF_COOLDOWNONSTART}, + {"KRF_NOTNEAREND",KRF_NOTNEAREND}, + {"KRF_NOTFORBOTTOM",KRF_NOTFORBOTTOM}, + {"KRF_RUNNERAUGMENT",KRF_RUNNERAUGMENT}, + {"KRF_HIDEFROMSPB",KRF_HIDEFROMSPB}, // kartspinoutflags_t {"KSPIN_THRUST",KSPIN_THRUST}, @@ -1684,10 +1707,6 @@ struct int_const_s const INT_CONST[] = { {"FACE_MINIMAP", FACE_MINIMAP}, {"NUMFACES", NUMFACES}, - // invintype - {"KARTINVIN_LEGACY", KARTINVIN_LEGACY}, - {"KARTINVIN_ALTERN", KARTINVIN_ALTERN}, - // k_waypoint.h values {"DEFAULT_WAYPOINT_RADIUS",DEFAULT_WAYPOINT_RADIUS}, diff --git a/src/deh_tables.h b/src/deh_tables.h index 2366001cb..5aba111c0 100644 --- a/src/deh_tables.h +++ b/src/deh_tables.h @@ -19,6 +19,7 @@ #include "lua_script.h" #include "strbuf.h" #include "m_menu.h" // menutype_t +#include "k_items.h" #ifdef __cplusplus extern "C" { @@ -30,17 +31,20 @@ extern strbuf_t *statenames; extern strbuf_t *mobjnames; extern strbuf_t *skincolornames; extern strbuf_t *menunames; +extern strbuf_t *kartitemnames; extern UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite freeslot in use! I would use ceil() here if I could, but it only saves 1 byte of memory anyway. const char *DEH_MobjtypeName(mobjtype_t i); const char *DEH_StateName(statenum_t i); const char *DEH_SkincolorName(skincolornum_t i); const char *DEH_MenutypeName(menutype_t i); +const char *DEH_KartItemName(kartitemtype_e i); mobjtype_t DEH_FindMobjtype(const char *word); statenum_t DEH_FindState(const char *word); skincolornum_t DEH_FindSkincolor(const char *word); menutype_t DEH_FindMenutype(const char *word); +kartitemtype_e DEH_FindKartItem(const char *word); struct flickytypes_s { const char *name; @@ -67,6 +71,11 @@ struct menu_drawer_s { menudrawer_f *drawer; }; +struct odds_func_s { + const char *name; + useoddsfunc_f *func; +}; + struct int_const_s { const char *n; // has to be able to hold both fixed_t and angle_t, so drastic measure!! @@ -97,6 +106,7 @@ extern const char *const KARTHUD_LIST[]; extern const char *const HUDITEMS_LIST[]; extern struct menu_routine_s const MENU_ROUTINES[]; extern struct menu_drawer_s const MENU_DRAWERS[]; +extern struct odds_func_s const USEODDS_FUNCS[]; extern struct int_const_s const INT_CONST[]; diff --git a/src/dehacked.c b/src/dehacked.c index ae6b46781..dd0fd670e 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -376,6 +376,22 @@ INT32 DEH_ReadFreeslot(const char *type, const char *word, INT32 *out) *out = nummenutypes++; return 0; } + else if (fastcmp(type, "KITEM")) + { + *out = DEH_FindKartItem(word); + if (*out != MAXKARTITEMS) + return 0; // already allocated + + if (numkartitems == MAXKARTITEMS) { + CONS_Alert(CONS_WARNING, "Ran out of free kartitem slots!\n"); + return -1; + } + CONS_Printf("Kartitem KITEM_%s allocated.\n",word); + DEH_Link(word, &kartitems[numkartitems].info, &kartitemnames); + K_RegisterItem(numkartitems); + *out = numkartitems++; + return 0; + } return -2; } @@ -750,6 +766,27 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) ignorelines(f); } } + else if (fastcmp(word, "KARTITEM")) + { + if (i == 0 && word2[0] != '0') // If word2 isn't a number + i = get_kartitem(word2); // find a kartitem by name + if (i >= (mainfile ? 0 : 1) && i < numkartitems) + readkartitem(f, &kartitems[i]); + else + { + // zero-based, but let's start at 1 + deh_warning("KartItem number %d out of range (1 - %d)", i, numkartitems-1); + ignorelines(f); + } + } + else if (fastncmp(word, "KARTRESULT", 10)) + { + boolean alternate = fastcmp(word, "KARTRESULTALT"); + kartresult_t *result = K_GetKartResultAlt(word2, alternate); + if (result == NULL) + result = K_RegisterResult(word2, alternate); + readkartresult(f, result); + } else if (fastcmp(word, "WEATHER") || fastcmp(word, "PRECIP") || fastcmp(word, "PRECIPITATION")) { if (i == 0 && word2[0] != '0') // If word2 isn't a number diff --git a/src/doomstat.h b/src/doomstat.h index 6f6fe218d..752350643 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -665,7 +665,6 @@ extern INT32 cheats; // SRB2kart extern UINT8 numlaps; extern UINT8 gamespeed; -extern UINT8 invintype; extern boolean franticitems; extern boolean encoremode, prevencoremode; extern boolean comeback; @@ -673,8 +672,6 @@ extern boolean comeback; extern SINT8 mostwanted; extern SINT8 battlewanted[4]; extern tic_t wantedcalcdelay; -extern tic_t indirectitemcooldown; -//extern tic_t hyubgone; extern tic_t mapreset; extern boolean thwompsactive; extern UINT8 lastLowestLap; diff --git a/src/f_dscredits.cpp b/src/f_dscredits.cpp index c079d2785..a62ce9d72 100644 --- a/src/f_dscredits.cpp +++ b/src/f_dscredits.cpp @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 2025 by Vivian "toastergrl" Grannell. // Copyright (C) 2025 by Kart Krew. -// Copyright (C) 2025 by "Anonimus". +// Copyright (C) 2025 by "yama". // Copyright (C) 2025 Blankart Team. // // This program is free software distributed under the diff --git a/src/f_dscredits.hpp b/src/f_dscredits.hpp index 562fbeccc..d2d55c000 100644 --- a/src/f_dscredits.hpp +++ b/src/f_dscredits.hpp @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 2025 by Vivian "toastergrl" Grannell. // Copyright (C) 2025 by Kart Krew. -// Copyright (C) 2025 by "Anonimus". +// Copyright (C) 2025 by "yama". // Copyright (C) 2025 Blankart Team. // // This program is free software distributed under the diff --git a/src/f_finale.c b/src/f_finale.c index 6fddafb38..fb673c7aa 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -890,7 +890,7 @@ const char *blancredits[] = { "\"GenericHeroGuy\"", "\"Alug\"", "\"Indev\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "\"minenice\"", "", "\1Support Programming", @@ -899,7 +899,7 @@ const char *blancredits[] = { "", "\1Item Programming", "\"NepDisk\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "", "\1External Programming", "\"Hanicef\"", @@ -922,7 +922,7 @@ const char *blancredits[] = { "", "\1Item Design", "\"NepDisk\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "\"GenericHeroGuy\"", "", "\1Design Support", @@ -937,7 +937,7 @@ const char *blancredits[] = { "\"Jin\"", "\"NepDisk\"", "\"Mayo\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "", "\1New HUD Art", "\"Spee\"", @@ -966,7 +966,7 @@ const char *blancredits[] = { "\1Super Special SIGSEGV Search and Smash Squad", "\"NepDisk\"", "\"GenericHeroGuy\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "\"MotoBugger\"", "\"Rim Jobless\"", "\"Toad The Fungus\"", @@ -982,7 +982,7 @@ const char *blancredits[] = { "\"JonUD\"", "\"MotoBugger\"", "\"Toad The Fungus\"", - "\"Anonimus\" aka \"yama\"", + "\"yama\"", "\"luna\"", "\"swift\"", "\"Rim Jobless\"", diff --git a/src/g_game.c b/src/g_game.c index 1bdb719a6..efc653668 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -61,7 +61,7 @@ #include "k_boss.h" #include "k_specialstage.h" #include "k_bot.h" -#include "k_odds.h" +#include "k_items.h" #include "doomstat.h" #include "acs/interface.h" #include "k_director.h" @@ -295,7 +295,6 @@ INT32 cheats; //for multiplayer cheat commands // Cvars that we don't want changed mid-game UINT8 numlaps; // Removed from Cvar hell UINT8 gamespeed; // Game's current speed (or difficulty, or cc, or etc); 0 for easy, 1 for normal, 2 for hard -UINT8 invintype; // How Invincibility functions. 0 for Legacy/Vanilla, 1 for Alternative. boolean encoremode = false; // Encore Mode currently enabled? boolean prevencoremode; boolean franticitems; // Frantic items currently enabled? @@ -310,8 +309,6 @@ SINT8 pickedvote; // What vote the host rolls SINT8 battlewanted[4]; // WANTED players in battle, worth x2 points SINT8 mostwanted; // The "most wanted" (first in line) player. 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 -// hyubgone was taken out back. See k_odds.c 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 @@ -442,11 +439,13 @@ consvar_t cv_showfreeplay = CVAR_INIT ("showfreeplay", "On", CV_SAVE, CV_OnOff, static CV_PossibleValue_t powermusic_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "SFX"}, {0, NULL}}; consvar_t cv_growmusic = CVAR_INIT ("growmusic", "On", CV_SAVE, powermusic_cons_t, NULL); consvar_t cv_supermusic = CVAR_INIT ("supermusic", "On", CV_SAVE, powermusic_cons_t, NULL); +consvar_t cv_altshrinkmusic = CVAR_INIT ("altshrinkmusic", "On", CV_SAVE, powermusic_cons_t, NULL); consvar_t cv_invertmouse = CVAR_INIT ("invertmouse", "Off", CV_SAVE, CV_OnOff, NULL); consvar_t cv_invincmusicfade = CVAR_INIT ("invincmusicfade", "300", CV_SAVE, CV_Unsigned, NULL); consvar_t cv_growmusicfade = CVAR_INIT ("growmusicfade", "500", CV_SAVE, CV_Unsigned, NULL); +consvar_t cv_altshrinkmusicfade = CVAR_INIT ("altshrinkmusicfade", "500", CV_SAVE, CV_Unsigned, NULL); consvar_t cv_resetspecialmusic = CVAR_INIT ("resetspecialmusic", "Yes", CV_SAVE, CV_YesNo, NULL); @@ -2807,8 +2806,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) // SRB2kart if (betweenmaps || leveltime <= starttime || spectator == true) { - itemroulette = KROULETTE_DISABLED; - previtemroulette = KROULETTE_DISABLED; + itemroulette = 0; + previtemroulette = 0; roulettetype = KROULETTETYPE_NORMAL; itemtype = 0; itemamount = 0; @@ -2848,8 +2847,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) } else { - itemroulette = (players[player].itemroulette > KROULETTE_DISABLED ? KROULETTE_ACTIVE : KROULETTE_DISABLED); - previtemroulette = (players[player].previtemroulette > KROULETTE_DISABLED ? KROULETTE_ACTIVE : KROULETTE_DISABLED); + itemroulette = players[player].itemroulette > 0 ? 1 : 0; + previtemroulette = players[player].previtemroulette > 0 ? 1 : 0; roulettetype = players[player].roulettetype; if (players[player].itemflags & IF_ITEMOUT) @@ -2864,7 +2863,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) } // Keep Shrink status, remove Grow status - if (players[player].growshrinktimer < 0) + // Alt. Shrink is a powerup, so don't keep that. + if (players[player].growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK)) growshrinktimer = players[player].growshrinktimer; else growshrinktimer = 0; diff --git a/src/g_game.h b/src/g_game.h index da46b853c..f61668e9a 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -71,7 +71,7 @@ extern consvar_t cv_chatwidth, cv_chatnotifications, cv_chatheight, cv_chattime, extern consvar_t cv_shoutname, cv_shoutcolor, cv_autoshout; extern consvar_t cv_songcredits; extern consvar_t cv_showfreeplay; -extern consvar_t cv_growmusic, cv_supermusic; +extern consvar_t cv_growmusic, cv_supermusic, cv_altshrinkmusic; extern consvar_t cv_pauseifunfocused; @@ -88,6 +88,7 @@ extern consvar_t cv_ghost_besttime, cv_ghost_bestlap, cv_ghost_last, cv_ghost_gu extern consvar_t cv_invincmusicfade; extern consvar_t cv_growmusicfade; +extern consvar_t cv_altshrinkmusicfade; extern consvar_t cv_resetspecialmusic; diff --git a/src/h_timers.cpp b/src/h_timers.cpp index 599f2f499..492edc9ee 100644 --- a/src/h_timers.cpp +++ b/src/h_timers.cpp @@ -336,6 +336,16 @@ void K_DisplayItemTimers(void) true, } ); + timers.push_back( + { // item cooldown + "itemusecooldown", + (INT32)stplyr->itemusecooldown, + //{qche("K_ISFLMS")}, + {qche("SERVLOCK")}, + 1, /*-15, -15,*/ -4, -2, + false, false + } + ); /*timers.push_back( { // dead diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c index 8ce3e07b6..76536350d 100644 --- a/src/hardware/hw_cache.c +++ b/src/hardware/hw_cache.c @@ -977,11 +977,20 @@ void HWR_PrecacheLevel(void) // Precache flats. HWR_PrecacheLevelFlats(); + // prevent timeouts + NetKeepAlive(); + // Precache textures. HWR_PrecacheLevelTextures(); + // prevent timeouts + NetKeepAlive(); + // Precache sprites. //HWR_PrecacheLevelSprites(); + + // prevent timeouts + //NetKeepAlive(); } void HWR_LoadMapTextures(size_t pnumtextures) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 1def2ef49..adf9cc13f 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -50,6 +50,7 @@ #include "../k_kart.h" #include "../r_fps.h" #include "../r_plane.h" // R_FlatDimensionsFromLumpSize +#include "../k_items.h" #define R_FAKEFLOORS #define HWPRECIP @@ -295,7 +296,7 @@ void HWR_Lighting(FSurfaceInfo *Surface, INT32 light_level, extracolormap_t *col } // Clamp the light level, since it can sometimes go out of the 0-255 range from animations - light_level = min(max(light_level, cv_glsecbright.value), 255); + light_level = min(max(light_level, cv_secbright.value), 255); V_CubeApply(&tint_color.s.red, &tint_color.s.green, &tint_color.s.blue); V_CubeApply(&fade_color.s.red, &fade_color.s.green, &fade_color.s.blue); @@ -4789,6 +4790,13 @@ static void HWR_ProjectSprite(mobj_t *thing) sprdef = &((skin_t *)thing->skin)->sprites[thing->sprite2]; #ifdef ROTSPRITE sprinfo = &((skin_t *)thing->skin)->sprinfo[thing->sprite2]; +#endif + } + else if (thing->sprite == SPR_ITEM) + { + sprdef = &kartitems[thing->threshold > 0 && thing->threshold < numkartitems ? thing->threshold : 0].spritedef; +#ifdef ROTSPRITE + sprinfo = &spriteinfo[SPR_UNKN]; #endif } else @@ -4905,9 +4913,9 @@ static void HWR_ProjectSprite(mobj_t *thing) const fixed_t visoffymul = (vflip ? -FRACUNIT : FRACUNIT); - if (R_ThingIsUsingBakedOffsets(thing)) + if (R_ThingIsUsingBakedOffsets(interptarg)) { - R_RotateSpriteOffsetsByPitchRoll(thing, + R_RotateSpriteOffsetsByPitchRoll(interptarg, vflip, hflip, &interp, diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h index 52581545f..d73e161c9 100644 --- a/src/hardware/hw_main.h +++ b/src/hardware/hw_main.h @@ -97,7 +97,6 @@ extern consvar_t cv_glcoronasize; #endif extern consvar_t cv_glshaders, cv_glallowshaders; -extern consvar_t cv_glsecbright; extern consvar_t cv_glmodels; // SRB2Kart: We don't like these options. diff --git a/src/info.c b/src/info.c index 9cef1eef2..28513667c 100644 --- a/src/info.c +++ b/src/info.c @@ -20,6 +20,7 @@ #include "d_main.h" #include "m_menu.h" #include "deh_tables.h" +#include "k_items.h" // Hey, moron! If you wanna change this table, you can just change the sprite enum in info/sprites.h, // so you don't have to copy and paste the list of sprite names back in here :^) @@ -116,6 +117,12 @@ static const char *hardcodemenus = #undef _ ; +static const char *hardcodeitems = +#define _(name, ...) #name "\0" +#include "info/kartitems.h" +#undef _ +; + void P_ResetData(INT32 flags) { INT32 i; @@ -233,4 +240,22 @@ void P_ResetData(INT32 flags) for (i = 0; i < MN_FIRSTFREESLOT; i++, name += strlen(name)+1) DEH_Link(name, &menudefs[i].info, &menunames); } + + // kartitems + if (init) + { + memset(kartitems, 0, sizeof(kartitems)); + + numkartitems = KITEM_FIRSTFREESLOT; + + if (kartitemnames) + Z_Free(kartitemnames); + kartitemnames = strbuf_alloc(); + name = hardcodeitems; + for (i = 0; i < KITEM_FIRSTFREESLOT; i++, name += strlen(name)+1) + { + DEH_Link(name, &kartitems[i].info, &kartitemnames); + K_RegisterItem(i); + } + } } diff --git a/src/info/kartitems.h b/src/info/kartitems.h new file mode 100644 index 000000000..6bff304ca --- /dev/null +++ b/src/info/kartitems.h @@ -0,0 +1,21 @@ +_(NONE) +_(SNEAKER) +_(ROCKETSNEAKER) +_(INVINCIBILITY) +_(BANANA) +_(EGGMAN) +_(ORBINAUT) +_(JAWZ) +_(MINE) +_(BALLHOG) +_(SPB) +_(GROW) +_(SHRINK) +_(THUNDERSHIELD) +_(HYUDORO) +_(POGOSPRING) +_(KITCHENSINK) +_(SUPERRING) +_(LANDMINE) +_(BUBBLESHIELD) +_(FLAMESHIELD) diff --git a/src/info/mobjs.h b/src/info/mobjs.h index 4d1f31325..448199356 100644 --- a/src/info/mobjs.h +++ b/src/info/mobjs.h @@ -602,7 +602,11 @@ _(SMOLDERING) // New explosion _(BOOMEXPLODE) _(BOOMPARTICLE) -_(LANDMINE) // Land Mine +// Land Mine +_(LANDMINE) +// Egg Mine +// _(EGGMINE_CAPSULE) +// _(EGGMINE_SHIELD) _(BALLHOG) // Ballhog _(BALLHOGBOOM) @@ -614,7 +618,7 @@ _(THUNDERSHIELD) // Shields _(BUBBLESHIELD) _(FLAMESHIELD) _(BUBBLESHIELDTRAP) -_(BUBBLESHLD_DEBRIS) +_(BUBBLESHIELD_DEBRIS) _(SINK) // Kitchen Sink Stuff _(SINK_SHIELD) diff --git a/src/info/sounds.h b/src/info/sounds.h index 8a692c256..2df90115b 100644 --- a/src/info/sounds.h +++ b/src/info/sounds.h @@ -41,8 +41,6 @@ _(pstop) _(steam1) _(steam2) _(wbreak) -_(ambmac) -_(spsmsh) _(rainin) _(litng1) @@ -114,7 +112,6 @@ _(dmpain) _(drown) _(fizzle) _(gbeep) -_(wepfir) _(ghit) _(gloop) _(gspray) @@ -126,19 +123,12 @@ _(lose) _(lvpass) _(mindig) _(mixup) -_(monton) _(pogo) _(pop) _(rail1) _(rail2) _(rlaunc) _(shield) -_(wirlsg) -_(forcsg) -_(frcssg) -_(elemsg) -_(armasg) -_(attrsg) _(shldls) _(spdpad) _(spkdth) @@ -154,24 +144,8 @@ _(trfire) _(trpowr) _(turhit) _(wdjump) -_(shrpsp) -_(shrpgo) _(mswarp) _(mspogo) -_(boingf) -_(corkp) -_(corkh) -_(alart) -_(vwre) -_(bowl) -_(chuchu) -//sfx_bsnipe, -_(sprong) -_(lvfal1) -_(pscree) -_(iceb) -_(shattr) -_(antiri) // Menu, interface _(chchng) @@ -185,16 +159,12 @@ _(radio) _(wepchg) _(wtrdng) _(zelda) -_(adderr) -_(notadd) -_(addfil) // NiGHTS _(ideya) _(xideya) // Xmas _(nbmper) _(nxbump) // Xmas -_(ncchip) _(ncitem) _(nxitem) // Xmas _(ngdone) @@ -210,14 +180,6 @@ _(hoop3) _(hidden) _(prloop) _(timeup) -_(ngjump) -_(peww) - -// Halloween -_(lntsit) -_(lntdie) -_(pumpkn) -_(ghosty) // Mario _(koopfr) @@ -400,7 +362,6 @@ _(s26f) _(s270) // S3&K sounds -_(s3k2b) _(s3k33) _(s3k34) _(s3k35) @@ -522,21 +483,6 @@ _(s3ka8) _(s3ka9) _(s3kaa) _(s3kab) -_(s3kab1) -_(s3kab2) -_(s3kab3) -_(s3kab4) -_(s3kab5) -_(s3kab6) -_(s3kab7) -_(s3kab8) -_(s3kab9) -_(s3kaba) -_(s3kabb) -_(s3kabc) -_(s3kabd) -_(s3kabe) -_(s3kabf) _(s3kac) _(s3kad) _(s3kae) @@ -845,18 +791,6 @@ _(itfree) _(dbgsal) _(bhurry) // mine: wasn't here already? -// Chaining Sound -_(bstchn) - -// Low Ring Sound -_(ringlw) - -// Flip Over Sound -_(flipos) - -// Shout message sound effect -_(sysmsg) - // Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy... // Engine class A - Low Speed, Low Weight _(krta00) @@ -997,3 +931,82 @@ _(kbost2) _(kslow) _(khitem) _(kgloat) + +// Relocated 2.2 sounds to fix magic numbers in v1 maps +_(ambmac) +_(spsmsh) + +_(wepfir) + +_(monton) + +_(wirlsg) +_(forcsg) +_(frcssg) +_(elemsg) +_(armasg) +_(attrsg) + +_(shrpsp) +_(shrpgo) + +_(boingf) +_(corkp) +_(corkh) +_(alart) +_(vwre) +_(bowl) +_(chuchu) +_(sprong) +_(lvfal1) +_(pscree) +_(iceb) +_(shattr) +_(antiri) + +_(adderr) +_(notadd) +_(addfil) + +_(ncchip) + +_(ngjump) +_(peww) + +// Halloween +_(lntsit) +_(lntdie) +_(pumpkn) +_(ghosty) + +_(s3k2b) + +_(s3kab1) +_(s3kab2) +_(s3kab3) +_(s3kab4) +_(s3kab5) +_(s3kab6) +_(s3kab7) +_(s3kab8) +_(s3kab9) +_(s3kaba) +_(s3kabb) +_(s3kabc) +_(s3kabd) +_(s3kabe) +_(s3kabf) + +// New Blan sounds + +// Chaining Sound +_(bstchn) + +// Low Ring Sound +_(ringlw) + +// Flip Over Sound +_(flipos) + +// Shout message sound effect +_(sysmsg) diff --git a/src/info/sprites.h b/src/info/sprites.h index 89595c2a4..3f87570ef 100644 --- a/src/info/sprites.h +++ b/src/info/sprites.h @@ -493,9 +493,6 @@ _(ARRO) // player arrows _(ITEM) // base item _(ITMO) // Multi-Orbinaut _(ITMI) // Invincibility -_(ITSN) // Multi-Sneaker -_(ITBA) // Multi-Banana -_(ITJA) // Multi-Jawz _(ITMN) _(WANT) diff --git a/src/info/states.h b/src/info/states.h index 0423ca11b..21102a637 100644 --- a/src/info/states.h +++ b/src/info/states.h @@ -2816,6 +2816,11 @@ _(SLOWBOOM10) _(LANDMINE) _(LANDMINE_EXPLODE) +// Egg mine +// _(EGGMINE) +// _(EGGMINE_POP) +// _(EGGMINE_SHIELD) + // Ballhog _(BALLHOG1) _(BALLHOG2) diff --git a/src/k_bot.cpp b/src/k_bot.cpp index 2a4d02491..75fa915f2 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -44,6 +44,7 @@ #include "blan/b_soc.h" #include "v_video.h" // for debugging #include "k_waypoint.h" +#include "k_items.h" consvar_t cv_forcebots = CVAR_INIT ("kartforcebots", "Off", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); consvar_t cv_botcontrol = CVAR_INIT ("kartbotcontrol", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL); diff --git a/src/k_botitem.cpp b/src/k_botitem.cpp index 4ff9197cd..21b9ca3d1 100644 --- a/src/k_botitem.cpp +++ b/src/k_botitem.cpp @@ -29,7 +29,7 @@ #include "m_random.h" #include "r_things.h" // numskins #include "m_easing.h" -#include "k_odds.h" +#include "k_items.h" #include "k_waypoint.h" // Looks for players around the bot, and presses the item button @@ -305,6 +305,14 @@ static void K_ItemConfirmForTarget(botdata_t *bd, const player_t *bot, const pla } } +/// @brief checks if given bot isn't prevented from using items due to item cooldowns +/// @param bot bot to check cooldowns for +/// @return if bot is able to use item-related actions (using items or mashing the roulette) +static boolean K_CheckBotItemCooldown(const player_t *bot) +{ + return bot->itemusecooldown <= 0; +} + // Presses the item button & aim buttons for the bot. // dir - Aiming direction: 1 for forwards, -1 for backwards, 0 for neutral. @@ -958,7 +966,8 @@ static void K_BotItemFlame(botdata_t *bd, const player_t *player) { ZoneScoped; - if (player->flametimer >= (itemtime*3)-5) + // if we're smart enough, allow overheating the flame shield if we want to go off-road + if (player->flametimer >= (itemtime*3)-5 && !(K_ApplyOffroad(player) && player->botvars.difficulty >= 6)) { bd->itemdelay = 5; } @@ -1155,7 +1164,7 @@ static void K_BotItemRouletteMash(botdata_t *bd, const player_t *player) } } - if (player->rings < 0 && cv_superring.value && K_RingsActive()) + if (player->rings < 0 && K_ItemResultEnabled(K_GetKartResult("superring"))) { // Uh oh, we need a loan! // It'll be better in the long run for bots to lose an item set for 5-15 free rings. @@ -1201,7 +1210,7 @@ void K_BotItemUsage(botdata_t *bd, const player_t *player) } return; } - + if (K_CanHandleItemDelay(bd, player)) { bd->itemdelay--; @@ -1209,6 +1218,8 @@ void K_BotItemUsage(botdata_t *bd, const player_t *player) return; } + if (!K_CheckBotItemCooldown(player)) return; + if (player->itemroulette) { // Mashing behaviors diff --git a/src/k_botsearch.cpp b/src/k_botsearch.cpp index 1597596c4..0d44462d1 100644 --- a/src/k_botsearch.cpp +++ b/src/k_botsearch.cpp @@ -33,6 +33,7 @@ #include "p_slopes.h" // P_GetZAt #include "m_perfstats.h" #include "k_objects.h" +#include "k_items.h" #include "blan/b_soc.h" diff --git a/src/k_collide.c b/src/k_collide.c index 44769c1d5..bc92a4ce8 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -7,7 +7,7 @@ #include "doomtype.h" #include "p_mobj.h" #include "k_kart.h" -#include "k_odds.h" +#include "k_items.h" #include "p_local.h" #include "s_sound.h" #include "r_main.h" // R_PointToAngle2, R_PointToDist2 @@ -256,10 +256,7 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2) } else { - K_DropItems(t2->player); //K_StripItems(t2->player); - //K_StripOther(t2->player); - t2->player->itemroulette = KROULETTE_ACTIVE; - t2->player->roulettetype = KROULETTETYPE_EGGMAN; + K_StartRoulette(t2->player, KROULETTETYPE_EGGMAN); } if (t2->player->flamestore && (K_GetShieldFromPlayer(t2->player) == KSHIELD_FLAME)) @@ -515,6 +512,12 @@ static inline BlockItReturn_t PIT_ThunderShieldAttack(mobj_t *thing) } #endif + if (thing->type == MT_SPB) // If you destroy a SPB, you don't get the luxury of a cooldown. + { + spbplace = -1; + K_SetIndirectItemCooldown(0); + } + P_DamageMobj(thing, lightningSource, lightningSource, 1, DMG_VOLTAGE|DMG_CANTHURTSELF); return BMIT_CONTINUE; } @@ -573,12 +576,13 @@ static void K_BubbleShieldCollideDrain(player_t *player, mobj_t *bubble, INT16 d static boolean K_BubbleReflectingTrapItem(const mobj_t *t) { return t->type == MT_BANANA || (t->type == MT_ORBINAUT && t->flags2 & MF2_AMBUSH) || t->type == MT_JAWZ_DUD || - t->type == MT_EGGMANITEM || t->type == MT_SSMINE || t->type == MT_SSMINE_SHIELD || t->type == MT_LANDMINE; + t->type == MT_EGGMANITEM || t->type == MT_SSMINE || t->type == MT_SSMINE_SHIELD || t->type == MT_LANDMINE;// || + // t->type == MT_EGGMINE_CAPSULE || t->type == MT_EGGMINE_SHIELD; } static boolean K_StrongPlayerBump(const player_t *player) { - return (((K_GetKartInvinType() == KARTINVIN_LEGACY) && (player->invincibilitytimer)) + return (((!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (player->invincibilitytimer)) || (player->growshrinktimer > 0)); } @@ -636,7 +640,7 @@ boolean K_BubbleShieldCanReflect(mobj_t *t) { return (t->type == MT_ORBINAUT || t->type == MT_JAWZ || t->type == MT_JAWZ_DUD || t->type == MT_BANANA || t->type == MT_EGGMANITEM || t->type == MT_BALLHOG - || t->type == MT_SSMINE || t->type == MT_LANDMINE || t->type == MT_SINK + || t->type == MT_SSMINE || t->type == MT_LANDMINE || t->type == MT_SINK //|| t->type == MT_EGGMINE_CAPSULE || t->type == MT_KART_LEFTOVER || t->type == MT_PLAYER); } @@ -656,6 +660,7 @@ static INT16 K_GetBubbleDamage(mobj_t* itm) { case MT_EGGMANITEM: case MT_BALLHOG: + // case MT_EGGMINE_CAPSULE: // Experiment: Let Bubble Shields hard-counter Ballhogs and Eggboxes. dmg = 0; break; @@ -820,7 +825,7 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) boolean t2Condition = false; // Rim suggestion: Flipover damage is negligible at best, just cull it from Invincibility as a whole. - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) { t1Condition = (t1->player->invincibilitytimer > 0); t2Condition = (t2->player->invincibilitytimer > 0); @@ -832,7 +837,7 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) K_DoInstashield(t2->player); return false; } - else if (K_GetKartInvinType() == KARTINVIN_LEGACY) + else if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) { if (t1Condition == true && t2Condition == false) { @@ -857,19 +862,40 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) { if (G_CompatLevel(0x0008) || !hyudoroT1) { - switch (cv_kartairsquish.value) + if ((t2->player) && (K_IsAltShrunk(t2->player)) && + (!(t1->player && t1->player->growshrinktimer > 0))) { - case 1: - P_DamageMobj(t2, t1, t1, 1, DMG_SQUISH); - return true; - break; - case 2: - P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER); - return true; - break; - default: - return false; - break; + // Alt. Shrink: Just make the player flip over. + if (P_DamageMobj(t2, NULL, NULL, 1, DMG_FLIPOVER)) + { + K_AltShrinkPityIncrease(t2->player); + t2->player->altshrinktimeshit++; + + P_InstaThrust(t2, K_MomentumAngle(t2), K_Momentum2D(t2) / 3); + + K_PlayPainSound(t2, NULL); + } + } + if (P_IsObjectOnGround(t1) && P_IsObjectOnGround(t2)) + { + P_DamageMobj(t2, t1, t1, 1, DMG_SQUISH); + } + else + { + switch (cv_kartairsquish.value) + { + case 1: + P_DamageMobj(t2, t1, t1, 1, DMG_SQUISH); + return true; + break; + case 2: + P_DamageMobj(t2, t1, t1, 1, DMG_FLIPOVER); + return true; + break; + default: + return false; + break; + } } } } @@ -878,20 +904,42 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) if (G_CompatLevel(0x0008) || !hyudoroT2) { // shitty code duplication 🥲 - switch (cv_kartairsquish.value) + + if ((t1->player) && (K_IsAltShrunk(t1->player)) && + (!(t2->player && t2->player->growshrinktimer > 0))) { - case 1: - P_DamageMobj(t1, t2, t2, 1, DMG_SQUISH); - return true; - break; - case 2: - P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER); - return true; - break; - default: - return false; - break; + // Alt. Shrink: Just make the player flip over. + if (P_DamageMobj(t1, NULL, NULL, 1, DMG_FLIPOVER)) + { + K_AltShrinkPityIncrease(t1->player); + t1->player->altshrinktimeshit++; + + P_InstaThrust(t1, K_MomentumAngle(t1), K_Momentum2D(t1) / 3); + + K_PlayPainSound(t1, NULL); + } } + else if (P_IsObjectOnGround(t1) && P_IsObjectOnGround(t2)) + { + P_DamageMobj(t1, t2, t2, 1, DMG_SQUISH); + } + else + { + switch (cv_kartairsquish.value) + { + case 1: + P_DamageMobj(t1, t2, t2, 1, DMG_SQUISH); + return true; + break; + case 2: + P_DamageMobj(t1, t2, t2, 1, DMG_FLIPOVER); + return true; + break; + default: + return false; + break; + } + } } return true; } diff --git a/src/k_hud.c b/src/k_hud.c index 4f46dcb4f..0b2a8fdf5 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -17,7 +17,7 @@ #include "k_boss.h" #include "k_color.h" #include "k_director.h" -#include "k_odds.h" +#include "k_items.h" #include "p_mobj.h" #include "screen.h" #include "doomtype.h" @@ -181,33 +181,12 @@ static patch_t *kp_wantedreticle; static patch_t *kp_minimapdot; static patch_t *kp_itembg[8]; +static patch_t *kp_itemalt[4]; static patch_t *kp_itemtimer[2]; static patch_t *kp_itemmulsticker[4]; static patch_t *kp_itemx; -static patch_t *kp_sadface[2]; -static patch_t *kp_sneaker[4]; -static patch_t *kp_rocketsneaker[2]; -static patch_t *kp_invincibility[13]; -static patch_t *kp_banana[5]; -static patch_t *kp_eggman[2]; -static patch_t *kp_orbinaut[5]; -static patch_t *kp_jawz[3]; -static patch_t *kp_mine[2]; -static patch_t *kp_ballhog[2]; -static patch_t *kp_selfpropelledbomb[2]; -static patch_t *kp_grow[2]; -static patch_t *kp_shrink[2]; -static patch_t *kp_thundershield[2]; -static patch_t *kp_hyudoro[2]; -static patch_t *kp_pogospring[2]; -static patch_t *kp_kitchensink[2]; -static patch_t *kp_superring[2]; -static patch_t *kp_landmine[2]; -static patch_t *kp_bubbleshield[2]; -static patch_t *kp_flameshield[2]; - -static patch_t *kp_check[6]; +static patch_t *kp_check[11]; static patch_t *kp_eggnum[4]; @@ -540,95 +519,54 @@ void K_LoadKartHUDGraphics(void) // Kart Item Windows HU_UpdatePatch(&kp_itembg[0], "K_ITBG"); HU_UpdatePatch(&kp_itembg[1], "K_ITBGD"); - HU_UpdatePatch(&kp_itembg[4], "K_ITBC"); - HU_UpdatePatch(&kp_itembg[5], "K_ITBCD"); - HU_UpdatePatch(&kp_itemtimer[0], "K_ITIMER"); - HU_UpdatePatch(&kp_itemmulsticker[0], "K_ITMUL"); - HU_UpdatePatch(&kp_itemmulsticker[2], "K_ITMULC"); - HU_UpdatePatch(&kp_itemx, "K_ITX"); - - HU_UpdatePatch(&kp_sadface[0], "K_ITSAD"); - HU_UpdatePatch(&kp_sneaker[0], "K_ITSHOE"); - HU_UpdatePatch(&kp_sneaker[1], "K_ITSHO2"); - HU_UpdatePatch(&kp_sneaker[2], "K_ITSHO3"); - HU_UpdatePatch(&kp_rocketsneaker[0], "K_ITRSHE"); - - sprintf(buffer, "K_ITINVx"); - for (i = 0; i < 7; i++) - { - buffer[7] = '1'+i; - HU_UpdatePatch(&kp_invincibility[i], "%s", buffer); - } - HU_UpdatePatch(&kp_banana[0], "K_ITBANA"); - HU_UpdatePatch(&kp_banana[1], "K_ITBAN2"); - HU_UpdatePatch(&kp_banana[2], "K_ITBAN3"); - HU_UpdatePatch(&kp_banana[3], "K_ITBAN4"); - HU_UpdatePatch(&kp_eggman[0], "K_ITEGGM"); - sprintf(buffer, "K_ITORBx"); - for (i = 0; i < 4; i++) - { - buffer[7] = '1'+i; - HU_UpdatePatch(&kp_orbinaut[i], "%s", buffer); - } - HU_UpdatePatch(&kp_jawz[0], "K_ITJAWZ"); - HU_UpdatePatch(&kp_jawz[1], "K_ITJAW2"); - HU_UpdatePatch(&kp_mine[0], "K_ITMINE"); - HU_UpdatePatch(&kp_ballhog[0], "K_ITBHOG"); - HU_UpdatePatch(&kp_selfpropelledbomb[0], "K_ITSPB"); - HU_UpdatePatch(&kp_grow[0], "K_ITGROW"); - HU_UpdatePatch(&kp_shrink[0], "K_ITSHRK"); - HU_UpdatePatch(&kp_thundershield[0], "K_ITTHNS"); - HU_UpdatePatch(&kp_hyudoro[0], "K_ITHYUD"); - HU_UpdatePatch(&kp_pogospring[0], "K_ITPOGO"); - HU_UpdatePatch(&kp_kitchensink[0], "K_ITSINK"); - HU_UpdatePatch(&kp_superring[0], "K_ITRING"); - HU_UpdatePatch(&kp_landmine[0], "K_ITLNDM"); - HU_UpdatePatch(&kp_bubbleshield[0], "K_ITBUBS"); - HU_UpdatePatch(&kp_flameshield[0], "K_ITFLMS"); - - // Splitscreen HU_UpdatePatch(&kp_itembg[2], "K_ISBG"); HU_UpdatePatch(&kp_itembg[3], "K_ISBGD"); + HU_UpdatePatch(&kp_itembg[4], "K_ITBC"); + HU_UpdatePatch(&kp_itembg[5], "K_ITBCD"); HU_UpdatePatch(&kp_itembg[6], "K_ISBC"); HU_UpdatePatch(&kp_itembg[7], "K_ISBCD"); + HU_UpdatePatch(&kp_itemalt[0], "K_ALTITM"); + HU_UpdatePatch(&kp_itemalt[1], "K_ALTITS"); + HU_UpdatePatch(&kp_itemalt[2], "K_ALTIMM"); + HU_UpdatePatch(&kp_itemalt[3], "K_ALTISM"); + HU_UpdatePatch(&kp_itemtimer[0], "K_ITIMER"); HU_UpdatePatch(&kp_itemtimer[1], "K_ISIMER"); + HU_UpdatePatch(&kp_itemmulsticker[0], "K_ITMUL"); HU_UpdatePatch(&kp_itemmulsticker[1], "K_ISMUL"); + HU_UpdatePatch(&kp_itemmulsticker[2], "K_ITMULC"); HU_UpdatePatch(&kp_itemmulsticker[3], "K_ISMULC"); + HU_UpdatePatch(&kp_itemx, "K_ITX"); - HU_UpdatePatch(&kp_sadface[1], "K_ISSAD"); - HU_UpdatePatch(&kp_sneaker[3], "K_ISSHOE"); - HU_UpdatePatch(&kp_rocketsneaker[1], "K_ISRSHE"); - sprintf(buffer, "K_ISINVx"); - for (i = 0; i < 6; i++) + for (i = 0; i < numkartitems; i++) { - buffer[7] = '1'+i; - HU_UpdatePatch(&kp_invincibility[i+7], "%s", buffer); + kartitem_t *item = &kartitems[i]; + for (j = 0; j < 2; j++) + { + kartitemgraphics_t *graphics = &item->graphics[j]; + for (INT32 k = 0; k < graphics->numpatches; k++) + { + if (graphics->patches[k] == NULL) + { + // have to manually do this because HU_UpdatePatch only checks graphics from added WADs during partadd + // UUUUUGGGGGGGHHHHHHHHHHHHHHHHHHh + lumpnum_t lump = W_CheckNumForName(graphics->patchnames[k]); + graphics->patches[k] = lump == LUMPERROR ? missingpat : W_CachePatchNum(lump, PU_HUDGFX); + } + else + HU_UpdatePatch(&graphics->patches[k], graphics->patchnames[k]); + } + } + R_AddKartItemSprites(item); } - HU_UpdatePatch(&kp_banana[4], "K_ISBANA"); - HU_UpdatePatch(&kp_eggman[1], "K_ISEGGM"); - HU_UpdatePatch(&kp_orbinaut[4], "K_ISORBN"); - HU_UpdatePatch(&kp_jawz[2], "K_ISJAWZ"); - HU_UpdatePatch(&kp_mine[1], "K_ISMINE"); - HU_UpdatePatch(&kp_ballhog[1], "K_ISBHOG"); - HU_UpdatePatch(&kp_selfpropelledbomb[1], "K_ISSPB"); - HU_UpdatePatch(&kp_grow[1], "K_ISGROW"); - HU_UpdatePatch(&kp_shrink[1], "K_ISSHRK"); - HU_UpdatePatch(&kp_thundershield[1], "K_ISTHNS"); - HU_UpdatePatch(&kp_hyudoro[1], "K_ISHYUD"); - HU_UpdatePatch(&kp_pogospring[1], "K_ISPOGO"); - HU_UpdatePatch(&kp_kitchensink[1], "K_ISSINK"); - HU_UpdatePatch(&kp_superring[1], "K_ISRING"); - HU_UpdatePatch(&kp_landmine[1], "K_ISLNDM"); - HU_UpdatePatch(&kp_bubbleshield[1], "K_ISBUBS"); - HU_UpdatePatch(&kp_flameshield[1], "K_ISFLMS"); // CHECK indicators sprintf(buffer, "K_CHECKx"); - for (i = 0; i < 6; i++) + for (i = 0; i < 10; i++) { buffer[7] = '1'+i; HU_UpdatePatch(&kp_check[i], "%s", buffer); } + HU_UpdatePatch(&kp_check[10], "K_CHECKA"); // Eggman warning numbers sprintf(buffer, "K_EGGNx"); @@ -723,103 +661,6 @@ void K_LoadKartHUDGraphics(void) } } -// For the item toggle menu -const char *K_GetItemPatch(UINT8 item, boolean tiny) -{ - switch (item) - { - case KITEM_SNEAKER: - return (tiny ? "K_ISSHOE" : "K_ITSHOE"); - case KITEM_ROCKETSNEAKER: - return (tiny ? "K_ISRSHE" : "K_ITRSHE"); - case KITEM_INVINCIBILITY: - return (tiny ? "K_ISINV1" : "K_ITINV1"); - case KITEM_BANANA: - return (tiny ? "K_ISBANA" : "K_ITBANA"); - case KITEM_EGGMAN: - return (tiny ? "K_ISEGGM" : "K_ITEGGM"); - case KITEM_ORBINAUT: - return (tiny ? "K_ISORBN" : "K_ITORB1"); - case KITEM_JAWZ: - return (tiny ? "K_ISJAWZ" : "K_ITJAWZ"); - case KITEM_MINE: - return (tiny ? "K_ISMINE" : "K_ITMINE"); - case KITEM_BALLHOG: - return (tiny ? "K_ISBHOG" : "K_ITBHOG"); - case KITEM_SPB: - return (tiny ? "K_ISSPB" : "K_ITSPB"); - case KITEM_GROW: - return (tiny ? "K_ISGROW" : "K_ITGROW"); - case KITEM_SHRINK: - return (tiny ? "K_ISSHRK" : "K_ITSHRK"); - case KITEM_THUNDERSHIELD: - return (tiny ? "K_ISTHNS" : "K_ITTHNS"); - case KITEM_HYUDORO: - return (tiny ? "K_ISHYUD" : "K_ITHYUD"); - case KITEM_POGOSPRING: - return (tiny ? "K_ISPOGO" : "K_ITPOGO"); - case KITEM_KITCHENSINK: - return (tiny ? "K_ISSINK" : "K_ITSINK"); - case KITEM_SUPERRING: - return (tiny ? "K_ISRING" : "K_ITRING"); - case KITEM_LANDMINE: - return (tiny ? "K_ISLNDM" : "K_ITLNDM"); - case KITEM_BUBBLESHIELD: - return (tiny ? "K_ISBUBS" : "K_ITBUBS"); - case KITEM_FLAMESHIELD: - return (tiny ? "K_ISFLMS" : "K_ITFLMS"); - case KRITEM_DUALSNEAKER: - return (tiny ? "K_ISSHOE" : "K_ITSHO2"); - case KRITEM_TRIPLESNEAKER: - return (tiny ? "K_ISSHOE" : "K_ITSHO3"); - case KRITEM_TRIPLEORBINAUT: - return (tiny ? "K_ISORBN" : "K_ITORB3"); - case KRITEM_DUALJAWZ: - return (tiny ? "K_ISJAWZ" : "K_ITJAW2"); - case KRITEM_TRIPLEBANANA: - return (tiny ? "K_ISBANA" : "K_ITBAN3"); - case KRITEM_TENFOLDBANANA: - return (tiny ? "K_ISBANA" : "K_ITBAN4"); - case KRITEM_QUADORBINAUT: - return (tiny ? "K_ISORBN" : "K_ITORB4"); - default: - return (tiny ? "K_ISSAD" : "K_ITSAD"); - } -} - -static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset) -{ - patch_t **kp[1 + NUMKARTITEMS] = { - kp_sadface, - NULL, - kp_sneaker, - kp_rocketsneaker, - kp_invincibility, - kp_banana, - kp_eggman, - kp_orbinaut, - kp_jawz, - kp_mine, - kp_ballhog, - kp_selfpropelledbomb, - kp_grow, - kp_shrink, - kp_thundershield, - kp_hyudoro, - kp_pogospring, - kp_kitchensink, - kp_superring, - kp_landmine, - kp_bubbleshield, - kp_flameshield, - }; - - if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS)) - return kp[item - KITEM_SAD][offset]; - else - return NULL; -} - //} INT32 ITEM_X, ITEM_Y; // Item Window @@ -890,6 +731,12 @@ patch_t *K_getItemMulPatch(boolean small) return K_UseColorHud() ? kp_itemmulsticker[2+ofs] : kp_itemmulsticker[ofs]; } +patch_t *K_getItemAltPatch(boolean small, boolean multimode) +{ + UINT8 ofs = (small ? 1 : 0) + (multimode ? 2 : 0); + return kp_itemalt[ofs]; +} + // This version of the function was prototyped in Lua by Nev3r ... a HUGE thank you goes out to them! void K_ObjectTracking(trackingResult_t *result, const vector3_t *point, boolean reverse) { @@ -1322,6 +1169,32 @@ static void K_DrawItemBar(INT32 fx, INT32 fy, INT32 fflags, fixed_t itembar, UIN V_DrawFixedFill(x+2*FRACUNIT, y+FRACUNIT, max(0, length - 2*FRACUNIT), FRACUNIT, colors[0]|fflags); // the shine } +// TODO: use an actual patch overlay and clip it instead of using a rect, now that an actual patch can be added for this +static void K_DrawItemCooldown(INT32 fx, INT32 fy, INT32 fflags, tic_t timer, tic_t maxtimer) +{ + const boolean fourp = r_splitscreen > 1; + const INT32 rectTopFour = 13*FRACUNIT; + INT32 rectTop = 10*FRACUNIT; + INT32 rectSize = 40*FRACUNIT; + fixed_t prog = FixedDiv(timer, maxtimer); + INT32 length = min(rectSize, FixedMul(rectSize, prog)); + if (timer > 0 && maxtimer > 0) + { + if (fourp) + { + rectTop = 14*FRACUNIT; + rectSize = 20*FRACUNIT; + length = min(rectSize, FixedMul(rectSize, prog)); + + V_DrawFixedFill(fx + rectTop, fy + rectTopFour + (rectSize - length), rectSize, length, 2|fflags); + } + else + { + V_DrawFixedFill(fx + rectTop, fy + rectTop + (rectSize - length), rectSize, length, 2|fflags); + } + } +} + // see also MT_PLAYERARROW mobjthinker in p_mobj.c static void K_drawKartItem(void) { @@ -1330,19 +1203,18 @@ static void K_drawKartItem(void) // Why write V_DrawScaledPatch calls over and over when they're all the same? // Set to 'no item' just in case. - const UINT8 offset = ((r_splitscreen > 1) ? 1 : 0); + const boolean tiny = r_splitscreen > 1; patch_t *localpatch = kp_nodraw; patch_t *localbg; - patch_t *localinv = ((offset) ? kp_invincibility[((leveltime % (6*3)) / 3) + 7] : kp_invincibility[(leveltime % (7*3)) / 3]); boolean dark = false; INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags... - INT32 numberdisplaymin = 2; fixed_t itembar = -1, flamebar = -1; UINT16 localcolor = SKINCOLOR_NONE; SINT8 colormode = TC_RAINBOW; UINT8 *colmap = NULL; UINT8 *colormap = NULL; boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff + boolean isalt = false; if (stplyr->itemroulette) { @@ -1351,26 +1223,7 @@ static void K_drawKartItem(void) if (K_GetHudColor()) localcolor = K_GetHudColor(); - switch (item) - { - case KITEM_SNEAKER: - localpatch = kp_sneaker[offset ? 3: 0]; - break; - case KITEM_BANANA: - localpatch = kp_banana[offset ? 4: 0]; - break; - case KITEM_ORBINAUT: - localpatch = kp_orbinaut[3+offset]; - break; - case KITEM_JAWZ: - localpatch = kp_jawz[offset ? 2: 0]; - break; - case KITEM_INVINCIBILITY: - localpatch = localinv; - break; - default: - localpatch = K_GetCachedItemPatch(item, offset); - } + localpatch = K_GetCachedItemPatch(item, tiny, 0); } else { @@ -1381,18 +1234,18 @@ static void K_drawKartItem(void) if (stplyr->stolentimer > 0) { if (leveltime & 2) - localpatch = kp_hyudoro[offset]; + localpatch = K_GetCachedItemPatch(KITEM_HYUDORO, tiny, 0); else localpatch = kp_nodraw; } else if ((stplyr->stealingtimer > 0) && (leveltime & 2)) { - localpatch = kp_hyudoro[offset]; + localpatch = K_GetCachedItemPatch(KITEM_HYUDORO, tiny, 0); } else if (stplyr->eggmanexplode > 1) { if (leveltime & 1) - localpatch = kp_eggman[offset]; + localpatch = K_GetCachedItemPatch(KITEM_EGGMAN, tiny, 0); else localpatch = kp_nodraw; } @@ -1401,7 +1254,7 @@ static void K_drawKartItem(void) itembar = FixedDiv(stplyr->rocketsneakertimer, itemtime*3); if (leveltime & 1) - localpatch = kp_rocketsneaker[offset]; + localpatch = K_GetCachedItemPatch(KITEM_ROCKETSNEAKER, tiny, 0); else localpatch = kp_nodraw; } @@ -1409,7 +1262,6 @@ static void K_drawKartItem(void) { itembar = FixedDiv(stplyr->flametimer, itemtime*3); flamebar = FixedDiv(stplyr->flamestore, FLAMESTOREMAX); - localbg = kp_itembg[offset+1]; dark = true; if ((stplyr->flamestore >= FLAMESTOREMAX-1) && (leveltime & 1)) @@ -1419,7 +1271,7 @@ static void K_drawKartItem(void) } if (leveltime & 1) - localpatch = kp_flameshield[offset]; + localpatch = K_GetCachedItemPatch(KITEM_FLAMESHIELD, tiny, 0); else localpatch = kp_nodraw; } @@ -1429,11 +1281,11 @@ static void K_drawKartItem(void) itembar = FixedDiv(stplyr->growcancel, 26); if (leveltime & 1) - localpatch = kp_grow[offset]; + localpatch = K_GetCachedItemPatch(KITEM_GROW, tiny, 0); else localpatch = kp_nodraw; } - else if ((stplyr->invincibilitytimer) && (K_GetKartInvinType() == KARTINVIN_ALTERN)) + else if ((stplyr->invincibilitytimer) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY))) { itembar = FixedDiv(stplyr->invincibilitytimer, max(1, stplyr->maxinvincibilitytime)); @@ -1441,13 +1293,16 @@ static void K_drawKartItem(void) flamebar = FixedDiv(stplyr->invincibilitycancel, 26); if (leveltime & 1) - localpatch = localinv; + { + localpatch = K_GetCachedItemPatch(KITEM_INVINCIBILITY, tiny, 0); + isalt = true; + } else localpatch = kp_nodraw; } else if (K_GetShieldFromPlayer(stplyr) == KSHIELD_BUBBLE) { - localpatch = kp_bubbleshield[offset]; + localpatch = K_GetCachedItemPatch(KITEM_BUBBLESHIELD, tiny, 0); dark = true; if (stplyr->bubbleblowup > 0 && leveltime & 1) @@ -1461,53 +1316,22 @@ static void K_drawKartItem(void) else if (stplyr->sadtimer > 0) { if (leveltime & 2) - localpatch = kp_sadface[offset]; + localpatch = K_GetCachedItemPatch(MAXKARTITEMS, tiny, 0); else localpatch = kp_nodraw; } else { - if (stplyr->itemamount <= 0) + if (stplyr->itemamount <= 0 && stplyr->itemusecooldown <= 0) return; - switch(stplyr->itemtype) - { - case KITEM_SNEAKER: - localpatch = kp_sneaker[(offset ? 3 : min(stplyr->itemamount-1, 2))]; - numberdisplaymin = 4; - numberdisplaymin = offset ? 2 : 4; - break; - case KITEM_INVINCIBILITY: - localpatch = localinv; - localbg = kp_itembg[offset+1]; - break; - case KITEM_BANANA: - localpatch = kp_banana[(offset ? 4 : min(stplyr->itemamount-1, 3))]; - numberdisplaymin = offset ? 2 : 5; - break; - case KITEM_ORBINAUT: - localpatch = kp_orbinaut[(offset ? 4 : min(stplyr->itemamount-1, 3))]; - numberdisplaymin = offset ? 2 : 5; - break; - case KITEM_JAWZ: - localpatch = kp_jawz[(offset ? 2 : min(stplyr->itemamount-1, 1))]; - numberdisplaymin = 3; - numberdisplaymin = offset ? 2 : 3; - break; - case KITEM_SPB: - case KITEM_THUNDERSHIELD: - case KITEM_BUBBLESHIELD: - case KITEM_FLAMESHIELD: - dark = true; - /*FALLTHRU*/ + localpatch = K_GetCachedItemPatch(stplyr->itemtype, tiny, stplyr->itemamount); + if (localpatch == NULL) + localpatch = kp_nodraw; // diagnose underflows + else if (K_IsKartItemAlternate(stplyr->itemtype)) + isalt = true; - default: - localpatch = K_GetCachedItemPatch(stplyr->itemtype, offset); - - if (localpatch == NULL) - localpatch = kp_nodraw; // diagnose underflows - break; - } + dark = K_GetItemFlags(stplyr->itemtype) & KIF_DARKBG; if ((stplyr->itemflags & IF_ITEMOUT) && !(leveltime & 1)) localpatch = kp_nodraw; @@ -1519,10 +1343,11 @@ static void K_drawKartItem(void) switch (stplyr->itemblinkmode) { - case 2: + case KITEMBLINK_DEBUG: + case KITEMBLINK_KARMA: localcolor = K_RainbowColor(leveltime); break; - case 1: + case KITEMBLINK_MASHED: localcolor = SKINCOLOR_RED; break; default: @@ -1532,7 +1357,7 @@ static void K_drawKartItem(void) } } - localbg = K_getItemBoxPatch((boolean)offset, dark); + localbg = K_getItemBoxPatch(tiny, dark); drawinfo_t info; K_getItemBoxDrawinfo(&info); fx = info.x; @@ -1540,7 +1365,14 @@ static void K_drawKartItem(void) fflags = info.flags; flipamount = info.flipamount; if (localcolor != SKINCOLOR_NONE) + { colmap = R_GetTranslationColormap(colormode, localcolor, GTC_CACHE); + } + else if (K_GetItemFlags(stplyr->itemtype) & KIF_COLPATCH2PLAYER) + { + colmap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); + } + if (K_UseColorHud()) colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE); @@ -1551,10 +1383,11 @@ static void K_drawKartItem(void) fixed_t rfy = fy<itemroulette && !stplyr->deadtimer) { fixed_t frac = R_GetTimeFrac(RTF_LEVEL); - UINT8 fancystep = (offset ? 6 : 10); + UINT8 fancystep = tiny ? 6 : 10; fixed_t fancyoffset = (stplyr->itemroulette % 3)-1; if (fancyoffset != 0) @@ -1567,12 +1400,21 @@ static void K_drawKartItem(void) } // Then, the numbers: - if (stplyr->itemamount >= numberdisplaymin && !stplyr->itemroulette) + if (stplyr->itemamount >= K_GetItemNumberDisplayMin(stplyr->itemtype, tiny) && !stplyr->itemroulette) { - localbg = K_getItemMulPatch((boolean)offset); + localbg = K_getItemMulPatch(tiny); V_DrawMappedPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), localbg, colormap); // flip this graphic for p2 and p4 in split and shift it. V_DrawFixedPatch(fx<itemusecooldown, stplyr->itemusecooldownmax); + + if (isalt) + { + V_DrawFixedPatch(fx<itemamount)); else @@ -1584,7 +1426,16 @@ static void K_drawKartItem(void) } } else + { V_DrawFixedPatch(fx<itemusecooldown, stplyr->itemusecooldownmax); + + // TODO: Some proper check for if an alt has unique graphics + if (isalt) + V_DrawFixedPatch(fx<eggmanexplode > 1) - V_DrawScaledPatch(fx+17, fy+13-offset, V_HUDTRANS|fflags, kp_eggnum[min(3, G_TicsToSeconds(stplyr->eggmanexplode))]); + V_DrawScaledPatch(fx+17, fy+13-(tiny ? 1 : 0), V_HUDTRANS|fflags, kp_eggnum[min(3, G_TicsToSeconds(stplyr->eggmanexplode))]); } void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode) @@ -2102,7 +1953,7 @@ static boolean K_drawKartPositionFaces(void) V_DrawMappedPatch(FACE_X + offsets.x, Y + offsets.y, V_HUDTRANS|V_SNAPTOLEFT, facerank, colormap); - if ((players[rankplayer[i]].invincibilitytimer) && (K_GetKartInvinType() == KARTINVIN_ALTERN)) + if ((players[rankplayer[i]].invincibilitytimer) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY))) { colormap = R_GetTranslationColormap(TC_BLINK, K_AltInvincibilityColor(leveltime / 2), GTC_CACHE); invinchudtrans = K_InvincibilityHUDVisibility(players[rankplayer[i]].invincibilitytimer); @@ -2488,7 +2339,7 @@ static void K_DrawServerMods(INT32 x, INT32 y) {"Bump Drift", 0, NULL, K_GetBumpSpark() > 0, true}, {"Bump Spark", 0, NULL, K_GetBumpSpark() > BUMPSPARK_NOCHARGE, true}, {"Bump Spring", 0, &cv_kartbumpspring, -1, true}, - {"Alt. Invin.", 0, NULL, K_GetKartInvinType() == KARTINVIN_ALTERN, true} + {"Alt. Invin.", 0, NULL, K_IsKartItemAlternate(KITEM_INVINCIBILITY), true} }; for (j = 0; j < 2; j++) @@ -3414,7 +3265,15 @@ static void K_drawKartPlayerCheck(void) pnum++; // white frames } - if (checkplayer->itemtype == KITEM_GROW || checkplayer->growshrinktimer > 0) + if (checkplayer->itemtype == KITEM_FLAMESHIELD || checkplayer->flametimer > 0) + { + pnum += 8; + } + else if (K_IsAltShrunk(checkplayer) && (checkplayer->itemtype == KITEM_SHRINK || checkplayer->growshrinktimer < 0)) + { + pnum += 6; + } + else if (checkplayer->itemtype == KITEM_GROW || checkplayer->growshrinktimer > 0) { pnum += 4; } @@ -4335,9 +4194,9 @@ static void K_drawKartMinimap(void) invingradient = K_InvincibilityGradient(players[i].invincibilitytimer); - if ((players[i].invincibilitytimer) && ((invingradient > (FRACUNIT/2)) || (K_GetKartInvinType() == KARTINVIN_LEGACY))) + if ((players[i].invincibilitytimer) && ((invingradient > (FRACUNIT/2)) || (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)))) { - usecolor = ((K_GetKartInvinType() == KARTINVIN_ALTERN) ? K_AltInvincibilityColor(leveltime / 2) : K_RainbowColor(leveltime / 2)); + usecolor = ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) ? K_AltInvincibilityColor(leveltime / 2) : K_RainbowColor(leveltime / 2)); colorizeplayer = true; } else @@ -4370,7 +4229,7 @@ static void K_drawKartMinimap(void) K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, &iconoffsets); #ifdef ROTSPRITE - if ((K_GetKartInvinType() == KARTINVIN_ALTERN) && + if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (players[i].invincibilitytimer) && (invingradient)) { // Draw Alt. Invin. sparkles @@ -4665,7 +4524,7 @@ static void K_drawKartMinimap(void) if ((players[localplayers[i]].invincibilitytimer) && ((invingradient > (FRACUNIT / 2)) || - (K_GetKartInvinType() == KARTINVIN_LEGACY))) + (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)))) { usecolor = (K_RainbowColor(leveltime / 2)); colorizeplayer = true; @@ -4735,7 +4594,7 @@ static void K_drawKartMinimap(void) K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, &iconoffsets); #ifdef ROTSPRITE - if ((!nocontest) && (K_GetKartInvinType() == KARTINVIN_ALTERN) && + if ((!nocontest) && (K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (players[localplayers[i]].invincibilitytimer) && (invingradient)) { // Draw Alt. Invin. sparkles @@ -5758,48 +5617,12 @@ K_drawMiniPing (void) } } -static patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item) -{ - UINT8 offset; - - item = K_ItemResultToType(item); - - switch (item) - { - case KITEM_INVINCIBILITY: - offset = 7; - break; - - case KITEM_BANANA: - offset = 4; - break; - - case KITEM_ORBINAUT: - offset = 4; - break; - - case KITEM_JAWZ: - offset = 2; - break; - - case KITEM_SNEAKER: - offset = 3; - break; - - default: - offset = 1; - } - - return K_GetCachedItemPatch(item, offset); -} - static void K_drawDistributionDebugger(void) { UINT8 useodds = 0; UINT8 pingame = 0, bestbumper = 0; UINT32 pdis = 0; INT32 i; - INT32 item; INT32 x = -9, y = -9; //boolean dontforcespb = false; boolean spbrush = false; @@ -5825,41 +5648,43 @@ static void K_drawDistributionDebugger(void) if (pingame == 1) { - if (stplyr->itemroulette && (stplyr->cmd.buttons & BT_ATTACK) && cv_superring.value && (K_RingsActive() == true)) - V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetSmallStaticCachedItemPatch(KITEM_SUPERRING)); + if (stplyr->itemroulette && stplyr->cmd.buttons & BT_ATTACK && K_ItemResultEnabled(K_GetKartResult("superring"))) + V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(KITEM_SUPERRING, true, 0)); else - V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetSmallStaticCachedItemPatch(KITEM_SNEAKER)); + V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(KITEM_SNEAKER, true, 0)); V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, va("%d", 200)); } else { - for (item = 1; item < NUMKARTRESULTS; item++) + INT32 itemodds[MAXKARTRESULTS]; + kartroulette_t roulette = { + .pdis = pdis, + .playerpos = stplyr->position, + .pos = useodds, + .ourDist = stplyr->distancetofinish, + .clusterDist = stplyr->distancefromcluster, + .mashed = 0, + .spbrush = spbrush, + .bot = stplyr->bot, + .rival = stplyr->bot && stplyr->botvars.rival, + .inBottom = K_IsPlayerLosing(stplyr), + }; + + K_KartGetItemOdds(&roulette, itemodds); + + for (i = 0; i < numkartresults; i++) { - INT32 itemodds; - INT32 amount; + kartresult_t *result = &kartresults[i]; - itemodds = K_KartGetItemOdds( - useodds, item, - stplyr->distancetofinish, - stplyr->distancefromcluster, - 0, - spbrush, stplyr->bot, (stplyr->bot && stplyr->botvars.rival), - K_IsPlayerLosing(stplyr) - ); - - if (itemodds <= 0) + if (itemodds[i] <= 0 && roulette.forceme[i] == 0) // At the very least display forced items; that info's also important. continue; - V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetSmallStaticCachedItemPatch(item)); - V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, va("%d", itemodds)); - - // Display amount for multi-items - amount = K_ItemResultToAmount(item); - if (amount > 1) - { - V_DrawString(x+24, y+31, V_SPLITSCREEN|V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", amount)); - } - + V_DrawScaledPatch(x, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, K_GetCachedItemPatch(result->type, true, 0)); + if (result->isalt) + V_DrawScaledPatch(x+2, y, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, W_CachePatchName("K_ALTITS", PU_CACHE)); + V_DrawThinString(x+11, y+31, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, roulette.forceme[i] ? va("\x85" "FRC(%d)" "\x80 ", roulette.forceme[i]) : va("%d", itemodds[i])); + if (result->amount > 1) + V_DrawString(x+24, y+31, V_SPLITSCREEN|V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", result->amount)); x += 32; if (x >= 297) @@ -5880,6 +5705,22 @@ static void K_drawDistributionDebugger(void) if (K_LegacyOddsMode()) V_DrawSmallString(70, 0, V_SPLITSCREEN|V_HUDTRANS|V_SNAPTOTOP, "Legacy Distance Mode"); + // cooldown timer debugging + x = 240; + y = 160; + for (i = 0; i < numkartresults; i++) + { + kartresult_t *result = &kartresults[i]; + if (result->cooldown > 0 && result->isalt == K_IsKartItemAlternate(result->type)) + { + INT32 color = result->flags & KRF_INDIRECTITEM ? V_ORANGEMAP : K_GetItemFlags(result->type) & KIF_UNIQUE ? V_YELLOWMAP : 0; + V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOBOTTOM, K_GetCachedItemPatch(result->type, true, 0)); + if (result->amount > 1) + V_DrawThinString(x+30, y+30, V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOBOTTOM|V_6WIDTHSPACE, va("x%d", result->amount)); + V_DrawString(x+11, y+31, V_HUDTRANS|V_SNAPTOBOTTOM|color, va("%u", result->cooldown/TICRATE)); + x -= 32; + } + } } static void K_drawCheckpointDebugger(void) diff --git a/src/k_hud.h b/src/k_hud.h index f330d950f..362c472ba 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -89,13 +89,13 @@ typedef struct patch_t *K_getItemBoxPatch(boolean small, boolean dark); patch_t *K_getItemMulPatch(boolean small); +patch_t *K_getItemAltPatch(boolean small, boolean multimode); void K_getItemBoxDrawinfo(drawinfo_t *out); void K_getLapsDrawinfo(drawinfo_t *out); void K_getRingsDrawinfo(drawinfo_t *out); void K_getMinimapDrawinfo(drawinfo_t *out); void K_getSlipstreamDrawinfo(drawinfo_t *out); void K_getLivesnStatsDrawinfo(drawinfo_t *out); -const char *K_GetItemPatch(UINT8 item, boolean tiny); void K_ReloadHUDColorCvar(void); boolean K_UseColorHud(void); UINT8 K_GetHudColor(void); diff --git a/src/k_items.c b/src/k_items.c new file mode 100644 index 000000000..1c8319700 --- /dev/null +++ b/src/k_items.c @@ -0,0 +1,2414 @@ +// BLANKART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2025 by Kart Krew. +// Copyright (C) 2025 by "yama". +// Copyright (C) 2025 Blankart Team. +// +// 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 k_items.c +/// \brief Kart item systems. + +// SRB2kart Roulette Code - Position Based + +#include "doomdef.h" +#include "doomstat.h" +#include "d_netcmd.h" +#include "d_player.h" +#include "g_game.h" +#include "info.h" +#include "m_easing.h" // Invincibility gradienting +#include "m_fixed.h" +#include "m_random.h" +#include "p_local.h" +#include "p_mobj.h" +#include "p_setup.h" +#include "s_sound.h" +#include "typedef.h" +#include "deh_tables.h" +#include "lua_hook.h" +#include "z_zone.h" + +#include "k_battle.h" +#include "k_boss.h" +#include "k_bot.h" +#include "k_kart.h" +#include "k_waypoint.h" +#include "k_director.h" +#include "k_cluster.hpp" +#include "k_items.h" +#include "k_collide.h" + +kartitem_t kartitems[MAXKARTITEMS] = {0}; +kartresult_t kartresults[MAXKARTRESULTS] = {0}; +UINT8 numkartitems = 0; +UINT8 numkartresults = 0; +UINT8 oddstablemax[MAXODDSTABLES] = { MAXODDS, 2, 4 }; + +static CV_PossibleValue_t kartdebugitem_cons_t[MAXKARTITEMS] = {{0, "NONE"}, {0, NULL}}; +consvar_t cv_kartdebugitem = CVAR_INIT ("kartdebugitem", "NONE", CV_NETVAR|CV_CHEAT, kartdebugitem_cons_t, NULL); +static CV_PossibleValue_t kartdebugamount_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}}; +consvar_t cv_kartdebugamount = CVAR_INIT ("kartdebugamount", "1", CV_NETVAR|CV_CHEAT, kartdebugamount_cons_t, NULL); + +static CV_PossibleValue_t kartitemvariant_cons_t[] = {{0, "Legacy"}, {1, "Alternative"}, {0, NULL}}; + +#define NUMKARTODDS (MAXODDS*10) + +// Base multiplication to ALL item odds to simulate fractional precision. +// Reduced from 4 to 1 due to 75-count probability. +#define BASEODDSMUL 1 + +// Multiplication to odds for Battle item odds specifically. +#define BATTLEODDSMUL 4 + +// Maximum probability. +#define REALMAXPROB 75 +#define MAXPROBABILITY (REALMAXPROB * BASEODDSMUL) + +#define REALMAXBATTLEPROB 10 +#define MAXBATTLEPROBABILITY (REALMAXBATTLEPROB * BATTLEODDSMUL) + +void K_RegisterItem(kartitemtype_e itemtype) +{ + kartdebugitem_cons_t[itemtype].strvalue = DEH_KartItemName(itemtype); + kartdebugitem_cons_t[itemtype].value = itemtype; + kartdebugitem_cons_t[itemtype + 1].strvalue = NULL; + kartdebugitem_cons_t[itemtype + 1].value = 0; +} + +kartresult_t *K_RegisterResult(const char *name, boolean alternate) +{ + kartresult_t *result = &kartresults[numkartresults]; + + if (alternate) + { + // link to the normal result + kartresult_t *prev = K_GetKartResult(name); + if (prev == NULL) + I_Error("K_RegisterResult: Result %s doesn't exist, can't create alt result", name); + + result->cvar = prev->cvar; + result->type = prev->type; + result->amount = prev->amount; + result->isalt = true; + + // check if the kartitem needs to have its alt cvar registered + kartitem_t *item = &kartitems[result->type]; + if (item->altcvar == NULL) + { + char *cvname = malloc(strlen(DEH_KartItemName(result->type))+1 + 8); + sprintf(cvname, "altitem_%s", DEH_KartItemName(result->type)); + strlwr(cvname); + + consvar_t *var = Z_Calloc(sizeof(consvar_t), PU_STATIC, &item->altcvar); + var->name = cvname; + var->defaultvalue = "Legacy"; + var->flags = CV_NETVAR/*|CV_CALL*/; + var->PossibleValue = kartitemvariant_cons_t; + var->func = NULL; + CV_RegisterVar(var); + } + } + else + { + consvar_t *var = Z_Calloc(sizeof(consvar_t), PU_STATIC, &result->cvar); + char *cvname = Z_StrDup(name); + strlwr(cvname); + + var->name = cvname; + var->defaultvalue = "On"; + var->flags = CV_NETVAR|CV_CHEAT; + var->PossibleValue = CV_OnOff; + var->func = NULL; + CV_RegisterVar(var); + } + + numkartresults++; + return result; +} + +kartresult_t *K_GetKartResultAlt(const char *name, boolean alternate) +{ + for (UINT8 i = 0; i < numkartresults; i++) + { + if (!stricmp(name, kartresults[i].cvar->name) && kartresults[i].isalt == alternate) + return &kartresults[i]; + } + + return NULL; +} + +void K_SetupItemOdds(void) +{ + // Update the item's variant based on the cvar's value. + for (kartitemtype_e i = 0; i < numkartitems; i++) + { + kartitem_t *item = &kartitems[i]; + if (item->altcvar != NULL && item->altenabled != !!item->altcvar->value) + { + CONS_Printf("KITEM_%s: updating variant to %s\n", DEH_KartItemName(i), item->altcvar->string); + item->altenabled = !!item->altcvar->value; + } + } + + for (UINT8 i = 0; i < numkartresults; i++) + kartresults[i].cooldown = 0; +} + +boolean K_ItemResultEnabled(const kartresult_t *result) +{ + if (result == NULL) + return false; + + if (result->type == KITEM_SUPERRING && !K_RingsActive()) + return false; + + return result->cvar->value == 1; +} + +boolean K_IsKartItemAlternate(kartitemtype_e itemtype) +{ + return itemtype < numkartitems && kartitems[itemtype].altenabled; +} + +kartitemflags_e K_GetItemFlags(kartitemtype_e itemtype) +{ + if (itemtype >= numkartitems) + return 0; + + return kartitems[itemtype].flags[K_IsKartItemAlternate(itemtype) ? 1 : 0]; +} + +UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny) +{ + if (type == MAXKARTITEMS) + type = 0; // KITEM_SAD displays like normal + if (type >= numkartitems) + return 1; // actually broken items show x1 to let you know + + return K_GetItemFlags(type) & KIF_ANIMATED ? 2 : kartitems[type].graphics[tiny ? 1 : 0].numpatches + 1; +} + +patch_t *K_GetCachedItemPatch(kartitemtype_e type, boolean tiny, UINT8 amount) +{ + kartitem_t *item = &kartitems[type >= numkartresults ? 0 : type]; + kartitemgraphics_t *graphics = &item->graphics[tiny ? 1 : 0]; + + if (graphics->numpatches == 0) + return missingpat; + + tic_t timer = G_GamestateUsesLevel() ? leveltime : finalecount; + + if (K_GetItemFlags(type) & KIF_ANIMATED) + return graphics->patches[(timer % (graphics->numpatches*3)) / 3]; + else + return graphics->patches[CLAMP(amount - 1, 0, graphics->numpatches - 1)]; +} + +// sprite3 baybeeeee!!!! +// the renderer uses mobj->threshold to determine the item type +void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCount) +{ + part->sprite = SPR_ITEM; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; + + if (itemType >= numkartitems) + itemType = 0; + + UINT8 numpatches = kartitems[itemType].graphics[0].numpatches; + if (K_GetItemFlags(itemType) & KIF_ANIMATED) + part->frame |= (leveltime % (numpatches*3)) / 3; + else + part->frame |= CLAMP(itemCount - 1, 0, numpatches - 1); +} + +/** \brief Are the odds in legacy distancing mode? + + \return true if kartforcelegacyodds is enabled, or the map uses legacy WPs +*/ +boolean K_LegacyOddsMode(void) +{ + return (K_UsingLegacyCheckpoints() || (cv_kartforcelegacyodds.value)); +} + +static boolean K_InStartCooldown(void) +{ + return (leveltime < (30*TICRATE)+starttime); +} + +// Magic number distance for use with item roulette tiers +#define ACTIVEDISTVAR (K_LegacyOddsMode() ? DISTVAR_LEGACY : DISTVAR) +#define ACTIVESPBDIST (K_LegacyOddsMode() ? SPBDISTVAR_LEGACY : SPBDISTVAR) + +#define SPBSTARTDIST (ACTIVESPBDIST) // Distance when SPB can start appearing +#define SPBFORCEDIST (7 * ACTIVESPBDIST / 2) // Distance when SPB is forced onto 2nd place (3.5x SPBDISTVAR) + +#define ENDDIST (12*ACTIVEDISTVAR) // Distance when the game stops giving you bananas + +static boolean K_RaceForceSPB(SINT8 playerpos, UINT32 pdis) +{ + return ((gametyperules & GTR_CIRCUIT) && playerpos == 2 && pdis > (UINT32)SPBFORCEDIST); +} + +// 1/21/2025: I hate tiptoeing around the integer limit. +// This is at a smaller scale. +static UINT32 K_Dist2D(INT32 x1, INT32 y1, INT32 x2, INT32 y2) +{ + // d = √((x2 - x1)² + (y2 - y1)²) + INT32 xdiff, ydiff; + INT64 xprod, yprod; + + xdiff = (x2 - x1); + ydiff = (y2 - y1); + + xprod = ((INT64)xdiff * (INT64)xdiff); + yprod = ((INT64)ydiff * (INT64)ydiff); + + return (UINT32)(IntSqrt64(xprod + yprod)); +} + +// Basic integer distancing, to quote myself: +// "Even if you did 256 units for 1 fracunit in distancing, it'd be a better result than trying to +// deal with overflows on a system that's already being pushed to the limit by needing 65536 units +// for precision. No seriously, I don't think anyone's losing sleep over "hmmmmm, 0.0000152 or +// 0.0039?????" when most 2D game engines only give a fuck about MAYBE 0.001" +/*static UINT32 K_IntDistance(fixed_t curx, + fixed_t cury, + fixed_t curz, + fixed_t destx, + fixed_t desty, + fixed_t destz) +{ + return K_Dist2D(0, + curz / FRACUNIT, + K_Dist2D(curx / FRACUNIT, cury / FRACUNIT, destx / FRACUNIT, desty / FRACUNIT), + destz / FRACUNIT); +}*/ + +// This one uses map scaling instead, use in case of loss of depth on mobjscaled maps. +static UINT32 K_IntDistanceForMap(fixed_t curx, + fixed_t cury, + fixed_t curz, + fixed_t destx, + fixed_t desty, + fixed_t destz) +{ + return K_Dist2D(0, + curz / mapobjectscale, + K_Dist2D(curx / mapobjectscale, + cury / mapobjectscale, + destx / mapobjectscale, + desty / mapobjectscale), + destz / mapobjectscale); +} + +/** \brief Prevent overpowered rolls while under the effects of Alt. Shrink. + + \param item the item to check + + \return Don't double for this item? +*/ +static boolean K_DontDoubleMyItems(kartitemtype_e type, UINT8 amount) +{ + return type == KITEM_BALLHOG || type == KITEM_SPB + || type == KITEM_INVINCIBILITY || type == KITEM_GROW + || type == KITEM_BUBBLESHIELD || type == KITEM_FLAMESHIELD + || type == KITEM_ROCKETSNEAKER || type == KITEM_SHRINK + || type == KITEM_HYUDORO + || (type == KITEM_BANANA && amount >= 4) + || (type == KITEM_ORBINAUT && amount >= 3) + || (type == KITEM_SNEAKER && amount >= 3) + || (type == KITEM_JAWZ && amount >= 2) + || type >= KITEM_FIRSTFREESLOT; // TODO: excludes custom items for now +} + +static sfxenum_t resultfx[] = { + [KITEMBLINK_NORMAL] = sfx_itrolf, + [KITEMBLINK_MASHED] = sfx_itrolm, + [KITEMBLINK_KARMA] = sfx_itrolk, + [KITEMBLINK_DEBUG] = sfx_dbgsal, +}; + +// be careful with invalid item types here +void K_AwardPlayerItem(player_t *player, kartitemtype_e type, UINT8 amount, kartitemblink_e blink) +{ + K_BotResetItemConfirm(player, true); + + player->itemblink = TICRATE; + player->itemblinkmode = blink; + player->itemroulette = 0; // Since we're done, clear the roulette number + player->roulettetype = KROULETTETYPE_NORMAL; // This too + + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, resultfx[blink]); + + player->itemtype = type; + + if (K_IsAltShrunk(player) && !K_DontDoubleMyItems(type, amount) && blink != KITEMBLINK_DEBUG) + amount *= 2; + + player->itemamount = amount; +} + +static void K_AwardPlayerResult(player_t *player, kartresult_t *result, kartitemblink_e blink) +{ + kartitemtype_e type = result != NULL ? result->type : MAXKARTITEMS; + UINT8 amount = result != NULL ? result->amount : 1; + + K_AwardPlayerItem(player, type, amount, blink); + + if (result == NULL) + return; + + if (result->basecooldown > 0) + result->cooldown = result->basecooldown; +} + +static fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush) +{ + // CEP: due to how baseplayer works, 17P+ lobbies will STILL have the disastrous odds of 0.22 prior, if not WORSE + // let's try adding another condition + + const UINT8 basePlayer = 16; // The player count we design most of the game around. + const UINT8 vanillaMax = 17; // CEP: Maximum players in "vanilla" (non-30P) clients. + const UINT8 extPlayer = 24; // CEP: Cap for 17P+ so that odds don't get too muddled. + + UINT8 playerCount = (spbrush ? 2 : numPlayers); + fixed_t playerScaling = 0; + + // 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. + // 2P: x1.4 + playerScaling = (basePlayer - playerCount) * (FRACUNIT / 10); + } + else if (playerCount > basePlayer) + { + // More than basePlayer: reduce odds slightly. + + // CEP: 17P+ adjustments + if (playerCount < vanillaMax) + { + // Less than vanillaMax: Use standard calculations. + // 16P: x0.6 + playerScaling = (basePlayer - playerCount) * (FRACUNIT / 20); + + } + else if (playerCount > vanillaMax) + { + // More than vanillaMax: Increase odds to fit with the increased playercount + // 24P: x0.6 + // 30P: x0.45 + playerScaling = (basePlayer - min(extPlayer, playerCount)) * (FRACUNIT / 40); // adding a cap here to be sure + } + } + + return playerScaling; +} + +UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush) +{ + if (mapobjectscale != FRACUNIT) + { + // Bring back to normal scale. + distance = FixedDiv(distance * FRACUNIT, mapobjectscale) / FRACUNIT; + } + + if (franticitems == true) + { + // Frantic items pretends everyone's farther apart, for crazier items. + 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; + } + + return distance; +} + +#define INVODDS 30 + +// Prevent integer overflows; don't let this go past 16383 +#define INVINDESPERATION 4 +#define MAXINVODDS ((MAXPROBABILITY * 2) * INVINDESPERATION) + +// Odds value for Alt. Invin. to force itself on trailing players. +#define INVFORCEODDS (MAXINVODDS / 2) + +#define FRAC_95pct (95 * FRACUNIT / 100) + +static INT32 K_KartGetInvincibilityOdds(UINT32 dist) +{ + UINT32 invindist = INVINDIST/2; + + if (dist < invindist) + return 0; + + INT32 finodds = 0; + fixed_t fac = (min(32000, (fixed_t)dist) * FRACUNIT) / (INVINDIST); + + if (fac > FRACUNIT) + { + // Desperation! Climb exponentially until Invincibility is practically guaranteed. + fac = (((min(32000, (fixed_t)dist) * FRACUNIT) / (INVINDIST)) - FRACUNIT) >> 1; + finodds = Easing_InCubic(fac, INVODDS, MAXINVODDS); + } + else + { + if (fac <= FRAC_95pct) + { + // Invincibility is practically useless at lower distances. + // Only let it appear at or above 95%. + return 0; + } + // Basic linear climb to "reasonable" odds. + finodds = FixedMul(INVODDS, fac); + } + + return min(MAXINVODDS, finodds); +} + +#undef FRAC_95pct + +// updates all result cooldown timers, and sets cooldowns for "unique" items +void K_UpdateItemCooldown(void) +{ + INT32 i; + + // tick down the cooldown timers + for (i = 0; i < numkartresults; i++) + if (kartresults[i].cooldown > 0) + kartresults[i].cooldown--; + + // figure out which unique items need their cooldown set + boolean setcooldown[MAXKARTITEMS] = {0}; + + // start by checking each player's item slot (KIF_UNIQUESLOT) + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = &players[i]; + + if (!playeringame[i] || player->spectator || player->exiting > 0) + continue; + + if (gametyperules & GTR_BUMPERS && player->bumper == 0) + continue; + + // special case for legacy shrink + if (player->growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK)) + setcooldown[KITEM_SHRINK] = true; + + if (player->itemtype <= 0 || player->itemtype >= numkartitems || player->itemamount <= 0) + continue; + + if (K_GetItemFlags(player->itemtype) & KIF_UNIQUESLOT) + setcooldown[player->itemtype] = true; + } + + // next, check for dropped items (KIF_UNIQUEDROP) + for (thinker_t *th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) + { + mobj_t *mobj = (mobj_t *)th; + + if (P_MobjWasRemoved(mobj)) + continue; + + switch (mobj->type) + { + case MT_FLOATINGITEM: + if (mobj->threshold > 0 && mobj->threshold < numkartitems && K_GetItemFlags(mobj->threshold) & KIF_UNIQUEDROP) + setcooldown[mobj->threshold] = true; + break; + + case MT_SPB: // SPBs in the field also count + setcooldown[KITEM_SPB] = true; + break; + + default: + break; + } + } + + // now it's FINALLY time to set cooldowns! + // unique item cooldowns only apply to race odds, unless it's an indirect item + for (i = 0; i < numkartresults; i++) + { + kartresult_t *result = &kartresults[i]; + if (result->isalt == K_IsKartItemAlternate(result->type) + && (gametyperules & GTR_RACEODDS || result->flags & KRF_INDIRECTITEM) + && setcooldown[result->type]) + result->cooldown = result->basecooldown; + } + + // all indirect items share a single timer, whichever is higher + K_SetIndirectItemCooldown(K_GetIndirectItemCooldown()); +} + +tic_t K_GetIndirectItemCooldown(void) +{ + tic_t maxcooldown = 0; + for (UINT8 i = 0; i < numkartresults; i++) + { + if (kartresults[i].flags & KRF_INDIRECTITEM) + maxcooldown = max(maxcooldown, kartresults[i].cooldown); + } + return maxcooldown; +} + +void K_SetIndirectItemCooldown(tic_t cooldown) +{ + for (UINT8 i = 0; i < numkartresults; i++) + { + if (kartresults[i].flags & KRF_INDIRECTITEM) + kartresults[i].cooldown = cooldown; + } +} + +static INT32 GetItemOdds(kartroulette_t *roulette, kartresult_t *result, UINT8 *forceme) +{ + INT32 newodds; + INT32 i; + + SINT8 first = -1, second = -1; + UINT32 secondToFirst = UINT32_MAX; + + UINT8 flags = result->flags; + + if (result->cvar->value == 0 && !modeattacking) + return 0; + + if (kartitems[result->type].altenabled != result->isalt) + return 0; // wrong alt item result + + /* + if (roulette->bot) + { + // TODO: Item use on bots should all be passed-in functions. + // Instead of manually inserting these, it should return 0 + // for any items without an item use function supplied + + switch (item) + { + case KITEM_SNEAKER: + break; + default: + return 0; + } + } + */ + + INT32 oddsmul = BASEODDSMUL; + + // Item type used for actual odds retrieval and cooldown assignments. + UINT8 oddstable; + + if (gametyperules & GTR_BATTLEODDS) + oddstable = ODDS_BATTLE; + else if (gametyperules & GTR_RACEODDS) + oddstable = ODDS_RACE; + else + oddstable = ODDS_SPECIAL; + + I_Assert(roulette->pos < oddstablemax[oddstable]); // DO NOT allow positions past the bounds of the table + + if (gametyperules & GTR_BATTLEODDS) + oddsmul = BATTLEODDSMUL; + + // TODO: braaap (make a separate table for the current level!) + newodds = result->odds[oddstable][roulette->pos]; + + // Blow up the odds with a multiplier. + newodds *= oddsmul; + + INT32 shieldtype = K_GetShieldFromItem(result->type); + + roulette->pexiting = 0; + roulette->pingame = 0; + roulette->firstDist = UINT32_MAX; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!(gametyperules & GTR_BUMPERS) || players[i].bumper) + roulette->pingame++; + + if (players[i].exiting) + roulette->pexiting++; + + if (shieldtype != KSHIELD_NONE && ((shieldtype == K_GetShieldFromItem(players[i].itemtype)) + || (shieldtype == K_GetShieldFromPlayer(&players[i])))) + { + // Don't allow more than one of each shield type at a time + return 0; + } + + if (players[i].mo && gametype == GT_RACE) + { + if (players[i].position == 1 && first == -1) + first = i; + if (players[i].position == 2 && second == -1) + second = i; + } + } + + if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB + { + + if (!K_LegacyOddsMode()) + { + roulette->firstDist = players[first].distancetofinish; + + if (mapobjectscale != FRACUNIT) + { + roulette->firstDist = FixedDiv(roulette->firstDist * FRACUNIT, mapobjectscale) / FRACUNIT; + } + + secondToFirst = K_ScaleItemDistance( + players[second].distancetofinish - players[first].distancetofinish, + roulette->pingame, roulette->spbrush + ); + } + else + { + secondToFirst = P_AproxDistance(P_AproxDistance( + players[first].mo->x/4 - players[second].mo->x/4, + players[first].mo->y/4 - players[second].mo->y/4), + players[first].mo->z/4 - players[second].mo->z/4); + + // Scale it to prevent overflow issues. + secondToFirst = (secondToFirst / FRACUNIT)*2; + + secondToFirst = K_ScaleItemDistance( + secondToFirst, + roulette->pingame, roulette->spbrush + ); + } + } + + // TODO: convert into uniqueodds functions (what happened to "already shrunk"?) + if (result->type == KITEM_SHRINK) + { + if (!K_IsKartItemAlternate(KITEM_SHRINK)) + { + if (roulette->pingame-1 <= roulette->pexiting) + newodds = 0; + } + else + { + if (roulette->rival) + { + // Rival bot or already shrunk. DON'T roll another. + newodds = 0; + } + } + } + + if (result->unique_odds[oddstable]) + { + // This item has unique odds! + + //CONS_Printf("Unique odds found. Assigning uoddsfunc..."); + useoddsfunc_f *uoddsfunc = result->unique_odds[oddstable]; + + //CONS_Printf("Running..."); + newodds = uoddsfunc(newodds, roulette, result, forceme); + //CONS_Printf("OK!\n"); + } + + // In very small matches, remove the stupid bottom half item limiter + if (roulette->pingame < 6) + { + flags &= ~KRF_NOTFORBOTTOM; + } + + if (newodds == 0) + { + // Nothing else we want to do with odds matters at this point :p + return newodds; + } + + if (flags & KRF_RUNNERAUGMENT) + { + // These odds get stronger as 1st's frontrun increases. + const INT32 distFromStart = max(secondToFirst - SPBSTARTDIST, 0); + const INT32 distRange = SPBFORCEDIST - SPBSTARTDIST; + const INT32 mulMax = 24; + + INT32 multiplier = (distFromStart * mulMax) / distRange; + + if (multiplier < 0) + multiplier = 0; + if (multiplier > mulMax) + multiplier = mulMax; + + newodds *= multiplier; + } + + if (result->cooldown > 0) + { + // This item is on cooldown; don't let it get rolled. + newodds = 0; + } + else if (flags & KRF_COOLDOWNONSTART && K_InStartCooldown()) + { + // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) + newodds = 0; + } + else if (!K_LegacyOddsMode() && flags & KRF_NOTNEAREND && roulette->ourDist < (UINT32)ENDDIST) + { + // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) + newodds = 0; + } + else if (flags & KRF_NOTFORBOTTOM && roulette->inBottom && leveltime >= (30*TICRATE)+starttime) + { + // This item should not appear for losing players. (Usually items that feel less effective at these positions) + newodds = 0; + } + else if (flags & KRF_HIDEFROMSPB && (spbplace != -1)) + { + // This item doesn't appear if an SPB is chasing a player. + newodds = 0; + } + else if (flags & KRF_POWERITEM) + { + // This item is a "power item". This activates "frantic item" toggle related functionality. + fixed_t fracOdds = newodds * FRACUNIT; + + if (franticitems == true) + { + // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. + fracOdds *= 2; + } + + if (roulette->rival == true) + { + // The Rival bot gets frantic-like items, also :p + fracOdds *= 2; + } + + fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(roulette->pingame, roulette->spbrush)); + + if (roulette->mashed > 0) + { + // Lastly, it *divides* it based on your mashed value, so that power items are less likely when you mash. + fracOdds = FixedDiv(fracOdds, FRACUNIT + roulette->mashed); + } + + newodds = fracOdds / FRACUNIT; + } + + return newodds; +} + +void K_KartGetItemOdds(kartroulette_t *roulette, INT32 outodds[static MAXKARTRESULTS]) +{ + // Reset forceme + memset(roulette->forceme, 0, sizeof(roulette->forceme)); + + for (UINT8 i = 0; i < numkartresults; i++) + { + outodds[i] = GetItemOdds(roulette, &kartresults[i], &roulette->forceme[i]); + } +}; + +//{ SRB2kart Roulette Code - Distance Based, yes waypoints + +UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) +{ + fixed_t oddsfac = max(FRACUNIT, (MAXODDS * FRACUNIT) / 8); + UINT8 i, j; + UINT8 useodds = 0; + UINT8 disttable[(MAXODDS - 1) * 2]; + UINT8 distlen = 0; + boolean oddsvalid[MAXODDS]; + boolean rivalodds = false; + + // Unused now, oops :V + (void)bestbumper; + + if ((player->bot && player->botvars.rival) || K_IsAltShrunk(player)) + { + // Rival bots and players using Alt. Shrink get crazier items. + rivalodds = true; + } + + INT32 itemodds[MAXKARTRESULTS]; + kartroulette_t roulette = { + .pdis = pdis, + .playerpos = player->position, + //.pos = i, + .ourDist = player->distancetofinish, + .clusterDist = player->distancefromcluster, + .mashed = mashed, + .spbrush = spbrush, + .bot = player->bot, + .rival = rivalodds, + .inBottom = K_IsPlayerLosing(player), + }; + + for (i = 0; i < MAXODDS; i++) + { + boolean available = false; + + if ((gametyperules & GTR_BATTLEODDS) && i > 1) + { + oddsvalid[i] = false; + break; + } + + roulette.pos = i; + K_KartGetItemOdds(&roulette, itemodds); + + for (j = 0; j < numkartresults; j++) + { + if (itemodds[j] > 0) + { + available = true; + break; + } + } + + oddsvalid[i] = available; + } + +#define SETUPDISTTABLE(odds, num) \ + if (oddsvalid[odds]) \ + for (i = num; i; --i) \ + { \ + disttable[distlen++] = odds; \ + distlen = min(sizeof(disttable) - 1, distlen); \ + } + + if (gametyperules & GTR_BATTLEODDS) // Battle Mode + { + if (player->roulettetype == KROULETTETYPE_KARMA && oddsvalid[1] == true) + { + // 1 is the extreme odds of player-controlled "Karma" items + useodds = 1; + } + else + { + useodds = 0; + + if (oddsvalid[0] == false && oddsvalid[1] == true) + { + // try to use karma odds as a fallback + useodds = 1; + } + } + } + else if (gametyperules & GTR_RACEODDS) + { + + INT32 tablediv = FixedMul(2, oddsfac); + INT32 jj; + + // "Why j instead of i"? + // Using i causes an infinite loop due to SETUPDISTTABLE. Yep. + for (j = 0; j < MAXODDS; j++) + { + if (j == (MAXODDS - 1)) + { + // Attempt to replicate vanilla behavior; Useodds 8 is set up like this. + SETUPDISTTABLE(j,1); + } + else + { + jj = max(1, ((j - 3) / tablediv) + 1); + + if (jj < 1) + { + jj = 1; + } + + //CONS_Printf("SETUPDISTTABLE(%d, %d)\n", j, jj); + + SETUPDISTTABLE(j,jj); + } + } + + /*SETUPDISTTABLE(0,1); + SETUPDISTTABLE(1,1); + SETUPDISTTABLE(2,1); + SETUPDISTTABLE(3,2); + SETUPDISTTABLE(4,2); + SETUPDISTTABLE(5,3); + SETUPDISTTABLE(6,3); + SETUPDISTTABLE(7,1);*/ + + const INT32 usedistvar = FixedDiv(ACTIVEDISTVAR, oddsfac); + + if (pdis == 0) + useodds = disttable[0]; + else if (pdis > (UINT32)ACTIVEDISTVAR * ((12 * distlen) / sizeof(disttable))) + useodds = disttable[max(0, distlen-1)]; + else + { + for (i = 1; i < (sizeof(disttable) - 1); i++) + { + INT32 distcalc = min(distlen-1, (i * distlen) / sizeof(disttable)); + + if (pdis <= (UINT32)usedistvar * distcalc) + { + useodds = disttable[distcalc]; + break; + } + } + } + } + +#undef SETUPDISTTABLE + + return min(MAXODDS - 1, useodds); +} + +INT32 K_GetRollingRouletteItem(player_t *player) +{ + static UINT8 translation[MAXKARTRESULTS]; + static UINT16 roulette_size; + + static INT16 odds_cached = -1; + + // Race odds have more columns than Battle + const UINT8 EMPTYODDS[MAXODDS] = {0}; // or not + + if (odds_cached != gametype) + { + UINT8 oddstable = 0; + kartitemtype_e seen[MAXKARTITEMS]; + size_t numseen = 0, j; + + roulette_size = 0; + + if (gametyperules & GTR_BATTLEODDS) + oddstable = ODDS_BATTLE; + else + oddstable = ODDS_RACE; + + for (UINT8 i = 0; i < numkartresults; i++) + { + kartresult_t *result = &kartresults[i]; + + if (K_GetItemFlags(result->type) & KIF_HIDEFROMROULETTE) + continue; + + for (j = 0; j < numseen; j++) + if (seen[j] == result->type) + break; + + if (j == numseen && memcmp(result->odds[oddstable], EMPTYODDS, sizeof(EMPTYODDS))) + translation[roulette_size++] = seen[numseen++] = result->type; + } + + roulette_size *= 3; + odds_cached = gametype; + } + + return translation[(player->itemroulette % roulette_size) / 3]; +} + +// Legacy odds are fickle and finicky, so we exaggerate distances +// to simulate parity with pathfind odds. +#define LEGACYODDSEXAGGERATE (2*FRACUNIT/3) + +/** \brief Gets the distance between a player and a position by chaining all other players + * between them and the target position. + + \param player player object + \param startPos first position to trace to the player from + + \return (UINT32) pdis +*/ +UINT32 K_GetCongaLineDistance(const player_t *player, UINT8 startPos) +{ + SINT8 sortedPlayers[MAXPLAYERS]; + UINT8 sortLength = 0; + + UINT32 pdis = 0; + INT32 i; + + memset(sortedPlayers, -1, sizeof(sortedPlayers)); + + if (player->mo != NULL && P_MobjWasRemoved(player->mo) == false) + { + // Sort all of the players ahead of you. + // Then tally up their distances in a conga line. + + // This will create a much more consistent item + // distance algorithm than the "spider web" thing + // that it was doing before. + + // Add yourself to the list. + // You'll always be the end of the list, + // so we can also calculate the length here. + sortedPlayers[ player->position - 1 ] = player - players; + sortLength = player->position; + + // Will only need to do this if there's goint to be + // more than yourself in the list. + if (sortLength > 1) + { + SINT8 firstIndex = -1; + SINT8 secondIndex = -1; + INT32 startFrom = INT32_MAX; + + // Add all of the other players. + for (i = 0; i < MAXPLAYERS; i++) + { + INT32 pos = INT32_MAX; + + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + if (players[i].mo == NULL || P_MobjWasRemoved(players[i].mo) == true) + { + continue; + } + + pos = players[i].position; + + if ((pos <= 0) || (pos > MAXPLAYERS) || (pos < startPos)) + { + // Invalid position. + continue; + } + + if (pos >= player->position) + { + // Tied / behind us. + // Also handles ourselves, obviously. + continue; + } + + // Ties are done with port priority, if there are any. + if (sortedPlayers[ pos - 1 ] == -1) + { + sortedPlayers[ pos - 1 ] = i; + } + } + + // The chance of this list having gaps is improbable, + // but not impossible. So we need to spend some extra time + // to prevent the gaps from mattering. + for (i = 0; i < sortLength-1; i++) + { + if (sortedPlayers[i] >= 0 && sortedPlayers[i] < MAXPLAYERS) + { + // First valid index in the list found. + firstIndex = sortedPlayers[i]; + + // Start the next loop after this player. + startFrom = i + 1; + break; + } + } + + if (firstIndex >= 0 && firstIndex < MAXPLAYERS + && startFrom < sortLength) + { + // First index is valid, so we can + // start comparing the players. + + player_t *firstPlayer = NULL; + player_t *secondPlayer = NULL; + + for (i = startFrom; i < sortLength; i++) + { + if (sortedPlayers[i] >= 0 && sortedPlayers[i] < MAXPLAYERS) + { + secondIndex = sortedPlayers[i]; + + firstPlayer = &players[firstIndex]; + secondPlayer = &players[secondIndex]; + + // Add the distance to the player behind you. + // At a (relative to map) integer scale using basic distancing + // arithmetic; more accurate and less concern for overflows. + // TODO: Overflow-safe addition (caps at UINT32_MAX). + pdis += K_IntDistanceForMap( + secondPlayer->mo->x, + secondPlayer->mo->y, + secondPlayer->mo->z, + firstPlayer->mo->x, + firstPlayer->mo->y, + firstPlayer->mo->z); + + // Advance to next index. + firstIndex = secondIndex; + } + } + } + } + } + + return pdis; +} + +UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame) +{ + UINT8 i; + UINT32 pdis = 0; + + (void)pingame; + + if (!K_LegacyOddsMode()) + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator + && players[i].position == 1) + { + // This player is first! Yay! + + if (player->distancetofinish <= players[i].distancetofinish) + { + // Guess you're in first / tied for first? + pdis = 0; + } + else + { + // Subtract 1st's distance from your distance, to get your distance from 1st! + pdis = player->distancetofinish - players[i].distancetofinish; + } + break; + } + } + } + else + { + pdis = K_GetCongaLineDistance(player, 1); + + // Exaggerate odds; don't you love the legacy system? :Trollic: + pdis = FixedMul(pdis, LEGACYODDSEXAGGERATE); + } + + return pdis; +} + +#undef LEGACYODDSEXAGGERATE + +UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrush) +{ + UINT32 pdis = 0; + + pdis = K_CalculateInitalPDIS(player, numPlayers); + + if (cv_kartspbrush.value && 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, numPlayers, *spbrush); + + if (player->bot && player->botvars.rival) + { + // Rival has better odds :) + pdis = (15 * pdis) / 14; + } + + // Can almost barely overflow this calc, fudge to prevent this. + if (pdis > 30000) + pdis = 30000; + + return pdis; +} + +static boolean K_BattleForceSPB(player_t *player) +{ + boolean battlecond = ((gametyperules & GTR_WANTED) && (gametyperules & GTR_WANTEDSPB) && (mostwanted != -1) && (!K_IsPlayerMostWanted(player))); + + return battlecond; +} + +void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) +{ + INT32 i; + UINT8 pingame = 0; + UINT8 roulettestop; + UINT32 pdis = 0; + UINT8 useodds = 0; + INT32 spawnchance[MAXKARTRESULTS]; + INT64 totalspawnchance = 0; // 75-scale numbers are going to get BIG. This is for paranoia's sake. + 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) + return; + player->itemroulette++; + + // Gotta check how many players are active at this moment. + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + pingame++; + if (players[i].exiting) + dontforcespb = true; + if (players[i].bumper > bestbumper) + bestbumper = players[i].bumper; + } + + // 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)) + { +#define PLAYROULETTESND S_StartSound(NULL, sfx_itrol1 + ((player->itemroulette / 3) % 8)) + for (i = 0; i <= r_splitscreen; i++) + { + if (player == &players[displayplayers[i]] && players[displayplayers[i]].itemroulette) + PLAYROULETTESND; + } +#undef PLAYROULETTESND + } + + roulettestop = TICRATE + (3*(pingame - player->position)); + + // If the roulette finishes or the player presses BT_ATTACK, stop the roulette and calculate the item. + // I'm returning via the exact opposite, however, to forgo having another bracket embed. Same result either way, I think. + // Finally, if you get past this check, now you can actually start calculating what item you get. + if ((cmd->buttons & BT_ATTACK) && (player->itemroulette >= roulettestop) + && (K_RingsActive() || (modeattacking == ATTACKING_NONE)) + && !(player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT|IF_USERINGS)) + && !(player->itemusecooldown)) + { + // Mashing reduces your chances for the good items + mashed = FixedDiv((player->itemroulette)*FRACUNIT, ((TICRATE*3)+roulettestop)*FRACUNIT) - FRACUNIT; + } + else if (!(player->itemroulette >= (TICRATE*3))) + return; + + + pdis = K_CalculatePDIS(player, pingame, &spbrush); + + // SPECIAL CASE No. 1: + // Fake Eggman items + if (player->roulettetype == KROULETTETYPE_EGGMAN) + { + player->eggmanexplode = 4*TICRATE; + player->itemroulette = 0; + player->roulettetype = KROULETTETYPE_NORMAL; + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, sfx_itrole); + return; + } + + // SPECIAL CASE No. 2: + // Give a debug item instead if specified + if (cv_kartdebugitem.value != 0 && !modeattacking) + { + K_AwardPlayerItem(player, cv_kartdebugitem.value, cv_kartdebugamount.value, KITEMBLINK_DEBUG); + return; + } + + // SPECIAL CASE No. 3: + // This Gametype never specified an odds type. Roll something random please! + if (!(gametyperules & GTR_RACEODDS) && !(gametyperules & GTR_BATTLEODDS)) + { + UINT8 itemroll = P_RandomRange(0, numkartresults - 1); + K_AwardPlayerResult(player, &kartresults[itemroll], KITEMBLINK_NORMAL); + return; + } + + // SPECIAL CASE No. 4: + // Record Attack / alone mashing behavior + if ((modeattacking || pingame == 1) + && ((gametyperules & GTR_RACEODDS) + || ((gametyperules & GTR_BATTLEODDS) && (itembreaker || bossinfo.boss)))) + { + if ((gametyperules & GTR_RACEODDS)) + { + if (mashed && (K_ItemResultEnabled(K_GetKartResult("superring")) || (modeattacking && K_RingsActive()))) // ANY mashed value? You get rings. + { + K_AwardPlayerResult(player, K_GetKartResult("superring"), KITEMBLINK_MASHED); + } + else + { + if (modeattacking || K_ItemResultEnabled(K_GetKartResult("sneaker"))) // Waited patiently? You get a sneaker! + K_AwardPlayerResult(player, K_GetKartResult("sneaker"), KITEMBLINK_NORMAL); + else // Default to sad if nothing's enabled... + K_AwardPlayerResult(player, NULL, KITEMBLINK_NORMAL); + } + } + else if (gametyperules & GTR_BATTLEODDS) + { + if (mashed && (bossinfo.boss || K_ItemResultEnabled(K_GetKartResult("banana"))) && !itembreaker) // ANY mashed value? You get a banana. + K_AwardPlayerResult(player, K_GetKartResult("banana"), KITEMBLINK_MASHED); + else if (bossinfo.boss) + K_AwardPlayerResult(player, K_GetKartResult("orbinaut"), KITEMBLINK_NORMAL); + else if (itembreaker) + K_AwardPlayerResult(player, K_GetKartResult("sneaker"), KITEMBLINK_MASHED); + } + return; + } + + // SPECIAL CASE No. 5: + // Being in ring debt occasionally forces Super Ring on you if you mashed + if (K_ItemResultEnabled(K_GetKartResult("superring")) && mashed && player->rings < 0) + { + INT32 debtamount = min(abs(player->ringmin), abs(player->rings)); + if (P_RandomChance((debtamount*FRACUNIT)/abs(player->ringmin))) + { + K_AwardPlayerResult(player, K_GetKartResult("superring"), KITEMBLINK_MASHED); + return; + } + } + + // SPECIAL CASE No. 6: + // In battle, an SPB is forced onto players to target the "most wanted" player + if (K_BattleForceSPB(player) + && spbplace == -1 && K_GetKartResult("spb")->cooldown == 0 && !dontforcespb + && K_ItemResultEnabled(K_GetKartResult("spb"))) + { + K_AwardPlayerResult(player, K_GetKartResult("spb"), KITEMBLINK_KARMA); + return; + } + + // NOW that we're done with most of those specialized cases, we can move onto the REAL item roulette tables. + // Initializes existing spawnchance values + memset(spawnchance, 0, sizeof(spawnchance)); + + // Split into another function for a debug function below + useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); + + kartroulette_t roulette = { + .pdis = pdis, + .playerpos = player->position, + .pos = useodds, + .ourDist = player->distancetofinish, + .clusterDist = player->distancefromcluster, + .mashed = mashed, + .spbrush = spbrush, + .bot = player->bot, + .rival = player->bot && player->botvars.rival, + .inBottom = K_IsPlayerLosing(player), + }; + + K_KartGetItemOdds(&roulette, spawnchance); + + // SPECIAL CASE No. 7: + // Item forcing; the item with the highest "forceme" priority is the one given. + { + kartresult_t *forceresult; + UINT8 bestforce = 0; + + for (i = 0; i < numkartresults; i++) + { + if (roulette.forceme[i] && roulette.forceme[i] > bestforce) + { + bestforce = roulette.forceme[i]; + forceresult = &kartresults[i]; + } + } + + if ((bestforce) && (forceresult)) + { + K_AwardPlayerResult(player, forceresult, KITEMBLINK_KARMA); + return; + } + } + + for (i = 0; i < numkartresults; i++) + { + totalspawnchance += spawnchance[i]; + spawnchance[i] = totalspawnchance; + } + + // Award the player whatever power is rolled + kartresult_t *result = NULL; + if (totalspawnchance > 0) + { + totalspawnchance = P_RandomKey(totalspawnchance); + for (i = 0; i < numkartresults && spawnchance[i] <= totalspawnchance; i++); + result = &kartresults[i]; + } + K_AwardPlayerResult(player, result, player->roulettetype == KROULETTETYPE_KARMA ? KITEMBLINK_KARMA : mashed ? KITEMBLINK_MASHED : KITEMBLINK_NORMAL); +} + +void K_StartRoulette(player_t *player, kartroulettetype_e roulettetype) +{ + if (roulettetype == KROULETTETYPE_EGGMAN) + K_DropItems(player); + + player->itemroulette = 1; + player->roulettetype = roulettetype; +} + +void K_SetPlayerItemCooldown(player_t *player, tic_t timer, boolean force) +{ + if ((timer >= player->itemusecooldown) || force) + { + player->itemusecooldown = timer; + player->itemusecooldownmax = max(timer, player->itemusecooldownmax); + } +} + +// Unique odds functions, for REAL this time + +// Alt. Invin. odds +INT32 KO_AltInvinOdds(INT32 odds, const kartroulette_t *roulette, const kartresult_t *result, UINT8 *forceme) +{ + (void)result; + odds = K_KartGetInvincibilityOdds(roulette->clusterDist); + + // Special case: if you're SERIOUSLY far behind before the cooldown finishes, ignore it and start forcing, + if (odds >= INVFORCEODDS) + { + *forceme = 3; // Take priority over SPBs + } + else if (K_InStartCooldown()) + { + odds = 0; + } + + odds *= BASEODDSMUL; + + return odds; +} + +// SPB odds +INT32 KO_SPBRaceOdds(INT32 odds, const kartroulette_t *roulette, const kartresult_t *result, UINT8 *forceme) +{ + (void)result; + if (!K_LegacyOddsMode() && roulette->firstDist < (UINT32)ENDDIST) // No SPB near the end of the race + { + odds = 0; + } + else if (K_LegacyOddsMode() && roulette->pexiting > 0) + { + odds = 0; + } + + // No forced SPB in 1v1s, it has to be randomly rolled + if (roulette->pingame <= 2) + { + *forceme = 0; + } + else if (K_RaceForceSPB(roulette->playerpos, roulette->pdis) + && spbplace == -1 && K_GetKartResult("spb")->cooldown == 0) + { + // Force SPB onto 2nd if they get too far behind. + *forceme = 2; + } + + return odds; +} + +#define ALTSHRINK_EPSILON (320 * FRACUNIT) +#define NEIGHBOR_IFRAMES (TICRATE / 2) +#define BASE_IFRAMES (2 * TICRATE) + +static void K_DoGrowShrink(player_t *player, boolean shrinking) +{ + player->mo->scalespeed = mapobjectscale/TICRATE; + player->mo->destscale = FixedMul(mapobjectscale, (shrinking) ? SHRINK_SCALE : GROW_SCALE); + + if (K_PlayerShrinkCheat(player) == true) + { + player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); + } + + if ((shrinking) && (ALTSHRINKTIME > 0)) + { + player->growshrinktimer = ALTSHRINKTIME * -TICRATE; + } + else + { + player->growshrinktimer = (gametyperules & GTR_CLOSERPLAYERS ? 8 : 12) * ((shrinking) ? -TICRATE : TICRATE); + } + + if (shrinking) + { + // Find neighbors + INT32 n = K_CountNeighboringPlayers(player, ALTSHRINK_EPSILON, &(vector3_t){}); + + // For every neighbor, add some iframes for a clean breakaway. + UINT32 iframes = BASE_IFRAMES + n * NEIGHBOR_IFRAMES; + player->flashing = iframes > UINT16_MAX ? UINT16_MAX : iframes; + } + + if (player->invincibilitytimer > 0) + { + ; // invincibility has priority in P_RestoreMusic, no point in starting here + } + else if (P_IsLocalPlayer(player) == true) + { + S_ChangeMusicSpecial((shrinking) ? "kshrnk" : "kgrow"); + } + else //used to be "if (P_IsDisplayPlayer(player) == false)" + { + if (!shrinking) + { + S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); + } + } + + P_RestoreMusic(player); +} + +static void K_DoShrink(player_t *user) +{ + INT32 i; + mobj_t *mobj, *next; + + S_StartSound(user->mo, sfx_kc46); // Sound the BANG! + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || !players[i].mo) + continue; + if (&players[i] == user) + continue; + if (players[i].position < user->position) + { + //P_FlashPal(&players[i], PAL_NUKE, 10); + + // Grow should get taken away. + if (players[i].growshrinktimer > 0) + K_RemoveGrowShrink(&players[i]); + else + { + // Start shrinking! + K_DropItems(&players[i]); + players[i].growshrinktimer = -(15*TICRATE); + + if (players[i].mo && !P_MobjWasRemoved(players[i].mo)) + { + players[i].mo->scalespeed = mapobjectscale/TICRATE; + players[i].mo->destscale = FixedMul(mapobjectscale, SHRINK_SCALE); + + if (K_PlayerShrinkCheat(&players[i]) == true) + { + players[i].mo->destscale = FixedMul(players[i].mo->destscale, SHRINK_SCALE); + } + + S_StartSound(players[i].mo, sfx_kc59); + } + } + } + } + + // kill everything in the kitem list while we're at it: + for (mobj = kitemcap; mobj; mobj = next) + { + next = mobj->itnext; + + // 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) + { + 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 + + if (mobj->type == MT_SPB) + spbplace = -1; + } +} + +static void K_DoHyudoroSteal(player_t *player) +{ + INT32 i, numplayers = 0; + INT32 playerswappable[MAXPLAYERS]; + INT32 stealplayer = -1; // The player that's getting stolen from + INT32 prandom = 0; + boolean sink = P_RandomChance(FRACUNIT/64); + boolean force_sink = false; + INT32 hyu = hyudorotime; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && players[i].mo && players[i].mo->health > 0 && players[i].playerstate == PST_LIVE + && player != &players[i] && !players[i].exiting && !players[i].spectator // Player in-game + + // Can steal from this player + && (gametype == GT_RACE //&& players[i].position < player->position) + || ((gametyperules & GTR_BUMPERS) && players[i].bumper > 0)) + + // Has an item + && (players[i].itemtype + && players[i].itemamount + && !(players[i].itemflags & IF_ITEMOUT) + && !players[i].itemblink)) + { + playerswappable[numplayers] = i; + numplayers++; + } + } + + prandom = P_RandomFixed(); + S_StartSound(player->mo, sfx_s3k92); + + if (numplayers == 1) // With just 2 players, we just need to set the other player to be the one to steal from + { + stealplayer = playerswappable[numplayers-1]; + } + else if (numplayers > 1) // We need to choose between the available candidates for the 2nd player + { + stealplayer = playerswappable[prandom%(numplayers-1)]; + } + + force_sink = LUA_HookKartHyudoro(player, &stealplayer, sink); + + if (force_sink || (sink && numplayers > 0 && K_ItemResultEnabled(K_GetKartResult("kitchensink")))) // BEHOLD THE KITCHEN SINK + { + player->hyudorotimer = hyu; + player->stealingtimer = stealtime; + + player->itemtype = KITEM_KITCHENSINK; + player->itemamount = 1; + K_UnsetItemOut(player); + + // Woah this could be big, lets get the inside scoop... + K_DirectorForceSwitch(player - players, 1); + return; + } + else if ((gametype == GT_RACE && player->position == 1) || numplayers == 0) // No-one can be stolen from? Oh well... + { + player->hyudorotimer = hyu; + player->stealingtimer = stealtime; + return; + } + + if (stealplayer > -1) // Now here's where we do the stealing, has to be done here because we still know the player we're stealing from + { + player->hyudorotimer = hyu; + player->stealingtimer = stealtime; + players[stealplayer].stolentimer = stealtime; + + player->itemtype = players[stealplayer].itemtype; + player->itemamount = players[stealplayer].itemamount; + K_UnsetItemOut(player); + + players[stealplayer].itemtype = KITEM_NONE; + players[stealplayer].itemamount = 0; + K_UnsetItemOut(&players[stealplayer]); + + if (P_IsDisplayPlayer(&players[stealplayer]) && !r_splitscreen) + S_StartSound(NULL, sfx_s3k92); + } +} + +#define THUNDERRADIUS 320 + +// Rough size of the outer-rim sprites, after scaling. +// (The hitbox is already pretty strict due to only 1 active frame, +// we don't need to have it disjointedly small too...) +#define THUNDERSPRITE 80 + +void K_DoThunderShield(player_t *player) +{ + mobj_t *mo; + int i = 0; + fixed_t sx; + fixed_t sy; + angle_t an; + + S_StartSound(player->mo, sfx_zio3); + K_ThunderShieldAttack(player->mo, (THUNDERRADIUS + THUNDERSPRITE) * FRACUNIT); + + // spawn vertical bolt + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); + P_SetTarget(&mo->target, player->mo); + P_SetMobjState(mo, S_LZIO11); + mo->color = SKINCOLOR_TEAL; + mo->scale = player->mo->scale*3 + (player->mo->scale/2); + + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); + P_SetTarget(&mo->target, player->mo); + P_SetMobjState(mo, S_LZIO21); + mo->color = SKINCOLOR_CYAN; + mo->scale = player->mo->scale*3 + (player->mo->scale/2); + + // spawn horizontal bolts; + for (i=0; i<7; i++) + { + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); + mo->angle = P_RandomRange(0, 359)*ANG1; + mo->fuse = P_RandomRange(20, 50); + P_SetTarget(&mo->target, player->mo); + P_SetMobjState(mo, S_KLIT1); + } + + // spawn the radius thing: + an = ANGLE_22h; + for (i=0; i<15; i++) + { + sx = player->mo->x + FixedMul((player->mo->scale*THUNDERRADIUS), FINECOSINE((an*i)>>ANGLETOFINESHIFT)); + sy = player->mo->y + FixedMul((player->mo->scale*THUNDERRADIUS), FINESINE((an*i)>>ANGLETOFINESHIFT)); + mo = P_SpawnMobj(sx, sy, player->mo->z, MT_THOK); + mo->angle = an*i; + mo->extravalue1 = THUNDERRADIUS; // Used to know whether we should teleport by radius or something. + mo->scale = player->mo->scale*3; + P_SetTarget(&mo->target, player->mo); + P_SetMobjState(mo, S_KSPARK1); + } +} + +#undef THUNDERRADIUS +#undef THUNDERSPRITE + +#define MAXSHARDCOUNT 40 +#define SHARDROT (360 * FRACUNIT / 40) +void K_BreakBubbleShield(player_t* player) +{ + if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) + return; + + const INT32 flip = P_MobjFlip(player->mo); + const fixed_t scalediff = player->shieldtracer->scale - mapobjectscale; + const fixed_t shieldrad = player->shieldtracer->radius; + + for (INT32 i = 0; i < MAXSHARDCOUNT; i++) + { + vector2_t mul_vec = { FRACUNIT, 0 }; + + fixed_t randang = SHARDROT * (i + 1); + fixed_t randzang = P_RandomRange(10, 120) * FRACUNIT; + fixed_t move_magnitude = + FixedMul((P_RandomRange(4, 16) * FRACUNIT), mapobjectscale + (scalediff / 4)); + + FV2_Rotate(&mul_vec, randzang); + + // Do shitty initial 3D rotations around the shield's radius. + mobj_t *shard = P_SpawnMobj( + player->shieldtracer->x + + FixedMul(FixedMul(mul_vec.x, shieldrad), FCOS(FixedAngle(randang))), + player->shieldtracer->y + + FixedMul(FixedMul(mul_vec.x, shieldrad), FSIN(FixedAngle(randang))), + player->shieldtracer->z + ((player->shieldtracer->height + scalediff) / 4) + + FixedMul(mul_vec.y * flip, shieldrad), + MT_BUBBLESHIELD_DEBRIS); + + if (!P_MobjWasRemoved(shard)) + { + //CONS_Printf(M_GetText("randzang: %d, randang: %d\n"), randzang / FRACUNIT, randang / FRACUNIT); + + mul_vec.x = FixedMul(move_magnitude, mul_vec.x); + mul_vec.y = FixedMul(move_magnitude, mul_vec.y * flip); + + vector3_t mom = { + .x = FixedMul(mul_vec.x, FCOS(FixedAngle(randang))), + .y = FixedMul(mul_vec.x, FSIN(FixedAngle(randang))), + .z = mul_vec.y, + }; + + shard->momx = mom.x + player->mo->momx; + shard->momy = mom.y + player->mo->momy; + shard->momz = mom.z + player->mo->momz; + + shard->fuse = 3*TICRATE; + shard->extravalue1 = i % 6; // 16% of shards play the death sound + } + } + + S_StartSound(player->mo, sfx_kc41); +} +#undef MAXSHARDCOUNT +#undef SHARDROT + +static void K_ThrowLandMine(player_t *player) +{ + mobj_t *landMine; + mobj_t *throwmo; + + landMine = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_LANDMINE); + K_FlipFromObject(landMine, player->mo); + landMine->threshold = 10; + + if (landMine->info->seesound) + S_StartSound(player->mo, landMine->info->seesound); + + P_SetTarget(&landMine->target, player->mo); + + P_SetScale(landMine, player->mo->scale); + landMine->destscale = player->mo->destscale; + + landMine->angle = player->mo->angle; + + landMine->momz = (30 * mapobjectscale * P_MobjFlip(player->mo)) + player->mo->momz; + landMine->color = player->skincolor; + + throwmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FIREDITEM); + P_SetTarget(&throwmo->target, player->mo); + // Ditto: + if (player->mo->eflags & MFE_VERTICALFLIP) + { + throwmo->z -= player->mo->height; + throwmo->eflags |= MFE_VERTICALFLIP; + } + + throwmo->movecount = 0; // above player +} + +void K_PlayerItemThink(player_t *player, boolean onground) +{ + const UINT16 buttons = K_GetKartButtons(player); + const boolean ATTACK_IS_DOWN = ((buttons & BT_ATTACK) && !(player->oldcmd.buttons & BT_ATTACK)); + const boolean HOLDING_ITEM = (player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)); + const boolean NO_HYUDORO = (player->stealingtimer == 0 && player->stolentimer == 0); + + boolean force = false; + if (LUA_HookPlayerItem(player, player->itemtype, HOLDING_ITEM, &force)) + return; + + switch (player->itemtype) + { + case KITEM_SNEAKER: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO) + { + K_DoSneaker(player, SNEAKERTYPE_SNEAKER); + K_PlayBoostTaunt(player->mo); + player->itemamount--; + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_ROCKETSNEAKER: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO + && player->rocketsneakertimer == 0) + { + INT32 moloop; + mobj_t *mo = NULL; + mobj_t *prev = player->mo; + + K_PlayBoostTaunt(player->mo); + S_StartSound(player->mo, sfx_s3k3a); + + //K_DoSneaker(player, SNEAKERTYPE_ROCKETSNEAKER); + + player->rocketsneakertimer = (itemtime*3); + player->itemamount--; + K_UpdateHnextList(player, true); + + for (moloop = 0; moloop < 2; moloop++) + { + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ROCKETSNEAKER); + K_MatchGenericExtraFlags(mo, player->mo); + mo->flags |= MF_NOCLIPTHING; + mo->angle = player->mo->angle; + mo->threshold = 10; + mo->movecount = moloop%2; + mo->movedir = mo->lastlook = moloop+1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_INVINCIBILITY: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage in Legacy, so you're free to waste it if you have multiple + { + K_DoInvincibility(player, K_GetInvincibilityTime(player)); + K_PlayPowerGloatSound(player->mo); + player->itemamount--; + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_BANANA: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + INT32 moloop; + mobj_t *mo; + mobj_t *prev = player->mo; + + //K_PlayAttackTaunt(player->mo); + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s254); + + for (moloop = 0; moloop < player->itemamount; moloop++) + { + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD); + if (!mo) + { + player->itemamount = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = player->itemamount; + mo->movedir = moloop+1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + + } + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Banana x3 thrown + { + player->itemamount--; + + mobj_t *mo = K_ThrowKartItem(player, false, MT_BANANA, -1, 0); + mo->color = player->skincolor; + + K_PlayAttackTaunt(player->mo); + K_UpdateHnextList(player, false); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_EGGMAN: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + player->itemamount--; + player->itemflags |= IF_EGGMANOUT; + S_StartSound(player->mo, sfx_s254); + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD); + if (mo) + { + K_FlipFromObject(mo, player->mo); + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = 1; + mo->movedir = 1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&player->mo->hnext, mo); + } + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_ORBINAUT: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + angle_t newangle; + INT32 moloop; + mobj_t *mo = NULL; + mobj_t *prev = player->mo; + + //K_PlayAttackTaunt(player->mo); + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s3k3a); + + for (moloop = 0; moloop < player->itemamount; moloop++) + { + newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD); + if (!mo) + { + player->itemamount = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->angle = newangle; + mo->threshold = 10; + mo->movecount = player->itemamount; + mo->movedir = mo->lastlook = moloop+1; + mo->color = player->skincolor; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + } + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Orbinaut x3 thrown + { + player->itemamount--; + K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0); + K_PlayAttackTaunt(player->mo); + K_UpdateHnextList(player, false); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_JAWZ: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + angle_t newangle; + INT32 moloop; + mobj_t *mo = NULL; + mobj_t *prev = player->mo; + + //K_PlayAttackTaunt(player->mo); + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s3k3a); + + for (moloop = 0; moloop < player->itemamount; moloop++) + { + newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD); + if (!mo) + { + player->itemamount = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->angle = newangle; + mo->threshold = 10; + mo->movecount = player->itemamount; + mo->movedir = mo->lastlook = moloop+1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + K_BotResetItemConfirm(player, false); + } + else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Jawz thrown + { + player->itemamount--; + if (player->throwdir == 1 || player->throwdir == 0) + K_ThrowKartItem(player, true, MT_JAWZ, 1, 0); + else if (player->throwdir == -1) // Throwing backward gives you a dud that doesn't home in + K_ThrowKartItem(player, true, MT_JAWZ_DUD, -1, 0); + K_PlayAttackTaunt(player->mo); + K_UpdateHnextList(player, false); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_MINE: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s254); + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SSMINE_SHIELD); + if (mo) + { + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = 1; + mo->movedir = 1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&player->mo->hnext, mo); + } + K_BotResetItemConfirm(player, false); + } + else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) + { + player->itemamount--; + K_ThrowKartItem(player, false, MT_SSMINE, 1, 1); + K_PlayAttackTaunt(player->mo); + player->itemflags &= ~IF_ITEMOUT; + K_UpdateHnextList(player, true); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_LANDMINE: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_ThrowLandMine(player); + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_BALLHOG: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0); + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_SPB: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_ThrowKartItem(player, true, MT_SPB, 1, 0); + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_GROW: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + if (player->growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK)) + { + // If you're shrunk, then "grow" will just make you normal again... + K_RemoveGrowShrink(player); + } + else + { + // ...in Legacy mode. + // Alt. Shrink's a powerup, so Grow overrides! + if (K_IsAltShrunk(player)) + { + player->growshrinktimer = 0; // Paranoia + } + + K_PlayPowerGloatSound(player->mo); + + K_DoGrowShrink(player, false); + + S_StartSound(player->mo, sfx_kc5a); + } + + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_SHRINK: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + if (K_IsKartItemAlternate(KITEM_SHRINK)) + { + K_DoGrowShrink(player, true); + + S_StartSound(player->mo, sfx_kc46); + } + else + { + K_DoShrink(player); + } + + player->itemamount--; + K_PlayPowerGloatSound(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_THUNDERSHIELD: + if (K_GetShieldFromPlayer(player) != KSHIELD_THUNDER) + { + mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THUNDERSHIELD); + P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); + P_SetTarget(&shield->target, player->mo); + P_SetTarget(&player->shieldtracer, shield); + S_StartSound(player->mo, sfx_s3k41); + } + + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + K_DoThunderShield(player); + if (player->itemamount > 0) + { + // Why is this a conditional? + // Thunder shield: the only item that allows you to + // activate a mine while you're out of its radius, + // the SAME tic it sets your itemamount to 0 + // ...:dumbestass: + player->itemamount--; + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + } + break; + case KITEM_BUBBLESHIELD: + if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) + { + mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BUBBLESHIELD); + P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); + P_SetTarget(&shield->target, player->mo); + P_SetTarget(&player->shieldtracer, shield); + S_StartSound(player->mo, sfx_s3k3f); + if (player->bubblehealth <= 0 || player->bubblehealth > MAXBUBBLEHEALTH) + player->bubblehealth = MAXBUBBLEHEALTH; + } + + if (!HOLDING_ITEM && NO_HYUDORO) + { + if ((buttons & BT_ATTACK && player->itemflags & IF_HOLDREADY) + || (player->bubbleblowup > 0 && player->bubblecool <= bubbletime/2)) // auto + { + if (player->bubblecool == 0) + S_StartSound(player->mo, sfx_s3k75); + + if (player->bubblecool < bubbletime && player->bubblehealth > 0) + { + player->bubbleblowup += 3; + player->bubblehealth--; + } + else if (player->bubblecool >= bubbletime) + player->bubbleblowup++; // overcharge bonus + + if (++player->bubblecool >= bubbletime + TICRATE/2) + { + // If you overcharge the Bubble Shield at + // any point, it pops and gives you a boost. + K_PopPlayerShield(player); + + if (onground) + { + // If you're on the ground, you're going to + // immediately feel the effects of this boost. + // Play a sound to let everyone know! + S_StartSoundAtVolume(player->mo, sfx_cdfm57, 170); + K_PlayBoostTaunt(player->mo); + } + + // experiment: don't boost, just give invulnerability + if (cv_kartbubble_boost_allow.value) + { + player->bubbleboost = 7 * sneakertime / 10; + } + player->flashing = 4*TICRATE/7; + } + } + else + { + if (player->bubblecool > bubbletime) + player->bubblecool = bubbletime; + + if (player->bubbleblowup > 0) + { + player->bubbleblowup--; + if (player->bubbleblowup == 0) + K_BotResetItemConfirm(player, false); + if (player->bubblecool < bubbletime) + player->bubblecool++; + } + + if (player->bubbleblowup == 0 && player->bubblecool > 0) + { + player->bubblecool--; + + if (player->bubblecool == 0 && player->bubblehealth <= 0) + K_PopPlayerShield(player); + } + + if (buttons & BT_ATTACK || player->bubblecool > 0) + { + player->itemflags &= ~IF_HOLDREADY; + } + else + { + player->itemflags |= IF_HOLDREADY; + } + } + } + break; + case KITEM_FLAMESHIELD: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && K_GetShieldFromPlayer(player) != KSHIELD_FLAME) + { + player->itemamount--; + player->flametimer = (itemtime*3); + mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FLAMESHIELD); + P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); + P_SetTarget(&shield->target, player->mo); + P_SetTarget(&player->shieldtracer, shield); + S_StartSound(player->mo, sfx_s3k3e); + } + break; + case KITEM_HYUDORO: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_DoHyudoroSteal(player); // yes. yes they do. + K_PlayAttackTaunt(player->mo); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_POGOSPRING: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && (onground || force) && NO_HYUDORO && player->pogospring == 0) + { + player->itemamount--; + K_PlayBoostTaunt(player->mo); + K_DoPogoSpring(player->mo, 32<pogospring = 1; + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_SUPERRING: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->itemamount--; + K_AwardScaledPlayerRings(player, ASR_SUPERRING); + K_BotResetItemConfirm(player, false); + } + break; + case KITEM_KITCHENSINK: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + K_SetItemOut(player); + S_StartSound(player->mo, sfx_s254); + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SINK_SHIELD); + if (mo) + { + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = 1; + mo->movedir = 1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&player->mo->hnext, mo); + } + K_BotResetItemConfirm(player, false); + + // Woah this could be big, lets get the inside scoop... + K_DirectorForceSwitch(player - players, 1); + } + else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Sink thrown + { + player->itemamount--; + K_ThrowKartItem(player, false, MT_SINK, 1, 2); + K_PlayAttackTaunt(player->mo); + player->itemflags &= ~IF_ITEMOUT; + K_UpdateHnextList(player, true); + K_BotResetItemConfirm(player, false); + } + break; + case MAXKARTITEMS: // aka KITEM_SAD + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO + && !player->sadtimer) + { + player->sadtimer = stealtime; + player->itemamount--; + K_BotResetItemConfirm(player, false); + } + break; + default: + break; + } +} + +INT16 K_GetShrinkTime(const player_t *player) +{ + return player->growshrinktimer * -1; +} + +boolean K_IsAltShrunk(const player_t *player) +{ + return player->growshrinktimer < 0 && K_IsKartItemAlternate(KITEM_SHRINK); +} + +#define PITY_SHRINKINCREASE_BASE (3) +#define PITY_SHRINKINCREASE (TICRATE / 2) + +void K_AltShrinkPityIncrease(player_t *player) +{ + // Increase your shrink timer by a little bit for every player you run into. + INT32 shrinktime = K_GetShrinkTime(player); + + fixed_t dimin = FRACUNIT; + + dimin = max(0, 5 - player->altshrinktimeshit) * FRACUNIT / 5; + + shrinktime = min(INT16_MAX, shrinktime + PITY_SHRINKINCREASE_BASE + FixedMul(PITY_SHRINKINCREASE, dimin)); + + player->growshrinktimer = ((INT16)shrinktime) * -1; +} diff --git a/src/k_items.h b/src/k_items.h new file mode 100644 index 000000000..b2be3e1fd --- /dev/null +++ b/src/k_items.h @@ -0,0 +1,217 @@ +// BLANKART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2025 by Kart Krew. +// Copyright (C) 2025 by "yama". +// Copyright (C) 2025 Blankart Team. +// +// 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 k_items.h +/// \brief Kart item systems. + +#ifndef __K_ITEMS__ +#define __K_ITEMS__ + +#include "doomdef.h" +#include "doomtype.h" +#include "r_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Max odds count +#define MAXODDS 16 + +// Distance variables +#define DISTVAR CV_Get(&cv_kartoddsdist) +#define DISTVAR_LEGACY CV_Get(&cv_kartlegacyoddsdist) +#define SPBDISTVAR CV_Get(&cv_kartspbdist) +#define SPBDISTVAR_LEGACY CV_Get(&cv_kartlegacyspbdist) + +typedef enum +{ +#define _(name, ...) KITEM_##name, +#include "info/kartitems.h" +#undef _ + KITEM_FIRSTFREESLOT, + KITEM_LASTFREESLOT = 126, + MAXKARTITEMS +} ATTRPACK kartitemtype_e; + +#define MAXKARTRESULTS 255 + +typedef enum +{ + KITEMBLINK_NORMAL, + KITEMBLINK_MASHED, + KITEMBLINK_KARMA, + KITEMBLINK_DEBUG, +} kartitemblink_e; + +typedef enum +{ + KROULETTETYPE_NORMAL, + KROULETTETYPE_KARMA, + KROULETTETYPE_EGGMAN, +} kartroulettetype_e; + +typedef enum +{ + ODDS_RACE, + ODDS_BATTLE, + ODDS_SPECIAL, + MAXODDSTABLES +} ATTRPACK kartoddstable_e; + +// item flags for usage and HUD +typedef enum +{ + KIF_UNIQUESLOT = 1<<0, // Prevents the item from being rolled if in a player's item slot + KIF_UNIQUEDROP = 1<<1, // Prevents the item from being rolled if it exists as a dropped item + KIF_UNIQUE = KIF_UNIQUESLOT|KIF_UNIQUEDROP, + + KIF_ANIMATED = 1<<2, // animate patches instead of using diffrent patches for different amounts (inversion of xItemLib's XIF_ICONFORAMT) + KIF_DARKBG = 1<<3, // use dark item roulette BG + KIF_COLPATCH2PLAYER = 1<<4, // colourize patch to player + KIF_HIDEFROMROULETTE = 1<<5, // don't show this item in the roulette (inversion of xItem's showInRoulette item flag) +} ATTRPACK kartitemflags_e; + +// result flags relevant to the roulette +typedef enum +{ + KRF_INDIRECTITEM = 1<<0, // This result's item affects others indirectly. All results with this flag share a single cooldown timer. + KRF_POWERITEM = 1<<1, // This result is a "power item". This activates "frantic item" toggle related functionality. + KRF_COOLDOWNONSTART = 1<<2, // This result should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) + KRF_NOTNEAREND = 1<<3, // This result should not appear at the end of a race. (Usually trap items that lose their effectiveness) + KRF_NOTFORBOTTOM = 1<<4, // After the first 30 seconds, this result stops appearing for losing players. (Usually items that feel less effective at these positions) + KRF_RUNNERAUGMENT = 1<<5, // This result's odds are affected by the severity of 1st place's frontrun. (Very likely only for SPBs, but who knows?) + KRF_HIDEFROMSPB = 1<<6, // This result refuses to appear when a Self-Propelled Bomb is active in a race or battle. +} ATTRPACK kartresultflags_e; + +// Unique useodds function +typedef INT32 (useoddsfunc_f)(INT32 odds, const kartroulette_t *roulette, const kartresult_t *result, UINT8 *forceme); + +#define MAXITEMPATCHES 10 + +struct kartitemgraphics_t +{ + UINT8 numpatches; + char *patchnames[MAXITEMPATCHES]; + patch_t *patches[MAXITEMPATCHES]; +}; + +struct kartitem_t +{ + dehinfo_t info; + kartitemflags_e flags[2]; + kartitemgraphics_t graphics[2]; + spritedef_t spritedef; + + consvar_t *altcvar; // if not NULL, an altitem exists + boolean altenabled; + + // note: could be how we do this? + // int (*oddsOverrideFunc)(player_t *p, int pos, int mashed, boolean spbrush, fixed_t seconddist, int pingame, int pexiting); + // int (*resultOverrideFunc)(player_t *p); +}; + +struct kartresult_t +{ + // this block is shared by all item variants + consvar_t *cvar; // contains name + kartitemtype_e type; + UINT8 amount; + + char *displayname; + boolean isalt; // is alt (i salt) + kartresultflags_e flags; + UINT8 odds[MAXODDSTABLES][MAXODDS]; + + // Functions that tell the game to use unique means of rolling these items. + // If NULL, the functions never execute. + useoddsfunc_f *unique_odds[MAXODDSTABLES]; + + tic_t basecooldown, cooldown; +}; + +// contains all data for a call to K_KartGetItemOdds +struct kartroulette_t +{ + UINT32 pdis; // the player's PDIS value + SINT8 playerpos; // the player's position + UINT8 pos; // position in the odds table (why is this just "pos"???) + UINT32 ourDist; // the player's finish line distance + UINT32 clusterDist; // distance to cluster + fixed_t mashed; + boolean spbrush; + boolean bot; + boolean rival; + boolean inBottom; + + // output: which results are being forced into a player's item slot for one reason or another. Higher value = higher priority. + UINT8 forceme[MAXKARTRESULTS]; + + // extras for useodds functions + UINT8 pingame; + UINT8 pexiting; + UINT32 firstDist; +}; + +extern kartitem_t kartitems[MAXKARTITEMS]; +extern UINT8 numkartitems; +extern kartresult_t kartresults[MAXKARTRESULTS]; +extern UINT8 numkartresults; +extern UINT8 oddstablemax[MAXODDSTABLES]; + +void K_RegisterItem(kartitemtype_e itemtype); +kartresult_t *K_RegisterResult(const char *name, boolean alternate); +kartresult_t *K_GetKartResultAlt(const char *name, boolean alternate); +#define K_GetKartResult(name) K_GetKartResultAlt(name, false) +void K_SetupItemOdds(void); +boolean K_ItemResultEnabled(const kartresult_t *result); +boolean K_IsKartItemAlternate(kartitemtype_e itemtype); +kartitemflags_e K_GetItemFlags(kartitemtype_e itemtype); +UINT8 K_GetItemNumberDisplayMin(kartitemtype_e type, boolean tiny); +patch_t *K_GetCachedItemPatch(kartitemtype_e type, boolean tiny, UINT8 amount); + +void K_AwardPlayerItem(player_t *player, kartitemtype_e type, UINT8 amount, kartitemblink_e blink); +void K_UpdateMobjItemOverlay(mobj_t *part, kartitemtype_e itemType, UINT8 itemCount); +void K_UpdateItemCooldown(void); +tic_t K_GetIndirectItemCooldown(void); +void K_SetIndirectItemCooldown(tic_t cooldown); +boolean K_LegacyOddsMode(void); +UINT32 K_GetCongaLineDistance(const player_t *player, UINT8 startPos); + +UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame); +UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrush); +UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush); +UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); +INT32 K_GetRollingRouletteItem(player_t *player); +void K_KartItemRoulette(player_t *player, ticcmd_t *cmd); +void K_StartRoulette(player_t *player, kartroulettetype_e roulettetype); + +void K_SetPlayerItemCooldown(player_t *player, tic_t timer, boolean force); + +useoddsfunc_f KO_AltInvinOdds; +useoddsfunc_f KO_SPBRaceOdds; + +void K_DoThunderShield(player_t *player); +void K_BreakBubbleShield(player_t* player); +INT16 K_GetShrinkTime(const player_t *player); +boolean K_IsAltShrunk(const player_t *player); +void K_AltShrinkPityIncrease(player_t *player); + +void K_PlayerItemThink(player_t *player, boolean onground); + +#ifdef __cplusplus +} // extern "C" +#endif + +#ifndef __cplusplus +void K_KartGetItemOdds(kartroulette_t *roulette, INT32 outodds[static MAXKARTRESULTS]); +#endif + +#endif // __K_ITEMS__ diff --git a/src/k_kart.c b/src/k_kart.c index 3379157ab..3dbf0142c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -64,7 +64,7 @@ #include "k_follower.h" #include "k_grandprix.h" #include "k_cluster.hpp" -#include "k_odds.h" +#include "k_items.h" #include "k_specialstage.h" #include "h_timers.h" @@ -89,7 +89,6 @@ consvar_t cv_karthitemdialog = CVAR_INIT ("karthitemdialog", "On", CV_SAVE, CV_O // 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 // Cluster point. @@ -232,37 +231,6 @@ angle_t K_ReflectAngle(angle_t yourangle, angle_t theirangle, fixed_t yourspeed, void K_RegisterKartStuff(void) { - CV_RegisterVar(&cv_sneaker); - CV_RegisterVar(&cv_rocketsneaker); - CV_RegisterVar(&cv_invincibility); - CV_RegisterVar(&cv_banana); - CV_RegisterVar(&cv_eggmanmonitor); - CV_RegisterVar(&cv_orbinaut); - CV_RegisterVar(&cv_jawz); - CV_RegisterVar(&cv_mine); - CV_RegisterVar(&cv_ballhog); - CV_RegisterVar(&cv_selfpropelledbomb); - CV_RegisterVar(&cv_grow); - CV_RegisterVar(&cv_shrink); - CV_RegisterVar(&cv_thundershield); - CV_RegisterVar(&cv_hyudoro); - CV_RegisterVar(&cv_pogospring); - CV_RegisterVar(&cv_kitchensink); - - // Nu-ITEMS - CV_RegisterVar(&cv_superring); - CV_RegisterVar(&cv_landmine); - CV_RegisterVar(&cv_bubbleshield); - CV_RegisterVar(&cv_flameshield); - - CV_RegisterVar(&cv_dualsneaker); - CV_RegisterVar(&cv_triplesneaker); - CV_RegisterVar(&cv_triplebanana); - CV_RegisterVar(&cv_decabanana); - CV_RegisterVar(&cv_tripleorbinaut); - CV_RegisterVar(&cv_quadorbinaut); - CV_RegisterVar(&cv_dualjawz); - CV_RegisterVar(&cv_kartminimap); CV_RegisterVar(&cv_kartcheck); CV_RegisterVar(&cv_kartinvinsfx); @@ -345,6 +313,10 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartstacking_grow_accelboost); CV_RegisterVar(&cv_kartstacking_grow_stackable); + CV_RegisterVar(&cv_kartstacking_altshrink_speedboost); + CV_RegisterVar(&cv_kartstacking_altshrink_accelboost); + CV_RegisterVar(&cv_kartstacking_altshrink_stackable); + CV_RegisterVar(&cv_kartstacking_bubble_speedboost); CV_RegisterVar(&cv_kartstacking_bubble_accelboost); CV_RegisterVar(&cv_kartstacking_bubble_stackable); @@ -410,7 +382,8 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartslopeboost); - CV_RegisterVar(&cv_kartinvintype); + CV_RegisterVar(&cv_kartaltshrinktime); + CV_RegisterVar(&cv_kartinvindist); CV_RegisterVar(&cv_kartinvindistmul); @@ -431,6 +404,8 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartspbdist); CV_RegisterVar(&cv_kartlegacyspbdist); + CV_RegisterVar(&cv_kartspbrush); + CV_RegisterVar(&cv_kartdriftsounds); CV_RegisterVar(&cv_kartdriftefx); CV_RegisterVar(&cv_kartsplashefx); @@ -913,7 +888,7 @@ static fixed_t K_CheckOffroadCollide(mobj_t *mo) static fixed_t K_OffroadGradient(player_t *player, fixed_t offroad) { // At 50% or lower Invincibility, offroad creeps up on you. - fixed_t invinoffroad = (K_GetKartInvinType() == KARTINVIN_LEGACY) ? ((player->invincibilitytimer) ? FRACUNIT : 0) : min(FRACUNIT, K_InvincibilityGradient(player->invincibilitytimer) << 1); + fixed_t invinoffroad = (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) ? ((player->invincibilitytimer) ? FRACUNIT : 0) : min(FRACUNIT, K_InvincibilityGradient(player->invincibilitytimer) << 1); fixed_t fac = CLAMP(FRACUNIT - invinoffroad, 0, FRACUNIT); return FixedMul(offroad, fac); @@ -2413,7 +2388,7 @@ UINT16 K_GetInvincibilityTime(player_t *player) UINT32 i, pingame; fixed_t distmul = FRACUNIT; - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return BASEINVINTIME; pingame = 0; @@ -2443,23 +2418,25 @@ UINT16 K_GetInvincibilityTime(player_t *player) } fixed_t clustermul = K_InvincibilityEasing(FixedMul(player->distancefromcluster,distmul)); - return FixedMul(BASEINVINTIME, clustermul); + UINT16 invintics = FixedMul(BASEINVINTIME, clustermul); + + return max(MININVINTIME, invintics); } #undef LEGACYALTINVINMUL -static fixed_t K_GetInvincibilitySpeed(UINT16 time) +fixed_t K_GetInvincibilitySpeed(UINT16 time) { - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return INVINSPEEDBOOSTLGC; fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time)); return Easing_OutCubic(t, 0, INVINSPEEDBOOSTALT); } -static fixed_t K_GetInvincibilityAccel(UINT16 time) +fixed_t K_GetInvincibilityAccel(UINT16 time) { - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return INVINACCELBOOSTLGC; fixed_t t = min(FRACUNIT, K_InvincibilityGradient(time)); @@ -2666,6 +2643,14 @@ static void K_GetKartBoostPower(player_t *player) K_DoBoost(player, GROWSPEEDBOOST, GROWACCELBOOST, GROWSTACKABLE, GROWSTACKABLE); // + 20% top speed, + 0% acceleration } + if (K_IsAltShrunk(player)) // Alt. Shrink + { + fixed_t shrinkspeed = FixedDiv(SHRINKSPEEDBOOST, SHRINK_SCALE); + fixed_t shrinkaccel = FixedDiv(SHRINKACCELBOOST, SHRINK_SCALE); + + K_DoBoost(player, FixedMul(shrinkspeed, GROW_SCALE), FixedMul(shrinkaccel, GROW_SCALE), SHRINKSTACKABLE, SHRINKSTACKABLE); // + 50% top speed, + 15% acceleration + } + if (player->bubbleboost) // Bubble Shield popping boost { K_DoBoost(player, BUBBLESPEEDBOOST, BUBBLEACCELBOOST, BUBBLESTACKABLE, BUBBLESTACKABLE); // + 35% top speed, + 800% acceleration @@ -3319,7 +3304,7 @@ void K_FlipPlayer(player_t *player, mobj_t *inflictor, mobj_t *source) P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); } -static void K_RemoveGrowShrink(player_t *player) +void K_RemoveGrowShrink(player_t *player) { if (player->mo && !P_MobjWasRemoved(player->mo)) { @@ -3352,8 +3337,8 @@ void K_SquishPlayer(player_t *player, mobj_t *inflictor, mobj_t *source) player->squishedtimer = TICRATE; - // Reduce Shrink timer - if (player->growshrinktimer < 0) + // Reduce Shrink timer for Legacy Shrink + if (player->growshrinktimer < 0 && !K_IsKartItemAlternate(KITEM_SHRINK)) { player->growshrinktimer += TICRATE; if (player->growshrinktimer >= 0) @@ -4607,65 +4592,6 @@ void K_PuntMine(mobj_t *origMine, mobj_t *punter) mine->flags &= ~MF_NOCLIPTHING; } -#define THUNDERRADIUS 320 - -// Rough size of the outer-rim sprites, after scaling. -// (The hitbox is already pretty strict due to only 1 active frame, -// we don't need to have it disjointedly small too...) -#define THUNDERSPRITE 80 - -static void K_DoThunderShield(player_t *player) -{ - mobj_t *mo; - int i = 0; - fixed_t sx; - fixed_t sy; - angle_t an; - - S_StartSound(player->mo, sfx_zio3); - K_ThunderShieldAttack(player->mo, (THUNDERRADIUS + THUNDERSPRITE) * FRACUNIT); - - // spawn vertical bolt - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); - P_SetTarget(&mo->target, player->mo); - P_SetMobjState(mo, S_LZIO11); - mo->color = SKINCOLOR_TEAL; - mo->scale = player->mo->scale*3 + (player->mo->scale/2); - - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); - P_SetTarget(&mo->target, player->mo); - P_SetMobjState(mo, S_LZIO21); - mo->color = SKINCOLOR_CYAN; - mo->scale = player->mo->scale*3 + (player->mo->scale/2); - - // spawn horizontal bolts; - for (i=0; i<7; i++) - { - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THOK); - mo->angle = P_RandomRange(0, 359)*ANG1; - mo->fuse = P_RandomRange(20, 50); - P_SetTarget(&mo->target, player->mo); - P_SetMobjState(mo, S_KLIT1); - } - - // spawn the radius thing: - an = ANGLE_22h; - for (i=0; i<15; i++) - { - sx = player->mo->x + FixedMul((player->mo->scale*THUNDERRADIUS), FINECOSINE((an*i)>>ANGLETOFINESHIFT)); - sy = player->mo->y + FixedMul((player->mo->scale*THUNDERRADIUS), FINESINE((an*i)>>ANGLETOFINESHIFT)); - mo = P_SpawnMobj(sx, sy, player->mo->z, MT_THOK); - mo->angle = an*i; - mo->extravalue1 = THUNDERRADIUS; // Used to know whether we should teleport by radius or something. - mo->scale = player->mo->scale*3; - P_SetTarget(&mo->target, player->mo); - P_SetMobjState(mo, S_KSPARK1); - } -} - -#undef THUNDERRADIUS -#undef THUNDERSPRITE - static void K_FlameDashLeftoverSmoke(mobj_t *src) { UINT8 i; @@ -4695,85 +4621,6 @@ static void K_FlameDashLeftoverSmoke(mobj_t *src) } } -static void K_DoHyudoroSteal(player_t *player) -{ - INT32 i, numplayers = 0; - INT32 playerswappable[MAXPLAYERS]; - INT32 stealplayer = -1; // The player that's getting stolen from - INT32 prandom = 0; - boolean sink = P_RandomChance(FRACUNIT/64); - INT32 hyu = hyudorotime; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && players[i].mo && players[i].mo->health > 0 && players[i].playerstate == PST_LIVE - && player != &players[i] && !players[i].exiting && !players[i].spectator // Player in-game - - // Can steal from this player - && (gametype == GT_RACE //&& players[i].position < player->position) - || ((gametyperules & GTR_BUMPERS) && players[i].bumper > 0)) - - // Has an item - && (players[i].itemtype - && players[i].itemamount - && !(players[i].itemflags & IF_ITEMOUT) - && !players[i].itemblink)) - { - playerswappable[numplayers] = i; - numplayers++; - } - } - - prandom = P_RandomFixed(); - S_StartSound(player->mo, sfx_s3k92); - - if (sink && numplayers > 0 && cv_kitchensink.value) // BEHOLD THE KITCHEN SINK - { - player->hyudorotimer = hyu; - player->stealingtimer = stealtime; - - player->itemtype = KITEM_KITCHENSINK; - player->itemamount = 1; - K_UnsetItemOut(player); - - // Woah this could be big, lets get the inside scoop... - K_DirectorForceSwitch(player - players, 1); - return; - } - else if ((gametype == GT_RACE && player->position == 1) || numplayers == 0) // No-one can be stolen from? Oh well... - { - player->hyudorotimer = hyu; - player->stealingtimer = stealtime; - return; - } - else if (numplayers == 1) // With just 2 players, we just need to set the other player to be the one to steal from - { - stealplayer = playerswappable[numplayers-1]; - } - else if (numplayers > 1) // We need to choose between the available candidates for the 2nd player - { - stealplayer = playerswappable[prandom%(numplayers-1)]; - } - - if (stealplayer > -1) // Now here's where we do the stealing, has to be done here because we still know the player we're stealing from - { - player->hyudorotimer = hyu; - player->stealingtimer = stealtime; - players[stealplayer].stolentimer = stealtime; - - player->itemtype = players[stealplayer].itemtype; - player->itemamount = players[stealplayer].itemamount; - K_UnsetItemOut(player); - - players[stealplayer].itemtype = KITEM_NONE; - players[stealplayer].itemamount = 0; - K_UnsetItemOut(&players[stealplayer]); - - if (P_IsDisplayPlayer(&players[stealplayer]) && !r_splitscreen) - S_StartSound(NULL, sfx_s3k92); - } -} - // Handle these else where to reduce code duplication between panels and sneakers static void K_SneakerPanelStackSound(player_t *player) { @@ -4830,6 +4677,9 @@ void K_DoSneaker(player_t *player, INT32 type) const fixed_t intendedboost = K_GetSneakerBoostSpeed(); const tic_t sneakerduration = (type == SNEAKERTYPE_WATERPANEL) ? waterpaneltime : sneakertime; + if (LUA_HookKartSneaker(player, type)) + return; + if (player->floorboost == 0 || player->floorboost == 3) { K_SneakerPanelStackSound(player); @@ -4864,76 +4714,6 @@ void K_DoSneaker(player_t *player, INT32 type) player->boostangle = player->mo->angle; } -static void K_DoShrink(player_t *user) -{ - INT32 i; - mobj_t *mobj, *next; - - S_StartSound(user->mo, sfx_kc46); // Sound the BANG! - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator || !players[i].mo) - continue; - if (&players[i] == user) - continue; - if (players[i].position < user->position) - { - //P_FlashPal(&players[i], PAL_NUKE, 10); - - // Grow should get taken away. - if (players[i].growshrinktimer > 0) - K_RemoveGrowShrink(&players[i]); - else - { - // Start shrinking! - K_DropItems(&players[i]); - players[i].growshrinktimer = -(15*TICRATE); - - if (players[i].mo && !P_MobjWasRemoved(players[i].mo)) - { - players[i].mo->scalespeed = mapobjectscale/TICRATE; - players[i].mo->destscale = FixedMul(mapobjectscale, SHRINK_SCALE); - - if (K_PlayerShrinkCheat(&players[i]) == true) - { - players[i].mo->destscale = FixedMul(players[i].mo->destscale, SHRINK_SCALE); - } - - S_StartSound(players[i].mo, sfx_kc59); - } - } - } - } - - // kill everything in the kitem list while we're at it: - for (mobj = kitemcap; mobj; mobj = next) - { - next = mobj->itnext; - - // 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) - { - 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 - - if (mobj->type == MT_SPB) - spbplace = -1; - } -} - void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) { const fixed_t vscale = mapobjectscale + (mo->scale - mapobjectscale); @@ -4993,42 +4773,10 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) S_StartSound(mo, (sound == 1 ? sfx_kc2f : sfx_kpogos)); } -static void K_ThrowLandMine(player_t *player) -{ - mobj_t *landMine; - mobj_t *throwmo; - - landMine = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_LANDMINE); - K_FlipFromObject(landMine, player->mo); - landMine->threshold = 10; - - if (landMine->info->seesound) - S_StartSound(player->mo, landMine->info->seesound); - - P_SetTarget(&landMine->target, player->mo); - - P_SetScale(landMine, player->mo->scale); - landMine->destscale = player->mo->destscale; - - landMine->angle = player->mo->angle; - - landMine->momz = (30 * mapobjectscale * P_MobjFlip(player->mo)) + player->mo->momz; - landMine->color = player->skincolor; - - throwmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FIREDITEM); - P_SetTarget(&throwmo->target, player->mo); - // Ditto: - if (player->mo->eflags & MFE_VERTICALFLIP) - { - throwmo->z -= player->mo->height; - throwmo->eflags |= MFE_VERTICALFLIP; - } - - throwmo->movecount = 0; // above player -} - void K_DoInvincibility(player_t *player, tic_t time) { + const boolean isalt = K_IsKartItemAlternate(KITEM_INVINCIBILITY); + if (!player->invincibilitytimer) { mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INVULNFLASH); @@ -5036,7 +4784,7 @@ void K_DoInvincibility(player_t *player, tic_t time) overlay->destscale = player->mo->scale; P_SetScale(overlay, player->mo->scale); - if (K_GetKartInvinType() == KARTINVIN_ALTERN) + if (isalt) { mobj_t *aura = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_OVERLAY); P_SetTarget(&aura->target, player->mo); @@ -5046,7 +4794,7 @@ void K_DoInvincibility(player_t *player, tic_t time) } } - if (K_GetKartInvinType() == KARTINVIN_ALTERN) + if (isalt) { // Rim suggestion: Don't allow already invincible players to chain. if (K_InvincibilityGradient(player->invincibilitytimer) < (FRACUNIT/2)) @@ -5060,7 +4808,14 @@ void K_DoInvincibility(player_t *player, tic_t time) player->invincibilitytimer = time; } - player->maxinvincibilitytime = time; + player->maxinvincibilitytime = player->invincibilitytimer; + + if (player->maxinvincibilitytime <= MININVINTIME && isalt) + { + // Merritt suggestion: Kill bottlenecking if you get a short Invincibility. + // Anti-bottleneck code 2: Signify to play the warning signal later than usual! + player->invincibilitybottleneck = (UINT16)(-2); + } if (P_IsLocalPlayer(player) == true) { @@ -5325,64 +5080,6 @@ void K_DropHnextList(player_t *player) } } -#define MAXSHARDCOUNT 40 -#define SHARDROT (360 * FRACUNIT / 40) -static void K_BreakBubbleShield(player_t* player) -{ - if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) - return; - - const INT32 flip = P_MobjFlip(player->mo); - const fixed_t scalediff = player->shieldtracer->scale - mapobjectscale; - const fixed_t shieldrad = player->shieldtracer->radius; - - for (INT32 i = 0; i < MAXSHARDCOUNT; i++) - { - vector2_t mul_vec = { FRACUNIT, 0 }; - - fixed_t randang = SHARDROT * (i + 1); - fixed_t randzang = P_RandomRange(10, 120) * FRACUNIT; - fixed_t move_magnitude = - FixedMul((P_RandomRange(4, 16) * FRACUNIT), mapobjectscale + (scalediff / 4)); - - FV2_Rotate(&mul_vec, randzang); - - // Do shitty initial 3D rotations around the shield's radius. - mobj_t *shard = P_SpawnMobj( - player->shieldtracer->x + - FixedMul(FixedMul(mul_vec.x, shieldrad), FCOS(FixedAngle(randang))), - player->shieldtracer->y + - FixedMul(FixedMul(mul_vec.x, shieldrad), FSIN(FixedAngle(randang))), - player->shieldtracer->z + ((player->shieldtracer->height + scalediff) / 4) - + FixedMul(mul_vec.y * flip, shieldrad), - MT_BUBBLESHLD_DEBRIS); - - if (!P_MobjWasRemoved(shard)) - { - //CONS_Printf(M_GetText("randzang: %d, randang: %d\n"), randzang / FRACUNIT, randang / FRACUNIT); - - mul_vec.x = FixedMul(move_magnitude, mul_vec.x); - mul_vec.y = FixedMul(move_magnitude, mul_vec.y * flip); - - vector3_t mom = { - .x = FixedMul(mul_vec.x, FCOS(FixedAngle(randang))), - .y = FixedMul(mul_vec.x, FSIN(FixedAngle(randang))), - .z = mul_vec.y, - }; - - shard->momx = mom.x + player->mo->momx; - shard->momy = mom.y + player->mo->momy; - shard->momz = mom.z + player->mo->momz; - - shard->extravalue1 = i % 5; // 20% chance to play the shard sound - } - } - - S_StartSound(player->mo, sfx_kc41); -} -#undef MAXSHARDCOUNT -#undef SHARDROT - // For getting hit! void K_PopPlayerShield(player_t *player) { @@ -5425,7 +5122,7 @@ void K_PopPlayerShield(player_t *player) } // Returns true is the bubble is actively in defense mode (inflating or inflated) -boolean K_IsBubbleDefending(player_t *player) +boolean K_IsBubbleDefending(const player_t *player) { if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) return false; return (player->bubbleblowup); @@ -5485,7 +5182,7 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 if (type == 0) { UINT8 useodds = 0; - INT32 spawnchance[NUMKARTRESULTS]; + INT32 spawnchance[MAXKARTRESULTS]; INT32 totalspawnchance = 0; INT32 i; @@ -5493,45 +5190,53 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 useodds = amount; - for (i = 1; i < NUMKARTRESULTS; i++) + kartroulette_t roulette = { + .pdis = 0, + .playerpos = 0, + .pos = useodds, + .ourDist = UINT32_MAX, + .clusterDist = 0, + .mashed = 0, + .spbrush = false, + .bot = false, + .rival = false, + .inBottom = false + }; + + K_KartGetItemOdds(&roulette, spawnchance); + + for (i = 0; i < numkartresults; i++) { - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - useodds, i, - UINT32_MAX, - 0, - 0, - false, false, false, false - ) - ); + totalspawnchance += spawnchance[i]; + spawnchance[i] = totalspawnchance; } if (totalspawnchance > 0) { totalspawnchance = P_RandomKey(totalspawnchance); - for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); + for (i = 0; i < numkartresults && spawnchance[i] <= totalspawnchance; i++); // TODO: this is bad! // K_KartGetItemResult requires a player // but item roulette will need rewritten to change this - const UINT8 newType = K_ItemResultToType(i); - const UINT8 newAmount = K_ItemResultToAmount(i); + const kartresult_t *result = &kartresults[i]; - if (newAmount > 1) + if (result->amount > 1) { UINT8 j; - for (j = 0; j < newAmount-1; j++) + for (j = 0; j < result->amount-1; j++) { K_CreatePaperItem( x, y, z, angle, flip, - newType, 1 + result->type, 1 ); } } - drop->threshold = newType; + drop->threshold = result->type; drop->movecount = 1; } else @@ -6367,6 +6072,7 @@ static void K_UpdateInvincibilitySounds(player_t *player) sfxnum = cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow; else if (player->invincibilitytimer > 0 && (!localplayer || cv_supermusic.value == 2)) sfxnum = cv_kartinvinsfx.value ? sfx_alarmi : sfx_kinvnc; + // FIXME: Does Alt. Shrink need an alarm? } if (sfxnum != sfx_None && !S_SoundPlaying(player->mo, sfxnum)) @@ -7260,6 +6966,15 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->sneakertimer) player->sneakertimer = K_ChainOrDeincrementTime(player, player->sneakertimer, 1, false); + if (player->itemusecooldown) + { + player->itemusecooldown--; + if (player->itemusecooldown <= 0) + { + player->itemusecooldownmax = 0; + } + } + if (player->sneakertimer <= 0) { player->mo->flags2 &= ~MF2_WATERRUN; @@ -7287,14 +7002,15 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->invincibilitytimer) { INT16 invinfac = 1; - if ((K_GetKartInvinType() == KARTINVIN_ALTERN) && (player->invincibilitytimer > 2)) + if ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && + (player->invincibilitytimer > 2)) { UINT32 invindist = INVINDIST >> 2; // Value to subtract from the Invincibility timer during bottlenecking. INT16 invin_subtrahend = 1; - if ((INT16)(player->invincibilitybottleneck) != -1) + if ((INT16)(player->invincibilitybottleneck) >= 0) { if (player->distancefromcluster < invindist) { @@ -7322,9 +7038,22 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->invincibilitytimer = (UINT16)(max( 2, (INT32)(player->invincibilitytimer) - invin_subtrahend)); - if ((K_InvincibilityGradient(player->invincibilitytimer) < - (FRACHALF * invin_subtrahend)) && - (player->maxinvincibilitytime >= (BASEINVINTIME - TICRATE))) + const boolean warning_cond_standard = + ((K_InvincibilityGradient(player->invincibilitytimer) < + (FRACHALF * invin_subtrahend)) && + (player->maxinvincibilitytime >= (BASEINVINTIME - TICRATE))); + + const boolean warning_cond_nobottleneck = + ((K_InvincibilityGradient(player->invincibilitytimer) < + SecsToFixedTens(MININVINTIME / 2)) && + (player->maxinvincibilitytime >= (MININVINTIME - TICRATE))); + + const boolean warning_cond = + ((INT16)(player->invincibilitybottleneck) == -2) + ? warning_cond_nobottleneck + : warning_cond_standard; + + if (warning_cond) { if (!player->invincibilitywarning) { @@ -7474,7 +7203,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->itemblink && player->itemblink-- <= 0) { - player->itemblinkmode = KITEMBLINKMODE_NORMAL; + player->itemblinkmode = KITEMBLINK_NORMAL; player->itemblink = 0; } @@ -7581,8 +7310,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_KartItemRoulette(player, cmd); // Gain rings when roulette starts. - if (player->itemroulette > KROULETTE_DISABLED - && player->previtemroulette == KROULETTE_DISABLED + if (player->itemroulette > 0 + && player->previtemroulette == 0 && player->roulettetype != KROULETTETYPE_EGGMAN) { K_AwardScaledPlayerRings(player, ASR_ITEMBOX); @@ -7685,7 +7414,7 @@ void K_KartResetPlayerColor(player_t *player) { boolean skip = false; - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) { fullbright = true; @@ -7710,7 +7439,7 @@ void K_KartResetPlayerColor(player_t *player) if (player->growshrinktimer % 5 == 0) { player->mo->colorized = true; - player->mo->color = (player->growshrinktimer < 0 ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE); + player->mo->color = player->growshrinktimer < 0 ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE; fullbright = true; goto finalise; } @@ -8985,8 +8714,11 @@ INT16 K_GetKartTurnValue(const player_t *player, INT16 turnvalue) if (K_SlipdashActive() && K_Sliptiding(player)) // slight handling boost based on weight turnvalue = FixedMul(turnvalue, FRACUNIT + (10 - player->kartweight)*FRACUNIT/48); - if (player->invincibilitytimer || player->sneakertimer || player->bubbleboost || player->growshrinktimer > 0) - turnvalue = FixedMul(turnvalue, FixedDiv(5*FRACUNIT, 4*FRACUNIT)); + if (player->invincibilitytimer || player->sneakertimer || player->bubbleboost || + player->growshrinktimer > 0 || K_IsAltShrunk(player)) + { + turnvalue = FixedMul(turnvalue, FixedDiv(5 * FRACUNIT, 4 * FRACUNIT)); + } if (player->flamedash && player->flamestore) // Reduce turning { @@ -9069,7 +8801,7 @@ static void K_SpawnDriftEFX(player_t *player,SINT8 level) // Sliptide conditions for Alternative Invincibility. static boolean K_AltInvinSliptideCondition(player_t *player) { - if (K_GetKartInvinType() != KARTINVIN_ALTERN) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) { // Not in Alternative, hit the bricks! return false; @@ -9962,9 +9694,9 @@ void K_StripItems(player_t *player) player->itemflags &= ~IF_EGGMANOUT; K_UnsetItemOut(player); - if (!player->itemroulette || player->roulettetype != 2) + if (!player->itemroulette || player->roulettetype != KROULETTETYPE_EGGMAN) { - player->itemroulette = KROULETTE_DISABLED; + player->itemroulette = 0; player->roulettetype = KROULETTETYPE_NORMAL; } @@ -9983,7 +9715,7 @@ void K_StripItems(player_t *player) void K_StripOther(player_t *player) { - player->itemroulette = KROULETTE_DISABLED; + player->itemroulette = 0; player->roulettetype = KROULETTETYPE_NORMAL; player->invincibilitytimer = 0; @@ -10222,7 +9954,7 @@ void K_UnsetItemOut(player_t *player) static boolean K_InvincibilitySlotHogging(player_t *player) { - if (K_GetKartInvinType() == KARTINVIN_LEGACY) + if (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)) return false; return ((player->invincibilitytimer) != 0); @@ -10264,7 +9996,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) || player->itemroulette || player->rocketsneakertimer || player->eggmanexplode - || ((K_GetKartInvinType() == KARTINVIN_ALTERN) && player->invincibilitytimer) + || ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && player->invincibilitytimer) || (player->growshrinktimer > 0) || player->flametimer || (leveltime < starttime) @@ -10344,7 +10076,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } } // Other items - else + // TODO: how should player->itemusecooldown interact with LUA_HookPlayerItem? + else if (player->itemusecooldown == 0) { // Eggman Monitor exploding if (player->eggmanexplode) @@ -10505,511 +10238,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else { - switch (player->itemtype) - { - case KITEM_SNEAKER: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO) - { - K_DoSneaker(player, SNEAKERTYPE_SNEAKER); - K_PlayBoostTaunt(player->mo); - player->itemamount--; - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_ROCKETSNEAKER: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO - && player->rocketsneakertimer == 0) - { - INT32 moloop; - mobj_t *mo = NULL; - mobj_t *prev = player->mo; - - K_PlayBoostTaunt(player->mo); - S_StartSound(player->mo, sfx_s3k3a); - - //K_DoSneaker(player, SNEAKERTYPE_ROCKETSNEAKER); - - player->rocketsneakertimer = (itemtime*3); - player->itemamount--; - K_UpdateHnextList(player, true); - - for (moloop = 0; moloop < 2; moloop++) - { - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ROCKETSNEAKER); - K_MatchGenericExtraFlags(mo, player->mo); - mo->flags |= MF_NOCLIPTHING; - mo->angle = player->mo->angle; - mo->threshold = 10; - mo->movecount = moloop%2; - mo->movedir = mo->lastlook = moloop+1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_INVINCIBILITY: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage in Legacy, so you're free to waste it if you have multiple - { - K_DoInvincibility(player, K_GetInvincibilityTime(player)); - K_PlayPowerGloatSound(player->mo); - player->itemamount--; - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_BANANA: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - INT32 moloop; - mobj_t *mo; - mobj_t *prev = player->mo; - - //K_PlayAttackTaunt(player->mo); - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s254); - - for (moloop = 0; moloop < player->itemamount; moloop++) - { - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD); - if (!mo) - { - player->itemamount = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = player->itemamount; - mo->movedir = moloop+1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - - } - else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Banana x3 thrown - { - player->itemamount--; - - mobj_t *mo = K_ThrowKartItem(player, false, MT_BANANA, -1, 0); - mo->color = player->skincolor; - - K_PlayAttackTaunt(player->mo); - K_UpdateHnextList(player, false); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_EGGMAN: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - player->itemamount--; - player->itemflags |= IF_EGGMANOUT; - S_StartSound(player->mo, sfx_s254); - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD); - if (mo) - { - K_FlipFromObject(mo, player->mo); - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = 1; - mo->movedir = 1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&player->mo->hnext, mo); - } - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_ORBINAUT: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - angle_t newangle; - INT32 moloop; - mobj_t *mo = NULL; - mobj_t *prev = player->mo; - - //K_PlayAttackTaunt(player->mo); - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s3k3a); - - for (moloop = 0; moloop < player->itemamount; moloop++) - { - newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD); - if (!mo) - { - player->itemamount = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->angle = newangle; - mo->threshold = 10; - mo->movecount = player->itemamount; - mo->movedir = mo->lastlook = moloop+1; - mo->color = player->skincolor; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - } - else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) // Orbinaut x3 thrown - { - player->itemamount--; - K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0); - K_PlayAttackTaunt(player->mo); - K_UpdateHnextList(player, false); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_JAWZ: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - angle_t newangle; - INT32 moloop; - mobj_t *mo = NULL; - mobj_t *prev = player->mo; - - //K_PlayAttackTaunt(player->mo); - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s3k3a); - - for (moloop = 0; moloop < player->itemamount; moloop++) - { - newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->itemamount) * moloop) << FRACBITS) + ANGLE_90; - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD); - if (!mo) - { - player->itemamount = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->angle = newangle; - mo->threshold = 10; - mo->movecount = player->itemamount; - mo->movedir = mo->lastlook = moloop+1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - K_BotResetItemConfirm(player, false); - } - else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Jawz thrown - { - player->itemamount--; - if (player->throwdir == 1 || player->throwdir == 0) - K_ThrowKartItem(player, true, MT_JAWZ, 1, 0); - else if (player->throwdir == -1) // Throwing backward gives you a dud that doesn't home in - K_ThrowKartItem(player, true, MT_JAWZ_DUD, -1, 0); - K_PlayAttackTaunt(player->mo); - K_UpdateHnextList(player, false); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_MINE: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s254); - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SSMINE_SHIELD); - if (mo) - { - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = 1; - mo->movedir = 1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&player->mo->hnext, mo); - } - K_BotResetItemConfirm(player, false); - } - else if (ATTACK_IS_DOWN && (player->itemflags & IF_ITEMOUT)) - { - player->itemamount--; - K_ThrowKartItem(player, false, MT_SSMINE, 1, 1); - K_PlayAttackTaunt(player->mo); - player->itemflags &= ~IF_ITEMOUT; - K_UpdateHnextList(player, true); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_LANDMINE: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_ThrowLandMine(player); - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_BALLHOG: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0); - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SPB: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_ThrowKartItem(player, true, MT_SPB, 1, 0); - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_GROW: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - if (player->growshrinktimer < 0) - { - // If you're shrunk, then "grow" will just make you normal again. - K_RemoveGrowShrink(player); - } - else - { - K_PlayPowerGloatSound(player->mo); - - player->mo->scalespeed = mapobjectscale/TICRATE; - player->mo->destscale = FixedMul(mapobjectscale, GROW_SCALE); - - if (K_PlayerShrinkCheat(player) == true) - { - player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); - } - - player->growshrinktimer = (gametyperules & GTR_CLOSERPLAYERS ? 8 : 12) * TICRATE; - - if (player->invincibilitytimer > 0) - { - ; // invincibility has priority in P_RestoreMusic, no point in starting here - } - else if (P_IsLocalPlayer(player) == true) - { - S_ChangeMusicSpecial("kgrow"); - } - else //used to be "if (P_IsDisplayPlayer(player) == false)" - { - S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); - } - - P_RestoreMusic(player); - S_StartSound(player->mo, sfx_kc5a); - } - - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SHRINK: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - K_DoShrink(player); - player->itemamount--; - K_PlayPowerGloatSound(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_THUNDERSHIELD: - if (K_GetShieldFromPlayer(player) != KSHIELD_THUNDER) - { - mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_THUNDERSHIELD); - P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); - P_SetTarget(&shield->target, player->mo); - P_SetTarget(&player->shieldtracer, shield); - S_StartSound(player->mo, sfx_s3k41); - } - - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - K_DoThunderShield(player); - if (player->itemamount > 0) - { - // Why is this a conditional? - // Thunder shield: the only item that allows you to - // activate a mine while you're out of its radius, - // the SAME tic it sets your itemamount to 0 - // ...:dumbestass: - player->itemamount--; - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - } - break; - case KITEM_BUBBLESHIELD: - if (K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) - { - mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BUBBLESHIELD); - P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); - P_SetTarget(&shield->target, player->mo); - P_SetTarget(&player->shieldtracer, shield); - S_StartSound(player->mo, sfx_s3k3f); - if (player->bubblehealth <= 0 || player->bubblehealth > MAXBUBBLEHEALTH) - player->bubblehealth = MAXBUBBLEHEALTH; - } - - if (!HOLDING_ITEM && NO_HYUDORO) - { - if ((buttons & BT_ATTACK && player->itemflags & IF_HOLDREADY) - || (player->bubbleblowup > 0 && player->bubblecool <= bubbletime/2)) // auto - { - if (player->bubblecool == 0) - S_StartSound(player->mo, sfx_s3k75); - - if (player->bubblecool < bubbletime && player->bubblehealth > 0) - { - player->bubbleblowup += 3; - player->bubblehealth--; - } - else if (player->bubblecool >= bubbletime) - player->bubbleblowup++; // overcharge bonus - - if (++player->bubblecool >= bubbletime + TICRATE/2) - { - // If you overcharge the Bubble Shield at - // any point, it pops and gives you a boost. - K_PopPlayerShield(player); - - if (onground) - { - // If you're on the ground, you're going to - // immediately feel the effects of this boost. - // Play a sound to let everyone know! - S_StartSoundAtVolume(player->mo, sfx_cdfm57, 170); - K_PlayBoostTaunt(player->mo); - } - - // experiment: don't boost, just give invulnerability - if (cv_kartbubble_boost_allow.value) - { - player->bubbleboost = 7 * sneakertime / 10; - } - player->flashing = 4*TICRATE/7; - } - } - else - { - if (player->bubblecool > bubbletime) - player->bubblecool = bubbletime; - - if (player->bubbleblowup > 0) - { - player->bubbleblowup--; - if (player->bubbleblowup == 0) - K_BotResetItemConfirm(player, false); - if (player->bubblecool < bubbletime) - player->bubblecool++; - } - - if (player->bubbleblowup == 0 && player->bubblecool > 0) - { - player->bubblecool--; - - if (player->bubblecool == 0 && player->bubblehealth <= 0) - K_PopPlayerShield(player); - } - - if (buttons & BT_ATTACK || player->bubblecool > 0) - { - player->itemflags &= ~IF_HOLDREADY; - } - else - { - player->itemflags |= IF_HOLDREADY; - } - } - } - break; - case KITEM_FLAMESHIELD: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO && K_GetShieldFromPlayer(player) != KSHIELD_FLAME) - { - player->itemamount--; - player->flametimer = (itemtime*3); - mobj_t *shield = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FLAMESHIELD); - P_SetScale(shield, (shield->destscale = (5*shield->destscale)>>2)); - P_SetTarget(&shield->target, player->mo); - P_SetTarget(&player->shieldtracer, shield); - S_StartSound(player->mo, sfx_s3k3e); - } - break; - case KITEM_HYUDORO: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_DoHyudoroSteal(player); // yes. yes they do. - K_PlayAttackTaunt(player->mo); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_POGOSPRING: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO && player->pogospring == 0) - { - player->itemamount--; - K_PlayBoostTaunt(player->mo); - K_DoPogoSpring(player->mo, 32<pogospring = 1; - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SUPERRING: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->itemamount--; - K_AwardScaledPlayerRings(player, ASR_SUPERRING); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_KITCHENSINK: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - K_SetItemOut(player); - S_StartSound(player->mo, sfx_s254); - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SINK_SHIELD); - if (mo) - { - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = 1; - mo->movedir = 1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&player->mo->hnext, mo); - } - K_BotResetItemConfirm(player, false); - - // Woah this could be big, lets get the inside scoop... - K_DirectorForceSwitch(player - players, 1); - } - else if (ATTACK_IS_DOWN && HOLDING_ITEM && (player->itemflags & IF_ITEMOUT)) // Sink thrown - { - player->itemamount--; - K_ThrowKartItem(player, false, MT_SINK, 1, 2); - K_PlayAttackTaunt(player->mo); - player->itemflags &= ~IF_ITEMOUT; - K_UpdateHnextList(player, true); - K_BotResetItemConfirm(player, false); - } - break; - case KITEM_SAD: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO - && !player->sadtimer) - { - player->sadtimer = stealtime; - player->itemamount--; - K_BotResetItemConfirm(player, false); - } - break; - default: - break; - } + K_PlayerItemThink(player, onground); } } } @@ -11024,14 +10253,12 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_RemoveMobj(player->shieldtracer); } + if (player->growshrinktimer == 0) + player->altshrinktimeshit = 0; + if (player->growshrinktimer <= 0) player->growcancel = -1; - if (player->itemtype == KITEM_SPB - || player->itemtype == KITEM_SHRINK - || player->growshrinktimer < 0) - indirectitemcooldown = 20*TICRATE; - if (player->hyudorotimer > 0) { INT32 hyu = hyudorotime; @@ -11299,16 +10526,6 @@ void K_CheckSpectateStatus(boolean considermapreset) } } -UINT8 K_GetInvincibilityItemFrame(void) -{ - return ((leveltime % (7*3)) / 3); -} - -UINT8 K_GetMultItemFrame(UINT8 count, UINT8 max) -{ - return min(count - 1, max); -} - boolean K_IsSPBInGame(void) { UINT8 i; @@ -11451,11 +10668,6 @@ boolean K_ItemPushingActive(void) return itempushing; } -boolean K_GetKartInvinType(void) -{ - return invintype; -} - boolean K_UsingLegacyCheckpoints(void) { if (K_UsingPatchedMap() && waypointcap != NULL) @@ -11491,41 +10703,6 @@ INT32 K_CheckpointThreshold(boolean roundup) return numstarposts; } -void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount) -{ - switch (itemType) - { - case KITEM_SNEAKER: - part->sprite = SPR_ITSN; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 2); - break; - case KITEM_ORBINAUT: - part->sprite = SPR_ITMO; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 3); - break; - case KITEM_BANANA: - part->sprite = SPR_ITBA; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 3); - break; - case KITEM_JAWZ: - part->sprite = SPR_ITJA; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetMultItemFrame(itemCount, 1); - break; - case KITEM_INVINCIBILITY: - part->sprite = SPR_ITMI; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); - break; - case KITEM_SAD: - part->sprite = SPR_ITEM; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; - break; - default: - part->sprite = SPR_ITEM; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType); - break; - } -} - boolean K_NotFreePlay(void) { UINT8 i; diff --git a/src/k_kart.h b/src/k_kart.h index d800661e7..902a2ffdc 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -33,6 +33,7 @@ Make sure this matches the actual number of states #define SHRINK_SCALE ((6*FRACUNIT)/8) #define BASEINVINTIME (10 * TICRATE) +#define MININVINTIME (7 * TICRATE) #define AUTORESPAWN_TIME (TICRATE/2) #define AUTORESPAWN_THRESHOLD (TICRATE/4) @@ -126,6 +127,11 @@ extern vector3_t clusterpoint, clusterdtf; #define FLAMEACCELBOOST CV_Get(&cv_kartstacking_flame_accelboost) #define FLAMESTACKABLE CV_Get(&cv_kartstacking_flame_stackable) +#define ALTSHRINKTIME CV_Get(&cv_kartaltshrinktime) +#define SHRINKSPEEDBOOST CV_Get(&cv_kartstacking_altshrink_speedboost) +#define SHRINKACCELBOOST CV_Get(&cv_kartstacking_altshrink_accelboost) +#define SHRINKSTACKABLE CV_Get(&cv_kartstacking_altshrink_stackable) + #define STARTSPEEDBOOST CV_Get(&cv_kartstacking_start_speedboost) #define STARTACCELBOOST CV_Get(&cv_kartstacking_start_accelboost) #define STARTSTACKABLE CV_Get(&cv_kartstacking_start_stackable) @@ -254,12 +260,14 @@ void K_DoSneaker(player_t *player, INT32 type); void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound); fixed_t K_InvincibilityGradient(UINT16 time); UINT16 K_GetInvincibilityTime(player_t *player); +fixed_t K_GetInvincibilitySpeed(UINT16 time); +fixed_t K_GetInvincibilityAccel(UINT16 time); void K_DoInvincibility(player_t *player, tic_t time); void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source); void K_UpdateHnextList(player_t *player, boolean clean); void K_DropHnextList(player_t *player); void K_PopPlayerShield(player_t *player); -boolean K_IsBubbleDefending(player_t *player); +boolean K_IsBubbleDefending(const player_t *player); void K_RemoveBubbleHealth(player_t *player, INT16 sub); boolean K_CheckBubbleChip(const mobj_t *mobj); void K_RepairOrbitChain(mobj_t *orbit); @@ -295,6 +303,7 @@ void K_SpawnWaterTrail(mobj_t *mobj); boolean K_ItemMobjAllowedtoWaterRun(mobj_t *item); void K_SetTireGrease(player_t *player, tic_t tics); boolean K_SlopeResistance(const player_t *player); +void K_RemoveGrowShrink(player_t *player); void K_SquishPlayer(player_t *player, mobj_t *inflictor, mobj_t *source); void K_ApplyTripWire(player_t *player, tripwirestate_t state); fixed_t K_GetKartSpeedFromStat(UINT8 kartspeed, boolean karmabomb); @@ -313,8 +322,6 @@ SINT8 K_Sliptiding(const player_t *player); fixed_t K_PlayerBaseFriction(const player_t *player, fixed_t original); void K_MoveKartPlayer(player_t *player, boolean onground); void K_CheckSpectateStatus(boolean considermapreset); -UINT8 K_GetInvincibilityItemFrame(void); -UINT8 K_GetMultItemFrame(UINT8 count, UINT8 max); boolean K_IsSPBInGame(void); // sound stuff for lua @@ -344,7 +351,6 @@ boolean K_DraftingActive(void); boolean K_AirDropActive(void); boolean K_ItemLitterActive(void); boolean K_ItemPushingActive(void); -boolean K_GetKartInvinType(void); INT32 K_GetBumpSpark(void); boolean K_BoostChain(player_t *player, INT32 timer, boolean chainsound); INT32 K_ChainOrDeincrementTime(player_t *player, INT32 timer, INT32 deincrement, boolean chainsound); @@ -352,8 +358,6 @@ boolean K_UsingLegacyCheckpoints(void); boolean K_UsingPatchedMap(void); INT32 K_CheckpointThreshold(boolean roundup); -void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount); - fixed_t K_BoostRescale(fixed_t value,fixed_t oldmin,fixed_t oldmax,fixed_t newmin,fixed_t newmax); void K_DoBoost(player_t *player, fixed_t speedboost, fixed_t accelboost, boolean stack, boolean visible); void K_ClearBoost(player_t *player); diff --git a/src/k_odds.c b/src/k_odds.c deleted file mode 100644 index a5f7d5747..000000000 --- a/src/k_odds.c +++ /dev/null @@ -1,1561 +0,0 @@ -// BLANKART -//----------------------------------------------------------------------------- -// Copyright (C) 2018-2025 by Kart Krew. -// Copyright (C) 2025 by "Anonimus". -// Copyright (C) 2025 Blankart Team. -// -// 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 k_odds.cpp -/// \brief Kart item odds systems. - -// SRB2kart Roulette Code - Position Based - -#include "doomdef.h" -#include "doomstat.h" -#include "d_netcmd.h" -#include "d_player.h" -#include "g_game.h" -#include "info.h" -#include "m_easing.h" // Invincibility gradienting -#include "m_fixed.h" -#include "m_random.h" -#include "p_local.h" -#include "p_mobj.h" -#include "p_setup.h" -#include "s_sound.h" -#include "typedef.h" - -#include "k_battle.h" -#include "k_boss.h" -#include "k_bot.h" -#include "k_kart.h" -#include "k_waypoint.h" - -#include "k_cluster.hpp" -#include "k_odds.h" - -consvar_t *KartItemCVars[NUMKARTRESULTS-1] = -{ - &cv_sneaker, - &cv_rocketsneaker, - &cv_invincibility, - &cv_banana, - &cv_eggmanmonitor, - &cv_orbinaut, - &cv_jawz, - &cv_mine, - &cv_ballhog, - &cv_selfpropelledbomb, - &cv_grow, - &cv_shrink, - &cv_thundershield, - &cv_hyudoro, - &cv_pogospring, - &cv_kitchensink, - &cv_superring, - &cv_landmine, - &cv_bubbleshield, - &cv_flameshield, - &cv_dualsneaker, - &cv_triplesneaker, - &cv_triplebanana, - &cv_decabanana, - &cv_tripleorbinaut, - &cv_quadorbinaut, - &cv_dualjawz -}; - -#define NUMKARTODDS (MAXODDS*10) - -// Base multiplication to ALL item odds to simulate fractional precision. -// Reduced from 4 to 1 due to 75-count probability. -#define BASEODDSMUL 1 - -// Multiplication to odds for Battle item odds specifically. -#define BATTLEODDSMUL 4 - -// Maximum probability. -#define REALMAXPROB 75 -#define MAXPROBABILITY (REALMAXPROB * BASEODDSMUL) - -#define REALMAXBATTLEPROB 10 -#define MAXBATTLEPROBABILITY (REALMAXBATTLEPROB * BATTLEODDSMUL) - -// Less ugly 2D arrays -// Expanded 16-tier useodds, for more flexible calculations. -// Item odds are now based around the max number being 75 as well. -static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][MAXODDS] = -{ - //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - //B C D E F G H I J K L M N O P Q - { 0, 0, 0, 0, 8, 24, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Sneaker - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 35, 42, 48, 37, 37}, // Rocket Sneaker - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 24, 50, 75, 75}, // Invincibility - {35, 28, 22, 10, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Banana - {18, 15, 12, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Eggman Monitor - {45, 28, 24, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Orbinaut - { 0, 11, 22, 9, 7, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Jawz - { 0, 0, 5, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Mine - { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Ballhog - { 0, 0, 2, 3, 4, 5, 6, 9, 10, 12, 8, 5, 2, 2, 0, 0}, // Self-Propelled Bomb - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 9, 12, 9, 0, 0, 0}, // Grow - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 3, 0, 0}, // Shrink - {12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Thunder Shield - { 0, 0, 0, 0, 0, 2, 2, 4, 4, 2, 0, 0, 0, 0, 0, 0}, // Hyudoro - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Pogo Spring - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Kitchen Sink - { 7, 11, 15, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Super Ring - { 8, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Land Mine - { 0, 0, 0, 12, 15, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Bubble Shield - { 0, 0, 0, 0, 0, 0, 2, 4, 12, 24, 27, 12, 7, 4, 0, 0}, // Flame Shield - { 0, 0, 0, 0, 0, 0, 8, 20, 40, 12, 0, 0, 0, 0, 0, 0}, // Sneaker x2 - { 0, 0, 0, 0, 0, 0, 0, 7, 35, 45, 60, 56, 52, 4, 0, 0}, // Sneaker x3 - { 0, 7, 7, 7, 9, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Banana x3 - { 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Banana x10 - { 0, 0, 0, 2, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Orbinaut x3 - { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Orbinaut x4 - { 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // Jawz x2 -}; - -static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS][2] = -{ - //R S - { 2, 1 }, // Sneaker - { 0, 0 }, // Rocket Sneaker - { 4, 1 }, // Invincibility - { 0, 0 }, // Banana - { 1, 0 }, // Eggman Monitor - { 8, 0 }, // Orbinaut - { 8, 1 }, // Jawz - { 6, 1 }, // Mine - { 2, 1 }, // Ballhog - { 0, 0 }, // Self-Propelled Bomb - { 2, 1 }, // Grow - { 0, 0 }, // Shrink - { 4, 0 }, // Thunder Shield - { 2, 0 }, // Hyudoro - { 3, 0 }, // Pogo Spring - { 0, 0 }, // Kitchen Sink - { 0, 0 }, // Super Ring - { 2, 0 }, // Land Mine - { 1, 0 }, // Bubble Shield - { 1, 0 }, // Flame Shield - { 0, 0 }, // Sneaker x2 - { 0, 1 }, // Sneaker x3 - { 0, 0 }, // Banana x3 - { 1, 1 }, // Banana x10 - { 2, 0 }, // Orbinaut x3 - { 1, 1 }, // Orbinaut x4 - { 5, 1 } // Jawz x2 -}; - -/* -static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = -{ - //M N O P - { 1, 1, 0, 0 }, // Sneaker - { 0, 0, 0, 0 }, // Rocket Sneaker - { 0, 0, 0, 0 }, // Invincibility - { 0, 0, 0, 0 }, // Banana - { 0, 0, 0, 0 }, // Eggman Monitor - { 1, 1, 0, 0 }, // Orbinaut - { 1, 1, 0, 0 }, // Jawz - { 0, 0, 0, 0 }, // Mine - { 0, 0, 0, 0 }, // Ballhog - { 0, 0, 0, 1 }, // Self-Propelled Bomb - { 0, 0, 0, 0 }, // Grow - { 0, 0, 0, 0 }, // Shrink - { 0, 0, 0, 0 }, // Thunder Shield - { 0, 0, 0, 0 }, // Hyudoro - { 0, 0, 0, 0 }, // Pogo Spring - { 0, 0, 0, 0 }, // Kitchen Sink - { 0, 0, 0, 0 }, // Super Ring - { 0, 0, 0, 0 }, // Land Mine - { 0, 0, 0, 0 }, // Bubble Shield - { 0, 0, 0, 0 }, // Flame Shield - { 0, 1, 1, 0 }, // Sneaker x2 - { 0, 0, 1, 1 }, // Sneaker x3 - { 0, 0, 0, 0 }, // Banana x3 - { 0, 0, 0, 0 }, // Banana x10 - { 0, 1, 1, 0 }, // Orbinaut x3 - { 0, 0, 1, 1 }, // Orbinaut x4 - { 0, 0, 1, 1 } // Jawz x2 -}; -*/ - -// Cooldown time table; contains both base (index 0) and current (index 1) -// times. Base times are in seconds, current times are in tics. -tic_t ItemBGone[NUMKARTRESULTS][2] = -{ - //BASE CURR - { 0, 0 }, // Null/Sad Face - { 0, 0 }, // Sneaker - { 0, 0 }, // Rocket Sneaker - { 0, 0 }, // Invincibility - { 0, 0 }, // Banana - { 10, 0 }, // Eggman Monitor - { 0, 0 }, // Orbinaut - { 5, 0 }, // Jawz - { 0, 0 }, // Mine - { 10, 0 }, // Ballhog - { 20, 0 }, // Self-Propelled Bomb - { 3, 0 }, // Grow - { 20, 0 }, // Shrink - { 0, 0 }, // Thunder Shield - { 20, 0 }, // Hyudoro - { 0, 0 }, // Pogo Spring - { 0, 0 }, // Kitchen Sink - { 0, 0 }, // Super Ring - { 0, 0 }, // Land Mine - { 5, 0 }, // Bubble Shield - { 8, 0 }, // Flame Shield - { 0, 0 }, // Sneaker x2 - { 0, 0 }, // Sneaker x3 - { 0, 0 }, // Banana x3 - { 30, 0 }, // Banana x10 - { 10, 0 }, // Orbinaut x3 - { 20, 0 }, // Orbinaut x4 - { 10, 0 } // Jawz x2 -}; - -// TODO: Vectorize all item tables and shove them into gamemode-uniqe pools (k_oddstable.cpp?). -// Basically necessary for any hopes of easy SOC support in the near future. - -/** \brief Sets the cooldown timer for an item. When active, the item is removed - * from all players' item pools for some time. - - \param item (SINT8) item to assign cooldown for - \param time (tic_t) cooldown time - - \return void -*/ -void K_SetBGone(SINT8 item, tic_t time) -{ - ItemBGone[item][GONER_CURRCOOLDOWN] = time; -} - -/** \brief Sets the cooldown timer for an item to its "base" (intended) time. - - \param item (SINT8) item to assign base cooldown for - - \return void -*/ -void K_SetBGoneToBase(SINT8 item) -{ - ItemBGone[item][GONER_CURRCOOLDOWN] = (ItemBGone[item][GONER_BASECOOLDOWN] * TICRATE); -} - -/** \brief Gets the cooldown timer for an item. - - \param item (SINT8) item to retrieve cooldown for - \param base (bool) if true, returns the "base" (intended) cooldown instead - - \return (tic_t) cooldown time -*/ -tic_t K_GetBGone(SINT8 item, boolean base) -{ - return ItemBGone[item][base ? GONER_BASECOOLDOWN : GONER_CURRCOOLDOWN] * (base ? TICRATE : 1); -} - -/** \brief Are the odds in legacy distancing mode? - - \return true if kartforcelegacyodds is enabled, or the map uses legacy WPs -*/ -boolean K_LegacyOddsMode(void) -{ - return (K_UsingLegacyCheckpoints() || (cv_kartforcelegacyodds.value)); -} - -// Magic number distance for use with item roulette tiers -#define ACTIVEDISTVAR (K_LegacyOddsMode() ? DISTVAR_LEGACY : DISTVAR) -#define ACTIVESPBDIST (K_LegacyOddsMode() ? SPBDISTVAR_LEGACY : SPBDISTVAR) - -#define SPBSTARTDIST (ACTIVESPBDIST) // Distance when SPB can start appearing -#define SPBFORCEDIST (7 * ACTIVESPBDIST / 2) // Distance when SPB is forced onto 2nd place (3.5x SPBDISTVAR) - -#define ENDDIST (12*ACTIVEDISTVAR) // Distance when the game stops giving you bananas - -// 1/21/2025: I hate tiptoeing around the integer limit. -// This is at a smaller scale. -static UINT32 K_Dist2D(INT32 x1, INT32 y1, INT32 x2, INT32 y2) -{ - // d = √((x2 - x1)² + (y2 - y1)²) - INT32 xdiff, ydiff; - INT64 xprod, yprod; - - xdiff = (x2 - x1); - ydiff = (y2 - y1); - - xprod = ((INT64)xdiff * (INT64)xdiff); - yprod = ((INT64)ydiff * (INT64)ydiff); - - return (UINT32)(IntSqrt64(xprod + yprod)); -} - -// Basic integer distancing, to quote myself: -// "Even if you did 256 units for 1 fracunit in distancing, it'd be a better result than trying to -// deal with overflows on a system that's already being pushed to the limit by needing 65536 units -// for precision. No seriously, I don't think anyone's losing sleep over "hmmmmm, 0.0000152 or -// 0.0039?????" when most 2D game engines only give a fuck about MAYBE 0.001" -/*static UINT32 K_IntDistance(fixed_t curx, - fixed_t cury, - fixed_t curz, - fixed_t destx, - fixed_t desty, - fixed_t destz) -{ - return K_Dist2D(0, - curz / FRACUNIT, - K_Dist2D(curx / FRACUNIT, cury / FRACUNIT, destx / FRACUNIT, desty / FRACUNIT), - destz / FRACUNIT); -}*/ - -// This one uses map scaling instead, use in case of loss of depth on mobjscaled maps. -static UINT32 K_IntDistanceForMap(fixed_t curx, - fixed_t cury, - fixed_t curz, - fixed_t destx, - fixed_t desty, - fixed_t destz) -{ - return K_Dist2D(0, - curz / mapobjectscale, - K_Dist2D(curx / mapobjectscale, - cury / mapobjectscale, - destx / mapobjectscale, - desty / mapobjectscale), - destz / mapobjectscale); -} - -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("K_ItemResultToType: Bad item 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 KRITEM_TENFOLDBANANA: - return 10; - - default: - return 1; - } -} - -/** \brief Item Roulette for Kart - - \param player player - \param getitem what item we're looking for - - \return void -*/ -static void K_KartGetItemResult(player_t *player, SINT8 getitem) -{ - if (getitem == KITEM_SPB || getitem == KITEM_SHRINK) // Indirect items - indirectitemcooldown = 20*TICRATE; - - if (K_GetBGone(getitem, true) > 0) // Item cooldowns - { - K_SetBGoneToBase(getitem); - } - - K_BotResetItemConfirm(player, true); - - player->itemtype = K_ItemResultToType(getitem); - UINT8 itemamount = K_ItemResultToAmount(getitem); - if (cv_kartdebugitem.value != KITEM_NONE && cv_kartdebugitem.value == player->itemtype && cv_kartdebugamount.value > 1) - itemamount = cv_kartdebugamount.value; - player->itemamount = itemamount; -} - -static fixed_t K_ItemOddsScale(UINT8 numPlayers, boolean spbrush) -{ - // CEP: due to how baseplayer works, 17P+ lobbies will STILL have the disastrous odds of 0.22 prior, if not WORSE - // let's try adding another condition - - const UINT8 basePlayer = 16; // The player count we design most of the game around. - const UINT8 vanillaMax = 17; // CEP: Maximum players in "vanilla" (non-30P) clients. - const UINT8 extPlayer = 24; // CEP: Cap for 17P+ so that odds don't get too muddled. - - UINT8 playerCount = (spbrush ? 2 : numPlayers); - fixed_t playerScaling = 0; - - // 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. - // 2P: x1.4 - playerScaling = (basePlayer - playerCount) * (FRACUNIT / 10); - } - else if (playerCount > basePlayer) - { - // More than basePlayer: reduce odds slightly. - - // CEP: 17P+ adjustments - if (playerCount < vanillaMax) - { - // Less than vanillaMax: Use standard calculations. - // 16P: x0.6 - playerScaling = (basePlayer - playerCount) * (FRACUNIT / 20); - - } - else if (playerCount > vanillaMax) - { - // More than vanillaMax: Increase odds to fit with the increased playercount - // 24P: x0.6 - // 30P: x0.45 - playerScaling = (basePlayer - min(extPlayer, playerCount)) * (FRACUNIT / 40); // adding a cap here to be sure - } - } - - return playerScaling; -} - -UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush) -{ - if (mapobjectscale != FRACUNIT) - { - // Bring back to normal scale. - distance = FixedDiv(distance * FRACUNIT, mapobjectscale) / FRACUNIT; - } - - if (franticitems == true) - { - // Frantic items pretends everyone's farther apart, for crazier items. - 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; - } - - return distance; -} - -#define INVODDS 30 - -// Prevent integer overflows; don't let this go past 16383 -#define INVINDESPERATION 4 -#define MAXINVODDS ((MAXPROBABILITY * 2) * INVINDESPERATION) - -// Odds value for Alt. Invin. to force itself on trailing players. -#define INVFORCEODDS (MAXINVODDS / 2) - -static INT32 K_KartGetInvincibilityOdds(UINT32 dist) -{ - UINT32 invindist = INVINDIST/2; - - if (dist < invindist) - return 0; - - INT32 finodds = 0; - fixed_t fac = (min(32000, (fixed_t)dist) * FRACUNIT) / (INVINDIST); - - if (fac > FRACUNIT) - { - // Desperation! Climb exponentially until Invincibility is practically guaranteed. - fac = (((min(32000, (fixed_t)dist) * FRACUNIT) / (INVINDIST)) - FRACUNIT) >> 1; - finodds = Easing_InCubic(fac, INVODDS, MAXINVODDS); - } - else - { - if (fac <= FRACHALF) - { - // Invincibility is practically useless at lower distances. - // At below half, remove it from the item pool. - return 0; - } - // Basic linear climb to "reasonable" odds. - finodds = FixedMul(INVODDS, fac); - } - - return min(MAXINVODDS, finodds); -} - -// Assigns general cooldowns to shield items. -void K_KartHandleShieldCooldown(SINT8 item) -{ - INT32 i; - - UINT8 pingame = 0, pexiting = 0; - - INT32 shieldtype = KSHIELD_NONE; - shieldtype = K_GetShieldFromItem(item); - - if (shieldtype == KSHIELD_NONE) - { - // Not a shield! - return; - } - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - - if (!(gametyperules & GTR_BUMPERS) || players[i].bumper) - pingame++; - - if (players[i].exiting) - pexiting++; - - if (((shieldtype == K_GetShieldFromItem(players[i].itemtype)) - || (shieldtype == K_GetShieldFromPlayer(&players[i])))) - { - // If this shield has a cooldown, force-apply the cooldown preeemptively for - // the entire time the shield is being held by a player. - if (K_GetBGone(item, true) > 0) - { - K_SetBGoneToBase(item); - break; - } - } - } -} - -/** \brief Item Roulette for Kart - - \param player player object passed from P_KartPlayerThink - - \return void -*/ - -INT32 K_KartGetItemOdds( - UINT8 pos, SINT8 item, - UINT32 ourDist, - UINT32 clusterDist, - fixed_t mashed, - boolean spbrush, boolean bot, boolean rival, boolean inBottom) -{ - INT32 newodds; - INT32 i; - - UINT8 pingame = 0, pexiting = 0; - - SINT8 first = -1, second = -1; - UINT32 firstDist = UINT32_MAX; - UINT32 secondToFirst = UINT32_MAX; - - boolean powerItem = false; - boolean cooldownOnStart = false; - boolean indirectItem = false; - boolean notNearEnd = false; - boolean notForBottom = false; - - INT32 shieldtype = KSHIELD_NONE; - - I_Assert(item > KITEM_NONE); // too many off by one scenarioes. - I_Assert(KartItemCVars[NUMKARTRESULTS-2] != NULL); // Make sure this exists - - if (!KartItemCVars[item-1]->value && !modeattacking) - return 0; - - /* - if (bot) - { - // TODO: Item use on bots should all be passed-in functions. - // Instead of manually inserting these, it should return 0 - // for any items without an item use function supplied - - switch (item) - { - case KITEM_SNEAKER: - break; - default: - return 0; - } - } - */ - (void)bot; - - INT32 oddsmul = BASEODDSMUL; - - if (gametyperules & GTR_BATTLEODDS) - { - I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table - newodds = K_KartItemOddsBattle[item-1][pos]; - oddsmul = BATTLEODDSMUL; - } - else if (gametyperules & GTR_RACEODDS) - { - I_Assert(pos < MAXODDS); // Ditto - newodds = K_KartItemOddsRace[item-1][pos]; - } - else - { - newodds = 0; - } - - // Blow up the odds with a multiplier. - newodds *= oddsmul; - - shieldtype = K_GetShieldFromItem(item); - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - - if (!(gametyperules & GTR_BUMPERS) || players[i].bumper) - pingame++; - - if (players[i].exiting) - pexiting++; - - if (shieldtype != KSHIELD_NONE && ((shieldtype == K_GetShieldFromItem(players[i].itemtype)) - || (shieldtype == K_GetShieldFromPlayer(&players[i])))) - { - // Don't allow more than one of each shield type at a time - return 0; - } - - if (players[i].mo && gametype == GT_RACE) - { - if (players[i].position == 1 && first == -1) - first = i; - if (players[i].position == 2 && second == -1) - second = i; - } - } - - if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB - { - - if (!K_LegacyOddsMode()) - { - firstDist = players[first].distancetofinish; - - if (mapobjectscale != FRACUNIT) - { - firstDist = FixedDiv(firstDist * FRACUNIT, mapobjectscale) / FRACUNIT; - } - - secondToFirst = K_ScaleItemDistance( - players[second].distancetofinish - players[first].distancetofinish, - pingame, spbrush - ); - } - else - { - secondToFirst = P_AproxDistance(P_AproxDistance( - players[first].mo->x/4 - players[second].mo->x/4, - players[first].mo->y/4 - players[second].mo->y/4), - players[first].mo->z/4 - players[second].mo->z/4); - - // Scale it to prevent overflow issues. - secondToFirst = (secondToFirst / FRACUNIT)*2; - - secondToFirst = K_ScaleItemDistance( - secondToFirst, - pingame, spbrush - ); - } - } - - switch (item) - { - case KITEM_BANANA: - notNearEnd = true; - notForBottom = true; - break; - case KITEM_EGGMAN: - // It blows you up and is overall ridiculous. This was *overdue*. - cooldownOnStart = true; - powerItem = true; - - notNearEnd = true; - notForBottom = true; - break; - case KITEM_SUPERRING: - notNearEnd = true; - notForBottom = true; - - if ((K_RingsActive() == false)) // No rings rolled if rings are turned off. - { - newodds = 0; - } - - break; - case KITEM_ROCKETSNEAKER: - case KITEM_LANDMINE: - case KRITEM_TRIPLESNEAKER: - powerItem = true; - break; - case KITEM_BUBBLESHIELD: - // Experiment: Given the refactoring of the item, remove it from the cooldown pool. - // Also: a given. - case KITEM_BALLHOG: - case KITEM_JAWZ: - case KRITEM_DUALJAWZ: - case KRITEM_TRIPLEORBINAUT: - case KRITEM_QUADORBINAUT: - notForBottom = true; - powerItem = true; - break; - case KRITEM_TRIPLEBANANA: - case KRITEM_TENFOLDBANANA: - powerItem = true; - notForBottom = true; - notNearEnd = true; - break; - case KITEM_INVINCIBILITY: - if ((K_GetKartInvinType() == KARTINVIN_ALTERN) && (gametyperules & GTR_RACEODDS)) - { - // It's a power item, yes, but we don't want mashing to lessen - // its chances, so we lie to the game's face. - // Nonetheless, apply the start cooldown. - cooldownOnStart = true; - - // Also, PLEASE prevent shitty last lap bagging endings. - notNearEnd = true; - - // Unique odds for Invincibility. - newodds = K_KartGetInvincibilityOdds(clusterDist); - - // Special case: if you're SERIOUSLY far behind before the cooldown - // finishes, remove the cooldown flag. - if (newodds >= INVFORCEODDS) - { - cooldownOnStart = false; - } - - newodds *= BASEODDSMUL; - break; - } - /*FALLTHRU*/ - case KITEM_MINE: - case KITEM_GROW: - case KITEM_FLAMESHIELD: - cooldownOnStart = true; - powerItem = true; - break; - case KITEM_SPB: - cooldownOnStart = true; - indirectItem = true; - notNearEnd = true; - - if (!K_LegacyOddsMode() && firstDist < (UINT32)ENDDIST) // No SPB near the end of the race - { - newodds = 0; - } - else if (K_LegacyOddsMode() && pexiting > 0) - { - newodds = 0; - } - else - { - const INT32 distFromStart = max(secondToFirst - SPBSTARTDIST, 0); - const INT32 distRange = SPBFORCEDIST - SPBSTARTDIST; - const INT32 mulMax = 24; - - INT32 multiplier = (distFromStart * mulMax) / distRange; - - if (multiplier < 0) - multiplier = 0; - if (multiplier > mulMax) - multiplier = mulMax; - - newodds *= multiplier; - } - break; - case KITEM_SHRINK: - cooldownOnStart = true; - powerItem = true; - indirectItem = true; - notNearEnd = true; - - if (pingame-1 <= pexiting) - newodds = 0; - break; - case KITEM_THUNDERSHIELD: - cooldownOnStart = true; - powerItem = true; - - if (spbplace != -1) - newodds = 0; - break; - case KITEM_HYUDORO: - cooldownOnStart = true; - break; - default: - break; - } - - // In very small matches, remove the stupid bottom half item limiter - if (pingame < 6) - { - notForBottom = false; - } - - if (newodds == 0) - { - // Nothing else we want to do with odds matters at this point :p - return newodds; - } - - if (K_GetBGone(item, false) > 0) - { - // (Replaces hyubgone) This item is on cooldown; don't let it get rolled. - newodds = 0; - } - else 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)) - { - // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) - newodds = 0; - } - else if (!K_LegacyOddsMode() && (notNearEnd == true) && (ourDist < (UINT32)ENDDIST)) - { - // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) - newodds = 0; - } - else if ((notForBottom == true) && (inBottom == true) && (leveltime >= (30*TICRATE)+starttime)) - { - // This item should not appear for losing players. (Usually items that feel less effective at these positions) - newodds = 0; - } - else if (powerItem == true) - { - // This item is a "power item". This activates "frantic item" toggle related functionality. - fixed_t fracOdds = newodds * FRACUNIT; - - if (franticitems == true) - { - // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. - fracOdds *= 2; - } - - if (rival == true) - { - // The Rival bot gets frantic-like items, also :p - fracOdds *= 2; - } - - fracOdds = FixedMul(fracOdds, FRACUNIT + K_ItemOddsScale(pingame, spbrush)); - - if (mashed > 0) - { - // Lastly, it *divides* it based on your mashed value, so that power items are less likely when you mash. - fracOdds = FixedDiv(fracOdds, FRACUNIT + mashed); - } - - newodds = fracOdds / FRACUNIT; - } - - return newodds; -} - -//{ SRB2kart Roulette Code - Distance Based, yes waypoints - -UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) -{ - fixed_t oddsfac = max(FRACUNIT, (MAXODDS * FRACUNIT) / 8); - INT32 oddsdiv = ((MAXODDS - 1) * 2); - - UINT8 i, j; - UINT8 useodds = 0; - UINT8 disttable[oddsdiv]; - UINT8 distlen = 0; - boolean oddsvalid[MAXODDS]; - - // Unused now, oops :V - (void)bestbumper; - - for (i = 0; i < MAXODDS; i++) - { - boolean available = false; - - if ((gametyperules & GTR_BATTLEODDS) && i > 1) - { - oddsvalid[i] = false; - break; - } - - for (j = 1; j < NUMKARTRESULTS; j++) - { - if (K_KartGetItemOdds( - i, j, - player->distancetofinish, - player->distancefromcluster, - mashed, - spbrush, - player->bot, - (player->bot && player->botvars.rival), - K_IsPlayerLosing(player) - ) > 0) - { - available = true; - break; - } - } - - oddsvalid[i] = available; - } - -#define SETUPDISTTABLE(odds, num) \ - if (oddsvalid[odds]) \ - for (i = num; i; --i) \ - { \ - disttable[distlen++] = odds; \ - distlen = min(oddsdiv - 1, distlen); \ - } - - if (gametyperules & GTR_BATTLEODDS) // Battle Mode - { - if (player->roulettetype == KROULETTETYPE_KARMA && oddsvalid[1] == true) - { - // 1 is the extreme odds of player-controlled "Karma" items - useodds = 1; - } - else - { - useodds = 0; - - if (oddsvalid[0] == false && oddsvalid[1] == true) - { - // try to use karma odds as a fallback - useodds = 1; - } - } - } - else if (gametyperules & GTR_RACEODDS) - { - - INT32 tablediv = FixedMul(2, oddsfac); - INT32 jj; - - // "Why j instead of i"? - // Using i causes an infinite loop due to SETUPDISTTABLE. Yep. - for (j = 0; j < MAXODDS; j++) - { - if (j == (MAXODDS - 1)) - { - // Attempt to replicate vanilla behavior; Useodds 8 is set up like this. - SETUPDISTTABLE(j,1); - } - else - { - jj = max(1, ((j - 3) / tablediv) + 1); - - if (jj < 1) - { - jj = 1; - } - - //CONS_Printf("SETUPDISTTABLE(%d, %d)\n", j, jj); - - SETUPDISTTABLE(j,jj); - } - } - - /*SETUPDISTTABLE(0,1); - SETUPDISTTABLE(1,1); - SETUPDISTTABLE(2,1); - SETUPDISTTABLE(3,2); - SETUPDISTTABLE(4,2); - SETUPDISTTABLE(5,3); - SETUPDISTTABLE(6,3); - SETUPDISTTABLE(7,1);*/ - - const INT32 usedistvar = FixedDiv(ACTIVEDISTVAR, oddsfac); - - if (pdis == 0) - useodds = disttable[0]; - else if (pdis > (UINT32)ACTIVEDISTVAR * ((12 * distlen) / oddsdiv)) - useodds = disttable[distlen-1]; - else - { - for (i = 1; i < (oddsdiv - 1); i++) - { - INT32 distcalc = min(distlen-1, (i * distlen) / oddsdiv); - - if (pdis <= (UINT32)usedistvar * distcalc) - { - useodds = disttable[distcalc]; - break; - } - } - } - } - -#undef SETUPDISTTABLE - - return min(MAXODDS - 1, useodds); -} - -INT32 K_GetRollingRouletteItem(player_t *player) -{ - static UINT8 translation[NUMKARTITEMS-1]; - static UINT16 roulette_size; - - static INT16 odds_cached = -1; - - // Race odds have more columns than Battle - const UINT8 EMPTYODDS[sizeof K_KartItemOddsRace[0]] = {0}; - - if (odds_cached != gametype) - { - UINT8 *odds_row; - size_t odds_row_size; - - UINT8 i; - - roulette_size = 0; - - if (gametyperules & GTR_BATTLEODDS) - { - odds_row = K_KartItemOddsBattle[0]; - odds_row_size = sizeof K_KartItemOddsBattle[0]; - } - else - { - odds_row = K_KartItemOddsRace[0]; - odds_row_size = sizeof K_KartItemOddsRace[0]; - } - - for (i = 1; i < NUMKARTITEMS; ++i) - { - if (memcmp(odds_row, EMPTYODDS, odds_row_size)) - { - translation[roulette_size] = i; - roulette_size++; - } - - odds_row += odds_row_size; - } - - roulette_size *= 3; - odds_cached = gametype; - } - - return translation[(player->itemroulette % roulette_size) / 3]; -} - -// Legacy odds are fickle and finicky, so we exaggerate distances -// to simulate parity with pathfind odds. -#define LEGACYODDSEXAGGERATE (2*FRACUNIT/3) - -/** \brief Gets the distance between a player and a position by chaining all other players - * between them and the target position. - - \param player player object - \param startPos first position to trace to the player from - - \return (UINT32) pdis -*/ -UINT32 K_GetCongaLineDistance(const player_t *player, UINT8 startPos) -{ - SINT8 sortedPlayers[MAXPLAYERS]; - UINT8 sortLength = 0; - - UINT32 pdis = 0; - INT32 i; - - memset(sortedPlayers, -1, sizeof(sortedPlayers)); - - if (player->mo != NULL && P_MobjWasRemoved(player->mo) == false) - { - // Sort all of the players ahead of you. - // Then tally up their distances in a conga line. - - // This will create a much more consistent item - // distance algorithm than the "spider web" thing - // that it was doing before. - - // Add yourself to the list. - // You'll always be the end of the list, - // so we can also calculate the length here. - sortedPlayers[ player->position - 1 ] = player - players; - sortLength = player->position; - - // Will only need to do this if there's goint to be - // more than yourself in the list. - if (sortLength > 1) - { - SINT8 firstIndex = -1; - SINT8 secondIndex = -1; - INT32 startFrom = INT32_MAX; - - // Add all of the other players. - for (i = 0; i < MAXPLAYERS; i++) - { - INT32 pos = INT32_MAX; - - if (!playeringame[i] || players[i].spectator) - { - continue; - } - - if (players[i].mo == NULL || P_MobjWasRemoved(players[i].mo) == true) - { - continue; - } - - pos = players[i].position; - - if ((pos <= 0) || (pos > MAXPLAYERS) || (pos < startPos)) - { - // Invalid position. - continue; - } - - if (pos >= player->position) - { - // Tied / behind us. - // Also handles ourselves, obviously. - continue; - } - - // Ties are done with port priority, if there are any. - if (sortedPlayers[ pos - 1 ] == -1) - { - sortedPlayers[ pos - 1 ] = i; - } - } - - // The chance of this list having gaps is improbable, - // but not impossible. So we need to spend some extra time - // to prevent the gaps from mattering. - for (i = 0; i < sortLength-1; i++) - { - if (sortedPlayers[i] >= 0 && sortedPlayers[i] < MAXPLAYERS) - { - // First valid index in the list found. - firstIndex = sortedPlayers[i]; - - // Start the next loop after this player. - startFrom = i + 1; - break; - } - } - - if (firstIndex >= 0 && firstIndex < MAXPLAYERS - && startFrom < sortLength) - { - // First index is valid, so we can - // start comparing the players. - - player_t *firstPlayer = NULL; - player_t *secondPlayer = NULL; - - for (i = startFrom; i < sortLength; i++) - { - if (sortedPlayers[i] >= 0 && sortedPlayers[i] < MAXPLAYERS) - { - secondIndex = sortedPlayers[i]; - - firstPlayer = &players[firstIndex]; - secondPlayer = &players[secondIndex]; - - // Add the distance to the player behind you. - // At a (relative to map) integer scale using basic distancing - // arithmetic; more accurate and less concern for overflows. - // TODO: Overflow-safe addition (caps at UINT32_MAX). - pdis += K_IntDistanceForMap( - secondPlayer->mo->x, - secondPlayer->mo->y, - secondPlayer->mo->z, - firstPlayer->mo->x, - firstPlayer->mo->y, - firstPlayer->mo->z); - - // Advance to next index. - firstIndex = secondIndex; - } - } - } - } - } - - return pdis; -} - -UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame) -{ - UINT8 i; - UINT32 pdis = 0; - - (void)pingame; - - if (!K_LegacyOddsMode()) - { - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator - && players[i].position == 1) - { - // This player is first! Yay! - - if (player->distancetofinish <= players[i].distancetofinish) - { - // Guess you're in first / tied for first? - pdis = 0; - } - else - { - // Subtract 1st's distance from your distance, to get your distance from 1st! - pdis = player->distancetofinish - players[i].distancetofinish; - } - break; - } - } - } - else - { - pdis = K_GetCongaLineDistance(player, 1); - } - - // Exaggerate odds; don't you love the legacy system? :Trollic: - pdis = FixedMul(pdis, LEGACYODDSEXAGGERATE); - - return pdis; -} - -#undef LEGACYODDSEXAGGERATE - -UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrush) -{ - UINT32 pdis = 0; - - pdis = K_CalculateInitalPDIS(player, numPlayers); - - 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, numPlayers, *spbrush); - - if (player->bot && player->botvars.rival) - { - // Rival has better odds :) - pdis = (15 * pdis) / 14; - } - - // Can almost barely overflow this calc, fudge to prevent this. - if (pdis > 30000) - pdis = 30000; - - return pdis; -} - -static boolean K_CanForceSPB(player_t *player, UINT32 pdis) -{ - boolean battlecond, racecond; - - battlecond = ((gametyperules & GTR_WANTED) && (gametyperules & GTR_WANTEDSPB) && (mostwanted != -1) && (!K_IsPlayerMostWanted(player))); - racecond = ((gametyperules & GTR_CIRCUIT) && player->position == 2 && pdis > (UINT32)SPBFORCEDIST); - - return ((battlecond) || (racecond)); -} - -void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) -{ - INT32 i; - UINT8 pingame = 0; - UINT8 roulettestop; - UINT32 pdis = 0; - UINT8 useodds = 0; - INT32 spawnchance[NUMKARTRESULTS]; - INT64 totalspawnchance = 0; // 75-scale numbers are going to get BIG. This is for paranoia's sake. - 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) - return; - player->itemroulette++; - - // Gotta check how many players are active at this moment. - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - pingame++; - if (players[i].exiting) - dontforcespb = true; - if (players[i].bumper > bestbumper) - bestbumper = players[i].bumper; - } - - // 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)) - { -#define PLAYROULETTESND S_StartSound(NULL, sfx_itrol1 + ((player->itemroulette / 3) % 8)) - for (i = 0; i <= r_splitscreen; i++) - { - if (player == &players[displayplayers[i]] && players[displayplayers[i]].itemroulette) - PLAYROULETTESND; - } -#undef PLAYROULETTESND - } - - roulettestop = TICRATE + (3*(pingame - player->position)); - - // If the roulette finishes or the player presses BT_ATTACK, stop the roulette and calculate the item. - // I'm returning via the exact opposite, however, to forgo having another bracket embed. Same result either way, I think. - // Finally, if you get past this check, now you can actually start calculating what item you get. - if ((cmd->buttons & BT_ATTACK) && (player->itemroulette >= roulettestop) - && (K_RingsActive() || (modeattacking == ATTACKING_NONE)) - && !(player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT|IF_USERINGS))) - { - // Mashing reduces your chances for the good items - mashed = FixedDiv((player->itemroulette)*FRACUNIT, ((TICRATE*3)+roulettestop)*FRACUNIT) - FRACUNIT; - } - else if (!(player->itemroulette >= (TICRATE*3))) - return; - - - pdis = K_CalculatePDIS(player, pingame, &spbrush); - - // SPECIAL CASE No. 1: - // Fake Eggman items - if (player->roulettetype == KROULETTETYPE_EGGMAN) - { - player->eggmanexplode = 4*TICRATE; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrole); - return; - } - - // SPECIAL CASE No. 2: - // Give a debug item instead if specified - if (cv_kartdebugitem.value != 0 && !modeattacking) - { - K_KartGetItemResult(player, cv_kartdebugitem.value); - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_KARMA; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_dbgsal); - return; - } - - // SPECIAL CASE No. 3: - // This Gametype never specified an odds type. Roll something random please! - if (!(gametyperules & GTR_RACEODDS) && !(gametyperules & GTR_BATTLEODDS)) - { - SINT8 itemroll = P_RandomRange(KITEM_SNEAKER, NUMKARTITEMS - 1); - - K_KartGetItemResult(player, itemroll); - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_NORMAL; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - return; - } - - // SPECIAL CASE No. 4: - // Record Attack / alone mashing behavior - if ((modeattacking || pingame == 1) - && ((gametyperules & GTR_RACEODDS) - || ((gametyperules & GTR_BATTLEODDS) && (itembreaker || bossinfo.boss)))) - { - if ((gametyperules & GTR_RACEODDS)) - { - if (mashed && ((K_RingsActive() == true) && (modeattacking || cv_superring.value))) // ANY mashed value? You get rings. - { - K_KartGetItemResult(player, KITEM_SUPERRING); - player->itemblinkmode = KITEMBLINKMODE_MASHED; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - } - else - { - if (modeattacking || cv_sneaker.value) // Waited patiently? You get a sneaker! - K_KartGetItemResult(player, KITEM_SNEAKER); - else // Default to sad if nothing's enabled... - K_KartGetItemResult(player, KITEM_SAD); - player->itemblinkmode = KITEMBLINKMODE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - } - } - else if (gametyperules & GTR_BATTLEODDS) - { - if (mashed && (bossinfo.boss || cv_banana.value) && !itembreaker) // ANY mashed value? You get a banana. - { - K_KartGetItemResult(player, KITEM_BANANA); - player->itemblinkmode = KITEMBLINKMODE_MASHED; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - } - else if (bossinfo.boss) - { - K_KartGetItemResult(player, KITEM_ORBINAUT); - player->itemblinkmode = KITEMBLINKMODE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - } - else if (itembreaker) - { - K_KartGetItemResult(player, KITEM_SNEAKER); - player->itemblinkmode = KITEMBLINKMODE_MASHED; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - } - } - - player->itemblink = TICRATE; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - return; - } - - // SPECIAL CASE No. 5: - // Being in ring debt occasionally forces Super Ring on you if you mashed - if ((K_RingsActive() == true) && mashed && player->rings < 0 && cv_superring.value) - { - INT32 debtamount = min(abs(player->ringmin), abs(player->rings)); - if (P_RandomChance((debtamount*FRACUNIT)/abs(player->ringmin))) - { - K_KartGetItemResult(player, KITEM_SUPERRING); - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_MASHED; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - return; - } - } - - // SPECIAL CASE No. 6: - // Force SPB onto 2nd if they get too far behind - // In battle, an SPB is forced onto players to target the "most wanted" player - if (K_CanForceSPB(player, pdis) - && spbplace == -1 && !indirectitemcooldown && !dontforcespb - && cv_selfpropelledbomb.value) - { - K_KartGetItemResult(player, KITEM_SPB); - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_KARMA; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolk); - return; - } - - // NOW that we're done with all of those specialized cases, we can move onto the REAL item roulette tables. - // Initializes existing spawnchance values - for (i = 0; i < NUMKARTRESULTS; i++) - spawnchance[i] = 0; - - // Split into another function for a debug function below - useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); - - for (i = 1; i < NUMKARTRESULTS; i++) - { - - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds( - useodds, i, - player->distancetofinish, - player->distancefromcluster, - mashed, - spbrush, - player->bot, - (player->bot && player->botvars.rival), - K_IsPlayerLosing(player)) - ); - } - - // Award the player whatever power is rolled - if (totalspawnchance > 0) - { - totalspawnchance = P_RandomKey(totalspawnchance); - for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); - - K_KartGetItemResult(player, i); - } - else - { - player->itemtype = KITEM_SAD; - player->itemamount = 1; - } - - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, ((player->roulettetype == KROULETTETYPE_KARMA) ? sfx_itrolk : (mashed ? sfx_itrolm : sfx_itrolf))); - - player->itemblink = TICRATE; - player->itemblinkmode = ((player->roulettetype == KROULETTETYPE_KARMA) ? KITEMBLINKMODE_KARMA : (mashed ? KITEMBLINKMODE_MASHED : KITEMBLINKMODE_NORMAL)); - - player->itemroulette = KROULETTE_DISABLED; // Since we're done, clear the roulette number - player->roulettetype = KROULETTETYPE_NORMAL; // This too -} diff --git a/src/k_odds.h b/src/k_odds.h deleted file mode 100644 index a762f8f0e..000000000 --- a/src/k_odds.h +++ /dev/null @@ -1,72 +0,0 @@ -// BLANKART -//----------------------------------------------------------------------------- -// Copyright (C) 2018-2025 by Kart Krew. -// Copyright (C) 2025 by "Anonimus". -// Copyright (C) 2025 Blankart Team. -// -// 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 k_odds.hpp -/// \brief Kart item odds systems. - -#ifndef __K_ODDS__ -#define __K_ODDS__ - -#include "command.h" // Need for player_t -#include "doomdef.h" -#include "doomtype.h" -#include "d_player.h" // Need for player_t -#include "d_ticcmd.h" -#include "m_fixed.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// Max odds count -#define MAXODDS 16 - -// Distance variables -#define DISTVAR CV_Get(&cv_kartoddsdist) -#define DISTVAR_LEGACY CV_Get(&cv_kartlegacyoddsdist) -#define SPBDISTVAR CV_Get(&cv_kartspbdist) -#define SPBDISTVAR_LEGACY CV_Get(&cv_kartlegacyspbdist) - -extern consvar_t *KartItemCVars[NUMKARTRESULTS-1]; - -// Goner; tracks how long an item should be "gone" for. -// AKA a time-based cooldown so Hyudoro and whatever else piss off to prevent -// spamming. - -typedef enum goner_sect_e { - GONER_BASECOOLDOWN = 0, - GONER_CURRCOOLDOWN = 1, -} goner_sect_t; - -extern tic_t ItemBGone[NUMKARTRESULTS][2]; - -void K_SetBGone(SINT8 item, tic_t time); -void K_SetBGoneToBase(SINT8 item); -tic_t K_GetBGone(SINT8 item, boolean base); -void K_KartHandleShieldCooldown(SINT8 item); - -boolean K_LegacyOddsMode(void); -UINT32 K_GetCongaLineDistance(const player_t *player, UINT8 startPos); - -UINT32 K_CalculateInitalPDIS(const player_t *player, UINT8 pingame); -UINT32 K_CalculatePDIS(const player_t *player, UINT8 numPlayers, boolean *spbrush); -UINT8 K_FindUseodds(const player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush); -UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers, boolean spbrush); -INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, UINT32 ourDist, UINT32 clusterDist, fixed_t mashed, boolean spbrush, boolean bot, boolean rival, boolean inBottom); -INT32 K_GetRollingRouletteItem(player_t *player); -SINT8 K_ItemResultToType(SINT8 getitem); -UINT8 K_ItemResultToAmount(SINT8 getitem); -void K_KartItemRoulette(player_t *player, ticcmd_t *cmd); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // __K_ODDS__ diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 7e11a3bee..84fed78e6 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -33,7 +33,7 @@ #include "k_color.h" #include "k_hud.h" #include "k_waypoint.h" -#include "k_odds.h" +#include "k_items.h" #include "d_netcmd.h" // IsPlayerAdmin #include "m_menu.h" // Player Setup menu color stuff #include "p_spec.h" // P_StartQuake @@ -1418,7 +1418,7 @@ static int lib_pMovePlayer(lua_State *L) static int lib_pDoPlayerExit(lua_State *L) { player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); - pflags_t flags = luaL_checkinteger(L, 2); + pflags_t flags = lua_isnoneornil(L, 2) ? 0 : luaL_checkinteger(L, 2); NOHUD INLEVEL if (!player) @@ -4026,10 +4026,44 @@ static int lib_kGetKartFlashing(lua_State *L) static int lib_kGetItemPatch(lua_State *L) { - UINT8 item = (UINT8)luaL_optinteger(L, 1, KITEM_NONE); + kartitemtype_e item = (kartitemtype_e)luaL_optinteger(L, 1, KITEM_NONE); boolean tiny = lua_optboolean(L, 2); //HUDSAFE - lua_pushstring(L, K_GetItemPatch(item, tiny)); + + // TODO: compatmode KRITEM + const char *sad = tiny ? "K_ISSAD" : "K_ITSAD"; + if (item <= 0 || item >= numkartitems) + { + lua_pushstring(L, sad); + return 1; + } + + kartitemgraphics_t *graphics = &kartitems[item].graphics[tiny ? 1 : 0]; + lua_pushstring(L, graphics->numpatches == 0 ? sad : graphics->patchnames[0]); + return 1; +} + +static int lib_kIsKartItemAlternate(lua_State *L) +{ + kartitemtype_e item = (kartitemtype_e)luaL_optinteger(L, 1, KITEM_NONE); + // HUDSAFE + + if (item <= 0 || item >= numkartitems) + return luaL_error(L, "item number %d out of range (0 - %d)", item, numkartitems-1); + + lua_pushboolean(L, K_IsKartItemAlternate(item)); + return 1; +} + +static int lib_kIsAltShrunk(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + + //HUDSAFE + if (!player) + return LUA_ErrInvalid(L, "player_t"); + + lua_pushboolean(L, K_IsAltShrunk(player)); return 1; } @@ -4102,7 +4136,7 @@ static int lib_kSetIndirectItemCountdown(lua_State *L) { tic_t c = (tic_t)luaL_checkinteger(L, 1); NOHUD - indirectitemcooldown = c; + K_SetIndirectItemCooldown(c); return 0; } @@ -4111,48 +4145,10 @@ static int lib_kSetHyuCountdown(lua_State *L) { tic_t c = (tic_t)luaL_checkinteger(L, 1); NOHUD - K_SetBGone(KITEM_HYUDORO, c); + K_GetKartResult("hyudoro")->cooldown = c; return 0; } -static int lib_kSetBGone(lua_State *L) -{ - SINT8 item = (SINT8)luaL_checkinteger(L, 1); - tic_t c = (tic_t)luaL_checkinteger(L, 2); - NOHUD - if (item < KITEM_SNEAKER || item > (NUMKARTRESULTS - 1)) - { - luaL_error(L, "given item ID is outside bgone range"); - } - K_SetBGone(item, c); - return 0; -} - -static int lib_kSetBGoneToBase(lua_State *L) -{ - SINT8 item = (SINT8)luaL_checkinteger(L, 1); - NOHUD - if (item < KITEM_SNEAKER || item > (NUMKARTRESULTS - 1)) - { - luaL_error(L, "given item ID is outside bgone range"); - } - K_SetBGoneToBase(item); - return 0; -} - -static int lib_kGetBGone(lua_State *L) -{ - SINT8 item = (SINT8)luaL_checkinteger(L, 1); - boolean base = lua_isnoneornil(L, 2) ? false : luaL_checkboolean(L, 2); - NOHUD - if (item < KITEM_SNEAKER || item > (NUMKARTRESULTS - 1)) - { - luaL_error(L, "given item ID is outside bgone range"); - } - lua_pushinteger(L, K_GetBGone(item, base)); - return 1; -} - // Checks if the floor closet floor under an object would be safe to respawn/land on. static int lib_kSafeRespawnPosition(lua_State *L) { @@ -4227,13 +4223,6 @@ static int lib_kItemLitterActive(lua_State *L) return 1; } -// Grabs the currently active invintype. -static int lib_kGetKartInvinType(lua_State *L) -{ - lua_pushinteger(L, K_GetKartInvinType()); - return 1; -} - // Gets the currently active bumpspark type. static int lib_kGetBumpSpark(lua_State *L) { @@ -4312,6 +4301,33 @@ static int lib_kGetNewSpeed(lua_State *L) return 1; } +static int lib_kInvincibilityGradient(lua_State *L) +{ + UINT16 time = (UINT16)luaL_checkinteger(L, 1); + // HUDSAFE + + lua_pushinteger(L, K_InvincibilityGradient(time)); + return 1; +} + +static int lib_kGetInvincibilitySpeed(lua_State *L) +{ + UINT16 time = (UINT16)luaL_checkinteger(L, 1); + // HUDSAFE + + lua_pushinteger(L, K_GetInvincibilitySpeed(time)); + return 1; +} + +static int lib_kGetInvincibilityAccel(lua_State *L) +{ + UINT16 time = (UINT16)luaL_checkinteger(L, 1); + // HUDSAFE + + lua_pushinteger(L, K_GetInvincibilityAccel(time)); + return 1; +} + static int lib_k3dKartMovement(lua_State *L) { player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); @@ -5053,6 +5069,17 @@ static int lib_kTerrainHasAffect(lua_State *L) return 1; } +static int lib_kSetPlayerItemCooldown(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + tic_t c = (tic_t)luaL_checkinteger(L, 2); + boolean force = lua_optboolean(L, 3); + if (!player) + return LUA_ErrInvalid(L, "player_t"); + K_SetPlayerItemCooldown(player, c, force); + return 0; +} + static luaL_Reg lib[] = { {"print", lib_print}, {"chatprint", lib_chatprint}, @@ -5337,13 +5364,12 @@ static luaL_Reg lib[] = { {"K_GetKartAccel",lib_kGetKartAccel}, {"K_GetKartFlashing",lib_kGetKartFlashing}, {"K_GetItemPatch",lib_kGetItemPatch}, + {"K_IsKartItemAlternate",lib_kIsKartItemAlternate}, + {"K_IsAltShrunk", lib_kIsAltShrunk}, {"K_SetRaceCountdown",lib_kSetRaceCountdown}, {"K_SetExitCountdown",lib_kSetExitCountdown}, {"K_SetIndirectItemCooldown",lib_kSetIndirectItemCountdown}, {"K_SetHyudoroCooldown",lib_kSetHyuCountdown}, - {"K_SetBGone", lib_kSetBGone}, - {"K_SetBGoneToBase", lib_kSetBGoneToBase}, - {"K_GetBGone", lib_kGetBGone}, {"K_SafeRespawnPosition",lib_kSafeRespawnPosition}, {"K_GetCollideAngle",lib_kGetCollideAngle}, @@ -5358,13 +5384,15 @@ static luaL_Reg lib[] = { {"K_AirDropActive",lib_kAirDropActive}, {"K_ItemLitterActive",lib_kItemLitterActive}, {"K_GetBumpSpark",lib_kGetBumpSpark}, - {"K_GetKartInvinType",lib_kGetKartInvinType}, {"K_UsingLegacyCheckpoints",lib_kUsingLegacyCheckpoints}, {"K_DoBoost",lib_kDoBoost}, {"K_ClearBoost",lib_kClearBoost}, {"K_BoostChain",lib_kBoostChain}, {"K_ChainOrDeincrementTime",lib_kChainOrDeincrementTime}, {"K_GetNewSpeed", lib_kGetNewSpeed}, + {"K_InvincibilityGradient", lib_kInvincibilityGradient}, + {"K_GetInvincibilitySpeed", lib_kGetInvincibilitySpeed}, + {"K_GetInvincibilityAccel", lib_kGetInvincibilityAccel}, {"K_3dKartMovement", lib_k3dKartMovement}, {"K_MomentumAngle", lib_kMomentumAngle}, {"K_GetKartSpeedFromStat", lib_kGetKartSpeedFromStat}, @@ -5435,6 +5463,9 @@ static luaL_Reg lib[] = { {"K_HandleFootstepParticles", lib_kHandleFootstepParticles}, {"K_UpdateTerrainOverlay", lib_kUpdateTerrainOverlay}, {"K_TerrainHasAffect", lib_kTerrainHasAffect}, + + // k_items + {"K_SetPlayerItemCooldown", lib_kSetPlayerItemCooldown}, {NULL, NULL} }; diff --git a/src/lua_hook.h b/src/lua_hook.h index 8e715193a..a09508570 100644 --- a/src/lua_hook.h +++ b/src/lua_hook.h @@ -1,4 +1,3 @@ - //----------------------------------------------------------------------------- // Copyright (C) 2012-2016 by John "JTE" Muniz. // Copyright (C) 2012-2022 by Sonic Team Junior. @@ -40,6 +39,7 @@ automatically. X (MobjFuse),/* when mobj->fuse runs out */\ X (MobjThinker),/* P_MobjThinker, P_SceneryThinker */\ X (BossThinker),/* P_GenericBossThinker */\ + X (MobjScaleChange),/*SRB2KART*/\ X (ShouldDamage),/* P_DamageMobj (Should mobj take damage?) */\ X (ShouldSpin),/* P_DamageMobj (Should mobj take spinout damage?) */\ X (ShouldExplode),/* P_DamageMobj (Should mobj take explosion damage?) */\ @@ -86,6 +86,9 @@ automatically. X (BotJoin),\ X (GPRankPoints),/* K_CalculateGPRankPoints */\ X (AddonLoaded),\ + X (PlayerItem),/*SRB2KART*/\ + X (KartHyudoro),/*SRB2KART*/\ + X (KartSneaker),/*SRB2KART*/\ #define STRING_HOOK_LIST(X) \ X (SpecialExecute),\ @@ -171,6 +174,12 @@ int LUA_HookGPRankPoints(UINT8 position, UINT8 numplayers, INT16 *points); int LUA_HookShouldJingleContinue(player_t *, const char *musname); int LUA_HookMusicChange(const char *oldname, struct MusicChange *); +// Item shit +boolean LUA_HookPlayerItem(player_t *player, UINT8 itemType, boolean wasHoldingItem, boolean *force); +boolean LUA_HookKartHyudoro(player_t *player, INT32 *target, boolean sink); // SRB2Kart: Hook for K_DoHyudoroSteal and overriding its results. +boolean LUA_HookMobjScaleChange(mobj_t *target, fixed_t newscale, fixed_t oldscale); // SRB2Kart: Hook for P_SetScale. +boolean LUA_HookKartSneaker(player_t *player, int type); // SRB2Kart: Hook for K_DoSneaker. + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index c1d059fb1..4b2321301 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -1260,3 +1260,115 @@ int LUA_HookMusicChange(const char *oldname, struct MusicChange *param) return hook.status; } + +// Allows to both override default behavior and force action. I don't like it but thats how it works +// in CEP and neptune. Fuckal whyyyyyyyyy +typedef struct { + boolean override; + boolean force; +} TrueForce_State; + +static void res_trueforce(Hook_State *hook) +{ + TrueForce_State *state = (TrueForce_State*)hook->userdata; + + if (lua_isboolean(gL, -2)) + state->override = lua_toboolean(gL, -2); + + if (lua_isboolean(gL, -1)) + state->force = lua_toboolean(gL, -1); +}; + +boolean LUA_HookPlayerItem(player_t *player, UINT8 itemType, boolean wasHoldingItem, boolean *force) +{ + Hook_State hook; + TrueForce_State state = {0}; + + if (prepare_hook(&hook, 0, HOOK(PlayerItem))) + { + LUA_PushUserdata(gL, player, META_PLAYER); + lua_pushinteger(gL, itemType); + lua_pushboolean(gL, wasHoldingItem); + + hook.userdata = &state; + + call_hooks(&hook, 2, res_trueforce); + } + + *force = state.force; + return state.override; +} + +typedef struct { + INT32 *target; + boolean force_sink; +} KartHyudoro_State; + +static void res_karthyudoro(Hook_State *hook) +{ + KartHyudoro_State *state = (KartHyudoro_State*)hook->userdata; + + if (lua_isnumber(gL, -2)) + *state->target = lua_tonumber(gL, -2); + else if (lua_isuserdata(gL, -2)) + { + player_t *player = *(player_t**)luaL_checkudata(gL, -2, META_PLAYER); + *state->target = player - players; + } + + if (lua_isboolean(gL, -1)) + state->force_sink = lua_toboolean(gL, -1); +} + +boolean LUA_HookKartHyudoro(player_t *player, INT32 *target, boolean sink) +{ + Hook_State hook; + KartHyudoro_State state = {0}; + state.target = target; + + if (prepare_hook(&hook, 0, HOOK(KartHyudoro))) + { + LUA_PushUserdata(gL, player, META_PLAYER); + if (*target >= 0) + LUA_PushUserdata(gL, &players[*target], META_PLAYER); + else + lua_pushnil(gL); + lua_pushboolean(gL, sink); + + hook.userdata = &state; + + call_hooks(&hook, 2, res_karthyudoro); + } + + return state.force_sink; + +} + +boolean LUA_HookMobjScaleChange(mobj_t *target, fixed_t newscale, fixed_t oldscale) +{ + Hook_State hook; + if (prepare_mobj_hook(&hook, false, MOBJ_HOOK(MobjScaleChange), target->type)) + { + LUA_PushUserdata(gL, target, META_MOBJ); + lua_pushfixed(gL, newscale); + lua_pushfixed(gL, oldscale); + + call_hooks(&hook, 1, res_true); + } + return hook.status; +} + +boolean LUA_HookKartSneaker(player_t *player, int type) +{ + Hook_State hook; + if (prepare_hook(&hook, false, HOOK(KartSneaker))) + { + LUA_PushUserdata(gL, player, META_PLAYER); + // Type of sneaker: 0 - perfect/panel, 1 - regular sneaker, 2 - rocket sneaker. + lua_pushinteger(gL, type); + + call_hooks(&hook, 1, res_true); + } + + return hook.status; +} diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index e33b4fb73..fa0eee598 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -178,7 +178,8 @@ static const char *const camera_opt[] = { enum hudpatch { hudpatch_item = 0, - hudpatch_itemmul + hudpatch_itemmul, + hudpatch_itemalt }; static const char *const hud_patch_options[] = { @@ -411,6 +412,9 @@ static int libd_getSpritePatch(lua_State *L) if (i == SPR_PLAY) // Use getSprite2Patch instead! return 0; + if (i == SPR_ITEM) // Use... uhhhhh... + return 0; + sprdef = &sprites[i]; // set frame number @@ -1184,7 +1188,7 @@ static int libd_getColorHudPatch(lua_State *L) enum hudpatch option = luaL_checkoption(L, 1, NULL, hud_patch_options); patch_t *patch; UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, K_GetHudColor(), GTC_CACHE); - boolean small, dark; + boolean small, dark, multi; switch (option) { case hudpatch_item: @@ -1198,6 +1202,11 @@ static int libd_getColorHudPatch(lua_State *L) small = lua_optboolean(L, 2); patch = K_getItemMulPatch(small); break; + case hudpatch_itemalt: + small = lua_optboolean(L, 2); + multi = lua_optboolean(L, 3); + patch = K_getItemAltPatch(small, multi); + break; default: return 0; // you shouldn't be here } diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index bda1fee17..0f4f0ee6a 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -249,6 +249,8 @@ enum player_e player_roulettetype, player_itemtype, player_itemamount, + player_itemusecooldown, + player_itemusecooldownmax, player_throwdir, player_sadtimer, player_rings, @@ -285,6 +287,10 @@ enum player_e player_squishedtimer, player_rocketsneakertimer, player_invincibilitytimer, + player_maxinvincibilitytime, + player_invincibilitybottleneck, + player_invincibilitycancel, + player_invincibilitywarning, player_eggmanexplode, player_eggmanblame, player_bananadrag, @@ -453,6 +459,8 @@ static const char *const player_opt[] = { "roulettetype", "itemtype", "itemamount", + "itemusecooldown", + "itemusecooldownmax", "throwdir", "sadtimer", "rings", @@ -489,6 +497,10 @@ static const char *const player_opt[] = { "squishedtimer", "rocketsneakertimer", "invincibilitytimer", + "maxinvincibilitytime", + "invincibilitybottleneck", + "invincibilitycancel", + "invincibilitywarning", // This gets cast to a boolean in Lua. "eggmanexplode", "eggmanblame", "bananadrag", @@ -855,6 +867,12 @@ static int player_get(lua_State *L) case player_itemamount: lua_pushinteger(L, plr->itemamount); break; + case player_itemusecooldown: + lua_pushinteger(L, plr->itemusecooldown); + break; + case player_itemusecooldownmax: + lua_pushinteger(L, plr->itemusecooldownmax); + break; case player_throwdir: lua_pushinteger(L, plr->throwdir); break; @@ -963,6 +981,19 @@ static int player_get(lua_State *L) case player_invincibilitytimer: lua_pushinteger(L, plr->invincibilitytimer); break; + case player_maxinvincibilitytime: + lua_pushinteger(L, plr->maxinvincibilitytime); + break; + case player_invincibilitybottleneck: + // Push as an INT16 due to the negative value signal systems. + lua_pushinteger(L, (INT16)plr->invincibilitybottleneck); + break; + case player_invincibilitycancel: + lua_pushinteger(L, plr->invincibilitycancel); + break; + case player_invincibilitywarning: + lua_pushboolean(L, (boolean)plr->invincibilitywarning); + break; case player_eggmanexplode: lua_pushinteger(L, plr->eggmanexplode); break; @@ -1568,6 +1599,12 @@ static int player_set(lua_State *L) case player_itemamount: plr->itemamount = luaL_checkinteger(L, 3); break; + case player_itemusecooldown: + plr->itemusecooldown = luaL_checkinteger(L, 3); + break; + case player_itemusecooldownmax: + plr->itemusecooldownmax = luaL_checkinteger(L, 3); + break; case player_throwdir: plr->throwdir = luaL_checkinteger(L, 3); break; @@ -1667,14 +1704,34 @@ static int player_set(lua_State *L) plr->growcancel = luaL_checkinteger(L, 3); break; case player_squishedtimer: - plr->squishedtimer = luaL_checkinteger(L, 3); + { + // Unsquish for the ease of Lua programmers + INT16 squishtimer = (INT16)luaL_checkinteger(L, 3); + + if (squishtimer == 0) + plr->mo->spriteyscale = FRACUNIT; + + plr->squishedtimer = squishtimer; break; + } case player_rocketsneakertimer: plr->rocketsneakertimer = luaL_checkinteger(L, 3); break; case player_invincibilitytimer: plr->invincibilitytimer = luaL_checkinteger(L, 3); break; + case player_maxinvincibilitytime: + // For most other cases, I'd just say: "it's your funeral". Not for this one. + return NOSET; + case player_invincibilitybottleneck: + plr->invincibilitybottleneck = (UINT16)luaL_checkinteger(L, 3); + break; + case player_invincibilitycancel: + plr->invincibilitycancel = (INT16)luaL_checkinteger(L, 3); + break; + case player_invincibilitywarning: + plr->invincibilitywarning = (UINT8)luaL_checkboolean(L, 3); + break; case player_eggmanexplode: plr->eggmanexplode = luaL_checkinteger(L, 3); break; @@ -2455,8 +2512,14 @@ static int kartstuff_set(lua_State *L) plr->growshrinktimer = CLAMP(i, INT16_MIN, INT16_MAX); break; case k_squishedtimer: + { + // Unsquish for the ease of Lua programmers + if (i == 0) + plr->mo->spriteyscale = FRACUNIT; + plr->squishedtimer = CLAMP(i, INT16_MIN, INT16_MAX); break; + } case k_rocketsneakertimer: plr->rocketsneakertimer = CLAMP(i, 0, UINT16_MAX); break; diff --git a/src/lua_script.c b/src/lua_script.c index 5a4136d23..6175350e4 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -27,7 +27,7 @@ #include "p_slopes.h" // for P_SlopeById and slopelist #include "p_polyobj.h" // polyobj_t, PolyObjects #include "k_battle.h" -#include "k_odds.h" +#include "k_items.h" #include "k_waypoint.h" #ifdef LUA_ALLOW_BYTECODE #include "d_netfil.h" // for LUA_DumpFile @@ -417,11 +417,8 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"itempushing")) { lua_pushboolean(L, itempushing); // hmm... i think this should be a boolean return 1; - } else if (fastcmp(word,"invintype")) { - lua_pushinteger(L, invintype); - return 1; } else if (fastcmp(word,"hyubgone")) { - lua_pushinteger(L, K_GetBGone(KITEM_HYUDORO, false)); + lua_pushinteger(L, K_GetKartResult("hyudoro")->cooldown); return 1; } else if (fastcmp(word,"encoremode")) { lua_pushboolean(L, encoremode); @@ -436,7 +433,7 @@ int LUA_PushGlobals(lua_State *L, const char *word) lua_pushinteger(L, wantedcalcdelay); return 1; } else if (fastcmp(word,"indirectitemcooldown")) { - lua_pushinteger(L, indirectitemcooldown); + lua_pushinteger(L, K_GetIndirectItemCooldown()); return 1; } else if (fastcmp(word,"thwompsactive")) { lua_pushboolean(L, thwompsactive); @@ -562,11 +559,9 @@ int LUA_WriteGlobals(lua_State *L, const char *word) else if (fastcmp(word,"exitcountdown")) exitcountdown = (tic_t)luaL_checkinteger(L, 2); else if (fastcmp(word,"indirectitemcooldown")) - indirectitemcooldown = (tic_t)luaL_checkinteger(L, 2); + K_SetIndirectItemCooldown(luaL_checkinteger(L, 2)); else if (fastcmp(word,"hyubgone")) - { - K_SetBGone(KITEM_HYUDORO, (tic_t)luaL_checkinteger(L, 2)); - } + K_GetKartResult("hyudoro")->cooldown = (tic_t)luaL_checkinteger(L, 2); else if (fastcmp(word,"starttime")) starttime = (tic_t)luaL_checkinteger(L, 2); else if (fastcmp(word,"introtime")) diff --git a/src/m_fixed.h b/src/m_fixed.h index 3190d0f1a..74b2f29b6 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -16,6 +16,8 @@ #define __M_FIXED__ #include "doomtype.h" +#include "doomdef.h" + #ifdef __GNUC__ #include #endif @@ -84,6 +86,53 @@ FUNCMATH FUNCINLINE static ATTRINLINE fixed_t DoubleToFixed(double f) return (fixed_t)(f * FRACUNIT); } +/** \brief Converts seconds (in tics) to fixed-point number values; 1 second is 1 fracunit. + + \param t (tic_t) time + + \return (fixed_t) seconds as fracunits +*/ +FUNCMATH FUNCINLINE static ATTRINLINE fixed_t SecsToFixed(tic_t t) +{ + return (fixed_t)(((UINT64)t * FRACUNIT) / (TICRATE)); +} + +/** \brief Converts tens of seconds (in tics) to fixed-point number values; 10 seconds is 1 + fracunit. + + \param t (tic_t) time + + \return (fixed_t) tens of seconds as fracunits +*/ +FUNCMATH FUNCINLINE static ATTRINLINE fixed_t SecsToFixedTens(tic_t t) +{ + return (fixed_t)(((UINT64)t * FRACUNIT) / (10 * TICRATE)); +} + +/** \brief 64-bit version of SecsToFixed. Converts seconds (in tics) to fixed-point number values; 1 + second is 1 fracunit. + + \param t (tic_t) time + + \return (sint64_t) seconds as fracunits +*/ +FUNCMATH FUNCINLINE static ATTRINLINE INT64 SecsToFixed64(tic_t t) +{ + return (INT64)(((UINT64)t * FRACUNIT) / (TICRATE)); +} + +/** \brief 64-bit version of SecsToFixedTens. Converts tens of seconds (in tics) to fixed-point + number values; 10 seconds is 1 fracunit. + + \param t (tic_t) time + + \return (sint64_t) tens of seconds as fracunits +*/ +FUNCMATH FUNCINLINE static ATTRINLINE INT64 SecsToFixedTens64(tic_t t) +{ + return (INT64)(((UINT64)t * FRACUNIT) / (10 * TICRATE)); +} + // for backwards compat #define FIXED_TO_FLOAT(x) FixedToFloat(x) // (((float)(x)) / ((float)FRACUNIT)) #define FLOAT_TO_FIXED(f) FloatToFixed(f) // (fixed_t)((f) * ((float)FRACUNIT)) diff --git a/src/m_menu.c b/src/m_menu.c index 00d2d3477..6a0a9acda 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -67,7 +67,7 @@ #include "i_sound.h" #include "k_hud.h" // SRB2kart #include "k_kart.h" -#include "k_odds.h" // KartItemCVars +#include "k_items.h" // KartItemCVars #include "k_pwrlv.h" #include "k_stats.h" // SRB2kart #include "d_player.h" // KITEM_ constants @@ -8317,6 +8317,56 @@ INT32 MR_CameraSetup(INT32 arg) static tic_t shitsfree = 0; +INT32 MR_SetupMonitorToggles(INT32 choice) +{ + (void)choice; + const INT32 height = 4; + menu_t *menudef = &menudefs[MN_OP_MONITORTOGGLE]; + + // in the interest of not rushing the implementation of menu frames... + // leak the memory. every single time. + // (i dont think this actually leaks anything lol) + // (unless someone adds menuitems to this menu for some stupid reason) + menudef->numitems = 0; + + for (UINT8 i = 0; i <= numkartresults; i++) + { + kartresult_t *result = i < 3 ? &kartresults[i] : i == 3 ? NULL : &kartresults[i - 1]; + if (result != NULL && result->isalt) + continue; + + menudef->menuitems = Z_Realloc(menudef->menuitems, sizeof(menuitem_t)*(menudef->numitems+1), PU_STATIC, NULL); + menuitem_t *item = menudef->menuitems + menudef->numitems++; + + if (result == NULL) + { + item->argument = 1; + item->patch = ""; + } + else + { + item->argument = 0; + item->patch = result->cvar->name; + if (kartitems[result->type].altcvar != NULL) + item->tooltip = "Press BACKSPACE to swap variants."; + else if (result->type == KITEM_SUPERRING && cv_kartrings.value == 0) + item->tooltip = "Rings must be enabled in Gameplay Mods!"; + } + } + + while (menudef->numitems % height != 0) + { + menudef->menuitems = Z_Realloc(menudef->menuitems, sizeof(menuitem_t)*(menudef->numitems+1), PU_STATIC, NULL); + menuitem_t *item = menudef->menuitems + menudef->numitems++; + + item->argument = 2; + item->patch = ""; + } + + menudef->x = 136 - FixedMul(max(0, min((menudef->numitems - 1) / height, 7)), TICRATE*FRACUNIT/2); + return true; +} + void MD_DrawMonitorToggles(void) { const INT32 edges = 4; @@ -8327,8 +8377,18 @@ void MD_DrawMonitorToggles(void) INT32 leftdraw, rightdraw, totaldraw; INT32 x = currentMenu->x, y = currentMenu->y+(spacing/4); INT32 onx = 0, ony = 0; - consvar_t *cv; - INT32 i, translucent, drawnum; + INT32 i, translucent; + const char *displayname; + + // INCREDIBLY goofy hack, but I don't wanna have to pass a "use cvar or altenabled" + // flag to literally ALL of the item functions just for the sake of the menu + boolean oldalt[MAXKARTITEMS]; + for (i = 0; i < numkartitems; i++) + { + kartitem_t *item = &kartitems[i]; + oldalt[i] = item->altenabled; + item->altenabled = item->altcvar != NULL && item->altcvar->value == 1; + } M_DrawMenuTitle(); @@ -8353,7 +8413,7 @@ void MD_DrawMonitorToggles(void) { INT32 j; - for (j = 0; j < height; j++) + for (j = 0; j < height; j++, y += spacing) { const INT32 thisitem = (i*height)+j; @@ -8364,74 +8424,56 @@ void MD_DrawMonitorToggles(void) { onx = x; ony = y; - y += spacing; continue; } -#ifdef ITEMTOGGLEBOTTOMRIGHT - if (currentMenu->menuitems[thisitem].argument == 255) + switch (currentMenu->menuitems[thisitem].argument) { + case 2: V_DrawScaledPatch(x, y, V_TRANSLUCENT, W_CachePatchName("K_ISBG", PU_CACHE)); continue; - } -#endif - if (currentMenu->menuitems[thisitem].argument == 0) - { + case 1: V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISBG", PU_CACHE)); V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISTOGL", PU_CACHE)); continue; } - cv = KartItemCVars[currentMenu->menuitems[thisitem].argument-1]; - translucent = (cv->value ? 0 : V_TRANSLUCENT); + const kartresult_t *result = K_GetKartResult(currentMenu->menuitems[thisitem].patch); + const kartitem_t *item = &kartitems[result->type]; + boolean enabled = result->cvar->value == 1; - switch (currentMenu->menuitems[thisitem].argument) - { - 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; - } + if (result->type == KITEM_SUPERRING && cv_kartrings.value == 0) + enabled = false; - if (cv->value) + translucent = enabled ? 0 : V_TRANSLUCENT; + + if (enabled) 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 (result->amount >= K_GetItemNumberDisplayMin(result->type, true)) { V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ISMUL", PU_CACHE)); - V_DrawScaledPatch(x, y, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[thisitem].argument, true), PU_CACHE)); - V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|translucent, va("x%d", drawnum)); + V_DrawScaledPatch(x, y, translucent, K_GetCachedItemPatch(result->type, true, result->amount)); + V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|translucent, va("x%d", result->amount)); } else - V_DrawScaledPatch(x, y, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[thisitem].argument, true), PU_CACHE)); + V_DrawScaledPatch(x, y, translucent, K_GetCachedItemPatch(result->type, true, result->amount)); - y += spacing; + if (item->altenabled) + V_DrawScaledPatch(x, y, 0, W_CachePatchName("K_ALTITS", PU_CACHE)); } x += spacing; y = currentMenu->y+(spacing/4); } + switch (currentMenu->menuitems[itemOn].argument) { -#ifdef ITEMTOGGLEBOTTOMRIGHT - if (currentMenu->menuitems[itemOn].argument == 255) + case 2: { + displayname = "---"; V_DrawScaledPatch(onx-1, ony-2, V_TRANSLUCENT, W_CachePatchName("K_ITBG", PU_CACHE)); if (shitsfree) { @@ -8442,58 +8484,69 @@ void MD_DrawMonitorToggles(void) trans = (10-shitsfree)<menuitems[itemOn].argument == 0) + case 1: { + displayname = "Toggle All"; V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITBG", PU_CACHE)); V_DrawScaledPatch(onx-1, ony-2, 0, W_CachePatchName("K_ITTOGL", PU_CACHE)); + break; } - else + default: { - cv = KartItemCVars[currentMenu->menuitems[itemOn].argument-1]; - translucent = (cv->value ? 0 : V_TRANSLUCENT); + const kartresult_t *result = K_GetKartResult(currentMenu->menuitems[itemOn].patch); + const kartitem_t *item = &kartitems[result->type]; + boolean enabled = result->cvar->value == 1; - switch (currentMenu->menuitems[itemOn].argument) - { - case KRITEM_TENFOLDBANANA: - drawnum = 10; - break; - default: - drawnum = 0; - break; - } + if (result->type == KITEM_SUPERRING && cv_kartrings.value == 0) + enabled = false; - if (cv->value) + displayname = result->displayname; + translucent = enabled ? 0 : V_TRANSLUCENT; + + if (enabled) 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 (result->amount >= K_GetItemNumberDisplayMin(result->type, false)) { 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].argument, false), PU_CACHE)); + V_DrawScaledPatch(onx-1, ony-2, translucent, K_GetCachedItemPatch(result->type, false, result->amount)); V_DrawScaledPatch(onx+27, ony+39, translucent, W_CachePatchName("K_ITX", PU_CACHE)); - V_DrawKartString(onx+37, ony+34, translucent, va("%d", drawnum)); + V_DrawKartString(onx+37, ony+34, translucent, va("%d", result->amount)); } else - V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName(K_GetItemPatch(currentMenu->menuitems[itemOn].argument, false), PU_CACHE)); + V_DrawScaledPatch(onx-1, ony-2, translucent, K_GetCachedItemPatch(result->type, false, result->amount)); + + if (item->altcvar != NULL) + { + translucent = item->altenabled ? 0 : V_TRANSLUCENT; + V_DrawScaledPatch(onx-1, ony-2, translucent, W_CachePatchName("K_ALTITM", PU_CACHE)); + } + break; } } - if (shitsfree) + if (renderisnewtic && shitsfree) shitsfree--; - V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y, MENUCAPS|highlightflags, va("* %s *", currentMenu->menuitems[itemOn].text)); + V_DrawCenteredString(BASEVIDWIDTH/2, currentMenu->y, MENUCAPS|highlightflags, va("* %s *", displayname)); + + M_DrawMenuTooltips(); + + for (i = 0; i < numkartitems; i++) + kartitems[i].altenabled = oldalt[i]; } INT32 MR_HandleMonitorToggles(INT32 choice) { - const INT32 width = 6, height = 4; + const INT32 height = 4; + const INT32 width = currentMenu->numitems/height; INT32 column = itemOn/height, row = itemOn%height; INT16 next; - UINT8 i; + kartresult_t *result = K_GetKartResult(currentMenu->menuitems[itemOn].patch); switch (choice) { @@ -8542,8 +8595,7 @@ INT32 MR_HandleMonitorToggles(INT32 choice) break; case KEY_ENTER: -#ifdef ITEMTOGGLEBOTTOMRIGHT - if (currentMenu->menuitems[itemOn].argument == 255) + if (currentMenu->menuitems[itemOn].argument == 2) { //S_StartSound(NULL, sfx_s26d); if (!shitsfree) @@ -8552,29 +8604,41 @@ INT32 MR_HandleMonitorToggles(INT32 choice) S_StartSound(NULL, sfx_itfree); } } - else -#endif - if (currentMenu->menuitems[itemOn].argument == 0) + else if (currentMenu->menuitems[itemOn].argument == 1) { - INT32 v = cv_sneaker.value; + INT32 v = K_ItemResultEnabled(K_GetKartResult("sneaker")) ? 1 : 0; S_StartSound(NULL, sfx_s1b4); - for (i = 0; i < NUMKARTRESULTS-1; i++) + for (UINT8 i = 0; i < numkartresults; i++) { - if (KartItemCVars[i]->value == v) - CV_AddValue(KartItemCVars[i], 1); + if (kartresults[i].cvar->value == v) + CV_AddValue(kartresults[i].cvar, 1); } } - else + else if (result != NULL) { - S_StartSound(NULL, sfx_s1ba); - CV_AddValue(KartItemCVars[currentMenu->menuitems[itemOn].argument-1], 1); + if (result->type == KITEM_SUPERRING && cv_kartrings.value == 0) + { + S_StartSound(NULL, sfx_s231); + } + else + { + S_StartSound(NULL, sfx_s1ba); + CV_AddValue(result->cvar, 1); + } + } + break; + + case KEY_BACKSPACE: + if (result != NULL && kartitems[result->type].altcvar != NULL) + { + S_StartSound(NULL, sfx_s3kb7); + CV_AddValue(kartitems[result->type].altcvar, 1); } break; default: return false; } - return true; } diff --git a/src/m_menu.h b/src/m_menu.h index 4942956b7..5cf612164 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -323,6 +323,7 @@ INT32 MR_HandleMusicTest(INT32 choice); INT32 MR_ResetControls(INT32 choice); INT32 MR_ChangeControl(INT32 arg); INT32 MR_AssignJoystick(INT32 arg); +INT32 MR_SetupMonitorToggles(INT32 choice); INT32 MR_HandleMonitorToggles(INT32 choice); INT32 MR_RestartAudio(INT32 choice); INT32 MR_EnterViewServer(INT32 choice); diff --git a/src/p_enemy.c b/src/p_enemy.c index b38026108..fbea63c8e 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -30,6 +30,7 @@ #include "k_waypoint.h" #include "k_battle.h" #include "k_collide.h" +#include "k_items.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -10838,7 +10839,7 @@ void A_ItemPop(void *thing) if (!((gametyperules & GTR_BUMPERS) && actor->target->player->bumper <= 0) && !itembreaker) { - actor->target->player->itemroulette = KROULETTE_ACTIVE; + K_StartRoulette(actor->target->player, KROULETTETYPE_NORMAL); } else if (itembreaker) { diff --git a/src/p_inter.c b/src/p_inter.c index 951bd2ffe..68e22f0c5 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -39,7 +39,7 @@ #include "k_boss.h" #include "p_spec.h" #include "k_objects.h" -#include "k_odds.h" +#include "k_items.h" #include "acs/interface.h" // CTF player names @@ -147,7 +147,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) if (player->stealingtimer || player->stolentimer || player->rocketsneakertimer || player->eggmanexplode - || ((K_GetKartInvinType() == KARTINVIN_ALTERN) && (player->invincibilitytimer)) + || ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (player->invincibilitytimer)) || (player->growshrinktimer > 0) || player->flametimer) return false; @@ -459,8 +459,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) special->target->player->karmadelay = comebacktime; - player->itemroulette = KROULETTE_ACTIVE; - player->roulettetype = KROULETTETYPE_KARMA; + K_StartRoulette(player, KROULETTETYPE_KARMA); } else if (special->target->player->karmamode == 2 && P_CanPickupItem(player, 2)) { @@ -495,11 +494,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) special->target->player->karmadelay = comebacktime; - K_DropItems(player); //K_StripItems(player); - //K_StripOther(player); - - player->itemroulette = KROULETTE_ACTIVE; - player->roulettetype = KROULETTETYPE_EGGMAN; + K_StartRoulette(player, KROULETTETYPE_EGGMAN); if (special->target->player->eggmanblame >= 0 && special->target->player->eggmanblame < MAXPLAYERS @@ -591,8 +586,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->fuse || !P_CanPickupItem(player, 1) || ((gametyperules & GTR_BUMPERS) && player->bumper <= 0)) return; - player->itemroulette = KROULETTE_ACTIVE; - player->roulettetype = KROULETTETYPE_KARMA; + K_StartRoulette(player, KROULETTETYPE_KARMA); // Karma fireworks for (i = 0; i < 5; i++) @@ -1742,25 +1736,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget break; } - if (target->threshold < 1 || target->threshold >= NUMKARTITEMS) // bruh moment prevention - { - player->itemtype = KITEM_SAD; - player->itemamount = 1; - } - else - { - player->itemtype = target->threshold; - if (K_GetShieldFromPlayer(player) != KSHIELD_NONE) // never give more than 1 shield - player->itemamount = 1; - else - player->itemamount = max(1, target->movecount); - } - player->itemblink = TICRATE; - player->itemblinkmode = KITEMBLINKMODE_NORMAL; - player->itemroulette = KROULETTE_DISABLED; - player->roulettetype = KROULETTETYPE_NORMAL; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); + K_AwardPlayerItem(player, target->threshold, max(1, target->movecount), KITEMBLINK_NORMAL); } break; } diff --git a/src/p_mobj.c b/src/p_mobj.c index 51cbb1927..ef57fb276 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -15,6 +15,7 @@ #include "d_netcmd.h" #include "d_think.h" #include "dehacked.h" +#include "deh_tables.h" #include "doomdef.h" #include "doomstat.h" #include "doomtype.h" @@ -51,7 +52,7 @@ #include "k_terrain.h" #include "k_collide.h" #include "k_objects.h" -#include "k_odds.h" +#include "k_items.h" #include "k_waypoint.h" // BlanKart @@ -1228,6 +1229,7 @@ fixed_t P_GetMobjGravity(mobj_t *mo) case MT_EGGMANITEM: case MT_SSMINE: case MT_LANDMINE: + // case MT_EGGMINE_CAPSULE: case MT_SINK: if (mo->extravalue2 > 0) { @@ -4164,8 +4166,8 @@ static void P_RefreshItemCapsuleParts(mobj_t *mobj) mobj_t *part; UINT32 newRenderFlags = 0; - if (itemType < 1 || itemType >= NUMKARTITEMS) - itemType = KITEM_SAD; + if (itemType < 1 || itemType >= numkartitems) + itemType = MAXKARTITEMS; part = mobj; while (!P_MobjWasRemoved(part->hnext)) @@ -4185,40 +4187,8 @@ static void P_RefreshItemCapsuleParts(mobj_t *mobj) K_UpdateMobjItemOverlay(part, itemType, mobj->movecount); // update number frame - if (K_GetShieldFromItem(itemType) != KSHIELD_NONE) // shields don't stack, so don't show a number - ; - else - { - switch (itemType) - { - case KITEM_SNEAKER: - if (mobj->movecount - 1 > K_GetMultItemFrame(mobj->movecount, 2)) - count = mobj->movecount; - break; - case KITEM_ORBINAUT: // only display the number when the sprite no longer changes - if (mobj->movecount - 1 > K_GetMultItemFrame(mobj->movecount, 3)) - count = mobj->movecount; - break; - case KITEM_BANANA: // only display the number when the sprite no longer changes - if (mobj->movecount - 1 > K_GetMultItemFrame(mobj->movecount, 3)) - count = mobj->movecount; - break; - case KITEM_JAWZ: // only display the number when the sprite no longer changes - if (mobj->movecount - 1 > K_GetMultItemFrame(mobj->movecount, 1)) - count = mobj->movecount; - break; - case KITEM_SUPERRING: // always display the number, and multiply it by 5 - count = mobj->movecount * 5; - break; - case KITEM_SAD: // never display the number - case KITEM_SPB: - break; - default: - if (mobj->movecount > 1) - count = mobj->movecount; - break; - } - } + if (mobj->movecount >= K_GetItemNumberDisplayMin(itemType, false)) + count = mobj->movecount; while (count > 0) { @@ -6192,6 +6162,9 @@ void P_SetScale2(mobj_t *mobj, fixed_t newscale, boolean compat) oldscale = mobj->scale; //keep for adjusting stuff below + if (LUA_HookMobjScaleChange(mobj, newscale, oldscale) || P_MobjWasRemoved(mobj)) + return; + mobj->scale = newscale; if (compat) @@ -6513,6 +6486,7 @@ boolean P_IsKartFieldItem(INT32 type) case MT_JAWZ_DUD: case MT_SSMINE: case MT_LANDMINE: + // case MT_EGGMINE_CAPSULE: case MT_BALLHOG: case MT_BUBBLESHIELDTRAP: case MT_SINK: @@ -7408,6 +7382,19 @@ static void P_MobjSceneryThink(mobj_t *mobj) return; } break; + case MT_BUBBLESHIELD_DEBRIS: + mobj->rollangle += ANG1 * 4; + + if (P_IsObjectOnGround(mobj) && mobj->health > 0) + { + // "Despawn" and play the glass landing sound. + if (mobj->extravalue1 == 0) + S_StartSoundAtVolume(mobj, mobj->info->deathsound, 192); + P_KillMobj(mobj, NULL, NULL, DMG_NORMAL); + mobj->fuse = TICRATE; + } + + goto dofuse; case MT_SMOLDERING: if (leveltime % 2 == 0) { @@ -7607,101 +7594,79 @@ static void P_MobjSceneryThink(mobj_t *mobj) if (!(mobj->renderflags & RF_DONTDRAW)) { - const INT32 numberdisplaymin = ((mobj->target->player->itemtype == KITEM_ORBINAUT) ? 5 : 2); + statenum_t statenum = S_PLAYERARROW_BOX; + kartitemtype_e itemtype = mobj->target->player->itemtype; + UINT8 itemamount = 0; + boolean hide = false; // Set it to use the correct states for its condition if (mobj->target->player->itemroulette) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = K_GetRollingRouletteItem(mobj->target->player) | FF_FULLBRIGHT; - mobj->tracer->renderflags &= ~RF_DONTDRAW; + itemtype = K_GetRollingRouletteItem(mobj->target->player); } else if (mobj->target->player->stealingtimer < 0) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_HYUDORO; - if (leveltime & 2) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; + itemtype = KITEM_HYUDORO; + hide = leveltime & 2; } else if ((mobj->target->player->stealingtimer > 0) && (leveltime & 2)) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_HYUDORO; - mobj->tracer->renderflags &= ~RF_DONTDRAW; + itemtype = KITEM_HYUDORO; } else if (mobj->target->player->eggmanexplode > 1) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_EGGMAN; - if (leveltime & 1) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; + itemtype = KITEM_EGGMAN; + hide = leveltime & 1; } else if (mobj->target->player->rocketsneakertimer > 1) { //itembar = mobj->target->player->rocketsneakertimer; -- not today satan - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_ROCKETSNEAKER; - if (leveltime & 1) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; + itemtype = KITEM_ROCKETSNEAKER; + hide = leveltime & 1; } else if (mobj->target->player->flametimer > 1) { //itembar = mobj->target->player->flametimer; -- not today satan - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_FLAMESHIELD; - if (leveltime & 1) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; + itemtype = KITEM_FLAMESHIELD; + hide = leveltime & 1; } else if (mobj->target->player->growshrinktimer > 0) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); - mobj->tracer->sprite = SPR_ITEM; - mobj->tracer->frame = FF_FULLBRIGHT|KITEM_GROW; - - if (leveltime & 1) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; + itemtype = KITEM_GROW; + hide = leveltime & 1; } else if (mobj->target->player->itemtype && mobj->target->player->itemamount > 0) { - P_SetMobjState(mobj, S_PLAYERARROW_BOX); + itemtype = mobj->target->player->itemtype; + itemamount = mobj->target->player->itemamount; + hide = mobj->target->player->itemflags & IF_ITEMOUT && leveltime & 1; + } + else + { + statenum = S_PLAYERARROW; + } - K_UpdateMobjItemOverlay(mobj->tracer, mobj->target->player->itemtype,mobj->target->player->itemamount); - - if (mobj->target->player->itemflags & IF_ITEMOUT) - { - if (leveltime & 1) - mobj->tracer->renderflags &= ~RF_DONTDRAW; - else - mobj->tracer->renderflags |= RF_DONTDRAW; - } + P_SetMobjState(mobj, statenum); + if (statenum == S_PLAYERARROW_BOX) + { + mobj->tracer->sprite = SPR_ITEM; + K_UpdateMobjItemOverlay(mobj->tracer, itemtype, itemamount); + mobj->tracer->threshold = itemtype; // sorry, i have to + mobj->tracer->frame &= ~FF_PAPERSPRITE; + if (hide) + mobj->tracer->renderflags |= RF_DONTDRAW; else mobj->tracer->renderflags &= ~RF_DONTDRAW; } else { - P_SetMobjState(mobj, S_PLAYERARROW); P_SetMobjState(mobj->tracer, S_PLAYERARROW_ITEM); + mobj->tracer->renderflags &= ~RF_DONTDRAW; } mobj->tracer->destscale = scale; - if (mobj->target->player->itemamount >= numberdisplaymin + if (mobj->target->player->itemamount >= K_GetItemNumberDisplayMin(mobj->target->player->itemtype, false) && mobj->target->player->itemamount <= 10) // Meh, too difficult to support greater than this; convert this to a decent HUD object and then maybe :V { mobj_t *number = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY); @@ -7717,6 +7682,10 @@ static void P_MobjSceneryThink(mobj_t *mobj) P_SetMobjState(numx, S_PLAYERARROW_X); P_SetScale(numx, mobj->scale); numx->destscale = scale; + + // ugh... overlays.... + number->old_z = number->z += FixedMul(number->state->var2 * FRACUNIT, mobj->scale); + numx->old_z = numx->z += FixedMul(number->state->var2 * FRACUNIT, mobj->scale); } if (K_IsPlayerWanted(mobj->target->player) && mobj->movecount != 1) @@ -7847,6 +7816,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) } /* FALLTHRU */ default: + dofuse: if (mobj->fuse) { // Scenery object fuse! Very basic! mobj->fuse--; @@ -8013,6 +7983,7 @@ static boolean P_MobjDeadThink(mobj_t *mobj) case MT_BANANA: case MT_EGGMANITEM: case MT_LANDMINE: + // case MT_EGGMINE_CAPSULE: case MT_SPB: if (P_IsObjectOnGround(mobj)) { @@ -8440,11 +8411,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } - if (mobj->threshold == KITEM_SPB || mobj->threshold == KITEM_SHRINK) - { - indirectitemcooldown = 20*TICRATE; - } - K_UpdateMobjItemOverlay(mobj, mobj->threshold, mobj->movecount); break; } @@ -8480,12 +8446,10 @@ static boolean P_MobjRegularThink(mobj_t *mobj) || mobj->movecount != part->movecount) // change the capsule properties if the item type or amount is updated P_RefreshItemCapsuleParts(mobj); - // animate invincibility capsules + K_UpdateMobjItemOverlay(part, mobj->threshold, mobj->movecount); + if (mobj->threshold == KITEM_INVINCIBILITY) - { mobj->color = K_RainbowColor(leveltime); - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); - } } break; case MT_ORBINAUT: @@ -8715,8 +8679,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->threshold--; break; case MT_SPB: - indirectitemcooldown = 20*TICRATE; - /* FALLTHRU */ case MT_BALLHOG: { mobj_t *ghost = P_SpawnGhostMobj(mobj); @@ -8805,6 +8767,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (mobj->threshold > 0) mobj->threshold--; break; + // case MT_EGGMINE_CAPSULE: + // todo + // decrement fuse while on ground + // when fuse runs out pop open and spawn a land mine + break; case MT_SPBEXPLOSION: mobj->health--; break; @@ -9218,18 +9185,17 @@ static boolean P_MobjRegularThink(mobj_t *mobj) for (i = 0; i < 2; i++) { angle_t a = mobj->angle + ((i & 1) ? ANGLE_180 : 0); - fixed_t ws = player->mo->scale/4 + scale/4; mobj_t *wave; wave = P_SpawnMobj( - (player->mo->x - player->mo->momx) + P_ReturnThrustX(NULL, a, mobj->radius - (21*ws)), - (player->mo->y - player->mo->momy) + P_ReturnThrustY(NULL, a, mobj->radius - (21*ws)), + (player->mo->x - player->mo->momx) + P_ReturnThrustX(NULL, a, mobj->radius - (5*scale)), + (player->mo->y - player->mo->momy) + P_ReturnThrustY(NULL, a, mobj->radius - (5*scale)), (player->mo->z - player->mo->momz), MT_THOK); wave->colorized = true; wave->color = SKINCOLOR_BLUE; wave->flags &= ~(MF_NOCLIPHEIGHT|MF_NOGRAVITY); - P_SetScale(wave, (wave->destscale = ws)); + P_SetScale(wave, (wave->destscale = scale/3)); P_SetMobjState(wave, S_SPLISH1); @@ -9300,45 +9266,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->flags |= MF_NOCLIP|MF_NOCLIPTHING; break; } - case MT_BUBBLESHLD_DEBRIS: - { - statenum_t curstate; - curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states))); - - if (curstate != S_INVISIBLE) - { - mobj->rollangle += ANG1 * 4; - } - else - { - mobj->rollangle = 0; - } - - if ((P_IsObjectOnGround(mobj)) && (!mobj->threshold)) - { - mobj->threshold = (TICRATE / 2); - - if (curstate != S_INVISIBLE) - { - // "Despawn" and play the glass landing sound. - if (mobj->extravalue1 == 0) - S_StartSoundAtVolume(mobj, mobj->info->activesound, 128); - P_SetMobjState(mobj, S_INVISIBLE); - } - } - - if (mobj->threshold) - { - mobj->threshold--; - - if (!mobj->threshold) - { - P_RemoveMobj(mobj); - } - } - - break; - } case MT_FLAMESHIELD: { statenum_t curstate; @@ -10926,6 +10853,8 @@ static void P_DefaultMobjShadowScale(mobj_t *thing) case MT_SSMINE: case MT_SSMINE_SHIELD: case MT_LANDMINE: + // case MT_EGGMINE_CAPSULE: + // case MT_EGGMINE_SHIELD: case MT_BALLHOG: case MT_SINK: case MT_ROCKETSNEAKER: @@ -11352,7 +11281,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) P_SetTarget(&spawn->target, mobj); break; } - case MT_BUBBLESHLD_DEBRIS: + case MT_BUBBLESHIELD_DEBRIS: { statenum_t shardset[] = {S_BUBBLEDEBRIS_1, S_BUBBLEDEBRIS_2, S_BUBBLEDEBRIS_3, S_BUBBLEDEBRIS_4, S_BUBBLEDEBRIS_5}; P_SetMobjState(mobj, shardset[P_RandomRange(0, 4)]); @@ -11370,7 +11299,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) else if (P_RandomChance(FRACUNIT/3)) mobj->threshold = KITEM_ORBINAUT; else - mobj->threshold = P_RandomRange(1, NUMKARTITEMS - 1); + mobj->threshold = P_RandomRange(1, numkartitems - 1); mobj->movecount = P_RandomChance(FRACUNIT/3) ? 1 : P_RandomKey(32) + 1; #else mobj->threshold = KITEM_SUPERRING; // default item is super ring @@ -12787,7 +12716,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) } case MT_ITEMCAPSULE: { - boolean isRingCapsule = (mthing->args[0] < 1 || mthing->args[0] == KITEM_SUPERRING || mthing->args[0] >= NUMKARTITEMS); + boolean isRingCapsule = mthing->args[0] < 1 || mthing->args[0] == KITEM_SUPERRING || mthing->args[0] >= numkartitems; // don't spawn ring capsules in with rings off. if (isRingCapsule && (K_RingsActive() == false)) @@ -13645,9 +13574,39 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean if (!P_IsObjectOnGround(mobj)) mobj->flags |= MF_NOGRAVITY; - // Angle = item type - if (mthing->args[0] > 0 && mthing->args[0] < NUMKARTITEMS) - mobj->threshold = mthing->args[0]; + // Item type + static kartitemtype_e rrtypes[] = { + KITEM_SNEAKER, + KITEM_ROCKETSNEAKER, + KITEM_INVINCIBILITY, + KITEM_BANANA, + KITEM_EGGMAN, + KITEM_ORBINAUT, + KITEM_JAWZ, + KITEM_MINE, + MAXKARTITEMS, // LANDMINE + KITEM_BALLHOG, + KITEM_SPB, + KITEM_GROW, + KITEM_SHRINK, + KITEM_THUNDERSHIELD, + KITEM_BUBBLESHIELD, + KITEM_FLAMESHIELD, + KITEM_HYUDORO, + KITEM_POGOSPRING, + KITEM_SUPERRING, + KITEM_KITCHENSINK, + MAXKARTITEMS, // DROPTARGET + MAXKARTITEMS, // GARDENTOP + MAXKARTITEMS, // GACHABOM + MAXKARTITEMS, // STONESHOE + MAXKARTITEMS, // TOXOMISTER + }; + + if (mthing->stringargs[0] != NULL) + mobj->threshold = DEH_FindKartItem(mthing->stringargs[0]); + else if (mapnamespace == MNS_RINGRACERS && mthing->args[0] > 0 && (size_t)mthing->args[0] < sizeof(rrtypes)/sizeof(*rrtypes)) + mobj->threshold = rrtypes[mthing->args[0] - 1]; // Parameter = extra items (x5 for rings) mobj->movecount += mthing->args[1]; diff --git a/src/p_saveg.c b/src/p_saveg.c index 7c96b3ec2..d35d1b3ad 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -41,7 +41,7 @@ #include "k_battle.h" #include "k_pwrlv.h" #include "k_terrain.h" -#include "k_odds.h" +#include "k_items.h" #include "acs/interface.h" #include "g_party.h" #include "k_waypoint.h" @@ -776,6 +776,9 @@ static void P_NetSyncPlayers(savebuffer_t *save) // Wall Transfer bonus SYNCBOOLEAN(players[i].walltransfered); SYNC(players[i].walltransferboost); + + SYNC(players[i].itemusecooldown); + SYNC(players[i].itemusecooldownmax); } TracyCZoneEnd(__zone); } @@ -3939,6 +3942,10 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending) SYNC(gametype); +#if MAXPLAYERS > 32 +#error Update playeringame syncing please +#endif + if (save->write) { UINT32 pig = 0; @@ -4182,7 +4189,6 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending) SYNCBOOLEAN(itemlittering); SYNCBOOLEAN(itempushing); SYNC(bumpsparkactive); - SYNC(invintype); SYNC(antibumptime); for (i = 0; i < sizeof(votelevels)/sizeof(*votelevels); i++) @@ -4267,14 +4273,12 @@ static boolean P_NetSyncMisc(savebuffer_t *save, boolean resending) SYNC(battlewanted[i]); SYNC(wantedcalcdelay); - SYNC(indirectitemcooldown); - for (i = 0; i < NUMKARTRESULTS; i++) - { - // hyubgone - //SYNC(ItemBGone[i][GONER_BASECOOLDOWN]); - SYNC(ItemBGone[i][GONER_CURRCOOLDOWN]); - } + for (i = 0; i < numkartitems; i++) + SYNC(kartitems[i].altenabled); + + for (i = 0; i < numkartresults; i++) + SYNC(kartresults[i].cooldown); SYNC(mapreset); diff --git a/src/p_setup.c b/src/p_setup.c index f07634c7e..e37767219 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -103,7 +103,7 @@ #include "k_terrain.h" // TRF_TRIPWIRE #include "k_brightmap.h" #include "k_director.h" // K_InitDirector -#include "k_odds.h" // ItemBGone +#include "k_items.h" #include "acs/interface.h" #include "doomstat.h" // MAXMUSNAMES #include "k_mapuser.h" @@ -8071,7 +8071,6 @@ static void P_InitLevelSettings(boolean reloadinggamestate) itempushing = true; bumpsparkactive = (UINT8)cv_kartbumpspark.value; - invintype = (UINT8)cv_kartinvintype.value; antibumptime = (tic_t)cv_kartantibump.value * TICRATE; @@ -8398,13 +8397,8 @@ static void P_InitGametype(void) numlaps = K_RaceLapCount(gamemap - 1); wantedcalcdelay = wantedfrequency*2; - indirectitemcooldown = 0; - for (i = 0; i < NUMKARTRESULTS; i++) - { - // hyubgone - ItemBGone[i][GONER_CURRCOOLDOWN] = 0; - } + K_SetupItemOdds(); mapreset = 0; diff --git a/src/p_spec.c b/src/p_spec.c index dfb60edcc..fe5f55483 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -45,6 +45,7 @@ // SRB2kart #include "k_kart.h" +#include "k_items.h" #include "console.h" // CON_LogMessage #include "k_terrain.h" #include "acs/interface.h" @@ -9222,7 +9223,8 @@ boolean P_AllowFriction(mobj_t *mobj) if (mobj->player->invincibilitytimer || mobj->player->hyudorotimer || mobj->player->sneakertimer - || mobj->player->growshrinktimer > 0) + || mobj->player->growshrinktimer > 0 + || K_IsAltShrunk(mobj->player)) return false; return true; diff --git a/src/p_tick.c b/src/p_tick.c index 81032f3ad..503689286 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -42,7 +42,7 @@ #include "k_director.h" #include "acs/interface.h" #include "k_bot.h" // K_BotTicker -#include "k_odds.h" // ItemBGone +#include "k_items.h" // ItemBGone #include "k_specialstage.h" #ifdef PARANOIA @@ -897,25 +897,7 @@ void P_Ticker(boolean run) if (exitcountdown > 1) exitcountdown--; - if (indirectitemcooldown > 0) - indirectitemcooldown--; - - for (i = 0; i < NUMKARTRESULTS; i++) - { - // hyubgone - if (K_GetBGone(i, false) > 0) - { - ItemBGone[i][GONER_CURRCOOLDOWN]--; - } - } - - if (gametyperules & GTR_RACEODDS) - { - // Shield cooldowns - K_KartHandleShieldCooldown(KITEM_THUNDERSHIELD); - K_KartHandleShieldCooldown(KITEM_BUBBLESHIELD); - K_KartHandleShieldCooldown(KITEM_FLAMESHIELD); - } + K_UpdateItemCooldown(); K_BossInfoTicker(); diff --git a/src/p_user.c b/src/p_user.c index 315556831..b3e723dc3 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -61,7 +61,7 @@ #include "k_terrain.h" // K_SpawnSplashForMobj #include "k_color.h" #include "k_follower.h" -#include "k_odds.h" +#include "k_items.h" #include "g_party.h" #include "acs/interface.h" @@ -831,6 +831,9 @@ void P_RestoreMusic(player_t *player) { wantedmus = 1; bestlocaltimer = players[p].growshrinktimer; } \ else if (players[p].invincibilitytimer > bestlocaltimer) \ { wantedmus = 2; bestlocaltimer = players[p].invincibilitytimer; } \ + else if ((K_IsKartItemAlternate(KITEM_SHRINK)) && \ + (K_GetShrinkTime(&players[p]) > bestlocaltimer)) \ + { wantedmus = 3; bestlocaltimer = K_GetShrinkTime(&players[p]); } \ } setbests(localplayertable[0]); setbests(localplayertable[1]); @@ -848,6 +851,9 @@ void P_RestoreMusic(player_t *player) wantedmus = 1; else if (player->invincibilitytimer > 1) wantedmus = 2; + else if ((K_IsKartItemAlternate(KITEM_SHRINK)) && + (K_GetShrinkTime(player) > 1)) + wantedmus = 3; } } @@ -864,6 +870,12 @@ void P_RestoreMusic(player_t *player) S_ChangeMusicInternal("kinvnc", true); S_SetRestoreMusicFadeInCvar(&cv_invincmusicfade); } + // Item - Shrink (Alternative) + else if ((wantedmus == 3) && cv_altshrinkmusic.value == 1) + { + S_ChangeMusicInternal("kshrnk", true); + S_SetRestoreMusicFadeInCvar(&cv_altshrinkmusicfade); + } else { #if 0 @@ -2286,7 +2298,7 @@ void P_MovePlayer(player_t *player) if ((player->invincibilitytimer > 0) && (((leveltime % max(1, 10 - FixedMul(9, K_InvincibilityGradient(player->invincibilitytimer)))) == 0) || - (K_GetKartInvinType() == KARTINVIN_LEGACY))) + (!K_IsKartItemAlternate(KITEM_INVINCIBILITY)))) { K_SpawnSparkleTrail(player->mo); } @@ -2483,7 +2495,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; + K_SetIndirectItemCooldown(0); } if (mo->flags & MF_BOSS) //don't OHKO bosses nor players! diff --git a/src/r_main.cpp b/src/r_main.cpp index 2612d4daa..f857dcb2e 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -140,6 +140,7 @@ static CV_PossibleValue_t fov_cons_t[] = {{60*FRACUNIT, "MIN"}, {179*FRACUNIT, " static CV_PossibleValue_t translucenthud_cons_t[] = {{0, "MIN"}, {10, "MAX"}, {0, NULL}}; static CV_PossibleValue_t maxportals_cons_t[] = {{0, "MIN"}, {12, "MAX"}, {0, NULL}}; // lmao rendering 32 portals, you're a card static CV_PossibleValue_t homremoval_cons_t[] = {{0, "No"}, {1, "Yes"}, {2, "Flash"}, {0, NULL}}; +static CV_PossibleValue_t secbright_cons_t[] = {{0, "MIN"}, {255, "MAX"}, {0, NULL}}; static void Fov_OnChange(void); @@ -182,6 +183,8 @@ consvar_t cv_fov[MAXSPLITSCREENPLAYERS] = { // Okay, whoever said homremoval causes a performance hit should be shot. consvar_t cv_homremoval = CVAR_INIT ("homremoval", "Yes", CV_SAVE, homremoval_cons_t, NULL); +consvar_t cv_secbright = CVAR_INIT ("r_secbright", "0", CV_SAVE, secbright_cons_t, NULL); + consvar_t cv_maxportals = CVAR_INIT ("maxportals", "2", CV_SAVE, maxportals_cons_t, NULL); consvar_t cv_renderstats = CVAR_INIT ("renderstats", "Off", 0, CV_OnOff, NULL); @@ -1780,6 +1783,7 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_shadow); CV_RegisterVar(&cv_skybox); CV_RegisterVar(&cv_ffloorclip); + CV_RegisterVar(&cv_secbright); for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { diff --git a/src/r_main.h b/src/r_main.h index c9fd110a7..ad018adaa 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -154,6 +154,7 @@ extern consvar_t cv_fov[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_skybox; extern consvar_t cv_tailspickup; extern consvar_t cv_debugfinishline; +extern consvar_t cv_secbright; extern consvar_t cv_sloperoll; extern consvar_t cv_sliptidetilt; diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index 274a686fd..406c8082e 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -18,6 +18,7 @@ #include "p_tick.h" #include "typedef.h" #include "r_fps.h" +#include "k_items.h" #ifdef ROTSPRITE fixed_t rollcosang[ROTANGLES]; @@ -84,7 +85,7 @@ static angle_t R_PlayerSpriteRotation(player_t *player, player_t *viewPlayer) // Hacky boolean to check if we're the rainbow Invincibility overlay boolean R_IsOverlayingInvinciblePlayer(mobj_t* mobj) { - return ((K_GetKartInvinType() == KARTINVIN_ALTERN) && (mobj->type == MT_OVERLAY) && + return ((K_IsKartItemAlternate(KITEM_INVINCIBILITY)) && (mobj->type == MT_OVERLAY) && (mobj->target) && (mobj->extravalue2) && (mobj->target->player) && (mobj->target->player->invincibilitytimer)); } diff --git a/src/r_plane.cpp b/src/r_plane.cpp index c3836b43e..1f44bb27b 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -1038,7 +1038,7 @@ void R_DrawSinglePlane(drawspandata_t *ds, visplane_t *pl, boolean allow_paralle spanfunctype = SPANDRAWFUNC_SPLAT; if (pl->polyobj->translucency == 0 || (pl->extra_colormap && (pl->extra_colormap->flags & CMF_FOG))) - light = (pl->lightlevel >> LIGHTSEGSHIFT); + light = std::max((pl->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else light = LIGHTLEVELS-1; } @@ -1079,16 +1079,17 @@ void R_DrawSinglePlane(drawspandata_t *ds, visplane_t *pl, boolean allow_paralle } if ((spanfunctype == SPANDRAWFUNC_SPLAT) || (pl->extra_colormap && (pl->extra_colormap->flags & CMF_FOG))) - light = (pl->lightlevel >> LIGHTSEGSHIFT); + light = std::max((pl->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else light = LIGHTLEVELS-1; } else if (pl->ffloor->fofflags & FOF_FOG) { spanfunctype = SPANDRAWFUNC_FOG; - light = (pl->lightlevel >> LIGHTSEGSHIFT); + light = std::max((pl->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); } - else light = (pl->lightlevel >> LIGHTSEGSHIFT); + else + light = std::max((pl->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); debug = SW_HI_FOFPLANES; } @@ -1147,6 +1148,8 @@ void R_DrawSinglePlane(drawspandata_t *ds, visplane_t *pl, boolean allow_paralle vidwidth, vidwidth); } } + else + light = std::max((pl->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); } ds->currentplane = pl; diff --git a/src/r_segs.cpp b/src/r_segs.cpp index e631578f6..af01e3f3d 100644 --- a/src/r_segs.cpp +++ b/src/r_segs.cpp @@ -274,7 +274,7 @@ static void R_RenderMaskedSegLoop(drawcolumndata_t* dc, drawseg_t *drawseg, INT3 if ((R_CheckColumnFunc(COLDRAWFUNC_FUZZY) == false) || (rlight->flags & FOF_FOG) || (rlight->extra_colormap && (rlight->extra_colormap->flags & CMF_FOG))) - lightnum = (rlight->lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((rlight->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else lightnum = LIGHTLEVELS - 1; @@ -919,9 +919,9 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) // Check if the current light effects the colormap/lightlevel if (pfloor->fofflags & FOF_FOG) - rlight->lightnum = (pfloor->master->frontsector->lightlevel >> LIGHTSEGSHIFT); + rlight->lightnum = std::max((pfloor->master->frontsector->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else - rlight->lightnum = (rlight->lightlevel >> LIGHTSEGSHIFT); + rlight->lightnum = std::max((rlight->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (pfloor->fofflags & FOF_FOG || rlight->flags & FOF_FOG || (rlight->extra_colormap && (rlight->extra_colormap->flags & CMF_FOG))) ; @@ -937,14 +937,14 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor) { // Get correct light level! if ((frontsector->extra_colormap && (frontsector->extra_colormap->flags & CMF_FOG))) - lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((frontsector->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else if (pfloor->fofflags & FOF_FOG) - lightnum = (pfloor->master->frontsector->lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((pfloor->master->frontsector->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); else if (R_CheckColumnFunc(COLDRAWFUNC_FUZZY) == true) lightnum = LIGHTLEVELS-1; else - lightnum = R_FakeFlat(frontsector, &tempsec, &templight, &templight, false) - ->lightlevel >> LIGHTSEGSHIFT; + lightnum = std::max((R_FakeFlat(frontsector, &tempsec, &templight, &templight, false) + ->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (pfloor->fofflags & FOF_FOG || (frontsector->extra_colormap && (frontsector->extra_colormap->flags & CMF_FOG))) ; @@ -1556,7 +1556,7 @@ static void R_RenderSegLoop (drawcolumndata_t* dc) for (i = 0; i < dc->numlights; i++) { INT32 lightnum; - lightnum = (dc->lightlist[i].lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((dc->lightlist[i].lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (dc->lightlist[i].extra_colormap) ; @@ -2665,7 +2665,7 @@ void R_StoreWallRange(INT32 start, INT32 stop) // use different light tables // for horizontal / vertical / diagonal // OPTIMIZE: get rid of LIGHTSEGSHIFT globally - lightnum = (frontsector->lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((frontsector->lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (P_ApplyLightOffset(lightnum, frontsector)) lightnum += curline->lightOffset; diff --git a/src/r_things.cpp b/src/r_things.cpp index b94349561..c537c5745 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -51,6 +51,7 @@ #include "k_color.h" #include "k_kart.h" #include "r_fps.h" +#include "k_items.h" #define MINZ (FRACUNIT*16) #define BASEYCENTER (BASEVIDHEIGHT/2) @@ -283,6 +284,57 @@ void R_AddKartFaces(skin_t *skin) } #undef NUMFACES +// dear god... what a mess... +void R_AddKartItemSprites(kartitem_t *item) +{ + spritedef_t *spritedef = &item->spritedef; + memset(sprtemp, 0xff, sizeof(sprtemp)); + spritename = "ITEM"; + maxframe = spritedef->numframes - 1; + + const kartitemgraphics_t *graphics = &item->graphics[0]; + for (UINT8 i = 0; i < graphics->numpatches; i++) + { + lumpnum_t lumpnum = W_CheckNumForName(graphics->patchnames[i]); + if (lumpnum == LUMPERROR) + I_Error("R_AddKartItemSprites: missing patch %s", graphics->patchnames[i]); + + spritecachedinfo[numspritelumps].width = graphics->patches[i]->width<patches[i]->leftoffset<patches[i]->topoffset<patches[i]->height<numframes && // has been allocated + spritedef->numframes < maxframe) // more frames are defined ? + { + Z_Free(spritedef->spriteframes); + spritedef->spriteframes = NULL; + } + + // allocate this sprite's frames + if (!spritedef->spriteframes) + spritedef->spriteframes = + static_cast(Z_Malloc(maxframe * sizeof (*spritedef->spriteframes), PU_STATIC, NULL)); + + spritedef->numframes = maxframe; + memcpy(spritedef->spriteframes, sprtemp, maxframe*sizeof (spriteframe_t)); +} + // Install a single sprite, given its identifying name (4 chars) // // (originally part of R_AddSpriteDefs) @@ -1268,7 +1320,7 @@ static void R_SplitSprite(vissprite_t *sprite) newsprite->cut = static_cast(newsprite->cut | SC_TOP); if (!(sector->lightlist[i].caster->fofflags & FOF_NOSHADE)) { - lightnum = (*sector->lightlist[i].lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((*sector->lightlist[i].lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (lightnum < 0) spritelights = scalelight[0]; @@ -1787,6 +1839,22 @@ static void R_ProjectSprite(mobj_t *thing) sprdef = &sprites[thing->sprite]; #ifdef ROTSPRITE sprinfo = &spriteinfo[thing->sprite]; +#endif + frame = thing->frame&FF_FRAMEMASK; + } + } + else if (thing->sprite == SPR_ITEM) + { + sprdef = &kartitems[thing->threshold > 0 && thing->threshold < numkartitems ? thing->threshold : 0].spritedef; + sprinfo = &spriteinfo[SPR_UNKN]; + + if (frame >= sprdef->numframes) { + CONS_Alert(CONS_ERROR, M_GetText("R_ProjectSprite: invalid kartitem sprite frame %zu\n"), frame); + thing->sprite = states[S_UNKNOWN].sprite; + thing->frame = states[S_UNKNOWN].frame; + sprdef = &sprites[thing->sprite]; +#ifdef ROTSPRITE + sprinfo = &spriteinfo[thing->sprite]; #endif frame = thing->frame&FF_FRAMEMASK; } @@ -1932,9 +2000,9 @@ static void R_ProjectSprite(mobj_t *thing) const fixed_t visoffymul = (vflip ? -FRACUNIT : FRACUNIT); - if (R_ThingIsUsingBakedOffsets(thing)) + if (R_ThingIsUsingBakedOffsets(interptarg)) { - R_RotateSpriteOffsetsByPitchRoll(thing, + R_RotateSpriteOffsetsByPitchRoll(interptarg, vflip, hflip, &interp, @@ -2286,7 +2354,7 @@ static void R_ProjectSprite(mobj_t *thing) lightnum = thing->subsector->sector->lightlevel; } - lightnum = (lightnum + R_ThingLightLevel(thing)) >> LIGHTSEGSHIFT; + lightnum = std::max((lightnum + R_ThingLightLevel(thing)) >> LIGHTSEGSHIFT, cv_secbright.value); if (maplighting.directional == true && P_SectorUsesDirectionalLighting(thing->subsector->sector)) { @@ -2712,7 +2780,7 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel) { if (sec->heightsec == -1) lightlevel = sec->lightlevel; - lightnum = (lightlevel >> LIGHTSEGSHIFT); + lightnum = std::max((lightlevel >> LIGHTSEGSHIFT), cv_secbright.value); if (lightnum < 0) spritelights = scalelight[0]; diff --git a/src/r_things.h b/src/r_things.h index cd101f2ea..3b6175af5 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -32,6 +32,7 @@ extern "C" { #define FEETADJUST (4<