diff --git a/src/d_netcmd.c b/src/d_netcmd.c index ecd1de50a..78054ff3d 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1886,8 +1886,6 @@ void SendWeaponPref(void) buf[0] = 0; if (cv_flipcam.value) buf[0] |= 1; - if (cv_analog.value) - buf[0] |= 2; SendNetXCmd(XD_WEAPONPREF, buf, 1); } @@ -1898,8 +1896,6 @@ void SendWeaponPref2(void) buf[0] = 0; if (cv_flipcam2.value) buf[0] |= 1; - if (cv_analog2.value) - buf[0] |= 2; SendNetXCmd2(XD_WEAPONPREF, buf, 1); } @@ -1910,8 +1906,6 @@ void SendWeaponPref3(void) buf[0] = 0; if (cv_flipcam3.value) buf[0] |= 1; - if (cv_analog3.value) - buf[0] |= 2; SendNetXCmd3(XD_WEAPONPREF, buf, 1); } @@ -1922,8 +1916,6 @@ void SendWeaponPref4(void) buf[0] = 0; if (cv_flipcam4.value) buf[0] |= 1; - if (cv_analog4.value) - buf[0] |= 2; SendNetXCmd4(XD_WEAPONPREF, buf, 1); } @@ -1931,11 +1923,9 @@ static void Got_WeaponPref(UINT8 **cp,INT32 playernum) { UINT8 prefs = READUINT8(*cp); - players[playernum].pflags &= ~(PF_FLIPCAM|PF_ANALOGMODE); + players[playernum].pflags &= ~(PF_FLIPCAM); if (prefs & 1) players[playernum].pflags |= PF_FLIPCAM; - if (prefs & 2) - players[playernum].pflags |= PF_ANALOGMODE; } static void Got_PowerLevel(UINT8 **cp,INT32 playernum) @@ -2857,13 +2847,15 @@ static void Got_Respawn(UINT8 **cp, INT32 playernum) return; } - // incase the above checks were modified to allow sending a respawn on these occasions: - if (players[respawnplayer].mo && !P_IsObjectOnGround(players[respawnplayer].mo)) - return; - if (players[respawnplayer].mo) - P_DamageMobj(players[respawnplayer].mo, NULL, NULL, 10000); - demo_extradata[playernum] |= DXD_RESPAWN; + { + // incase the above checks were modified to allow sending a respawn on these occasions: + if (!P_IsObjectOnGround(players[respawnplayer].mo)) + return; + + K_DoIngameRespawn(&players[respawnplayer]); + demo_extradata[playernum] |= DXD_RESPAWN; + } } /** Deals with an ::XD_RANDOMSEED message in a netgame. diff --git a/src/d_player.h b/src/d_player.h index 6e238e74c..07e0f7a97 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -121,7 +121,7 @@ typedef enum /*** misc ***/ PF_FORCESTRAFE = 1<<29, // Turning inputs are translated into strafing inputs - PF_ANALOGMODE = 1<<30, // Analog mode? + PF_HITFINISHLINE = 1<<30, // Already hit the finish line this tic // free: 1<<30 and 1<<31 } pflags_t; @@ -330,6 +330,7 @@ typedef enum k_springstars, // Spawn stars around a player when they hit a spring k_springcolor, // Color of spring stars k_killfield, // How long have you been in the kill field, stay in too long and lose a bumper + k_wrongway, // Display WRONG WAY on screen NUMKARTSTUFF } kartstufftype_t; diff --git a/src/dehacked.c b/src/dehacked.c index 88c283c3e..31986e942 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -6648,6 +6648,11 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit // DEZ respawn laser "S_DEZLASER", + "S_DEZLASER_TRAIL1", + "S_DEZLASER_TRAIL2", + "S_DEZLASER_TRAIL3", + "S_DEZLASER_TRAIL4", + "S_DEZLASER_TRAIL5", // Audience Members "S_RANDOMAUDIENCE", @@ -8167,7 +8172,7 @@ static const char *const PLAYERFLAG_LIST[] = { /*** misc ***/ "FORCESTRAFE", // Translate turn inputs into strafe inputs - "ANALOGMODE", // Analog mode? + "HITFINISHLINE", // Already hit the finish line this tic NULL // stop loop here. }; @@ -8485,7 +8490,8 @@ static const char *const KARTSTUFF_LIST[] = { "TIREGREASE", "SPRINGSTARS", "SPRINGCOLOR", - "KILLFIELD" + "KILLFIELD", + "WRONGWAY" }; #endif diff --git a/src/g_game.c b/src/g_game.c index 2845c78d3..df74da9fd 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2597,7 +2597,7 @@ void G_PlayerReborn(INT32 player) jointime = players[player].jointime; splitscreenindex = players[player].splitscreenindex; spectator = players[player].spectator; - pflags = (players[player].pflags & (PF_TIMEOVER|PF_FLIPCAM|PF_TAGIT|PF_TAGGED|PF_ANALOGMODE|PF_WANTSTOJOIN)); + pflags = (players[player].pflags & (PF_TIMEOVER|PF_FLIPCAM|PF_TAGIT|PF_TAGGED|PF_WANTSTOJOIN)); // As long as we're not in multiplayer, carry over cheatcodes from map to map if (!(netgame || multiplayer)) @@ -4935,7 +4935,10 @@ void G_ReadDemoExtraData(void) if (extradata & DXD_RESPAWN) { if (players[p].mo) - P_DamageMobj(players[p].mo, NULL, NULL, 10000); // Is this how this should work..? + { + // Is this how this should work..? + K_DoIngameRespawn(&players[p]); + } } if (extradata & DXD_SKIN) { diff --git a/src/info.c b/src/info.c index 9b6ccab8d..b1d95859b 100644 --- a/src/info.c +++ b/src/info.c @@ -2863,7 +2863,12 @@ state_t states[NUMSTATES] = {SPR_KBLN, FF_FULLBRIGHT|1, -1, {NULL}, 0, 0, S_BATTLEBUMPER2}, // S_BATTLEBUMPER2 {SPR_KBLN, FF_FULLBRIGHT|2, -1, {NULL}, 0, 0, S_BATTLEBUMPER3}, // S_BATTLEBUMPER3 - {SPR_DEZL, FF_FULLBRIGHT|FF_PAPERSPRITE, 8, {NULL}, 0, 0, S_NULL}, // S_DEZLASER + {SPR_DEZL, FF_FULLBRIGHT|FF_PAPERSPRITE, 8, {NULL}, 0, 0, S_NULL}, // S_DEZLASER + {SPR_DEZL, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL2}, // S_DEZLASER_TRAIL1 + {SPR_DEZL, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL3}, // S_DEZLASER_TRAIL2 + {SPR_DEZL, FF_FULLBRIGHT|FF_PAPERSPRITE|3, 4, {NULL}, 0, 0, S_DEZLASER_TRAIL4}, // S_DEZLASER_TRAIL3 + {SPR_DEZL, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL5}, // S_DEZLASER_TRAIL4 + {SPR_DEZL, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_NULL}, // S_DEZLASER_TRAIL5 {SPR_NULL, 0, 1, {A_RandomStateRange}, S_AUDIENCE_CHAO_CHEER1, S_AUDIENCE_CHAO_CHEER2, S_RANDOMAUDIENCE}, // S_RANDOMAUDIENCE @@ -16036,7 +16041,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_SPB_DEAD, // deathstate S_NULL, // xdeathstate sfx_s3k5d, // deathsound - 96*FRACUNIT, // speed + 80*FRACUNIT, // speed 24*FRACUNIT, // radius 48*FRACUNIT, // height 0, // display offset diff --git a/src/info.h b/src/info.h index b6cc0e7ff..c1eacfd31 100644 --- a/src/info.h +++ b/src/info.h @@ -3537,6 +3537,11 @@ typedef enum state // DEZ Laser respawn S_DEZLASER, + S_DEZLASER_TRAIL1, + S_DEZLASER_TRAIL2, + S_DEZLASER_TRAIL3, + S_DEZLASER_TRAIL4, + S_DEZLASER_TRAIL5, // Audience Members S_RANDOMAUDIENCE, diff --git a/src/k_kart.c b/src/k_kart.c index 7100e7053..27a4b1e1f 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1,5292 +1,5571 @@ -// SONIC ROBO BLAST 2 KART ~ ZarroTsu -//----------------------------------------------------------------------------- -/// \file k_kart.c -/// \brief SRB2kart general. -/// All of the SRB2kart-unique stuff. - -#include "k_kart.h" -#include "k_pwrlv.h" -#include "doomdef.h" -#include "hu_stuff.h" -#include "g_game.h" -#include "m_random.h" -#include "p_local.h" -#include "p_slopes.h" -#include "r_draw.h" -#include "r_local.h" -#include "s_sound.h" -#include "st_stuff.h" -#include "v_video.h" -#include "z_zone.h" -#include "m_misc.h" -#include "m_cond.h" -#include "f_finale.h" -#include "lua_hud.h" // For Lua hud checks -#include "lua_hook.h" // For MobjDamage and ShouldDamage - -#include "k_waypoint.h" - -// SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: -// gamespeed is cc (0 for easy, 1 for normal, 2 for hard) -// franticitems is Frantic Mode items, bool -// 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 - -//{ SRB2kart Color Code - -#define SKIN_RAMP_LENGTH 16 -#define DEFAULT_STARTTRANSCOLOR 96 -#define NUM_PALETTE_ENTRIES 256 - -// These should be within 14 characters to fit on the character select screen -const char *KartColor_Names[MAXSKINCOLORS] = -{ - "None", // SKINCOLOR_NONE - "White", // SKINCOLOR_WHITE - "Silver", // SKINCOLOR_SILVER - "Grey", // SKINCOLOR_GREY - "Nickel", // SKINCOLOR_NICKEL - "Black", // SKINCOLOR_BLACK - "Fairy", // SKINCOLOR_FAIRY - "Popcorn", // SKINCOLOR_POPCORN - "Artichoke", // SKINCOLOR_ARTICHOKE - "Pigeon", // SKINCOLOR_PIGEON - "Sepia", // SKINCOLOR_SEPIA - "Beige", // SKINCOLOR_BEIGE - "Caramel", // SKINCOLOR_CARAMEL - "Peach", // SKINCOLOR_PEACH - "Brown", // SKINCOLOR_BROWN - "Leather", // SKINCOLOR_LEATHER - "Salmon", // SKINCOLOR_SALMON - "Pink", // SKINCOLOR_PINK - "Rose", // SKINCOLOR_ROSE - "Cinnamon", // SKINCOLOR_CINNAMON - "Ruby", // SKINCOLOR_RUBY - "Raspberry", // SKINCOLOR_RASPBERRY - "Red", // SKINCOLOR_RED - "Crimson", // SKINCOLOR_CRIMSON - "Maroon", // SKINCOLOR_MAROON - "Lemonade", // SKINCOLOR_LEMONADE - "Scarlet", // SKINCOLOR_SCARLET - "Ketchup", // SKINCOLOR_KETCHUP - "Dawn", // SKINCOLOR_DAWN - "Sunset", // SKINCOLOR_SUNSET - "Creamsicle", // SKINCOLOR_CREAMSICLE - "Orange", // SKINCOLOR_ORANGE - "Rosewood", // SKINCOLOR_ROSEWOOD - "Tangerine", // SKINCOLOR_TANGERINE - "Tan", // SKINCOLOR_TAN - "Cream", // SKINCOLOR_CREAM - "Gold", // SKINCOLOR_GOLD - "Royal", // SKINCOLOR_ROYAL - "Bronze", // SKINCOLOR_BRONZE - "Copper", // SKINCOLOR_COPPER - "Yellow", // SKINCOLOR_YELLOW - "Mustard", // SKINCOLOR_MUSTARD - "Banana", // SKINCOLOR_BANANA - "Olive", // SKINCOLOR_OLIVE - "Crocodile", // SKINCOLOR_CROCODILE - "Peridot", // SKINCOLOR_PERIDOT - "Vomit", // SKINCOLOR_VOMIT - "Garden", // SKINCOLOR_GARDEN - "Lime", // SKINCOLOR_LIME - "Handheld", // SKINCOLOR_HANDHELD - "Tea", // SKINCOLOR_TEA - "Pistachio", // SKINCOLOR_PISTACHIO - "Moss", // SKINCOLOR_MOSS - "Camouflage", // SKINCOLOR_CAMOUFLAGE - "Robo-Hood", // SKINCOLOR_ROBOHOOD - "Mint", // SKINCOLOR_MINT - "Green", // SKINCOLOR_GREEN - "Pinetree", // SKINCOLOR_PINETREE - "Turtle", // SKINCOLOR_TURTLE - "Swamp", // SKINCOLOR_SWAMP - "Dream", // SKINCOLOR_DREAM - "Plague", // SKINCOLOR_PLAGUE - "Emerald", // SKINCOLOR_EMERALD - "Algae", // SKINCOLOR_ALGAE - "Caribbean", // SKINCOLOR_CARIBBEAN - "Azure", // SKINCOLOR_AZURE - "Aquamarine", // SKINCOLOR_AQUAMARINE - "Turquoise", // SKINCOLOR_TURQUOISE - "Teal", // SKINCOLOR_TEAL - "Cyan", // SKINCOLOR_CYAN - "Jawz", // SKINCOLOR_JAWZ - "Cerulean", // SKINCOLOR_CERULEAN - "Navy", // SKINCOLOR_NAVY - "Platinum", // SKINCOLOR_PLATINUM - "Slate", // SKINCOLOR_SLATE - "Steel", // SKINCOLOR_STEEL - "Thunder", // SKINCOLOR_THUNDER - "Nova", // SKINCOLOR_NOVA - "Rust", // SKINCOLOR_RUST - "Wristwatch", // SKINCOLOR_WRISTWATCH - "Jet", // SKINCOLOR_JET - "Sapphire", // SKINCOLOR_SAPPHIRE - "Ultramarine", // SKINCOLOR_ULTRAMARINE - "Periwinkle", // SKINCOLOR_PERIWINKLE - "Blue", // SKINCOLOR_BLUE - "Blueberry", // SKINCOLOR_BLUEBERRY - "Thistle", // SKINCOLOR_THISTLE - "Purple", // SKINCOLOR_PURPLE - "Pastel", // SKINCOLOR_PASTEL - "Moonslam", // SKINCOLOR_MOONSLAM - "Dusk", // SKINCOLOR_DUSK - "Bubblegum", // SKINCOLOR_BUBBLEGUM - "Magenta", // SKINCOLOR_MAGENTA - "Fuchsia", // SKINCOLOR_FUCHSIA - "Toxic", // SKINCOLOR_TOXIC - "Mauve", // SKINCOLOR_MAUVE - "Lavender", // SKINCOLOR_LAVENDER - "Byzantium", // SKINCOLOR_BYZANTIUM - "Pomegranate", // SKINCOLOR_POMEGRANATE - "Lilac", // SKINCOLOR_LILAC - "Taffy" // SKINCOLOR_TAFFY -}; - -// Color_Opposite replacement; frame setting has not been changed from 8 for most, should be done later -const UINT8 KartColor_Opposite[MAXSKINCOLORS*2] = -{ - SKINCOLOR_NONE,8, // SKINCOLOR_NONE - SKINCOLOR_BLACK,8, // SKINCOLOR_WHITE - SKINCOLOR_NICKEL,8, // SKINCOLOR_SILVER - SKINCOLOR_GREY,8, // SKINCOLOR_GREY - SKINCOLOR_SILVER,8, // SKINCOLOR_NICKEL - SKINCOLOR_WHITE,8, // SKINCOLOR_BLACK - SKINCOLOR_ARTICHOKE,12, // SKINCOLOR_FAIRY - SKINCOLOR_PIGEON,12, // SKINCOLOR_POPCORN - SKINCOLOR_FAIRY,12, // SKINCOLOR_ARTICHOKE - SKINCOLOR_POPCORN,12, // SKINCOLOR_PIGEON - SKINCOLOR_LEATHER,6, // SKINCOLOR_SEPIA - SKINCOLOR_BROWN,2, // SKINCOLOR_BEIGE - SKINCOLOR_CERULEAN,8, // SKINCOLOR_CARAMEL - SKINCOLOR_CYAN,8, // SKINCOLOR_PEACH - SKINCOLOR_BEIGE,8, // SKINCOLOR_BROWN - SKINCOLOR_SEPIA,8, // SKINCOLOR_LEATHER - SKINCOLOR_TEA,8, // SKINCOLOR_SALMON - SKINCOLOR_PISTACHIO,8, // SKINCOLOR_PINK - SKINCOLOR_MOSS,8, // SKINCOLOR_ROSE - SKINCOLOR_WRISTWATCH,6, // SKINCOLOR_CINNAMON - SKINCOLOR_SAPPHIRE,8, // SKINCOLOR_RUBY - SKINCOLOR_MINT,8, // SKINCOLOR_RASPBERRY - SKINCOLOR_GREEN,6, // SKINCOLOR_RED - SKINCOLOR_PINETREE,6, // SKINCOLOR_CRIMSON - SKINCOLOR_TOXIC,8, // SKINCOLOR_MAROON - SKINCOLOR_THUNDER,8, // SKINCOLOR_LEMONADE - SKINCOLOR_ALGAE,10, // SKINCOLOR_SCARLET - SKINCOLOR_MUSTARD,10, // SKINCOLOR_KETCHUP - SKINCOLOR_DUSK,8, // SKINCOLOR_DAWN - SKINCOLOR_MOONSLAM,8, // SKINCOLOR_SUNSET - SKINCOLOR_PERIWINKLE,8, // SKINCOLOR_CREAMSICLE - SKINCOLOR_BLUE,8, // SKINCOLOR_ORANGE - SKINCOLOR_BLUEBERRY,6, // SKINCOLOR_ROSEWOOD - SKINCOLOR_LIME,8, // SKINCOLOR_TANGERINE - SKINCOLOR_RUST,8, // SKINCOLOR_TAN - SKINCOLOR_COPPER,10, // SKINCOLOR_CREAM - SKINCOLOR_SLATE,8, // SKINCOLOR_GOLD - SKINCOLOR_PLATINUM,6, // SKINCOLOR_ROYAL - SKINCOLOR_STEEL,8, // SKINCOLOR_BRONZE - SKINCOLOR_CREAM,6, // SKINCOLOR_COPPER - SKINCOLOR_AQUAMARINE,8, // SKINCOLOR_YELLOW - SKINCOLOR_KETCHUP,8, // SKINCOLOR_MUSTARD - SKINCOLOR_EMERALD,8, // SKINCOLOR_BANANA - SKINCOLOR_TEAL,8, // SKINCOLOR_OLIVE - SKINCOLOR_BUBBLEGUM,8, // SKINCOLOR_CROCODILE - SKINCOLOR_NAVY,6, // SKINCOLOR_PERIDOT - SKINCOLOR_ROBOHOOD,8, // SKINCOLOR_VOMIT - SKINCOLOR_LAVENDER,6, // SKINCOLOR_GARDEN - SKINCOLOR_TANGERINE,8, // SKINCOLOR_LIME - SKINCOLOR_ULTRAMARINE,8, // SKINCOLOR_HANDHELD - SKINCOLOR_SALMON,8, // SKINCOLOR_TEA - SKINCOLOR_PINK,6, // SKINCOLOR_PISTACHIO - SKINCOLOR_ROSE,8, // SKINCOLOR_MOSS - SKINCOLOR_CAMOUFLAGE,8, // SKINCOLOR_CAMOUFLAGE - SKINCOLOR_VOMIT,8, // SKINCOLOR_ROBOHOOD - SKINCOLOR_RASPBERRY,8, // SKINCOLOR_MINT - SKINCOLOR_RED,8, // SKINCOLOR_GREEN - SKINCOLOR_CRIMSON,8, // SKINCOLOR_PINETREE - SKINCOLOR_MAGENTA,8, // SKINCOLOR_TURTLE - SKINCOLOR_BYZANTIUM,8, // SKINCOLOR_SWAMP - SKINCOLOR_POMEGRANATE,8, // SKINCOLOR_DREAM - SKINCOLOR_NOVA,8, // SKINCOLOR_PLAGUE - SKINCOLOR_BANANA,8, // SKINCOLOR_EMERALD - SKINCOLOR_SCARLET,10, // SKINCOLOR_ALGAE - SKINCOLOR_PURPLE,8, // SKINCOLOR_CARIBBEAN - SKINCOLOR_THISTLE,8, // SKINCOLOR_AZURE - SKINCOLOR_YELLOW,8, // SKINCOLOR_AQUAMARINE - SKINCOLOR_MAUVE,10, // SKINCOLOR_TURQUOISE - SKINCOLOR_OLIVE,8, // SKINCOLOR_TEAL - SKINCOLOR_PEACH,8, // SKINCOLOR_CYAN - SKINCOLOR_LILAC,10, // SKINCOLOR_JAWZ - SKINCOLOR_CARAMEL,8, // SKINCOLOR_CERULEAN - SKINCOLOR_PERIDOT,8, // SKINCOLOR_NAVY - SKINCOLOR_ROYAL,8, // SKINCOLOR_PLATINUM - SKINCOLOR_GOLD,10, // SKINCOLOR_SLATE - SKINCOLOR_BRONZE,10, // SKINCOLOR_STEEL - SKINCOLOR_LEMONADE,8, // SKINCOLOR_THUNDER - SKINCOLOR_PLAGUE,10, // SKINCOLOR_NOVA - SKINCOLOR_TAN,8, // SKINCOLOR_RUST - SKINCOLOR_CINNAMON,8, // SKINCOLOR_WRISTWATCH - SKINCOLOR_TAFFY,8, // SKINCOLOR_JET - SKINCOLOR_RUBY,6, // SKINCOLOR_SAPPHIRE - SKINCOLOR_HANDHELD,10, // SKINCOLOR_ULTRAMARINE - SKINCOLOR_CREAMSICLE,8, // SKINCOLOR_PERIWINKLE - SKINCOLOR_ORANGE,8, // SKINCOLOR_BLUE - SKINCOLOR_ROSEWOOD,8, // SKINCOLOR_BLUEBERRY - SKINCOLOR_AZURE,8, // SKINCOLOR_THISTLE - SKINCOLOR_CARIBBEAN,10, // SKINCOLOR_PURPLE - SKINCOLOR_FUCHSIA,11, // SKINCOLOR_PASTEL - SKINCOLOR_SUNSET,10, // SKINCOLOR_MOONSLAM - SKINCOLOR_DAWN,6, // SKINCOLOR_DUSK - SKINCOLOR_CROCODILE,8, // SKINCOLOR_BUBBLEGUM - SKINCOLOR_TURTLE,8, // SKINCOLOR_MAGENTA - SKINCOLOR_PASTEL,11, // SKINCOLOR_FUCHSIA - SKINCOLOR_MAROON,8, // SKINCOLOR_TOXIC - SKINCOLOR_TURQUOISE,8, // SKINCOLOR_MAUVE - SKINCOLOR_GARDEN,6, // SKINCOLOR_LAVENDER - SKINCOLOR_SWAMP,8, // SKINCOLOR_BYZANTIUM - SKINCOLOR_DREAM,8, // SKINCOLOR_POMEGRANATE - SKINCOLOR_JAWZ,6, // SKINCOLOR_LILAC - SKINCOLOR_JET,8 // SKINCOLOR_TAFFY -}; - -UINT8 colortranslations[MAXTRANSLATIONS][16] = { - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // SKINCOLOR_NONE - { 0, 0, 0, 0, 1, 2, 5, 8, 9, 11, 14, 17, 20, 22, 25, 28}, // SKINCOLOR_WHITE - { 0, 1, 2, 3, 5, 7, 9, 12, 13, 15, 18, 20, 23, 25, 27, 30}, // SKINCOLOR_SILVER - { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31}, // SKINCOLOR_GREY - { 3, 5, 8, 11, 15, 17, 19, 21, 23, 24, 25, 26, 27, 29, 30, 31}, // SKINCOLOR_NICKEL - { 4, 7, 11, 15, 20, 22, 24, 27, 28, 28, 28, 29, 29, 30, 30, 31}, // SKINCOLOR_BLACK - { 0, 1, 208, 208, 209, 210, 10, 14, 16, 18, 20, 22, 24, 26, 28, 31}, // SKINCOLOR_FAIRY - { 0, 80, 80, 81, 82, 218, 240, 11, 13, 16, 18, 21, 23, 26, 28, 31}, // SKINCOLOR_POPCORN - { 80, 88, 89, 98, 99, 91, 12, 14, 16, 18, 20, 22, 24, 26, 28, 31}, // SKINCOLOR_ARTICHOKE - { 0, 128, 129, 130, 146, 170, 14, 15, 17, 19, 21, 23, 25, 27, 29, 31}, // SKINCOLOR_PIGEON - { 0, 1, 3, 5, 7, 9, 241, 242, 243, 245, 247, 249, 236, 237, 238, 239}, // SKINCOLOR_SEPIA - { 0, 208, 216, 217, 240, 241, 242, 243, 245, 247, 249, 250, 251, 237, 238, 239}, // SKINCOLOR_BEIGE - {208, 48, 216, 217, 218, 220, 221, 223, 224, 226, 228, 230, 232, 234, 236, 239}, // SKINCOLOR_CARAMEL - { 0, 208, 48, 216, 218, 221, 212, 213, 214, 215, 206, 207, 197, 198, 199, 254}, // SKINCOLOR_PEACH - {216, 217, 219, 221, 224, 225, 227, 229, 230, 232, 234, 235, 237, 239, 29, 30}, // SKINCOLOR_BROWN - {218, 221, 224, 227, 229, 231, 233, 235, 237, 239, 28, 28, 29, 29, 30, 31}, // SKINCOLOR_LEATHER - { 0, 0, 0, 208, 208, 209, 210, 32, 34, 35, 36, 38, 40, 42, 44, 46}, // SKINCOLOR_SALMON - { 0, 208, 208, 209, 209, 210, 211, 211, 212, 213, 214, 215, 41, 43, 45, 46}, // SKINCOLOR_PINK - {209, 210, 211, 211, 212, 213, 214, 215, 41, 42, 43, 44, 45, 71, 46, 47}, // SKINCOLOR_ROSE - {216, 221, 224, 226, 228, 60, 61, 43, 44, 45, 71, 46, 47, 29, 30, 31}, // SKINCOLOR_CINNAMON - { 0, 208, 209, 210, 211, 213, 39, 40, 41, 43, 186, 186, 169, 169, 253, 254}, // SKINCOLOR_RUBY - { 0, 208, 209, 210, 32, 33, 34, 35, 37, 39, 41, 43, 44, 45, 46, 47}, // SKINCOLOR_RASPBERRY - {209, 210, 32, 34, 36, 38, 39, 40, 41, 42, 43, 44 , 45, 71, 46, 47}, // SKINCOLOR_RED - {210, 33, 35, 38, 40, 42, 43, 45, 71, 71, 46, 46, 47, 47, 30, 31}, // SKINCOLOR_CRIMSON - { 32, 33, 35, 37, 39, 41, 43, 237, 26, 26, 27, 27, 28, 29, 30, 31}, // SKINCOLOR_MAROON - { 0, 80, 81, 82, 83, 216, 210, 211, 212, 213, 214, 215, 43, 44, 71, 47}, // SKINCOLOR_LEMONADE - { 48, 49, 50, 51, 53, 34, 36, 38, 184, 185, 168, 168, 169, 169, 254, 31}, // SKINCOLOR_SCARLET - { 72, 73, 64, 51, 52, 54, 34, 36, 38, 40, 42, 43, 44, 71, 46, 47}, // SKINCOLOR_KETCHUP - { 0, 208, 216, 209, 210, 211, 212, 57, 58, 59, 60, 61, 63, 71, 47, 31}, // SKINCOLOR_DAWN - { 82, 72, 73, 64, 51, 53, 55, 213, 214, 195, 195, 173, 174, 175, 253, 254}, // SKINCOLOR_SUNSET - { 0, 0, 208, 208, 48, 49, 50, 52, 53, 54, 56, 57, 58, 60, 61, 63}, // SKINCOLOR_CREAMSICLE - {208, 48, 49, 50, 51, 52, 53, 54, 55, 57, 59, 60, 62, 44, 71, 47}, // SKINCOLOR_ORANGE - { 50, 52, 55, 56, 58, 59, 60, 61, 62, 63, 44, 45, 71, 46, 47, 30}, // SKINCOLOR_ROSEWOOD - { 80, 81, 82, 83, 64, 51, 52, 54, 55, 57, 58, 60, 61, 63, 71, 47}, // SKINCOLOR_TANGERINE - { 0, 80, 81, 82, 83, 84, 85, 86, 87, 245, 246, 248, 249, 251, 237, 239}, // SKINCOLOR_TAN - { 0, 80, 80, 81, 81, 49, 51, 222, 224, 227, 230, 233, 236, 239, 29, 31}, // SKINCOLOR_CREAM - { 0, 80, 81, 83, 64, 65, 66, 67, 68, 215, 69, 70, 44, 71, 46, 47}, // SKINCOLOR_GOLD - { 80, 81, 83, 64, 65, 223, 229, 196, 196, 197, 197, 198, 199, 29, 30, 31}, // SKINCOLOR_ROYAL - { 83, 64, 65, 66, 67, 215, 69, 70, 44, 44, 45, 71, 46, 47, 29, 31}, // SKINCOLOR_BRONZE - { 0, 82, 64, 65, 67, 68, 70, 237, 239, 28, 28, 29, 29, 30, 30, 31}, // SKINCOLOR_COPPER - { 0, 80, 81, 82, 83, 73, 84, 74, 64, 65, 66, 67, 68, 69, 70, 71}, // SKINCOLOR_YELLOW - { 80, 81, 82, 83, 64, 65, 65, 76, 76, 77, 77, 78, 79, 237, 239, 29}, // SKINCOLOR_MUSTARD - { 80, 81, 83, 72, 73, 74, 75, 76, 77, 78, 79, 236, 237, 238, 239, 30}, // SKINCOLOR_BANANA - { 80, 82, 73, 74, 75, 76, 77, 78, 79, 236, 237, 238, 239, 28, 29, 31}, // SKINCOLOR_OLIVE - { 0, 80, 81, 88, 88, 188, 189, 76, 76, 77, 78, 79, 236, 237, 238, 239}, // SKINCOLOR_CROCODILE - { 0, 80, 81, 88, 188, 189, 190, 191, 94, 94, 95, 95, 109, 110, 111, 31}, // SKINCOLOR_PERIDOT - { 0, 208, 216, 209, 218, 51, 65, 76, 191, 191, 126, 143, 138, 175, 169, 254}, // SKINCOLOR_VOMIT - { 81, 82, 83, 73, 64, 65, 66, 92, 92, 93, 93, 94, 95, 109, 110, 111}, // SKINCOLOR_GARDEN - { 0, 80, 81, 82, 83, 88, 89, 99, 100, 102, 104, 126, 143, 138, 139, 31}, // SKINCOLOR_LIME - { 83, 72, 73, 74, 75, 76, 102, 104, 105, 106, 107, 108, 109, 110, 111, 31}, // SKINCOLOR_HANDHELD - { 0, 80, 80, 81, 88, 89, 90, 91, 92, 93, 94, 95, 109, 110, 111, 31}, // SKINCOLOR_TEA - { 0, 80, 88, 88, 89, 90, 91, 102, 103, 104, 105, 106, 107, 108, 109, 110}, // SKINCOLOR_PISTACHIO - { 88, 89, 90, 91, 91, 92, 93, 94, 107, 107, 108, 108, 109, 109, 110, 111}, // SKINCOLOR_MOSS - {208, 84, 85, 240, 241, 243, 245, 94, 107, 108, 108, 109, 109, 110, 110, 111}, // SKINCOLOR_CAMOUFLAGE - { 0, 88, 98, 101, 103, 104, 105, 94, 94, 107, 95, 109, 110, 111, 30, 31}, // SKINCOLOR_ROBOHOOD - { 0, 88, 88, 89, 89, 100, 101, 102, 125, 126, 143, 143, 138, 175, 169, 254}, // SKINCOLOR_MINT - { 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}, // SKINCOLOR_GREEN - { 97, 99, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 30, 30, 31}, // SKINCOLOR_PINETREE - { 96, 112, 112, 113, 113, 114, 114, 115, 115, 116, 116, 117, 117, 118, 119, 111}, // SKINCOLOR_TURTLE - { 96, 112, 113, 114, 115, 116, 117, 118, 119, 119, 29, 29, 30, 30, 31, 31}, // SKINCOLOR_SWAMP - { 0, 0, 208, 208, 48, 89, 98, 100, 148, 148, 172, 172, 173, 173, 174, 175}, // SKINCOLOR_DREAM - { 80, 88, 96, 112, 113, 124, 142, 149, 149, 173, 174, 175, 169, 253, 254, 31}, // SKINCOLOR_PLAGUE - { 0, 120, 121, 112, 113, 114, 115, 125, 125, 126, 126, 127, 138, 175, 253, 254}, // SKINCOLOR_EMERALD - {128, 128, 129, 129, 130, 140, 124, 103, 104, 116, 116, 117, 118, 119, 111, 31}, // SKINCOLOR_ALGAE - { 0, 88, 89, 97, 113, 141, 135, 136, 136, 173, 173, 174, 174, 175, 199, 31}, // SKINCOLOR_CARIBBEAN - { 0, 80, 81, 82, 89, 140, 134, 135, 136, 172, 196, 197, 198, 199, 30, 31}, // SKINCOLOR_AZURE - { 0, 128, 120, 121, 122, 123, 124, 125, 126, 126, 127, 127, 118, 118, 119, 111}, // SKINCOLOR_AQUAMARINE - {128, 120, 121, 122, 123, 141, 141, 142, 142, 143, 143, 138, 138, 139, 139, 31}, // SKINCOLOR_TURQUOISE - { 0, 120, 120, 121, 140, 141, 142, 143, 143, 138, 138, 139, 139, 254, 254, 31}, // SKINCOLOR_TEAL - { 0, 0, 128, 128, 255, 131, 132, 134, 142, 142, 143, 127, 118, 119, 110, 111}, // SKINCOLOR_CYAN - { 0, 0, 128, 128, 129, 146, 133, 134, 135, 149, 149, 173, 173, 174, 175, 31}, // SKINCOLOR_JAWZ - { 0, 128, 129, 130, 131, 132, 133, 135, 136, 136, 137, 137, 138, 138, 139, 31}, // SKINCOLOR_CERULEAN - {128, 129, 130, 132, 134, 135, 136, 137, 137, 138, 138, 139, 139, 29, 30, 31}, // SKINCOLOR_NAVY - { 0, 0, 0, 144, 144, 145, 9, 11, 14, 142, 136, 137, 138, 138, 139, 31}, // SKINCOLOR_PLATINUM - { 0, 0, 144, 144, 144, 145, 145, 145, 170, 170, 171, 171, 172, 173, 174, 175}, // SKINCOLOR_SLATE - { 0, 144, 144, 145, 145, 170, 170, 171, 171, 172, 172, 173, 173, 174, 175, 31}, // SKINCOLOR_STEEL - { 80, 81, 82, 83, 64, 65, 11, 171, 172, 173, 173, 157, 158, 159, 254, 31}, // SKINCOLOR_THUNDER - { 0, 83, 49, 50, 51, 32, 192, 148, 148, 172, 173, 174, 175, 29, 30, 31}, // SKINCOLOR_NOVA - {208, 48, 216, 217, 240, 241, 242, 171, 172, 173, 24, 25, 26, 28, 29, 31}, // SKINCOLOR_RUST - { 48, 218, 221, 224, 227, 231, 196, 173, 173, 174, 159, 159, 253, 253, 254, 31}, // SKINCOLOR_WRISTWATCH - {145, 146, 147, 148, 149, 173, 173, 174, 175, 175, 28, 28, 29, 29, 30, 31}, // SKINCOLOR_JET - { 0, 128, 129, 131, 133, 135, 149, 150, 152, 154, 156, 158, 159, 253, 254, 31}, // SKINCOLOR_SAPPHIRE - { 0, 0, 120, 120, 121, 133, 135, 149, 149, 166, 166, 167, 168, 169, 254, 31}, // SKINCOLOR_ULTRAMARINE - { 0, 0, 144, 144, 145, 146, 147, 149, 150, 152, 154, 155, 157, 159, 253, 254}, // SKINCOLOR_PERIWINKLE - {144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 155, 156, 158, 253, 254, 31}, // SKINCOLOR_BLUE - {146, 148, 149, 150, 152, 153, 155, 157, 159, 253, 253, 254, 254, 31, 31, 31}, // SKINCOLOR_BLUEBERRY - { 0, 0, 0, 252, 252, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 254}, // SKINCOLOR_THISTLE - { 0, 252, 160, 161, 162, 163, 164, 165, 166, 167, 168, 168, 169, 169, 253, 254}, // SKINCOLOR_PURPLE - { 0, 128, 128, 129, 129, 146, 170, 162, 163, 164, 165, 166, 167, 168, 169, 254}, // SKINCOLOR_PASTEL - { 0, 144, 145, 146, 170, 162, 163, 184, 184, 207, 207, 44, 45, 46, 47, 31}, // SKINCOLOR_MOONSLAM - {252, 200, 201, 192, 193, 194, 172, 172, 173, 173, 174, 174, 175, 169, 253, 254}, // SKINCOLOR_DUSK - { 0, 252, 252, 200, 201, 181, 182, 183, 184, 166, 167, 168, 169, 253, 254, 31}, // SKINCOLOR_BUBBLEGUM - {176, 177, 178, 179, 180, 181, 182, 183, 184, 184, 185, 185, 186, 187, 30, 31}, // SKINCOLOR_MAGENTA - {208, 209, 209, 32, 33, 182, 183, 184, 185, 185, 186, 186, 187, 253, 254, 31}, // SKINCOLOR_FUCHSIA - { 0, 0, 88, 88, 89, 6, 8, 10, 193, 194, 195, 184, 185, 186, 187, 31}, // SKINCOLOR_TOXIC - { 80, 81, 82, 83, 64, 50, 201, 192, 193, 194, 195, 173, 174, 175, 253, 254}, // SKINCOLOR_MAUVE - {252, 177, 179, 192, 193, 194, 195, 196, 196, 197, 197, 198, 198, 199, 30, 31}, // SKINCOLOR_LAVENDER - {145, 192, 193, 194, 195, 196, 197, 198, 199, 199, 29, 29, 30, 30, 31, 31}, // SKINCOLOR_BYZANTIUM - {208, 209, 210, 211, 212, 213, 214, 195, 195, 196, 196, 197, 198, 199, 29, 30}, // SKINCOLOR_POMEGRANATE - { 0, 0, 0, 252, 252, 176, 200, 201, 179, 192, 193, 194, 195, 196, 197, 198}, // SKINCOLOR_LILAC - { 0, 252, 252, 200, 200, 201, 202, 203, 204, 204, 205, 206, 207, 43, 45, 47}, // SKINCOLOR_TAFFY - - // THESE STILL NEED CONVERTED!!! - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 100, 104, 113, 116, 119}, // SKINCOLOR_SUPER1 - { 0, 0, 0, 0, 0, 0, 0, 0, 96, 98, 101, 104, 113, 115, 117, 119}, // SKINCOLOR_SUPER2 - { 0, 0, 0, 0, 0, 0, 96, 98, 100, 102, 104, 113, 114, 116, 117, 119}, // SKINCOLOR_SUPER3 - { 0, 0, 0, 0, 96, 97, 99, 100, 102, 104, 113, 114, 115, 116, 117, 119}, // SKINCOLOR_SUPER4 - { 0, 0, 96, 0, 0, 0, 0, 0, 104, 113, 114, 115, 116, 117, 118, 119}, // SKINCOLOR_SUPER5 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 82, 85, 115, 117, 119}, // SKINCOLOR_TSUPER1 - { 0, 0, 0, 0, 0, 0, 0, 0, 80, 81, 83, 85, 115, 116, 117, 119}, // SKINCOLOR_TSUPER2 - { 0, 0, 0, 0, 0, 0, 80, 81, 82, 83, 85, 115, 116, 117, 118, 119}, // SKINCOLOR_TSUPER3 - { 0, 0, 0, 0, 80, 81, 82, 83, 84, 85, 115, 115, 116, 117, 118, 119}, // SKINCOLOR_TSUPER4 - { 0, 0, 80, 80, 81, 82, 83, 84, 85, 115, 115, 116, 117, 117, 118, 119}, // SKINCOLOR_TSUPER5 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 121, 123, 125, 127, 129, 132}, // SKINCOLOR_KSUPER1 - { 0, 0, 0, 0, 0, 0, 0, 0, 121, 122, 124, 125, 127, 128, 130, 132}, // SKINCOLOR_KSUPER2 - { 0, 0, 0, 0, 0, 0, 121, 122, 123, 124, 125, 127, 128, 129, 130, 132}, // SKINCOLOR_KSUPER3 - { 0, 0, 0, 0, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132}, // SKINCOLOR_KSUPER4 - { 0, 0, 121, 121, 122, 123, 124, 125, 126, 126, 127, 128, 129, 130, 131, 132}, // SKINCOLOR_KSUPER5 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 122, 124, 248, 251, 255}, // SKINCOLOR_PSUPER1 - { 0, 0, 0, 0, 0, 0, 0, 0, 1, 121, 122, 124, 248, 250, 252, 255}, // SKINCOLOR_PSUPER2 - { 0, 0, 0, 0, 0, 0, 1, 121, 122, 123, 124, 248, 249, 251, 253, 255}, // SKINCOLOR_PSUPER3 - { 0, 0, 0, 0, 1, 121, 122, 123, 124, 248, 249, 250, 251, 252, 253, 255}, // SKINCOLOR_PSUPER4 - { 0, 0, 1, 121, 122, 123, 124, 248, 248, 249, 250, 251, 252, 253, 254, 255}, // SKINCOLOR_PSUPER5 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 225, 227, 228, 230, 232}, // SKINCOLOR_BSUPER1 - { 0, 0, 0, 0, 0, 0, 0, 0, 224, 225, 226, 227, 228, 229, 230, 232}, // SKINCOLOR_BSUPER2 - { 0, 0, 0, 0, 0, 0, 224, 224, 225, 226, 227, 228, 229, 230, 231, 232}, // SKINCOLOR_BSUPER3 - { 0, 0, 0, 0, 224, 224, 225, 226, 226, 227, 228, 229, 229, 230, 231, 232}, // SKINCOLOR_BSUPER4 - { 0, 0, 224, 224, 225, 225, 226, 227, 227, 228, 228, 229, 230, 230, 231, 232}, // SKINCOLOR_BSUPER5 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 208, 210, 212, 215, 220, 222}, // SKINCOLOR_ASUPER1 - { 0, 0, 0, 0, 0, 0, 0, 0, 208, 209, 211, 213, 215, 220, 221, 223}, // SKINCOLOR_ASUPER2 - { 0, 0, 0, 0, 0, 0, 208, 209, 210, 211, 212, 213, 215, 220, 221, 223}, // SKINCOLOR_ASUPER3 - { 0, 0, 0, 0, 208, 209, 210, 211, 212, 213, 214, 215, 220, 221, 222, 223}, // SKINCOLOR_ASUPER4 - { 0, 0, 208, 208, 209, 210, 211, 211, 212, 213, 214, 215, 220, 221, 222, 223}, // SKINCOLOR_ASUPER5 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 160, 163, 167, 171, 175}, // SKINCOLOR_GSUPER1 - { 0, 0, 0, 0, 0, 0, 0, 0, 176, 176, 160, 163, 166, 169, 172, 175}, // SKINCOLOR_GSUPER2 - { 0, 0, 0, 0, 0, 0, 176, 176, 160, 162, 164, 166, 168, 170, 172, 175}, // SKINCOLOR_GSUPER3 - { 0, 0, 0, 0, 176, 176, 176, 160, 161, 163, 165, 167, 169, 171, 173, 175}, // SKINCOLOR_GSUPER4 - { 0, 0, 176, 176, 176, 160, 161, 163, 164, 166, 167, 169, 170, 172, 173, 175}, // SKINCOLOR_GSUPER5 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // SKINCOLOR_WSUPER1 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 9}, // SKINCOLOR_WSUPER2 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 6, 8, 11}, // SKINCOLOR_WSUPER3 - { 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 4, 6, 8, 9, 11, 13}, // SKINCOLOR_WSUPER4 - { 0, 0, 0, 0, 1, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 15}, // SKINCOLOR_WSUPER5 - { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 98, 99, 81, 73, 79}, // SKINCOLOR_CSUPER1 - { 0, 0, 0, 0, 0, 0, 0, 0, 96, 97, 98, 81, 81, 71, 75, 79}, // SKINCOLOR_CSUPER2 - { 0, 0, 0, 0, 0, 0, 96, 97, 98, 99, 81, 81, 70, 73, 76, 79}, // SKINCOLOR_CSUPER3 - { 0, 0, 0, 0, 96, 96, 97, 98, 99, 81, 81, 70, 72, 74, 76, 79}, // SKINCOLOR_CSUPER4 - { 0, 0, 96, 96, 97, 98, 98, 99, 81, 81, 69, 71, 73, 75, 77, 79}, // SKINCOLOR_CSUPER5 -}; - -// Define for getting accurate color brightness readings according to how the human eye sees them. -// https://en.wikipedia.org/wiki/Relative_luminance -// 0.2126 to red -// 0.7152 to green -// 0.0722 to blue -// (See this same define in hw_md2.c!) -#define SETBRIGHTNESS(brightness,r,g,b) \ - brightness = (UINT8)(((1063*(UINT16)(r))/5000) + ((3576*(UINT16)(g))/5000) + ((361*(UINT16)(b))/5000)) - -/** \brief Generates the rainbow colourmaps that are used when a player has the invincibility power - - \param dest_colormap colormap to populate - \param skincolor translation color -*/ -void K_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor) -{ - INT32 i; - RGBA_t color; - UINT8 brightness; - INT32 j; - UINT8 colorbrightnesses[16]; - UINT16 brightdif; - INT32 temp; - - // first generate the brightness of all the colours of that skincolour - for (i = 0; i < 16; i++) - { - color = V_GetColor(colortranslations[skincolor][i]); - SETBRIGHTNESS(colorbrightnesses[i], color.s.red, color.s.green, color.s.blue); - } - - // next, for every colour in the palette, choose the transcolor that has the closest brightness - for (i = 0; i < NUM_PALETTE_ENTRIES; i++) - { - if (i == 0 || i == 31) // pure black and pure white don't change - { - dest_colormap[i] = (UINT8)i; - continue; - } - color = V_GetColor(i); - SETBRIGHTNESS(brightness, color.s.red, color.s.green, color.s.blue); - brightdif = 256; - for (j = 0; j < 16; j++) - { - temp = abs((INT16)brightness - (INT16)colorbrightnesses[j]); - if (temp < brightdif) - { - brightdif = (UINT16)temp; - dest_colormap[i] = colortranslations[skincolor][j]; +// SONIC ROBO BLAST 2 KART ~ ZarroTsu +//----------------------------------------------------------------------------- +/// \file k_kart.c +/// \brief SRB2kart general. +/// All of the SRB2kart-unique stuff. + +#include "k_kart.h" +#include "k_pwrlv.h" +#include "doomdef.h" +#include "hu_stuff.h" +#include "g_game.h" +#include "m_random.h" +#include "p_local.h" +#include "p_slopes.h" +#include "p_setup.h" +#include "r_draw.h" +#include "r_local.h" +#include "s_sound.h" +#include "st_stuff.h" +#include "v_video.h" +#include "z_zone.h" +#include "m_misc.h" +#include "m_cond.h" +#include "f_finale.h" +#include "lua_hud.h" // For Lua hud checks +#include "lua_hook.h" // For MobjDamage and ShouldDamage + +#include "k_waypoint.h" + +// SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: +// gamespeed is cc (0 for easy, 1 for normal, 2 for hard) +// franticitems is Frantic Mode items, bool +// 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 + +//{ SRB2kart Color Code + +#define SKIN_RAMP_LENGTH 16 +#define DEFAULT_STARTTRANSCOLOR 96 +#define NUM_PALETTE_ENTRIES 256 + +// These should be within 14 characters to fit on the character select screen +const char *KartColor_Names[MAXSKINCOLORS] = +{ + "None", // SKINCOLOR_NONE + "White", // SKINCOLOR_WHITE + "Silver", // SKINCOLOR_SILVER + "Grey", // SKINCOLOR_GREY + "Nickel", // SKINCOLOR_NICKEL + "Black", // SKINCOLOR_BLACK + "Fairy", // SKINCOLOR_FAIRY + "Popcorn", // SKINCOLOR_POPCORN + "Artichoke", // SKINCOLOR_ARTICHOKE + "Pigeon", // SKINCOLOR_PIGEON + "Sepia", // SKINCOLOR_SEPIA + "Beige", // SKINCOLOR_BEIGE + "Caramel", // SKINCOLOR_CARAMEL + "Peach", // SKINCOLOR_PEACH + "Brown", // SKINCOLOR_BROWN + "Leather", // SKINCOLOR_LEATHER + "Salmon", // SKINCOLOR_SALMON + "Pink", // SKINCOLOR_PINK + "Rose", // SKINCOLOR_ROSE + "Cinnamon", // SKINCOLOR_CINNAMON + "Ruby", // SKINCOLOR_RUBY + "Raspberry", // SKINCOLOR_RASPBERRY + "Red", // SKINCOLOR_RED + "Crimson", // SKINCOLOR_CRIMSON + "Maroon", // SKINCOLOR_MAROON + "Lemonade", // SKINCOLOR_LEMONADE + "Scarlet", // SKINCOLOR_SCARLET + "Ketchup", // SKINCOLOR_KETCHUP + "Dawn", // SKINCOLOR_DAWN + "Sunset", // SKINCOLOR_SUNSET + "Creamsicle", // SKINCOLOR_CREAMSICLE + "Orange", // SKINCOLOR_ORANGE + "Rosewood", // SKINCOLOR_ROSEWOOD + "Tangerine", // SKINCOLOR_TANGERINE + "Tan", // SKINCOLOR_TAN + "Cream", // SKINCOLOR_CREAM + "Gold", // SKINCOLOR_GOLD + "Royal", // SKINCOLOR_ROYAL + "Bronze", // SKINCOLOR_BRONZE + "Copper", // SKINCOLOR_COPPER + "Yellow", // SKINCOLOR_YELLOW + "Mustard", // SKINCOLOR_MUSTARD + "Banana", // SKINCOLOR_BANANA + "Olive", // SKINCOLOR_OLIVE + "Crocodile", // SKINCOLOR_CROCODILE + "Peridot", // SKINCOLOR_PERIDOT + "Vomit", // SKINCOLOR_VOMIT + "Garden", // SKINCOLOR_GARDEN + "Lime", // SKINCOLOR_LIME + "Handheld", // SKINCOLOR_HANDHELD + "Tea", // SKINCOLOR_TEA + "Pistachio", // SKINCOLOR_PISTACHIO + "Moss", // SKINCOLOR_MOSS + "Camouflage", // SKINCOLOR_CAMOUFLAGE + "Robo-Hood", // SKINCOLOR_ROBOHOOD + "Mint", // SKINCOLOR_MINT + "Green", // SKINCOLOR_GREEN + "Pinetree", // SKINCOLOR_PINETREE + "Turtle", // SKINCOLOR_TURTLE + "Swamp", // SKINCOLOR_SWAMP + "Dream", // SKINCOLOR_DREAM + "Plague", // SKINCOLOR_PLAGUE + "Emerald", // SKINCOLOR_EMERALD + "Algae", // SKINCOLOR_ALGAE + "Caribbean", // SKINCOLOR_CARIBBEAN + "Azure", // SKINCOLOR_AZURE + "Aquamarine", // SKINCOLOR_AQUAMARINE + "Turquoise", // SKINCOLOR_TURQUOISE + "Teal", // SKINCOLOR_TEAL + "Cyan", // SKINCOLOR_CYAN + "Jawz", // SKINCOLOR_JAWZ + "Cerulean", // SKINCOLOR_CERULEAN + "Navy", // SKINCOLOR_NAVY + "Platinum", // SKINCOLOR_PLATINUM + "Slate", // SKINCOLOR_SLATE + "Steel", // SKINCOLOR_STEEL + "Thunder", // SKINCOLOR_THUNDER + "Nova", // SKINCOLOR_NOVA + "Rust", // SKINCOLOR_RUST + "Wristwatch", // SKINCOLOR_WRISTWATCH + "Jet", // SKINCOLOR_JET + "Sapphire", // SKINCOLOR_SAPPHIRE + "Ultramarine", // SKINCOLOR_ULTRAMARINE + "Periwinkle", // SKINCOLOR_PERIWINKLE + "Blue", // SKINCOLOR_BLUE + "Blueberry", // SKINCOLOR_BLUEBERRY + "Thistle", // SKINCOLOR_THISTLE + "Purple", // SKINCOLOR_PURPLE + "Pastel", // SKINCOLOR_PASTEL + "Moonslam", // SKINCOLOR_MOONSLAM + "Dusk", // SKINCOLOR_DUSK + "Bubblegum", // SKINCOLOR_BUBBLEGUM + "Magenta", // SKINCOLOR_MAGENTA + "Fuchsia", // SKINCOLOR_FUCHSIA + "Toxic", // SKINCOLOR_TOXIC + "Mauve", // SKINCOLOR_MAUVE + "Lavender", // SKINCOLOR_LAVENDER + "Byzantium", // SKINCOLOR_BYZANTIUM + "Pomegranate", // SKINCOLOR_POMEGRANATE + "Lilac", // SKINCOLOR_LILAC + "Taffy" // SKINCOLOR_TAFFY +}; + +// Color_Opposite replacement; frame setting has not been changed from 8 for most, should be done later +const UINT8 KartColor_Opposite[MAXSKINCOLORS*2] = +{ + SKINCOLOR_NONE,8, // SKINCOLOR_NONE + SKINCOLOR_BLACK,8, // SKINCOLOR_WHITE + SKINCOLOR_NICKEL,8, // SKINCOLOR_SILVER + SKINCOLOR_GREY,8, // SKINCOLOR_GREY + SKINCOLOR_SILVER,8, // SKINCOLOR_NICKEL + SKINCOLOR_WHITE,8, // SKINCOLOR_BLACK + SKINCOLOR_ARTICHOKE,12, // SKINCOLOR_FAIRY + SKINCOLOR_PIGEON,12, // SKINCOLOR_POPCORN + SKINCOLOR_FAIRY,12, // SKINCOLOR_ARTICHOKE + SKINCOLOR_POPCORN,12, // SKINCOLOR_PIGEON + SKINCOLOR_LEATHER,6, // SKINCOLOR_SEPIA + SKINCOLOR_BROWN,2, // SKINCOLOR_BEIGE + SKINCOLOR_CERULEAN,8, // SKINCOLOR_CARAMEL + SKINCOLOR_CYAN,8, // SKINCOLOR_PEACH + SKINCOLOR_BEIGE,8, // SKINCOLOR_BROWN + SKINCOLOR_SEPIA,8, // SKINCOLOR_LEATHER + SKINCOLOR_TEA,8, // SKINCOLOR_SALMON + SKINCOLOR_PISTACHIO,8, // SKINCOLOR_PINK + SKINCOLOR_MOSS,8, // SKINCOLOR_ROSE + SKINCOLOR_WRISTWATCH,6, // SKINCOLOR_CINNAMON + SKINCOLOR_SAPPHIRE,8, // SKINCOLOR_RUBY + SKINCOLOR_MINT,8, // SKINCOLOR_RASPBERRY + SKINCOLOR_GREEN,6, // SKINCOLOR_RED + SKINCOLOR_PINETREE,6, // SKINCOLOR_CRIMSON + SKINCOLOR_TOXIC,8, // SKINCOLOR_MAROON + SKINCOLOR_THUNDER,8, // SKINCOLOR_LEMONADE + SKINCOLOR_ALGAE,10, // SKINCOLOR_SCARLET + SKINCOLOR_MUSTARD,10, // SKINCOLOR_KETCHUP + SKINCOLOR_DUSK,8, // SKINCOLOR_DAWN + SKINCOLOR_MOONSLAM,8, // SKINCOLOR_SUNSET + SKINCOLOR_PERIWINKLE,8, // SKINCOLOR_CREAMSICLE + SKINCOLOR_BLUE,8, // SKINCOLOR_ORANGE + SKINCOLOR_BLUEBERRY,6, // SKINCOLOR_ROSEWOOD + SKINCOLOR_LIME,8, // SKINCOLOR_TANGERINE + SKINCOLOR_RUST,8, // SKINCOLOR_TAN + SKINCOLOR_COPPER,10, // SKINCOLOR_CREAM + SKINCOLOR_SLATE,8, // SKINCOLOR_GOLD + SKINCOLOR_PLATINUM,6, // SKINCOLOR_ROYAL + SKINCOLOR_STEEL,8, // SKINCOLOR_BRONZE + SKINCOLOR_CREAM,6, // SKINCOLOR_COPPER + SKINCOLOR_AQUAMARINE,8, // SKINCOLOR_YELLOW + SKINCOLOR_KETCHUP,8, // SKINCOLOR_MUSTARD + SKINCOLOR_EMERALD,8, // SKINCOLOR_BANANA + SKINCOLOR_TEAL,8, // SKINCOLOR_OLIVE + SKINCOLOR_BUBBLEGUM,8, // SKINCOLOR_CROCODILE + SKINCOLOR_NAVY,6, // SKINCOLOR_PERIDOT + SKINCOLOR_ROBOHOOD,8, // SKINCOLOR_VOMIT + SKINCOLOR_LAVENDER,6, // SKINCOLOR_GARDEN + SKINCOLOR_TANGERINE,8, // SKINCOLOR_LIME + SKINCOLOR_ULTRAMARINE,8, // SKINCOLOR_HANDHELD + SKINCOLOR_SALMON,8, // SKINCOLOR_TEA + SKINCOLOR_PINK,6, // SKINCOLOR_PISTACHIO + SKINCOLOR_ROSE,8, // SKINCOLOR_MOSS + SKINCOLOR_CAMOUFLAGE,8, // SKINCOLOR_CAMOUFLAGE + SKINCOLOR_VOMIT,8, // SKINCOLOR_ROBOHOOD + SKINCOLOR_RASPBERRY,8, // SKINCOLOR_MINT + SKINCOLOR_RED,8, // SKINCOLOR_GREEN + SKINCOLOR_CRIMSON,8, // SKINCOLOR_PINETREE + SKINCOLOR_MAGENTA,8, // SKINCOLOR_TURTLE + SKINCOLOR_BYZANTIUM,8, // SKINCOLOR_SWAMP + SKINCOLOR_POMEGRANATE,8, // SKINCOLOR_DREAM + SKINCOLOR_NOVA,8, // SKINCOLOR_PLAGUE + SKINCOLOR_BANANA,8, // SKINCOLOR_EMERALD + SKINCOLOR_SCARLET,10, // SKINCOLOR_ALGAE + SKINCOLOR_PURPLE,8, // SKINCOLOR_CARIBBEAN + SKINCOLOR_THISTLE,8, // SKINCOLOR_AZURE + SKINCOLOR_YELLOW,8, // SKINCOLOR_AQUAMARINE + SKINCOLOR_MAUVE,10, // SKINCOLOR_TURQUOISE + SKINCOLOR_OLIVE,8, // SKINCOLOR_TEAL + SKINCOLOR_PEACH,8, // SKINCOLOR_CYAN + SKINCOLOR_LILAC,10, // SKINCOLOR_JAWZ + SKINCOLOR_CARAMEL,8, // SKINCOLOR_CERULEAN + SKINCOLOR_PERIDOT,8, // SKINCOLOR_NAVY + SKINCOLOR_ROYAL,8, // SKINCOLOR_PLATINUM + SKINCOLOR_GOLD,10, // SKINCOLOR_SLATE + SKINCOLOR_BRONZE,10, // SKINCOLOR_STEEL + SKINCOLOR_LEMONADE,8, // SKINCOLOR_THUNDER + SKINCOLOR_PLAGUE,10, // SKINCOLOR_NOVA + SKINCOLOR_TAN,8, // SKINCOLOR_RUST + SKINCOLOR_CINNAMON,8, // SKINCOLOR_WRISTWATCH + SKINCOLOR_TAFFY,8, // SKINCOLOR_JET + SKINCOLOR_RUBY,6, // SKINCOLOR_SAPPHIRE + SKINCOLOR_HANDHELD,10, // SKINCOLOR_ULTRAMARINE + SKINCOLOR_CREAMSICLE,8, // SKINCOLOR_PERIWINKLE + SKINCOLOR_ORANGE,8, // SKINCOLOR_BLUE + SKINCOLOR_ROSEWOOD,8, // SKINCOLOR_BLUEBERRY + SKINCOLOR_AZURE,8, // SKINCOLOR_THISTLE + SKINCOLOR_CARIBBEAN,10, // SKINCOLOR_PURPLE + SKINCOLOR_FUCHSIA,11, // SKINCOLOR_PASTEL + SKINCOLOR_SUNSET,10, // SKINCOLOR_MOONSLAM + SKINCOLOR_DAWN,6, // SKINCOLOR_DUSK + SKINCOLOR_CROCODILE,8, // SKINCOLOR_BUBBLEGUM + SKINCOLOR_TURTLE,8, // SKINCOLOR_MAGENTA + SKINCOLOR_PASTEL,11, // SKINCOLOR_FUCHSIA + SKINCOLOR_MAROON,8, // SKINCOLOR_TOXIC + SKINCOLOR_TURQUOISE,8, // SKINCOLOR_MAUVE + SKINCOLOR_GARDEN,6, // SKINCOLOR_LAVENDER + SKINCOLOR_SWAMP,8, // SKINCOLOR_BYZANTIUM + SKINCOLOR_DREAM,8, // SKINCOLOR_POMEGRANATE + SKINCOLOR_JAWZ,6, // SKINCOLOR_LILAC + SKINCOLOR_JET,8 // SKINCOLOR_TAFFY +}; + +UINT8 colortranslations[MAXTRANSLATIONS][16] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // SKINCOLOR_NONE + { 0, 0, 0, 0, 1, 2, 5, 8, 9, 11, 14, 17, 20, 22, 25, 28}, // SKINCOLOR_WHITE + { 0, 1, 2, 3, 5, 7, 9, 12, 13, 15, 18, 20, 23, 25, 27, 30}, // SKINCOLOR_SILVER + { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31}, // SKINCOLOR_GREY + { 3, 5, 8, 11, 15, 17, 19, 21, 23, 24, 25, 26, 27, 29, 30, 31}, // SKINCOLOR_NICKEL + { 4, 7, 11, 15, 20, 22, 24, 27, 28, 28, 28, 29, 29, 30, 30, 31}, // SKINCOLOR_BLACK + { 0, 1, 208, 208, 209, 210, 10, 14, 16, 18, 20, 22, 24, 26, 28, 31}, // SKINCOLOR_FAIRY + { 0, 80, 80, 81, 82, 218, 240, 11, 13, 16, 18, 21, 23, 26, 28, 31}, // SKINCOLOR_POPCORN + { 80, 88, 89, 98, 99, 91, 12, 14, 16, 18, 20, 22, 24, 26, 28, 31}, // SKINCOLOR_ARTICHOKE + { 0, 128, 129, 130, 146, 170, 14, 15, 17, 19, 21, 23, 25, 27, 29, 31}, // SKINCOLOR_PIGEON + { 0, 1, 3, 5, 7, 9, 241, 242, 243, 245, 247, 249, 236, 237, 238, 239}, // SKINCOLOR_SEPIA + { 0, 208, 216, 217, 240, 241, 242, 243, 245, 247, 249, 250, 251, 237, 238, 239}, // SKINCOLOR_BEIGE + {208, 48, 216, 217, 218, 220, 221, 223, 224, 226, 228, 230, 232, 234, 236, 239}, // SKINCOLOR_CARAMEL + { 0, 208, 48, 216, 218, 221, 212, 213, 214, 215, 206, 207, 197, 198, 199, 254}, // SKINCOLOR_PEACH + {216, 217, 219, 221, 224, 225, 227, 229, 230, 232, 234, 235, 237, 239, 29, 30}, // SKINCOLOR_BROWN + {218, 221, 224, 227, 229, 231, 233, 235, 237, 239, 28, 28, 29, 29, 30, 31}, // SKINCOLOR_LEATHER + { 0, 0, 0, 208, 208, 209, 210, 32, 34, 35, 36, 38, 40, 42, 44, 46}, // SKINCOLOR_SALMON + { 0, 208, 208, 209, 209, 210, 211, 211, 212, 213, 214, 215, 41, 43, 45, 46}, // SKINCOLOR_PINK + {209, 210, 211, 211, 212, 213, 214, 215, 41, 42, 43, 44, 45, 71, 46, 47}, // SKINCOLOR_ROSE + {216, 221, 224, 226, 228, 60, 61, 43, 44, 45, 71, 46, 47, 29, 30, 31}, // SKINCOLOR_CINNAMON + { 0, 208, 209, 210, 211, 213, 39, 40, 41, 43, 186, 186, 169, 169, 253, 254}, // SKINCOLOR_RUBY + { 0, 208, 209, 210, 32, 33, 34, 35, 37, 39, 41, 43, 44, 45, 46, 47}, // SKINCOLOR_RASPBERRY + {209, 210, 32, 34, 36, 38, 39, 40, 41, 42, 43, 44 , 45, 71, 46, 47}, // SKINCOLOR_RED + {210, 33, 35, 38, 40, 42, 43, 45, 71, 71, 46, 46, 47, 47, 30, 31}, // SKINCOLOR_CRIMSON + { 32, 33, 35, 37, 39, 41, 43, 237, 26, 26, 27, 27, 28, 29, 30, 31}, // SKINCOLOR_MAROON + { 0, 80, 81, 82, 83, 216, 210, 211, 212, 213, 214, 215, 43, 44, 71, 47}, // SKINCOLOR_LEMONADE + { 48, 49, 50, 51, 53, 34, 36, 38, 184, 185, 168, 168, 169, 169, 254, 31}, // SKINCOLOR_SCARLET + { 72, 73, 64, 51, 52, 54, 34, 36, 38, 40, 42, 43, 44, 71, 46, 47}, // SKINCOLOR_KETCHUP + { 0, 208, 216, 209, 210, 211, 212, 57, 58, 59, 60, 61, 63, 71, 47, 31}, // SKINCOLOR_DAWN + { 82, 72, 73, 64, 51, 53, 55, 213, 214, 195, 195, 173, 174, 175, 253, 254}, // SKINCOLOR_SUNSET + { 0, 0, 208, 208, 48, 49, 50, 52, 53, 54, 56, 57, 58, 60, 61, 63}, // SKINCOLOR_CREAMSICLE + {208, 48, 49, 50, 51, 52, 53, 54, 55, 57, 59, 60, 62, 44, 71, 47}, // SKINCOLOR_ORANGE + { 50, 52, 55, 56, 58, 59, 60, 61, 62, 63, 44, 45, 71, 46, 47, 30}, // SKINCOLOR_ROSEWOOD + { 80, 81, 82, 83, 64, 51, 52, 54, 55, 57, 58, 60, 61, 63, 71, 47}, // SKINCOLOR_TANGERINE + { 0, 80, 81, 82, 83, 84, 85, 86, 87, 245, 246, 248, 249, 251, 237, 239}, // SKINCOLOR_TAN + { 0, 80, 80, 81, 81, 49, 51, 222, 224, 227, 230, 233, 236, 239, 29, 31}, // SKINCOLOR_CREAM + { 0, 80, 81, 83, 64, 65, 66, 67, 68, 215, 69, 70, 44, 71, 46, 47}, // SKINCOLOR_GOLD + { 80, 81, 83, 64, 65, 223, 229, 196, 196, 197, 197, 198, 199, 29, 30, 31}, // SKINCOLOR_ROYAL + { 83, 64, 65, 66, 67, 215, 69, 70, 44, 44, 45, 71, 46, 47, 29, 31}, // SKINCOLOR_BRONZE + { 0, 82, 64, 65, 67, 68, 70, 237, 239, 28, 28, 29, 29, 30, 30, 31}, // SKINCOLOR_COPPER + { 0, 80, 81, 82, 83, 73, 84, 74, 64, 65, 66, 67, 68, 69, 70, 71}, // SKINCOLOR_YELLOW + { 80, 81, 82, 83, 64, 65, 65, 76, 76, 77, 77, 78, 79, 237, 239, 29}, // SKINCOLOR_MUSTARD + { 80, 81, 83, 72, 73, 74, 75, 76, 77, 78, 79, 236, 237, 238, 239, 30}, // SKINCOLOR_BANANA + { 80, 82, 73, 74, 75, 76, 77, 78, 79, 236, 237, 238, 239, 28, 29, 31}, // SKINCOLOR_OLIVE + { 0, 80, 81, 88, 88, 188, 189, 76, 76, 77, 78, 79, 236, 237, 238, 239}, // SKINCOLOR_CROCODILE + { 0, 80, 81, 88, 188, 189, 190, 191, 94, 94, 95, 95, 109, 110, 111, 31}, // SKINCOLOR_PERIDOT + { 0, 208, 216, 209, 218, 51, 65, 76, 191, 191, 126, 143, 138, 175, 169, 254}, // SKINCOLOR_VOMIT + { 81, 82, 83, 73, 64, 65, 66, 92, 92, 93, 93, 94, 95, 109, 110, 111}, // SKINCOLOR_GARDEN + { 0, 80, 81, 82, 83, 88, 89, 99, 100, 102, 104, 126, 143, 138, 139, 31}, // SKINCOLOR_LIME + { 83, 72, 73, 74, 75, 76, 102, 104, 105, 106, 107, 108, 109, 110, 111, 31}, // SKINCOLOR_HANDHELD + { 0, 80, 80, 81, 88, 89, 90, 91, 92, 93, 94, 95, 109, 110, 111, 31}, // SKINCOLOR_TEA + { 0, 80, 88, 88, 89, 90, 91, 102, 103, 104, 105, 106, 107, 108, 109, 110}, // SKINCOLOR_PISTACHIO + { 88, 89, 90, 91, 91, 92, 93, 94, 107, 107, 108, 108, 109, 109, 110, 111}, // SKINCOLOR_MOSS + {208, 84, 85, 240, 241, 243, 245, 94, 107, 108, 108, 109, 109, 110, 110, 111}, // SKINCOLOR_CAMOUFLAGE + { 0, 88, 98, 101, 103, 104, 105, 94, 94, 107, 95, 109, 110, 111, 30, 31}, // SKINCOLOR_ROBOHOOD + { 0, 88, 88, 89, 89, 100, 101, 102, 125, 126, 143, 143, 138, 175, 169, 254}, // SKINCOLOR_MINT + { 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111}, // SKINCOLOR_GREEN + { 97, 99, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 30, 30, 31}, // SKINCOLOR_PINETREE + { 96, 112, 112, 113, 113, 114, 114, 115, 115, 116, 116, 117, 117, 118, 119, 111}, // SKINCOLOR_TURTLE + { 96, 112, 113, 114, 115, 116, 117, 118, 119, 119, 29, 29, 30, 30, 31, 31}, // SKINCOLOR_SWAMP + { 0, 0, 208, 208, 48, 89, 98, 100, 148, 148, 172, 172, 173, 173, 174, 175}, // SKINCOLOR_DREAM + { 80, 88, 96, 112, 113, 124, 142, 149, 149, 173, 174, 175, 169, 253, 254, 31}, // SKINCOLOR_PLAGUE + { 0, 120, 121, 112, 113, 114, 115, 125, 125, 126, 126, 127, 138, 175, 253, 254}, // SKINCOLOR_EMERALD + {128, 128, 129, 129, 130, 140, 124, 103, 104, 116, 116, 117, 118, 119, 111, 31}, // SKINCOLOR_ALGAE + { 0, 88, 89, 97, 113, 141, 135, 136, 136, 173, 173, 174, 174, 175, 199, 31}, // SKINCOLOR_CARIBBEAN + { 0, 80, 81, 82, 89, 140, 134, 135, 136, 172, 196, 197, 198, 199, 30, 31}, // SKINCOLOR_AZURE + { 0, 128, 120, 121, 122, 123, 124, 125, 126, 126, 127, 127, 118, 118, 119, 111}, // SKINCOLOR_AQUAMARINE + {128, 120, 121, 122, 123, 141, 141, 142, 142, 143, 143, 138, 138, 139, 139, 31}, // SKINCOLOR_TURQUOISE + { 0, 120, 120, 121, 140, 141, 142, 143, 143, 138, 138, 139, 139, 254, 254, 31}, // SKINCOLOR_TEAL + { 0, 0, 128, 128, 255, 131, 132, 134, 142, 142, 143, 127, 118, 119, 110, 111}, // SKINCOLOR_CYAN + { 0, 0, 128, 128, 129, 146, 133, 134, 135, 149, 149, 173, 173, 174, 175, 31}, // SKINCOLOR_JAWZ + { 0, 128, 129, 130, 131, 132, 133, 135, 136, 136, 137, 137, 138, 138, 139, 31}, // SKINCOLOR_CERULEAN + {128, 129, 130, 132, 134, 135, 136, 137, 137, 138, 138, 139, 139, 29, 30, 31}, // SKINCOLOR_NAVY + { 0, 0, 0, 144, 144, 145, 9, 11, 14, 142, 136, 137, 138, 138, 139, 31}, // SKINCOLOR_PLATINUM + { 0, 0, 144, 144, 144, 145, 145, 145, 170, 170, 171, 171, 172, 173, 174, 175}, // SKINCOLOR_SLATE + { 0, 144, 144, 145, 145, 170, 170, 171, 171, 172, 172, 173, 173, 174, 175, 31}, // SKINCOLOR_STEEL + { 80, 81, 82, 83, 64, 65, 11, 171, 172, 173, 173, 157, 158, 159, 254, 31}, // SKINCOLOR_THUNDER + { 0, 83, 49, 50, 51, 32, 192, 148, 148, 172, 173, 174, 175, 29, 30, 31}, // SKINCOLOR_NOVA + {208, 48, 216, 217, 240, 241, 242, 171, 172, 173, 24, 25, 26, 28, 29, 31}, // SKINCOLOR_RUST + { 48, 218, 221, 224, 227, 231, 196, 173, 173, 174, 159, 159, 253, 253, 254, 31}, // SKINCOLOR_WRISTWATCH + {145, 146, 147, 148, 149, 173, 173, 174, 175, 175, 28, 28, 29, 29, 30, 31}, // SKINCOLOR_JET + { 0, 128, 129, 131, 133, 135, 149, 150, 152, 154, 156, 158, 159, 253, 254, 31}, // SKINCOLOR_SAPPHIRE + { 0, 0, 120, 120, 121, 133, 135, 149, 149, 166, 166, 167, 168, 169, 254, 31}, // SKINCOLOR_ULTRAMARINE + { 0, 0, 144, 144, 145, 146, 147, 149, 150, 152, 154, 155, 157, 159, 253, 254}, // SKINCOLOR_PERIWINKLE + {144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 155, 156, 158, 253, 254, 31}, // SKINCOLOR_BLUE + {146, 148, 149, 150, 152, 153, 155, 157, 159, 253, 253, 254, 254, 31, 31, 31}, // SKINCOLOR_BLUEBERRY + { 0, 0, 0, 252, 252, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 254}, // SKINCOLOR_THISTLE + { 0, 252, 160, 161, 162, 163, 164, 165, 166, 167, 168, 168, 169, 169, 253, 254}, // SKINCOLOR_PURPLE + { 0, 128, 128, 129, 129, 146, 170, 162, 163, 164, 165, 166, 167, 168, 169, 254}, // SKINCOLOR_PASTEL + { 0, 144, 145, 146, 170, 162, 163, 184, 184, 207, 207, 44, 45, 46, 47, 31}, // SKINCOLOR_MOONSLAM + {252, 200, 201, 192, 193, 194, 172, 172, 173, 173, 174, 174, 175, 169, 253, 254}, // SKINCOLOR_DUSK + { 0, 252, 252, 200, 201, 181, 182, 183, 184, 166, 167, 168, 169, 253, 254, 31}, // SKINCOLOR_BUBBLEGUM + {176, 177, 178, 179, 180, 181, 182, 183, 184, 184, 185, 185, 186, 187, 30, 31}, // SKINCOLOR_MAGENTA + {208, 209, 209, 32, 33, 182, 183, 184, 185, 185, 186, 186, 187, 253, 254, 31}, // SKINCOLOR_FUCHSIA + { 0, 0, 88, 88, 89, 6, 8, 10, 193, 194, 195, 184, 185, 186, 187, 31}, // SKINCOLOR_TOXIC + { 80, 81, 82, 83, 64, 50, 201, 192, 193, 194, 195, 173, 174, 175, 253, 254}, // SKINCOLOR_MAUVE + {252, 177, 179, 192, 193, 194, 195, 196, 196, 197, 197, 198, 198, 199, 30, 31}, // SKINCOLOR_LAVENDER + {145, 192, 193, 194, 195, 196, 197, 198, 199, 199, 29, 29, 30, 30, 31, 31}, // SKINCOLOR_BYZANTIUM + {208, 209, 210, 211, 212, 213, 214, 195, 195, 196, 196, 197, 198, 199, 29, 30}, // SKINCOLOR_POMEGRANATE + { 0, 0, 0, 252, 252, 176, 200, 201, 179, 192, 193, 194, 195, 196, 197, 198}, // SKINCOLOR_LILAC + { 0, 252, 252, 200, 200, 201, 202, 203, 204, 204, 205, 206, 207, 43, 45, 47}, // SKINCOLOR_TAFFY + + // THESE STILL NEED CONVERTED!!! + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 100, 104, 113, 116, 119}, // SKINCOLOR_SUPER1 + { 0, 0, 0, 0, 0, 0, 0, 0, 96, 98, 101, 104, 113, 115, 117, 119}, // SKINCOLOR_SUPER2 + { 0, 0, 0, 0, 0, 0, 96, 98, 100, 102, 104, 113, 114, 116, 117, 119}, // SKINCOLOR_SUPER3 + { 0, 0, 0, 0, 96, 97, 99, 100, 102, 104, 113, 114, 115, 116, 117, 119}, // SKINCOLOR_SUPER4 + { 0, 0, 96, 0, 0, 0, 0, 0, 104, 113, 114, 115, 116, 117, 118, 119}, // SKINCOLOR_SUPER5 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 80, 82, 85, 115, 117, 119}, // SKINCOLOR_TSUPER1 + { 0, 0, 0, 0, 0, 0, 0, 0, 80, 81, 83, 85, 115, 116, 117, 119}, // SKINCOLOR_TSUPER2 + { 0, 0, 0, 0, 0, 0, 80, 81, 82, 83, 85, 115, 116, 117, 118, 119}, // SKINCOLOR_TSUPER3 + { 0, 0, 0, 0, 80, 81, 82, 83, 84, 85, 115, 115, 116, 117, 118, 119}, // SKINCOLOR_TSUPER4 + { 0, 0, 80, 80, 81, 82, 83, 84, 85, 115, 115, 116, 117, 117, 118, 119}, // SKINCOLOR_TSUPER5 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 121, 123, 125, 127, 129, 132}, // SKINCOLOR_KSUPER1 + { 0, 0, 0, 0, 0, 0, 0, 0, 121, 122, 124, 125, 127, 128, 130, 132}, // SKINCOLOR_KSUPER2 + { 0, 0, 0, 0, 0, 0, 121, 122, 123, 124, 125, 127, 128, 129, 130, 132}, // SKINCOLOR_KSUPER3 + { 0, 0, 0, 0, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132}, // SKINCOLOR_KSUPER4 + { 0, 0, 121, 121, 122, 123, 124, 125, 126, 126, 127, 128, 129, 130, 131, 132}, // SKINCOLOR_KSUPER5 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 122, 124, 248, 251, 255}, // SKINCOLOR_PSUPER1 + { 0, 0, 0, 0, 0, 0, 0, 0, 1, 121, 122, 124, 248, 250, 252, 255}, // SKINCOLOR_PSUPER2 + { 0, 0, 0, 0, 0, 0, 1, 121, 122, 123, 124, 248, 249, 251, 253, 255}, // SKINCOLOR_PSUPER3 + { 0, 0, 0, 0, 1, 121, 122, 123, 124, 248, 249, 250, 251, 252, 253, 255}, // SKINCOLOR_PSUPER4 + { 0, 0, 1, 121, 122, 123, 124, 248, 248, 249, 250, 251, 252, 253, 254, 255}, // SKINCOLOR_PSUPER5 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 225, 227, 228, 230, 232}, // SKINCOLOR_BSUPER1 + { 0, 0, 0, 0, 0, 0, 0, 0, 224, 225, 226, 227, 228, 229, 230, 232}, // SKINCOLOR_BSUPER2 + { 0, 0, 0, 0, 0, 0, 224, 224, 225, 226, 227, 228, 229, 230, 231, 232}, // SKINCOLOR_BSUPER3 + { 0, 0, 0, 0, 224, 224, 225, 226, 226, 227, 228, 229, 229, 230, 231, 232}, // SKINCOLOR_BSUPER4 + { 0, 0, 224, 224, 225, 225, 226, 227, 227, 228, 228, 229, 230, 230, 231, 232}, // SKINCOLOR_BSUPER5 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 208, 210, 212, 215, 220, 222}, // SKINCOLOR_ASUPER1 + { 0, 0, 0, 0, 0, 0, 0, 0, 208, 209, 211, 213, 215, 220, 221, 223}, // SKINCOLOR_ASUPER2 + { 0, 0, 0, 0, 0, 0, 208, 209, 210, 211, 212, 213, 215, 220, 221, 223}, // SKINCOLOR_ASUPER3 + { 0, 0, 0, 0, 208, 209, 210, 211, 212, 213, 214, 215, 220, 221, 222, 223}, // SKINCOLOR_ASUPER4 + { 0, 0, 208, 208, 209, 210, 211, 211, 212, 213, 214, 215, 220, 221, 222, 223}, // SKINCOLOR_ASUPER5 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 176, 160, 163, 167, 171, 175}, // SKINCOLOR_GSUPER1 + { 0, 0, 0, 0, 0, 0, 0, 0, 176, 176, 160, 163, 166, 169, 172, 175}, // SKINCOLOR_GSUPER2 + { 0, 0, 0, 0, 0, 0, 176, 176, 160, 162, 164, 166, 168, 170, 172, 175}, // SKINCOLOR_GSUPER3 + { 0, 0, 0, 0, 176, 176, 176, 160, 161, 163, 165, 167, 169, 171, 173, 175}, // SKINCOLOR_GSUPER4 + { 0, 0, 176, 176, 176, 160, 161, 163, 164, 166, 167, 169, 170, 172, 173, 175}, // SKINCOLOR_GSUPER5 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // SKINCOLOR_WSUPER1 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 9}, // SKINCOLOR_WSUPER2 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 6, 8, 11}, // SKINCOLOR_WSUPER3 + { 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 4, 6, 8, 9, 11, 13}, // SKINCOLOR_WSUPER4 + { 0, 0, 0, 0, 1, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 15}, // SKINCOLOR_WSUPER5 + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 98, 99, 81, 73, 79}, // SKINCOLOR_CSUPER1 + { 0, 0, 0, 0, 0, 0, 0, 0, 96, 97, 98, 81, 81, 71, 75, 79}, // SKINCOLOR_CSUPER2 + { 0, 0, 0, 0, 0, 0, 96, 97, 98, 99, 81, 81, 70, 73, 76, 79}, // SKINCOLOR_CSUPER3 + { 0, 0, 0, 0, 96, 96, 97, 98, 99, 81, 81, 70, 72, 74, 76, 79}, // SKINCOLOR_CSUPER4 + { 0, 0, 96, 96, 97, 98, 98, 99, 81, 81, 69, 71, 73, 75, 77, 79}, // SKINCOLOR_CSUPER5 +}; + +// Define for getting accurate color brightness readings according to how the human eye sees them. +// https://en.wikipedia.org/wiki/Relative_luminance +// 0.2126 to red +// 0.7152 to green +// 0.0722 to blue +// (See this same define in hw_md2.c!) +#define SETBRIGHTNESS(brightness,r,g,b) \ + brightness = (UINT8)(((1063*(UINT16)(r))/5000) + ((3576*(UINT16)(g))/5000) + ((361*(UINT16)(b))/5000)) + +/** \brief Generates the rainbow colourmaps that are used when a player has the invincibility power + + \param dest_colormap colormap to populate + \param skincolor translation color +*/ +void K_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor) +{ + INT32 i; + RGBA_t color; + UINT8 brightness; + INT32 j; + UINT8 colorbrightnesses[16]; + UINT16 brightdif; + INT32 temp; + + // first generate the brightness of all the colours of that skincolour + for (i = 0; i < 16; i++) + { + color = V_GetColor(colortranslations[skincolor][i]); + SETBRIGHTNESS(colorbrightnesses[i], color.s.red, color.s.green, color.s.blue); + } + + // next, for every colour in the palette, choose the transcolor that has the closest brightness + for (i = 0; i < NUM_PALETTE_ENTRIES; i++) + { + if (i == 0 || i == 31) // pure black and pure white don't change + { + dest_colormap[i] = (UINT8)i; + continue; + } + color = V_GetColor(i); + SETBRIGHTNESS(brightness, color.s.red, color.s.green, color.s.blue); + brightdif = 256; + for (j = 0; j < 16; j++) + { + temp = abs((INT16)brightness - (INT16)colorbrightnesses[j]); + if (temp < brightdif) + { + brightdif = (UINT16)temp; + dest_colormap[i] = colortranslations[skincolor][j]; + } + } + } +} + +#undef SETBRIGHTNESS + +/** \brief Generates a translation colormap for Kart, to replace R_GenerateTranslationColormap in r_draw.c + + \param dest_colormap colormap to populate + \param skinnum number of skin, TC_DEFAULT or TC_BOSS + \param color translation color + + \return void +*/ +void K_GenerateKartColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color) +{ + INT32 i; + INT32 starttranscolor; + + // Handle a couple of simple special cases + if (skinnum == TC_BOSS + || skinnum == TC_ALLWHITE + || skinnum == TC_METALSONIC + || skinnum == TC_BLINK + || color == SKINCOLOR_NONE) + { + for (i = 0; i < NUM_PALETTE_ENTRIES; i++) + { + if (skinnum == TC_ALLWHITE) + dest_colormap[i] = 0; + else if (skinnum == TC_BLINK) + dest_colormap[i] = colortranslations[color][3]; + else + dest_colormap[i] = (UINT8)i; + } + + // White! + if (skinnum == TC_BOSS) + dest_colormap[31] = 0; + else if (skinnum == TC_METALSONIC) + dest_colormap[143] = 0; + + return; + } + else if (skinnum == TC_RAINBOW) + { + K_RainbowColormap(dest_colormap, color); + return; + } + + starttranscolor = (skinnum != TC_DEFAULT) ? skins[skinnum].starttranscolor : DEFAULT_STARTTRANSCOLOR; + + // Fill in the entries of the palette that are fixed + for (i = 0; i < starttranscolor; i++) + dest_colormap[i] = (UINT8)i; + + for (i = (UINT8)(starttranscolor + 16); i < NUM_PALETTE_ENTRIES; i++) + dest_colormap[i] = (UINT8)i; + + // Build the translated ramp + for (i = 0; i < SKIN_RAMP_LENGTH; i++) + { + // Sryder 2017-10-26: What was here before was most definitely not particularly readable, check above for new color translation table + dest_colormap[starttranscolor + i] = colortranslations[color][i]; + } +} + +/** \brief Pulls kart color by name, to replace R_GetColorByName in r_draw.c + + \param name color name + + \return 0 +*/ +UINT8 K_GetKartColorByName(const char *name) +{ + UINT8 color = (UINT8)atoi(name); + if (color > 0 && color < MAXSKINCOLORS) + return color; + for (color = 1; color < MAXSKINCOLORS; color++) + if (!stricmp(KartColor_Names[color], name)) + return color; + return 0; +} + +//} + +//{ SRB2kart Net Variables + +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_superring); + CV_RegisterVar(&cv_kitchensink); + + 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); + CV_RegisterVar(&cv_kartspeed); + CV_RegisterVar(&cv_kartbumpers); + CV_RegisterVar(&cv_kartfrantic); + CV_RegisterVar(&cv_kartcomeback); + CV_RegisterVar(&cv_kartencore); + CV_RegisterVar(&cv_kartvoterulechanges); + CV_RegisterVar(&cv_kartspeedometer); + CV_RegisterVar(&cv_kartvoices); + CV_RegisterVar(&cv_karteliminatelast); + CV_RegisterVar(&cv_kartusepwrlv); + CV_RegisterVar(&cv_votetime); + + CV_RegisterVar(&cv_kartdebugitem); + CV_RegisterVar(&cv_kartdebugamount); + CV_RegisterVar(&cv_kartdebugshrink); + CV_RegisterVar(&cv_kartdebugdistribution); + CV_RegisterVar(&cv_kartdebughuddrop); + CV_RegisterVar(&cv_kartdebugwaypoints); + + CV_RegisterVar(&cv_kartdebugcheckpoint); + CV_RegisterVar(&cv_kartdebugnodes); + CV_RegisterVar(&cv_kartdebugcolorize); +} + +//} + +boolean K_IsPlayerLosing(player_t *player) +{ + INT32 winningpos = 1; + UINT8 i, pcount = 0; + + if (player->kartstuff[k_position] == 1) + return false; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + if (players[i].kartstuff[k_position] > pcount) + pcount = players[i].kartstuff[k_position]; + } + + if (pcount <= 1) + return false; + + winningpos = pcount/2; + if (pcount % 2) // any remainder? + winningpos++; + + return (player->kartstuff[k_position] > winningpos); +} + +boolean K_IsPlayerWanted(player_t *player) +{ + UINT8 i; + if (!(G_BattleGametype())) + return false; + for (i = 0; i < 4; i++) + { + if (battlewanted[i] == -1) + break; + if (player == &players[battlewanted[i]]) + return true; + } + return false; +} + +fixed_t K_GetKartGameSpeedScalar(SINT8 value) +{ + // Easy = 81.25% + // Normal = 100% + // Hard = 118.75% + // Nightmare = 137.5% ?!?! + return ((13 + (3*value)) << FRACBITS) / 16; +} + +//{ SRB2kart Roulette Code - Position Based + +#define NUMKARTODDS 80 + +// Less ugly 2D arrays +static INT32 K_KartItemOddsRace[NUMKARTRESULTS][8] = +{ + //P-Odds 0 1 2 3 4 5 6 7 + /*Sneaker*/ { 0, 0, 4, 6, 7, 0, 0, 0 }, // Sneaker + /*Rocket Sneaker*/ { 0, 0, 0, 0, 1, 4, 5, 3 }, // Rocket Sneaker + /*Invincibility*/ { 0, 0, 0, 0, 1, 4, 6,10 }, // Invincibility + /*Banana*/ { 9, 4, 2, 1, 0, 0, 0, 0 }, // Banana + /*Eggman Monitor*/ { 3, 2, 1, 0, 0, 0, 0, 0 }, // Eggman Monitor + /*Orbinaut*/ { 7, 6, 4, 2, 0, 0, 0, 0 }, // Orbinaut + /*Jawz*/ { 0, 3, 2, 1, 1, 0, 0, 0 }, // Jawz + /*Mine*/ { 0, 2, 2, 1, 0, 0, 0, 0 }, // Mine + /*Ballhog*/ { 0, 0, 2, 1, 0, 0, 0, 0 }, // Ballhog + /*Self-Propelled Bomb*/ { 0, 1, 2, 3, 4, 2, 2, 0 }, // Self-Propelled Bomb + /*Grow*/ { 0, 0, 0, 0, 0, 2, 5, 7 }, // Grow + /*Shrink*/ { 0, 0, 0, 0, 0, 0, 2, 0 }, // Shrink + /*Thunder Shield*/ { 1, 2, 0, 0, 0, 0, 0, 0 }, // Thunder Shield + /*Hyudoro*/ { 0, 0, 0, 1, 2, 1, 0, 0 }, // Hyudoro + /*Pogo Spring*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring + /*Super Ring*/ { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring + /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink + /*Sneaker x3*/ { 0, 0, 0, 3, 7, 9, 2, 0 }, // Sneaker x3 + /*Banana x3*/ { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 + /*Banana x10*/ { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10 + /*Orbinaut x3*/ { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 + /*Orbinaut x4*/ { 0, 0, 0, 1, 1, 0, 0, 0 }, // Orbinaut x4 + /*Jawz x2*/ { 0, 0, 1, 2, 0, 0, 0, 0 } // Jawz x2 +}; + +static INT32 K_KartItemOddsBattle[NUMKARTRESULTS][6] = +{ + //P-Odds 0 1 2 3 4 5 + /*Sneaker*/ { 3, 2, 2, 2, 0, 2 }, // Sneaker + /*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 0 }, // Rocket Sneaker + /*Invincibility*/ { 0, 1, 2, 3, 4, 2 }, // Invincibility + /*Banana*/ { 2, 1, 0, 0, 0, 0 }, // Banana + /*Eggman Monitor*/ { 1, 1, 0, 0, 0, 0 }, // Eggman Monitor + /*Orbinaut*/ { 6, 2, 1, 0, 0, 0 }, // Orbinaut + /*Jawz*/ { 3, 3, 3, 2, 0, 2 }, // Jawz + /*Mine*/ { 2, 3, 3, 1, 0, 2 }, // Mine + /*Ballhog*/ { 0, 1, 2, 1, 0, 2 }, // Ballhog + /*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 0 }, // Self-Propelled Bomb + /*Grow*/ { 0, 0, 1, 2, 4, 2 }, // Grow + /*Shrink*/ { 0, 0, 0, 0, 0, 0 }, // Shrink + /*Thunder Shield*/ { 0, 0, 0, 0, 0, 0 }, // Thunder Shield + /*Hyudoro*/ { 1, 1, 0, 0, 0, 0 }, // Hyudoro + /*Pogo Spring*/ { 1, 1, 0, 0, 0, 0 }, // Pogo Spring + /*Super Ring*/ { 0, 0, 0, 0, 0, 0 }, // Super Ring + /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0 }, // Kitchen Sink + /*Sneaker x3*/ { 0, 0, 0, 2, 4, 2 }, // Sneaker x3 + /*Banana x3*/ { 1, 2, 1, 0, 0, 0 }, // Banana x3 + /*Banana x10*/ { 0, 0, 1, 1, 0, 2 }, // Banana x10 + /*Orbinaut x3*/ { 0, 1, 2, 1, 0, 0 }, // Orbinaut x3 + /*Orbinaut x4*/ { 0, 0, 1, 3, 4, 2 }, // Orbinaut x4 + /*Jawz x2*/ { 0, 0, 1, 2, 4, 2 } // Jawz x2 +}; + +#define DISTVAR (2048) // Magic number distance for use with item roulette tiers + +/** \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 (getitem == KITEM_HYUDORO) // Hyudoro cooldown + hyubgone = 5*TICRATE; + + switch (getitem) + { + // Special roulettes first, then the generic ones are handled by default + case KRITEM_TRIPLESNEAKER: // Sneaker x3 + player->kartstuff[k_itemtype] = KITEM_SNEAKER; + player->kartstuff[k_itemamount] = 3; + break; + case KRITEM_TRIPLEBANANA: // Banana x3 + player->kartstuff[k_itemtype] = KITEM_BANANA; + player->kartstuff[k_itemamount] = 3; + break; + case KRITEM_TENFOLDBANANA: // Banana x10 + player->kartstuff[k_itemtype] = KITEM_BANANA; + player->kartstuff[k_itemamount] = 10; + break; + case KRITEM_TRIPLEORBINAUT: // Orbinaut x3 + player->kartstuff[k_itemtype] = KITEM_ORBINAUT; + player->kartstuff[k_itemamount] = 3; + break; + case KRITEM_QUADORBINAUT: // Orbinaut x4 + player->kartstuff[k_itemtype] = KITEM_ORBINAUT; + player->kartstuff[k_itemamount] = 4; + break; + case KRITEM_DUALJAWZ: // Jawz x2 + player->kartstuff[k_itemtype] = KITEM_JAWZ; + player->kartstuff[k_itemamount] = 2; + break; + default: + if (getitem <= 0 || getitem >= NUMKARTRESULTS) // Sad (Fallback) + { + if (getitem != 0) + CONS_Printf("ERROR: P_KartGetItemResult - Item roulette gave bad item (%d) :(\n", getitem); + player->kartstuff[k_itemtype] = KITEM_SAD; + } + else + player->kartstuff[k_itemtype] = getitem; + player->kartstuff[k_itemamount] = 1; + break; + } +} + +/** \brief Item Roulette for Kart + + \param player player object passed from P_KartPlayerThink + + \return void +*/ + +static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush) +{ + INT32 newodds; + INT32 i; + UINT8 pingame = 0, pexiting = 0; + boolean thunderisout = false; + SINT8 first = -1, second = -1; + INT32 secondist = 0; + boolean itemenabled[NUMKARTRESULTS-1] = { + cv_sneaker.value, + cv_rocketsneaker.value, + cv_invincibility.value, + cv_banana.value, + cv_eggmanmonitor.value, + cv_orbinaut.value, + cv_jawz.value, + cv_mine.value, + cv_ballhog.value, + cv_selfpropelledbomb.value, + cv_grow.value, + cv_shrink.value, + cv_thundershield.value, + cv_hyudoro.value, + cv_pogospring.value, + cv_superring.value, + cv_kitchensink.value, + cv_triplesneaker.value, + cv_triplebanana.value, + cv_decabanana.value, + cv_tripleorbinaut.value, + cv_quadorbinaut.value, + cv_dualjawz.value + }; + + I_Assert(item > KITEM_NONE); // too many off by one scenarioes. + + if (!itemenabled[item-1] && !modeattacking) + return 0; + + if (G_BattleGametype()) + newodds = K_KartItemOddsBattle[item-1][pos]; + else + newodds = K_KartItemOddsRace[item-1][pos]; + + // Base multiplication to ALL item odds to simulate fractional precision + newodds *= 4; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (!G_BattleGametype() || players[i].kartstuff[k_bumper]) + pingame++; + + if (players[i].exiting) + pexiting++; + + if (players[i].mo) + { + if (players[i].kartstuff[k_itemtype] == KITEM_THUNDERSHIELD) + thunderisout = true; + + if (!G_BattleGametype()) + { + if (players[i].kartstuff[k_position] == 1 && first == -1) + first = i; + if (players[i].kartstuff[k_position] == 2 && second == -1) + second = i; + } + } + } + + if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB + { + secondist = players[second].distancetofinish - players[first].distancetofinish; + if (franticitems) + secondist = (15 * secondist) / 14; + secondist = ((28 + (8-pingame)) * secondist) / 28; + } + + // POWERITEMODDS handles all of the "frantic item" related functionality, for all of our powerful items. + // First, it multiplies it by 2 if franticitems is true; easy-peasy. + // Next, it multiplies it again if it's in SPB mode and 2nd needs to apply pressure to 1st. + // Then, it multiplies it further if the player count isn't equal to 8. + // This is done to make low player count races more interesting and high player count rates more fair. + // (2P normal would be about halfway between 8P normal and 8P frantic.) + // (This scaling is not done for SPB Rush, so that catchup strength is not weakened.) + // Lastly, it *divides* it by your mashed value, which was determined in K_KartItemRoulette, for lesser items needed in a pinch. + +#define PLAYERSCALING (8 - (spbrush ? 2 : pingame)) + +#define POWERITEMODDS(odds) {\ + if (franticitems) \ + odds <<= 1; \ + odds = FixedMul(odds<> FRACBITS; \ + if (mashed > 0) \ + odds = FixedDiv(odds<> FRACBITS; \ +} + +#define COOLDOWNONSTART (leveltime < (30*TICRATE)+starttime) + + switch (item) + { + case KITEM_ROCKETSNEAKER: + case KITEM_JAWZ: + case KITEM_BALLHOG: + case KRITEM_TRIPLESNEAKER: + case KRITEM_TRIPLEBANANA: + case KRITEM_TENFOLDBANANA: + case KRITEM_TRIPLEORBINAUT: + case KRITEM_QUADORBINAUT: + case KRITEM_DUALJAWZ: + POWERITEMODDS(newodds); + break; + case KITEM_INVINCIBILITY: + case KITEM_MINE: + case KITEM_GROW: + if (COOLDOWNONSTART) + newodds = 0; + else + POWERITEMODDS(newodds); + break; + case KITEM_SPB: + if ((indirectitemcooldown > 0) || (secondist/DISTVAR < 3) + || (first != -1 && players[first].distancetofinish > 8*DISTVAR)) // No SPB near the end of the race + newodds = 0; + else + newodds *= min((secondist/DISTVAR)-4, 3); // POWERITEMODDS(newodds); + break; + case KITEM_SHRINK: + if ((indirectitemcooldown > 0) || (pingame-1 <= pexiting) || COOLDOWNONSTART) + newodds = 0; + else + POWERITEMODDS(newodds); + break; + case KITEM_THUNDERSHIELD: + if (thunderisout || spbplace != -1) + newodds = 0; + else + POWERITEMODDS(newodds); + case KITEM_HYUDORO: + if ((hyubgone > 0) || COOLDOWNONSTART) + newodds = 0; + break; + default: + break; + } + +#undef POWERITEMODDS + + return newodds; +} + +//{ SRB2kart Roulette Code - Distance Based, yes waypoints + +static UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) +{ + UINT8 i; + UINT8 n = 0; + UINT8 useodds = 0; + UINT8 disttable[14]; + UINT8 totallen = 0; + UINT8 distlen = 0; + boolean oddsvalid[8]; + + for (i = 0; i < 8; i++) + { + UINT8 j; + boolean available = false; + + if (G_BattleGametype() && i > 5) + { + oddsvalid[i] = false; + break; + } + + for (j = 1; j < NUMKARTRESULTS; j++) + { + if (K_KartGetItemOdds(i, j, mashed, spbrush) > 0) + { + available = true; + break; + } + } + + oddsvalid[i] = available; + } + +#define SETUPDISTTABLE(odds, num) \ + if (oddsvalid[odds]) \ + for (i = num; i; --i) \ + disttable[distlen++] = odds; \ + totallen += num; + + if (G_BattleGametype()) // Battle Mode + { + SETUPDISTTABLE(0,1); + SETUPDISTTABLE(1,1); + SETUPDISTTABLE(2,1); + SETUPDISTTABLE(3,1); + SETUPDISTTABLE(4,1); + + if (player->kartstuff[k_roulettetype] == 1 && oddsvalid[5]) // 5 is the extreme odds of player-controlled "Karma" items + useodds = 5; + else + { + SINT8 wantedpos = (bestbumper-player->kartstuff[k_bumper]); // 0 is the best player's bumper count, 1 is a bumper below best, 2 is two bumpers below, etc + if (K_IsPlayerWanted(player)) + wantedpos++; + if (wantedpos > 4) // Don't run off into karma items + wantedpos = 4; + if (wantedpos < 0) // Don't go below somehow + wantedpos = 0; + n = (wantedpos * distlen) / totallen; + useodds = disttable[n]; + } + } + else + { + SETUPDISTTABLE(0,1); + SETUPDISTTABLE(1,1); + SETUPDISTTABLE(2,1); + SETUPDISTTABLE(3,2); + SETUPDISTTABLE(4,2); + SETUPDISTTABLE(5,3); + SETUPDISTTABLE(6,3); + SETUPDISTTABLE(7,1); + + if (pdis == 0) + useodds = disttable[0]; + else if (pdis > DISTVAR * ((12 * distlen) / 14)) + useodds = disttable[distlen-1]; + else + { + for (i = 1; i < 13; i++) + { + if (pdis <= DISTVAR * ((i * distlen) / 14)) + { + useodds = disttable[((i * distlen) / 14)]; + break; + } + } + } + } + +#undef SETUPDISTTABLE + + return useodds; +} + +static 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]; + INT32 totalspawnchance = 0; + UINT8 bestbumper = 0; + fixed_t mashed = 0; + boolean dontforcespb = false; + boolean spbrush = false; + + // This makes the roulette cycle through items - if this is 0, you shouldn't be here. + if (player->kartstuff[k_itemroulette]) + player->kartstuff[k_itemroulette]++; + else + return; + + // 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].kartstuff[k_bumper] > bestbumper) + bestbumper = players[i].kartstuff[k_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->kartstuff[k_itemroulette] % 3) == 1 && P_IsDisplayPlayer(player)) + { +#define PLAYROULETTESND S_StartSound(NULL, sfx_itrol1 + ((player->kartstuff[k_itemroulette] / 3) % 8)) + for (i = 0; i <= splitscreen; i++) + { + if (player == &players[displayplayers[i]] && players[displayplayers[i]].kartstuff[k_itemroulette]) + PLAYROULETTESND; + } +#undef PLAYROULETTESND + } + + roulettestop = TICRATE + (3*(pingame - player->kartstuff[k_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->kartstuff[k_itemroulette] >= roulettestop) + && !(player->kartstuff[k_eggmanheld] || player->kartstuff[k_itemheld] || player->kartstuff[k_userings])) + { + // Mashing reduces your chances for the good items + mashed = FixedDiv((player->kartstuff[k_itemroulette])*FRACUNIT, ((TICRATE*3)+roulettestop)*FRACUNIT) - FRACUNIT; + } + else if (!(player->kartstuff[k_itemroulette] >= (TICRATE*3))) + return; + + if (cmd->buttons & BT_ATTACK) + player->pflags |= PF_ATTACKDOWN; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator + && players[i].kartstuff[k_position] == 1) + { + // This player is first! Yay! + pdis = player->distancetofinish - players[i].distancetofinish; + break; + } + } + + if (mapobjectscale != FRACUNIT) + pdis = FixedDiv(pdis, mapobjectscale); + + if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items + pdis = (15 * pdis) / 14; + + if (spbplace != -1 && player->kartstuff[k_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 = ((28 + (8-pingame)) * pdis) / 28; // scale with player count + + // SPECIAL CASE No. 1: + // Fake Eggman items + if (player->kartstuff[k_roulettetype] == 2) + { + player->kartstuff[k_eggmanexplode] = 4*TICRATE; + //player->karthud[khud_itemblink] = TICRATE; + //player->karthud[khud_itemblinkmode] = 1; + player->kartstuff[k_itemroulette] = 0; + player->kartstuff[k_roulettetype] = 0; + 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->kartstuff[k_itemamount] = cv_kartdebugamount.value; + player->karthud[khud_itemblink] = TICRATE; + player->karthud[khud_itemblinkmode] = 2; + player->kartstuff[k_itemroulette] = 0; + player->kartstuff[k_roulettetype] = 0; + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, sfx_dbgsal); + return; + } + + // SPECIAL CASE No. 3: + // Record Attack / alone mashing behavior + if (modeattacking || pingame == 1) + { + if (G_RaceGametype()) + { + if (mashed && (modeattacking || cv_superring.value)) // ANY mashed value? You get rings. + { + K_KartGetItemResult(player, KITEM_SUPERRING); + player->karthud[khud_itemblinkmode] = 1; + 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->karthud[khud_itemblinkmode] = 0; + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, sfx_itrolf); + } + } + else if (G_BattleGametype()) + { + if (mashed && (modeattacking || cv_banana.value)) // ANY mashed value? You get a banana. + { + K_KartGetItemResult(player, KITEM_BANANA); + player->karthud[khud_itemblinkmode] = 1; + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, sfx_itrolm); + } + else + { + if (modeattacking || cv_orbinaut.value) // Waited patiently? You get an orbinaut! + K_KartGetItemResult(player, KITEM_ORBINAUT); + else // Default to sad if nothing's enabled... + K_KartGetItemResult(player, KITEM_SAD); + player->karthud[khud_itemblinkmode] = 0; + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, sfx_itrolf); + } + } + + player->karthud[khud_itemblink] = TICRATE; + player->kartstuff[k_itemroulette] = 0; + player->kartstuff[k_roulettetype] = 0; + return; + } + + // SPECIAL CASE No. 4: + // Being in ring debt occasionally forces Super Ring on you if you mashed + if (mashed && player->kartstuff[k_rings] < 0 && cv_superring.value) + { + INT32 debtamount = min(20, abs(player->kartstuff[k_rings])); + if (P_RandomChance((debtamount*FRACUNIT)/20)) + { + K_KartGetItemResult(player, KITEM_SUPERRING); + player->karthud[khud_itemblink] = TICRATE; + player->karthud[khud_itemblinkmode] = 1; + player->kartstuff[k_itemroulette] = 0; + player->kartstuff[k_roulettetype] = 0; + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, (mashed ? sfx_itrolm : sfx_itrolf)); + return; + } + } + + // SPECIAL CASE No. 5: + // Force SPB onto 2nd if they get too far behind + if (player->kartstuff[k_position] == 2 && pdis > (DISTVAR*6) + && spbplace == -1 && !indirectitemcooldown && !dontforcespb + && cv_selfpropelledbomb.value) + { + K_KartGetItemResult(player, KITEM_SPB); + player->karthud[khud_itemblink] = TICRATE; + player->karthud[khud_itemblinkmode] = (mashed ? 1 : 0); + player->kartstuff[k_itemroulette] = 0; + player->kartstuff[k_roulettetype] = 0; + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, (mashed ? sfx_itrolm : sfx_itrolf)); + 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, mashed, spbrush)); + + // 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->kartstuff[k_itemtype] = KITEM_SAD; + player->kartstuff[k_itemamount] = 1; + } + + if (P_IsDisplayPlayer(player)) + S_StartSound(NULL, ((player->kartstuff[k_roulettetype] == 1) ? sfx_itrolk : (mashed ? sfx_itrolm : sfx_itrolf))); + + player->karthud[khud_itemblink] = TICRATE; + player->karthud[khud_itemblinkmode] = ((player->kartstuff[k_roulettetype] == 1) ? 2 : (mashed ? 1 : 0)); + + player->kartstuff[k_itemroulette] = 0; // Since we're done, clear the roulette number + player->kartstuff[k_roulettetype] = 0; // This too +} + +//} + +//{ SRB2kart p_user.c Stuff + +static fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against) +{ + fixed_t weight = 5<type) + { + case MT_PLAYER: + if (!mobj->player) + break; + if (against->player && !against->player->kartstuff[k_spinouttimer] && mobj->player->kartstuff[k_spinouttimer]) + weight = 0; // Do not bump + else + { + weight = (mobj->player->kartweight)<player->speed > K_GetKartSpeed(mobj->player, false)) + weight += (mobj->player->speed - K_GetKartSpeed(mobj->player, false))/8; + } + break; + case MT_FALLINGROCK: + if (against->player) + { + if (against->player->kartstuff[k_invincibilitytimer] + || against->player->kartstuff[k_growshrinktimer] > 0) + weight = 0; + else + weight = (against->player->kartweight)<player) + weight = (against->player->kartweight)<player) + weight = (against->player->kartweight+3)<health <= 0) + return; + + if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0 + || player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0 + || (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1))) + return; + + player->kartstuff[k_ringboost] = 0; + player->kartstuff[k_driftboost] = 0; + player->kartstuff[k_drift] = 0; + player->kartstuff[k_driftcharge] = 0; + player->kartstuff[k_pogospring] = 0; + + player->kartstuff[k_spinouttype] = 2; + player->kartstuff[k_spinouttimer] = length; + player->kartstuff[k_wipeoutslow] = min(length-1, wipeoutslowtime+1); + + if (player->mo->state != &states[S_KART_SPIN]) + P_SetPlayerMobjState(player->mo, S_KART_SPIN); + + K_DropHnextList(player); + return; +} + +void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) +{ + mobj_t *fx; + fixed_t momdifx, momdify; + fixed_t distx, disty; + fixed_t dot, force; + fixed_t mass1, mass2; + + if (!mobj1 || !mobj2) + return; + + // Don't bump when you're being reborn + if ((mobj1->player && mobj1->player->playerstate != PST_LIVE) + || (mobj2->player && mobj2->player->playerstate != PST_LIVE)) + return; + + if ((mobj1->player && mobj1->player->kartstuff[k_respawn]) + || (mobj2->player && mobj2->player->kartstuff[k_respawn])) + return; + + { // Don't bump if you're flashing + INT32 flash; + + flash = K_GetKartFlashing(mobj1->player); + if (mobj1->player && mobj1->player->powers[pw_flashing] > 0 && mobj1->player->powers[pw_flashing] < flash) + { + if (mobj1->player->powers[pw_flashing] < flash-1) + mobj1->player->powers[pw_flashing]++; + return; + } + + flash = K_GetKartFlashing(mobj2->player); + if (mobj2->player && mobj2->player->powers[pw_flashing] > 0 && mobj2->player->powers[pw_flashing] < flash) + { + if (mobj2->player->powers[pw_flashing] < flash-1) + mobj2->player->powers[pw_flashing]++; + return; + } + } + + // Don't bump if you've recently bumped + if (mobj1->player && mobj1->player->kartstuff[k_justbumped]) + { + mobj1->player->kartstuff[k_justbumped] = bumptime; + return; + } + + if (mobj2->player && mobj2->player->kartstuff[k_justbumped]) + { + mobj2->player->kartstuff[k_justbumped] = bumptime; + return; + } + + mass1 = K_GetMobjWeight(mobj1, mobj2); + + if (solid == true && mass1 > 0) + mass2 = mass1; + else + mass2 = K_GetMobjWeight(mobj2, mobj1); + + momdifx = mobj1->momx - mobj2->momx; + momdify = mobj1->momy - mobj2->momy; + + // Adds the OTHER player's momentum times a bunch, for the best chance of getting the correct direction + distx = (mobj1->x + mobj2->momx*3) - (mobj2->x + mobj1->momx*3); + disty = (mobj1->y + mobj2->momy*3) - (mobj2->y + mobj1->momy*3); + + if (distx == 0 && disty == 0) + // if there's no distance between the 2, they're directly on top of each other, don't run this + return; + + { // Normalize distance to the sum of the two objects' radii, since in a perfect world that would be the distance at the point of collision... + fixed_t dist = P_AproxDistance(distx, disty); + fixed_t nx = FixedDiv(distx, dist); + fixed_t ny = FixedDiv(disty, dist); + + dist = dist ? dist : 1; + distx = FixedMul(mobj1->radius+mobj2->radius, nx); + disty = FixedMul(mobj1->radius+mobj2->radius, ny); + + if (momdifx == 0 && momdify == 0) + { + // If there's no momentum difference, they're moving at exactly the same rate. Pretend they moved into each other. + momdifx = -nx; + momdify = -ny; + } + } + + // if the speed difference is less than this let's assume they're going proportionately faster from each other + if (P_AproxDistance(momdifx, momdify) < (25*mapobjectscale)) + { + fixed_t momdiflength = P_AproxDistance(momdifx, momdify); + fixed_t normalisedx = FixedDiv(momdifx, momdiflength); + fixed_t normalisedy = FixedDiv(momdify, momdiflength); + momdifx = FixedMul((25*mapobjectscale), normalisedx); + momdify = FixedMul((25*mapobjectscale), normalisedy); + } + + dot = FixedMul(momdifx, distx) + FixedMul(momdify, disty); + + if (dot >= 0) + { + // They're moving away from each other + return; + } + + force = FixedDiv(dot, FixedMul(distx, distx)+FixedMul(disty, disty)); + + if (bounce == true && mass2 > 0) // Perform a Goomba Bounce. + mobj1->momz = -mobj1->momz; + else + { + fixed_t newz = mobj1->momz; + if (mass2 > 0) + mobj1->momz = mobj2->momz; + if (mass1 > 0 && solid == false) + mobj2->momz = newz; + } + + if (mass2 > 0) + { + mobj1->momx = mobj1->momx - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), force), distx); + mobj1->momy = mobj1->momy - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), force), disty); + } + + if (mass1 > 0 && solid == false) + { + mobj2->momx = mobj2->momx - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), force), -distx); + mobj2->momy = mobj2->momy - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), force), -disty); + } + + // Do the bump fx when we've CONFIRMED we can bump. + S_StartSound(mobj1, sfx_s3k49); + + fx = P_SpawnMobj(mobj1->x/2 + mobj2->x/2, mobj1->y/2 + mobj2->y/2, mobj1->z/2 + mobj2->z/2, MT_BUMP); + if (mobj1->eflags & MFE_VERTICALFLIP) + fx->eflags |= MFE_VERTICALFLIP; + else + fx->eflags &= ~MFE_VERTICALFLIP; + P_SetScale(fx, mobj1->scale); + + // Because this is done during collision now, rmomx and rmomy need to be recalculated + // so that friction doesn't immediately decide to stop the player if they're at a standstill + // Also set justbumped here + if (mobj1->player) + { + mobj1->player->rmomx = mobj1->momx - mobj1->player->cmomx; + mobj1->player->rmomy = mobj1->momy - mobj1->player->cmomy; + mobj1->player->kartstuff[k_justbumped] = bumptime; + + if (mobj1->player->kartstuff[k_spinouttimer]) + { + mobj1->player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1; + mobj1->player->kartstuff[k_spinouttimer] = max(wipeoutslowtime+1, mobj1->player->kartstuff[k_spinouttimer]); + //mobj1->player->kartstuff[k_spinouttype] = 1; // Enforce type + } + else if (mobj2->player) // Player VS player bumping only + { + if (mobj1->player->kartstuff[k_rings] <= 0) + { + K_DebtStingPlayer(mobj1->player, TICRATE + (4 * (mobj2->player->kartweight - mobj1->player->kartweight))); + K_KartPainEnergyFling(mobj1->player); + P_PlayRinglossSound(mobj1); + } + P_PlayerRingBurst(mobj1->player, 1); + } + } + + if (mobj2->player) + { + mobj2->player->rmomx = mobj2->momx - mobj2->player->cmomx; + mobj2->player->rmomy = mobj2->momy - mobj2->player->cmomy; + mobj2->player->kartstuff[k_justbumped] = bumptime; + + if (mobj2->player->kartstuff[k_spinouttimer]) + { + mobj2->player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1; + mobj2->player->kartstuff[k_spinouttimer] = max(wipeoutslowtime+1, mobj2->player->kartstuff[k_spinouttimer]); + //mobj2->player->kartstuff[k_spinouttype] = 1; // Enforce type + } + else if (mobj1->player) // Player VS player bumping only + { + if (mobj2->player->kartstuff[k_rings] <= 0) + { + K_DebtStingPlayer(mobj2->player, TICRATE + (4 * (mobj1->player->kartweight - mobj2->player->kartweight))); + K_KartPainEnergyFling(mobj2->player); + P_PlayRinglossSound(mobj2); + } + P_PlayerRingBurst(mobj2->player, 1); + } + } +} + +/** \brief Checks that the player is on an offroad subsector for realsies + + \param mo player mobj object + + \return boolean +*/ +static UINT8 K_CheckOffroadCollide(mobj_t *mo) +{ + UINT8 i; + + I_Assert(mo != NULL); + I_Assert(!P_MobjWasRemoved(mo)); + + for (i = 2; i < 5; i++) + { + if (P_MobjTouchingSectorSpecial(mo, 1, i, true)) + return i-1; + } + + return 0; +} + +/** \brief Updates the Player's offroad value once per frame + + \param player player object passed from K_KartPlayerThink + + \return void +*/ +static void K_UpdateOffroad(player_t *player) +{ + fixed_t offroadstrength = (K_CheckOffroadCollide(player->mo) << FRACBITS); + + // If you are in offroad, a timer starts. + if (offroadstrength) + { + if (player->kartstuff[k_offroad] < offroadstrength) + player->kartstuff[k_offroad] += offroadstrength / TICRATE; + + if (player->kartstuff[k_offroad] > offroadstrength) + player->kartstuff[k_offroad] = offroadstrength; + } + else + player->kartstuff[k_offroad] = 0; +} + +static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t curdist, fixed_t maxdist, boolean transparent) +{ +#define CHAOTIXBANDLEN 15 +#define CHAOTIXBANDCOLORS 9 + static const UINT8 colors[CHAOTIXBANDCOLORS] = { + SKINCOLOR_SAPPHIRE, + SKINCOLOR_PLATINUM, + SKINCOLOR_TEA, + SKINCOLOR_GARDEN, + SKINCOLOR_BANANA, + SKINCOLOR_GOLD, + SKINCOLOR_ORANGE, + SKINCOLOR_SCARLET, + SKINCOLOR_TAFFY + }; + fixed_t minimumdist = FixedMul(RING_DIST>>1, player->mo->scale); + UINT8 c = FixedMul(CHAOTIXBANDCOLORS<> FRACBITS; + UINT8 n = CHAOTIXBANDLEN; + UINT8 offset = ((leveltime / 3) % 3); + fixed_t stepx, stepy, stepz; + fixed_t curx, cury, curz; + + stepx = (victim->mo->x - player->mo->x) / CHAOTIXBANDLEN; + stepy = (victim->mo->y - player->mo->y) / CHAOTIXBANDLEN; + stepz = ((victim->mo->z + (victim->mo->height / 2)) - (player->mo->z + (player->mo->height / 2))) / CHAOTIXBANDLEN; + + curx = player->mo->x + stepx; + cury = player->mo->y + stepy; + curz = player->mo->z + stepz; + + while (n) + { + if (offset == 0) + { + mobj_t *band = P_SpawnMobj(curx + (P_RandomRange(-12,12)*mapobjectscale), + cury + (P_RandomRange(-12,12)*mapobjectscale), + curz + (P_RandomRange(24,48)*mapobjectscale), + MT_SIGNSPARKLE); + P_SetMobjState(band, S_SIGNSPARK1 + (leveltime % 11)); + P_SetScale(band, (band->destscale = (3*player->mo->scale)/2)); + band->color = colors[c]; + band->colorized = true; + band->fuse = 2; + if (transparent) + band->flags2 |= MF2_SHADOW; + if (!P_IsDisplayPlayer(player) && !P_IsDisplayPlayer(victim)) + band->flags2 |= MF2_DONTDRAW; + } + + curx += stepx; + cury += stepy; + curz += stepz; + + offset = abs(offset-1) % 3; + n--; + } +#undef CHAOTIXBANDLEN +} + +/** \brief Updates the player's drafting values once per frame + + \param player player object passed from K_KartPlayerThink + + \return void +*/ +static void K_UpdateDraft(player_t *player) +{ + fixed_t topspd = K_GetKartSpeed(player, false); + fixed_t draftdistance; + UINT8 leniency; + UINT8 i; + + // Distance you have to be to draft. If you're still accelerating, then this distance is lessened. + // This distance biases toward low weight! (min weight gets 4096 units, max weight gets 3072 units) + // This distance is also scaled based on game speed. + draftdistance = (3072 + (128 * (9 - player->kartweight))) * player->mo->scale; + if (player->speed < topspd) + draftdistance = FixedMul(draftdistance, FixedDiv(player->speed, topspd)); + draftdistance = FixedMul(draftdistance, K_GetKartGameSpeedScalar(gamespeed)); + + // On the contrary, the leniency period biases toward high weight. + // (See also: the leniency variable in K_SpawnDraftDust) + leniency = (3*TICRATE)/4 + ((player->kartweight-1) * (TICRATE/4)); + + // Not enough speed to draft. + if (player->speed >= 20*player->mo->scale) + { +//#define EASYDRAFTTEST + // Let's hunt for players to draft off of! + for (i = 0; i < MAXPLAYERS; i++) + { + fixed_t dist, olddraft; +#ifndef EASYDRAFTTEST + angle_t yourangle, theirangle, diff; +#endif + + if (!playeringame[i] || players[i].spectator || !players[i].mo) + continue; + +#ifndef EASYDRAFTTEST + // Don't draft on yourself :V + if (&players[i] == player) + continue; +#endif + + // Not enough speed to draft off of. + if (players[i].speed < 20*players[i].mo->scale) + continue; + +#ifndef EASYDRAFTTEST + yourangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); + theirangle = R_PointToAngle2(0, 0, players[i].mo->momx, players[i].mo->momy); + + diff = R_PointToAngle2(player->mo->x, player->mo->y, players[i].mo->x, players[i].mo->y) - yourangle; + if (diff > ANGLE_180) + diff = InvAngle(diff); + + // Not in front of this player. + if (diff > ANG10) + continue; + + diff = yourangle - theirangle; + if (diff > ANGLE_180) + diff = InvAngle(diff); + + // Not moving in the same direction. + if (diff > ANGLE_90) + continue; +#endif + + dist = P_AproxDistance(P_AproxDistance(players[i].mo->x - player->mo->x, players[i].mo->y - player->mo->y), players[i].mo->z - player->mo->z); + +#ifndef EASYDRAFTTEST + // TOO close to draft. + if (dist < FixedMul(RING_DIST>>1, player->mo->scale)) + continue; + + // Not close enough to draft. + if (dist > draftdistance) + continue; +#endif + + olddraft = player->kartstuff[k_draftpower]; + + player->kartstuff[k_draftleeway] = leniency; + player->kartstuff[k_lastdraft] = i; + + // Draft power is used later in K_GetKartBoostPower, ranging from 0 for normal speed and FRACUNIT for max draft speed. + // How much this increments every tic biases toward acceleration! (min speed gets 1.5% per tic, max speed gets 0.5% per tic) + if (player->kartstuff[k_draftpower] < FRACUNIT) + player->kartstuff[k_draftpower] += (FRACUNIT/200) + ((9 - player->kartspeed) * ((3*FRACUNIT)/1600)); + + if (player->kartstuff[k_draftpower] > FRACUNIT) + player->kartstuff[k_draftpower] = FRACUNIT; + + // Play draft finish noise + if (olddraft < FRACUNIT && player->kartstuff[k_draftpower] >= FRACUNIT) + S_StartSound(player->mo, sfx_cdfm62); + + // Spawn in the visual! + K_DrawDraftCombiring(player, &players[i], dist, draftdistance, false); + + return; // Finished doing our draft. + } + } + + // No one to draft off of? Then you can knock that off. + if (player->kartstuff[k_draftleeway]) // Prevent small disruptions from stopping your draft. + { + player->kartstuff[k_draftleeway]--; + if (player->kartstuff[k_lastdraft] >= 0 + && player->kartstuff[k_lastdraft] < MAXPLAYERS + && playeringame[player->kartstuff[k_lastdraft]] + && !players[player->kartstuff[k_lastdraft]].spectator + && players[player->kartstuff[k_lastdraft]].mo) + { + player_t *victim = &players[player->kartstuff[k_lastdraft]]; + fixed_t dist = P_AproxDistance(P_AproxDistance(victim->mo->x - player->mo->x, victim->mo->y - player->mo->y), victim->mo->z - player->mo->z); + K_DrawDraftCombiring(player, victim, dist, draftdistance, true); + } + } + else // Remove draft speed boost. + { + player->kartstuff[k_draftpower] = 0; + player->kartstuff[k_lastdraft] = -1; + } +} + +void K_KartPainEnergyFling(player_t *player) +{ + static const UINT8 numfling = 5; + INT32 i; + mobj_t *mo; + angle_t fa; + fixed_t ns; + fixed_t z; + + // Better safe than sorry. + if (!player) + return; + + // P_PlayerRingBurst: "There's no ring spilling in kart, so I'm hijacking this for the same thing as TD" + // :oh: + + for (i = 0; i < numfling; i++) + { + INT32 objType = mobjinfo[MT_FLINGENERGY].reactiontime; + fixed_t momxy, momz; // base horizonal/vertical thrusts + + z = player->mo->z; + if (player->mo->eflags & MFE_VERTICALFLIP) + z += player->mo->height - mobjinfo[objType].height; + + mo = P_SpawnMobj(player->mo->x, player->mo->y, z, objType); + + mo->fuse = 8*TICRATE; + P_SetTarget(&mo->target, player->mo); + + mo->destscale = player->mo->scale; + P_SetScale(mo, player->mo->scale); + + // Angle offset by player angle, then slightly offset by amount of fling + fa = ((i*FINEANGLES/16) + (player->mo->angle>>ANGLETOFINESHIFT) - ((numfling-1)*FINEANGLES/32)) & FINEMASK; + + if (i > 15) + { + momxy = 3*FRACUNIT; + momz = 4*FRACUNIT; + } + else + { + momxy = 28*FRACUNIT; + momz = 3*FRACUNIT; + } + + ns = FixedMul(momxy, mo->scale); + mo->momx = FixedMul(FINECOSINE(fa),ns); + + ns = momz; + P_SetObjectMomZ(mo, ns, false); + + if (i & 1) + P_SetObjectMomZ(mo, ns, true); + + if (player->mo->eflags & MFE_VERTICALFLIP) + mo->momz *= -1; + } +} + +// Adds gravity flipping to an object relative to its master and shifts the z coordinate accordingly. +void K_FlipFromObject(mobj_t *mo, mobj_t *master) +{ + mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP)|(master->eflags & MFE_VERTICALFLIP); + mo->flags2 = (mo->flags2 & ~MF2_OBJECTFLIP)|(master->flags2 & MF2_OBJECTFLIP); + + if (mo->eflags & MFE_VERTICALFLIP) + mo->z += master->height - FixedMul(master->scale, mo->height); +} + +// These have to go earlier than its sisters because of K_RespawnChecker... +void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master) +{ + // flipping + // handle z shifting from there too. This is here since there's no reason not to flip us if needed when we do this anyway; + K_FlipFromObject(mo, master); + + // visibility (usually for hyudoro) + mo->flags2 = (mo->flags2 & ~MF2_DONTDRAW)|(master->flags2 & MF2_DONTDRAW); + mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP1)|(master->eflags & MFE_DRAWONLYFORP1); + mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP2)|(master->eflags & MFE_DRAWONLYFORP2); + mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP3)|(master->eflags & MFE_DRAWONLYFORP3); + mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP4)|(master->eflags & MFE_DRAWONLYFORP4); +} + +static void K_SpawnDashDustRelease(player_t *player) +{ + fixed_t newx; + fixed_t newy; + mobj_t *dust; + angle_t travelangle; + INT32 i; + + I_Assert(player != NULL); + I_Assert(player->mo != NULL); + I_Assert(!P_MobjWasRemoved(player->mo)); + + if (!P_IsObjectOnGround(player->mo)) + return; + + if (!player->speed && !player->kartstuff[k_startboost]) + return; + + travelangle = player->mo->angle; + + if (player->kartstuff[k_drift] || player->kartstuff[k_driftend]) + travelangle -= (ANGLE_45/5)*player->kartstuff[k_drift]; + + for (i = 0; i < 2; i++) + { + newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_90, FixedMul(48*FRACUNIT, player->mo->scale)); + newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_90, FixedMul(48*FRACUNIT, player->mo->scale)); + dust = P_SpawnMobj(newx, newy, player->mo->z, MT_FASTDUST); + + P_SetTarget(&dust->target, player->mo); + dust->angle = travelangle - ((i&1) ? -1 : 1)*ANGLE_45; + dust->destscale = player->mo->scale; + P_SetScale(dust, player->mo->scale); + + dust->momx = 3*player->mo->momx/5; + dust->momy = 3*player->mo->momy/5; + //dust->momz = 3*player->mo->momz/5; + + K_MatchGenericExtraFlags(dust, player->mo); + } +} + +static void K_SpawnBrakeDriftSparks(player_t *player) // Be sure to update the mobj thinker case too! +{ + mobj_t *sparks; + + I_Assert(player != NULL); + I_Assert(player->mo != NULL); + I_Assert(!P_MobjWasRemoved(player->mo)); + + // Position & etc are handled in its thinker, and its spawned invisible. + // This avoids needing to dupe code if we don't need it. + sparks = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BRAKEDRIFT); + P_SetTarget(&sparks->target, player->mo); + P_SetScale(sparks, (sparks->destscale = player->mo->scale)); + K_MatchGenericExtraFlags(sparks, player->mo); + sparks->flags2 |= MF2_DONTDRAW; +} + +/** \brief Preps a player to respawn + + \param player player to respawn + + \return void +*/ +void K_DoIngameRespawn(player_t *player) +{ + if (!player->mo || P_MobjWasRemoved(player->mo)) + return; + + if (player->kartstuff[k_respawn]) + return; + + if (leveltime <= starttime) + return; + + if (player->nextwaypoint == NULL) // Starpost xyz not initalized(?) + { + UINT32 bestdist = UINT32_MAX; + mapthing_t *beststart = NULL; + UINT8 numstarts = 0; + + if (G_RaceGametype()) + { + numstarts = numcoopstarts; + } + else if (G_BattleGametype()) + { + numstarts = numdmstarts; + } + + if (numstarts > 0) + { + UINT8 i = 0; + + for (i = 0; i < numstarts; i++) + { + UINT32 dist = UINT32_MAX; + mapthing_t *checkstart = NULL; + + if (G_RaceGametype()) + { + checkstart = playerstarts[i]; + } + else if (G_BattleGametype()) + { + checkstart = deathmatchstarts[i]; + } + else + { + break; + } + + dist = (UINT32)P_AproxDistance((player->mo->x >> FRACBITS) - checkstart->x, + (player->mo->y >> FRACBITS) - checkstart->y); + + if (dist < bestdist) + { + beststart = checkstart; + bestdist = dist; + } + } + } + + if (beststart == NULL) + { + CONS_Alert(CONS_WARNING, "No respawn points!\n"); + } + else + { + sector_t *s; + fixed_t z = (beststart->options >> ZSHIFT); + + player->starpostx = beststart->x; + player->starposty = beststart->y; + s = R_PointInSubsector(beststart->x << FRACBITS, beststart->y << FRACBITS)->sector; + + if (beststart->options & MTF_OBJECTFLIP) + { + player->starpostz = ( +#ifdef ESLOPE + s->c_slope ? P_GetZAt(s->c_slope, beststart->x << FRACBITS, beststart->y << FRACBITS) : +#endif + s->ceilingheight) >> FRACBITS; + + if (z) + player->starpostz -= z; + + player->starpostz -= mobjinfo[MT_PLAYER].height; + player->kartstuff[k_starpostflip] = 1; + } + else + { + player->starpostz = ( +#ifdef ESLOPE + s->f_slope ? P_GetZAt(s->f_slope, beststart->x << FRACBITS, beststart->y << FRACBITS) : +#endif + s->floorheight) >> FRACBITS; + + if (z) + player->starpostz += z; + + player->kartstuff[k_starpostflip] = 0; + } + } + } + + player->mo->flags &= ~(MF_SOLID|MF_SHOOTABLE); + player->mo->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY; + player->mo->flags2 &= ~MF2_DONTDRAW; + + player->kartstuff[k_respawn] = 48; +} + +/** \brief Calculates the respawn timer and drop-boosting + + \param player player object passed from K_KartPlayerThink + + \return void +*/ +void K_RespawnChecker(player_t *player) +{ + ticcmd_t *cmd = &player->cmd; + + if (player->spectator) + return; + + if (player->kartstuff[k_respawn] > 1) + { + fixed_t destx = 0, desty = 0, destz = 0; + + player->mo->momx = player->mo->momy = player->mo->momz = 0; + player->powers[pw_flashing] = 2; + player->powers[pw_nocontrol] = 2; + + if (leveltime % 8 == 0 && !mapreset) + S_StartSound(player->mo, sfx_s3kcas); + + destx = (player->starpostx << FRACBITS); + desty = (player->starposty << FRACBITS); + destz = (player->starpostz << FRACBITS); + + if (player->kartstuff[k_starpostflip]) + destz -= (128 * mapobjectscale) + (player->mo->height); + else + destz += (128 * mapobjectscale); + + if (player->mo->x != destx || player->mo->y != desty || player->mo->z != destz) + { + fixed_t step = 64*mapobjectscale; + fixed_t dist = P_AproxDistance(P_AproxDistance(player->mo->x - destx, player->mo->y - desty), player->mo->z - destz); + + if (dist <= step) // You're ready to respawn + { + P_TryMove(player->mo, destx, desty, true); + player->mo->z = destz; + } + else + { + fixed_t stepx = 0, stepy = 0, stepz = 0; + angle_t stepha = R_PointToAngle2(player->mo->x, player->mo->y, destx, desty); + angle_t stepva = R_PointToAngle2(0, player->mo->z, P_AproxDistance(player->mo->x - destx, player->mo->y - desty), destz); + fixed_t laserx = 0, lasery = 0, laserz = 0; + UINT8 lasersteps = 4; + + // Move toward the respawn point + stepx = FixedMul(FixedMul(FINECOSINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + stepy = FixedMul(FixedMul(FINESINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + stepz = FixedMul(FINESINE(stepva >> ANGLETOFINESHIFT), 2*step); + + P_TryMove(player->mo, player->mo->x + stepx, player->mo->y + stepy, true); + player->mo->z += stepz; + + // Spawn lasers along the path + laserx = player->mo->x + (stepx / 2); + lasery = player->mo->y + (stepy / 2); + laserz = player->mo->z + (stepz / 2); + dist = P_AproxDistance(P_AproxDistance(laserx - destx, lasery - desty), laserz - destz); + + if (dist > step/2) + { + while (lasersteps) + { + + stepha = R_PointToAngle2(laserx, lasery, destx, desty); + stepva = R_PointToAngle2(0, laserz, P_AproxDistance(laserx - destx, lasery - desty), destz); + + stepx = FixedMul(FixedMul(FINECOSINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + stepy = FixedMul(FixedMul(FINESINE(stepha >> ANGLETOFINESHIFT), step), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + stepz = FixedMul(FINESINE(stepva >> ANGLETOFINESHIFT), 2*step); + + laserx += stepx; + lasery += stepy; + laserz += stepz; + dist = P_AproxDistance(P_AproxDistance(laserx - destx, lasery - desty), laserz - destz); + + if (dist <= step/2) + break; + + lasersteps--; + } + } + + if (lasersteps == 0) // Don't spawn them beyond the respawn point. + { + mobj_t *laser; + + laser = P_SpawnMobj(laserx, lasery, laserz + (player->mo->height / 2), MT_DEZLASER); + + if (laser && !P_MobjWasRemoved(laser)) + { + P_SetMobjState(laser, S_DEZLASER_TRAIL1); + if (player->mo->eflags & MFE_VERTICALFLIP) + laser->eflags |= MFE_VERTICALFLIP; + P_SetTarget(&laser->target, player->mo); + laser->angle = stepha + ANGLE_90; + P_SetScale(laser, (laser->destscale = FRACUNIT)); + } + } + } + } + else + { + player->kartstuff[k_respawn]--; + + player->mo->flags |= MF_SOLID|MF_SHOOTABLE; + player->mo->flags &= ~(MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY); + if (!(player->pflags & PF_NOCLIP)) + player->mo->flags &= ~MF_NOCLIP; + + if (leveltime % 8 == 0) + { + INT32 i; + + for (i = 0; i < 8; i++) + { + mobj_t *laser; + angle_t newangle; + fixed_t newx, newy, newz; + + newangle = FixedAngle(((360/8)*i)*FRACUNIT); + newx = player->mo->x + P_ReturnThrustX(player->mo, newangle, 31<mo->y + P_ReturnThrustY(player->mo, newangle, 31<mo->eflags & MFE_VERTICALFLIP) + newz = player->mo->z + player->mo->height; + else + newz = player->mo->z; + + laser = P_SpawnMobj(newx, newy, newz, MT_DEZLASER); + + if (laser && !P_MobjWasRemoved(laser)) + { + if (player->mo->eflags & MFE_VERTICALFLIP) + laser->eflags |= MFE_VERTICALFLIP; + P_SetTarget(&laser->target, player->mo); + laser->angle = newangle+ANGLE_90; + laser->momz = (8<mo); + P_SetScale(laser, (laser->destscale = FRACUNIT)); + } + } + } + } + } + else if (player->kartstuff[k_respawn] == 1) + { + if (player->kartstuff[k_growshrinktimer] < 0) + { + player->mo->scalespeed = mapobjectscale/TICRATE; + player->mo->destscale = (6*mapobjectscale)/8; + if (cv_kartdebugshrink.value && !modeattacking && !player->bot) + player->mo->destscale = (6*player->mo->destscale)/8; + } + + if (!P_IsObjectOnGround(player->mo) && !mapreset) + { + player->powers[pw_flashing] = K_GetKartFlashing(player); + + // Sal: The old behavior was stupid and prone to accidental usage. + // Let's rip off Mania instead, and turn this into a Drop Dash! + + if (cmd->buttons & BT_ACCELERATE) + player->kartstuff[k_dropdash]++; + else + player->kartstuff[k_dropdash] = 0; + + if (player->kartstuff[k_dropdash] == TICRATE/4) + S_StartSound(player->mo, sfx_ddash); + + if ((player->kartstuff[k_dropdash] >= TICRATE/4) + && (player->kartstuff[k_dropdash] & 1)) + player->mo->colorized = true; + else + player->mo->colorized = false; + } + else + { + if ((cmd->buttons & BT_ACCELERATE) && (player->kartstuff[k_dropdash] >= TICRATE/4)) + { + S_StartSound(player->mo, sfx_s23c); + player->kartstuff[k_startboost] = 50; + K_SpawnDashDustRelease(player); } - } - } -} - -#undef SETBRIGHTNESS - -/** \brief Generates a translation colormap for Kart, to replace R_GenerateTranslationColormap in r_draw.c - - \param dest_colormap colormap to populate - \param skinnum number of skin, TC_DEFAULT or TC_BOSS - \param color translation color - - \return void -*/ -void K_GenerateKartColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color) -{ - INT32 i; - INT32 starttranscolor; - - // Handle a couple of simple special cases - if (skinnum == TC_BOSS - || skinnum == TC_ALLWHITE - || skinnum == TC_METALSONIC - || skinnum == TC_BLINK - || color == SKINCOLOR_NONE) - { - for (i = 0; i < NUM_PALETTE_ENTRIES; i++) - { - if (skinnum == TC_ALLWHITE) - dest_colormap[i] = 0; - else if (skinnum == TC_BLINK) - dest_colormap[i] = colortranslations[color][3]; - else - dest_colormap[i] = (UINT8)i; - } - - // White! - if (skinnum == TC_BOSS) - dest_colormap[31] = 0; - else if (skinnum == TC_METALSONIC) - dest_colormap[143] = 0; - - return; - } - else if (skinnum == TC_RAINBOW) - { - K_RainbowColormap(dest_colormap, color); - return; - } - - starttranscolor = (skinnum != TC_DEFAULT) ? skins[skinnum].starttranscolor : DEFAULT_STARTTRANSCOLOR; - - // Fill in the entries of the palette that are fixed - for (i = 0; i < starttranscolor; i++) - dest_colormap[i] = (UINT8)i; - - for (i = (UINT8)(starttranscolor + 16); i < NUM_PALETTE_ENTRIES; i++) - dest_colormap[i] = (UINT8)i; - - // Build the translated ramp - for (i = 0; i < SKIN_RAMP_LENGTH; i++) - { - // Sryder 2017-10-26: What was here before was most definitely not particularly readable, check above for new color translation table - dest_colormap[starttranscolor + i] = colortranslations[color][i]; - } -} - -/** \brief Pulls kart color by name, to replace R_GetColorByName in r_draw.c - - \param name color name - - \return 0 -*/ -UINT8 K_GetKartColorByName(const char *name) -{ - UINT8 color = (UINT8)atoi(name); - if (color > 0 && color < MAXSKINCOLORS) - return color; - for (color = 1; color < MAXSKINCOLORS; color++) - if (!stricmp(KartColor_Names[color], name)) - return color; - return 0; -} - -//} - -//{ SRB2kart Net Variables - -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_superring); - CV_RegisterVar(&cv_kitchensink); - - 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); - CV_RegisterVar(&cv_kartspeed); - CV_RegisterVar(&cv_kartbumpers); - CV_RegisterVar(&cv_kartfrantic); - CV_RegisterVar(&cv_kartcomeback); - CV_RegisterVar(&cv_kartencore); - CV_RegisterVar(&cv_kartvoterulechanges); - CV_RegisterVar(&cv_kartspeedometer); - CV_RegisterVar(&cv_kartvoices); - CV_RegisterVar(&cv_karteliminatelast); - CV_RegisterVar(&cv_kartusepwrlv); - CV_RegisterVar(&cv_votetime); - - CV_RegisterVar(&cv_kartdebugitem); - CV_RegisterVar(&cv_kartdebugamount); - CV_RegisterVar(&cv_kartdebugshrink); - CV_RegisterVar(&cv_kartdebugdistribution); - CV_RegisterVar(&cv_kartdebughuddrop); - CV_RegisterVar(&cv_kartdebugwaypoints); - - CV_RegisterVar(&cv_kartdebugcheckpoint); - CV_RegisterVar(&cv_kartdebugnodes); - CV_RegisterVar(&cv_kartdebugcolorize); -} - -//} - -boolean K_IsPlayerLosing(player_t *player) -{ - INT32 winningpos = 1; - UINT8 i, pcount = 0; - - if (player->kartstuff[k_position] == 1) - return false; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - if (players[i].kartstuff[k_position] > pcount) - pcount = players[i].kartstuff[k_position]; - } - - if (pcount <= 1) - return false; - - winningpos = pcount/2; - if (pcount % 2) // any remainder? - winningpos++; - - return (player->kartstuff[k_position] > winningpos); -} - -boolean K_IsPlayerWanted(player_t *player) -{ - UINT8 i; - if (!(G_BattleGametype())) - return false; - for (i = 0; i < 4; i++) - { - if (battlewanted[i] == -1) - break; - if (player == &players[battlewanted[i]]) - return true; - } - return false; -} - -fixed_t K_GetKartGameSpeedScalar(SINT8 value) -{ - // Easy = 81.25% - // Normal = 100% - // Hard = 118.75% - // Nightmare = 137.5% ?!?! - return ((13 + (3*value)) << FRACBITS) / 16; -} - -//{ SRB2kart Roulette Code - Position Based - -#define NUMKARTODDS 80 - -// Less ugly 2D arrays -static INT32 K_KartItemOddsRace[NUMKARTRESULTS][8] = -{ - //P-Odds 0 1 2 3 4 5 6 7 - /*Sneaker*/ { 0, 0, 4, 6, 7, 0, 0, 0 }, // Sneaker - /*Rocket Sneaker*/ { 0, 0, 0, 0, 1, 4, 5, 3 }, // Rocket Sneaker - /*Invincibility*/ { 0, 0, 0, 0, 1, 4, 6,10 }, // Invincibility - /*Banana*/ { 9, 4, 2, 1, 0, 0, 0, 0 }, // Banana - /*Eggman Monitor*/ { 3, 2, 1, 0, 0, 0, 0, 0 }, // Eggman Monitor - /*Orbinaut*/ { 7, 6, 4, 2, 0, 0, 0, 0 }, // Orbinaut - /*Jawz*/ { 0, 3, 2, 1, 1, 0, 0, 0 }, // Jawz - /*Mine*/ { 0, 2, 2, 1, 0, 0, 0, 0 }, // Mine - /*Ballhog*/ { 0, 0, 2, 1, 0, 0, 0, 0 }, // Ballhog - /*Self-Propelled Bomb*/ { 0, 1, 2, 3, 4, 2, 2, 0 }, // Self-Propelled Bomb - /*Grow*/ { 0, 0, 0, 0, 0, 2, 5, 7 }, // Grow - /*Shrink*/ { 0, 0, 0, 0, 0, 0, 2, 0 }, // Shrink - /*Thunder Shield*/ { 1, 2, 0, 0, 0, 0, 0, 0 }, // Thunder Shield - /*Hyudoro*/ { 0, 0, 0, 1, 2, 1, 0, 0 }, // Hyudoro - /*Pogo Spring*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring - /*Super Ring*/ { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring - /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink - /*Sneaker x3*/ { 0, 0, 0, 3, 7, 9, 2, 0 }, // Sneaker x3 - /*Banana x3*/ { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 - /*Banana x10*/ { 0, 0, 0, 1, 0, 0, 0, 0 }, // Banana x10 - /*Orbinaut x3*/ { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 - /*Orbinaut x4*/ { 0, 0, 0, 1, 1, 0, 0, 0 }, // Orbinaut x4 - /*Jawz x2*/ { 0, 0, 1, 2, 0, 0, 0, 0 } // Jawz x2 -}; - -static INT32 K_KartItemOddsBattle[NUMKARTRESULTS][6] = -{ - //P-Odds 0 1 2 3 4 5 - /*Sneaker*/ { 3, 2, 2, 2, 0, 2 }, // Sneaker - /*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 0 }, // Rocket Sneaker - /*Invincibility*/ { 0, 1, 2, 3, 4, 2 }, // Invincibility - /*Banana*/ { 2, 1, 0, 0, 0, 0 }, // Banana - /*Eggman Monitor*/ { 1, 1, 0, 0, 0, 0 }, // Eggman Monitor - /*Orbinaut*/ { 6, 2, 1, 0, 0, 0 }, // Orbinaut - /*Jawz*/ { 3, 3, 3, 2, 0, 2 }, // Jawz - /*Mine*/ { 2, 3, 3, 1, 0, 2 }, // Mine - /*Ballhog*/ { 0, 1, 2, 1, 0, 2 }, // Ballhog - /*Self-Propelled Bomb*/ { 0, 0, 0, 0, 0, 0 }, // Self-Propelled Bomb - /*Grow*/ { 0, 0, 1, 2, 4, 2 }, // Grow - /*Shrink*/ { 0, 0, 0, 0, 0, 0 }, // Shrink - /*Thunder Shield*/ { 0, 0, 0, 0, 0, 0 }, // Thunder Shield - /*Hyudoro*/ { 1, 1, 0, 0, 0, 0 }, // Hyudoro - /*Pogo Spring*/ { 1, 1, 0, 0, 0, 0 }, // Pogo Spring - /*Super Ring*/ { 0, 0, 0, 0, 0, 0 }, // Super Ring - /*Kitchen Sink*/ { 0, 0, 0, 0, 0, 0 }, // Kitchen Sink - /*Sneaker x3*/ { 0, 0, 0, 2, 4, 2 }, // Sneaker x3 - /*Banana x3*/ { 1, 2, 1, 0, 0, 0 }, // Banana x3 - /*Banana x10*/ { 0, 0, 1, 1, 0, 2 }, // Banana x10 - /*Orbinaut x3*/ { 0, 1, 2, 1, 0, 0 }, // Orbinaut x3 - /*Orbinaut x4*/ { 0, 0, 1, 3, 4, 2 }, // Orbinaut x4 - /*Jawz x2*/ { 0, 0, 1, 2, 4, 2 } // Jawz x2 -}; - -#define DISTVAR (64*14) - -/** \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 (getitem == KITEM_HYUDORO) // Hyudoro cooldown - hyubgone = 5*TICRATE; - - switch (getitem) - { - // Special roulettes first, then the generic ones are handled by default - case KRITEM_TRIPLESNEAKER: // Sneaker x3 - player->kartstuff[k_itemtype] = KITEM_SNEAKER; - player->kartstuff[k_itemamount] = 3; - break; - case KRITEM_TRIPLEBANANA: // Banana x3 - player->kartstuff[k_itemtype] = KITEM_BANANA; - player->kartstuff[k_itemamount] = 3; - break; - case KRITEM_TENFOLDBANANA: // Banana x10 - player->kartstuff[k_itemtype] = KITEM_BANANA; - player->kartstuff[k_itemamount] = 10; - break; - case KRITEM_TRIPLEORBINAUT: // Orbinaut x3 - player->kartstuff[k_itemtype] = KITEM_ORBINAUT; - player->kartstuff[k_itemamount] = 3; - break; - case KRITEM_QUADORBINAUT: // Orbinaut x4 - player->kartstuff[k_itemtype] = KITEM_ORBINAUT; - player->kartstuff[k_itemamount] = 4; - break; - case KRITEM_DUALJAWZ: // Jawz x2 - player->kartstuff[k_itemtype] = KITEM_JAWZ; - player->kartstuff[k_itemamount] = 2; - break; - default: - if (getitem <= 0 || getitem >= NUMKARTRESULTS) // Sad (Fallback) - { - if (getitem != 0) - CONS_Printf("ERROR: P_KartGetItemResult - Item roulette gave bad item (%d) :(\n", getitem); - player->kartstuff[k_itemtype] = KITEM_SAD; - } - else - player->kartstuff[k_itemtype] = getitem; - player->kartstuff[k_itemamount] = 1; - break; - } -} - -/** \brief Item Roulette for Kart - - \param player player object passed from P_KartPlayerThink - - \return void -*/ - -static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush) -{ - INT32 newodds; - INT32 i; - UINT8 pingame = 0, pexiting = 0; - boolean thunderisout = false; - SINT8 first = -1, second = -1; - INT32 secondist = 0; - boolean itemenabled[NUMKARTRESULTS-1] = { - cv_sneaker.value, - cv_rocketsneaker.value, - cv_invincibility.value, - cv_banana.value, - cv_eggmanmonitor.value, - cv_orbinaut.value, - cv_jawz.value, - cv_mine.value, - cv_ballhog.value, - cv_selfpropelledbomb.value, - cv_grow.value, - cv_shrink.value, - cv_thundershield.value, - cv_hyudoro.value, - cv_pogospring.value, - cv_superring.value, - cv_kitchensink.value, - cv_triplesneaker.value, - cv_triplebanana.value, - cv_decabanana.value, - cv_tripleorbinaut.value, - cv_quadorbinaut.value, - cv_dualjawz.value - }; - - I_Assert(item > KITEM_NONE); // too many off by one scenarioes. - - if (!itemenabled[item-1] && !modeattacking) - return 0; - - if (G_BattleGametype()) - newodds = K_KartItemOddsBattle[item-1][pos]; - else - newodds = K_KartItemOddsRace[item-1][pos]; - - // Base multiplication to ALL item odds to simulate fractional precision - newodds *= 4; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - - if (!G_BattleGametype() || players[i].kartstuff[k_bumper]) - pingame++; - - if (players[i].exiting) - pexiting++; - - if (players[i].mo) - { - if (players[i].kartstuff[k_itemtype] == KITEM_THUNDERSHIELD) - thunderisout = true; - - if (!G_BattleGametype()) - { - if (players[i].kartstuff[k_position] == 1 && first == -1) - first = i; - if (players[i].kartstuff[k_position] == 2 && second == -1) - second = i; - } - } - } - - if (first != -1 && second != -1) // calculate 2nd's distance from 1st, for SPB - { - secondist = P_AproxDistance(P_AproxDistance(players[first].mo->x - players[second].mo->x, - players[first].mo->y - players[second].mo->y), - players[first].mo->z - players[second].mo->z) / mapobjectscale; - if (franticitems) - secondist = (15 * secondist) / 14; - secondist = ((28 + (8-pingame)) * secondist) / 28; - } - - // POWERITEMODDS handles all of the "frantic item" related functionality, for all of our powerful items. - // First, it multiplies it by 2 if franticitems is true; easy-peasy. - // Next, it multiplies it again if it's in SPB mode and 2nd needs to apply pressure to 1st. - // Then, it multiplies it further if the player count isn't equal to 8. - // This is done to make low player count races more interesting and high player count rates more fair. - // (2P normal would be about halfway between 8P normal and 8P frantic.) - // (This scaling is not done for SPB Rush, so that catchup strength is not weakened.) - // Lastly, it *divides* it by your mashed value, which was determined in K_KartItemRoulette, for lesser items needed in a pinch. - -#define PLAYERSCALING (8 - (spbrush ? 2 : pingame)) - -#define POWERITEMODDS(odds) {\ - if (franticitems) \ - odds <<= 1; \ - odds = FixedMul(odds<> FRACBITS; \ - if (mashed > 0) \ - odds = FixedDiv(odds<> FRACBITS; \ -} - -#define COOLDOWNONSTART (leveltime < (30*TICRATE)+starttime) - - switch (item) - { - case KITEM_ROCKETSNEAKER: - case KITEM_JAWZ: - case KITEM_BALLHOG: - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEBANANA: - case KRITEM_TENFOLDBANANA: - case KRITEM_TRIPLEORBINAUT: - case KRITEM_QUADORBINAUT: - case KRITEM_DUALJAWZ: - POWERITEMODDS(newodds); - break; - case KITEM_INVINCIBILITY: - case KITEM_MINE: - case KITEM_GROW: - if (COOLDOWNONSTART) - newodds = 0; - else - POWERITEMODDS(newodds); - break; - case KITEM_SPB: - if ((indirectitemcooldown > 0) || (pexiting > 0) || (secondist/DISTVAR < 3)) - newodds = 0; - else - newodds *= min((secondist/DISTVAR)-4, 3); // POWERITEMODDS(newodds); - break; - case KITEM_SHRINK: - if ((indirectitemcooldown > 0) || (pingame-1 <= pexiting) || COOLDOWNONSTART) - newodds = 0; - else - POWERITEMODDS(newodds); - break; - case KITEM_THUNDERSHIELD: - if (thunderisout || spbplace != -1) - newodds = 0; - else - POWERITEMODDS(newodds); - case KITEM_HYUDORO: - if ((hyubgone > 0) || COOLDOWNONSTART) - newodds = 0; - break; - default: - break; - } - -#undef POWERITEMODDS - - return newodds; -} - -//{ SRB2kart Roulette Code - Distance Based, no waypoints - -static INT32 K_FindUseodds(player_t *player, fixed_t mashed, INT32 pdis, INT32 bestbumper, boolean spbrush) -{ - INT32 i; - INT32 n = 0; - INT32 useodds = 0; - UINT8 disttable[14]; - UINT8 totallen = 0; - UINT8 distlen = 0; - boolean oddsvalid[8]; - - for (i = 0; i < 8; i++) - { - INT32 j; - boolean available = false; - - if (G_BattleGametype() && i > 5) - { - oddsvalid[i] = false; - break; - } - - for (j = 1; j < NUMKARTRESULTS; j++) - { - if (K_KartGetItemOdds(i, j, mashed, spbrush) > 0) - { - available = true; - break; - } - } - - oddsvalid[i] = available; - } - -#define SETUPDISTTABLE(odds, num) \ - if (oddsvalid[odds]) \ - for (i = num; i; --i) \ - disttable[distlen++] = odds; \ - totallen += num; - - if (G_BattleGametype()) // Battle Mode - { - SETUPDISTTABLE(0,1); - SETUPDISTTABLE(1,1); - SETUPDISTTABLE(2,1); - SETUPDISTTABLE(3,1); - SETUPDISTTABLE(4,1); - - if (player->kartstuff[k_roulettetype] == 1 && oddsvalid[5]) // 5 is the extreme odds of player-controlled "Karma" items - useodds = 5; - else - { - SINT8 wantedpos = (bestbumper-player->kartstuff[k_bumper]); // 0 is the best player's bumper count, 1 is a bumper below best, 2 is two bumpers below, etc - if (K_IsPlayerWanted(player)) - wantedpos++; - if (wantedpos > 4) // Don't run off into karma items - wantedpos = 4; - if (wantedpos < 0) // Don't go below somehow - wantedpos = 0; - n = (wantedpos * distlen) / totallen; - useodds = disttable[n]; - } - } - else - { - SETUPDISTTABLE(0,1); - SETUPDISTTABLE(1,1); - SETUPDISTTABLE(2,1); - SETUPDISTTABLE(3,2); - SETUPDISTTABLE(4,2); - SETUPDISTTABLE(5,3); - SETUPDISTTABLE(6,3); - SETUPDISTTABLE(7,1); - - if (pdis <= 0) // (64*14) * 0 = 0 - useodds = disttable[0]; - else if (pdis > DISTVAR * ((12 * distlen) / 14)) // (64*14) * 12 = 10752 - useodds = disttable[distlen-1]; - else - { - for (i = 1; i < 13; i++) - { - if (pdis <= DISTVAR * ((i * distlen) / 14)) - { - useodds = disttable[((i * distlen) / 14)]; - break; - } - } - } - } - -#undef SETUPDISTTABLE - - return useodds; -} - -static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) -{ - INT32 i; - UINT8 pingame = 0; - UINT8 roulettestop; - INT32 pdis = 0; - INT32 useodds = 0; - INT32 spawnchance[NUMKARTRESULTS]; - INT32 totalspawnchance = 0; - INT32 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->kartstuff[k_itemroulette]) - player->kartstuff[k_itemroulette]++; - else - return; - - // 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].kartstuff[k_bumper] > bestbumper) - bestbumper = players[i].kartstuff[k_bumper]; - } - - // This makes the roulette produce the random noises. - if ((player->kartstuff[k_itemroulette] % 3) == 1 && P_IsDisplayPlayer(player)) - { -#define PLAYROULETTESND S_StartSound(NULL, sfx_itrol1 + ((player->kartstuff[k_itemroulette] / 3) % 8)) - for (i = 0; i <= splitscreen; i++) - { - if (player == &players[displayplayers[i]] && players[displayplayers[i]].kartstuff[k_itemroulette]) - PLAYROULETTESND; - } -#undef PLAYROULETTESND - } - - roulettestop = TICRATE + (3*(pingame - player->kartstuff[k_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->kartstuff[k_itemroulette] >= roulettestop) - && !(player->kartstuff[k_eggmanheld] || player->kartstuff[k_itemheld] || player->kartstuff[k_userings])) - { - // Mashing reduces your chances for the good items - mashed = FixedDiv((player->kartstuff[k_itemroulette])*FRACUNIT, ((TICRATE*3)+roulettestop)*FRACUNIT) - FRACUNIT; - } - else if (!(player->kartstuff[k_itemroulette] >= (TICRATE*3))) - return; - - if (cmd->buttons & BT_ATTACK) - player->pflags |= PF_ATTACKDOWN; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (players[i].kartstuff[k_position] == 1) - { - // This player is first! Yay! - pdis = player->distancetofinish - players[i].distancetofinish; - break; - } - } - - if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items - pdis = (15 * pdis) / 14; - - if (spbplace != -1 && player->kartstuff[k_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 = ((28 + (8-pingame)) * pdis) / 28; // scale with player count - - // SPECIAL CASE No. 1: - // Fake Eggman items - if (player->kartstuff[k_roulettetype] == 2) - { - player->kartstuff[k_eggmanexplode] = 4*TICRATE; - //player->karthud[khud_itemblink] = TICRATE; - //player->karthud[khud_itemblinkmode] = 1; - player->kartstuff[k_itemroulette] = 0; - player->kartstuff[k_roulettetype] = 0; - 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->kartstuff[k_itemamount] = cv_kartdebugamount.value; - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = 2; - player->kartstuff[k_itemroulette] = 0; - player->kartstuff[k_roulettetype] = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_dbgsal); - return; - } - - // SPECIAL CASE No. 3: - // Record Attack / alone mashing behavior - if (modeattacking || pingame == 1) - { - if (G_RaceGametype()) - { - if (mashed && (modeattacking || cv_superring.value)) // ANY mashed value? You get rings. - { - K_KartGetItemResult(player, KITEM_SUPERRING); - player->karthud[khud_itemblinkmode] = 1; - 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->karthud[khud_itemblinkmode] = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - } - } - else if (G_BattleGametype()) - { - if (mashed && (modeattacking || cv_banana.value)) // ANY mashed value? You get a banana. - { - K_KartGetItemResult(player, KITEM_BANANA); - player->karthud[khud_itemblinkmode] = 1; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolm); - } - else - { - if (modeattacking || cv_orbinaut.value) // Waited patiently? You get an orbinaut! - K_KartGetItemResult(player, KITEM_ORBINAUT); - else // Default to sad if nothing's enabled... - K_KartGetItemResult(player, KITEM_SAD); - player->karthud[khud_itemblinkmode] = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, sfx_itrolf); - } - } - - player->karthud[khud_itemblink] = TICRATE; - player->kartstuff[k_itemroulette] = 0; - player->kartstuff[k_roulettetype] = 0; - return; - } - - // SPECIAL CASE No. 4: - // Being in ring debt occasionally forces Super Ring on you if you mashed - if (mashed && player->kartstuff[k_rings] < 0 && cv_superring.value) - { - INT32 debtamount = min(20, abs(player->kartstuff[k_rings])); - if (P_RandomChance((debtamount*FRACUNIT)/20)) - { - K_KartGetItemResult(player, KITEM_SUPERRING); - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = 1; - player->kartstuff[k_itemroulette] = 0; - player->kartstuff[k_roulettetype] = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, (mashed ? sfx_itrolm : sfx_itrolf)); - return; - } - } - - // SPECIAL CASE No. 5: - // Force SPB onto 2nd if they get too far behind - if (player->kartstuff[k_position] == 2 && pdis > (DISTVAR*6) - && spbplace == -1 && !indirectitemcooldown && !dontforcespb - && cv_selfpropelledbomb.value) - { - K_KartGetItemResult(player, KITEM_SPB); - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = (mashed ? 1 : 0); - player->kartstuff[k_itemroulette] = 0; - player->kartstuff[k_roulettetype] = 0; - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, (mashed ? sfx_itrolm : sfx_itrolf)); - 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, mashed, spbrush)); - - // 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->kartstuff[k_itemtype] = KITEM_SAD; - player->kartstuff[k_itemamount] = 1; - } - - if (P_IsDisplayPlayer(player)) - S_StartSound(NULL, ((player->kartstuff[k_roulettetype] == 1) ? sfx_itrolk : (mashed ? sfx_itrolm : sfx_itrolf))); - - player->karthud[khud_itemblink] = TICRATE; - player->karthud[khud_itemblinkmode] = ((player->kartstuff[k_roulettetype] == 1) ? 2 : (mashed ? 1 : 0)); - - player->kartstuff[k_itemroulette] = 0; // Since we're done, clear the roulette number - player->kartstuff[k_roulettetype] = 0; // This too -} - -//} - -//{ SRB2kart p_user.c Stuff - -static fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against) -{ - fixed_t weight = 5<type) - { - case MT_PLAYER: - if (!mobj->player) - break; - if (against->player && !against->player->kartstuff[k_spinouttimer] && mobj->player->kartstuff[k_spinouttimer]) - weight = 0; // Do not bump - else - { - weight = (mobj->player->kartweight)<player->speed > K_GetKartSpeed(mobj->player, false)) - weight += (mobj->player->speed - K_GetKartSpeed(mobj->player, false))/8; - } - break; - case MT_FALLINGROCK: - if (against->player) - { - if (against->player->kartstuff[k_invincibilitytimer] - || against->player->kartstuff[k_growshrinktimer] > 0) - weight = 0; - else - weight = (against->player->kartweight)<player) - weight = (against->player->kartweight)<player) - weight = (against->player->kartweight+3)<health <= 0) - return; - - if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0 - || player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0 - || (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1))) - return; - - player->kartstuff[k_ringboost] = 0; - player->kartstuff[k_driftboost] = 0; - player->kartstuff[k_drift] = 0; - player->kartstuff[k_driftcharge] = 0; - player->kartstuff[k_pogospring] = 0; - - player->kartstuff[k_spinouttype] = 2; - player->kartstuff[k_spinouttimer] = length; - player->kartstuff[k_wipeoutslow] = min(length-1, wipeoutslowtime+1); - - if (player->mo->state != &states[S_KART_SPIN]) - P_SetPlayerMobjState(player->mo, S_KART_SPIN); - - K_DropHnextList(player); - return; -} - -void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) -{ - mobj_t *fx; - fixed_t momdifx, momdify; - fixed_t distx, disty; - fixed_t dot, force; - fixed_t mass1, mass2; - - if (!mobj1 || !mobj2) - return; - - // Don't bump when you're being reborn - if ((mobj1->player && mobj1->player->playerstate != PST_LIVE) - || (mobj2->player && mobj2->player->playerstate != PST_LIVE)) - return; - - if ((mobj1->player && mobj1->player->kartstuff[k_respawn]) - || (mobj2->player && mobj2->player->kartstuff[k_respawn])) - return; - - { // Don't bump if you're flashing - INT32 flash; - - flash = K_GetKartFlashing(mobj1->player); - if (mobj1->player && mobj1->player->powers[pw_flashing] > 0 && mobj1->player->powers[pw_flashing] < flash) - { - if (mobj1->player->powers[pw_flashing] < flash-1) - mobj1->player->powers[pw_flashing]++; - return; - } - - flash = K_GetKartFlashing(mobj2->player); - if (mobj2->player && mobj2->player->powers[pw_flashing] > 0 && mobj2->player->powers[pw_flashing] < flash) - { - if (mobj2->player->powers[pw_flashing] < flash-1) - mobj2->player->powers[pw_flashing]++; - return; - } - } - - // Don't bump if you've recently bumped - if (mobj1->player && mobj1->player->kartstuff[k_justbumped]) - { - mobj1->player->kartstuff[k_justbumped] = bumptime; - return; - } - - if (mobj2->player && mobj2->player->kartstuff[k_justbumped]) - { - mobj2->player->kartstuff[k_justbumped] = bumptime; - return; - } - - mass1 = K_GetMobjWeight(mobj1, mobj2); - - if (solid == true && mass1 > 0) - mass2 = mass1; - else - mass2 = K_GetMobjWeight(mobj2, mobj1); - - momdifx = mobj1->momx - mobj2->momx; - momdify = mobj1->momy - mobj2->momy; - - // Adds the OTHER player's momentum times a bunch, for the best chance of getting the correct direction - distx = (mobj1->x + mobj2->momx*3) - (mobj2->x + mobj1->momx*3); - disty = (mobj1->y + mobj2->momy*3) - (mobj2->y + mobj1->momy*3); - - if (distx == 0 && disty == 0) - // if there's no distance between the 2, they're directly on top of each other, don't run this - return; - - { // Normalize distance to the sum of the two objects' radii, since in a perfect world that would be the distance at the point of collision... - fixed_t dist = P_AproxDistance(distx, disty); - fixed_t nx = FixedDiv(distx, dist); - fixed_t ny = FixedDiv(disty, dist); - - dist = dist ? dist : 1; - distx = FixedMul(mobj1->radius+mobj2->radius, nx); - disty = FixedMul(mobj1->radius+mobj2->radius, ny); - - if (momdifx == 0 && momdify == 0) - { - // If there's no momentum difference, they're moving at exactly the same rate. Pretend they moved into each other. - momdifx = -nx; - momdify = -ny; - } - } - - // if the speed difference is less than this let's assume they're going proportionately faster from each other - if (P_AproxDistance(momdifx, momdify) < (25*mapobjectscale)) - { - fixed_t momdiflength = P_AproxDistance(momdifx, momdify); - fixed_t normalisedx = FixedDiv(momdifx, momdiflength); - fixed_t normalisedy = FixedDiv(momdify, momdiflength); - momdifx = FixedMul((25*mapobjectscale), normalisedx); - momdify = FixedMul((25*mapobjectscale), normalisedy); - } - - dot = FixedMul(momdifx, distx) + FixedMul(momdify, disty); - - if (dot >= 0) - { - // They're moving away from each other - return; - } - - force = FixedDiv(dot, FixedMul(distx, distx)+FixedMul(disty, disty)); - - if (bounce == true && mass2 > 0) // Perform a Goomba Bounce. - mobj1->momz = -mobj1->momz; - else - { - fixed_t newz = mobj1->momz; - if (mass2 > 0) - mobj1->momz = mobj2->momz; - if (mass1 > 0 && solid == false) - mobj2->momz = newz; - } - - if (mass2 > 0) - { - mobj1->momx = mobj1->momx - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), force), distx); - mobj1->momy = mobj1->momy - FixedMul(FixedMul(FixedDiv(2*mass2, mass1 + mass2), force), disty); - } - - if (mass1 > 0 && solid == false) - { - mobj2->momx = mobj2->momx - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), force), -distx); - mobj2->momy = mobj2->momy - FixedMul(FixedMul(FixedDiv(2*mass1, mass1 + mass2), force), -disty); - } - - // Do the bump fx when we've CONFIRMED we can bump. - S_StartSound(mobj1, sfx_s3k49); - - fx = P_SpawnMobj(mobj1->x/2 + mobj2->x/2, mobj1->y/2 + mobj2->y/2, mobj1->z/2 + mobj2->z/2, MT_BUMP); - if (mobj1->eflags & MFE_VERTICALFLIP) - fx->eflags |= MFE_VERTICALFLIP; - else - fx->eflags &= ~MFE_VERTICALFLIP; - P_SetScale(fx, mobj1->scale); - - // Because this is done during collision now, rmomx and rmomy need to be recalculated - // so that friction doesn't immediately decide to stop the player if they're at a standstill - // Also set justbumped here - if (mobj1->player) - { - mobj1->player->rmomx = mobj1->momx - mobj1->player->cmomx; - mobj1->player->rmomy = mobj1->momy - mobj1->player->cmomy; - mobj1->player->kartstuff[k_justbumped] = bumptime; - - if (mobj1->player->kartstuff[k_spinouttimer]) - { - mobj1->player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1; - mobj1->player->kartstuff[k_spinouttimer] = max(wipeoutslowtime+1, mobj1->player->kartstuff[k_spinouttimer]); - //mobj1->player->kartstuff[k_spinouttype] = 1; // Enforce type - } - else if (mobj2->player) // Player VS player bumping only - { - if (mobj1->player->kartstuff[k_rings] <= 0) - { - K_DebtStingPlayer(mobj1->player, TICRATE + (4 * (mobj2->player->kartweight - mobj1->player->kartweight))); - K_KartPainEnergyFling(mobj1->player); - P_PlayRinglossSound(mobj1); - } - P_PlayerRingBurst(mobj1->player, 1); - } - } - - if (mobj2->player) - { - mobj2->player->rmomx = mobj2->momx - mobj2->player->cmomx; - mobj2->player->rmomy = mobj2->momy - mobj2->player->cmomy; - mobj2->player->kartstuff[k_justbumped] = bumptime; - - if (mobj2->player->kartstuff[k_spinouttimer]) - { - mobj2->player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1; - mobj2->player->kartstuff[k_spinouttimer] = max(wipeoutslowtime+1, mobj2->player->kartstuff[k_spinouttimer]); - //mobj2->player->kartstuff[k_spinouttype] = 1; // Enforce type - } - else if (mobj1->player) // Player VS player bumping only - { - if (mobj2->player->kartstuff[k_rings] <= 0) - { - K_DebtStingPlayer(mobj2->player, TICRATE + (4 * (mobj1->player->kartweight - mobj2->player->kartweight))); - K_KartPainEnergyFling(mobj2->player); - P_PlayRinglossSound(mobj2); - } - P_PlayerRingBurst(mobj2->player, 1); - } - } -} - -/** \brief Checks that the player is on an offroad subsector for realsies - - \param mo player mobj object - - \return boolean -*/ -static UINT8 K_CheckOffroadCollide(mobj_t *mo) -{ - UINT8 i; - - I_Assert(mo != NULL); - I_Assert(!P_MobjWasRemoved(mo)); - - for (i = 2; i < 5; i++) - { - if (P_MobjTouchingSectorSpecial(mo, 1, i, true)) - return i-1; - } - - return 0; -} - -/** \brief Updates the Player's offroad value once per frame - - \param player player object passed from K_KartPlayerThink - - \return void -*/ -static void K_UpdateOffroad(player_t *player) -{ - fixed_t offroadstrength = (K_CheckOffroadCollide(player->mo) << FRACBITS); - - // If you are in offroad, a timer starts. - if (offroadstrength) - { - if (player->kartstuff[k_offroad] < offroadstrength) - player->kartstuff[k_offroad] += offroadstrength / TICRATE; - - if (player->kartstuff[k_offroad] > offroadstrength) - player->kartstuff[k_offroad] = offroadstrength; - } - else - player->kartstuff[k_offroad] = 0; -} - -static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t curdist, fixed_t maxdist, boolean transparent) -{ -#define CHAOTIXBANDLEN 15 -#define CHAOTIXBANDCOLORS 9 - static const UINT8 colors[CHAOTIXBANDCOLORS] = { - SKINCOLOR_SAPPHIRE, - SKINCOLOR_PLATINUM, - SKINCOLOR_TEA, - SKINCOLOR_GARDEN, - SKINCOLOR_BANANA, - SKINCOLOR_GOLD, - SKINCOLOR_ORANGE, - SKINCOLOR_SCARLET, - SKINCOLOR_TAFFY - }; - fixed_t minimumdist = FixedMul(RING_DIST>>1, player->mo->scale); - UINT8 c = FixedMul(CHAOTIXBANDCOLORS<> FRACBITS; - UINT8 n = CHAOTIXBANDLEN; - UINT8 offset = ((leveltime / 3) % 3); - fixed_t stepx, stepy, stepz; - fixed_t curx, cury, curz; - - stepx = (victim->mo->x - player->mo->x) / CHAOTIXBANDLEN; - stepy = (victim->mo->y - player->mo->y) / CHAOTIXBANDLEN; - stepz = ((victim->mo->z + (victim->mo->height / 2)) - (player->mo->z + (player->mo->height / 2))) / CHAOTIXBANDLEN; - - curx = player->mo->x + stepx; - cury = player->mo->y + stepy; - curz = player->mo->z + stepz; - - while (n) - { - if (offset == 0) - { - mobj_t *band = P_SpawnMobj(curx + (P_RandomRange(-12,12)*mapobjectscale), - cury + (P_RandomRange(-12,12)*mapobjectscale), - curz + (P_RandomRange(24,48)*mapobjectscale), - MT_SIGNSPARKLE); - P_SetMobjState(band, S_SIGNSPARK1 + (leveltime % 11)); - P_SetScale(band, (band->destscale = (3*player->mo->scale)/2)); - band->color = colors[c]; - band->colorized = true; - band->fuse = 2; - if (transparent) - band->flags2 |= MF2_SHADOW; - if (!P_IsDisplayPlayer(player) && !P_IsDisplayPlayer(victim)) - band->flags2 |= MF2_DONTDRAW; - } - - curx += stepx; - cury += stepy; - curz += stepz; - - offset = abs(offset-1) % 3; - n--; - } -#undef CHAOTIXBANDLEN -} - -/** \brief Updates the player's drafting values once per frame - - \param player player object passed from K_KartPlayerThink - - \return void -*/ -static void K_UpdateDraft(player_t *player) -{ - fixed_t topspd = K_GetKartSpeed(player, false); - fixed_t draftdistance; - UINT8 leniency; - UINT8 i; - - // Distance you have to be to draft. If you're still accelerating, then this distance is lessened. - // This distance biases toward low weight! (min weight gets 4096 units, max weight gets 3072 units) - // This distance is also scaled based on game speed. - draftdistance = (3072 + (128 * (9 - player->kartweight))) * player->mo->scale; - if (player->speed < topspd) - draftdistance = FixedMul(draftdistance, FixedDiv(player->speed, topspd)); - draftdistance = FixedMul(draftdistance, K_GetKartGameSpeedScalar(gamespeed)); - - // On the contrary, the leniency period biases toward high weight. - // (See also: the leniency variable in K_SpawnDraftDust) - leniency = (3*TICRATE)/4 + ((player->kartweight-1) * (TICRATE/4)); - - // Not enough speed to draft. - if (player->speed >= 20*player->mo->scale) - { -//#define EASYDRAFTTEST - // Let's hunt for players to draft off of! - for (i = 0; i < MAXPLAYERS; i++) - { - fixed_t dist, olddraft; -#ifndef EASYDRAFTTEST - angle_t yourangle, theirangle, diff; -#endif - - if (!playeringame[i] || players[i].spectator || !players[i].mo) - continue; - -#ifndef EASYDRAFTTEST - // Don't draft on yourself :V - if (&players[i] == player) - continue; -#endif - - // Not enough speed to draft off of. - if (players[i].speed < 20*players[i].mo->scale) - continue; - -#ifndef EASYDRAFTTEST - yourangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); - theirangle = R_PointToAngle2(0, 0, players[i].mo->momx, players[i].mo->momy); - - diff = R_PointToAngle2(player->mo->x, player->mo->y, players[i].mo->x, players[i].mo->y) - yourangle; - if (diff > ANGLE_180) - diff = InvAngle(diff); - - // Not in front of this player. - if (diff > ANG10) - continue; - - diff = yourangle - theirangle; - if (diff > ANGLE_180) - diff = InvAngle(diff); - - // Not moving in the same direction. - if (diff > ANGLE_90) - continue; -#endif - - dist = P_AproxDistance(P_AproxDistance(players[i].mo->x - player->mo->x, players[i].mo->y - player->mo->y), players[i].mo->z - player->mo->z); - -#ifndef EASYDRAFTTEST - // TOO close to draft. - if (dist < FixedMul(RING_DIST>>1, player->mo->scale)) - continue; - - // Not close enough to draft. - if (dist > draftdistance) - continue; -#endif - - olddraft = player->kartstuff[k_draftpower]; - - player->kartstuff[k_draftleeway] = leniency; - player->kartstuff[k_lastdraft] = i; - - // Draft power is used later in K_GetKartBoostPower, ranging from 0 for normal speed and FRACUNIT for max draft speed. - // How much this increments every tic biases toward acceleration! (min speed gets 1.5% per tic, max speed gets 0.5% per tic) - if (player->kartstuff[k_draftpower] < FRACUNIT) - player->kartstuff[k_draftpower] += (FRACUNIT/200) + ((9 - player->kartspeed) * ((3*FRACUNIT)/1600)); - - if (player->kartstuff[k_draftpower] > FRACUNIT) - player->kartstuff[k_draftpower] = FRACUNIT; - - // Play draft finish noise - if (olddraft < FRACUNIT && player->kartstuff[k_draftpower] >= FRACUNIT) - S_StartSound(player->mo, sfx_cdfm62); - - // Spawn in the visual! - K_DrawDraftCombiring(player, &players[i], dist, draftdistance, false); - - return; // Finished doing our draft. - } - } - - // No one to draft off of? Then you can knock that off. - if (player->kartstuff[k_draftleeway]) // Prevent small disruptions from stopping your draft. - { - player->kartstuff[k_draftleeway]--; - if (player->kartstuff[k_lastdraft] >= 0 - && player->kartstuff[k_lastdraft] < MAXPLAYERS - && playeringame[player->kartstuff[k_lastdraft]] - && !players[player->kartstuff[k_lastdraft]].spectator - && players[player->kartstuff[k_lastdraft]].mo) - { - player_t *victim = &players[player->kartstuff[k_lastdraft]]; - fixed_t dist = P_AproxDistance(P_AproxDistance(victim->mo->x - player->mo->x, victim->mo->y - player->mo->y), victim->mo->z - player->mo->z); - K_DrawDraftCombiring(player, victim, dist, draftdistance, true); - } - } - else // Remove draft speed boost. - { - player->kartstuff[k_draftpower] = 0; - player->kartstuff[k_lastdraft] = -1; - } -} - -void K_KartPainEnergyFling(player_t *player) -{ - static const UINT8 numfling = 5; - INT32 i; - mobj_t *mo; - angle_t fa; - fixed_t ns; - fixed_t z; - - // Better safe than sorry. - if (!player) - return; - - // P_PlayerRingBurst: "There's no ring spilling in kart, so I'm hijacking this for the same thing as TD" - // :oh: - - for (i = 0; i < numfling; i++) - { - INT32 objType = mobjinfo[MT_FLINGENERGY].reactiontime; - fixed_t momxy, momz; // base horizonal/vertical thrusts - - z = player->mo->z; - if (player->mo->eflags & MFE_VERTICALFLIP) - z += player->mo->height - mobjinfo[objType].height; - - mo = P_SpawnMobj(player->mo->x, player->mo->y, z, objType); - - mo->fuse = 8*TICRATE; - P_SetTarget(&mo->target, player->mo); - - mo->destscale = player->mo->scale; - P_SetScale(mo, player->mo->scale); - - // Angle offset by player angle, then slightly offset by amount of fling - fa = ((i*FINEANGLES/16) + (player->mo->angle>>ANGLETOFINESHIFT) - ((numfling-1)*FINEANGLES/32)) & FINEMASK; - - if (i > 15) - { - momxy = 3*FRACUNIT; - momz = 4*FRACUNIT; - } - else - { - momxy = 28*FRACUNIT; - momz = 3*FRACUNIT; - } - - ns = FixedMul(momxy, mo->scale); - mo->momx = FixedMul(FINECOSINE(fa),ns); - - ns = momz; - P_SetObjectMomZ(mo, ns, false); - - if (i & 1) - P_SetObjectMomZ(mo, ns, true); - - if (player->mo->eflags & MFE_VERTICALFLIP) - mo->momz *= -1; - } -} - -// Adds gravity flipping to an object relative to its master and shifts the z coordinate accordingly. -void K_FlipFromObject(mobj_t *mo, mobj_t *master) -{ - mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP)|(master->eflags & MFE_VERTICALFLIP); - mo->flags2 = (mo->flags2 & ~MF2_OBJECTFLIP)|(master->flags2 & MF2_OBJECTFLIP); - - if (mo->eflags & MFE_VERTICALFLIP) - mo->z += master->height - FixedMul(master->scale, mo->height); -} - -// These have to go earlier than its sisters because of K_RespawnChecker... -void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master) -{ - // flipping - // handle z shifting from there too. This is here since there's no reason not to flip us if needed when we do this anyway; - K_FlipFromObject(mo, master); - - // visibility (usually for hyudoro) - mo->flags2 = (mo->flags2 & ~MF2_DONTDRAW)|(master->flags2 & MF2_DONTDRAW); - mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP1)|(master->eflags & MFE_DRAWONLYFORP1); - mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP2)|(master->eflags & MFE_DRAWONLYFORP2); - mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP3)|(master->eflags & MFE_DRAWONLYFORP3); - mo->eflags = (mo->eflags & ~MFE_DRAWONLYFORP4)|(master->eflags & MFE_DRAWONLYFORP4); -} - -static void K_SpawnDashDustRelease(player_t *player) -{ - fixed_t newx; - fixed_t newy; - mobj_t *dust; - angle_t travelangle; - INT32 i; - - I_Assert(player != NULL); - I_Assert(player->mo != NULL); - I_Assert(!P_MobjWasRemoved(player->mo)); - - if (!P_IsObjectOnGround(player->mo)) - return; - - if (!player->speed && !player->kartstuff[k_startboost]) - return; - - travelangle = player->mo->angle; - - if (player->kartstuff[k_drift] || player->kartstuff[k_driftend]) - travelangle -= (ANGLE_45/5)*player->kartstuff[k_drift]; - - for (i = 0; i < 2; i++) - { - newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_90, FixedMul(48*FRACUNIT, player->mo->scale)); - newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_90, FixedMul(48*FRACUNIT, player->mo->scale)); - dust = P_SpawnMobj(newx, newy, player->mo->z, MT_FASTDUST); - - P_SetTarget(&dust->target, player->mo); - dust->angle = travelangle - ((i&1) ? -1 : 1)*ANGLE_45; - dust->destscale = player->mo->scale; - P_SetScale(dust, player->mo->scale); - - dust->momx = 3*player->mo->momx/5; - dust->momy = 3*player->mo->momy/5; - //dust->momz = 3*player->mo->momz/5; - - K_MatchGenericExtraFlags(dust, player->mo); - } -} - -static void K_SpawnBrakeDriftSparks(player_t *player) // Be sure to update the mobj thinker case too! -{ - mobj_t *sparks; - - I_Assert(player != NULL); - I_Assert(player->mo != NULL); - I_Assert(!P_MobjWasRemoved(player->mo)); - - // Position & etc are handled in its thinker, and its spawned invisible. - // This avoids needing to dupe code if we don't need it. - sparks = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BRAKEDRIFT); - P_SetTarget(&sparks->target, player->mo); - P_SetScale(sparks, (sparks->destscale = player->mo->scale)); - K_MatchGenericExtraFlags(sparks, player->mo); - sparks->flags2 |= MF2_DONTDRAW; -} - -/** \brief Calculates the respawn timer and drop-boosting - - \param player player object passed from K_KartPlayerThink - - \return void -*/ -void K_RespawnChecker(player_t *player) -{ - ticcmd_t *cmd = &player->cmd; - - if (player->spectator) - return; - - if (player->kartstuff[k_respawn] > 1) - { - player->kartstuff[k_respawn]--; - player->mo->momz = 0; - player->powers[pw_flashing] = 2; - player->powers[pw_nocontrol] = 2; - if (leveltime % 8 == 0) - { - INT32 i; - if (!mapreset) - S_StartSound(player->mo, sfx_s3kcas); - - for (i = 0; i < 8; i++) - { - mobj_t *mo; - angle_t newangle; - fixed_t newx, newy, newz; - - newangle = FixedAngle(((360/8)*i)*FRACUNIT); - newx = player->mo->x + P_ReturnThrustX(player->mo, newangle, 31<mo->y + P_ReturnThrustY(player->mo, newangle, 31<mo->eflags & MFE_VERTICALFLIP) - newz = player->mo->z + player->mo->height; - else - newz = player->mo->z; - - mo = P_SpawnMobj(newx, newy, newz, MT_DEZLASER); - if (mo) - { - if (player->mo->eflags & MFE_VERTICALFLIP) - mo->eflags |= MFE_VERTICALFLIP; - P_SetTarget(&mo->target, player->mo); - mo->angle = newangle+ANGLE_90; - mo->momz = (8<mo); - P_SetScale(mo, (mo->destscale = FRACUNIT)); - } - } - } - } - else if (player->kartstuff[k_respawn] == 1) - { - if (player->kartstuff[k_growshrinktimer] < 0) - { - player->mo->scalespeed = mapobjectscale/TICRATE; - player->mo->destscale = (6*mapobjectscale)/8; - if (cv_kartdebugshrink.value && !modeattacking && !player->bot) - player->mo->destscale = (6*player->mo->destscale)/8; - } - - if (!P_IsObjectOnGround(player->mo) && !mapreset) - { - player->powers[pw_flashing] = K_GetKartFlashing(player); - - // Sal: The old behavior was stupid and prone to accidental usage. - // Let's rip off Mania instead, and turn this into a Drop Dash! - - if (cmd->buttons & BT_ACCELERATE) - player->kartstuff[k_dropdash]++; - else - player->kartstuff[k_dropdash] = 0; - - if (player->kartstuff[k_dropdash] == TICRATE/4) - S_StartSound(player->mo, sfx_ddash); - - if ((player->kartstuff[k_dropdash] >= TICRATE/4) - && (player->kartstuff[k_dropdash] & 1)) - player->mo->colorized = true; - else - player->mo->colorized = false; - } - else - { - if ((cmd->buttons & BT_ACCELERATE) && (player->kartstuff[k_dropdash] >= TICRATE/4)) - { - S_StartSound(player->mo, sfx_s23c); - player->kartstuff[k_startboost] = 50; - K_SpawnDashDustRelease(player); - } - player->mo->colorized = false; - player->kartstuff[k_dropdash] = 0; + + player->mo->colorized = false; + player->kartstuff[k_dropdash] = 0; player->kartstuff[k_respawn] = 0; - //P_PlayRinglossSound(player->mo); + + //P_PlayRinglossSound(player->mo); P_PlayerRingBurst(player, 3); - } - } -} -/** \brief Handles the state changing for moving players, moved here to eliminate duplicate code - - \param player player data - - \return void -*/ -void K_KartMoveAnimation(player_t *player) -{ - ticcmd_t *cmd = &player->cmd; - // Standing frames - S_KART_STND1 S_KART_STND1_L S_KART_STND1_R - if (player->speed == 0) - { - if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_STND1_R] && player->mo->state <= &states[S_KART_STND2_R])) - P_SetPlayerMobjState(player->mo, S_KART_STND1_R); - else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_STND1_L] && player->mo->state <= &states[S_KART_STND2_L])) - P_SetPlayerMobjState(player->mo, S_KART_STND1_L); - else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_STND1] && player->mo->state <= &states[S_KART_STND2])) - P_SetPlayerMobjState(player->mo, S_KART_STND1); - } - // Drifting Left - S_KART_DRIFT1_L - else if (player->kartstuff[k_drift] > 0 && P_IsObjectOnGround(player->mo)) - { - if (!(player->mo->state >= &states[S_KART_DRIFT1_L] && player->mo->state <= &states[S_KART_DRIFT2_L])) - P_SetPlayerMobjState(player->mo, S_KART_DRIFT1_L); - } - // Drifting Right - S_KART_DRIFT1_R - else if (player->kartstuff[k_drift] < 0 && P_IsObjectOnGround(player->mo)) - { - if (!(player->mo->state >= &states[S_KART_DRIFT1_R] && player->mo->state <= &states[S_KART_DRIFT2_R])) - P_SetPlayerMobjState(player->mo, S_KART_DRIFT1_R); - } - // Run frames - S_KART_RUN1 S_KART_RUN1_L S_KART_RUN1_R - else if (player->speed > (20*player->mo->scale)) - { - if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_RUN1_R] && player->mo->state <= &states[S_KART_RUN2_R])) - P_SetPlayerMobjState(player->mo, S_KART_RUN1_R); - else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_RUN1_L] && player->mo->state <= &states[S_KART_RUN2_L])) - P_SetPlayerMobjState(player->mo, S_KART_RUN1_L); - else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_RUN1] && player->mo->state <= &states[S_KART_RUN2])) - P_SetPlayerMobjState(player->mo, S_KART_RUN1); - } - // Walk frames - S_KART_WALK1 S_KART_WALK1_L S_KART_WALK1_R - else if (player->speed <= (20*player->mo->scale)) - { - if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_WALK1_R] && player->mo->state <= &states[S_KART_WALK2_R])) - P_SetPlayerMobjState(player->mo, S_KART_WALK1_R); - else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_WALK1_L] && player->mo->state <= &states[S_KART_WALK2_L])) - P_SetPlayerMobjState(player->mo, S_KART_WALK1_L); - else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_WALK1] && player->mo->state <= &states[S_KART_WALK2])) - P_SetPlayerMobjState(player->mo, S_KART_WALK1); - } -} - -static void K_TauntVoiceTimers(player_t *player) -{ - if (!player) - return; - - player->karthud[khud_tauntvoices] = 6*TICRATE; - player->karthud[khud_voices] = 4*TICRATE; -} - -static void K_RegularVoiceTimers(player_t *player) -{ - if (!player) - return; - - player->karthud[khud_voices] = 4*TICRATE; - - if (player->karthud[khud_tauntvoices] < 4*TICRATE) - player->karthud[khud_tauntvoices] = 4*TICRATE; -} - -void K_PlayAttackTaunt(mobj_t *source) -{ - sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons - boolean tasteful = (!source->player || !source->player->karthud[khud_tauntvoices]); - - if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2)) - S_StartSound(source, sfx_kattk1+pick); - - if (!tasteful) - return; - - K_TauntVoiceTimers(source->player); -} - -void K_PlayBoostTaunt(mobj_t *source) -{ - sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons - boolean tasteful = (!source->player || !source->player->karthud[khud_tauntvoices]); - - if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2)) - S_StartSound(source, sfx_kbost1+pick); - - if (!tasteful) - return; - - K_TauntVoiceTimers(source->player); -} - -void K_PlayOvertakeSound(mobj_t *source) -{ - boolean tasteful = (!source->player || !source->player->karthud[khud_voices]); - - if (!G_RaceGametype()) // Only in race - return; - - // 4 seconds from before race begins, 10 seconds afterwards - if (leveltime < starttime+(10*TICRATE)) - return; - - if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2)) - S_StartSound(source, sfx_kslow); - - if (!tasteful) - return; - - K_RegularVoiceTimers(source->player); -} - -void K_PlayPainSound(mobj_t *source) -{ - sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons - - if (cv_kartvoices.value) - S_StartSound(source, sfx_khurt1 + pick); - - K_RegularVoiceTimers(source->player); -} - -void K_PlayHitEmSound(mobj_t *source) -{ - if (cv_kartvoices.value) - S_StartSound(source, sfx_khitem); - else - S_StartSound(source, sfx_s1c9); // The only lost gameplay functionality with voices disabled - - K_RegularVoiceTimers(source->player); -} - -void K_PlayPowerGloatSound(mobj_t *source) -{ - if (cv_kartvoices.value) - S_StartSound(source, sfx_kgloat); - - K_RegularVoiceTimers(source->player); -} - -void K_MomentumToFacing(player_t *player) -{ - angle_t dangle = player->mo->angle - R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); - - if (dangle > ANGLE_180) - dangle = InvAngle(dangle); - - // If you aren't on the ground or are moving in too different of a direction don't do this - if (player->mo->eflags & MFE_JUSTHITFLOOR) - ; // Just hit floor ALWAYS redirects - else if (!P_IsObjectOnGround(player->mo) || dangle > ANGLE_90) - return; - - P_Thrust(player->mo, player->mo->angle, player->speed - FixedMul(player->speed, player->mo->friction)); - player->mo->momx = FixedMul(player->mo->momx - player->cmomx, player->mo->friction) + player->cmomx; - player->mo->momy = FixedMul(player->mo->momy - player->cmomy, player->mo->friction) + player->cmomy; -} - -// sets k_boostpower, k_speedboost, and k_accelboost to whatever we need it to be -static void K_GetKartBoostPower(player_t *player) -{ - fixed_t boostpower = FRACUNIT; - fixed_t speedboost = 0, accelboost = 0; - UINT8 numboosts = 0; - - if (player->kartstuff[k_spinouttimer] && player->kartstuff[k_wipeoutslow] == 1) // Slow down after you've been bumped - { - player->kartstuff[k_boostpower] = player->kartstuff[k_speedboost] = player->kartstuff[k_accelboost] = 0; - return; - } - - // Offroad is separate, it's difficult to factor it in with a variable value anyway. - if (!(player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_hyudorotimer] || EITHERSNEAKER(player)) - && player->kartstuff[k_offroad] >= 0) - boostpower = FixedDiv(boostpower, FixedMul(player->kartstuff[k_offroad], K_GetKartGameSpeedScalar(gamespeed)) + FRACUNIT); - - if (player->kartstuff[k_bananadrag] > TICRATE) - boostpower = (4*boostpower)/5; - -#define ADDBOOST(s,a) { \ - numboosts++; \ - speedboost += (s) / numboosts; \ - accelboost += (a) / numboosts; \ -} - - if (player->kartstuff[k_levelbooster]) // Level boosters - ADDBOOST(FRACUNIT/2, 8*FRACUNIT); // + 50% top speed, + 800% acceleration - - if (player->kartstuff[k_sneakertimer]) // Sneaker - ADDBOOST(FRACUNIT/2, 8*FRACUNIT); // + 50% top speed, + 800% acceleration - - if (player->kartstuff[k_invincibilitytimer]) // Invincibility - ADDBOOST((3*FRACUNIT)/8, 3*FRACUNIT); // + 37.5% top speed, + 300% acceleration - - if (player->kartstuff[k_startboost]) // Startup Boost - ADDBOOST(FRACUNIT/4, 6*FRACUNIT); // + 25% top speed, + 600% acceleration - - if (player->kartstuff[k_driftboost]) // Drift Boost - ADDBOOST(FRACUNIT/4, 4*FRACUNIT); // + 25% top speed, + 400% acceleration - - if (player->kartstuff[k_ringboost]) // Ring Boost - ADDBOOST(FRACUNIT/5, 4*FRACUNIT); // + 20% top speed, + 400% acceleration - - if (player->kartstuff[k_growshrinktimer] > 0) // Grow - { - // Grow's design is weird with booster stacking. - // We'll see how to replace its design BEFORE v2 gets released. - speedboost += (FRACUNIT/5); // + 20% - //numboosts++; // Don't add any boost afterimages to Grow - } - - if (player->kartstuff[k_draftpower] > 0) // Drafting - { - fixed_t draftspeed = ((3*FRACUNIT)/10) + ((player->kartspeed-1) * (FRACUNIT/50)); // min is 30%, max is 46% - speedboost += FixedMul(draftspeed, player->kartstuff[k_draftpower]); // (Drafting suffers no boost stack penalty.) - numboosts++; - } - - player->kartstuff[k_boostpower] = boostpower; - - // value smoothing - if (speedboost > player->kartstuff[k_speedboost]) - player->kartstuff[k_speedboost] = speedboost; - else - player->kartstuff[k_speedboost] += (speedboost - player->kartstuff[k_speedboost]) / (TICRATE/2); - - player->kartstuff[k_accelboost] = accelboost; - player->kartstuff[k_numboosts] = numboosts; -} - -fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower) -{ - const fixed_t xspd = (3*FRACUNIT)/64; - fixed_t g_cc = K_GetKartGameSpeedScalar(gamespeed) + xspd; - fixed_t k_speed = 150; - UINT8 kartspeed = player->kartspeed; - fixed_t finalspeed; - - if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) - kartspeed = 1; - - k_speed += kartspeed*3; // 153 - 177 - - finalspeed = FixedMul(k_speed<<14, g_cc); - - if (player->mo && !P_MobjWasRemoved(player->mo)) - finalspeed = FixedMul(finalspeed, player->mo->scale); - - if (doboostpower) - return FixedMul(finalspeed, player->kartstuff[k_boostpower]+player->kartstuff[k_speedboost]); - return finalspeed; -} - -fixed_t K_GetKartAccel(player_t *player) -{ - fixed_t k_accel = 32; // 36; - UINT8 kartspeed = player->kartspeed; - - if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) - kartspeed = 1; - - //k_accel += 3 * (9 - kartspeed); // 36 - 60 - k_accel += 4 * (9 - kartspeed); // 32 - 64 - - return FixedMul(k_accel, FRACUNIT+player->kartstuff[k_accelboost]); -} - -UINT16 K_GetKartFlashing(player_t *player) -{ - UINT16 tics = flashingtics; - - if (!player) - return tics; - - if (G_BattleGametype()) - tics *= 2; - - tics += (flashingtics/8) * (player->kartspeed); - - return tics; -} - -fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove) -{ - fixed_t accelmax = 4000; - fixed_t newspeed, oldspeed, finalspeed; - fixed_t p_speed = K_GetKartSpeed(player, true); - fixed_t p_accel = K_GetKartAccel(player); - - if (!onground) return 0; // If the player isn't on the ground, there is no change in speed - - // ACCELCODE!!!1!11! - oldspeed = R_PointToDist2(0, 0, player->rmomx, player->rmomy); // FixedMul(P_AproxDistance(player->rmomx, player->rmomy), player->mo->scale); - // Don't calculate the acceleration as ever being above top speed - if (oldspeed > p_speed) - oldspeed = p_speed; - newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), ORIG_FRICTION); - - if (player->kartstuff[k_pogospring]) // Pogo Spring minimum/maximum thrust - { - const fixed_t hscale = mapobjectscale /*+ (mapobjectscale - player->mo->scale)*/; - const fixed_t minspeed = 24*hscale; - const fixed_t maxspeed = 28*hscale; - - if (newspeed > maxspeed && player->kartstuff[k_pogospring] == 2) - newspeed = maxspeed; - if (newspeed < minspeed) - newspeed = minspeed; - } - - finalspeed = newspeed - oldspeed; - - // forwardmove is: - // 50 while accelerating, - // 25 while clutching, - // 0 with no gas, and - // -25 when only braking. - - finalspeed *= forwardmove/25; - finalspeed /= 2; - - if (forwardmove < 0 && finalspeed > mapobjectscale*2) - return finalspeed/2; - else if (forwardmove < 0) - return -mapobjectscale/2; - - if (finalspeed < 0) - finalspeed = 0; - - return finalspeed; -} - -void K_DoInstashield(player_t *player) -{ - mobj_t *layera; - mobj_t *layerb; - - if (player->kartstuff[k_instashield] > 0) - return; - - player->kartstuff[k_instashield] = 15; // length of instashield animation - S_StartSound(player->mo, sfx_cdpcm9); - - layera = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDA); - P_SetTarget(&layera->target, player->mo); - - layerb = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDB); - P_SetTarget(&layerb->target, player->mo); -} - -void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount) -{ - statenum_t st; - mobj_t *pt; - - if (!source || !source->mo) - return; - - if (amount == 1) - st = S_BATTLEPOINT1A; - else if (amount == 2) - st = S_BATTLEPOINT2A; - else if (amount == 3) - st = S_BATTLEPOINT3A; - else - return; // NO STATE! - - pt = P_SpawnMobj(source->mo->x, source->mo->y, source->mo->z, MT_BATTLEPOINT); - P_SetTarget(&pt->target, source->mo); - P_SetMobjState(pt, st); - if (victim && victim->skincolor) - pt->color = victim->skincolor; - else - pt->color = source->skincolor; -} - -void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, mobj_t *inflictor, boolean trapitem) -{ - UINT8 scoremultiply = 1; - // PS: Inflictor is unused for all purposes here and is actually only ever relevant to Lua. It may be nil too. -#ifdef HAVE_BLUA - boolean force = false; // Used to check if Lua ShouldSpin should get us damaged reguardless of flashtics or heck knows what. - UINT8 shouldForce = LUAh_ShouldSpin(player, inflictor, source); - if (P_MobjWasRemoved(player->mo)) - return; // mobj was removed (in theory that shouldn't happen) - if (shouldForce == 1) - force = true; - else if (shouldForce == 2) - return; -#else - static const boolean force = false; - (void)inflictor; // in case some weirdo doesn't want Lua. -#endif - - if (!trapitem && G_BattleGametype()) - { - if (K_IsPlayerWanted(player)) - scoremultiply = 3; - else if (player->kartstuff[k_bumper] == 1) - scoremultiply = 2; - } - - if (player->health <= 0) - return; - - if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || (player->kartstuff[k_spinouttimer] > 0 && player->kartstuff[k_spinouttype] != 2) - || player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0 - || (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1))) - { - if (!force) // if shoulddamage force, we go THROUGH that. - { - K_DoInstashield(player); - return; - } - } - -#ifdef HAVE_BLUA - if (LUAh_PlayerSpin(player, inflictor, source)) // Let Lua do its thing or overwrite if it wants to. Make sure to let any possible instashield happen because we didn't get "damaged" in this case. - return; -#endif - - if (source && source != player->mo && source->player) - K_PlayHitEmSound(source); - - player->kartstuff[k_sneakertimer] = 0; - //player->kartstuff[k_levelbooster] = 0; - player->kartstuff[k_driftboost] = 0; - player->kartstuff[k_ringboost] = 0; - - player->kartstuff[k_drift] = 0; - player->kartstuff[k_driftcharge] = 0; - player->kartstuff[k_pogospring] = 0; - - if (G_BattleGametype()) - { - if (source && source->player && player != source->player) - { - P_AddPlayerScore(source->player, scoremultiply); - K_SpawnBattlePoints(source->player, player, scoremultiply); - if (!trapitem) + if (G_BattleGametype()) { - source->player->kartstuff[k_wanted] -= wantedreduce; - player->kartstuff[k_wanted] -= (wantedreduce/2); - } - } - - if (player->kartstuff[k_bumper] > 0) - { - if (player->kartstuff[k_bumper] == 1) - { - mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!! - P_SetTarget(&karmahitbox->target, player->mo); - karmahitbox->destscale = player->mo->scale; - P_SetScale(karmahitbox, player->mo->scale); - CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]); - } - player->kartstuff[k_bumper]--; - if (K_IsPlayerWanted(player)) - K_CalculateBattleWanted(); - } - - if (!player->kartstuff[k_bumper]) - { - player->kartstuff[k_comebacktimer] = comebacktime; - if (player->kartstuff[k_comebackmode] == 2) - { - mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE); - S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound); - player->kartstuff[k_comebackmode] = 0; - } - } - - K_CheckBumpers(); - } - - player->kartstuff[k_spinouttype] = type; - - if (player->kartstuff[k_spinouttype] <= 0) // type 0 is spinout, type 1 is wipeout, type 2 is no-invuln wipeout - { - // At spinout, player speed is increased to 1/4 their regular speed, moving them forward - if (player->speed < K_GetKartSpeed(player, true)/4) - P_InstaThrust(player->mo, player->mo->angle, FixedMul(K_GetKartSpeed(player, true)/4, player->mo->scale)); - S_StartSound(player->mo, sfx_slip); - } - - player->kartstuff[k_spinouttimer] = (3*TICRATE/2)+2; - player->powers[pw_flashing] = K_GetKartFlashing(player); - - P_PlayRinglossSound(player->mo); - P_PlayerRingBurst(player, 5); - K_PlayPainSound(player->mo); - - if (player->mo->state != &states[S_KART_SPIN]) - P_SetPlayerMobjState(player->mo, S_KART_SPIN); - - player->kartstuff[k_instashield] = 15; - if (cv_kartdebughuddrop.value && !modeattacking) - K_DropItems(player); - else - K_DropHnextList(player); - return; -} - -static void K_RemoveGrowShrink(player_t *player) -{ - if (player->mo && !P_MobjWasRemoved(player->mo)) - { - if (player->kartstuff[k_growshrinktimer] > 0) // Play Shrink noise - S_StartSound(player->mo, sfx_kc59); - else if (player->kartstuff[k_growshrinktimer] < 0) // Play Grow noise - S_StartSound(player->mo, sfx_kc5a); - - if (player->kartstuff[k_invincibilitytimer] == 0) - player->mo->color = player->skincolor; - - player->mo->scalespeed = mapobjectscale/TICRATE; - player->mo->destscale = mapobjectscale; - if (cv_kartdebugshrink.value && !modeattacking && !player->bot) - player->mo->destscale = (6*player->mo->destscale)/8; - } - - player->kartstuff[k_growshrinktimer] = 0; - player->kartstuff[k_growcancel] = -1; - - P_RestoreMusic(player); -} - -void K_SquishPlayer(player_t *player, mobj_t *source, mobj_t *inflictor) -{ - UINT8 scoremultiply = 1; - // PS: Inflictor is unused for all purposes here and is actually only ever relevant to Lua. It may be nil too. -#ifdef HAVE_BLUA - boolean force = false; // Used to check if Lua ShouldSquish should get us damaged reguardless of flashtics or heck knows what. - UINT8 shouldForce = LUAh_ShouldSquish(player, inflictor, source); - if (P_MobjWasRemoved(player->mo)) - return; // mobj was removed (in theory that shouldn't happen) - if (shouldForce == 1) - force = true; - else if (shouldForce == 2) - return; -#else - static const boolean force = false; - (void)inflictor; // Please stop forgetting to put inflictor in yer functions thank -Lat' -#endif - - if (G_BattleGametype()) - { - if (K_IsPlayerWanted(player)) - scoremultiply = 3; - else if (player->kartstuff[k_bumper] == 1) - scoremultiply = 2; - } - - if (player->health <= 0) - return; - - if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_invincibilitytimer] > 0 - || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0 - || (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1))) - { - if (!force) // You know the drill by now. - { - K_DoInstashield(player); - return; - } - } - -#ifdef HAVE_BLUA - if (LUAh_PlayerSquish(player, inflictor, source)) // Let Lua do its thing or overwrite if it wants to. Make sure to let any possible instashield happen because we didn't get "damaged" in this case. - return; -#endif - - player->kartstuff[k_sneakertimer] = 0; - player->kartstuff[k_levelbooster] = 0; - player->kartstuff[k_driftboost] = 0; - player->kartstuff[k_ringboost] = 0; - - player->kartstuff[k_drift] = 0; - player->kartstuff[k_driftcharge] = 0; - player->kartstuff[k_pogospring] = 0; - - if (G_BattleGametype()) - { - if (source && source->player && player != source->player) - { - P_AddPlayerScore(source->player, scoremultiply); - K_SpawnBattlePoints(source->player, player, scoremultiply); - source->player->kartstuff[k_wanted] -= wantedreduce; - player->kartstuff[k_wanted] -= (wantedreduce/2); - } - - if (player->kartstuff[k_bumper] > 0) - { - if (player->kartstuff[k_bumper] == 1) - { - mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!! - P_SetTarget(&karmahitbox->target, player->mo); - karmahitbox->destscale = player->mo->scale; - P_SetScale(karmahitbox, player->mo->scale); - CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]); - } - player->kartstuff[k_bumper]--; - if (K_IsPlayerWanted(player)) - K_CalculateBattleWanted(); - } - - if (!player->kartstuff[k_bumper]) - { - player->kartstuff[k_comebacktimer] = comebacktime; - if (player->kartstuff[k_comebackmode] == 2) - { - mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE); - S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound); - player->kartstuff[k_comebackmode] = 0; - } - } - - K_CheckBumpers(); - } - - player->kartstuff[k_squishedtimer] = TICRATE; - - // Reduce Shrink timer - if (player->kartstuff[k_growshrinktimer] < 0) - { - player->kartstuff[k_growshrinktimer] += TICRATE; - if (player->kartstuff[k_growshrinktimer] >= 0) - K_RemoveGrowShrink(player); - } - - player->powers[pw_flashing] = K_GetKartFlashing(player); - - player->mo->flags |= MF_NOCLIP; - - if (player->mo->state != &states[S_KART_SQUISH]) // Squash - P_SetPlayerMobjState(player->mo, S_KART_SQUISH); - - P_PlayRinglossSound(player->mo); - P_PlayerRingBurst(player, 5); - K_PlayPainSound(player->mo); - - player->kartstuff[k_instashield] = 15; - if (cv_kartdebughuddrop.value && !modeattacking) - K_DropItems(player); - else - K_DropHnextList(player); - return; -} - -void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor) // A bit of a hack, we just throw the player up higher here and extend their spinout timer -{ - UINT8 scoremultiply = 1; -#ifdef HAVE_BLUA - boolean force = false; // Used to check if Lua ShouldExplode should get us damaged reguardless of flashtics or heck knows what. - UINT8 shouldForce = LUAh_ShouldExplode(player, inflictor, source); - - if (P_MobjWasRemoved(player->mo)) - return; // mobj was removed (in theory that shouldn't happen) - if (shouldForce == 1) - force = true; - else if (shouldForce == 2) - return; - -#else - static const boolean force = false; -#endif - - if (G_BattleGametype()) - { - if (K_IsPlayerWanted(player)) - scoremultiply = 3; - else if (player->kartstuff[k_bumper] == 1) - scoremultiply = 2; - } - - if (player->health <= 0) - return; - - if (player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0 // Do not check spinout, because SPB and Eggman should combo - || (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1))) - { - if (!force) // ShouldDamage can bypass that, again. - { - K_DoInstashield(player); - return; - } - } - -#ifdef HAVE_BLUA - if (LUAh_PlayerExplode(player, inflictor, source)) // Same thing. Also make sure to let Instashield happen blah blah - return; -#endif - - if (source && source != player->mo && source->player) - K_PlayHitEmSound(source); - - player->mo->momz = 18*mapobjectscale*P_MobjFlip(player->mo); // please stop forgetting mobjflip checks!!!! - player->mo->momx = player->mo->momy = 0; - - player->kartstuff[k_sneakertimer] = 0; - player->kartstuff[k_levelbooster] = 0; - player->kartstuff[k_driftboost] = 0; - player->kartstuff[k_ringboost] = 0; - - player->kartstuff[k_drift] = 0; - player->kartstuff[k_driftcharge] = 0; - player->kartstuff[k_pogospring] = 0; - - // This is the only part that SHOULDN'T combo :VVVVV - if (G_BattleGametype() && !(player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || (player->kartstuff[k_spinouttimer] > 0 && player->kartstuff[k_spinouttype] != 2))) - { - if (source && source->player && player != source->player) - { - P_AddPlayerScore(source->player, scoremultiply); - K_SpawnBattlePoints(source->player, player, scoremultiply); - source->player->kartstuff[k_wanted] -= wantedreduce; - player->kartstuff[k_wanted] -= (wantedreduce/2); - } - - if (player->kartstuff[k_bumper] > 0) - { - if (player->kartstuff[k_bumper] == 1) - { - mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!! - P_SetTarget(&karmahitbox->target, player->mo); - karmahitbox->destscale = player->mo->scale; - P_SetScale(karmahitbox, player->mo->scale); - CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]); - } - player->kartstuff[k_bumper]--; - if (K_IsPlayerWanted(player)) - K_CalculateBattleWanted(); - } - - if (!player->kartstuff[k_bumper]) - { - player->kartstuff[k_comebacktimer] = comebacktime; - if (player->kartstuff[k_comebackmode] == 2) - { - mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE); - S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound); - player->kartstuff[k_comebackmode] = 0; - } - } - - K_CheckBumpers(); - } - - player->kartstuff[k_spinouttype] = 1; - player->kartstuff[k_spinouttimer] = (3*TICRATE/2)+2; - - player->powers[pw_flashing] = K_GetKartFlashing(player); - - if (inflictor && inflictor->type == MT_SPBEXPLOSION && inflictor->extravalue1) - { - player->kartstuff[k_spinouttimer] = ((5*player->kartstuff[k_spinouttimer])/2)+1; - player->mo->momz *= 2; - } - - if (player->mo->eflags & MFE_UNDERWATER) - player->mo->momz = (117 * player->mo->momz) / 200; - - if (player->mo->state != &states[S_KART_SPIN]) - P_SetPlayerMobjState(player->mo, S_KART_SPIN); - - P_PlayRinglossSound(player->mo); - P_PlayerRingBurst(player, 5); - K_PlayPainSound(player->mo); - - if (P_IsDisplayPlayer(player)) - { - quake.intensity = 64*FRACUNIT; - quake.time = 5; - } - - player->kartstuff[k_instashield] = 15; - K_DropItems(player); - - return; -} - -void K_StealBumper(player_t *player, player_t *victim, boolean force) -{ - INT32 newbumper; - angle_t newangle, diff; - fixed_t newx, newy; - mobj_t *newmo; - - if (!G_BattleGametype()) - return; - - if (player->health <= 0 || victim->health <= 0) - return; - - if (!force) - { - if (victim->kartstuff[k_bumper] <= 0) // || player->kartstuff[k_bumper] >= cv_kartbumpers.value+2 - return; - - if (player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0) - return; - - if (victim->powers[pw_flashing] > 0 || victim->kartstuff[k_squishedtimer] > 0 || (victim->kartstuff[k_spinouttimer] > 0 && victim->kartstuff[k_spinouttype] != 2) - || victim->kartstuff[k_invincibilitytimer] > 0 || victim->kartstuff[k_growshrinktimer] > 0 || victim->kartstuff[k_hyudorotimer] > 0) - { - K_DoInstashield(victim); - return; - } - } - - if (netgame && player->kartstuff[k_bumper] <= 0) - CONS_Printf(M_GetText("%s is back in the game!\n"), player_names[player-players]); - - newbumper = player->kartstuff[k_bumper]; - if (newbumper <= 1) - diff = 0; - else - diff = FixedAngle(360*FRACUNIT/newbumper); - - newangle = player->mo->angle; - newx = player->mo->x + P_ReturnThrustX(player->mo, newangle + ANGLE_180, 64*FRACUNIT); - newy = player->mo->y + P_ReturnThrustY(player->mo, newangle + ANGLE_180, 64*FRACUNIT); - - newmo = P_SpawnMobj(newx, newy, player->mo->z, MT_BATTLEBUMPER); - newmo->threshold = newbumper; - P_SetTarget(&newmo->tracer, victim->mo); - P_SetTarget(&newmo->target, player->mo); - newmo->angle = (diff * (newbumper-1)); - newmo->color = victim->skincolor; - - if (newbumper+1 < 2) - P_SetMobjState(newmo, S_BATTLEBUMPER3); - else if (newbumper+1 < 3) - P_SetMobjState(newmo, S_BATTLEBUMPER2); - else - P_SetMobjState(newmo, S_BATTLEBUMPER1); - - S_StartSound(player->mo, sfx_3db06); - - player->kartstuff[k_bumper]++; - player->kartstuff[k_comebackpoints] = 0; - player->powers[pw_flashing] = K_GetKartFlashing(player); - player->kartstuff[k_comebacktimer] = comebacktime; - - /*victim->powers[pw_flashing] = K_GetKartFlashing(victim); - victim->kartstuff[k_comebacktimer] = comebacktime;*/ - - victim->kartstuff[k_instashield] = 15; - if (cv_kartdebughuddrop.value && !modeattacking) - K_DropItems(victim); - else - K_DropHnextList(victim); - return; -} - -// source is the mobj that originally threw the bomb that exploded etc. -// Spawns the sphere around the explosion that handles spinout -void K_SpawnKartExplosion(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle, boolean spawncenter, boolean ghostit, mobj_t *source) -{ - mobj_t *mobj; - mobj_t *ghost = NULL; - INT32 i; - TVector v; - TVector *res; - fixed_t finalx, finaly, finalz, dist; - //mobj_t hoopcenter; - angle_t degrees, fa, closestangle; - fixed_t mobjx, mobjy, mobjz; - - //hoopcenter.x = x; - //hoopcenter.y = y; - //hoopcenter.z = z; - - //hoopcenter.z = z - mobjinfo[type].height/2; - - degrees = FINEANGLES/number; - - closestangle = 0; - - // Create the hoop! - for (i = 0; i < number; i++) - { - fa = (i*degrees); - v[0] = FixedMul(FINECOSINE(fa),radius); - v[1] = 0; - v[2] = FixedMul(FINESINE(fa),radius); - v[3] = FRACUNIT; - - res = VectorMatrixMultiply(v, *RotateXMatrix(rotangle)); - M_Memcpy(&v, res, sizeof (v)); - res = VectorMatrixMultiply(v, *RotateZMatrix(closestangle)); - M_Memcpy(&v, res, sizeof (v)); - - finalx = x + v[0]; - finaly = y + v[1]; - finalz = z + v[2]; - - mobj = P_SpawnMobj(finalx, finaly, finalz, type); - - mobj->z -= mobj->height>>1; - - // change angle - mobj->angle = R_PointToAngle2(mobj->x, mobj->y, x, y); - - // change slope - dist = P_AproxDistance(P_AproxDistance(x - mobj->x, y - mobj->y), z - mobj->z); - - if (dist < 1) - dist = 1; - - mobjx = mobj->x; - mobjy = mobj->y; - mobjz = mobj->z; - - if (ghostit) - { - ghost = P_SpawnGhostMobj(mobj); - P_SetMobjState(mobj, S_NULL); - mobj = ghost; - } - - if (spawncenter) - { - mobj->x = x; - mobj->y = y; - mobj->z = z; - } - - mobj->momx = FixedMul(FixedDiv(mobjx - x, dist), FixedDiv(dist, 6*FRACUNIT)); - mobj->momy = FixedMul(FixedDiv(mobjy - y, dist), FixedDiv(dist, 6*FRACUNIT)); - mobj->momz = FixedMul(FixedDiv(mobjz - z, dist), FixedDiv(dist, 6*FRACUNIT)); - - if (source && !P_MobjWasRemoved(source)) - P_SetTarget(&mobj->target, source); - } -} - -// Spawns the purely visual explosion -void K_SpawnMineExplosion(mobj_t *source, UINT8 color) -{ - INT32 i, radius, height; - mobj_t *smoldering = P_SpawnMobj(source->x, source->y, source->z, MT_SMOLDERING); - mobj_t *dust; - mobj_t *truc; - INT32 speed, speed2; - - K_MatchGenericExtraFlags(smoldering, source); - smoldering->tics = TICRATE*3; - radius = source->radius>>FRACBITS; - height = source->height>>FRACBITS; - - if (!color) - color = SKINCOLOR_KETCHUP; - - for (i = 0; i < 32; i++) - { - dust = P_SpawnMobj(source->x, source->y, source->z, MT_SMOKE); - P_SetMobjState(dust, S_OPAQUESMOKE1); - dust->angle = (ANGLE_180/16) * i; - P_SetScale(dust, source->scale); - dust->destscale = source->scale*10; - dust->scalespeed = source->scale/12; - P_InstaThrust(dust, dust->angle, FixedMul(20*FRACUNIT, source->scale)); - - truc = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT, - source->y + P_RandomRange(-radius, radius)*FRACUNIT, - source->z + P_RandomRange(0, height)*FRACUNIT, MT_BOOMEXPLODE); - K_MatchGenericExtraFlags(truc, source); - P_SetScale(truc, source->scale); - truc->destscale = source->scale*6; - truc->scalespeed = source->scale/12; - speed = FixedMul(10*FRACUNIT, source->scale)>>FRACBITS; - truc->momx = P_RandomRange(-speed, speed)*FRACUNIT; - truc->momy = P_RandomRange(-speed, speed)*FRACUNIT; - speed = FixedMul(20*FRACUNIT, source->scale)>>FRACBITS; - truc->momz = P_RandomRange(-speed, speed)*FRACUNIT*P_MobjFlip(truc); - if (truc->eflags & MFE_UNDERWATER) - truc->momz = (117 * truc->momz) / 200; - truc->color = color; - } - - for (i = 0; i < 16; i++) - { - dust = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT, - source->y + P_RandomRange(-radius, radius)*FRACUNIT, - source->z + P_RandomRange(0, height)*FRACUNIT, MT_SMOKE); - P_SetMobjState(dust, S_OPAQUESMOKE1); - P_SetScale(dust, source->scale); - dust->destscale = source->scale*10; - dust->scalespeed = source->scale/12; - dust->tics = 30; - dust->momz = P_RandomRange(FixedMul(3*FRACUNIT, source->scale)>>FRACBITS, FixedMul(7*FRACUNIT, source->scale)>>FRACBITS)*FRACUNIT; - - truc = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT, - source->y + P_RandomRange(-radius, radius)*FRACUNIT, - source->z + P_RandomRange(0, height)*FRACUNIT, MT_BOOMPARTICLE); - K_MatchGenericExtraFlags(truc, source); - P_SetScale(truc, source->scale); - truc->destscale = source->scale*5; - truc->scalespeed = source->scale/12; - speed = FixedMul(20*FRACUNIT, source->scale)>>FRACBITS; - truc->momx = P_RandomRange(-speed, speed)*FRACUNIT; - truc->momy = P_RandomRange(-speed, speed)*FRACUNIT; - speed = FixedMul(15*FRACUNIT, source->scale)>>FRACBITS; - speed2 = FixedMul(45*FRACUNIT, source->scale)>>FRACBITS; - truc->momz = P_RandomRange(speed, speed2)*FRACUNIT*P_MobjFlip(truc); - if (P_RandomChance(FRACUNIT/2)) - truc->momz = -truc->momz; - if (truc->eflags & MFE_UNDERWATER) - truc->momz = (117 * truc->momz) / 200; - truc->tics = TICRATE*2; - truc->color = color; - } -} - -static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, INT32 flags2, fixed_t speed) -{ - mobj_t *th; - fixed_t x, y, z; - fixed_t finalspeed = speed; - mobj_t *throwmo; - - if (source->player && source->player->speed > K_GetKartSpeed(source->player, false)) - { - angle_t input = source->angle - an; - boolean invert = (input > ANGLE_180); - if (invert) - input = InvAngle(input); - - finalspeed = max(speed, FixedMul(speed, FixedMul( - FixedDiv(source->player->speed, K_GetKartSpeed(source->player, false)), // Multiply speed to be proportional to your own, boosted maxspeed. - (((180<x + source->momx + FixedMul(finalspeed, FINECOSINE(an>>ANGLETOFINESHIFT)); - y = source->y + source->momy + FixedMul(finalspeed, FINESINE(an>>ANGLETOFINESHIFT)); - z = source->z; // spawn on the ground please - - if (P_MobjFlip(source) < 0) - { - z = source->z+source->height - mobjinfo[type].height; - } - - th = P_SpawnMobj(x, y, z, type); - - th->flags2 |= flags2; - - th->threshold = 10; - - if (th->info->seesound) - S_StartSound(source, th->info->seesound); - - P_SetTarget(&th->target, source); - - if (P_IsObjectOnGround(source)) - { - // floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn - // This should set it for FOFs - P_TeleportMove(th, th->x, th->y, th->z); - // spawn on the ground if the player is on the ground - if (P_MobjFlip(source) < 0) - { - th->z = th->ceilingz - th->height; - th->eflags |= MFE_VERTICALFLIP; - } - else - th->z = th->floorz; - } - - th->angle = an; - th->momx = FixedMul(finalspeed, FINECOSINE(an>>ANGLETOFINESHIFT)); - th->momy = FixedMul(finalspeed, FINESINE(an>>ANGLETOFINESHIFT)); - - switch (type) - { - case MT_ORBINAUT: - if (source && source->player) - th->color = source->player->skincolor; - else - th->color = SKINCOLOR_GREY; - th->movefactor = finalspeed; - break; - case MT_JAWZ: - if (source && source->player) - { - INT32 lasttarg = source->player->kartstuff[k_lastjawztarget]; - th->cvmem = source->player->skincolor; - if ((lasttarg >= 0 && lasttarg < MAXPLAYERS) - && playeringame[lasttarg] - && !players[lasttarg].spectator - && players[lasttarg].mo) + if (player->kartstuff[k_bumper] > 0) { - P_SetTarget(&th->tracer, players[lasttarg].mo); - } - } - else - th->cvmem = SKINCOLOR_KETCHUP; - /* FALLTHRU */ - case MT_JAWZ_DUD: - S_StartSound(th, th->info->activesound); - /* FALLTHRU */ - case MT_SPB: - th->movefactor = finalspeed; - break; - default: - break; - } - - x = x + P_ReturnThrustX(source, an, source->radius + th->radius); - y = y + P_ReturnThrustY(source, an, source->radius + th->radius); - throwmo = P_SpawnMobj(x, y, z, MT_FIREDITEM); - throwmo->movecount = 1; - throwmo->movedir = source->angle - an; - P_SetTarget(&throwmo->target, source); - - return NULL; -} - -static void K_SpawnDriftSparks(player_t *player) -{ - fixed_t newx; - fixed_t newy; - mobj_t *spark; - angle_t travelangle; - INT32 i; - - I_Assert(player != NULL); - I_Assert(player->mo != NULL); - I_Assert(!P_MobjWasRemoved(player->mo)); - - if (leveltime % 2 == 1) - return; - - if (!P_IsObjectOnGround(player->mo)) - return; - - if (!player->kartstuff[k_drift] || player->kartstuff[k_driftcharge] < K_GetKartDriftSparkValue(player)) - return; - - travelangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift]; - - for (i = 0; i < 2; i++) - { - newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale)); - newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale)); - spark = P_SpawnMobj(newx, newy, player->mo->z, MT_DRIFTSPARK); - - P_SetTarget(&spark->target, player->mo); - spark->angle = travelangle-(ANGLE_45/5)*player->kartstuff[k_drift]; - spark->destscale = player->mo->scale; - P_SetScale(spark, player->mo->scale); - - spark->momx = player->mo->momx/2; - spark->momy = player->mo->momy/2; - //spark->momz = player->mo->momz/2; - - if (player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(player)*4) - { - spark->color = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))); - } - else if (player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(player)*2) - { - if (player->kartstuff[k_driftcharge] <= (K_GetKartDriftSparkValue(player)*2)+(24*3)) - spark->color = SKINCOLOR_RASPBERRY; // transition - else - spark->color = SKINCOLOR_KETCHUP; - } - else - { - spark->color = SKINCOLOR_SAPPHIRE; - } - - if ((player->kartstuff[k_drift] > 0 && player->cmd.driftturn > 0) // Inward drifts - || (player->kartstuff[k_drift] < 0 && player->cmd.driftturn < 0)) - { - if ((player->kartstuff[k_drift] < 0 && (i & 1)) - || (player->kartstuff[k_drift] > 0 && !(i & 1))) - P_SetMobjState(spark, S_DRIFTSPARK_A1); - else if ((player->kartstuff[k_drift] < 0 && !(i & 1)) - || (player->kartstuff[k_drift] > 0 && (i & 1))) - P_SetMobjState(spark, S_DRIFTSPARK_C1); - } - else if ((player->kartstuff[k_drift] > 0 && player->cmd.driftturn < 0) // Outward drifts - || (player->kartstuff[k_drift] < 0 && player->cmd.driftturn > 0)) - { - if ((player->kartstuff[k_drift] < 0 && (i & 1)) - || (player->kartstuff[k_drift] > 0 && !(i & 1))) - P_SetMobjState(spark, S_DRIFTSPARK_C1); - else if ((player->kartstuff[k_drift] < 0 && !(i & 1)) - || (player->kartstuff[k_drift] > 0 && (i & 1))) - P_SetMobjState(spark, S_DRIFTSPARK_A1); - } - - K_MatchGenericExtraFlags(spark, player->mo); - } -} - -static void K_SpawnAIZDust(player_t *player) -{ - fixed_t newx; - fixed_t newy; - mobj_t *spark; - angle_t travelangle; - - I_Assert(player != NULL); - I_Assert(player->mo != NULL); - I_Assert(!P_MobjWasRemoved(player->mo)); - - if (leveltime % 2 == 1) - return; - - if (!P_IsObjectOnGround(player->mo)) - return; - - travelangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); - //S_StartSound(player->mo, sfx_s3k47); - - { - newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle - (player->kartstuff[k_aizdriftstrat]*ANGLE_45), FixedMul(24*FRACUNIT, player->mo->scale)); - newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle - (player->kartstuff[k_aizdriftstrat]*ANGLE_45), FixedMul(24*FRACUNIT, player->mo->scale)); - spark = P_SpawnMobj(newx, newy, player->mo->z, MT_AIZDRIFTSTRAT); - - spark->angle = travelangle+(player->kartstuff[k_aizdriftstrat]*ANGLE_90); - P_SetScale(spark, (spark->destscale = (3*player->mo->scale)>>2)); - - spark->momx = (6*player->mo->momx)/5; - spark->momy = (6*player->mo->momy)/5; - //spark->momz = player->mo->momz/2; - - K_MatchGenericExtraFlags(spark, player->mo); - } -} - -void K_SpawnBoostTrail(player_t *player) -{ - fixed_t newx; - fixed_t newy; - fixed_t ground; - mobj_t *flame; - angle_t travelangle; - INT32 i; - - I_Assert(player != NULL); - I_Assert(player->mo != NULL); - I_Assert(!P_MobjWasRemoved(player->mo)); - - if (!P_IsObjectOnGround(player->mo) - || player->kartstuff[k_hyudorotimer] != 0 - || (G_BattleGametype() && player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer])) - return; - - if (player->mo->eflags & MFE_VERTICALFLIP) - ground = player->mo->ceilingz - FixedMul(mobjinfo[MT_SNEAKERTRAIL].height, player->mo->scale); - else - ground = player->mo->floorz; - - if (player->kartstuff[k_drift] != 0) - travelangle = player->mo->angle; - else - travelangle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy); - - for (i = 0; i < 2; i++) - { - newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale)); - newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale)); -#ifdef ESLOPE - if (player->mo->standingslope) - { - ground = P_GetZAt(player->mo->standingslope, newx, newy); - if (player->mo->eflags & MFE_VERTICALFLIP) - ground -= FixedMul(mobjinfo[MT_SNEAKERTRAIL].height, player->mo->scale); - } -#endif - flame = P_SpawnMobj(newx, newy, ground, MT_SNEAKERTRAIL); - - P_SetTarget(&flame->target, player->mo); - flame->angle = travelangle; - flame->fuse = TICRATE*2; - flame->destscale = player->mo->scale; - P_SetScale(flame, player->mo->scale); - // not K_MatchGenericExtraFlags so that a stolen sneaker can be seen - K_FlipFromObject(flame, player->mo); - - flame->momx = 8; - P_XYMovement(flame); - if (P_MobjWasRemoved(flame)) - continue; - - if (player->mo->eflags & MFE_VERTICALFLIP) - { - if (flame->z + flame->height < flame->ceilingz) - P_RemoveMobj(flame); - } - else if (flame->z > flame->floorz) - P_RemoveMobj(flame); - } -} - -void K_SpawnSparkleTrail(mobj_t *mo) -{ - const INT32 rad = (mo->radius*2)>>FRACBITS; - mobj_t *sparkle; - INT32 i; - - I_Assert(mo != NULL); - I_Assert(!P_MobjWasRemoved(mo)); - - for (i = 0; i < 3; i++) - { - fixed_t newx = mo->x + mo->momx + (P_RandomRange(-rad, rad)<y + mo->momy + (P_RandomRange(-rad, rad)<z + mo->momz + (P_RandomRange(0, mo->height>>FRACBITS)<target, mo); - sparkle->destscale = mo->destscale; - P_SetScale(sparkle, mo->scale); - sparkle->color = mo->color; - //sparkle->colorized = mo->colorized; - } - - P_SetMobjState(sparkle, S_KARTINVULN_LARGE1); -} - -void K_SpawnWipeoutTrail(mobj_t *mo, boolean translucent) -{ - mobj_t *dust; - angle_t aoff; - - I_Assert(mo != NULL); - I_Assert(!P_MobjWasRemoved(mo)); - - if (mo->player) - aoff = (mo->player->frameangle + ANGLE_180); - else - aoff = (mo->angle + ANGLE_180); - - if ((leveltime / 2) & 1) - aoff -= ANGLE_45; - else - aoff += ANGLE_45; - - dust = P_SpawnMobj(mo->x + FixedMul(24*mo->scale, FINECOSINE(aoff>>ANGLETOFINESHIFT)) + (P_RandomRange(-8,8) << FRACBITS), - mo->y + FixedMul(24*mo->scale, FINESINE(aoff>>ANGLETOFINESHIFT)) + (P_RandomRange(-8,8) << FRACBITS), - mo->z, MT_WIPEOUTTRAIL); - - P_SetTarget(&dust->target, mo); - dust->angle = R_PointToAngle2(0,0,mo->momx,mo->momy); - dust->destscale = mo->scale; - P_SetScale(dust, mo->scale); - K_FlipFromObject(dust, mo); - - if (translucent) // offroad effect - { - dust->momx = mo->momx/2; - dust->momy = mo->momy/2; - dust->momz = mo->momz/2; - } - - if (translucent) - dust->flags2 |= MF2_SHADOW; -} - -void K_SpawnDraftDust(mobj_t *mo) -{ - UINT8 i; - - I_Assert(mo != NULL); - I_Assert(!P_MobjWasRemoved(mo)); - - for (i = 0; i < 2; i++) - { - angle_t ang, aoff; - SINT8 sign = 1; - UINT8 foff = 0; - mobj_t *dust; - boolean drifting = false; - - if (mo->player) - { - UINT8 leniency = (3*TICRATE)/4 + ((mo->player->kartweight-1) * (TICRATE/4)); - - ang = mo->player->frameangle; - - if (mo->player->kartstuff[k_drift] != 0) - { - drifting = true; - ang += (mo->player->kartstuff[k_drift] * ((ANGLE_270 + ANGLE_22h) / 5)); // -112.5 doesn't work. I fucking HATE SRB2 angles - if (mo->player->kartstuff[k_drift] < 0) - sign = 1; - else - sign = -1; - } - - foff = 5 - ((mo->player->kartstuff[k_draftleeway] * 5) / leniency); - - // this shouldn't happen - if (foff > 4) - foff = 4; - } - else - ang = mo->angle; - - if (!drifting) - { - if (i & 1) - sign = -1; - else - sign = 1; - } - - aoff = (ang + ANGLE_180) + (ANGLE_45 * sign); - - dust = P_SpawnMobj(mo->x + FixedMul(24*mo->scale, FINECOSINE(aoff>>ANGLETOFINESHIFT)), - mo->y + FixedMul(24*mo->scale, FINESINE(aoff>>ANGLETOFINESHIFT)), - mo->z, MT_DRAFTDUST); - - P_SetMobjState(dust, S_DRAFTDUST1 + foff); - - P_SetTarget(&dust->target, mo); - dust->angle = ang - (ANGLE_90 * sign); // point completely perpendicular from the player - dust->destscale = mo->scale; - P_SetScale(dust, mo->scale); - K_FlipFromObject(dust, mo); - - if (leveltime & 1) - dust->tics++; // "randomize" animation - - dust->momx = (4*mo->momx)/5; - dust->momy = (4*mo->momy)/5; - //dust->momz = (4*mo->momz)/5; - - P_Thrust(dust, dust->angle, 4*mo->scale); - - if (drifting) // only 1 trail while drifting - break; - } -} - -// K_DriftDustHandling -// Parameters: -// spawner: The map object that is spawning the drift dust -// Description: Spawns the drift dust for objects, players use rmomx/y, other objects use regular momx/y. -// Also plays the drift sound. -// Other objects should be angled towards where they're trying to go so they don't randomly spawn dust -// Do note that most of the function won't run in odd intervals of frames -void K_DriftDustHandling(mobj_t *spawner) -{ - angle_t anglediff; - const INT16 spawnrange = spawner->radius>>FRACBITS; - - if (!P_IsObjectOnGround(spawner) || leveltime % 2 != 0) - return; - - if (spawner->player) - { - if (spawner->player->pflags & PF_SKIDDOWN) - { - anglediff = abs((signed)(spawner->angle - spawner->player->frameangle)); - if (leveltime % 6 == 0) - S_StartSound(spawner, sfx_screec); // repeated here because it doesn't always happen to be within the range when this is the case - } - else - { - angle_t playerangle = spawner->angle; - - if (spawner->player->speed < 5<player->cmd.forwardmove < 0) - playerangle += ANGLE_180; - - anglediff = abs((signed)(playerangle - R_PointToAngle2(0, 0, spawner->player->rmomx, spawner->player->rmomy))); - } - } - else - { - if (P_AproxDistance(spawner->momx, spawner->momy) < 5<angle - R_PointToAngle2(0, 0, spawner->momx, spawner->momy))); - } - - if (anglediff > ANGLE_180) - anglediff = InvAngle(anglediff); - - if (anglediff > ANG10*4) // Trying to turn further than 40 degrees - { - fixed_t spawnx = P_RandomRange(-spawnrange, spawnrange)<x + spawnx, spawner->y + spawny, spawner->z, MT_DRIFTDUST); - dust->momx = FixedMul(spawner->momx + (P_RandomRange(-speedrange, speedrange)<scale)/4); - dust->momy = FixedMul(spawner->momy + (P_RandomRange(-speedrange, speedrange)<scale)/4); - dust->momz = P_MobjFlip(spawner) * (P_RandomRange(1, 4) * (spawner->scale)); - P_SetScale(dust, spawner->scale/2); - dust->destscale = spawner->scale * 3; - dust->scalespeed = spawner->scale/12; - - if (leveltime % 6 == 0) - S_StartSound(spawner, sfx_screec); - - K_MatchGenericExtraFlags(dust, spawner); - } -} - -static mobj_t *K_FindLastTrailMobj(player_t *player) -{ - mobj_t *trail; - - if (!player || !(trail = player->mo) || !player->mo->hnext || !player->mo->hnext->health) - return NULL; - - while (trail->hnext && !P_MobjWasRemoved(trail->hnext) && trail->hnext->health) - { - trail = trail->hnext; - } - - return trail; -} - -static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow) -{ - mobj_t *mo; - INT32 dir; - fixed_t PROJSPEED; - angle_t newangle; - fixed_t newx, newy, newz; - mobj_t *throwmo; - - if (!player) - return NULL; - - // Figure out projectile speed by game speed - if (missile && mapthing != MT_BALLHOG) // Trying to keep compatability... - PROJSPEED = FixedMul(mobjinfo[mapthing].speed, FRACUNIT + ((gamespeed-1) * (FRACUNIT/4))); - else - PROJSPEED = (82 + ((gamespeed-1) * 14)) * FRACUNIT; // Avg Speed is 41 in Normal - PROJSPEED = FixedMul(PROJSPEED, mapobjectscale); - - if (altthrow) - { - if (altthrow == 2) // Kitchen sink throwing - { -#if 0 - if (player->kartstuff[k_throwdir] == 1) - dir = 3; - else if (player->kartstuff[k_throwdir] == -1) - dir = 1; - else - dir = 2; -#else - if (player->kartstuff[k_throwdir] == 1) - dir = 2; - else - dir = 1; -#endif - } - else - { - if (player->kartstuff[k_throwdir] == 1) - dir = 2; - else if (player->kartstuff[k_throwdir] == -1) - dir = -1; - else - dir = 1; - } - } - else - { - if (player->kartstuff[k_throwdir] != 0) - dir = player->kartstuff[k_throwdir]; - else - dir = defaultDir; - } - - if (missile) // Shootables - { - if (mapthing == MT_BALLHOG) // Messy - { - if (dir == -1) - { - // Shoot backward - mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 - 0x06000000, 0, PROJSPEED/4); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 - 0x03000000, 0, PROJSPEED/4); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/4); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 + 0x03000000, 0, PROJSPEED/4); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 + 0x06000000, 0, PROJSPEED/4); - } - else - { - // Shoot forward - mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle - 0x06000000, 0, PROJSPEED); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle - 0x03000000, 0, PROJSPEED); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle, 0, PROJSPEED); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + 0x03000000, 0, PROJSPEED); - K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + 0x06000000, 0, PROJSPEED); - } - } - else - { - if (dir == -1 && mapthing != MT_SPB) - { - // Shoot backward - mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/2); - } - else - { - // Shoot forward - mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle, 0, PROJSPEED); - } - } - } - else - { - player->kartstuff[k_bananadrag] = 0; // RESET timer, for multiple bananas - - if (dir > 0) - { - // Shoot forward - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, mapthing); - //K_FlipFromObject(mo, player->mo); - // These are really weird so let's make it a very specific case to make SURE it works... - if (player->mo->eflags & MFE_VERTICALFLIP) - { - mo->z -= player->mo->height; - mo->flags2 |= MF2_OBJECTFLIP; - mo->eflags |= MFE_VERTICALFLIP; - } - - mo->threshold = 10; - P_SetTarget(&mo->target, player->mo); - - S_StartSound(player->mo, mo->info->seesound); - - if (mo) - { - angle_t fa = player->mo->angle>>ANGLETOFINESHIFT; - fixed_t HEIGHT = (20 + (dir*10))*mapobjectscale + (player->mo->momz*P_MobjFlip(player->mo)); - - P_SetObjectMomZ(mo, HEIGHT, false); - mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), PROJSPEED*dir); - mo->momy = player->mo->momy + FixedMul(FINESINE(fa), PROJSPEED*dir); - - mo->extravalue2 = dir; - - if (mo->eflags & MFE_UNDERWATER) - mo->momz = (117 * mo->momz) / 200; - } - - // this is the small graphic effect that plops in you when you throw an item: - 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->flags2 |= MF2_OBJECTFLIP; - throwmo->eflags |= MFE_VERTICALFLIP; - } - - throwmo->movecount = 0; // above player - } - else - { - mobj_t *lasttrail = K_FindLastTrailMobj(player); - - if (lasttrail) - { - newx = lasttrail->x; - newy = lasttrail->y; - newz = lasttrail->z; - } - else - { - // Drop it directly behind you. - fixed_t dropradius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(mobjinfo[mapthing].radius, mobjinfo[mapthing].radius); - - newangle = player->mo->angle; - - newx = player->mo->x + P_ReturnThrustX(player->mo, newangle + ANGLE_180, dropradius); - newy = player->mo->y + P_ReturnThrustY(player->mo, newangle + ANGLE_180, dropradius); - newz = player->mo->z; - } - - mo = P_SpawnMobj(newx, newy, newz, mapthing); // this will never return null because collision isn't processed here - K_FlipFromObject(mo, player->mo); - - mo->threshold = 10; - P_SetTarget(&mo->target, player->mo); - - if (P_IsObjectOnGround(player->mo)) - { - // floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn - // This should set it for FOFs - P_TeleportMove(mo, mo->x, mo->y, mo->z); // however, THIS can fuck up your day. just absolutely ruin you. - if (P_MobjWasRemoved(mo)) - return NULL; - - if (P_MobjFlip(mo) > 0) - { - if (mo->floorz > mo->target->z - mo->height) + if (player->kartstuff[k_bumper] == 1) { - mo->z = mo->floorz; + mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!! + P_SetTarget(&karmahitbox->target, player->mo); + karmahitbox->destscale = player->mo->scale; + P_SetScale(karmahitbox, player->mo->scale); + CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]); + } + player->kartstuff[k_bumper]--; + if (K_IsPlayerWanted(player)) + K_CalculateBattleWanted(); + } + + if (!player->kartstuff[k_bumper]) + { + player->kartstuff[k_comebacktimer] = comebacktime; + if (player->kartstuff[k_comebackmode] == 2) + { + mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE); + S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound); + player->kartstuff[k_comebackmode] = 0; } } - else - { - if (mo->ceilingz < mo->target->z + mo->target->height + mo->height) - { - mo->z = mo->ceilingz - mo->height; - } - } - } - if (player->mo->eflags & MFE_VERTICALFLIP) - mo->eflags |= MFE_VERTICALFLIP; - - if (mapthing == MT_SSMINE) - mo->extravalue1 = 49; // Pads the start-up length from 21 frames to a full 2 seconds - } - } - - return mo; -} - -void K_PuntMine(mobj_t *thismine, mobj_t *punter) -{ - angle_t fa = R_PointToAngle2(0, 0, punter->momx, punter->momy) >> ANGLETOFINESHIFT; - fixed_t z = 30*mapobjectscale + punter->momz; - fixed_t spd; - mobj_t *mine; - - if (!thismine || P_MobjWasRemoved(thismine)) - return; - - if (thismine->type == MT_SSMINE_SHIELD) // Create a new mine - { - mine = P_SpawnMobj(thismine->x, thismine->y, thismine->z, MT_SSMINE); - P_SetTarget(&mine->target, thismine->target); - mine->angle = thismine->angle; - mine->flags2 = thismine->flags2; - mine->floorz = thismine->floorz; - mine->ceilingz = thismine->ceilingz; - P_RemoveMobj(thismine); - } - else - mine = thismine; - - if (!mine || P_MobjWasRemoved(mine)) - return; - - spd = (82 + ((gamespeed-1) * 14))*mapobjectscale; // Avg Speed is 41 in Normal - - mine->flags |= MF_NOCLIPTHING; - - P_SetMobjState(mine, S_SSMINE_AIR1); - mine->threshold = 10; - mine->extravalue1 = 0; - mine->reactiontime = mine->info->reactiontime; - - mine->momx = punter->momx + FixedMul(FINECOSINE(fa), spd); - mine->momy = punter->momy + FixedMul(FINESINE(fa), spd); - mine->momz = P_MobjFlip(mine) * z; - - mine->flags &= ~MF_NOCLIPTHING; -} - -#define THUNDERRADIUS 320 - -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); - //player->kartstuff[k_thunderanim] = 35; - P_NukeEnemies(player->mo, player->mo, RING_DIST/4); - - // 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 - -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); - - 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 - && (G_RaceGametype() //&& players[i].kartstuff[k_position] < player->kartstuff[k_position]) - || (G_BattleGametype() && players[i].kartstuff[k_bumper] > 0)) - - // Has an item - && (players[i].kartstuff[k_itemtype] - && players[i].kartstuff[k_itemamount] - && !players[i].kartstuff[k_itemheld] - && !players[i].karthud[khud_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->kartstuff[k_hyudorotimer] = hyudorotime; - player->kartstuff[k_stealingtimer] = stealtime; - - player->kartstuff[k_itemtype] = KITEM_KITCHENSINK; - player->kartstuff[k_itemamount] = 1; - player->kartstuff[k_itemheld] = 0; - return; - } - else if ((G_RaceGametype() && player->kartstuff[k_position] == 1) || numplayers == 0) // No-one can be stolen from? Oh well... - { - player->kartstuff[k_hyudorotimer] = hyudorotime; - player->kartstuff[k_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->kartstuff[k_hyudorotimer] = hyudorotime; - player->kartstuff[k_stealingtimer] = stealtime; - players[stealplayer].kartstuff[k_stolentimer] = stealtime; - - player->kartstuff[k_itemtype] = players[stealplayer].kartstuff[k_itemtype]; - player->kartstuff[k_itemamount] = players[stealplayer].kartstuff[k_itemamount]; - player->kartstuff[k_itemheld] = 0; - - players[stealplayer].kartstuff[k_itemtype] = KITEM_NONE; - players[stealplayer].kartstuff[k_itemamount] = 0; - players[stealplayer].kartstuff[k_itemheld] = 0; - - if (P_IsDisplayPlayer(&players[stealplayer]) && !splitscreen) - S_StartSound(NULL, sfx_s3k92); - } -} - -void K_DoSneaker(player_t *player, INT32 type) -{ - const fixed_t intendedboost = FRACUNIT/2; - - if (!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) - { - S_StartSound(player->mo, sfx_cdfm01); - K_SpawnDashDustRelease(player); - if (intendedboost > player->kartstuff[k_speedboost]) - player->karthud[khud_destboostcam] = FixedMul(FRACUNIT, FixedDiv((intendedboost - player->kartstuff[k_speedboost]), intendedboost)); - } - - if (!EITHERSNEAKER(player)) - { - if (type == 2) - { - if (player->mo->hnext) - { - mobj_t *cur = player->mo->hnext; - while (cur && !P_MobjWasRemoved(cur)) - { - if (!cur->tracer) - { - mobj_t *overlay = P_SpawnMobj(cur->x, cur->y, cur->z, MT_BOOSTFLAME); - P_SetTarget(&overlay->target, cur); - P_SetTarget(&cur->tracer, overlay); - P_SetScale(overlay, (overlay->destscale = 3*cur->scale/4)); - K_FlipFromObject(overlay, cur); - } - cur = cur->hnext; - } - } - } - else - { - mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BOOSTFLAME); - P_SetTarget(&overlay->target, player->mo); - P_SetScale(overlay, (overlay->destscale = player->mo->scale)); - K_FlipFromObject(overlay, player->mo); - } - } - - if (type != 0) - { - player->pflags |= PF_ATTACKDOWN; - K_PlayBoostTaunt(player->mo); - player->kartstuff[k_sneakertimer] = sneakertime; - } - else - player->kartstuff[k_levelbooster] = sneakertime; - - // set angle for spun out players: - player->kartstuff[k_boostangle] = (INT32)player->mo->angle; -} - -static void K_DoShrink(player_t *user) -{ - INT32 i; - - S_StartSound(user->mo, sfx_kc46); // Sound the BANG! - user->pflags |= PF_ATTACKDOWN; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator || !players[i].mo) - continue; - if (&players[i] == user) - continue; - if (players[i].kartstuff[k_position] < user->kartstuff[k_position]) - { - //P_FlashPal(&players[i], PAL_NUKE, 10); - - // Grow should get taken away. - if (players[i].kartstuff[k_growshrinktimer] > 0) - K_RemoveGrowShrink(&players[i]); - // Don't hit while invulnerable! - else if (!players[i].kartstuff[k_invincibilitytimer] - && players[i].kartstuff[k_growshrinktimer] <= 0 - && !players[i].kartstuff[k_hyudorotimer]) - { - // Start shrinking! - K_DropItems(&players[i]); - players[i].kartstuff[k_growshrinktimer] = -(15*TICRATE); - - if (players[i].mo && !P_MobjWasRemoved(players[i].mo)) - { - players[i].mo->scalespeed = mapobjectscale/TICRATE; - players[i].mo->destscale = (6*mapobjectscale)/8; - if (cv_kartdebugshrink.value && !modeattacking && !players[i].bot) - players[i].mo->destscale = (6*players[i].mo->destscale)/8; - S_StartSound(players[i].mo, sfx_kc59); - } - } - } - } -} - - -void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) -{ - const fixed_t vscale = mapobjectscale + (mo->scale - mapobjectscale); - - if (mo->player && mo->player->spectator) - return; - - if (mo->eflags & MFE_SPRUNG) - return; - -#ifdef ESLOPE - mo->standingslope = NULL; -#endif - - mo->eflags |= MFE_SPRUNG; - - if (mo->eflags & MFE_VERTICALFLIP) - vertispeed *= -1; - - if (vertispeed == 0) - { - fixed_t thrust; - - if (mo->player) - { - thrust = 3*mo->player->speed/2; - if (thrust < 48< 72<player->kartstuff[k_pogospring] != 2) - { - if (EITHERSNEAKER(mo->player)) - thrust = FixedMul(thrust, (5*FRACUNIT)/4); - else if (mo->player->kartstuff[k_invincibilitytimer]) - thrust = FixedMul(thrust, (9*FRACUNIT)/8); - } - } - else - { - thrust = FixedDiv(3*P_AproxDistance(mo->momx, mo->momy)/2, 5*FRACUNIT/2); - if (thrust < 16< 32<momz = P_MobjFlip(mo)*FixedMul(FINESINE(ANGLE_22h>>ANGLETOFINESHIFT), FixedMul(thrust, vscale)); - } - else - mo->momz = FixedMul(vertispeed, vscale); - - if (mo->eflags & MFE_UNDERWATER) - mo->momz = (117 * mo->momz) / 200; - - if (sound) - S_StartSound(mo, (sound == 1 ? sfx_kc2f : sfx_kpogos)); -} - -void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source) -{ - mobj_t *cachenext; - -killnext: - cachenext = banana->hnext; - - if (banana->health) - { - if (banana->eflags & MFE_VERTICALFLIP) - banana->z -= banana->height; - else - banana->z += banana->height; - - S_StartSound(banana, banana->info->deathsound); - P_KillMobj(banana, inflictor, source); - - P_SetObjectMomZ(banana, 8*FRACUNIT, false); - if (inflictor) - P_InstaThrust(banana, R_PointToAngle2(inflictor->x, inflictor->y, banana->x, banana->y)+ANGLE_90, 16*FRACUNIT); - } - - if ((banana = cachenext)) - goto killnext; -} - -// Just for firing/dropping items. -void K_UpdateHnextList(player_t *player, boolean clean) -{ - mobj_t *work = player->mo, *nextwork; - - if (!work) - return; - - nextwork = work->hnext; - - while ((work = nextwork) && !P_MobjWasRemoved(work)) - { - nextwork = work->hnext; - - if (!clean && (!work->movedir || work->movedir <= (UINT16)player->kartstuff[k_itemamount])) - continue; - - P_RemoveMobj(work); - } -} - -// For getting hit! -void K_DropHnextList(player_t *player) -{ - mobj_t *work = player->mo, *nextwork, *dropwork; - INT32 flip; - mobjtype_t type; - boolean orbit, ponground, dropall = true; - - if (!work || P_MobjWasRemoved(work)) - return; - - flip = P_MobjFlip(player->mo); - ponground = P_IsObjectOnGround(player->mo); - - if (player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD && player->kartstuff[k_itemamount]) - { - K_DoThunderShield(player); - player->kartstuff[k_itemamount] = 0; - player->kartstuff[k_itemtype] = KITEM_NONE; - player->kartstuff[k_curshield] = 0; - } - - nextwork = work->hnext; - - while ((work = nextwork) && !P_MobjWasRemoved(work)) - { - nextwork = work->hnext; - - switch (work->type) - { - // Kart orbit items - case MT_ORBINAUT_SHIELD: - orbit = true; - type = MT_ORBINAUT; - break; - case MT_JAWZ_SHIELD: - orbit = true; - type = MT_JAWZ_DUD; - break; - // Kart trailing items - case MT_BANANA_SHIELD: - orbit = false; - type = MT_BANANA; - break; - case MT_SSMINE_SHIELD: - orbit = false; - dropall = false; - type = MT_SSMINE; - break; - case MT_EGGMANITEM_SHIELD: - orbit = false; - type = MT_EGGMANITEM; - break; - // intentionally do nothing - case MT_SINK_SHIELD: - case MT_ROCKETSNEAKER: - return; - default: - continue; - } - - dropwork = P_SpawnMobj(work->x, work->y, work->z, type); - P_SetTarget(&dropwork->target, player->mo); - dropwork->angle = work->angle; - dropwork->flags2 = work->flags2; - dropwork->flags |= MF_NOCLIPTHING; - dropwork->floorz = work->floorz; - dropwork->ceilingz = work->ceilingz; - - if (ponground) - { - // floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn - // This should set it for FOFs - //P_TeleportMove(dropwork, dropwork->x, dropwork->y, dropwork->z); -- handled better by above floorz/ceilingz passing - - if (flip == 1) - { - if (dropwork->floorz > dropwork->target->z - dropwork->height) - { - dropwork->z = dropwork->floorz; - } - } - else - { - if (dropwork->ceilingz < dropwork->target->z + dropwork->target->height + dropwork->height) - { - dropwork->z = dropwork->ceilingz - dropwork->height; - } - } - } - - if (orbit) // splay out - { - dropwork->flags2 |= MF2_AMBUSH; - dropwork->z += flip; - dropwork->momx = player->mo->momx>>1; - dropwork->momy = player->mo->momy>>1; - dropwork->momz = 3*flip*mapobjectscale; - if (dropwork->eflags & MFE_UNDERWATER) - dropwork->momz = (117 * dropwork->momz) / 200; - P_Thrust(dropwork, work->angle - ANGLE_90, 6*mapobjectscale); - dropwork->movecount = 2; - dropwork->movedir = work->angle - ANGLE_90; - P_SetMobjState(dropwork, dropwork->info->deathstate); - dropwork->tics = -1; - if (type == MT_JAWZ_DUD) - dropwork->z += 20*flip*dropwork->scale; - else - { - dropwork->color = work->color; - dropwork->angle -= ANGLE_90; - } - } - else // plop on the ground - { - dropwork->flags &= ~MF_NOCLIPTHING; - dropwork->threshold = 10; - } - - P_RemoveMobj(work); - } - - { - // we need this here too because this is done in afterthink - pointers are cleaned up at the START of each tic... - P_SetTarget(&player->mo->hnext, NULL); - player->kartstuff[k_bananadrag] = 0; - if (player->kartstuff[k_eggmanheld]) - player->kartstuff[k_eggmanheld] = 0; - else if (player->kartstuff[k_itemheld] - && (dropall || (--player->kartstuff[k_itemamount] <= 0))) - { - player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0; - player->kartstuff[k_itemtype] = KITEM_NONE; - } - } -} - -// For getting EXTRA hit! -void K_DropItems(player_t *player) -{ - boolean thunderhack = (player->kartstuff[k_curshield] && player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD); - - if (thunderhack) - player->kartstuff[k_itemtype] = KITEM_NONE; - - K_DropHnextList(player); - - if (player->mo && !P_MobjWasRemoved(player->mo) && player->kartstuff[k_itemamount]) - { - mobj_t *drop = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FLOATINGITEM); - P_SetScale(drop, drop->scale>>4); - drop->destscale = (3*drop->destscale)/2;; - - drop->angle = player->mo->angle + ANGLE_90; - P_Thrust(drop, - FixedAngle(P_RandomFixed()*180) + player->mo->angle + ANGLE_90, - 16*mapobjectscale); - drop->momz = P_MobjFlip(player->mo)*3*mapobjectscale; - if (drop->eflags & MFE_UNDERWATER) - drop->momz = (117 * drop->momz) / 200; - - drop->threshold = (thunderhack ? KITEM_THUNDERSHIELD : player->kartstuff[k_itemtype]); - drop->movecount = player->kartstuff[k_itemamount]; - - drop->flags |= MF_NOCLIPTHING; - } - - K_StripItems(player); -} - -// When an item in the hnext chain dies. -void K_RepairOrbitChain(mobj_t *orbit) -{ - mobj_t *cachenext = orbit->hnext; - - // First, repair the chain - if (orbit->hnext && orbit->hnext->health && !P_MobjWasRemoved(orbit->hnext)) - { - P_SetTarget(&orbit->hnext->hprev, orbit->hprev); - P_SetTarget(&orbit->hnext, NULL); - } - - if (orbit->hprev && orbit->hprev->health && !P_MobjWasRemoved(orbit->hprev)) - { - P_SetTarget(&orbit->hprev->hnext, cachenext); - P_SetTarget(&orbit->hprev, NULL); - } - - // Then recount to make sure item amount is correct - if (orbit->target && orbit->target->player) - { - INT32 num = 0; - - mobj_t *cur = orbit->target->hnext; - mobj_t *prev = NULL; - - while (cur && !P_MobjWasRemoved(cur)) - { - prev = cur; - cur = cur->hnext; - if (++num > orbit->target->player->kartstuff[k_itemamount]) - P_RemoveMobj(prev); - else - prev->movedir = num; - } - - if (orbit->target->player->kartstuff[k_itemamount] != num) - orbit->target->player->kartstuff[k_itemamount] = num; - } -} - -// Simplified version of a code bit in P_MobjFloorZ -static fixed_t K_BananaSlopeZ(pslope_t *slope, fixed_t x, fixed_t y, fixed_t radius, boolean ceiling) -{ - fixed_t testx, testy; - - if (slope->d.x < 0) - testx = radius; - else - testx = -radius; - - if (slope->d.y < 0) - testy = radius; - else - testy = -radius; - - if ((slope->zdelta > 0) ^ !!(ceiling)) - { - testx = -testx; - testy = -testy; - } - - testx += x; - testy += y; - - return P_GetZAt(slope, testx, testy); -} - -static void K_CalculateBananaSlope(mobj_t *mobj, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t height, boolean flip, boolean player) -{ - fixed_t newz; - sector_t *sec; -#ifdef ESLOPE - pslope_t *slope = NULL; -#endif - - sec = R_PointInSubsector(x, y)->sector; - - if (flip) - { -#ifdef ESLOPE - if (sec->c_slope) - { - slope = sec->c_slope; - newz = K_BananaSlopeZ(slope, x, y, radius, true); - } - else -#endif - newz = sec->ceilingheight; - } - else - { -#ifdef ESLOPE - if (sec->f_slope) - { - slope = sec->f_slope; - newz = K_BananaSlopeZ(slope, x, y, radius, false); - } - else -#endif - newz = sec->floorheight; - } - - // Check FOFs for a better suited slope - if (sec->ffloors) - { - ffloor_t *rover; - - for (rover = sec->ffloors; rover; rover = rover->next) - { - fixed_t top, bottom; - fixed_t d1, d2; - - if (!(rover->flags & FF_EXISTS)) - continue; - - if ((!(((rover->flags & FF_BLOCKPLAYER && player) - || (rover->flags & FF_BLOCKOTHERS && !player)) - || (rover->flags & FF_QUICKSAND)) - || (rover->flags & FF_SWIMMABLE))) - continue; - -#ifdef ESLOPE - if (*rover->t_slope) - top = K_BananaSlopeZ(*rover->t_slope, x, y, radius, false); - else -#endif - top = *rover->topheight; - -#ifdef ESLOPE - if (*rover->b_slope) - bottom = K_BananaSlopeZ(*rover->b_slope, x, y, radius, true); - else -#endif - bottom = *rover->bottomheight; - - if (flip) - { - if (rover->flags & FF_QUICKSAND) - { - if (z < top && (z + height) > bottom) - { - if (newz > (z + height)) - { - newz = (z + height); - slope = NULL; - } - } - continue; - } - - d1 = (z + height) - (top + ((bottom - top)/2)); - d2 = z - (top + ((bottom - top)/2)); - - if (bottom < newz && abs(d1) < abs(d2)) - { - newz = bottom; -#ifdef ESLOPE - if (*rover->b_slope) - slope = *rover->b_slope; -#endif - } - } - else - { - if (rover->flags & FF_QUICKSAND) - { - if (z < top && (z + height) > bottom) - { - if (newz < z) - { - newz = z; - slope = NULL; - } - } - continue; - } - - d1 = z - (bottom + ((top - bottom)/2)); - d2 = (z + height) - (bottom + ((top - bottom)/2)); - - if (top > newz && abs(d1) < abs(d2)) - { - newz = top; -#ifdef ESLOPE - if (*rover->t_slope) - slope = *rover->t_slope; -#endif - } - } - } - } - -#if 0 - mobj->standingslope = slope; -#endif - -#ifdef HWRENDER - mobj->modeltilt = slope; -#endif -} - -// Move the hnext chain! -static void K_MoveHeldObjects(player_t *player) -{ - if (!player->mo) - return; - - if (!player->mo->hnext) - { - player->kartstuff[k_bananadrag] = 0; - if (player->kartstuff[k_eggmanheld]) - player->kartstuff[k_eggmanheld] = 0; - else if (player->kartstuff[k_itemheld]) - { - player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0; - player->kartstuff[k_itemtype] = KITEM_NONE; - } - return; - } - - if (P_MobjWasRemoved(player->mo->hnext)) - { - // we need this here too because this is done in afterthink - pointers are cleaned up at the START of each tic... - P_SetTarget(&player->mo->hnext, NULL); - player->kartstuff[k_bananadrag] = 0; - if (player->kartstuff[k_eggmanheld]) - player->kartstuff[k_eggmanheld] = 0; - else if (player->kartstuff[k_itemheld]) - { - player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0; - player->kartstuff[k_itemtype] = KITEM_NONE; - } - return; - } - - switch (player->mo->hnext->type) - { - case MT_ORBINAUT_SHIELD: // Kart orbit items - case MT_JAWZ_SHIELD: - { - mobj_t *cur = player->mo->hnext; - fixed_t speed = ((8 - min(4, player->kartstuff[k_itemamount])) * cur->info->speed) / 7; - - player->kartstuff[k_bananadrag] = 0; // Just to make sure - - while (cur && !P_MobjWasRemoved(cur)) - { - const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius); // mobj's distance from its Target, or Radius. - fixed_t z; - - if (!cur->health) - { - cur = cur->hnext; - continue; - } - - cur->color = player->skincolor; - - cur->angle -= ANGLE_90; - cur->angle += FixedAngle(speed); - - if (cur->extravalue1 < radius) - cur->extravalue1 += P_AproxDistance(cur->extravalue1, radius) / 12; - if (cur->extravalue1 > radius) - cur->extravalue1 = radius; - - // If the player is on the ceiling, then flip your items as well. - if (player && player->mo->eflags & MFE_VERTICALFLIP) - cur->eflags |= MFE_VERTICALFLIP; - else - cur->eflags &= ~MFE_VERTICALFLIP; - - // Shrink your items if the player shrunk too. - P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale))); - - if (P_MobjFlip(cur) > 0) - z = player->mo->z; - else - z = player->mo->z + player->mo->height - cur->height; - - cur->flags |= MF_NOCLIPTHING; // temporarily make them noclip other objects so they can't hit anyone while in the player - P_TeleportMove(cur, player->mo->x, player->mo->y, z); - cur->momx = FixedMul(FINECOSINE(cur->angle>>ANGLETOFINESHIFT), cur->extravalue1); - cur->momy = FixedMul(FINESINE(cur->angle>>ANGLETOFINESHIFT), cur->extravalue1); - cur->flags &= ~MF_NOCLIPTHING; - if (!P_TryMove(cur, player->mo->x + cur->momx, player->mo->y + cur->momy, true)) - P_SlideMove(cur, true); - if (P_IsObjectOnGround(player->mo)) - { - if (P_MobjFlip(cur) > 0) - { - if (cur->floorz > player->mo->z - cur->height) - z = cur->floorz; - } - else - { - if (cur->ceilingz < player->mo->z + player->mo->height + cur->height) - z = cur->ceilingz - cur->height; - } - } - - // Center it during the scale up animation - z += (FixedMul(mobjinfo[cur->type].height, player->mo->scale - cur->scale)>>1) * P_MobjFlip(cur); - - cur->z = z; - cur->momx = cur->momy = 0; - cur->angle += ANGLE_90; - - cur = cur->hnext; - } - } - break; - case MT_BANANA_SHIELD: // Kart trailing items - case MT_SSMINE_SHIELD: - case MT_EGGMANITEM_SHIELD: - case MT_SINK_SHIELD: - { - mobj_t *cur = player->mo->hnext; - mobj_t *targ = player->mo; - - if (P_IsObjectOnGround(player->mo) && player->speed > 0) - player->kartstuff[k_bananadrag]++; - - while (cur && !P_MobjWasRemoved(cur)) - { - const fixed_t radius = FixedHypot(targ->radius, targ->radius) + FixedHypot(cur->radius, cur->radius); - angle_t ang; - fixed_t targx, targy, targz; - fixed_t speed, dist; - - cur->flags &= ~MF_NOCLIPTHING; - - if (!cur->health) - { - cur = cur->hnext; - continue; - } - - if (cur->extravalue1 < radius) - cur->extravalue1 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12); - if (cur->extravalue1 > radius) - cur->extravalue1 = radius; - - if (cur != player->mo->hnext) - { - targ = cur->hprev; - dist = cur->extravalue1/4; - } - else - dist = cur->extravalue1/2; - - if (!targ || P_MobjWasRemoved(targ)) - continue; - - // Shrink your items if the player shrunk too. - P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale))); - - ang = targ->angle; - targx = targ->x + P_ReturnThrustX(cur, ang + ANGLE_180, dist); - targy = targ->y + P_ReturnThrustY(cur, ang + ANGLE_180, dist); - targz = targ->z; - - speed = FixedMul(R_PointToDist2(cur->x, cur->y, targx, targy), 3*FRACUNIT/4); - if (P_IsObjectOnGround(targ)) - targz = cur->floorz; - - cur->angle = R_PointToAngle2(cur->x, cur->y, targx, targy); - - /*if (P_IsObjectOnGround(player->mo) && player->speed > 0 && player->kartstuff[k_bananadrag] > TICRATE - && P_RandomChance(min(FRACUNIT/2, FixedDiv(player->speed, K_GetKartSpeed(player, false))/2))) - { - if (leveltime & 1) - targz += 8*(2*FRACUNIT)/7; - else - targz -= 8*(2*FRACUNIT)/7; - }*/ - - if (speed > dist) - P_InstaThrust(cur, cur->angle, speed-dist); - - P_SetObjectMomZ(cur, FixedMul(targz - cur->z, 7*FRACUNIT/8) - gravity, false); - - if (R_PointToDist2(cur->x, cur->y, targx, targy) > 768*FRACUNIT) - P_TeleportMove(cur, targx, targy, cur->z); - -#ifdef ESLOPE - if (P_IsObjectOnGround(cur)) - { - K_CalculateBananaSlope(cur, cur->x, cur->y, cur->z, - cur->radius, cur->height, (cur->eflags & MFE_VERTICALFLIP), false); - } -#endif - - cur = cur->hnext; - } - } - break; - case MT_ROCKETSNEAKER: // Special rocket sneaker stuff - { - mobj_t *cur = player->mo->hnext; - INT32 num = 0; - - while (cur && !P_MobjWasRemoved(cur)) - { - const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius); - boolean vibrate = ((leveltime & 1) && !cur->tracer); - angle_t angoffset; - fixed_t targx, targy, targz; - - cur->flags &= ~MF_NOCLIPTHING; - - if (player->kartstuff[k_rocketsneakertimer] <= TICRATE && (leveltime & 1)) - cur->flags2 |= MF2_DONTDRAW; - else - cur->flags2 &= ~MF2_DONTDRAW; - - if (num & 1) - P_SetMobjStateNF(cur, (vibrate ? S_ROCKETSNEAKER_LVIBRATE : S_ROCKETSNEAKER_L)); - else - P_SetMobjStateNF(cur, (vibrate ? S_ROCKETSNEAKER_RVIBRATE : S_ROCKETSNEAKER_R)); - - if (!player->kartstuff[k_rocketsneakertimer] || cur->extravalue2 || !cur->health) - { - num = (num+1) % 2; - cur = cur->hnext; - continue; - } - - if (cur->extravalue1 < radius) - cur->extravalue1 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12); - if (cur->extravalue1 > radius) - cur->extravalue1 = radius; - - // Shrink your items if the player shrunk too. - P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale))); - -#if 1 - { - angle_t input = player->frameangle - cur->angle; - boolean invert = (input > ANGLE_180); - if (invert) - input = InvAngle(input); - - input = FixedAngle(AngleFixed(input)/4); - if (invert) - input = InvAngle(input); - - cur->angle = cur->angle + input; - } -#else - cur->angle = player->frameangle; -#endif - - angoffset = ANGLE_90 + (ANGLE_180 * num); - - targx = player->mo->x + P_ReturnThrustX(cur, cur->angle + angoffset, cur->extravalue1); - targy = player->mo->y + P_ReturnThrustY(cur, cur->angle + angoffset, cur->extravalue1); - - { // bobbing, copy pasted from my kimokawaiii entry - const fixed_t pi = (22<mo->scale, 8 * FINESINE((((2*pi*(4*TICRATE)) * leveltime)>>ANGLETOFINESHIFT) & FINEMASK)); - targz = (player->mo->z + (player->mo->height/2)) + sine; - if (player->mo->eflags & MFE_VERTICALFLIP) - targz += (player->mo->height/2 - 32*player->mo->scale)*6; - - } - - if (cur->tracer) - { - fixed_t diffx, diffy, diffz; - - diffx = targx - cur->x; - diffy = targy - cur->y; - diffz = targz - cur->z; - - P_TeleportMove(cur->tracer, cur->tracer->x + diffx + P_ReturnThrustX(cur, cur->angle + angoffset, 6*cur->scale), - cur->tracer->y + diffy + P_ReturnThrustY(cur, cur->angle + angoffset, 6*cur->scale), cur->tracer->z + diffz); - P_SetScale(cur->tracer, (cur->tracer->destscale = 3*cur->scale/4)); - } - - P_TeleportMove(cur, targx, targy, targz); - K_FlipFromObject(cur, player->mo); // Update graviflip in real time thanks. -#ifdef HWRENDER - cur->modeltilt = player->mo->modeltilt; -#endif - num = (num+1) % 2; - cur = cur->hnext; - } - } - break; - default: - break; - } -} - -player_t *K_FindJawzTarget(mobj_t *actor, player_t *source) -{ - fixed_t best = -1; - player_t *wtarg = NULL; - INT32 i; - - for (i = 0; i < MAXPLAYERS; i++) - { - angle_t thisang; - player_t *player; - - if (!playeringame[i]) - continue; - - player = &players[i]; - - if (player->spectator) - continue; // spectator - - if (!player->mo) - continue; - - if (player->mo->health <= 0) - continue; // dead - - // Don't target yourself, stupid. - if (player == source) - continue; - - // Don't home in on teammates. - if (G_GametypeHasTeams() && source->ctfteam == player->ctfteam) - continue; - - // Invisible, don't bother - if (player->kartstuff[k_hyudorotimer]) - continue; - - // Find the angle, see who's got the best. - thisang = actor->angle - R_PointToAngle2(actor->x, actor->y, player->mo->x, player->mo->y); - if (thisang > ANGLE_180) - thisang = InvAngle(thisang); - - // Jawz only go after the person directly ahead of you in race... sort of literally now! - if (G_RaceGametype()) - { - // Don't go for people who are behind you - if (thisang > ANGLE_67h) - continue; - // Don't pay attention to people who aren't above your position - if (player->kartstuff[k_position] >= source->kartstuff[k_position]) - continue; - if ((best == -1) || (player->kartstuff[k_position] > best)) - { - wtarg = player; - best = player->kartstuff[k_position]; - } - } - else - { - fixed_t thisdist; - fixed_t thisavg; - - // Don't go for people who are behind you - if (thisang > ANGLE_45) - continue; - - // Don't pay attention to dead players - if (player->kartstuff[k_bumper] <= 0) - continue; - - // Z pos too high/low - if (abs(player->mo->z - (actor->z + actor->momz)) > RING_DIST/8) - continue; - - thisdist = P_AproxDistance(player->mo->x - (actor->x + actor->momx), player->mo->y - (actor->y + actor->momy)); - - if (thisdist > 2*RING_DIST) // Don't go for people who are too far away - continue; - - thisavg = (AngleFixed(thisang) + thisdist) / 2; - - //CONS_Printf("got avg %d from player # %d\n", thisavg>>FRACBITS, i); - - if ((best == -1) || (thisavg < best)) - { - wtarg = player; - best = thisavg; - } - } - } - - return wtarg; -} - -// Engine Sounds. -static void K_UpdateEngineSounds(player_t *player, ticcmd_t *cmd) -{ - const INT32 numsnds = 13; - INT32 class, s, w; // engine class number - UINT8 volume = 255; - fixed_t volumedampen = 0; - INT32 targetsnd = 0; - INT32 i; - - s = (player->kartspeed-1)/3; - w = (player->kartweight-1)/3; - -#define LOCKSTAT(stat) \ - if (stat < 0) { stat = 0; } \ - if (stat > 2) { stat = 2; } - LOCKSTAT(s); - LOCKSTAT(w); -#undef LOCKSTAT - - class = s+(3*w); - - // Silence the engines - if (leveltime < 8 || player->spectator || player->exiting) - { - player->karthud[khud_enginesnd] = 0; // Reset sound number - return; - } - -#if 0 - if ((leveltime % 8) != ((player-players) % 8)) // Per-player offset, to make engines sound distinct! -#else - if (leveltime % 8) // .25 seconds of wait time between engine sounds -#endif - return; - - if ((leveltime >= starttime-(2*TICRATE) && leveltime <= starttime) || (player->kartstuff[k_respawn] == 1)) // Startup boosts - targetsnd = ((cmd->buttons & BT_ACCELERATE) ? 12 : 0); - else - targetsnd = (((6*cmd->forwardmove)/25) + ((player->speed / mapobjectscale)/5))/2; - - if (targetsnd < 0) - targetsnd = 0; - if (targetsnd > 12) - targetsnd = 12; - - if (player->karthud[khud_enginesnd] < targetsnd) - player->karthud[khud_enginesnd]++; - if (player->karthud[khud_enginesnd] > targetsnd) - player->karthud[khud_enginesnd]--; - - if (player->karthud[khud_enginesnd] < 0) - player->karthud[khud_enginesnd] = 0; - if (player->karthud[khud_enginesnd] > 12) - player->karthud[khud_enginesnd] = 12; - - for (i = 0; i < MAXPLAYERS; i++) - { - UINT8 thisvol = 0; - fixed_t dist; - - if (!playeringame[i] || !players[i].mo || players[i].spectator || players[i].exiting) - continue; - - if (P_IsDisplayPlayer(&players[i])) - { - volumedampen += FRACUNIT; // We already know what this is gonna be, let's not waste our time. - continue; - } - - dist = P_AproxDistance(P_AproxDistance(player->mo->x-players[i].mo->x, - player->mo->y-players[i].mo->y), player->mo->z-players[i].mo->z) / 2; - - dist = FixedDiv(dist, mapobjectscale); - - if (dist > 1536<>FRACBITS)) / (((1536<>(FRACBITS+4)); - - if (thisvol == 0) - continue; - - volumedampen += (thisvol * 257); // 255 * 257 = FRACUNIT - } - - if (volumedampen > FRACUNIT) - volume = FixedDiv(volume<>FRACBITS; - - if (volume <= 0) // Might as well - return; - - S_StartSoundAtVolume(player->mo, (sfx_krta00 + player->karthud[khud_enginesnd]) + (class*numsnds), volume); -} - -static void K_UpdateInvincibilitySounds(player_t *player) -{ - INT32 sfxnum = sfx_None; - - if (player->mo->health > 0 && !P_IsDisplayPlayer(player)) - { - if (cv_kartinvinsfx.value) - { - if (player->kartstuff[k_growshrinktimer] > 0) // Prioritize Grow - sfxnum = sfx_alarmg; - else if (player->kartstuff[k_invincibilitytimer] > 0) - sfxnum = sfx_alarmi; - } - else - { - if (player->kartstuff[k_growshrinktimer] > 0) - sfxnum = sfx_kgrow; - else if (player->kartstuff[k_invincibilitytimer] > 0) - sfxnum = sfx_kinvnc; - } - } - - if (sfxnum != sfx_None && !S_SoundPlaying(player->mo, sfxnum)) - S_StartSound(player->mo, sfxnum); - -#define STOPTHIS(this) \ - if (sfxnum != this && S_SoundPlaying(player->mo, this)) \ - S_StopSoundByID(player->mo, this); - STOPTHIS(sfx_alarmi); - STOPTHIS(sfx_alarmg); - STOPTHIS(sfx_kinvnc); - STOPTHIS(sfx_kgrow); -#undef STOPTHIS -} - -#define RINGANIM_NUMFRAMES 10 -#define RINGANIM_DELAYMAX 5 - -void K_KartPlayerHUDUpdate(player_t *player) -{ - if (player->karthud[khud_lapanimation]) - player->karthud[khud_lapanimation]--; - - if (player->karthud[khud_yougotem]) - player->karthud[khud_yougotem]--; - - if (player->karthud[khud_voices]) - player->karthud[khud_voices]--; - - if (player->karthud[khud_tauntvoices]) - player->karthud[khud_tauntvoices]--; - - if (G_RaceGametype()) - { - // 0 is the fast spin animation, set at 30 tics of ring boost or higher! - if (player->kartstuff[k_ringboost] >= 30) - player->karthud[khud_ringdelay] = 0; - else - player->karthud[khud_ringdelay] = ((RINGANIM_DELAYMAX+1) * (30 - player->kartstuff[k_ringboost])) / 30; - - if (player->karthud[khud_ringframe] == 0 && player->karthud[khud_ringdelay] > RINGANIM_DELAYMAX) - { - player->karthud[khud_ringframe] = 0; - player->karthud[khud_ringtics] = 0; - } - else if ((player->karthud[khud_ringtics]--) <= 0) - { - if (player->karthud[khud_ringdelay] == 0) // fast spin animation - { - player->karthud[khud_ringframe] = ((player->karthud[khud_ringframe]+2) % RINGANIM_NUMFRAMES); - player->karthud[khud_ringtics] = 0; - } - else - { - player->karthud[khud_ringframe] = ((player->karthud[khud_ringframe]+1) % RINGANIM_NUMFRAMES); - player->karthud[khud_ringtics] = min(RINGANIM_DELAYMAX, player->karthud[khud_ringdelay])-1; - } - } - - if (player->kartstuff[k_ringlock]) - { - UINT8 normalanim = (leveltime % 14); - UINT8 debtanim = 14 + (leveltime % 2); - - if (player->karthud[khud_ringspblock] >= 14) // debt animation - { - if ((player->kartstuff[k_rings] > 0) // Get out of 0 ring animation - && (normalanim == 3 || normalanim == 10)) // on these transition frames. - player->karthud[khud_ringspblock] = normalanim; - else - player->karthud[khud_ringspblock] = debtanim; - } - else // normal animation - { - if ((player->kartstuff[k_rings] <= 0) // Go into 0 ring animation - && (player->karthud[khud_ringspblock] == 1 || player->karthud[khud_ringspblock] == 8)) // on these transition frames. - player->karthud[khud_ringspblock] = debtanim; - else - player->karthud[khud_ringspblock] = normalanim; - } - } - else - player->karthud[khud_ringspblock] = (leveltime % 14); // reset to normal anim next time - } - - if (G_BattleGametype() && (player->exiting || player->kartstuff[k_comebacktimer])) - { - if (player->exiting) - { - if (player->exiting < 6*TICRATE) - player->karthud[khud_cardanimation] += ((164-player->karthud[khud_cardanimation])/8)+1; - else if (player->exiting == 6*TICRATE) - player->karthud[khud_cardanimation] = 0; - else if (player->karthud[khud_cardanimation] < 2*TICRATE) - player->karthud[khud_cardanimation]++; - } - else - { - if (player->kartstuff[k_comebacktimer] < 6*TICRATE) - player->karthud[khud_cardanimation] -= ((164-player->karthud[khud_cardanimation])/8)+1; - else if (player->kartstuff[k_comebacktimer] < 9*TICRATE) - player->karthud[khud_cardanimation] += ((164-player->karthud[khud_cardanimation])/8)+1; - } - - if (player->karthud[khud_cardanimation] > 164) - player->karthud[khud_cardanimation] = 164; - if (player->karthud[khud_cardanimation] < 0) - player->karthud[khud_cardanimation] = 0; - } - else if (G_RaceGametype() && player->exiting) - { - if (player->karthud[khud_cardanimation] < 2*TICRATE) - player->karthud[khud_cardanimation]++; - } - else - player->karthud[khud_cardanimation] = 0; -} - -#undef RINGANIM_DELAYMAX - -/** \brief Decreases various kart timers and powers per frame. Called in P_PlayerThink in p_user.c - - \param player player object passed from P_PlayerThink - \param cmd control input from player - - \return void -*/ -void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) -{ - K_UpdateOffroad(player); - K_UpdateDraft(player); - K_UpdateEngineSounds(player, cmd); // Thanks, VAda! - - // update boost angle if not spun out - if (!player->kartstuff[k_spinouttimer] && !player->kartstuff[k_wipeoutslow]) - player->kartstuff[k_boostangle] = (INT32)player->mo->angle; - - K_GetKartBoostPower(player); - - // Special effect objects! - if (player->mo && !player->spectator) - { - if (player->kartstuff[k_dashpadcooldown]) // Twinkle Circuit afterimages - { - mobj_t *ghost; - ghost = P_SpawnGhostMobj(player->mo); - ghost->fuse = player->kartstuff[k_dashpadcooldown]+1; - ghost->momx = player->mo->momx / (player->kartstuff[k_dashpadcooldown]+1); - ghost->momy = player->mo->momy / (player->kartstuff[k_dashpadcooldown]+1); - ghost->momz = player->mo->momz / (player->kartstuff[k_dashpadcooldown]+1); - player->kartstuff[k_dashpadcooldown]--; - } - - if (player->speed > 0) - { - // Speed lines - if (EITHERSNEAKER(player) || player->kartstuff[k_ringboost] - || player->kartstuff[k_driftboost] || player->kartstuff[k_startboost]) - { - mobj_t *fast = P_SpawnMobj(player->mo->x + (P_RandomRange(-36,36) * player->mo->scale), - player->mo->y + (P_RandomRange(-36,36) * player->mo->scale), - player->mo->z + (player->mo->height/2) + (P_RandomRange(-20,20) * player->mo->scale), - MT_FASTLINE); - fast->angle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); - fast->momx = 3*player->mo->momx/4; - fast->momy = 3*player->mo->momy/4; - fast->momz = 3*player->mo->momz/4; - K_MatchGenericExtraFlags(fast, player->mo); - } - - if (player->kartstuff[k_numboosts] > 0) // Boosting after images - { - mobj_t *ghost; - ghost = P_SpawnGhostMobj(player->mo); - ghost->extravalue1 = player->kartstuff[k_numboosts]+1; - ghost->extravalue2 = (leveltime % ghost->extravalue1); - ghost->fuse = ghost->extravalue1; - ghost->frame |= FF_FULLBRIGHT; - ghost->colorized = true; - //ghost->color = player->skincolor; - //ghost->momx = (3*player->mo->momx)/4; - //ghost->momy = (3*player->mo->momy)/4; - //ghost->momz = (3*player->mo->momz)/4; - if (leveltime & 1) - ghost->flags2 |= MF2_DONTDRAW; - } - - if (P_IsObjectOnGround(player->mo)) - { - // Offroad dust - if (player->kartstuff[k_boostpower] < FRACUNIT) - { - K_SpawnWipeoutTrail(player->mo, true); - if (leveltime % 6 == 0) - S_StartSound(player->mo, sfx_cdfm70); - } - - // Draft dust - if (player->kartstuff[k_draftpower] >= FRACUNIT) - { - K_SpawnDraftDust(player->mo); - /*if (leveltime % 23 == 0 || !S_SoundPlaying(player->mo, sfx_s265)) - S_StartSound(player->mo, sfx_s265);*/ - } - } - } - - if (G_RaceGametype() && player->kartstuff[k_rings] <= 0) // spawn ring debt indicator - { - mobj_t *debtflag = P_SpawnMobj(player->mo->x + player->mo->momx, player->mo->y + player->mo->momy, - player->mo->z + player->mo->momz + player->mo->height + (24*player->mo->scale), MT_THOK); - P_SetMobjState(debtflag, S_RINGDEBT); - P_SetScale(debtflag, (debtflag->destscale = player->mo->scale)); - K_MatchGenericExtraFlags(debtflag, player->mo); - debtflag->frame += (leveltime % 4); - if ((leveltime/12) & 1) - debtflag->frame += 4; - debtflag->color = player->skincolor; - debtflag->fuse = 2; - if (P_IsDisplayPlayer(player)) - debtflag->flags2 |= MF2_DONTDRAW; - } - - if (player->kartstuff[k_springstars] && (leveltime & 1)) - { - fixed_t randx = P_RandomRange(-40, 40) * player->mo->scale; - fixed_t randy = P_RandomRange(-40, 40) * player->mo->scale; - fixed_t randz = P_RandomRange(0, player->mo->height >> FRACBITS) << FRACBITS; - mobj_t *star = P_SpawnMobj( - player->mo->x + randx, - player->mo->y + randy, - player->mo->z + randz, - MT_KARMAFIREWORK); - - star->color = player->kartstuff[k_springcolor]; - star->flags |= MF_NOGRAVITY; - star->momx = player->mo->momx / 2; - star->momy = player->mo->momy / 2; - star->momz = player->mo->momz / 2; - star->fuse = 12; - star->scale = player->mo->scale; - star->destscale = star->scale / 2; - - player->kartstuff[k_springstars]--; - } - } - - if (player->playerstate == PST_DEAD || player->kartstuff[k_respawn] > 1) // Ensure these are set correctly here - { - player->mo->colorized = false; - player->mo->color = player->skincolor; - } - else if (player->kartstuff[k_eggmanexplode]) // You're gonna diiiiie - { - const INT32 flashtime = 4<<(player->kartstuff[k_eggmanexplode]/TICRATE); - if (player->kartstuff[k_eggmanexplode] == 1 || (player->kartstuff[k_eggmanexplode] % (flashtime/2) != 0)) - { - player->mo->colorized = false; - player->mo->color = player->skincolor; - } - else if (player->kartstuff[k_eggmanexplode] % flashtime == 0) - { - player->mo->colorized = true; - player->mo->color = SKINCOLOR_BLACK; - } - else - { - player->mo->colorized = true; - player->mo->color = SKINCOLOR_CRIMSON; - } - } - else if (player->kartstuff[k_invincibilitytimer]) // setting players to use the star colormap and spawning afterimages - { - player->mo->colorized = true; - } - else if (player->kartstuff[k_growshrinktimer]) // Ditto, for grow/shrink - { - if (player->kartstuff[k_growshrinktimer] % 5 == 0) - { - player->mo->colorized = true; - player->mo->color = (player->kartstuff[k_growshrinktimer] < 0 ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE); - } - else - { - player->mo->colorized = false; - player->mo->color = player->skincolor; - } + K_CheckBumpers(); + } + } + } +} + +/** \brief Handles the state changing for moving players, moved here to eliminate duplicate code + + \param player player data + + \return void +*/ +void K_KartMoveAnimation(player_t *player) +{ + ticcmd_t *cmd = &player->cmd; + // Standing frames - S_KART_STND1 S_KART_STND1_L S_KART_STND1_R + if (player->speed == 0) + { + if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_STND1_R] && player->mo->state <= &states[S_KART_STND2_R])) + P_SetPlayerMobjState(player->mo, S_KART_STND1_R); + else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_STND1_L] && player->mo->state <= &states[S_KART_STND2_L])) + P_SetPlayerMobjState(player->mo, S_KART_STND1_L); + else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_STND1] && player->mo->state <= &states[S_KART_STND2])) + P_SetPlayerMobjState(player->mo, S_KART_STND1); + } + // Drifting Left - S_KART_DRIFT1_L + else if (player->kartstuff[k_drift] > 0 && P_IsObjectOnGround(player->mo)) + { + if (!(player->mo->state >= &states[S_KART_DRIFT1_L] && player->mo->state <= &states[S_KART_DRIFT2_L])) + P_SetPlayerMobjState(player->mo, S_KART_DRIFT1_L); + } + // Drifting Right - S_KART_DRIFT1_R + else if (player->kartstuff[k_drift] < 0 && P_IsObjectOnGround(player->mo)) + { + if (!(player->mo->state >= &states[S_KART_DRIFT1_R] && player->mo->state <= &states[S_KART_DRIFT2_R])) + P_SetPlayerMobjState(player->mo, S_KART_DRIFT1_R); + } + // Run frames - S_KART_RUN1 S_KART_RUN1_L S_KART_RUN1_R + else if (player->speed > (20*player->mo->scale)) + { + if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_RUN1_R] && player->mo->state <= &states[S_KART_RUN2_R])) + P_SetPlayerMobjState(player->mo, S_KART_RUN1_R); + else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_RUN1_L] && player->mo->state <= &states[S_KART_RUN2_L])) + P_SetPlayerMobjState(player->mo, S_KART_RUN1_L); + else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_RUN1] && player->mo->state <= &states[S_KART_RUN2])) + P_SetPlayerMobjState(player->mo, S_KART_RUN1); + } + // Walk frames - S_KART_WALK1 S_KART_WALK1_L S_KART_WALK1_R + else if (player->speed <= (20*player->mo->scale)) + { + if (cmd->driftturn < 0 && !(player->mo->state >= &states[S_KART_WALK1_R] && player->mo->state <= &states[S_KART_WALK2_R])) + P_SetPlayerMobjState(player->mo, S_KART_WALK1_R); + else if (cmd->driftturn > 0 && !(player->mo->state >= &states[S_KART_WALK1_L] && player->mo->state <= &states[S_KART_WALK2_L])) + P_SetPlayerMobjState(player->mo, S_KART_WALK1_L); + else if (cmd->driftturn == 0 && !(player->mo->state >= &states[S_KART_WALK1] && player->mo->state <= &states[S_KART_WALK2])) + P_SetPlayerMobjState(player->mo, S_KART_WALK1); + } +} + +static void K_TauntVoiceTimers(player_t *player) +{ + if (!player) + return; + + player->karthud[khud_tauntvoices] = 6*TICRATE; + player->karthud[khud_voices] = 4*TICRATE; +} + +static void K_RegularVoiceTimers(player_t *player) +{ + if (!player) + return; + + player->karthud[khud_voices] = 4*TICRATE; + + if (player->karthud[khud_tauntvoices] < 4*TICRATE) + player->karthud[khud_tauntvoices] = 4*TICRATE; +} + +void K_PlayAttackTaunt(mobj_t *source) +{ + sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons + boolean tasteful = (!source->player || !source->player->karthud[khud_tauntvoices]); + + if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2)) + S_StartSound(source, sfx_kattk1+pick); + + if (!tasteful) + return; + + K_TauntVoiceTimers(source->player); +} + +void K_PlayBoostTaunt(mobj_t *source) +{ + sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons + boolean tasteful = (!source->player || !source->player->karthud[khud_tauntvoices]); + + if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2)) + S_StartSound(source, sfx_kbost1+pick); + + if (!tasteful) + return; + + K_TauntVoiceTimers(source->player); +} + +void K_PlayOvertakeSound(mobj_t *source) +{ + boolean tasteful = (!source->player || !source->player->karthud[khud_voices]); + + if (!G_RaceGametype()) // Only in race + return; + + // 4 seconds from before race begins, 10 seconds afterwards + if (leveltime < starttime+(10*TICRATE)) + return; + + if (cv_kartvoices.value && (tasteful || cv_kartvoices.value == 2)) + S_StartSound(source, sfx_kslow); + + if (!tasteful) + return; + + K_RegularVoiceTimers(source->player); +} + +void K_PlayPainSound(mobj_t *source) +{ + sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons + + if (cv_kartvoices.value) + S_StartSound(source, sfx_khurt1 + pick); + + K_RegularVoiceTimers(source->player); +} + +void K_PlayHitEmSound(mobj_t *source) +{ + if (cv_kartvoices.value) + S_StartSound(source, sfx_khitem); + else + S_StartSound(source, sfx_s1c9); // The only lost gameplay functionality with voices disabled + + K_RegularVoiceTimers(source->player); +} + +void K_PlayPowerGloatSound(mobj_t *source) +{ + if (cv_kartvoices.value) + S_StartSound(source, sfx_kgloat); + + K_RegularVoiceTimers(source->player); +} + +void K_MomentumToFacing(player_t *player) +{ + angle_t dangle = player->mo->angle - R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); + + if (dangle > ANGLE_180) + dangle = InvAngle(dangle); + + // If you aren't on the ground or are moving in too different of a direction don't do this + if (player->mo->eflags & MFE_JUSTHITFLOOR) + ; // Just hit floor ALWAYS redirects + else if (!P_IsObjectOnGround(player->mo) || dangle > ANGLE_90) + return; + + P_Thrust(player->mo, player->mo->angle, player->speed - FixedMul(player->speed, player->mo->friction)); + player->mo->momx = FixedMul(player->mo->momx - player->cmomx, player->mo->friction) + player->cmomx; + player->mo->momy = FixedMul(player->mo->momy - player->cmomy, player->mo->friction) + player->cmomy; +} + +// sets k_boostpower, k_speedboost, and k_accelboost to whatever we need it to be +static void K_GetKartBoostPower(player_t *player) +{ + fixed_t boostpower = FRACUNIT; + fixed_t speedboost = 0, accelboost = 0; + UINT8 numboosts = 0; + + if (player->kartstuff[k_spinouttimer] && player->kartstuff[k_wipeoutslow] == 1) // Slow down after you've been bumped + { + player->kartstuff[k_boostpower] = player->kartstuff[k_speedboost] = player->kartstuff[k_accelboost] = 0; + return; + } + + // Offroad is separate, it's difficult to factor it in with a variable value anyway. + if (!(player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_hyudorotimer] || EITHERSNEAKER(player)) + && player->kartstuff[k_offroad] >= 0) + boostpower = FixedDiv(boostpower, FixedMul(player->kartstuff[k_offroad], K_GetKartGameSpeedScalar(gamespeed)) + FRACUNIT); + + if (player->kartstuff[k_bananadrag] > TICRATE) + boostpower = (4*boostpower)/5; + +#define ADDBOOST(s,a) { \ + numboosts++; \ + speedboost += (s) / numboosts; \ + accelboost += (a) / numboosts; \ +} + + if (player->kartstuff[k_levelbooster]) // Level boosters + ADDBOOST(FRACUNIT/2, 8*FRACUNIT); // + 50% top speed, + 800% acceleration + + if (player->kartstuff[k_sneakertimer]) // Sneaker + ADDBOOST(FRACUNIT/2, 8*FRACUNIT); // + 50% top speed, + 800% acceleration + + if (player->kartstuff[k_invincibilitytimer]) // Invincibility + ADDBOOST((3*FRACUNIT)/8, 3*FRACUNIT); // + 37.5% top speed, + 300% acceleration + + if (player->kartstuff[k_startboost]) // Startup Boost + ADDBOOST(FRACUNIT/4, 6*FRACUNIT); // + 25% top speed, + 600% acceleration + + if (player->kartstuff[k_driftboost]) // Drift Boost + ADDBOOST(FRACUNIT/4, 4*FRACUNIT); // + 25% top speed, + 400% acceleration + + if (player->kartstuff[k_ringboost]) // Ring Boost + ADDBOOST(FRACUNIT/5, 4*FRACUNIT); // + 20% top speed, + 400% acceleration + + if (player->kartstuff[k_growshrinktimer] > 0) // Grow + { + // Grow's design is weird with booster stacking. + // We'll see how to replace its design BEFORE v2 gets released. + speedboost += (FRACUNIT/5); // + 20% + //numboosts++; // Don't add any boost afterimages to Grow + } + + if (player->kartstuff[k_draftpower] > 0) // Drafting + { + fixed_t draftspeed = ((3*FRACUNIT)/10) + ((player->kartspeed-1) * (FRACUNIT/50)); // min is 30%, max is 46% + speedboost += FixedMul(draftspeed, player->kartstuff[k_draftpower]); // (Drafting suffers no boost stack penalty.) + numboosts++; + } + + player->kartstuff[k_boostpower] = boostpower; + + // value smoothing + if (speedboost > player->kartstuff[k_speedboost]) + player->kartstuff[k_speedboost] = speedboost; + else + player->kartstuff[k_speedboost] += (speedboost - player->kartstuff[k_speedboost]) / (TICRATE/2); + + player->kartstuff[k_accelboost] = accelboost; + player->kartstuff[k_numboosts] = numboosts; +} + +fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower) +{ + const fixed_t xspd = (3*FRACUNIT)/64; + fixed_t g_cc = K_GetKartGameSpeedScalar(gamespeed) + xspd; + fixed_t k_speed = 150; + UINT8 kartspeed = player->kartspeed; + fixed_t finalspeed; + + if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) + kartspeed = 1; + + k_speed += kartspeed*3; // 153 - 177 + + finalspeed = FixedMul(k_speed<<14, g_cc); + + if (player->mo && !P_MobjWasRemoved(player->mo)) + finalspeed = FixedMul(finalspeed, player->mo->scale); + + if (doboostpower) + return FixedMul(finalspeed, player->kartstuff[k_boostpower]+player->kartstuff[k_speedboost]); + return finalspeed; +} + +fixed_t K_GetKartAccel(player_t *player) +{ + fixed_t k_accel = 32; // 36; + UINT8 kartspeed = player->kartspeed; + + if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) + kartspeed = 1; + + //k_accel += 3 * (9 - kartspeed); // 36 - 60 + k_accel += 4 * (9 - kartspeed); // 32 - 64 + + return FixedMul(k_accel, FRACUNIT+player->kartstuff[k_accelboost]); +} + +UINT16 K_GetKartFlashing(player_t *player) +{ + UINT16 tics = flashingtics; + + if (!player) + return tics; + + if (G_BattleGametype()) + tics *= 2; + + tics += (flashingtics/8) * (player->kartspeed); + + return tics; +} + +fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove) +{ + fixed_t accelmax = 4000; + fixed_t newspeed, oldspeed, finalspeed; + fixed_t p_speed = K_GetKartSpeed(player, true); + fixed_t p_accel = K_GetKartAccel(player); + + if (!onground) return 0; // If the player isn't on the ground, there is no change in speed + + // ACCELCODE!!!1!11! + oldspeed = R_PointToDist2(0, 0, player->rmomx, player->rmomy); // FixedMul(P_AproxDistance(player->rmomx, player->rmomy), player->mo->scale); + // Don't calculate the acceleration as ever being above top speed + if (oldspeed > p_speed) + oldspeed = p_speed; + newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), ORIG_FRICTION); + + if (player->kartstuff[k_pogospring]) // Pogo Spring minimum/maximum thrust + { + const fixed_t hscale = mapobjectscale /*+ (mapobjectscale - player->mo->scale)*/; + const fixed_t minspeed = 24*hscale; + const fixed_t maxspeed = 28*hscale; + + if (newspeed > maxspeed && player->kartstuff[k_pogospring] == 2) + newspeed = maxspeed; + if (newspeed < minspeed) + newspeed = minspeed; + } + + finalspeed = newspeed - oldspeed; + + // forwardmove is: + // 50 while accelerating, + // 25 while clutching, + // 0 with no gas, and + // -25 when only braking. + + finalspeed *= forwardmove/25; + finalspeed /= 2; + + if (forwardmove < 0 && finalspeed > mapobjectscale*2) + return finalspeed/2; + else if (forwardmove < 0) + return -mapobjectscale/2; + + if (finalspeed < 0) + finalspeed = 0; + + return finalspeed; +} + +void K_DoInstashield(player_t *player) +{ + mobj_t *layera; + mobj_t *layerb; + + if (player->kartstuff[k_instashield] > 0) + return; + + player->kartstuff[k_instashield] = 15; // length of instashield animation + S_StartSound(player->mo, sfx_cdpcm9); + + layera = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDA); + P_SetTarget(&layera->target, player->mo); + + layerb = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INSTASHIELDB); + P_SetTarget(&layerb->target, player->mo); +} + +void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount) +{ + statenum_t st; + mobj_t *pt; + + if (!source || !source->mo) + return; + + if (amount == 1) + st = S_BATTLEPOINT1A; + else if (amount == 2) + st = S_BATTLEPOINT2A; + else if (amount == 3) + st = S_BATTLEPOINT3A; + else + return; // NO STATE! + + pt = P_SpawnMobj(source->mo->x, source->mo->y, source->mo->z, MT_BATTLEPOINT); + P_SetTarget(&pt->target, source->mo); + P_SetMobjState(pt, st); + if (victim && victim->skincolor) + pt->color = victim->skincolor; + else + pt->color = source->skincolor; +} + +void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, mobj_t *inflictor, boolean trapitem) +{ + UINT8 scoremultiply = 1; + // PS: Inflictor is unused for all purposes here and is actually only ever relevant to Lua. It may be nil too. +#ifdef HAVE_BLUA + boolean force = false; // Used to check if Lua ShouldSpin should get us damaged reguardless of flashtics or heck knows what. + UINT8 shouldForce = LUAh_ShouldSpin(player, inflictor, source); + if (P_MobjWasRemoved(player->mo)) + return; // mobj was removed (in theory that shouldn't happen) + if (shouldForce == 1) + force = true; + else if (shouldForce == 2) + return; +#else + static const boolean force = false; + (void)inflictor; // in case some weirdo doesn't want Lua. +#endif + + if (!trapitem && G_BattleGametype()) + { + if (K_IsPlayerWanted(player)) + scoremultiply = 3; + else if (player->kartstuff[k_bumper] == 1) + scoremultiply = 2; + } + + if (player->health <= 0) + return; + + if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || (player->kartstuff[k_spinouttimer] > 0 && player->kartstuff[k_spinouttype] != 2) + || player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0 + || (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1))) + { + if (!force) // if shoulddamage force, we go THROUGH that. + { + K_DoInstashield(player); + return; + } + } + +#ifdef HAVE_BLUA + if (LUAh_PlayerSpin(player, inflictor, source)) // Let Lua do its thing or overwrite if it wants to. Make sure to let any possible instashield happen because we didn't get "damaged" in this case. + return; +#endif + + if (source && source != player->mo && source->player) + K_PlayHitEmSound(source); + + player->kartstuff[k_sneakertimer] = 0; + //player->kartstuff[k_levelbooster] = 0; + player->kartstuff[k_driftboost] = 0; + player->kartstuff[k_ringboost] = 0; + + player->kartstuff[k_drift] = 0; + player->kartstuff[k_driftcharge] = 0; + player->kartstuff[k_pogospring] = 0; + + if (G_BattleGametype()) + { + if (source && source->player && player != source->player) + { + P_AddPlayerScore(source->player, scoremultiply); + K_SpawnBattlePoints(source->player, player, scoremultiply); + if (!trapitem) + { + source->player->kartstuff[k_wanted] -= wantedreduce; + player->kartstuff[k_wanted] -= (wantedreduce/2); + } + } + + if (player->kartstuff[k_bumper] > 0) + { + if (player->kartstuff[k_bumper] == 1) + { + mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!! + P_SetTarget(&karmahitbox->target, player->mo); + karmahitbox->destscale = player->mo->scale; + P_SetScale(karmahitbox, player->mo->scale); + CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]); + } + player->kartstuff[k_bumper]--; + if (K_IsPlayerWanted(player)) + K_CalculateBattleWanted(); + } + + if (!player->kartstuff[k_bumper]) + { + player->kartstuff[k_comebacktimer] = comebacktime; + if (player->kartstuff[k_comebackmode] == 2) + { + mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE); + S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound); + player->kartstuff[k_comebackmode] = 0; + } + } + + K_CheckBumpers(); + } + + player->kartstuff[k_spinouttype] = type; + + if (player->kartstuff[k_spinouttype] <= 0) // type 0 is spinout, type 1 is wipeout, type 2 is no-invuln wipeout + { + // At spinout, player speed is increased to 1/4 their regular speed, moving them forward + if (player->speed < K_GetKartSpeed(player, true)/4) + P_InstaThrust(player->mo, player->mo->angle, FixedMul(K_GetKartSpeed(player, true)/4, player->mo->scale)); + S_StartSound(player->mo, sfx_slip); + } + + player->kartstuff[k_spinouttimer] = (3*TICRATE/2)+2; + player->powers[pw_flashing] = K_GetKartFlashing(player); + + P_PlayRinglossSound(player->mo); + P_PlayerRingBurst(player, 5); + K_PlayPainSound(player->mo); + + if (player->mo->state != &states[S_KART_SPIN]) + P_SetPlayerMobjState(player->mo, S_KART_SPIN); + + player->kartstuff[k_instashield] = 15; + if (cv_kartdebughuddrop.value && !modeattacking) + K_DropItems(player); + else + K_DropHnextList(player); + return; +} + +static void K_RemoveGrowShrink(player_t *player) +{ + if (player->mo && !P_MobjWasRemoved(player->mo)) + { + if (player->kartstuff[k_growshrinktimer] > 0) // Play Shrink noise + S_StartSound(player->mo, sfx_kc59); + else if (player->kartstuff[k_growshrinktimer] < 0) // Play Grow noise + S_StartSound(player->mo, sfx_kc5a); + + if (player->kartstuff[k_invincibilitytimer] == 0) + player->mo->color = player->skincolor; + + player->mo->scalespeed = mapobjectscale/TICRATE; + player->mo->destscale = mapobjectscale; + if (cv_kartdebugshrink.value && !modeattacking && !player->bot) + player->mo->destscale = (6*player->mo->destscale)/8; + } + + player->kartstuff[k_growshrinktimer] = 0; + player->kartstuff[k_growcancel] = -1; + + P_RestoreMusic(player); +} + +void K_SquishPlayer(player_t *player, mobj_t *source, mobj_t *inflictor) +{ + UINT8 scoremultiply = 1; + // PS: Inflictor is unused for all purposes here and is actually only ever relevant to Lua. It may be nil too. +#ifdef HAVE_BLUA + boolean force = false; // Used to check if Lua ShouldSquish should get us damaged reguardless of flashtics or heck knows what. + UINT8 shouldForce = LUAh_ShouldSquish(player, inflictor, source); + if (P_MobjWasRemoved(player->mo)) + return; // mobj was removed (in theory that shouldn't happen) + if (shouldForce == 1) + force = true; + else if (shouldForce == 2) + return; +#else + static const boolean force = false; + (void)inflictor; // Please stop forgetting to put inflictor in yer functions thank -Lat' +#endif + + if (G_BattleGametype()) + { + if (K_IsPlayerWanted(player)) + scoremultiply = 3; + else if (player->kartstuff[k_bumper] == 1) + scoremultiply = 2; + } + + if (player->health <= 0) + return; + + if (player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_invincibilitytimer] > 0 + || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0 + || (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1))) + { + if (!force) // You know the drill by now. + { + K_DoInstashield(player); + return; + } + } + +#ifdef HAVE_BLUA + if (LUAh_PlayerSquish(player, inflictor, source)) // Let Lua do its thing or overwrite if it wants to. Make sure to let any possible instashield happen because we didn't get "damaged" in this case. + return; +#endif + + player->kartstuff[k_sneakertimer] = 0; + player->kartstuff[k_levelbooster] = 0; + player->kartstuff[k_driftboost] = 0; + player->kartstuff[k_ringboost] = 0; + + player->kartstuff[k_drift] = 0; + player->kartstuff[k_driftcharge] = 0; + player->kartstuff[k_pogospring] = 0; + + if (G_BattleGametype()) + { + if (source && source->player && player != source->player) + { + P_AddPlayerScore(source->player, scoremultiply); + K_SpawnBattlePoints(source->player, player, scoremultiply); + source->player->kartstuff[k_wanted] -= wantedreduce; + player->kartstuff[k_wanted] -= (wantedreduce/2); + } + + if (player->kartstuff[k_bumper] > 0) + { + if (player->kartstuff[k_bumper] == 1) + { + mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!! + P_SetTarget(&karmahitbox->target, player->mo); + karmahitbox->destscale = player->mo->scale; + P_SetScale(karmahitbox, player->mo->scale); + CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]); + } + player->kartstuff[k_bumper]--; + if (K_IsPlayerWanted(player)) + K_CalculateBattleWanted(); + } + + if (!player->kartstuff[k_bumper]) + { + player->kartstuff[k_comebacktimer] = comebacktime; + if (player->kartstuff[k_comebackmode] == 2) + { + mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE); + S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound); + player->kartstuff[k_comebackmode] = 0; + } + } + + K_CheckBumpers(); + } + + player->kartstuff[k_squishedtimer] = TICRATE; + + // Reduce Shrink timer + if (player->kartstuff[k_growshrinktimer] < 0) + { + player->kartstuff[k_growshrinktimer] += TICRATE; + if (player->kartstuff[k_growshrinktimer] >= 0) + K_RemoveGrowShrink(player); + } + + player->powers[pw_flashing] = K_GetKartFlashing(player); + + player->mo->flags |= MF_NOCLIP; + + if (player->mo->state != &states[S_KART_SQUISH]) // Squash + P_SetPlayerMobjState(player->mo, S_KART_SQUISH); + + P_PlayRinglossSound(player->mo); + P_PlayerRingBurst(player, 5); + K_PlayPainSound(player->mo); + + player->kartstuff[k_instashield] = 15; + if (cv_kartdebughuddrop.value && !modeattacking) + K_DropItems(player); + else + K_DropHnextList(player); + return; +} + +void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor) // A bit of a hack, we just throw the player up higher here and extend their spinout timer +{ + UINT8 scoremultiply = 1; +#ifdef HAVE_BLUA + boolean force = false; // Used to check if Lua ShouldExplode should get us damaged reguardless of flashtics or heck knows what. + UINT8 shouldForce = LUAh_ShouldExplode(player, inflictor, source); + + if (P_MobjWasRemoved(player->mo)) + return; // mobj was removed (in theory that shouldn't happen) + if (shouldForce == 1) + force = true; + else if (shouldForce == 2) + return; + +#else + static const boolean force = false; +#endif + + if (G_BattleGametype()) + { + if (K_IsPlayerWanted(player)) + scoremultiply = 3; + else if (player->kartstuff[k_bumper] == 1) + scoremultiply = 2; + } + + if (player->health <= 0) + return; + + if (player->kartstuff[k_invincibilitytimer] > 0 || player->kartstuff[k_growshrinktimer] > 0 || player->kartstuff[k_hyudorotimer] > 0 // Do not check spinout, because SPB and Eggman should combo + || (G_BattleGametype() && ((player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || player->kartstuff[k_comebackmode] == 1))) + { + if (!force) // ShouldDamage can bypass that, again. + { + K_DoInstashield(player); + return; + } + } + +#ifdef HAVE_BLUA + if (LUAh_PlayerExplode(player, inflictor, source)) // Same thing. Also make sure to let Instashield happen blah blah + return; +#endif + + if (source && source != player->mo && source->player) + K_PlayHitEmSound(source); + + player->mo->momz = 18*mapobjectscale*P_MobjFlip(player->mo); // please stop forgetting mobjflip checks!!!! + player->mo->momx = player->mo->momy = 0; + + player->kartstuff[k_sneakertimer] = 0; + player->kartstuff[k_levelbooster] = 0; + player->kartstuff[k_driftboost] = 0; + player->kartstuff[k_ringboost] = 0; + + player->kartstuff[k_drift] = 0; + player->kartstuff[k_driftcharge] = 0; + player->kartstuff[k_pogospring] = 0; + + // This is the only part that SHOULDN'T combo :VVVVV + if (G_BattleGametype() && !(player->powers[pw_flashing] > 0 || player->kartstuff[k_squishedtimer] > 0 || (player->kartstuff[k_spinouttimer] > 0 && player->kartstuff[k_spinouttype] != 2))) + { + if (source && source->player && player != source->player) + { + P_AddPlayerScore(source->player, scoremultiply); + K_SpawnBattlePoints(source->player, player, scoremultiply); + source->player->kartstuff[k_wanted] -= wantedreduce; + player->kartstuff[k_wanted] -= (wantedreduce/2); + } + + if (player->kartstuff[k_bumper] > 0) + { + if (player->kartstuff[k_bumper] == 1) + { + mobj_t *karmahitbox = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_KARMAHITBOX); // Player hitbox is too small!! + P_SetTarget(&karmahitbox->target, player->mo); + karmahitbox->destscale = player->mo->scale; + P_SetScale(karmahitbox, player->mo->scale); + CONS_Printf(M_GetText("%s lost all of their bumpers!\n"), player_names[player-players]); + } + player->kartstuff[k_bumper]--; + if (K_IsPlayerWanted(player)) + K_CalculateBattleWanted(); + } + + if (!player->kartstuff[k_bumper]) + { + player->kartstuff[k_comebacktimer] = comebacktime; + if (player->kartstuff[k_comebackmode] == 2) + { + mobj_t *poof = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EXPLODE); + S_StartSound(poof, mobjinfo[MT_KARMAHITBOX].seesound); + player->kartstuff[k_comebackmode] = 0; + } + } + + K_CheckBumpers(); + } + + player->kartstuff[k_spinouttype] = 1; + player->kartstuff[k_spinouttimer] = (3*TICRATE/2)+2; + + player->powers[pw_flashing] = K_GetKartFlashing(player); + + if (inflictor && inflictor->type == MT_SPBEXPLOSION && inflictor->extravalue1) + { + player->kartstuff[k_spinouttimer] = ((5*player->kartstuff[k_spinouttimer])/2)+1; + player->mo->momz *= 2; + } + + if (player->mo->eflags & MFE_UNDERWATER) + player->mo->momz = (117 * player->mo->momz) / 200; + + if (player->mo->state != &states[S_KART_SPIN]) + P_SetPlayerMobjState(player->mo, S_KART_SPIN); + + P_PlayRinglossSound(player->mo); + P_PlayerRingBurst(player, 5); + K_PlayPainSound(player->mo); + + if (P_IsDisplayPlayer(player)) + { + quake.intensity = 64*FRACUNIT; + quake.time = 5; + } + + player->kartstuff[k_instashield] = 15; + K_DropItems(player); + + return; +} + +void K_StealBumper(player_t *player, player_t *victim, boolean force) +{ + INT32 newbumper; + angle_t newangle, diff; + fixed_t newx, newy; + mobj_t *newmo; + + if (!G_BattleGametype()) + return; + + if (player->health <= 0 || victim->health <= 0) + return; + + if (!force) + { + if (victim->kartstuff[k_bumper] <= 0) // || player->kartstuff[k_bumper] >= cv_kartbumpers.value+2 + return; + + if (player->kartstuff[k_squishedtimer] > 0 || player->kartstuff[k_spinouttimer] > 0) + return; + + if (victim->powers[pw_flashing] > 0 || victim->kartstuff[k_squishedtimer] > 0 || (victim->kartstuff[k_spinouttimer] > 0 && victim->kartstuff[k_spinouttype] != 2) + || victim->kartstuff[k_invincibilitytimer] > 0 || victim->kartstuff[k_growshrinktimer] > 0 || victim->kartstuff[k_hyudorotimer] > 0) + { + K_DoInstashield(victim); + return; + } + } + + if (netgame && player->kartstuff[k_bumper] <= 0) + CONS_Printf(M_GetText("%s is back in the game!\n"), player_names[player-players]); + + newbumper = player->kartstuff[k_bumper]; + if (newbumper <= 1) + diff = 0; + else + diff = FixedAngle(360*FRACUNIT/newbumper); + + newangle = player->mo->angle; + newx = player->mo->x + P_ReturnThrustX(player->mo, newangle + ANGLE_180, 64*FRACUNIT); + newy = player->mo->y + P_ReturnThrustY(player->mo, newangle + ANGLE_180, 64*FRACUNIT); + + newmo = P_SpawnMobj(newx, newy, player->mo->z, MT_BATTLEBUMPER); + newmo->threshold = newbumper; + P_SetTarget(&newmo->tracer, victim->mo); + P_SetTarget(&newmo->target, player->mo); + newmo->angle = (diff * (newbumper-1)); + newmo->color = victim->skincolor; + + if (newbumper+1 < 2) + P_SetMobjState(newmo, S_BATTLEBUMPER3); + else if (newbumper+1 < 3) + P_SetMobjState(newmo, S_BATTLEBUMPER2); + else + P_SetMobjState(newmo, S_BATTLEBUMPER1); + + S_StartSound(player->mo, sfx_3db06); + + player->kartstuff[k_bumper]++; + player->kartstuff[k_comebackpoints] = 0; + player->powers[pw_flashing] = K_GetKartFlashing(player); + player->kartstuff[k_comebacktimer] = comebacktime; + + /*victim->powers[pw_flashing] = K_GetKartFlashing(victim); + victim->kartstuff[k_comebacktimer] = comebacktime;*/ + + victim->kartstuff[k_instashield] = 15; + if (cv_kartdebughuddrop.value && !modeattacking) + K_DropItems(victim); + else + K_DropHnextList(victim); + return; +} + +// source is the mobj that originally threw the bomb that exploded etc. +// Spawns the sphere around the explosion that handles spinout +void K_SpawnKartExplosion(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle, boolean spawncenter, boolean ghostit, mobj_t *source) +{ + mobj_t *mobj; + mobj_t *ghost = NULL; + INT32 i; + TVector v; + TVector *res; + fixed_t finalx, finaly, finalz, dist; + //mobj_t hoopcenter; + angle_t degrees, fa, closestangle; + fixed_t mobjx, mobjy, mobjz; + + //hoopcenter.x = x; + //hoopcenter.y = y; + //hoopcenter.z = z; + + //hoopcenter.z = z - mobjinfo[type].height/2; + + degrees = FINEANGLES/number; + + closestangle = 0; + + // Create the hoop! + for (i = 0; i < number; i++) + { + fa = (i*degrees); + v[0] = FixedMul(FINECOSINE(fa),radius); + v[1] = 0; + v[2] = FixedMul(FINESINE(fa),radius); + v[3] = FRACUNIT; + + res = VectorMatrixMultiply(v, *RotateXMatrix(rotangle)); + M_Memcpy(&v, res, sizeof (v)); + res = VectorMatrixMultiply(v, *RotateZMatrix(closestangle)); + M_Memcpy(&v, res, sizeof (v)); + + finalx = x + v[0]; + finaly = y + v[1]; + finalz = z + v[2]; + + mobj = P_SpawnMobj(finalx, finaly, finalz, type); + + mobj->z -= mobj->height>>1; + + // change angle + mobj->angle = R_PointToAngle2(mobj->x, mobj->y, x, y); + + // change slope + dist = P_AproxDistance(P_AproxDistance(x - mobj->x, y - mobj->y), z - mobj->z); + + if (dist < 1) + dist = 1; + + mobjx = mobj->x; + mobjy = mobj->y; + mobjz = mobj->z; + + if (ghostit) + { + ghost = P_SpawnGhostMobj(mobj); + P_SetMobjState(mobj, S_NULL); + mobj = ghost; + } + + if (spawncenter) + { + mobj->x = x; + mobj->y = y; + mobj->z = z; + } + + mobj->momx = FixedMul(FixedDiv(mobjx - x, dist), FixedDiv(dist, 6*FRACUNIT)); + mobj->momy = FixedMul(FixedDiv(mobjy - y, dist), FixedDiv(dist, 6*FRACUNIT)); + mobj->momz = FixedMul(FixedDiv(mobjz - z, dist), FixedDiv(dist, 6*FRACUNIT)); + + if (source && !P_MobjWasRemoved(source)) + P_SetTarget(&mobj->target, source); + } +} + +// Spawns the purely visual explosion +void K_SpawnMineExplosion(mobj_t *source, UINT8 color) +{ + INT32 i, radius, height; + mobj_t *smoldering = P_SpawnMobj(source->x, source->y, source->z, MT_SMOLDERING); + mobj_t *dust; + mobj_t *truc; + INT32 speed, speed2; + + K_MatchGenericExtraFlags(smoldering, source); + smoldering->tics = TICRATE*3; + radius = source->radius>>FRACBITS; + height = source->height>>FRACBITS; + + if (!color) + color = SKINCOLOR_KETCHUP; + + for (i = 0; i < 32; i++) + { + dust = P_SpawnMobj(source->x, source->y, source->z, MT_SMOKE); + P_SetMobjState(dust, S_OPAQUESMOKE1); + dust->angle = (ANGLE_180/16) * i; + P_SetScale(dust, source->scale); + dust->destscale = source->scale*10; + dust->scalespeed = source->scale/12; + P_InstaThrust(dust, dust->angle, FixedMul(20*FRACUNIT, source->scale)); + + truc = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT, + source->y + P_RandomRange(-radius, radius)*FRACUNIT, + source->z + P_RandomRange(0, height)*FRACUNIT, MT_BOOMEXPLODE); + K_MatchGenericExtraFlags(truc, source); + P_SetScale(truc, source->scale); + truc->destscale = source->scale*6; + truc->scalespeed = source->scale/12; + speed = FixedMul(10*FRACUNIT, source->scale)>>FRACBITS; + truc->momx = P_RandomRange(-speed, speed)*FRACUNIT; + truc->momy = P_RandomRange(-speed, speed)*FRACUNIT; + speed = FixedMul(20*FRACUNIT, source->scale)>>FRACBITS; + truc->momz = P_RandomRange(-speed, speed)*FRACUNIT*P_MobjFlip(truc); + if (truc->eflags & MFE_UNDERWATER) + truc->momz = (117 * truc->momz) / 200; + truc->color = color; + } + + for (i = 0; i < 16; i++) + { + dust = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT, + source->y + P_RandomRange(-radius, radius)*FRACUNIT, + source->z + P_RandomRange(0, height)*FRACUNIT, MT_SMOKE); + P_SetMobjState(dust, S_OPAQUESMOKE1); + P_SetScale(dust, source->scale); + dust->destscale = source->scale*10; + dust->scalespeed = source->scale/12; + dust->tics = 30; + dust->momz = P_RandomRange(FixedMul(3*FRACUNIT, source->scale)>>FRACBITS, FixedMul(7*FRACUNIT, source->scale)>>FRACBITS)*FRACUNIT; + + truc = P_SpawnMobj(source->x + P_RandomRange(-radius, radius)*FRACUNIT, + source->y + P_RandomRange(-radius, radius)*FRACUNIT, + source->z + P_RandomRange(0, height)*FRACUNIT, MT_BOOMPARTICLE); + K_MatchGenericExtraFlags(truc, source); + P_SetScale(truc, source->scale); + truc->destscale = source->scale*5; + truc->scalespeed = source->scale/12; + speed = FixedMul(20*FRACUNIT, source->scale)>>FRACBITS; + truc->momx = P_RandomRange(-speed, speed)*FRACUNIT; + truc->momy = P_RandomRange(-speed, speed)*FRACUNIT; + speed = FixedMul(15*FRACUNIT, source->scale)>>FRACBITS; + speed2 = FixedMul(45*FRACUNIT, source->scale)>>FRACBITS; + truc->momz = P_RandomRange(speed, speed2)*FRACUNIT*P_MobjFlip(truc); + if (P_RandomChance(FRACUNIT/2)) + truc->momz = -truc->momz; + if (truc->eflags & MFE_UNDERWATER) + truc->momz = (117 * truc->momz) / 200; + truc->tics = TICRATE*2; + truc->color = color; + } +} + +static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, INT32 flags2, fixed_t speed) +{ + mobj_t *th; + fixed_t x, y, z; + fixed_t finalspeed = speed; + mobj_t *throwmo; + + if (source->player && source->player->speed > K_GetKartSpeed(source->player, false)) + { + angle_t input = source->angle - an; + boolean invert = (input > ANGLE_180); + if (invert) + input = InvAngle(input); + + finalspeed = max(speed, FixedMul(speed, FixedMul( + FixedDiv(source->player->speed, K_GetKartSpeed(source->player, false)), // Multiply speed to be proportional to your own, boosted maxspeed. + (((180<x + source->momx + FixedMul(finalspeed, FINECOSINE(an>>ANGLETOFINESHIFT)); + y = source->y + source->momy + FixedMul(finalspeed, FINESINE(an>>ANGLETOFINESHIFT)); + z = source->z; // spawn on the ground please + + if (P_MobjFlip(source) < 0) + { + z = source->z+source->height - mobjinfo[type].height; + } + + th = P_SpawnMobj(x, y, z, type); + + th->flags2 |= flags2; + + th->threshold = 10; + + if (th->info->seesound) + S_StartSound(source, th->info->seesound); + + P_SetTarget(&th->target, source); + + if (P_IsObjectOnGround(source)) + { + // floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn + // This should set it for FOFs + P_TeleportMove(th, th->x, th->y, th->z); + // spawn on the ground if the player is on the ground + if (P_MobjFlip(source) < 0) + { + th->z = th->ceilingz - th->height; + th->eflags |= MFE_VERTICALFLIP; + } + else + th->z = th->floorz; + } + + th->angle = an; + th->momx = FixedMul(finalspeed, FINECOSINE(an>>ANGLETOFINESHIFT)); + th->momy = FixedMul(finalspeed, FINESINE(an>>ANGLETOFINESHIFT)); + + switch (type) + { + case MT_ORBINAUT: + if (source && source->player) + th->color = source->player->skincolor; + else + th->color = SKINCOLOR_GREY; + th->movefactor = finalspeed; + break; + case MT_JAWZ: + if (source && source->player) + { + INT32 lasttarg = source->player->kartstuff[k_lastjawztarget]; + th->cvmem = source->player->skincolor; + if ((lasttarg >= 0 && lasttarg < MAXPLAYERS) + && playeringame[lasttarg] + && !players[lasttarg].spectator + && players[lasttarg].mo) + { + P_SetTarget(&th->tracer, players[lasttarg].mo); + } + } + else + th->cvmem = SKINCOLOR_KETCHUP; + /* FALLTHRU */ + case MT_JAWZ_DUD: + S_StartSound(th, th->info->activesound); + /* FALLTHRU */ + case MT_SPB: + th->movefactor = finalspeed; + break; + default: + break; + } + + x = x + P_ReturnThrustX(source, an, source->radius + th->radius); + y = y + P_ReturnThrustY(source, an, source->radius + th->radius); + throwmo = P_SpawnMobj(x, y, z, MT_FIREDITEM); + throwmo->movecount = 1; + throwmo->movedir = source->angle - an; + P_SetTarget(&throwmo->target, source); + + return NULL; +} + +static void K_SpawnDriftSparks(player_t *player) +{ + fixed_t newx; + fixed_t newy; + mobj_t *spark; + angle_t travelangle; + INT32 i; + + I_Assert(player != NULL); + I_Assert(player->mo != NULL); + I_Assert(!P_MobjWasRemoved(player->mo)); + + if (leveltime % 2 == 1) + return; + + if (!P_IsObjectOnGround(player->mo)) + return; + + if (!player->kartstuff[k_drift] || player->kartstuff[k_driftcharge] < K_GetKartDriftSparkValue(player)) + return; + + travelangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift]; + + for (i = 0; i < 2; i++) + { + newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale)); + newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(32*FRACUNIT, player->mo->scale)); + spark = P_SpawnMobj(newx, newy, player->mo->z, MT_DRIFTSPARK); + + P_SetTarget(&spark->target, player->mo); + spark->angle = travelangle-(ANGLE_45/5)*player->kartstuff[k_drift]; + spark->destscale = player->mo->scale; + P_SetScale(spark, player->mo->scale); + + spark->momx = player->mo->momx/2; + spark->momy = player->mo->momy/2; + //spark->momz = player->mo->momz/2; + + if (player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(player)*4) + { + spark->color = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))); + } + else if (player->kartstuff[k_driftcharge] >= K_GetKartDriftSparkValue(player)*2) + { + if (player->kartstuff[k_driftcharge] <= (K_GetKartDriftSparkValue(player)*2)+(24*3)) + spark->color = SKINCOLOR_RASPBERRY; // transition + else + spark->color = SKINCOLOR_KETCHUP; + } + else + { + spark->color = SKINCOLOR_SAPPHIRE; + } + + if ((player->kartstuff[k_drift] > 0 && player->cmd.driftturn > 0) // Inward drifts + || (player->kartstuff[k_drift] < 0 && player->cmd.driftturn < 0)) + { + if ((player->kartstuff[k_drift] < 0 && (i & 1)) + || (player->kartstuff[k_drift] > 0 && !(i & 1))) + P_SetMobjState(spark, S_DRIFTSPARK_A1); + else if ((player->kartstuff[k_drift] < 0 && !(i & 1)) + || (player->kartstuff[k_drift] > 0 && (i & 1))) + P_SetMobjState(spark, S_DRIFTSPARK_C1); + } + else if ((player->kartstuff[k_drift] > 0 && player->cmd.driftturn < 0) // Outward drifts + || (player->kartstuff[k_drift] < 0 && player->cmd.driftturn > 0)) + { + if ((player->kartstuff[k_drift] < 0 && (i & 1)) + || (player->kartstuff[k_drift] > 0 && !(i & 1))) + P_SetMobjState(spark, S_DRIFTSPARK_C1); + else if ((player->kartstuff[k_drift] < 0 && !(i & 1)) + || (player->kartstuff[k_drift] > 0 && (i & 1))) + P_SetMobjState(spark, S_DRIFTSPARK_A1); + } + + K_MatchGenericExtraFlags(spark, player->mo); + } +} + +static void K_SpawnAIZDust(player_t *player) +{ + fixed_t newx; + fixed_t newy; + mobj_t *spark; + angle_t travelangle; + + I_Assert(player != NULL); + I_Assert(player->mo != NULL); + I_Assert(!P_MobjWasRemoved(player->mo)); + + if (leveltime % 2 == 1) + return; + + if (!P_IsObjectOnGround(player->mo)) + return; + + travelangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); + //S_StartSound(player->mo, sfx_s3k47); + + { + newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle - (player->kartstuff[k_aizdriftstrat]*ANGLE_45), FixedMul(24*FRACUNIT, player->mo->scale)); + newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle - (player->kartstuff[k_aizdriftstrat]*ANGLE_45), FixedMul(24*FRACUNIT, player->mo->scale)); + spark = P_SpawnMobj(newx, newy, player->mo->z, MT_AIZDRIFTSTRAT); + + spark->angle = travelangle+(player->kartstuff[k_aizdriftstrat]*ANGLE_90); + P_SetScale(spark, (spark->destscale = (3*player->mo->scale)>>2)); + + spark->momx = (6*player->mo->momx)/5; + spark->momy = (6*player->mo->momy)/5; + //spark->momz = player->mo->momz/2; + + K_MatchGenericExtraFlags(spark, player->mo); + } +} + +void K_SpawnBoostTrail(player_t *player) +{ + fixed_t newx; + fixed_t newy; + fixed_t ground; + mobj_t *flame; + angle_t travelangle; + INT32 i; + + I_Assert(player != NULL); + I_Assert(player->mo != NULL); + I_Assert(!P_MobjWasRemoved(player->mo)); + + if (!P_IsObjectOnGround(player->mo) + || player->kartstuff[k_hyudorotimer] != 0 + || (G_BattleGametype() && player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer])) + return; + + if (player->mo->eflags & MFE_VERTICALFLIP) + ground = player->mo->ceilingz - FixedMul(mobjinfo[MT_SNEAKERTRAIL].height, player->mo->scale); + else + ground = player->mo->floorz; + + if (player->kartstuff[k_drift] != 0) + travelangle = player->mo->angle; + else + travelangle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy); + + for (i = 0; i < 2; i++) + { + newx = player->mo->x + P_ReturnThrustX(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale)); + newy = player->mo->y + P_ReturnThrustY(player->mo, travelangle + ((i&1) ? -1 : 1)*ANGLE_135, FixedMul(24*FRACUNIT, player->mo->scale)); +#ifdef ESLOPE + if (player->mo->standingslope) + { + ground = P_GetZAt(player->mo->standingslope, newx, newy); + if (player->mo->eflags & MFE_VERTICALFLIP) + ground -= FixedMul(mobjinfo[MT_SNEAKERTRAIL].height, player->mo->scale); + } +#endif + flame = P_SpawnMobj(newx, newy, ground, MT_SNEAKERTRAIL); + + P_SetTarget(&flame->target, player->mo); + flame->angle = travelangle; + flame->fuse = TICRATE*2; + flame->destscale = player->mo->scale; + P_SetScale(flame, player->mo->scale); + // not K_MatchGenericExtraFlags so that a stolen sneaker can be seen + K_FlipFromObject(flame, player->mo); + + flame->momx = 8; + P_XYMovement(flame); + if (P_MobjWasRemoved(flame)) + continue; + + if (player->mo->eflags & MFE_VERTICALFLIP) + { + if (flame->z + flame->height < flame->ceilingz) + P_RemoveMobj(flame); + } + else if (flame->z > flame->floorz) + P_RemoveMobj(flame); + } +} + +void K_SpawnSparkleTrail(mobj_t *mo) +{ + const INT32 rad = (mo->radius*2)>>FRACBITS; + mobj_t *sparkle; + INT32 i; + + I_Assert(mo != NULL); + I_Assert(!P_MobjWasRemoved(mo)); + + for (i = 0; i < 3; i++) + { + fixed_t newx = mo->x + mo->momx + (P_RandomRange(-rad, rad)<y + mo->momy + (P_RandomRange(-rad, rad)<z + mo->momz + (P_RandomRange(0, mo->height>>FRACBITS)<target, mo); + sparkle->destscale = mo->destscale; + P_SetScale(sparkle, mo->scale); + sparkle->color = mo->color; + //sparkle->colorized = mo->colorized; + } + + P_SetMobjState(sparkle, S_KARTINVULN_LARGE1); +} + +void K_SpawnWipeoutTrail(mobj_t *mo, boolean translucent) +{ + mobj_t *dust; + angle_t aoff; + + I_Assert(mo != NULL); + I_Assert(!P_MobjWasRemoved(mo)); + + if (mo->player) + aoff = (mo->player->frameangle + ANGLE_180); + else + aoff = (mo->angle + ANGLE_180); + + if ((leveltime / 2) & 1) + aoff -= ANGLE_45; + else + aoff += ANGLE_45; + + dust = P_SpawnMobj(mo->x + FixedMul(24*mo->scale, FINECOSINE(aoff>>ANGLETOFINESHIFT)) + (P_RandomRange(-8,8) << FRACBITS), + mo->y + FixedMul(24*mo->scale, FINESINE(aoff>>ANGLETOFINESHIFT)) + (P_RandomRange(-8,8) << FRACBITS), + mo->z, MT_WIPEOUTTRAIL); + + P_SetTarget(&dust->target, mo); + dust->angle = R_PointToAngle2(0,0,mo->momx,mo->momy); + dust->destscale = mo->scale; + P_SetScale(dust, mo->scale); + K_FlipFromObject(dust, mo); + + if (translucent) // offroad effect + { + dust->momx = mo->momx/2; + dust->momy = mo->momy/2; + dust->momz = mo->momz/2; + } + + if (translucent) + dust->flags2 |= MF2_SHADOW; +} + +void K_SpawnDraftDust(mobj_t *mo) +{ + UINT8 i; + + I_Assert(mo != NULL); + I_Assert(!P_MobjWasRemoved(mo)); + + for (i = 0; i < 2; i++) + { + angle_t ang, aoff; + SINT8 sign = 1; + UINT8 foff = 0; + mobj_t *dust; + boolean drifting = false; + + if (mo->player) + { + UINT8 leniency = (3*TICRATE)/4 + ((mo->player->kartweight-1) * (TICRATE/4)); + + ang = mo->player->frameangle; + + if (mo->player->kartstuff[k_drift] != 0) + { + drifting = true; + ang += (mo->player->kartstuff[k_drift] * ((ANGLE_270 + ANGLE_22h) / 5)); // -112.5 doesn't work. I fucking HATE SRB2 angles + if (mo->player->kartstuff[k_drift] < 0) + sign = 1; + else + sign = -1; + } + + foff = 5 - ((mo->player->kartstuff[k_draftleeway] * 5) / leniency); + + // this shouldn't happen + if (foff > 4) + foff = 4; + } + else + ang = mo->angle; + + if (!drifting) + { + if (i & 1) + sign = -1; + else + sign = 1; + } + + aoff = (ang + ANGLE_180) + (ANGLE_45 * sign); + + dust = P_SpawnMobj(mo->x + FixedMul(24*mo->scale, FINECOSINE(aoff>>ANGLETOFINESHIFT)), + mo->y + FixedMul(24*mo->scale, FINESINE(aoff>>ANGLETOFINESHIFT)), + mo->z, MT_DRAFTDUST); + + P_SetMobjState(dust, S_DRAFTDUST1 + foff); + + P_SetTarget(&dust->target, mo); + dust->angle = ang - (ANGLE_90 * sign); // point completely perpendicular from the player + dust->destscale = mo->scale; + P_SetScale(dust, mo->scale); + K_FlipFromObject(dust, mo); + + if (leveltime & 1) + dust->tics++; // "randomize" animation + + dust->momx = (4*mo->momx)/5; + dust->momy = (4*mo->momy)/5; + //dust->momz = (4*mo->momz)/5; + + P_Thrust(dust, dust->angle, 4*mo->scale); + + if (drifting) // only 1 trail while drifting + break; + } +} + +// K_DriftDustHandling +// Parameters: +// spawner: The map object that is spawning the drift dust +// Description: Spawns the drift dust for objects, players use rmomx/y, other objects use regular momx/y. +// Also plays the drift sound. +// Other objects should be angled towards where they're trying to go so they don't randomly spawn dust +// Do note that most of the function won't run in odd intervals of frames +void K_DriftDustHandling(mobj_t *spawner) +{ + angle_t anglediff; + const INT16 spawnrange = spawner->radius>>FRACBITS; + + if (!P_IsObjectOnGround(spawner) || leveltime % 2 != 0) + return; + + if (spawner->player) + { + if (spawner->player->pflags & PF_SKIDDOWN) + { + anglediff = abs((signed)(spawner->angle - spawner->player->frameangle)); + if (leveltime % 6 == 0) + S_StartSound(spawner, sfx_screec); // repeated here because it doesn't always happen to be within the range when this is the case + } + else + { + angle_t playerangle = spawner->angle; + + if (spawner->player->speed < 5<player->cmd.forwardmove < 0) + playerangle += ANGLE_180; + + anglediff = abs((signed)(playerangle - R_PointToAngle2(0, 0, spawner->player->rmomx, spawner->player->rmomy))); + } + } + else + { + if (P_AproxDistance(spawner->momx, spawner->momy) < 5<angle - R_PointToAngle2(0, 0, spawner->momx, spawner->momy))); + } + + if (anglediff > ANGLE_180) + anglediff = InvAngle(anglediff); + + if (anglediff > ANG10*4) // Trying to turn further than 40 degrees + { + fixed_t spawnx = P_RandomRange(-spawnrange, spawnrange)<x + spawnx, spawner->y + spawny, spawner->z, MT_DRIFTDUST); + dust->momx = FixedMul(spawner->momx + (P_RandomRange(-speedrange, speedrange)<scale)/4); + dust->momy = FixedMul(spawner->momy + (P_RandomRange(-speedrange, speedrange)<scale)/4); + dust->momz = P_MobjFlip(spawner) * (P_RandomRange(1, 4) * (spawner->scale)); + P_SetScale(dust, spawner->scale/2); + dust->destscale = spawner->scale * 3; + dust->scalespeed = spawner->scale/12; + + if (leveltime % 6 == 0) + S_StartSound(spawner, sfx_screec); + + K_MatchGenericExtraFlags(dust, spawner); + } +} + +static mobj_t *K_FindLastTrailMobj(player_t *player) +{ + mobj_t *trail; + + if (!player || !(trail = player->mo) || !player->mo->hnext || !player->mo->hnext->health) + return NULL; + + while (trail->hnext && !P_MobjWasRemoved(trail->hnext) && trail->hnext->health) + { + trail = trail->hnext; + } + + return trail; +} + +static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow) +{ + mobj_t *mo; + INT32 dir; + fixed_t PROJSPEED; + angle_t newangle; + fixed_t newx, newy, newz; + mobj_t *throwmo; + + if (!player) + return NULL; + + // Figure out projectile speed by game speed + if (missile && mapthing != MT_BALLHOG) // Trying to keep compatability... + PROJSPEED = FixedMul(mobjinfo[mapthing].speed, FRACUNIT + ((gamespeed-1) * (FRACUNIT/4))); + else + PROJSPEED = (82 + ((gamespeed-1) * 14)) * FRACUNIT; // Avg Speed is 41 in Normal + PROJSPEED = FixedMul(PROJSPEED, mapobjectscale); + + if (altthrow) + { + if (altthrow == 2) // Kitchen sink throwing + { +#if 0 + if (player->kartstuff[k_throwdir] == 1) + dir = 3; + else if (player->kartstuff[k_throwdir] == -1) + dir = 1; + else + dir = 2; +#else + if (player->kartstuff[k_throwdir] == 1) + dir = 2; + else + dir = 1; +#endif + } + else + { + if (player->kartstuff[k_throwdir] == 1) + dir = 2; + else if (player->kartstuff[k_throwdir] == -1) + dir = -1; + else + dir = 1; + } + } + else + { + if (player->kartstuff[k_throwdir] != 0) + dir = player->kartstuff[k_throwdir]; + else + dir = defaultDir; + } + + if (missile) // Shootables + { + if (mapthing == MT_BALLHOG) // Messy + { + if (dir == -1) + { + // Shoot backward + mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 - 0x06000000, 0, PROJSPEED/4); + K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 - 0x03000000, 0, PROJSPEED/4); + K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/4); + K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 + 0x03000000, 0, PROJSPEED/4); + K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180 + 0x06000000, 0, PROJSPEED/4); + } + else + { + // Shoot forward + mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle - 0x06000000, 0, PROJSPEED); + K_SpawnKartMissile(player->mo, mapthing, player->mo->angle - 0x03000000, 0, PROJSPEED); + K_SpawnKartMissile(player->mo, mapthing, player->mo->angle, 0, PROJSPEED); + K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + 0x03000000, 0, PROJSPEED); + K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + 0x06000000, 0, PROJSPEED); + } + } + else + { + if (dir == -1 && mapthing != MT_SPB) + { + // Shoot backward + mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle + ANGLE_180, 0, PROJSPEED/2); + } + else + { + // Shoot forward + mo = K_SpawnKartMissile(player->mo, mapthing, player->mo->angle, 0, PROJSPEED); + } + } + } + else + { + player->kartstuff[k_bananadrag] = 0; // RESET timer, for multiple bananas + + if (dir > 0) + { + // Shoot forward + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, mapthing); + //K_FlipFromObject(mo, player->mo); + // These are really weird so let's make it a very specific case to make SURE it works... + if (player->mo->eflags & MFE_VERTICALFLIP) + { + mo->z -= player->mo->height; + mo->flags2 |= MF2_OBJECTFLIP; + mo->eflags |= MFE_VERTICALFLIP; + } + + mo->threshold = 10; + P_SetTarget(&mo->target, player->mo); + + S_StartSound(player->mo, mo->info->seesound); + + if (mo) + { + angle_t fa = player->mo->angle>>ANGLETOFINESHIFT; + fixed_t HEIGHT = (20 + (dir*10))*mapobjectscale + (player->mo->momz*P_MobjFlip(player->mo)); + + P_SetObjectMomZ(mo, HEIGHT, false); + mo->momx = player->mo->momx + FixedMul(FINECOSINE(fa), PROJSPEED*dir); + mo->momy = player->mo->momy + FixedMul(FINESINE(fa), PROJSPEED*dir); + + mo->extravalue2 = dir; + + if (mo->eflags & MFE_UNDERWATER) + mo->momz = (117 * mo->momz) / 200; + } + + // this is the small graphic effect that plops in you when you throw an item: + 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->flags2 |= MF2_OBJECTFLIP; + throwmo->eflags |= MFE_VERTICALFLIP; + } + + throwmo->movecount = 0; // above player + } + else + { + mobj_t *lasttrail = K_FindLastTrailMobj(player); + + if (lasttrail) + { + newx = lasttrail->x; + newy = lasttrail->y; + newz = lasttrail->z; + } + else + { + // Drop it directly behind you. + fixed_t dropradius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(mobjinfo[mapthing].radius, mobjinfo[mapthing].radius); + + newangle = player->mo->angle; + + newx = player->mo->x + P_ReturnThrustX(player->mo, newangle + ANGLE_180, dropradius); + newy = player->mo->y + P_ReturnThrustY(player->mo, newangle + ANGLE_180, dropradius); + newz = player->mo->z; + } + + mo = P_SpawnMobj(newx, newy, newz, mapthing); // this will never return null because collision isn't processed here + K_FlipFromObject(mo, player->mo); + + mo->threshold = 10; + P_SetTarget(&mo->target, player->mo); + + if (P_IsObjectOnGround(player->mo)) + { + // floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn + // This should set it for FOFs + P_TeleportMove(mo, mo->x, mo->y, mo->z); // however, THIS can fuck up your day. just absolutely ruin you. + if (P_MobjWasRemoved(mo)) + return NULL; + + if (P_MobjFlip(mo) > 0) + { + if (mo->floorz > mo->target->z - mo->height) + { + mo->z = mo->floorz; + } + } + else + { + if (mo->ceilingz < mo->target->z + mo->target->height + mo->height) + { + mo->z = mo->ceilingz - mo->height; + } + } + } + + if (player->mo->eflags & MFE_VERTICALFLIP) + mo->eflags |= MFE_VERTICALFLIP; + + if (mapthing == MT_SSMINE) + mo->extravalue1 = 49; // Pads the start-up length from 21 frames to a full 2 seconds + } + } + + return mo; +} + +void K_PuntMine(mobj_t *thismine, mobj_t *punter) +{ + angle_t fa = R_PointToAngle2(0, 0, punter->momx, punter->momy) >> ANGLETOFINESHIFT; + fixed_t z = 30*mapobjectscale + punter->momz; + fixed_t spd; + mobj_t *mine; + + if (!thismine || P_MobjWasRemoved(thismine)) + return; + + if (thismine->type == MT_SSMINE_SHIELD) // Create a new mine + { + mine = P_SpawnMobj(thismine->x, thismine->y, thismine->z, MT_SSMINE); + P_SetTarget(&mine->target, thismine->target); + mine->angle = thismine->angle; + mine->flags2 = thismine->flags2; + mine->floorz = thismine->floorz; + mine->ceilingz = thismine->ceilingz; + P_RemoveMobj(thismine); + } + else + mine = thismine; + + if (!mine || P_MobjWasRemoved(mine)) + return; + + spd = (82 + ((gamespeed-1) * 14))*mapobjectscale; // Avg Speed is 41 in Normal + + mine->flags |= MF_NOCLIPTHING; + + P_SetMobjState(mine, S_SSMINE_AIR1); + mine->threshold = 10; + mine->extravalue1 = 0; + mine->reactiontime = mine->info->reactiontime; + + mine->momx = punter->momx + FixedMul(FINECOSINE(fa), spd); + mine->momy = punter->momy + FixedMul(FINESINE(fa), spd); + mine->momz = P_MobjFlip(mine) * z; + + mine->flags &= ~MF_NOCLIPTHING; +} + +#define THUNDERRADIUS 320 + +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); + //player->kartstuff[k_thunderanim] = 35; + P_NukeEnemies(player->mo, player->mo, RING_DIST/4); + + // 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 + +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); + + 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 + && (G_RaceGametype() //&& players[i].kartstuff[k_position] < player->kartstuff[k_position]) + || (G_BattleGametype() && players[i].kartstuff[k_bumper] > 0)) + + // Has an item + && (players[i].kartstuff[k_itemtype] + && players[i].kartstuff[k_itemamount] + && !players[i].kartstuff[k_itemheld] + && !players[i].karthud[khud_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->kartstuff[k_hyudorotimer] = hyudorotime; + player->kartstuff[k_stealingtimer] = stealtime; + + player->kartstuff[k_itemtype] = KITEM_KITCHENSINK; + player->kartstuff[k_itemamount] = 1; + player->kartstuff[k_itemheld] = 0; + return; + } + else if ((G_RaceGametype() && player->kartstuff[k_position] == 1) || numplayers == 0) // No-one can be stolen from? Oh well... + { + player->kartstuff[k_hyudorotimer] = hyudorotime; + player->kartstuff[k_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->kartstuff[k_hyudorotimer] = hyudorotime; + player->kartstuff[k_stealingtimer] = stealtime; + players[stealplayer].kartstuff[k_stolentimer] = stealtime; + + player->kartstuff[k_itemtype] = players[stealplayer].kartstuff[k_itemtype]; + player->kartstuff[k_itemamount] = players[stealplayer].kartstuff[k_itemamount]; + player->kartstuff[k_itemheld] = 0; + + players[stealplayer].kartstuff[k_itemtype] = KITEM_NONE; + players[stealplayer].kartstuff[k_itemamount] = 0; + players[stealplayer].kartstuff[k_itemheld] = 0; + + if (P_IsDisplayPlayer(&players[stealplayer]) && !splitscreen) + S_StartSound(NULL, sfx_s3k92); + } +} + +void K_DoSneaker(player_t *player, INT32 type) +{ + const fixed_t intendedboost = FRACUNIT/2; + + if (!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) + { + S_StartSound(player->mo, sfx_cdfm01); + K_SpawnDashDustRelease(player); + if (intendedboost > player->kartstuff[k_speedboost]) + player->karthud[khud_destboostcam] = FixedMul(FRACUNIT, FixedDiv((intendedboost - player->kartstuff[k_speedboost]), intendedboost)); + } + + if (!EITHERSNEAKER(player)) + { + if (type == 2) + { + if (player->mo->hnext) + { + mobj_t *cur = player->mo->hnext; + while (cur && !P_MobjWasRemoved(cur)) + { + if (!cur->tracer) + { + mobj_t *overlay = P_SpawnMobj(cur->x, cur->y, cur->z, MT_BOOSTFLAME); + P_SetTarget(&overlay->target, cur); + P_SetTarget(&cur->tracer, overlay); + P_SetScale(overlay, (overlay->destscale = 3*cur->scale/4)); + K_FlipFromObject(overlay, cur); + } + cur = cur->hnext; + } + } + } + else + { + mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BOOSTFLAME); + P_SetTarget(&overlay->target, player->mo); + P_SetScale(overlay, (overlay->destscale = player->mo->scale)); + K_FlipFromObject(overlay, player->mo); + } + } + + if (type != 0) + { + player->pflags |= PF_ATTACKDOWN; + K_PlayBoostTaunt(player->mo); + player->kartstuff[k_sneakertimer] = sneakertime; + } + else + player->kartstuff[k_levelbooster] = sneakertime; + + // set angle for spun out players: + player->kartstuff[k_boostangle] = (INT32)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! + user->pflags |= PF_ATTACKDOWN; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || !players[i].mo) + continue; + if (&players[i] == user) + continue; + if (players[i].kartstuff[k_position] < user->kartstuff[k_position]) + { + //P_FlashPal(&players[i], PAL_NUKE, 10); + + // Grow should get taken away. + if (players[i].kartstuff[k_growshrinktimer] > 0) + K_RemoveGrowShrink(&players[i]); + else + { + // Start shrinking! + K_DropItems(&players[i]); + players[i].kartstuff[k_growshrinktimer] = -(15*TICRATE); + + if (players[i].mo && !P_MobjWasRemoved(players[i].mo)) + { + players[i].mo->scalespeed = mapobjectscale/TICRATE; + players[i].mo->destscale = (6*mapobjectscale)/8; + if (cv_kartdebugshrink.value && !modeattacking && !players[i].bot) + players[i].mo->destscale = (6*players[i].mo->destscale)/8; + 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->kartstuff[k_position] > user->kartstuff[k_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); + + if (mo->player && mo->player->spectator) + return; + + if (mo->eflags & MFE_SPRUNG) + return; + +#ifdef ESLOPE + mo->standingslope = NULL; +#endif + + mo->eflags |= MFE_SPRUNG; + + if (mo->eflags & MFE_VERTICALFLIP) + vertispeed *= -1; + + if (vertispeed == 0) + { + fixed_t thrust; + + if (mo->player) + { + thrust = 3*mo->player->speed/2; + if (thrust < 48< 72<player->kartstuff[k_pogospring] != 2) + { + if (EITHERSNEAKER(mo->player)) + thrust = FixedMul(thrust, (5*FRACUNIT)/4); + else if (mo->player->kartstuff[k_invincibilitytimer]) + thrust = FixedMul(thrust, (9*FRACUNIT)/8); + } + } + else + { + thrust = FixedDiv(3*P_AproxDistance(mo->momx, mo->momy)/2, 5*FRACUNIT/2); + if (thrust < 16< 32<momz = P_MobjFlip(mo)*FixedMul(FINESINE(ANGLE_22h>>ANGLETOFINESHIFT), FixedMul(thrust, vscale)); + } + else + mo->momz = FixedMul(vertispeed, vscale); + + if (mo->eflags & MFE_UNDERWATER) + mo->momz = (117 * mo->momz) / 200; + + if (sound) + S_StartSound(mo, (sound == 1 ? sfx_kc2f : sfx_kpogos)); +} + +void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source) +{ + mobj_t *cachenext; + +killnext: + cachenext = banana->hnext; + + if (banana->health) + { + if (banana->eflags & MFE_VERTICALFLIP) + banana->z -= banana->height; + else + banana->z += banana->height; + + S_StartSound(banana, banana->info->deathsound); + P_KillMobj(banana, inflictor, source); + + P_SetObjectMomZ(banana, 8*FRACUNIT, false); + if (inflictor) + P_InstaThrust(banana, R_PointToAngle2(inflictor->x, inflictor->y, banana->x, banana->y)+ANGLE_90, 16*FRACUNIT); + } + + if ((banana = cachenext)) + goto killnext; +} + +// Just for firing/dropping items. +void K_UpdateHnextList(player_t *player, boolean clean) +{ + mobj_t *work = player->mo, *nextwork; + + if (!work) + return; + + nextwork = work->hnext; + + while ((work = nextwork) && !P_MobjWasRemoved(work)) + { + nextwork = work->hnext; + + if (!clean && (!work->movedir || work->movedir <= (UINT16)player->kartstuff[k_itemamount])) + continue; + + P_RemoveMobj(work); + } +} + +// For getting hit! +void K_DropHnextList(player_t *player) +{ + mobj_t *work = player->mo, *nextwork, *dropwork; + INT32 flip; + mobjtype_t type; + boolean orbit, ponground, dropall = true; + + if (!work || P_MobjWasRemoved(work)) + return; + + flip = P_MobjFlip(player->mo); + ponground = P_IsObjectOnGround(player->mo); + + if (player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD && player->kartstuff[k_itemamount]) + { + K_DoThunderShield(player); + player->kartstuff[k_itemamount] = 0; + player->kartstuff[k_itemtype] = KITEM_NONE; + player->kartstuff[k_curshield] = 0; + } + + nextwork = work->hnext; + + while ((work = nextwork) && !P_MobjWasRemoved(work)) + { + nextwork = work->hnext; + + switch (work->type) + { + // Kart orbit items + case MT_ORBINAUT_SHIELD: + orbit = true; + type = MT_ORBINAUT; + break; + case MT_JAWZ_SHIELD: + orbit = true; + type = MT_JAWZ_DUD; + break; + // Kart trailing items + case MT_BANANA_SHIELD: + orbit = false; + type = MT_BANANA; + break; + case MT_SSMINE_SHIELD: + orbit = false; + dropall = false; + type = MT_SSMINE; + break; + case MT_EGGMANITEM_SHIELD: + orbit = false; + type = MT_EGGMANITEM; + break; + // intentionally do nothing + case MT_SINK_SHIELD: + case MT_ROCKETSNEAKER: + return; + default: + continue; + } + + dropwork = P_SpawnMobj(work->x, work->y, work->z, type); + P_SetTarget(&dropwork->target, player->mo); + P_AddKartItem(dropwork); // needs to be called here so shrink can bust items off players in front of the user. + dropwork->angle = work->angle; + dropwork->flags2 = work->flags2; + dropwork->flags |= MF_NOCLIPTHING; + dropwork->floorz = work->floorz; + dropwork->ceilingz = work->ceilingz; + + if (ponground) + { + // floorz and ceilingz aren't properly set to account for FOFs and Polyobjects on spawn + // This should set it for FOFs + //P_TeleportMove(dropwork, dropwork->x, dropwork->y, dropwork->z); -- handled better by above floorz/ceilingz passing + + if (flip == 1) + { + if (dropwork->floorz > dropwork->target->z - dropwork->height) + { + dropwork->z = dropwork->floorz; + } + } + else + { + if (dropwork->ceilingz < dropwork->target->z + dropwork->target->height + dropwork->height) + { + dropwork->z = dropwork->ceilingz - dropwork->height; + } + } + } + + if (orbit) // splay out + { + dropwork->flags2 |= MF2_AMBUSH; + dropwork->z += flip; + dropwork->momx = player->mo->momx>>1; + dropwork->momy = player->mo->momy>>1; + dropwork->momz = 3*flip*mapobjectscale; + if (dropwork->eflags & MFE_UNDERWATER) + dropwork->momz = (117 * dropwork->momz) / 200; + P_Thrust(dropwork, work->angle - ANGLE_90, 6*mapobjectscale); + dropwork->movecount = 2; + dropwork->movedir = work->angle - ANGLE_90; + P_SetMobjState(dropwork, dropwork->info->deathstate); + dropwork->tics = -1; + if (type == MT_JAWZ_DUD) + dropwork->z += 20*flip*dropwork->scale; + else + { + dropwork->color = work->color; + dropwork->angle -= ANGLE_90; + } + } + else // plop on the ground + { + dropwork->flags &= ~MF_NOCLIPTHING; + dropwork->threshold = 10; + } + + P_RemoveMobj(work); + } + + { + // we need this here too because this is done in afterthink - pointers are cleaned up at the START of each tic... + P_SetTarget(&player->mo->hnext, NULL); + player->kartstuff[k_bananadrag] = 0; + if (player->kartstuff[k_eggmanheld]) + player->kartstuff[k_eggmanheld] = 0; + else if (player->kartstuff[k_itemheld] + && (dropall || (--player->kartstuff[k_itemamount] <= 0))) + { + player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0; + player->kartstuff[k_itemtype] = KITEM_NONE; + } + } +} + +// For getting EXTRA hit! +void K_DropItems(player_t *player) +{ + boolean thunderhack = (player->kartstuff[k_curshield] && player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD); + + if (thunderhack) + player->kartstuff[k_itemtype] = KITEM_NONE; + + K_DropHnextList(player); + + if (player->mo && !P_MobjWasRemoved(player->mo) && player->kartstuff[k_itemamount]) + { + mobj_t *drop = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FLOATINGITEM); + P_SetScale(drop, drop->scale>>4); + drop->destscale = (3*drop->destscale)/2;; + + drop->angle = player->mo->angle + ANGLE_90; + P_Thrust(drop, + FixedAngle(P_RandomFixed()*180) + player->mo->angle + ANGLE_90, + 16*mapobjectscale); + drop->momz = P_MobjFlip(player->mo)*3*mapobjectscale; + if (drop->eflags & MFE_UNDERWATER) + drop->momz = (117 * drop->momz) / 200; + + drop->threshold = (thunderhack ? KITEM_THUNDERSHIELD : player->kartstuff[k_itemtype]); + drop->movecount = player->kartstuff[k_itemamount]; + + drop->flags |= MF_NOCLIPTHING; + } + + K_StripItems(player); +} + +// When an item in the hnext chain dies. +void K_RepairOrbitChain(mobj_t *orbit) +{ + mobj_t *cachenext = orbit->hnext; + + // First, repair the chain + if (orbit->hnext && orbit->hnext->health && !P_MobjWasRemoved(orbit->hnext)) + { + P_SetTarget(&orbit->hnext->hprev, orbit->hprev); + P_SetTarget(&orbit->hnext, NULL); + } + + if (orbit->hprev && orbit->hprev->health && !P_MobjWasRemoved(orbit->hprev)) + { + P_SetTarget(&orbit->hprev->hnext, cachenext); + P_SetTarget(&orbit->hprev, NULL); + } + + // Then recount to make sure item amount is correct + if (orbit->target && orbit->target->player) + { + INT32 num = 0; + + mobj_t *cur = orbit->target->hnext; + mobj_t *prev = NULL; + + while (cur && !P_MobjWasRemoved(cur)) + { + prev = cur; + cur = cur->hnext; + if (++num > orbit->target->player->kartstuff[k_itemamount]) + P_RemoveMobj(prev); + else + prev->movedir = num; + } + + if (orbit->target->player->kartstuff[k_itemamount] != num) + orbit->target->player->kartstuff[k_itemamount] = num; + } +} + +// Simplified version of a code bit in P_MobjFloorZ +static fixed_t K_BananaSlopeZ(pslope_t *slope, fixed_t x, fixed_t y, fixed_t radius, boolean ceiling) +{ + fixed_t testx, testy; + + if (slope->d.x < 0) + testx = radius; + else + testx = -radius; + + if (slope->d.y < 0) + testy = radius; + else + testy = -radius; + + if ((slope->zdelta > 0) ^ !!(ceiling)) + { + testx = -testx; + testy = -testy; + } + + testx += x; + testy += y; + + return P_GetZAt(slope, testx, testy); +} + +static void K_CalculateBananaSlope(mobj_t *mobj, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t height, boolean flip, boolean player) +{ + fixed_t newz; + sector_t *sec; +#ifdef ESLOPE + pslope_t *slope = NULL; +#endif + + sec = R_PointInSubsector(x, y)->sector; + + if (flip) + { +#ifdef ESLOPE + if (sec->c_slope) + { + slope = sec->c_slope; + newz = K_BananaSlopeZ(slope, x, y, radius, true); + } + else +#endif + newz = sec->ceilingheight; + } + else + { +#ifdef ESLOPE + if (sec->f_slope) + { + slope = sec->f_slope; + newz = K_BananaSlopeZ(slope, x, y, radius, false); + } + else +#endif + newz = sec->floorheight; + } + + // Check FOFs for a better suited slope + if (sec->ffloors) + { + ffloor_t *rover; + + for (rover = sec->ffloors; rover; rover = rover->next) + { + fixed_t top, bottom; + fixed_t d1, d2; + + if (!(rover->flags & FF_EXISTS)) + continue; + + if ((!(((rover->flags & FF_BLOCKPLAYER && player) + || (rover->flags & FF_BLOCKOTHERS && !player)) + || (rover->flags & FF_QUICKSAND)) + || (rover->flags & FF_SWIMMABLE))) + continue; + +#ifdef ESLOPE + if (*rover->t_slope) + top = K_BananaSlopeZ(*rover->t_slope, x, y, radius, false); + else +#endif + top = *rover->topheight; + +#ifdef ESLOPE + if (*rover->b_slope) + bottom = K_BananaSlopeZ(*rover->b_slope, x, y, radius, true); + else +#endif + bottom = *rover->bottomheight; + + if (flip) + { + if (rover->flags & FF_QUICKSAND) + { + if (z < top && (z + height) > bottom) + { + if (newz > (z + height)) + { + newz = (z + height); + slope = NULL; + } + } + continue; + } + + d1 = (z + height) - (top + ((bottom - top)/2)); + d2 = z - (top + ((bottom - top)/2)); + + if (bottom < newz && abs(d1) < abs(d2)) + { + newz = bottom; +#ifdef ESLOPE + if (*rover->b_slope) + slope = *rover->b_slope; +#endif + } + } + else + { + if (rover->flags & FF_QUICKSAND) + { + if (z < top && (z + height) > bottom) + { + if (newz < z) + { + newz = z; + slope = NULL; + } + } + continue; + } + + d1 = z - (bottom + ((top - bottom)/2)); + d2 = (z + height) - (bottom + ((top - bottom)/2)); + + if (top > newz && abs(d1) < abs(d2)) + { + newz = top; +#ifdef ESLOPE + if (*rover->t_slope) + slope = *rover->t_slope; +#endif + } + } + } + } + +#if 0 + mobj->standingslope = slope; +#endif + +#ifdef HWRENDER + mobj->modeltilt = slope; +#endif +} + +// Move the hnext chain! +static void K_MoveHeldObjects(player_t *player) +{ + if (!player->mo) + return; + + if (!player->mo->hnext) + { + player->kartstuff[k_bananadrag] = 0; + if (player->kartstuff[k_eggmanheld]) + player->kartstuff[k_eggmanheld] = 0; + else if (player->kartstuff[k_itemheld]) + { + player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0; + player->kartstuff[k_itemtype] = KITEM_NONE; + } + return; + } + + if (P_MobjWasRemoved(player->mo->hnext)) + { + // we need this here too because this is done in afterthink - pointers are cleaned up at the START of each tic... + P_SetTarget(&player->mo->hnext, NULL); + player->kartstuff[k_bananadrag] = 0; + if (player->kartstuff[k_eggmanheld]) + player->kartstuff[k_eggmanheld] = 0; + else if (player->kartstuff[k_itemheld]) + { + player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0; + player->kartstuff[k_itemtype] = KITEM_NONE; + } + return; + } + + switch (player->mo->hnext->type) + { + case MT_ORBINAUT_SHIELD: // Kart orbit items + case MT_JAWZ_SHIELD: + { + mobj_t *cur = player->mo->hnext; + fixed_t speed = ((8 - min(4, player->kartstuff[k_itemamount])) * cur->info->speed) / 7; + + player->kartstuff[k_bananadrag] = 0; // Just to make sure + + while (cur && !P_MobjWasRemoved(cur)) + { + const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius); // mobj's distance from its Target, or Radius. + fixed_t z; + + if (!cur->health) + { + cur = cur->hnext; + continue; + } + + cur->color = player->skincolor; + + cur->angle -= ANGLE_90; + cur->angle += FixedAngle(speed); + + if (cur->extravalue1 < radius) + cur->extravalue1 += P_AproxDistance(cur->extravalue1, radius) / 12; + if (cur->extravalue1 > radius) + cur->extravalue1 = radius; + + // If the player is on the ceiling, then flip your items as well. + if (player && player->mo->eflags & MFE_VERTICALFLIP) + cur->eflags |= MFE_VERTICALFLIP; + else + cur->eflags &= ~MFE_VERTICALFLIP; + + // Shrink your items if the player shrunk too. + P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale))); + + if (P_MobjFlip(cur) > 0) + z = player->mo->z; + else + z = player->mo->z + player->mo->height - cur->height; + + cur->flags |= MF_NOCLIPTHING; // temporarily make them noclip other objects so they can't hit anyone while in the player + P_TeleportMove(cur, player->mo->x, player->mo->y, z); + cur->momx = FixedMul(FINECOSINE(cur->angle>>ANGLETOFINESHIFT), cur->extravalue1); + cur->momy = FixedMul(FINESINE(cur->angle>>ANGLETOFINESHIFT), cur->extravalue1); + cur->flags &= ~MF_NOCLIPTHING; + if (!P_TryMove(cur, player->mo->x + cur->momx, player->mo->y + cur->momy, true)) + P_SlideMove(cur, true); + if (P_IsObjectOnGround(player->mo)) + { + if (P_MobjFlip(cur) > 0) + { + if (cur->floorz > player->mo->z - cur->height) + z = cur->floorz; + } + else + { + if (cur->ceilingz < player->mo->z + player->mo->height + cur->height) + z = cur->ceilingz - cur->height; + } + } + + // Center it during the scale up animation + z += (FixedMul(mobjinfo[cur->type].height, player->mo->scale - cur->scale)>>1) * P_MobjFlip(cur); + + cur->z = z; + cur->momx = cur->momy = 0; + cur->angle += ANGLE_90; + + cur = cur->hnext; + } + } + break; + case MT_BANANA_SHIELD: // Kart trailing items + case MT_SSMINE_SHIELD: + case MT_EGGMANITEM_SHIELD: + case MT_SINK_SHIELD: + { + mobj_t *cur = player->mo->hnext; + mobj_t *targ = player->mo; + + if (P_IsObjectOnGround(player->mo) && player->speed > 0) + player->kartstuff[k_bananadrag]++; + + while (cur && !P_MobjWasRemoved(cur)) + { + const fixed_t radius = FixedHypot(targ->radius, targ->radius) + FixedHypot(cur->radius, cur->radius); + angle_t ang; + fixed_t targx, targy, targz; + fixed_t speed, dist; + + cur->flags &= ~MF_NOCLIPTHING; + + if (!cur->health) + { + cur = cur->hnext; + continue; + } + + if (cur->extravalue1 < radius) + cur->extravalue1 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12); + if (cur->extravalue1 > radius) + cur->extravalue1 = radius; + + if (cur != player->mo->hnext) + { + targ = cur->hprev; + dist = cur->extravalue1/4; + } + else + dist = cur->extravalue1/2; + + if (!targ || P_MobjWasRemoved(targ)) + continue; + + // Shrink your items if the player shrunk too. + P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale))); + + ang = targ->angle; + targx = targ->x + P_ReturnThrustX(cur, ang + ANGLE_180, dist); + targy = targ->y + P_ReturnThrustY(cur, ang + ANGLE_180, dist); + targz = targ->z; + + speed = FixedMul(R_PointToDist2(cur->x, cur->y, targx, targy), 3*FRACUNIT/4); + if (P_IsObjectOnGround(targ)) + targz = cur->floorz; + + cur->angle = R_PointToAngle2(cur->x, cur->y, targx, targy); + + /*if (P_IsObjectOnGround(player->mo) && player->speed > 0 && player->kartstuff[k_bananadrag] > TICRATE + && P_RandomChance(min(FRACUNIT/2, FixedDiv(player->speed, K_GetKartSpeed(player, false))/2))) + { + if (leveltime & 1) + targz += 8*(2*FRACUNIT)/7; + else + targz -= 8*(2*FRACUNIT)/7; + }*/ + + if (speed > dist) + P_InstaThrust(cur, cur->angle, speed-dist); + + P_SetObjectMomZ(cur, FixedMul(targz - cur->z, 7*FRACUNIT/8) - gravity, false); + + if (R_PointToDist2(cur->x, cur->y, targx, targy) > 768*FRACUNIT) + P_TeleportMove(cur, targx, targy, cur->z); + +#ifdef ESLOPE + if (P_IsObjectOnGround(cur)) + { + K_CalculateBananaSlope(cur, cur->x, cur->y, cur->z, + cur->radius, cur->height, (cur->eflags & MFE_VERTICALFLIP), false); + } +#endif + + cur = cur->hnext; + } + } + break; + case MT_ROCKETSNEAKER: // Special rocket sneaker stuff + { + mobj_t *cur = player->mo->hnext; + INT32 num = 0; + + while (cur && !P_MobjWasRemoved(cur)) + { + const fixed_t radius = FixedHypot(player->mo->radius, player->mo->radius) + FixedHypot(cur->radius, cur->radius); + boolean vibrate = ((leveltime & 1) && !cur->tracer); + angle_t angoffset; + fixed_t targx, targy, targz; + + cur->flags &= ~MF_NOCLIPTHING; + + if (player->kartstuff[k_rocketsneakertimer] <= TICRATE && (leveltime & 1)) + cur->flags2 |= MF2_DONTDRAW; + else + cur->flags2 &= ~MF2_DONTDRAW; + + if (num & 1) + P_SetMobjStateNF(cur, (vibrate ? S_ROCKETSNEAKER_LVIBRATE : S_ROCKETSNEAKER_L)); + else + P_SetMobjStateNF(cur, (vibrate ? S_ROCKETSNEAKER_RVIBRATE : S_ROCKETSNEAKER_R)); + + if (!player->kartstuff[k_rocketsneakertimer] || cur->extravalue2 || !cur->health) + { + num = (num+1) % 2; + cur = cur->hnext; + continue; + } + + if (cur->extravalue1 < radius) + cur->extravalue1 += FixedMul(P_AproxDistance(cur->extravalue1, radius), FRACUNIT/12); + if (cur->extravalue1 > radius) + cur->extravalue1 = radius; + + // Shrink your items if the player shrunk too. + P_SetScale(cur, (cur->destscale = FixedMul(FixedDiv(cur->extravalue1, radius), player->mo->scale))); + +#if 1 + { + angle_t input = player->frameangle - cur->angle; + boolean invert = (input > ANGLE_180); + if (invert) + input = InvAngle(input); + + input = FixedAngle(AngleFixed(input)/4); + if (invert) + input = InvAngle(input); + + cur->angle = cur->angle + input; + } +#else + cur->angle = player->frameangle; +#endif + + angoffset = ANGLE_90 + (ANGLE_180 * num); + + targx = player->mo->x + P_ReturnThrustX(cur, cur->angle + angoffset, cur->extravalue1); + targy = player->mo->y + P_ReturnThrustY(cur, cur->angle + angoffset, cur->extravalue1); + + { // bobbing, copy pasted from my kimokawaiii entry + const fixed_t pi = (22<mo->scale, 8 * FINESINE((((2*pi*(4*TICRATE)) * leveltime)>>ANGLETOFINESHIFT) & FINEMASK)); + targz = (player->mo->z + (player->mo->height/2)) + sine; + if (player->mo->eflags & MFE_VERTICALFLIP) + targz += (player->mo->height/2 - 32*player->mo->scale)*6; + + } + + if (cur->tracer) + { + fixed_t diffx, diffy, diffz; + + diffx = targx - cur->x; + diffy = targy - cur->y; + diffz = targz - cur->z; + + P_TeleportMove(cur->tracer, cur->tracer->x + diffx + P_ReturnThrustX(cur, cur->angle + angoffset, 6*cur->scale), + cur->tracer->y + diffy + P_ReturnThrustY(cur, cur->angle + angoffset, 6*cur->scale), cur->tracer->z + diffz); + P_SetScale(cur->tracer, (cur->tracer->destscale = 3*cur->scale/4)); + } + + P_TeleportMove(cur, targx, targy, targz); + K_FlipFromObject(cur, player->mo); // Update graviflip in real time thanks. +#ifdef HWRENDER + cur->modeltilt = player->mo->modeltilt; +#endif + num = (num+1) % 2; + cur = cur->hnext; + } + } + break; + default: + break; + } +} + +player_t *K_FindJawzTarget(mobj_t *actor, player_t *source) +{ + fixed_t best = -1; + player_t *wtarg = NULL; + INT32 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + angle_t thisang; + player_t *player; + + if (!playeringame[i]) + continue; + + player = &players[i]; + + if (player->spectator) + continue; // spectator + + if (!player->mo) + continue; + + if (player->mo->health <= 0) + continue; // dead + + // Don't target yourself, stupid. + if (player == source) + continue; + + // Don't home in on teammates. + if (G_GametypeHasTeams() && source->ctfteam == player->ctfteam) + continue; + + // Invisible, don't bother + if (player->kartstuff[k_hyudorotimer]) + continue; + + // Find the angle, see who's got the best. + thisang = actor->angle - R_PointToAngle2(actor->x, actor->y, player->mo->x, player->mo->y); + if (thisang > ANGLE_180) + thisang = InvAngle(thisang); + + // Jawz only go after the person directly ahead of you in race... sort of literally now! + if (G_RaceGametype()) + { + // Don't go for people who are behind you + if (thisang > ANGLE_67h) + continue; + // Don't pay attention to people who aren't above your position + if (player->kartstuff[k_position] >= source->kartstuff[k_position]) + continue; + if ((best == -1) || (player->kartstuff[k_position] > best)) + { + wtarg = player; + best = player->kartstuff[k_position]; + } + } + else + { + fixed_t thisdist; + fixed_t thisavg; + + // Don't go for people who are behind you + if (thisang > ANGLE_45) + continue; + + // Don't pay attention to dead players + if (player->kartstuff[k_bumper] <= 0) + continue; + + // Z pos too high/low + if (abs(player->mo->z - (actor->z + actor->momz)) > RING_DIST/8) + continue; + + thisdist = P_AproxDistance(player->mo->x - (actor->x + actor->momx), player->mo->y - (actor->y + actor->momy)); + + if (thisdist > 2*RING_DIST) // Don't go for people who are too far away + continue; + + thisavg = (AngleFixed(thisang) + thisdist) / 2; + + //CONS_Printf("got avg %d from player # %d\n", thisavg>>FRACBITS, i); + + if ((best == -1) || (thisavg < best)) + { + wtarg = player; + best = thisavg; + } + } + } + + return wtarg; +} + +// Engine Sounds. +static void K_UpdateEngineSounds(player_t *player, ticcmd_t *cmd) +{ + const INT32 numsnds = 13; + INT32 class, s, w; // engine class number + UINT8 volume = 255; + fixed_t volumedampen = 0; + INT32 targetsnd = 0; + INT32 i; + + s = (player->kartspeed-1)/3; + w = (player->kartweight-1)/3; + +#define LOCKSTAT(stat) \ + if (stat < 0) { stat = 0; } \ + if (stat > 2) { stat = 2; } + LOCKSTAT(s); + LOCKSTAT(w); +#undef LOCKSTAT + + class = s+(3*w); + + // Silence the engines + if (leveltime < 8 || player->spectator || player->exiting) + { + player->karthud[khud_enginesnd] = 0; // Reset sound number + return; + } + +#if 0 + if ((leveltime % 8) != ((player-players) % 8)) // Per-player offset, to make engines sound distinct! +#else + if (leveltime % 8) // .25 seconds of wait time between engine sounds +#endif + return; + + if ((leveltime >= starttime-(2*TICRATE) && leveltime <= starttime) || (player->kartstuff[k_respawn] == 1)) // Startup boosts + targetsnd = ((cmd->buttons & BT_ACCELERATE) ? 12 : 0); + else + targetsnd = (((6*cmd->forwardmove)/25) + ((player->speed / mapobjectscale)/5))/2; + + if (targetsnd < 0) + targetsnd = 0; + if (targetsnd > 12) + targetsnd = 12; + + if (player->karthud[khud_enginesnd] < targetsnd) + player->karthud[khud_enginesnd]++; + if (player->karthud[khud_enginesnd] > targetsnd) + player->karthud[khud_enginesnd]--; + + if (player->karthud[khud_enginesnd] < 0) + player->karthud[khud_enginesnd] = 0; + if (player->karthud[khud_enginesnd] > 12) + player->karthud[khud_enginesnd] = 12; + + for (i = 0; i < MAXPLAYERS; i++) + { + UINT8 thisvol = 0; + fixed_t dist; + + if (!playeringame[i] || !players[i].mo || players[i].spectator || players[i].exiting) + continue; + + if (P_IsDisplayPlayer(&players[i])) + { + volumedampen += FRACUNIT; // We already know what this is gonna be, let's not waste our time. + continue; + } + + dist = P_AproxDistance(P_AproxDistance(player->mo->x-players[i].mo->x, + player->mo->y-players[i].mo->y), player->mo->z-players[i].mo->z) / 2; + + dist = FixedDiv(dist, mapobjectscale); + + if (dist > 1536<>FRACBITS)) / (((1536<>(FRACBITS+4)); + + if (thisvol == 0) + continue; + + volumedampen += (thisvol * 257); // 255 * 257 = FRACUNIT + } + + if (volumedampen > FRACUNIT) + volume = FixedDiv(volume<>FRACBITS; + + if (volume <= 0) // Might as well + return; + + S_StartSoundAtVolume(player->mo, (sfx_krta00 + player->karthud[khud_enginesnd]) + (class*numsnds), volume); +} + +static void K_UpdateInvincibilitySounds(player_t *player) +{ + INT32 sfxnum = sfx_None; + + if (player->mo->health > 0 && !P_IsDisplayPlayer(player)) + { + if (cv_kartinvinsfx.value) + { + if (player->kartstuff[k_growshrinktimer] > 0) // Prioritize Grow + sfxnum = sfx_alarmg; + else if (player->kartstuff[k_invincibilitytimer] > 0) + sfxnum = sfx_alarmi; + } + else + { + if (player->kartstuff[k_growshrinktimer] > 0) + sfxnum = sfx_kgrow; + else if (player->kartstuff[k_invincibilitytimer] > 0) + sfxnum = sfx_kinvnc; + } + } + + if (sfxnum != sfx_None && !S_SoundPlaying(player->mo, sfxnum)) + S_StartSound(player->mo, sfxnum); + +#define STOPTHIS(this) \ + if (sfxnum != this && S_SoundPlaying(player->mo, this)) \ + S_StopSoundByID(player->mo, this); + STOPTHIS(sfx_alarmi); + STOPTHIS(sfx_alarmg); + STOPTHIS(sfx_kinvnc); + STOPTHIS(sfx_kgrow); +#undef STOPTHIS +} + +#define RINGANIM_NUMFRAMES 10 +#define RINGANIM_DELAYMAX 5 + +void K_KartPlayerHUDUpdate(player_t *player) +{ + if (player->karthud[khud_lapanimation]) + player->karthud[khud_lapanimation]--; + + if (player->karthud[khud_yougotem]) + player->karthud[khud_yougotem]--; + + if (player->karthud[khud_voices]) + player->karthud[khud_voices]--; + + if (player->karthud[khud_tauntvoices]) + player->karthud[khud_tauntvoices]--; + + if (G_RaceGametype()) + { + // 0 is the fast spin animation, set at 30 tics of ring boost or higher! + if (player->kartstuff[k_ringboost] >= 30) + player->karthud[khud_ringdelay] = 0; + else + player->karthud[khud_ringdelay] = ((RINGANIM_DELAYMAX+1) * (30 - player->kartstuff[k_ringboost])) / 30; + + if (player->karthud[khud_ringframe] == 0 && player->karthud[khud_ringdelay] > RINGANIM_DELAYMAX) + { + player->karthud[khud_ringframe] = 0; + player->karthud[khud_ringtics] = 0; + } + else if ((player->karthud[khud_ringtics]--) <= 0) + { + if (player->karthud[khud_ringdelay] == 0) // fast spin animation + { + player->karthud[khud_ringframe] = ((player->karthud[khud_ringframe]+2) % RINGANIM_NUMFRAMES); + player->karthud[khud_ringtics] = 0; + } + else + { + player->karthud[khud_ringframe] = ((player->karthud[khud_ringframe]+1) % RINGANIM_NUMFRAMES); + player->karthud[khud_ringtics] = min(RINGANIM_DELAYMAX, player->karthud[khud_ringdelay])-1; + } + } + + if (player->kartstuff[k_ringlock]) + { + UINT8 normalanim = (leveltime % 14); + UINT8 debtanim = 14 + (leveltime % 2); + + if (player->karthud[khud_ringspblock] >= 14) // debt animation + { + if ((player->kartstuff[k_rings] > 0) // Get out of 0 ring animation + && (normalanim == 3 || normalanim == 10)) // on these transition frames. + player->karthud[khud_ringspblock] = normalanim; + else + player->karthud[khud_ringspblock] = debtanim; + } + else // normal animation + { + if ((player->kartstuff[k_rings] <= 0) // Go into 0 ring animation + && (player->karthud[khud_ringspblock] == 1 || player->karthud[khud_ringspblock] == 8)) // on these transition frames. + player->karthud[khud_ringspblock] = debtanim; + else + player->karthud[khud_ringspblock] = normalanim; + } + } + else + player->karthud[khud_ringspblock] = (leveltime % 14); // reset to normal anim next time + } + + if (G_BattleGametype() && (player->exiting || player->kartstuff[k_comebacktimer])) + { + if (player->exiting) + { + if (player->exiting < 6*TICRATE) + player->karthud[khud_cardanimation] += ((164-player->karthud[khud_cardanimation])/8)+1; + else if (player->exiting == 6*TICRATE) + player->karthud[khud_cardanimation] = 0; + else if (player->karthud[khud_cardanimation] < 2*TICRATE) + player->karthud[khud_cardanimation]++; + } + else + { + if (player->kartstuff[k_comebacktimer] < 6*TICRATE) + player->karthud[khud_cardanimation] -= ((164-player->karthud[khud_cardanimation])/8)+1; + else if (player->kartstuff[k_comebacktimer] < 9*TICRATE) + player->karthud[khud_cardanimation] += ((164-player->karthud[khud_cardanimation])/8)+1; + } + + if (player->karthud[khud_cardanimation] > 164) + player->karthud[khud_cardanimation] = 164; + if (player->karthud[khud_cardanimation] < 0) + player->karthud[khud_cardanimation] = 0; + } + else if (G_RaceGametype() && player->exiting) + { + if (player->karthud[khud_cardanimation] < 2*TICRATE) + player->karthud[khud_cardanimation]++; + } + else + player->karthud[khud_cardanimation] = 0; +} + +#undef RINGANIM_DELAYMAX + +/** \brief Decreases various kart timers and powers per frame. Called in P_PlayerThink in p_user.c + + \param player player object passed from P_PlayerThink + \param cmd control input from player + + \return void +*/ +void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) +{ + K_UpdateOffroad(player); + K_UpdateDraft(player); + K_UpdateEngineSounds(player, cmd); // Thanks, VAda! + + // update boost angle if not spun out + if (!player->kartstuff[k_spinouttimer] && !player->kartstuff[k_wipeoutslow]) + player->kartstuff[k_boostangle] = (INT32)player->mo->angle; + + K_GetKartBoostPower(player); + + // Special effect objects! + if (player->mo && !player->spectator) + { + if (player->kartstuff[k_dashpadcooldown]) // Twinkle Circuit afterimages + { + mobj_t *ghost; + ghost = P_SpawnGhostMobj(player->mo); + ghost->fuse = player->kartstuff[k_dashpadcooldown]+1; + ghost->momx = player->mo->momx / (player->kartstuff[k_dashpadcooldown]+1); + ghost->momy = player->mo->momy / (player->kartstuff[k_dashpadcooldown]+1); + ghost->momz = player->mo->momz / (player->kartstuff[k_dashpadcooldown]+1); + player->kartstuff[k_dashpadcooldown]--; + } + + if (player->speed > 0) + { + // Speed lines + if (EITHERSNEAKER(player) || player->kartstuff[k_ringboost] + || player->kartstuff[k_driftboost] || player->kartstuff[k_startboost]) + { + mobj_t *fast = P_SpawnMobj(player->mo->x + (P_RandomRange(-36,36) * player->mo->scale), + player->mo->y + (P_RandomRange(-36,36) * player->mo->scale), + player->mo->z + (player->mo->height/2) + (P_RandomRange(-20,20) * player->mo->scale), + MT_FASTLINE); + fast->angle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); + fast->momx = 3*player->mo->momx/4; + fast->momy = 3*player->mo->momy/4; + fast->momz = 3*player->mo->momz/4; + K_MatchGenericExtraFlags(fast, player->mo); + } + + if (player->kartstuff[k_numboosts] > 0) // Boosting after images + { + mobj_t *ghost; + ghost = P_SpawnGhostMobj(player->mo); + ghost->extravalue1 = player->kartstuff[k_numboosts]+1; + ghost->extravalue2 = (leveltime % ghost->extravalue1); + ghost->fuse = ghost->extravalue1; + ghost->frame |= FF_FULLBRIGHT; + ghost->colorized = true; + //ghost->color = player->skincolor; + //ghost->momx = (3*player->mo->momx)/4; + //ghost->momy = (3*player->mo->momy)/4; + //ghost->momz = (3*player->mo->momz)/4; + if (leveltime & 1) + ghost->flags2 |= MF2_DONTDRAW; + } + + if (P_IsObjectOnGround(player->mo)) + { + // Offroad dust + if (player->kartstuff[k_boostpower] < FRACUNIT) + { + K_SpawnWipeoutTrail(player->mo, true); + if (leveltime % 6 == 0) + S_StartSound(player->mo, sfx_cdfm70); + } + + // Draft dust + if (player->kartstuff[k_draftpower] >= FRACUNIT) + { + K_SpawnDraftDust(player->mo); + /*if (leveltime % 23 == 0 || !S_SoundPlaying(player->mo, sfx_s265)) + S_StartSound(player->mo, sfx_s265);*/ + } + } + } + + if (G_RaceGametype() && player->kartstuff[k_rings] <= 0) // spawn ring debt indicator + { + mobj_t *debtflag = P_SpawnMobj(player->mo->x + player->mo->momx, player->mo->y + player->mo->momy, + player->mo->z + player->mo->momz + player->mo->height + (24*player->mo->scale), MT_THOK); + P_SetMobjState(debtflag, S_RINGDEBT); + P_SetScale(debtflag, (debtflag->destscale = player->mo->scale)); + K_MatchGenericExtraFlags(debtflag, player->mo); + debtflag->frame += (leveltime % 4); + if ((leveltime/12) & 1) + debtflag->frame += 4; + debtflag->color = player->skincolor; + debtflag->fuse = 2; + if (P_IsDisplayPlayer(player)) + debtflag->flags2 |= MF2_DONTDRAW; + } + + if (player->kartstuff[k_springstars] && (leveltime & 1)) + { + fixed_t randx = P_RandomRange(-40, 40) * player->mo->scale; + fixed_t randy = P_RandomRange(-40, 40) * player->mo->scale; + fixed_t randz = P_RandomRange(0, player->mo->height >> FRACBITS) << FRACBITS; + mobj_t *star = P_SpawnMobj( + player->mo->x + randx, + player->mo->y + randy, + player->mo->z + randz, + MT_KARMAFIREWORK); + + star->color = player->kartstuff[k_springcolor]; + star->flags |= MF_NOGRAVITY; + star->momx = player->mo->momx / 2; + star->momy = player->mo->momy / 2; + star->momz = player->mo->momz / 2; + star->fuse = 12; + star->scale = player->mo->scale; + star->destscale = star->scale / 2; + + player->kartstuff[k_springstars]--; + } + } + + if (player->playerstate == PST_DEAD || player->kartstuff[k_respawn] > 1) // Ensure these are set correctly here + { + player->mo->colorized = false; + player->mo->color = player->skincolor; + } + else if (player->kartstuff[k_eggmanexplode]) // You're gonna diiiiie + { + const INT32 flashtime = 4<<(player->kartstuff[k_eggmanexplode]/TICRATE); + if (player->kartstuff[k_eggmanexplode] == 1 || (player->kartstuff[k_eggmanexplode] % (flashtime/2) != 0)) + { + player->mo->colorized = false; + player->mo->color = player->skincolor; + } + else if (player->kartstuff[k_eggmanexplode] % flashtime == 0) + { + player->mo->colorized = true; + player->mo->color = SKINCOLOR_BLACK; + } + else + { + player->mo->colorized = true; + player->mo->color = SKINCOLOR_CRIMSON; + } + } + else if (player->kartstuff[k_invincibilitytimer]) // setting players to use the star colormap and spawning afterimages + { + player->mo->colorized = true; + } + else if (player->kartstuff[k_growshrinktimer]) // Ditto, for grow/shrink + { + if (player->kartstuff[k_growshrinktimer] % 5 == 0) + { + player->mo->colorized = true; + player->mo->color = (player->kartstuff[k_growshrinktimer] < 0 ? SKINCOLOR_CREAMSICLE : SKINCOLOR_PERIWINKLE); + } + else + { + player->mo->colorized = false; + player->mo->color = player->skincolor; + } } else if (player->kartstuff[k_killfield]) // You're gonna REALLY diiiiie { @@ -5306,178 +5585,185 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->mo->colorized = true; player->mo->color = SKINCOLOR_RUBY; } - } - else if (player->kartstuff[k_ringboost] && (leveltime & 1)) // ring boosting - { - player->mo->colorized = true; - } - else - { - player->mo->colorized = false; - } - - // DKR style camera for boosting - if (player->karthud[khud_boostcam] != 0 || player->karthud[khud_destboostcam] != 0) - { - if (player->karthud[khud_boostcam] < player->karthud[khud_destboostcam] - && player->karthud[khud_destboostcam] != 0) - { - player->karthud[khud_boostcam] += FRACUNIT/(TICRATE/4); - if (player->karthud[khud_boostcam] >= player->karthud[khud_destboostcam]) - player->karthud[khud_destboostcam] = 0; - } - else - { - player->karthud[khud_boostcam] -= FRACUNIT/TICRATE; - if (player->karthud[khud_boostcam] < player->karthud[khud_destboostcam]) - player->karthud[khud_boostcam] = player->karthud[khud_destboostcam] = 0; - } - //CONS_Printf("cam: %d, dest: %d\n", player->karthud[khud_boostcam], player->karthud[khud_destboostcam]); - } - - player->karthud[khud_timeovercam] = 0; - - // Specific hack because it insists on setting flashing tics during this anyway... - if (player->kartstuff[k_spinouttype] == 2) - { - player->powers[pw_flashing] = 0; - } - // Make ABSOLUTELY SURE that your flashing tics don't get set WHILE you're still in hit animations. - else if (player->kartstuff[k_spinouttimer] != 0 - || player->kartstuff[k_wipeoutslow] != 0 - || player->kartstuff[k_squishedtimer] != 0) - { - player->powers[pw_flashing] = K_GetKartFlashing(player); - } - else if (player->powers[pw_flashing] >= K_GetKartFlashing(player)) - { - player->powers[pw_flashing]--; - } - - if (player->kartstuff[k_spinouttimer]) - { - if ((P_IsObjectOnGround(player->mo) - || (player->kartstuff[k_spinouttype] != 0)) - && (!EITHERSNEAKER(player))) - { - player->kartstuff[k_spinouttimer]--; - if (player->kartstuff[k_wipeoutslow] > 1) - player->kartstuff[k_wipeoutslow]--; - // Actually, this caused more problems than it solved. Just make sure you set type before you spinout. Which K_SpinPlayer always does. - /*if (player->kartstuff[k_spinouttimer] == 0) - player->kartstuff[k_spinouttype] = 0;*/ // Reset type - } - } - else - { - if (player->kartstuff[k_wipeoutslow] >= 1) - player->mo->friction = ORIG_FRICTION; - player->kartstuff[k_wipeoutslow] = 0; - if (!comeback) - player->kartstuff[k_comebacktimer] = comebacktime; - else if (player->kartstuff[k_comebacktimer]) - { - player->kartstuff[k_comebacktimer]--; - if (P_IsDisplayPlayer(player) && player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer] <= 0) - comebackshowninfo = true; // client has already seen the message - } - } - - if (player->kartstuff[k_rings] > 20) - player->kartstuff[k_rings] = 20; - else if (player->kartstuff[k_rings] < -20) - player->kartstuff[k_rings] = -20; - - if (player->kartstuff[k_ringdelay]) - player->kartstuff[k_ringdelay]--; - - if (player->kartstuff[k_spinouttimer] || player->kartstuff[k_squishedtimer]) - player->kartstuff[k_ringboost] = 0; - else if (player->kartstuff[k_ringboost]) - player->kartstuff[k_ringboost]--; - - if (player->kartstuff[k_sneakertimer]) - player->kartstuff[k_sneakertimer]--; - - if (player->kartstuff[k_levelbooster]) - player->kartstuff[k_levelbooster]--; - - if (EITHERSNEAKER(player) && player->kartstuff[k_wipeoutslow] > 0 && player->kartstuff[k_wipeoutslow] < wipeoutslowtime+1) - player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1; - - if (player->kartstuff[k_floorboost]) - player->kartstuff[k_floorboost]--; - - if (player->kartstuff[k_driftboost]) - player->kartstuff[k_driftboost]--; - - if (player->kartstuff[k_startboost]) - player->kartstuff[k_startboost]--; - - if (player->kartstuff[k_invincibilitytimer]) - player->kartstuff[k_invincibilitytimer]--; - - if (!player->kartstuff[k_respawn] && player->kartstuff[k_growshrinktimer] != 0) - { - if (player->kartstuff[k_growshrinktimer] > 0) - player->kartstuff[k_growshrinktimer]--; - if (player->kartstuff[k_growshrinktimer] < 0) - player->kartstuff[k_growshrinktimer]++; - - // Back to normal - if (player->kartstuff[k_growshrinktimer] == 0) - K_RemoveGrowShrink(player); - } - - if (player->kartstuff[k_superring]) - { - if (player->kartstuff[k_superring] % 3 == 0) - { - mobj_t *ring = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RING); - ring->extravalue1 = 1; // Ring collect animation timer - ring->angle = player->mo->angle; // animation angle - P_SetTarget(&ring->target, player->mo); // toucher for thinker - player->kartstuff[k_pickuprings]++; - if (player->kartstuff[k_superring] <= 3) - ring->cvmem = 1; // play caching when collected - } - player->kartstuff[k_superring]--; - } - - if (player->kartstuff[k_stealingtimer] == 0 && player->kartstuff[k_stolentimer] == 0 - && player->kartstuff[k_rocketsneakertimer]) - player->kartstuff[k_rocketsneakertimer]--; - - if (player->kartstuff[k_hyudorotimer]) - player->kartstuff[k_hyudorotimer]--; - - if (player->kartstuff[k_sadtimer]) - player->kartstuff[k_sadtimer]--; - - if (player->kartstuff[k_stealingtimer]) - player->kartstuff[k_stealingtimer]--; - - if (player->kartstuff[k_stolentimer]) - player->kartstuff[k_stolentimer]--; - - if (player->kartstuff[k_squishedtimer]) - player->kartstuff[k_squishedtimer]--; - - if (player->kartstuff[k_justbumped]) - player->kartstuff[k_justbumped]--; - - if (player->kartstuff[k_tiregrease]) - player->kartstuff[k_tiregrease]--; - - // This doesn't go in HUD update because it has potential gameplay ramifications - if (player->karthud[khud_itemblink] && player->karthud[khud_itemblink]-- <= 0) - { - player->karthud[khud_itemblinkmode] = 0; - player->karthud[khud_itemblink] = 0; - } - - K_KartPlayerHUDUpdate(player); - + } + else if (player->kartstuff[k_ringboost] && (leveltime & 1)) // ring boosting + { + player->mo->colorized = true; + } + else + { + player->mo->colorized = false; + } + + // DKR style camera for boosting + if (player->karthud[khud_boostcam] != 0 || player->karthud[khud_destboostcam] != 0) + { + if (player->karthud[khud_boostcam] < player->karthud[khud_destboostcam] + && player->karthud[khud_destboostcam] != 0) + { + player->karthud[khud_boostcam] += FRACUNIT/(TICRATE/4); + if (player->karthud[khud_boostcam] >= player->karthud[khud_destboostcam]) + player->karthud[khud_destboostcam] = 0; + } + else + { + player->karthud[khud_boostcam] -= FRACUNIT/TICRATE; + if (player->karthud[khud_boostcam] < player->karthud[khud_destboostcam]) + player->karthud[khud_boostcam] = player->karthud[khud_destboostcam] = 0; + } + //CONS_Printf("cam: %d, dest: %d\n", player->karthud[khud_boostcam], player->karthud[khud_destboostcam]); + } + + player->karthud[khud_timeovercam] = 0; + + // Specific hack because it insists on setting flashing tics during this anyway... + if (player->kartstuff[k_spinouttype] == 2) + { + player->powers[pw_flashing] = 0; + } + // Make ABSOLUTELY SURE that your flashing tics don't get set WHILE you're still in hit animations. + else if (player->kartstuff[k_spinouttimer] != 0 + || player->kartstuff[k_wipeoutslow] != 0 + || player->kartstuff[k_squishedtimer] != 0) + { + player->powers[pw_flashing] = K_GetKartFlashing(player); + } + else if (player->powers[pw_flashing] >= K_GetKartFlashing(player)) + { + player->powers[pw_flashing]--; + } + + if (player->kartstuff[k_spinouttimer]) + { + if ((P_IsObjectOnGround(player->mo) + || (player->kartstuff[k_spinouttype] != 0)) + && (!EITHERSNEAKER(player))) + { + player->kartstuff[k_spinouttimer]--; + if (player->kartstuff[k_wipeoutslow] > 1) + player->kartstuff[k_wipeoutslow]--; + // Actually, this caused more problems than it solved. Just make sure you set type before you spinout. Which K_SpinPlayer always does. + /*if (player->kartstuff[k_spinouttimer] == 0) + player->kartstuff[k_spinouttype] = 0;*/ // Reset type + } + } + else + { + if (player->kartstuff[k_wipeoutslow] >= 1) + player->mo->friction = ORIG_FRICTION; + player->kartstuff[k_wipeoutslow] = 0; + if (!comeback) + player->kartstuff[k_comebacktimer] = comebacktime; + else if (player->kartstuff[k_comebacktimer]) + { + player->kartstuff[k_comebacktimer]--; + if (P_IsDisplayPlayer(player) && player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer] <= 0) + comebackshowninfo = true; // client has already seen the message + } + } + + if (player->kartstuff[k_rings] > 20) + player->kartstuff[k_rings] = 20; + else if (player->kartstuff[k_rings] < -20) + player->kartstuff[k_rings] = -20; + + if (player->kartstuff[k_ringdelay]) + player->kartstuff[k_ringdelay]--; + + if (player->kartstuff[k_spinouttimer] || player->kartstuff[k_squishedtimer]) + player->kartstuff[k_ringboost] = 0; + else if (player->kartstuff[k_ringboost]) + player->kartstuff[k_ringboost]--; + + if (player->kartstuff[k_sneakertimer]) + player->kartstuff[k_sneakertimer]--; + + if (player->kartstuff[k_levelbooster]) + player->kartstuff[k_levelbooster]--; + + if (EITHERSNEAKER(player) && player->kartstuff[k_wipeoutslow] > 0 && player->kartstuff[k_wipeoutslow] < wipeoutslowtime+1) + player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1; + + if (player->kartstuff[k_floorboost]) + player->kartstuff[k_floorboost]--; + + if (player->kartstuff[k_driftboost]) + player->kartstuff[k_driftboost]--; + + if (player->kartstuff[k_startboost]) + player->kartstuff[k_startboost]--; + + if (player->kartstuff[k_invincibilitytimer]) + player->kartstuff[k_invincibilitytimer]--; + + if (!player->kartstuff[k_respawn] && player->kartstuff[k_growshrinktimer] != 0) + { + if (player->kartstuff[k_growshrinktimer] > 0) + player->kartstuff[k_growshrinktimer]--; + if (player->kartstuff[k_growshrinktimer] < 0) + player->kartstuff[k_growshrinktimer]++; + + // Back to normal + if (player->kartstuff[k_growshrinktimer] == 0) + K_RemoveGrowShrink(player); + } + + if (player->kartstuff[k_superring]) + { + if (player->kartstuff[k_superring] % 3 == 0) + { + mobj_t *ring = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RING); + ring->extravalue1 = 1; // Ring collect animation timer + ring->angle = player->mo->angle; // animation angle + P_SetTarget(&ring->target, player->mo); // toucher for thinker + player->kartstuff[k_pickuprings]++; + if (player->kartstuff[k_superring] <= 3) + ring->cvmem = 1; // play caching when collected + } + player->kartstuff[k_superring]--; + } + + if (player->kartstuff[k_stealingtimer] == 0 && player->kartstuff[k_stolentimer] == 0 + && player->kartstuff[k_rocketsneakertimer]) + player->kartstuff[k_rocketsneakertimer]--; + + if (player->kartstuff[k_hyudorotimer]) + player->kartstuff[k_hyudorotimer]--; + + if (player->kartstuff[k_sadtimer]) + player->kartstuff[k_sadtimer]--; + + if (player->kartstuff[k_stealingtimer]) + player->kartstuff[k_stealingtimer]--; + + if (player->kartstuff[k_stolentimer]) + player->kartstuff[k_stolentimer]--; + + if (player->kartstuff[k_squishedtimer]) + { + player->kartstuff[k_squishedtimer]--; + + if ((player->kartstuff[k_squishedtimer] == 0) && !(player->pflags & PF_NOCLIP)) + { + player->mo->flags &= ~MF_NOCLIP; + } + } + + if (player->kartstuff[k_justbumped]) + player->kartstuff[k_justbumped]--; + + if (player->kartstuff[k_tiregrease]) + player->kartstuff[k_tiregrease]--; + + // This doesn't go in HUD update because it has potential gameplay ramifications + if (player->karthud[khud_itemblink] && player->karthud[khud_itemblink]-- <= 0) + { + player->karthud[khud_itemblinkmode] = 0; + player->karthud[khud_itemblink] = 0; + } + + K_KartPlayerHUDUpdate(player); + if (G_BattleGametype() && player->kartstuff[k_bumper] > 0 && !player->kartstuff[k_spinouttimer] && !player->kartstuff[k_squishedtimer] && !player->kartstuff[k_respawn] && !player->powers[pw_flashing]) @@ -5499,3832 +5785,3905 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) } } else if (player->kartstuff[k_killfield] > 0) - player->kartstuff[k_killfield]--; - - if (P_IsObjectOnGround(player->mo)) - player->kartstuff[k_waterskip] = 0; - - if (player->kartstuff[k_instashield]) - player->kartstuff[k_instashield]--; - - if (player->kartstuff[k_eggmanexplode]) - { - if (player->spectator || (G_BattleGametype() && !player->kartstuff[k_bumper])) - player->kartstuff[k_eggmanexplode] = 0; - else - { - player->kartstuff[k_eggmanexplode]--; - if (player->kartstuff[k_eggmanexplode] <= 0) - { - mobj_t *eggsexplode; - //player->powers[pw_flashing] = 0; - eggsexplode = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SPBEXPLOSION); - if (player->kartstuff[k_eggmanblame] >= 0 - && player->kartstuff[k_eggmanblame] < MAXPLAYERS - && playeringame[player->kartstuff[k_eggmanblame]] - && !players[player->kartstuff[k_eggmanblame]].spectator - && players[player->kartstuff[k_eggmanblame]].mo) - P_SetTarget(&eggsexplode->target, players[player->kartstuff[k_eggmanblame]].mo); - } - } - } - - if (player->kartstuff[k_comebacktimer]) - player->kartstuff[k_comebackmode] = 0; - - if (P_IsObjectOnGround(player->mo) && player->kartstuff[k_pogospring]) - { - if (P_MobjFlip(player->mo)*player->mo->momz <= 0) - player->kartstuff[k_pogospring] = 0; - } - - if (cmd->buttons & BT_DRIFT) - player->kartstuff[k_jmp] = 1; - else - player->kartstuff[k_jmp] = 0; - - // Respawn Checker - if (player->kartstuff[k_respawn]) - K_RespawnChecker(player); - - // Roulette Code - K_KartItemRoulette(player, cmd); - - // Handle invincibility sfx - K_UpdateInvincibilitySounds(player); // Also thanks, VAda! - - // Plays the music after the starting countdown. - if (P_IsLocalPlayer(player) && leveltime == (starttime + (TICRATE/2))) - { - S_ChangeMusic(mapmusname, mapmusflags, true); - S_ShowMusicCredit(); - } -} - -void K_KartPlayerAfterThink(player_t *player) -{ - // Moved to afterthink, as at this point the players have had their distances to the finish line updated - // and this will correctly account for all players - K_KartUpdatePosition(player); - - if (player->kartstuff[k_curshield] - || player->kartstuff[k_invincibilitytimer] - || (player->kartstuff[k_growshrinktimer] != 0 && player->kartstuff[k_growshrinktimer] % 5 == 4)) // 4 instead of 0 because this is afterthink! - { - player->mo->frame |= FF_FULLBRIGHT; - } - else - { - if (!(player->mo->state->frame & FF_FULLBRIGHT)) - player->mo->frame &= ~FF_FULLBRIGHT; - } - - // Move held objects (Bananas, Orbinaut, etc) - K_MoveHeldObjects(player); - - // Jawz reticule (seeking) - if (player->kartstuff[k_itemtype] == KITEM_JAWZ && player->kartstuff[k_itemheld]) - { - INT32 lasttarg = player->kartstuff[k_lastjawztarget]; - player_t *targ; - mobj_t *ret; - - if (player->kartstuff[k_jawztargetdelay] && playeringame[lasttarg] && !players[lasttarg].spectator) - { - targ = &players[lasttarg]; - player->kartstuff[k_jawztargetdelay]--; - } - else - targ = K_FindJawzTarget(player->mo, player); - - if (!targ || !targ->mo || P_MobjWasRemoved(targ->mo)) - { - player->kartstuff[k_lastjawztarget] = -1; - player->kartstuff[k_jawztargetdelay] = 0; - return; - } - - ret = P_SpawnMobj(targ->mo->x, targ->mo->y, targ->mo->z, MT_PLAYERRETICULE); - P_SetTarget(&ret->target, targ->mo); - ret->frame |= ((leveltime % 10) / 2); - ret->tics = 1; - ret->color = player->skincolor; - - if (targ-players != lasttarg) - { - if (P_IsDisplayPlayer(player) || P_IsDisplayPlayer(targ)) - S_StartSound(NULL, sfx_s3k89); - else - S_StartSound(targ->mo, sfx_s3k89); - - player->kartstuff[k_lastjawztarget] = targ-players; - player->kartstuff[k_jawztargetdelay] = 5; - } - } - else - { - player->kartstuff[k_lastjawztarget] = -1; - player->kartstuff[k_jawztargetdelay] = 0; - } -} - -// Returns false if this player being placed here causes them to collide with any other player -// Used in g_game.c for match etc. respawning -// This does not check along the z because the z is not correctly set for the spawnee at this point -boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y) -{ - INT32 i; - fixed_t p1radius = players[playernum].mo->radius; - for (i = 0; i < MAXPLAYERS; i++) - { - if (playernum == i || !playeringame[i] || players[i].spectator || !players[i].mo || players[i].mo->health <= 0 - || players[i].playerstate != PST_LIVE || (players[i].mo->flags & MF_NOCLIP) || (players[i].mo->flags & MF_NOCLIPTHING)) - continue; - - if (abs(x - players[i].mo->x) < (p1radius + players[i].mo->radius) - && abs(y - players[i].mo->y) < (p1radius + players[i].mo->radius)) - { - return false; - } - } - return true; -} - -// countersteer is how strong the controls are telling us we are turning -// turndir is the direction the controls are telling us to turn, -1 if turning right and 1 if turning left -static INT16 K_GetKartDriftValue(player_t *player, fixed_t countersteer) -{ - INT16 basedrift, driftangle; - fixed_t driftweight = player->kartweight*14; // 12 - - // If they aren't drifting or on the ground this doesn't apply - if (player->kartstuff[k_drift] == 0 || !P_IsObjectOnGround(player->mo)) - return 0; - - if (player->kartstuff[k_driftend] != 0) - return -266*player->kartstuff[k_drift]; // Drift has ended and we are tweaking their angle back a bit - - //basedrift = 90*player->kartstuff[k_drift]; // 450 - //basedrift = 93*player->kartstuff[k_drift] - driftweight*3*player->kartstuff[k_drift]/10; // 447 - 303 - basedrift = 83*player->kartstuff[k_drift] - (driftweight - 14)*player->kartstuff[k_drift]/5; // 415 - 303 - driftangle = abs((252 - driftweight)*player->kartstuff[k_drift]/5); - - if (player->kartstuff[k_tiregrease] > 0) // Buff drift-steering while in greasemode - basedrift += (basedrift / greasetics) * player->kartstuff[k_tiregrease]; - - return basedrift + FixedMul(driftangle, countersteer); -} - -INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) -{ - fixed_t p_maxspeed = FixedMul(K_GetKartSpeed(player, false), 3*FRACUNIT); - fixed_t adjustangle = FixedDiv((p_maxspeed>>16) - (player->speed>>16), (p_maxspeed>>16) + player->kartweight); - - if (player->spectator) - return turnvalue; - - if (player->kartstuff[k_drift] != 0 && P_IsObjectOnGround(player->mo)) - { - // If we're drifting we have a completely different turning value - if (player->kartstuff[k_driftend] == 0) - { - // 800 is the max set in g_game.c with angleturn - fixed_t countersteer = FixedDiv(turnvalue*FRACUNIT, 800*FRACUNIT); - turnvalue = K_GetKartDriftValue(player, countersteer); - } - else - turnvalue = (INT16)(turnvalue + K_GetKartDriftValue(player, FRACUNIT)); - - return turnvalue; - } - - turnvalue = FixedMul(turnvalue, adjustangle); // Weight has a small effect on turning - - if (EITHERSNEAKER(player) || player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_growshrinktimer] > 0) - turnvalue = FixedMul(turnvalue, FixedDiv(5*FRACUNIT, 4*FRACUNIT)); - - return turnvalue; -} - -INT32 K_GetKartDriftSparkValue(player_t *player) -{ - UINT8 kartspeed = (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) - ? 1 - : player->kartspeed; - return (26*4 + kartspeed*2 + (9 - player->kartweight))*8; -} - -static void K_KartDrift(player_t *player, boolean onground) -{ - fixed_t minspeed = (10 * player->mo->scale); - INT32 dsone = K_GetKartDriftSparkValue(player); - INT32 dstwo = dsone*2; - INT32 dsthree = dstwo*2; - - // Drifting is actually straffing + automatic turning. - // Holding the Jump button will enable drifting. - - // Drift Release (Moved here so you can't "chain" drifts) - if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5) - // || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1)) - && player->kartstuff[k_driftcharge] < dsone - && onground) - { - player->kartstuff[k_driftcharge] = 0; - } - else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5) - // || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1)) - && (player->kartstuff[k_driftcharge] >= dsone && player->kartstuff[k_driftcharge] < dstwo) - && onground) - { - if (player->kartstuff[k_driftboost] < 20) - player->kartstuff[k_driftboost] = 20; - S_StartSound(player->mo, sfx_s23c); - //K_SpawnDashDustRelease(player); - player->kartstuff[k_driftcharge] = 0; - } - else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5) - // || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1)) - && player->kartstuff[k_driftcharge] < dsthree - && onground) - { - if (player->kartstuff[k_driftboost] < 50) - player->kartstuff[k_driftboost] = 50; - S_StartSound(player->mo, sfx_s23c); - //K_SpawnDashDustRelease(player); - player->kartstuff[k_driftcharge] = 0; - } - else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5) - // || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1)) - && player->kartstuff[k_driftcharge] >= dsthree - && onground) - { - if (player->kartstuff[k_driftboost] < 125) - player->kartstuff[k_driftboost] = 125; - S_StartSound(player->mo, sfx_s23c); - //K_SpawnDashDustRelease(player); - player->kartstuff[k_driftcharge] = 0; - } - - // Drifting: left or right? - if ((player->cmd.driftturn > 0) && player->speed > minspeed && player->kartstuff[k_jmp] == 1 - && (player->kartstuff[k_drift] == 0 || player->kartstuff[k_driftend] == 1)) // && player->kartstuff[k_drift] != 1) - { - // Starting left drift - player->kartstuff[k_drift] = 1; - player->kartstuff[k_driftend] = player->kartstuff[k_driftcharge] = 0; - } - else if ((player->cmd.driftturn < 0) && player->speed > minspeed && player->kartstuff[k_jmp] == 1 - && (player->kartstuff[k_drift] == 0 || player->kartstuff[k_driftend] == 1)) // && player->kartstuff[k_drift] != -1) - { - // Starting right drift - player->kartstuff[k_drift] = -1; - player->kartstuff[k_driftend] = player->kartstuff[k_driftcharge] = 0; - } - else if (player->kartstuff[k_jmp] == 0) // || player->kartstuff[k_turndir] == 0) - { - // drift is not being performed so if we're just finishing set driftend and decrement counters - if (player->kartstuff[k_drift] > 0) - { - player->kartstuff[k_drift]--; - player->kartstuff[k_driftend] = 1; - } - else if (player->kartstuff[k_drift] < 0) - { - player->kartstuff[k_drift]++; - player->kartstuff[k_driftend] = 1; - } - else - player->kartstuff[k_driftend] = 0; - } - - - // Incease/decrease the drift value to continue drifting in that direction - if (player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_jmp] == 1 && onground && player->kartstuff[k_drift] != 0) - { - fixed_t driftadditive = 24; - - if (player->kartstuff[k_drift] >= 1) // Drifting to the left - { - player->kartstuff[k_drift]++; - if (player->kartstuff[k_drift] > 5) - player->kartstuff[k_drift] = 5; - - if (player->cmd.driftturn > 0) // Inward - driftadditive += abs(player->cmd.driftturn)/100; - if (player->cmd.driftturn < 0) // Outward - driftadditive -= abs(player->cmd.driftturn)/75; - } - else if (player->kartstuff[k_drift] <= -1) // Drifting to the right - { - player->kartstuff[k_drift]--; - if (player->kartstuff[k_drift] < -5) - player->kartstuff[k_drift] = -5; - - if (player->cmd.driftturn < 0) // Inward - driftadditive += abs(player->cmd.driftturn)/100; - if (player->cmd.driftturn > 0) // Outward - driftadditive -= abs(player->cmd.driftturn)/75; - } - - // Disable drift-sparks until you're going fast enough - if (player->kartstuff[k_getsparks] == 0 - || (player->kartstuff[k_offroad] - && !player->kartstuff[k_invincibilitytimer] - && !player->kartstuff[k_hyudorotimer] - && !EITHERSNEAKER(player))) - driftadditive = 0; - if (player->speed > minspeed*2) - player->kartstuff[k_getsparks] = 1; - - // This spawns the drift sparks - if (player->kartstuff[k_driftcharge] + driftadditive >= dsone) - K_SpawnDriftSparks(player); - - // Sound whenever you get a different tier of sparks - if (P_IsDisplayPlayer(player) // UGHGHGH... - && ((player->kartstuff[k_driftcharge] < dsone && player->kartstuff[k_driftcharge]+driftadditive >= dsone) - || (player->kartstuff[k_driftcharge] < dstwo && player->kartstuff[k_driftcharge]+driftadditive >= dstwo) - || (player->kartstuff[k_driftcharge] < dsthree && player->kartstuff[k_driftcharge]+driftadditive >= dsthree))) - { - //S_StartSound(player->mo, sfx_s3ka2); - S_StartSoundAtVolume(player->mo, sfx_s3ka2, 192); // Ugh... - } - - player->kartstuff[k_driftcharge] += driftadditive; - player->kartstuff[k_driftend] = 0; - } - - // Stop drifting - if (player->kartstuff[k_spinouttimer] > 0 || player->speed < minspeed) - { - player->kartstuff[k_drift] = player->kartstuff[k_driftcharge] = 0; - player->kartstuff[k_aizdriftstrat] = player->kartstuff[k_brakedrift] = 0; - player->kartstuff[k_getsparks] = 0; - } - - if ((!EITHERSNEAKER(player)) - || (!player->cmd.driftturn) - || (!player->kartstuff[k_aizdriftstrat]) - || (player->cmd.driftturn > 0) != (player->kartstuff[k_aizdriftstrat] > 0)) - { - if (!player->kartstuff[k_drift]) - player->kartstuff[k_aizdriftstrat] = 0; - else - player->kartstuff[k_aizdriftstrat] = ((player->kartstuff[k_drift] > 0) ? 1 : -1); - } - else if (player->kartstuff[k_aizdriftstrat] && !player->kartstuff[k_drift]) - K_SpawnAIZDust(player); - - if (player->kartstuff[k_drift] - && ((player->cmd.buttons & BT_BRAKE) - || !(player->cmd.buttons & BT_ACCELERATE)) - && P_IsObjectOnGround(player->mo)) - { - if (!player->kartstuff[k_brakedrift]) - K_SpawnBrakeDriftSparks(player); - player->kartstuff[k_brakedrift] = 1; - } - else - player->kartstuff[k_brakedrift] = 0; -} - -/*-------------------------------------------------- - static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) - - Gets the next waypoint of a player, by finding their closest waypoint, then checking which of itself and next or - previous waypoints are infront of the player. - - Input Arguments:- - player - The player the next waypoint is being found for - - Return:- - The waypoint that is the player's next waypoint ---------------------------------------------------*/ -static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) -{ - waypoint_t *bestwaypoint = NULL; - if ((player != NULL) && (player->mo != NULL) && (P_MobjWasRemoved(player->mo) == false)) - { - waypoint_t *waypoint = NULL; - - waypoint = K_GetClosestWaypointToMobj(player->mo); - bestwaypoint = waypoint; - - // check the waypoint's location in relation to the player - // If it's generally in front, it's fine, otherwise, use the best next/previous waypoint. - // EXCEPTION: If our closest waypoint is the finishline AND we're facing towards it, don't do this. - // Otherwise it breaks the distance calculations. - if (waypoint != NULL) - { - boolean finishlinehack = false; - angle_t playerangle = player->mo->angle; - angle_t angletowaypoint = - R_PointToAngle2(player->mo->x, player->mo->y, waypoint->mobj->x, waypoint->mobj->y); - angle_t angledelta = playerangle - angletowaypoint; - - - if (angledelta > ANGLE_180) - { - angledelta = InvAngle(angledelta); - } - - if (bestwaypoint == K_GetFinishLineWaypoint()) - { - // facing towards the finishline - if (angledelta <= ANGLE_90) - { - finishlinehack = true; - } - } - - if ((angledelta > ANGLE_45) && (finishlinehack == false)) - { - angle_t nextbestdelta = angledelta; - size_t i = 0U; - - if ((waypoint->nextwaypoints != NULL) && (waypoint->numnextwaypoints > 0U)) - { - for (i = 0U; i < waypoint->numnextwaypoints; i++) - { - angletowaypoint = R_PointToAngle2( - player->mo->x, player->mo->y, - waypoint->nextwaypoints[i]->mobj->x, waypoint->nextwaypoints[i]->mobj->y); - angledelta = playerangle - angletowaypoint; - - if (angledelta > ANGLE_180) - { - angledelta = InvAngle(angledelta); - } - - if (angledelta < nextbestdelta) - { - bestwaypoint = waypoint->nextwaypoints[i]; - nextbestdelta = angledelta; - } - } - } - - if ((waypoint->prevwaypoints != NULL) && (waypoint->numprevwaypoints > 0U)) - { - for (i = 0U; i < waypoint->numprevwaypoints; i++) - { - angletowaypoint = R_PointToAngle2( - player->mo->x, player->mo->y, - waypoint->prevwaypoints[i]->mobj->x, waypoint->prevwaypoints[i]->mobj->y); - angledelta = playerangle - angletowaypoint; - - if (angledelta > ANGLE_180) - { - angledelta = InvAngle(angledelta); - } - - if (angledelta < nextbestdelta) - { - bestwaypoint = waypoint->prevwaypoints[i]; - nextbestdelta = angledelta; - } - } - } - } - } - } - - return bestwaypoint; -} - -static boolean K_PlayerCloserToNextWaypoints(waypoint_t *const waypoint, player_t *const player) -{ - boolean nextiscloser = true; - - if ((waypoint != NULL) && (player != NULL) && (player->mo != NULL)) - { - size_t i = 0U; - waypoint_t *currentwpcheck = NULL; - angle_t angletoplayer = ANGLE_MAX; - angle_t currentanglecheck = ANGLE_MAX; - angle_t bestangle = ANGLE_MAX; - - angletoplayer = R_PointToAngle2(waypoint->mobj->x, waypoint->mobj->y, - player->mo->x, player->mo->y); - - for (i = 0U; i < waypoint->numnextwaypoints; i++) - { - currentwpcheck = waypoint->nextwaypoints[i]; - currentanglecheck = R_PointToAngle2( - waypoint->mobj->x, waypoint->mobj->y, currentwpcheck->mobj->x, currentwpcheck->mobj->y); - - // Get delta angle - currentanglecheck = currentanglecheck - angletoplayer; - - if (currentanglecheck > ANGLE_180) - { - currentanglecheck = InvAngle(currentanglecheck); - } - - if (currentanglecheck < bestangle) - { - bestangle = currentanglecheck; - } - } - - for (i = 0U; i < waypoint->numprevwaypoints; i++) - { - currentwpcheck = waypoint->prevwaypoints[i]; - currentanglecheck = R_PointToAngle2( - waypoint->mobj->x, waypoint->mobj->y, currentwpcheck->mobj->x, currentwpcheck->mobj->y); - - // Get delta angle - currentanglecheck = currentanglecheck - angletoplayer; - - if (currentanglecheck > ANGLE_180) - { - currentanglecheck = InvAngle(currentanglecheck); - } - - if (currentanglecheck < bestangle) - { - bestangle = currentanglecheck; - nextiscloser = false; - break; - } - } - } - - return nextiscloser; -} - -/*-------------------------------------------------- - static void K_UpdateDistanceFromFinishLine(player_t *const player) - - Updates the distance a player has to the finish line. - - Input Arguments:- - player - The player the distance is being updated for - - Return:- - None ---------------------------------------------------*/ -static void K_UpdateDistanceFromFinishLine(player_t *const player) -{ - if ((player != NULL) && (player->mo != NULL)) - { - if (player->exiting) - { - player->nextwaypoint = K_GetFinishLineWaypoint(); - player->distancetofinish = 0U; - } - else - { - waypoint_t *finishline = K_GetFinishLineWaypoint(); - waypoint_t *nextwaypoint = K_GetPlayerNextWaypoint(player); - - if ((nextwaypoint != player->nextwaypoint) && - (K_GetWaypointIsShortcut(nextwaypoint) == false) && (K_GetWaypointIsEnabled(nextwaypoint) == true)) - { - size_t i = 0U; - waypoint_t *aimwaypoint = NULL; - player->starpostx = nextwaypoint->mobj->x >> FRACBITS; - player->starposty = nextwaypoint->mobj->y >> FRACBITS; - player->starpostz = nextwaypoint->mobj->z >> FRACBITS; - - // player gravflip determines which way to respawn - player->kartstuff[k_starpostflip] = player->mo->flags2 & MF2_OBJECTFLIP; - - // starpostangle is to the first valid nextwaypoint for simplicity - // if we reach the last waypoint and it's still not valid, just use it anyway. Someone needs to fix - // their map! - for (i = 0U; i < nextwaypoint->numnextwaypoints; i++) - { - aimwaypoint = nextwaypoint->nextwaypoints[i]; - - if ((i == nextwaypoint->numnextwaypoints - 1U) - || ((K_GetWaypointIsEnabled(aimwaypoint) == true) - && (K_GetWaypointIsSpawnpoint(aimwaypoint) == true))) - { - player->starpostangle = R_PointToAngle2( - nextwaypoint->mobj->x, nextwaypoint->mobj->y, aimwaypoint->mobj->x, aimwaypoint->mobj->y); - break; - } - } - } - - player->nextwaypoint = nextwaypoint; - - // nextwaypoint is now the waypoint that is in front of us - if ((player->nextwaypoint != NULL) && (finishline != NULL)) - { - const boolean useshortcuts = false; - const boolean huntbackwards = false; - boolean pathfindsuccess = false; - path_t pathtofinish = {}; - - pathfindsuccess = - K_PathfindToWaypoint(player->nextwaypoint, finishline, &pathtofinish, useshortcuts, huntbackwards); - - // Update the player's distance to the finish line if a path was found. - // Using shortcuts won't find a path, so distance won't be updated until the player gets back on track - if (pathfindsuccess == true) - { - // Add euclidean distance to the next waypoint to the distancetofinish - UINT32 adddist; - fixed_t disttowaypoint = - P_AproxDistance( - player->mo->x - player->nextwaypoint->mobj->x, - player->mo->y - player->nextwaypoint->mobj->y); - disttowaypoint = P_AproxDistance(disttowaypoint, player->mo->z - player->nextwaypoint->mobj->z); - - adddist = ((UINT32)disttowaypoint) >> FRACBITS; - - player->distancetofinish = pathtofinish.totaldist + adddist; - Z_Free(pathtofinish.array); - - // distancetofinish is currently a flat distance to the finish line, but in order to be fully - // correct we need to add to it the length of the entire circuit multiplied by the number of laps - // left after this one. This will give us the total distance to the finish line, and allow item - // distance calculation to work easily - if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == 0U) - { - const UINT8 numfulllapsleft = ((UINT8)cv_numlaps.value - player->laps); - - player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); - - // An additional HACK, to fix looking backwards towards the finish line - // If the player's next waypoint is the finishline and the angle distance from player to - // connectin waypoints implies they're closer to a next waypoint, add a full track distance - if (player->nextwaypoint == finishline) - { - if (K_PlayerCloserToNextWaypoints(player->nextwaypoint, player) == true) - { - player->distancetofinish += K_GetCircuitLength(); - } - } - } - } - } - } - } -} - -// -// K_KartUpdatePosition -// -void K_KartUpdatePosition(player_t *player) -{ - fixed_t position = 1; - fixed_t oldposition = player->kartstuff[k_position]; - fixed_t i; - - if (player->spectator || !player->mo) - return; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator || !players[i].mo) - continue; - - if (G_RaceGametype()) - { - // I'm a lap behind this player OR - // My distance to the finish line is higher, so I'm behind - if ((players[i].laps > player->laps) - || (players[i].distancetofinish < player->distancetofinish)) - { - position++; - } - } - else if (G_BattleGametype()) - { - if (player->exiting) // End of match standings - { - if (players[i].marescore > player->marescore) // Only score matters - position++; - } - else - { - if (players[i].kartstuff[k_bumper] == player->kartstuff[k_bumper] && players[i].marescore > player->marescore) - position++; - else if (players[i].kartstuff[k_bumper] > player->kartstuff[k_bumper]) - position++; - } - } - } - - if (leveltime < starttime || oldposition == 0) - oldposition = position; - - if (oldposition != position) // Changed places? - player->kartstuff[k_positiondelay] = 10; // Position number growth - - player->kartstuff[k_position] = position; -} - -// -// K_StripItems -// -void K_StripItems(player_t *player) -{ - player->kartstuff[k_itemtype] = KITEM_NONE; - player->kartstuff[k_itemamount] = 0; - player->kartstuff[k_itemheld] = 0; - - player->kartstuff[k_rocketsneakertimer] = 0; - - if (!player->kartstuff[k_itemroulette] || player->kartstuff[k_roulettetype] != 2) - { - player->kartstuff[k_itemroulette] = 0; - player->kartstuff[k_roulettetype] = 0; - } - player->kartstuff[k_eggmanheld] = 0; - - player->kartstuff[k_hyudorotimer] = 0; - player->kartstuff[k_stealingtimer] = 0; - player->kartstuff[k_stolentimer] = 0; - - player->kartstuff[k_curshield] = 0; - //player->kartstuff[k_thunderanim] = 0; - player->kartstuff[k_bananadrag] = 0; - - player->kartstuff[k_sadtimer] = 0; - - K_UpdateHnextList(player, true); -} - -void K_StripOther(player_t *player) -{ - player->kartstuff[k_itemroulette] = 0; - player->kartstuff[k_roulettetype] = 0; - - player->kartstuff[k_invincibilitytimer] = 0; - K_RemoveGrowShrink(player); - - if (player->kartstuff[k_eggmanexplode]) - { - player->kartstuff[k_eggmanexplode] = 0; - player->kartstuff[k_eggmanblame] = -1; - } -} - -// SRB2Kart: blockmap iterate for attraction shield users -static mobj_t *attractmo; -static fixed_t attractdist; -static inline boolean PIT_AttractingRings(mobj_t *thing) -{ - if (!attractmo || P_MobjWasRemoved(attractmo)) - return false; - - if (!attractmo->player) - return false; // not a player - - if (thing->health <= 0 || !thing) - return true; // dead - - if (thing->type != MT_RING && thing->type != MT_FLINGRING) - return true; // not a ring - - if (thing->extravalue1) - return true; // in special ring animation - - if (thing->cusval) - return true; // already attracted - - // see if it went over / under - if (attractmo->z - (attractdist>>2) > thing->z + thing->height) - return true; // overhead - if (attractmo->z + attractmo->height + (attractdist>>2) < thing->z) - return true; // underneath - - if (P_AproxDistance(attractmo->x - thing->x, attractmo->y - thing->y) < attractdist) - return true; // Too far away - - // set target - P_SetTarget(&thing->tracer, attractmo); - // flag to show it's been attracted once before - thing->cusval = 1; - return true; // find other rings -} - -/** Looks for rings near a player in the blockmap. - * - * \param pmo Player object looking for rings to attract - * \sa A_AttractChase - */ -static void K_LookForRings(mobj_t *pmo) -{ - INT32 bx, by, xl, xh, yl, yh; - attractdist = FixedMul(RING_DIST, pmo->scale)>>2; - - // Use blockmap to check for nearby rings - yh = (unsigned)(pmo->y + attractdist - bmaporgy)>>MAPBLOCKSHIFT; - yl = (unsigned)(pmo->y - attractdist - bmaporgy)>>MAPBLOCKSHIFT; - xh = (unsigned)(pmo->x + attractdist - bmaporgx)>>MAPBLOCKSHIFT; - xl = (unsigned)(pmo->x - attractdist - bmaporgx)>>MAPBLOCKSHIFT; - - attractmo = pmo; - - for (by = yl; by <= yh; by++) - for (bx = xl; bx <= xh; bx++) - P_BlockThingsIterator(bx, by, PIT_AttractingRings); -} - -// -// K_MoveKartPlayer -// -void K_MoveKartPlayer(player_t *player, boolean onground) -{ - ticcmd_t *cmd = &player->cmd; - boolean ATTACK_IS_DOWN = ((cmd->buttons & BT_ATTACK) && !(player->pflags & PF_ATTACKDOWN)); - boolean HOLDING_ITEM = (player->kartstuff[k_itemheld] || player->kartstuff[k_eggmanheld]); - boolean NO_HYUDORO = (player->kartstuff[k_stolentimer] == 0 && player->kartstuff[k_stealingtimer] == 0); - - K_UpdateDistanceFromFinishLine(player); - - if (!player->exiting) - { - if (player->kartstuff[k_oldposition] < player->kartstuff[k_position]) // But first, if you lost a place, - { - player->kartstuff[k_oldposition] = player->kartstuff[k_position]; // then the other player taunts. - K_RegularVoiceTimers(player); // and you can't for a bit - } - else if (player->kartstuff[k_oldposition] > player->kartstuff[k_position]) // Otherwise, - { - K_PlayOvertakeSound(player->mo); // Say "YOU'RE TOO SLOW!" - player->kartstuff[k_oldposition] = player->kartstuff[k_position]; // Restore the old position, - } - } - - if (player->kartstuff[k_positiondelay]) - player->kartstuff[k_positiondelay]--; - - // Prevent ring misfire - if (!(cmd->buttons & BT_ATTACK)) - { - if (player->kartstuff[k_itemtype] == KITEM_NONE - && NO_HYUDORO && !(HOLDING_ITEM - || player->kartstuff[k_itemamount] - || player->kartstuff[k_itemroulette] - || player->kartstuff[k_growshrinktimer] > 0 - || player->kartstuff[k_rocketsneakertimer] - || player->kartstuff[k_eggmanexplode])) - player->kartstuff[k_userings] = 1; - else - player->kartstuff[k_userings] = 0; - } - - if ((player->pflags & PF_ATTACKDOWN) && !(cmd->buttons & BT_ATTACK)) - player->pflags &= ~PF_ATTACKDOWN; - else if (cmd->buttons & BT_ATTACK) - player->pflags |= PF_ATTACKDOWN; - - if (player && player->mo && player->mo->health > 0 && !player->spectator && !(player->exiting || mapreset) && leveltime > starttime - && player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_squishedtimer] == 0 && player->kartstuff[k_respawn] == 0) - { - // First, the really specific, finicky items that function without the item being directly in your item slot. - // Karma item dropping - if (player->kartstuff[k_comebackmode] && !player->kartstuff[k_comebacktimer]) - { - if (ATTACK_IS_DOWN) - { - mobj_t *newitem; - - if (player->kartstuff[k_comebackmode] == 1) - { - newitem = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RANDOMITEM); - newitem->threshold = 69; // selected "randomly". - } - else - { - newitem = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM); - if (player->kartstuff[k_eggmanblame] >= 0 - && player->kartstuff[k_eggmanblame] < MAXPLAYERS - && playeringame[player->kartstuff[k_eggmanblame]] - && !players[player->kartstuff[k_eggmanblame]].spectator - && players[player->kartstuff[k_eggmanblame]].mo) - P_SetTarget(&newitem->target, players[player->kartstuff[k_eggmanblame]].mo); - player->kartstuff[k_eggmanblame] = -1; - } - - newitem->flags2 = (player->mo->flags2 & MF2_OBJECTFLIP); - newitem->fuse = 15*TICRATE; // selected randomly. - - player->kartstuff[k_comebackmode] = 0; - player->kartstuff[k_comebacktimer] = comebacktime; - S_StartSound(player->mo, sfx_s254); - } - } - else - { - // Ring boosting - if (player->kartstuff[k_userings]) - { - if ((player->pflags & PF_ATTACKDOWN) && !player->kartstuff[k_ringdelay] && player->kartstuff[k_rings] > 0) - { - mobj_t *ring = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RING); - P_SetMobjState(ring, S_FASTRING1); - ring->extravalue1 = 1; // Ring use animation timer - ring->extravalue2 = 1; // Ring use animation flag - ring->shadowscale = 0; - P_SetTarget(&ring->target, player->mo); // user - player->kartstuff[k_rings]--; - player->kartstuff[k_ringdelay] = 3; - } - } - // Other items - else - { - // Eggman Monitor exploding - if (player->kartstuff[k_eggmanexplode]) - { - if (ATTACK_IS_DOWN && player->kartstuff[k_eggmanexplode] <= 3*TICRATE && player->kartstuff[k_eggmanexplode] > 1) - player->kartstuff[k_eggmanexplode] = 1; - } - // Eggman Monitor throwing - else if (player->kartstuff[k_eggmanheld]) - { - if (ATTACK_IS_DOWN) - { - K_ThrowKartItem(player, false, MT_EGGMANITEM, -1, 0); - K_PlayAttackTaunt(player->mo); - player->kartstuff[k_eggmanheld] = 0; - K_UpdateHnextList(player, true); - } - } - // Rocket Sneaker usage - else if (player->kartstuff[k_rocketsneakertimer] > 1) - { - if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO) - { - K_DoSneaker(player, 2); - K_PlayBoostTaunt(player->mo); - player->kartstuff[k_rocketsneakertimer] -= 2*TICRATE; - if (player->kartstuff[k_rocketsneakertimer] < 1) - player->kartstuff[k_rocketsneakertimer] = 1; - } - } - // Grow Canceling - else if (player->kartstuff[k_growshrinktimer] > 0) - { - if (player->kartstuff[k_growcancel] >= 0) - { - if (cmd->buttons & BT_ATTACK) - { - player->kartstuff[k_growcancel]++; - if (player->kartstuff[k_growcancel] > 26) - K_RemoveGrowShrink(player); - } - else - player->kartstuff[k_growcancel] = 0; - } - else - { - if ((cmd->buttons & BT_ATTACK) || (player->pflags & PF_ATTACKDOWN)) - player->kartstuff[k_growcancel] = -1; - else - player->kartstuff[k_growcancel] = 0; - } - } - else if (player->kartstuff[k_itemamount] <= 0) - { - player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0; - } - else - { - switch (player->kartstuff[k_itemtype]) - { - case KITEM_SNEAKER: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO) - { - K_DoSneaker(player, 1); - K_PlayBoostTaunt(player->mo); - player->kartstuff[k_itemamount]--; - } - break; - case KITEM_ROCKETSNEAKER: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO - && player->kartstuff[k_rocketsneakertimer] == 0) - { - INT32 moloop; - mobj_t *mo = NULL; - mobj_t *prev = player->mo; - - K_PlayBoostTaunt(player->mo); - //player->kartstuff[k_itemheld] = 1; - S_StartSound(player->mo, sfx_s3k3a); - - //K_DoSneaker(player, 2); - - player->kartstuff[k_rocketsneakertimer] = (itemtime*3); - player->kartstuff[k_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; - } - } - break; - case KITEM_INVINCIBILITY: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage normally, so you're free to waste it if you have multiple - { - if (!player->kartstuff[k_invincibilitytimer]) - { - mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INVULNFLASH); - P_SetTarget(&overlay->target, player->mo); - overlay->destscale = player->mo->scale; - P_SetScale(overlay, player->mo->scale); - } - player->kartstuff[k_invincibilitytimer] = itemtime+(2*TICRATE); // 10 seconds - P_RestoreMusic(player); - if (!P_IsDisplayPlayer(player)) - S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmi : sfx_kinvnc)); - K_PlayPowerGloatSound(player->mo); - player->kartstuff[k_itemamount]--; - } - 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); - player->kartstuff[k_itemheld] = 1; - S_StartSound(player->mo, sfx_s254); - - for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++) - { - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD); - if (!mo) - { - player->kartstuff[k_itemamount] = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->threshold = 10; - mo->movecount = player->kartstuff[k_itemamount]; - mo->movedir = moloop+1; - P_SetTarget(&mo->target, player->mo); - P_SetTarget(&mo->hprev, prev); - P_SetTarget(&prev->hnext, mo); - prev = mo; - } - } - else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld]) // Banana x3 thrown - { - K_ThrowKartItem(player, false, MT_BANANA, -1, 0); - K_PlayAttackTaunt(player->mo); - player->kartstuff[k_itemamount]--; - K_UpdateHnextList(player, false); - } - break; - case KITEM_EGGMAN: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - player->kartstuff[k_itemamount]--; - player->kartstuff[k_eggmanheld] = 1; - S_StartSound(player->mo, sfx_s254); - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_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); - } - } - 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); - player->kartstuff[k_itemheld] = 1; - S_StartSound(player->mo, sfx_s3k3a); - - for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++) - { - newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->kartstuff[k_itemamount]) * moloop) << FRACBITS) + ANGLE_90; - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD); - if (!mo) - { - player->kartstuff[k_itemamount] = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->angle = newangle; - mo->threshold = 10; - mo->movecount = player->kartstuff[k_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; - } - } - else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld]) // Orbinaut x3 thrown - { - K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0); - K_PlayAttackTaunt(player->mo); - player->kartstuff[k_itemamount]--; - K_UpdateHnextList(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); - player->kartstuff[k_itemheld] = 1; - S_StartSound(player->mo, sfx_s3k3a); - - for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++) - { - newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->kartstuff[k_itemamount]) * moloop) << FRACBITS) + ANGLE_90; - mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD); - if (!mo) - { - player->kartstuff[k_itemamount] = moloop; - break; - } - mo->flags |= MF_NOCLIPTHING; - mo->angle = newangle; - mo->threshold = 10; - mo->movecount = player->kartstuff[k_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; - } - } - else if (ATTACK_IS_DOWN && HOLDING_ITEM && player->kartstuff[k_itemheld]) // Jawz thrown - { - if (player->kartstuff[k_throwdir] == 1 || player->kartstuff[k_throwdir] == 0) - K_ThrowKartItem(player, true, MT_JAWZ, 1, 0); - else if (player->kartstuff[k_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); - player->kartstuff[k_itemamount]--; - K_UpdateHnextList(player, false); - } - break; - case KITEM_MINE: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - player->kartstuff[k_itemheld] = 1; - 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); - } - } - else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld]) - { - K_ThrowKartItem(player, false, MT_SSMINE, 1, 1); - K_PlayAttackTaunt(player->mo); - player->kartstuff[k_itemamount]--; - player->kartstuff[k_itemheld] = 0; - K_UpdateHnextList(player, true); - } - break; - case KITEM_BALLHOG: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->kartstuff[k_itemamount]--; - K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0); - K_PlayAttackTaunt(player->mo); - } - break; - case KITEM_SPB: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->kartstuff[k_itemamount]--; - K_ThrowKartItem(player, true, MT_SPB, 1, 0); - K_PlayAttackTaunt(player->mo); - } - break; - case KITEM_GROW: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO - && player->kartstuff[k_growshrinktimer] <= 0) // Grow holds the item box hostage - { - if (player->kartstuff[k_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 = (3*mapobjectscale)/2; - if (cv_kartdebugshrink.value && !modeattacking && !player->bot) - player->mo->destscale = (6*player->mo->destscale)/8; - player->kartstuff[k_growshrinktimer] = itemtime+(4*TICRATE); // 12 seconds - P_RestoreMusic(player); - if (!P_IsDisplayPlayer(player)) - S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); - S_StartSound(player->mo, sfx_kc5a); - } - player->kartstuff[k_itemamount]--; - } - break; - case KITEM_SHRINK: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - K_DoShrink(player); - player->kartstuff[k_itemamount]--; - K_PlayPowerGloatSound(player->mo); - } - break; - case KITEM_THUNDERSHIELD: - if (player->kartstuff[k_curshield] != 1) - { - 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); - S_StartSound(shield, sfx_s3k41); - player->kartstuff[k_curshield] = 1; - } - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - K_DoThunderShield(player); - player->kartstuff[k_itemamount]--; - K_PlayAttackTaunt(player->mo); - } - break; - case KITEM_HYUDORO: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->kartstuff[k_itemamount]--; - K_DoHyudoroSteal(player); // yes. yes they do. - } - break; - case KITEM_POGOSPRING: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO - && !player->kartstuff[k_pogospring]) - { - K_PlayBoostTaunt(player->mo); - K_DoPogoSpring(player->mo, 32<kartstuff[k_pogospring] = 1; - player->kartstuff[k_itemamount]--; - } - break; - case KITEM_SUPERRING: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - player->kartstuff[k_superring] += (10*3); - player->kartstuff[k_itemamount]--; - } - break; - case KITEM_KITCHENSINK: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) - { - mobj_t *mo; - player->kartstuff[k_itemheld] = 1; - 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); - } - } - else if (ATTACK_IS_DOWN && HOLDING_ITEM && player->kartstuff[k_itemheld]) // Sink thrown - { - K_ThrowKartItem(player, false, MT_SINK, 1, 2); - K_PlayAttackTaunt(player->mo); - player->kartstuff[k_itemamount]--; - player->kartstuff[k_itemheld] = 0; - K_UpdateHnextList(player, true); - } - break; - case KITEM_SAD: - if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO - && !player->kartstuff[k_sadtimer]) - { - player->kartstuff[k_sadtimer] = stealtime; - player->kartstuff[k_itemamount]--; - } - break; - default: - break; - } - } - } - } - - // No more! - if (!player->kartstuff[k_itemamount]) - { - player->kartstuff[k_itemheld] = 0; - player->kartstuff[k_itemtype] = KITEM_NONE; - } - - if (spbplace == -1 || player->kartstuff[k_position] != spbplace) - player->kartstuff[k_ringlock] = 0; // reset ring lock - - if (player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD) - { - if ((player->kartstuff[k_rings]+player->kartstuff[k_pickuprings]) < 20 && !player->kartstuff[k_ringlock]) - K_LookForRings(player->mo); - } - else - player->kartstuff[k_curshield] = 0; - - if (player->kartstuff[k_growshrinktimer] <= 0) - player->kartstuff[k_growcancel] = -1; - - if (player->kartstuff[k_itemtype] == KITEM_SPB - || player->kartstuff[k_itemtype] == KITEM_SHRINK - || player->kartstuff[k_growshrinktimer] < 0) - indirectitemcooldown = 20*TICRATE; - - if (player->kartstuff[k_hyudorotimer] > 0) - { - if (splitscreen) - { - if (leveltime & 1) - player->mo->flags2 |= MF2_DONTDRAW; - else - player->mo->flags2 &= ~MF2_DONTDRAW; - - if (player->kartstuff[k_hyudorotimer] >= (1*TICRATE/2) && player->kartstuff[k_hyudorotimer] <= hyudorotime-(1*TICRATE/2)) - { - if (player == &players[displayplayers[1]]) - player->mo->eflags |= MFE_DRAWONLYFORP2; - else if (player == &players[displayplayers[2]] && splitscreen > 1) - player->mo->eflags |= MFE_DRAWONLYFORP3; - else if (player == &players[displayplayers[3]] && splitscreen > 2) - player->mo->eflags |= MFE_DRAWONLYFORP4; - else if (player == &players[displayplayers[0]]) - player->mo->eflags |= MFE_DRAWONLYFORP1; - else - player->mo->flags2 |= MF2_DONTDRAW; - } - else - player->mo->eflags &= ~(MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4); - } - else - { - if (P_IsDisplayPlayer(player) - || (!P_IsDisplayPlayer(player) && (player->kartstuff[k_hyudorotimer] < (1*TICRATE/2) || player->kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2)))) - { - if (leveltime & 1) - player->mo->flags2 |= MF2_DONTDRAW; - else - player->mo->flags2 &= ~MF2_DONTDRAW; - } - else - player->mo->flags2 |= MF2_DONTDRAW; - } - - player->powers[pw_flashing] = player->kartstuff[k_hyudorotimer]; // We'll do this for now, let's people know about the invisible people through subtle hints - } - else if (player->kartstuff[k_hyudorotimer] == 0) - { - player->mo->flags2 &= ~MF2_DONTDRAW; - player->mo->eflags &= ~(MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4); - } - - if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) // dead in match? you da bomb - { - K_DropItems(player); //K_StripItems(player); - K_StripOther(player); - player->mo->flags2 |= MF2_SHADOW; - player->powers[pw_flashing] = player->kartstuff[k_comebacktimer]; - } - else if (G_RaceGametype() || player->kartstuff[k_bumper] > 0) - { - player->mo->flags2 &= ~MF2_SHADOW; - } - } - - if (onground) - { - fixed_t prevfriction = player->mo->friction; - - // Reduce friction after hitting a horizontal spring - if (player->kartstuff[k_tiregrease]) - player->mo->friction += ((FRACUNIT - prevfriction) / greasetics) * player->kartstuff[k_tiregrease]; - - // Friction - if (!player->kartstuff[k_offroad]) - { - if (player->speed > 0 && cmd->forwardmove == 0 && player->mo->friction == 59392) - player->mo->friction += 4608; - } - - if (player->speed > 0 && cmd->forwardmove < 0) // change friction while braking no matter what, otherwise it's not any more effective than just letting go off accel - player->mo->friction -= 2048; - - // Karma ice physics - if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) - player->mo->friction += 1228; - - // Wipeout slowdown - if (player->kartstuff[k_spinouttimer] && player->kartstuff[k_wipeoutslow]) - { - if (player->kartstuff[k_offroad]) - player->mo->friction -= 4912; - if (player->kartstuff[k_wipeoutslow] == 1) - player->mo->friction -= 9824; - } - - // Friction was changed, so we must recalculate a bunch of stuff - if (player->mo->friction != prevfriction) - { - if (player->mo->friction > FRACUNIT) - player->mo->friction = FRACUNIT; - if (player->mo->friction < 0) - player->mo->friction = 0; - - player->mo->movefactor = FixedDiv(ORIG_FRICTION, player->mo->friction); - - if (player->mo->movefactor < FRACUNIT) - player->mo->movefactor = 19*player->mo->movefactor - 18*FRACUNIT; - else - player->mo->movefactor = FRACUNIT; - - if (player->mo->movefactor < 32) - player->mo->movefactor = 32; - } - } - - K_KartDrift(player, P_IsObjectOnGround(player->mo)); // Not using onground, since we don't want this affected by spring pads - - // Quick Turning - // You can't turn your kart when you're not moving. - // So now it's time to burn some rubber! - if (player->speed < 2 && leveltime > starttime && cmd->buttons & BT_ACCELERATE && cmd->buttons & BT_BRAKE && cmd->driftturn != 0) - { - if (leveltime % 8 == 0) - S_StartSound(player->mo, sfx_s224); - } - - // Squishing - // If a Grow player or a sector crushes you, get flattened instead of being killed. - - if (player->kartstuff[k_squishedtimer] <= 0) - { - player->mo->flags &= ~MF_NOCLIP; - } - else - { - player->mo->flags |= MF_NOCLIP; - player->mo->momx = 0; - player->mo->momy = 0; - } - - // Play the starting countdown sounds - if (player == &players[displayplayers[0]]) // Don't play louder in splitscreen - { - if ((leveltime == starttime-(3*TICRATE)) || (leveltime == starttime-(2*TICRATE)) || (leveltime == starttime-TICRATE)) - S_StartSound(NULL, sfx_s3ka7); - if (leveltime == starttime) - { - S_StartSound(NULL, sfx_s3kad); - S_StopMusic(); // The GO! sound stops the level start ambience - } - } - - // Start charging once you're given the opportunity. - if (leveltime >= starttime-(2*TICRATE) && leveltime <= starttime) - { - if (cmd->buttons & BT_ACCELERATE) - { - if (player->kartstuff[k_boostcharge] == 0) - player->kartstuff[k_boostcharge] = cmd->latency; - - player->kartstuff[k_boostcharge]++; - } - else - player->kartstuff[k_boostcharge] = 0; - } - - // Increase your size while charging your engine. - if (leveltime < starttime+10) - { - player->mo->scalespeed = mapobjectscale/12; - player->mo->destscale = mapobjectscale + (player->kartstuff[k_boostcharge]*131); - if (cv_kartdebugshrink.value && !modeattacking && !player->bot) - player->mo->destscale = (6*player->mo->destscale)/8; - } - - // Determine the outcome of your charge. - if (leveltime > starttime && player->kartstuff[k_boostcharge]) - { - // Not even trying? - if (player->kartstuff[k_boostcharge] < 35) - { - if (player->kartstuff[k_boostcharge] > 17) - S_StartSound(player->mo, sfx_cdfm00); // chosen instead of a conventional skid because it's more engine-like - } - // Get an instant boost! - else if (player->kartstuff[k_boostcharge] <= 50) - { - player->kartstuff[k_startboost] = (50-player->kartstuff[k_boostcharge])+20; - - if (player->kartstuff[k_boostcharge] <= 36) - { - player->kartstuff[k_startboost] = 0; - K_DoSneaker(player, 0); - player->kartstuff[k_sneakertimer] = 70; // PERFECT BOOST!! - - if (!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) // Let everyone hear this one - S_StartSound(player->mo, sfx_s25f); - } - else - { - K_SpawnDashDustRelease(player); // already handled for perfect boosts by K_DoSneaker - if ((!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) && P_IsDisplayPlayer(player)) - { - if (player->kartstuff[k_boostcharge] <= 40) - S_StartSound(player->mo, sfx_cdfm01); // You were almost there! - else - S_StartSound(player->mo, sfx_s23c); // Nope, better luck next time. - } - } - } - // You overcharged your engine? Those things are expensive!!! - else if (player->kartstuff[k_boostcharge] > 50) - { - player->powers[pw_nocontrol] = 40; - //S_StartSound(player->mo, sfx_kc34); - S_StartSound(player->mo, sfx_s3k83); - player->pflags |= PF_SKIDDOWN; // cheeky pflag reuse - } - - player->kartstuff[k_boostcharge] = 0; - } -} - -void K_CalculateBattleWanted(void) -{ - UINT8 numingame = 0, numplaying = 0, numwanted = 0; - SINT8 bestbumperplayer = -1, bestbumper = -1; - SINT8 camppos[MAXPLAYERS]; // who is the biggest camper - UINT8 ties = 0, nextcamppos = 0; - boolean setbumper = false; - UINT8 i, j; - - if (!G_BattleGametype()) - { - for (i = 0; i < 4; i++) - battlewanted[i] = -1; - return; - } - - wantedcalcdelay = wantedfrequency; - - for (i = 0; i < MAXPLAYERS; i++) - camppos[i] = -1; // initialize - - for (i = 0; i < MAXPLAYERS; i++) - { - UINT8 position = 1; - - if (!playeringame[i] || players[i].spectator) // Not playing - continue; - - if (players[i].exiting) // We're done, don't calculate. - return; - - numplaying++; - - if (players[i].kartstuff[k_bumper] <= 0) // Not alive, so don't do anything else - continue; - - numingame++; - - if (bestbumper == -1 || players[i].kartstuff[k_bumper] > bestbumper) - { - bestbumper = players[i].kartstuff[k_bumper]; - bestbumperplayer = i; - } - else if (players[i].kartstuff[k_bumper] == bestbumper) - bestbumperplayer = -1; // Tie, no one has best bumper. - - for (j = 0; j < MAXPLAYERS; j++) - { - if (!playeringame[j] || players[j].spectator) - continue; - if (players[j].kartstuff[k_bumper] <= 0) - continue; - if (j == i) - continue; - if (players[j].kartstuff[k_wanted] == players[i].kartstuff[k_wanted] && players[j].marescore > players[i].marescore) - position++; - else if (players[j].kartstuff[k_wanted] > players[i].kartstuff[k_wanted]) - position++; - } - - position--; // Make zero based - - while (camppos[position] != -1) // Port priority! - position++; - - camppos[position] = i; - } - - if (numplaying <= 2 || (numingame <= 2 && bestbumper == 1)) // In 1v1s then there's no need for WANTED. In bigger netgames, don't show anyone as WANTED when they're equally matched. - numwanted = 0; - else - numwanted = min(4, 1 + ((numingame-2) / 4)); - - for (i = 0; i < 4; i++) - { - if (i+1 > numwanted) // Not enough players for this slot to be wanted! - battlewanted[i] = -1; - else if (bestbumperplayer != -1 && !setbumper) // If there's a player who has an untied bumper lead over everyone else, they are the first to be wanted. - { - battlewanted[i] = bestbumperplayer; - setbumper = true; // Don't set twice - } - else - { - // Don't accidentally set the same player, if the bestbumperplayer is also a huge camper. - while (bestbumperplayer != -1 && camppos[nextcamppos] != -1 - && bestbumperplayer == camppos[nextcamppos]) - nextcamppos++; - - // Do not add *any* more people if there's too many times that are tied with others. - // This could theoretically happen very easily if people don't hit each other for a while after the start of a match. - // (I will be sincerely impressed if more than 2 people tie after people start hitting each other though) - - if (camppos[nextcamppos] == -1 // Out of entries - || ties >= (numwanted-i)) // Already counted ties - { - battlewanted[i] = -1; - continue; - } - - if (ties < (numwanted-i)) - { - ties = 0; // Reset - for (j = 0; j < 2; j++) - { - if (camppos[nextcamppos+(j+1)] == -1) // Nothing beyond, cancel - break; - if (players[camppos[nextcamppos]].kartstuff[k_wanted] == players[camppos[nextcamppos+(j+1)]].kartstuff[k_wanted]) - ties++; - } - } - - if (ties < (numwanted-i)) // Is it still low enough after counting? - { - battlewanted[i] = camppos[nextcamppos]; - nextcamppos++; - } - else - battlewanted[i] = -1; - } - } -} - -void K_CheckBumpers(void) -{ - UINT8 i; - UINT8 numingame = 0; - SINT8 winnernum = -1; - INT32 winnerscoreadd = 0; - - if (!multiplayer) - return; - - if (!G_BattleGametype()) - return; - - if (gameaction == ga_completed) - return; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) // not even in-game - continue; - - if (players[i].exiting) // we're already exiting! stop! - return; - - numingame++; - winnerscoreadd += players[i].marescore; - - if (players[i].kartstuff[k_bumper] <= 0) // if you don't have any bumpers, you're probably not a winner - continue; - else if (winnernum > -1) // TWO winners? that's dumb :V - return; - - winnernum = i; - winnerscoreadd -= players[i].marescore; - } - - if (numingame <= 1) - return; - - if (winnernum > -1 && playeringame[winnernum]) - { - players[winnernum].marescore += winnerscoreadd; - CONS_Printf(M_GetText("%s recieved %d point%s for winning!\n"), player_names[winnernum], winnerscoreadd, (winnerscoreadd == 1 ? "" : "s")); - } - - for (i = 0; i < MAXPLAYERS; i++) // This can't go in the earlier loop because winning adds points - K_KartUpdatePosition(&players[i]); - - for (i = 0; i < MAXPLAYERS; i++) // and it can't be merged with this loop because it needs to be all updated before exiting... multi-loops suck... - P_DoPlayerExit(&players[i]); -} - -void K_CheckSpectateStatus(void) -{ - UINT8 respawnlist[MAXPLAYERS]; - UINT8 i, j, numingame = 0, numjoiners = 0; - - // Maintain spectate wait timer - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - continue; - if (players[i].spectator && (players[i].pflags & PF_WANTSTOJOIN)) - players[i].kartstuff[k_spectatewait]++; - else - players[i].kartstuff[k_spectatewait] = 0; - } - - // No one's allowed to join - if (!cv_allowteamchange.value) - return; - - // Get the number of players in game, and the players to be de-spectated. - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - continue; - - if (!players[i].spectator) - { - numingame++; - if (cv_ingamecap.value && numingame >= cv_ingamecap.value) // DON'T allow if you've hit the in-game player cap - return; - if (gamestate != GS_LEVEL) // Allow if you're not in a level - continue; - if (players[i].exiting) // DON'T allow if anyone's exiting - return; - if (numingame < 2 || leveltime < starttime || mapreset) // Allow if the match hasn't started yet - continue; - if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in - return; - if (G_RaceGametype() && players[i].laps >= 2) // DON'T allow if the race is at 2 laps - return; - continue; - } - else if (!(players[i].pflags & PF_WANTSTOJOIN)) - continue; - - respawnlist[numjoiners++] = i; - } - - // literally zero point in going any further if nobody is joining - if (!numjoiners) - return; - - // Organize by spectate wait timer - if (cv_ingamecap.value) - { - UINT8 oldrespawnlist[MAXPLAYERS]; - memcpy(oldrespawnlist, respawnlist, numjoiners); - for (i = 0; i < numjoiners; i++) - { - UINT8 pos = 0; - INT32 ispecwait = players[oldrespawnlist[i]].kartstuff[k_spectatewait]; - - for (j = 0; j < numjoiners; j++) - { - INT32 jspecwait = players[oldrespawnlist[j]].kartstuff[k_spectatewait]; - if (j == i) - continue; - if (jspecwait > ispecwait) - pos++; - else if (jspecwait == ispecwait && j < i) - pos++; - } - - respawnlist[pos] = oldrespawnlist[i]; - } - } - - // Finally, we can de-spectate everyone! - for (i = 0; i < numjoiners; i++) - { - if (cv_ingamecap.value && numingame+i >= cv_ingamecap.value) // Hit the in-game player cap while adding people? - break; - P_SpectatorJoinGame(&players[respawnlist[i]]); - } - - // Reset the match if you're in an empty server - if (!mapreset && gamestate == GS_LEVEL && leveltime >= starttime && (numingame < 2 && numingame+i >= 2)) // use previous i value - { - S_ChangeMusicInternal("chalng", false); // COME ON - mapreset = 3*TICRATE; // Even though only the server uses this for game logic, set for everyone for HUD - } -} - -//} - -//{ SRB2kart HUD Code - -#define NUMPOSNUMS 10 -#define NUMPOSFRAMES 7 // White, three blues, three reds -#define NUMWINFRAMES 6 // Red, yellow, green, cyan, blue, purple - -//{ Patch Definitions -static patch_t *kp_nodraw; - -static patch_t *kp_timesticker; -static patch_t *kp_timestickerwide; -static patch_t *kp_lapsticker; -static patch_t *kp_lapstickerwide; -static patch_t *kp_lapstickernarrow; -static patch_t *kp_splitlapflag; -static patch_t *kp_bumpersticker; -static patch_t *kp_bumperstickerwide; -static patch_t *kp_karmasticker; -static patch_t *kp_splitkarmabomb; -static patch_t *kp_timeoutsticker; - -static patch_t *kp_startcountdown[16]; -static patch_t *kp_racefinish[6]; - -static patch_t *kp_positionnum[NUMPOSNUMS][NUMPOSFRAMES]; -static patch_t *kp_winnernum[NUMPOSFRAMES]; - -static patch_t *kp_facenum[MAXPLAYERS+1]; -static patch_t *kp_facehighlight[8]; - -static patch_t *kp_ringsticker[2]; -static patch_t *kp_ringstickersplit[4]; -static patch_t *kp_ring[6]; -static patch_t *kp_smallring[6]; -static patch_t *kp_ringdebtminus; -static patch_t *kp_ringdebtminussmall; -static patch_t *kp_ringspblock[16]; -static patch_t *kp_ringspblocksmall[16]; - -static patch_t *kp_speedometersticker; -static patch_t *kp_speedometerlabel[4]; - -static patch_t *kp_rankbumper; -static patch_t *kp_tinybumper[2]; -static patch_t *kp_ranknobumpers; - -static patch_t *kp_battlewin; -static patch_t *kp_battlecool; -static patch_t *kp_battlelose; -static patch_t *kp_battlewait; -static patch_t *kp_battleinfo; -static patch_t *kp_wanted; -static patch_t *kp_wantedsplit; -static patch_t *kp_wantedreticle; - -static patch_t *kp_itembg[4]; -static patch_t *kp_itemtimer[2]; -static patch_t *kp_itemmulsticker[2]; -static patch_t *kp_itemx; - -static patch_t *kp_superring[2]; -static patch_t *kp_sneaker[2]; -static patch_t *kp_rocketsneaker[2]; -static patch_t *kp_invincibility[13]; -static patch_t *kp_banana[2]; -static patch_t *kp_eggman[2]; -static patch_t *kp_orbinaut[5]; -static patch_t *kp_jawz[2]; -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_sadface[2]; - -static patch_t *kp_check[6]; - -static patch_t *kp_eggnum[4]; - -static patch_t *kp_fpview[3]; -static patch_t *kp_inputwheel[5]; - -static patch_t *kp_challenger[25]; - -static patch_t *kp_lapanim_lap[7]; -static patch_t *kp_lapanim_final[11]; -static patch_t *kp_lapanim_number[10][3]; -static patch_t *kp_lapanim_emblem[2]; -static patch_t *kp_lapanim_hand[3]; - + player->kartstuff[k_killfield]--; + + if (P_IsObjectOnGround(player->mo)) + player->kartstuff[k_waterskip] = 0; + + if (player->kartstuff[k_instashield]) + player->kartstuff[k_instashield]--; + + if (player->kartstuff[k_eggmanexplode]) + { + if (player->spectator || (G_BattleGametype() && !player->kartstuff[k_bumper])) + player->kartstuff[k_eggmanexplode] = 0; + else + { + player->kartstuff[k_eggmanexplode]--; + if (player->kartstuff[k_eggmanexplode] <= 0) + { + mobj_t *eggsexplode; + //player->powers[pw_flashing] = 0; + eggsexplode = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_SPBEXPLOSION); + if (player->kartstuff[k_eggmanblame] >= 0 + && player->kartstuff[k_eggmanblame] < MAXPLAYERS + && playeringame[player->kartstuff[k_eggmanblame]] + && !players[player->kartstuff[k_eggmanblame]].spectator + && players[player->kartstuff[k_eggmanblame]].mo) + P_SetTarget(&eggsexplode->target, players[player->kartstuff[k_eggmanblame]].mo); + } + } + } + + if (player->kartstuff[k_comebacktimer]) + player->kartstuff[k_comebackmode] = 0; + + if (P_IsObjectOnGround(player->mo) && player->kartstuff[k_pogospring]) + { + if (P_MobjFlip(player->mo)*player->mo->momz <= 0) + player->kartstuff[k_pogospring] = 0; + } + + if (cmd->buttons & BT_DRIFT) + player->kartstuff[k_jmp] = 1; + else + player->kartstuff[k_jmp] = 0; + + // Respawn Checker + if (player->kartstuff[k_respawn]) + K_RespawnChecker(player); + + // Roulette Code + K_KartItemRoulette(player, cmd); + + // Handle invincibility sfx + K_UpdateInvincibilitySounds(player); // Also thanks, VAda! + + // Plays the music after the starting countdown. + if (P_IsLocalPlayer(player) && leveltime == (starttime + (TICRATE/2))) + { + S_ChangeMusic(mapmusname, mapmusflags, true); + S_ShowMusicCredit(); + } +} + +void K_KartPlayerAfterThink(player_t *player) +{ + // Moved to afterthink, as at this point the players have had their distances to the finish line updated + // and this will correctly account for all players + K_KartUpdatePosition(player); + + if (player->kartstuff[k_curshield] + || player->kartstuff[k_invincibilitytimer] + || (player->kartstuff[k_growshrinktimer] != 0 && player->kartstuff[k_growshrinktimer] % 5 == 4)) // 4 instead of 0 because this is afterthink! + { + player->mo->frame |= FF_FULLBRIGHT; + } + else + { + if (!(player->mo->state->frame & FF_FULLBRIGHT)) + player->mo->frame &= ~FF_FULLBRIGHT; + } + + // Move held objects (Bananas, Orbinaut, etc) + K_MoveHeldObjects(player); + + // Jawz reticule (seeking) + if (player->kartstuff[k_itemtype] == KITEM_JAWZ && player->kartstuff[k_itemheld]) + { + INT32 lasttarg = player->kartstuff[k_lastjawztarget]; + player_t *targ; + mobj_t *ret; + + if (player->kartstuff[k_jawztargetdelay] && playeringame[lasttarg] && !players[lasttarg].spectator) + { + targ = &players[lasttarg]; + player->kartstuff[k_jawztargetdelay]--; + } + else + targ = K_FindJawzTarget(player->mo, player); + + if (!targ || !targ->mo || P_MobjWasRemoved(targ->mo)) + { + player->kartstuff[k_lastjawztarget] = -1; + player->kartstuff[k_jawztargetdelay] = 0; + return; + } + + ret = P_SpawnMobj(targ->mo->x, targ->mo->y, targ->mo->z, MT_PLAYERRETICULE); + P_SetTarget(&ret->target, targ->mo); + ret->frame |= ((leveltime % 10) / 2); + ret->tics = 1; + ret->color = player->skincolor; + + if (targ-players != lasttarg) + { + if (P_IsDisplayPlayer(player) || P_IsDisplayPlayer(targ)) + S_StartSound(NULL, sfx_s3k89); + else + S_StartSound(targ->mo, sfx_s3k89); + + player->kartstuff[k_lastjawztarget] = targ-players; + player->kartstuff[k_jawztargetdelay] = 5; + } + } + else + { + player->kartstuff[k_lastjawztarget] = -1; + player->kartstuff[k_jawztargetdelay] = 0; + } +} + +/*-------------------------------------------------- + static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) + + Gets the next waypoint of a player, by finding their closest waypoint, then checking which of itself and next or + previous waypoints are infront of the player. + + Input Arguments:- + player - The player the next waypoint is being found for + + Return:- + The waypoint that is the player's next waypoint +--------------------------------------------------*/ +static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) +{ + waypoint_t *bestwaypoint = NULL; + + if ((player != NULL) && (player->mo != NULL) && (P_MobjWasRemoved(player->mo) == false)) + { + waypoint_t *waypoint = K_GetBestWaypointForMobj(player->mo); + boolean updaterespawn = false; + + bestwaypoint = waypoint; + + // check the waypoint's location in relation to the player + // If it's generally in front, it's fine, otherwise, use the best next/previous waypoint. + // EXCEPTION: If our best waypoint is the finishline AND we're facing towards it, don't do this. + // Otherwise it breaks the distance calculations. + if (waypoint != NULL) + { + boolean finishlinehack = false; + angle_t playerangle = player->mo->angle; + angle_t momangle = player->mo->angle; + angle_t angletowaypoint = + R_PointToAngle2(player->mo->x, player->mo->y, waypoint->mobj->x, waypoint->mobj->y); + angle_t angledelta = ANGLE_MAX; + angle_t momdelta = ANGLE_MAX; + + if (player->mo->momx != 0 || player->mo->momy != 0) + { + // Defaults to facing angle if you're not moving. + momangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); + } + + angledelta = playerangle - angletowaypoint; + if (angledelta > ANGLE_180) + { + angledelta = InvAngle(angledelta); + } + + momdelta = momangle - angletowaypoint; + if (momdelta > ANGLE_180) + { + momdelta = InvAngle(momdelta); + } + + if (bestwaypoint == K_GetFinishLineWaypoint()) + { + // facing towards the finishline + if (angledelta <= ANGLE_90) + { + finishlinehack = true; + } + } + + // We're using a lot of angle calculations here, because only using facing angle or only using momentum angle both have downsides. + // nextwaypoints will be picked if you're facing OR moving forward. + // prevwaypoints will be picked if you're facing AND moving backward. + if ((angledelta > ANGLE_45 || momdelta > ANGLE_45) + && (finishlinehack == false)) + { + angle_t nextbestdelta = angledelta; + angle_t nextbestmomdelta = momdelta; + size_t i = 0U; + + if ((waypoint->nextwaypoints != NULL) && (waypoint->numnextwaypoints > 0U)) + { + for (i = 0U; i < waypoint->numnextwaypoints; i++) + { + angletowaypoint = R_PointToAngle2( + player->mo->x, player->mo->y, + waypoint->nextwaypoints[i]->mobj->x, waypoint->nextwaypoints[i]->mobj->y); + + angledelta = playerangle - angletowaypoint; + if (angledelta > ANGLE_180) + { + angledelta = InvAngle(angledelta); + } + + momdelta = momangle - angletowaypoint; + if (momdelta > ANGLE_180) + { + momdelta = InvAngle(momdelta); + } + + if (angledelta < nextbestdelta || momdelta < nextbestmomdelta) + { + bestwaypoint = waypoint->nextwaypoints[i]; + + nextbestdelta = angledelta; + nextbestmomdelta = momdelta; + + // Remove wrong way flag if we're using nextwaypoints + player->kartstuff[k_wrongway] = 0; + updaterespawn = true; + } + } + } + + if ((waypoint->prevwaypoints != NULL) && (waypoint->numprevwaypoints > 0U)) + { + for (i = 0U; i < waypoint->numprevwaypoints; i++) + { + angletowaypoint = R_PointToAngle2( + player->mo->x, player->mo->y, + waypoint->prevwaypoints[i]->mobj->x, waypoint->prevwaypoints[i]->mobj->y); + + angledelta = playerangle - angletowaypoint; + if (angledelta > ANGLE_180) + { + angledelta = InvAngle(angledelta); + } + + momdelta = momangle - angletowaypoint; + if (momdelta > ANGLE_180) + { + momdelta = InvAngle(momdelta); + } + + if (angledelta < nextbestdelta && momdelta < nextbestmomdelta) + { + bestwaypoint = waypoint->prevwaypoints[i]; + + nextbestdelta = angledelta; + nextbestmomdelta = momdelta; + + // Ser wrong way flag if we're using prevwaypoints + player->kartstuff[k_wrongway] = 1; + updaterespawn = false; + } + } + } + } + } + + if (!P_IsObjectOnGround(player->mo)) + { + updaterespawn = false; + } + + // Respawn point should only be updated when we're going to a nextwaypoint + if ((updaterespawn) && + (bestwaypoint != NULL) && + (bestwaypoint != player->nextwaypoint) && + (player->kartstuff[k_respawn] == 0) && + (K_GetWaypointIsShortcut(bestwaypoint) == false) && (K_GetWaypointIsEnabled(bestwaypoint) == true)) + { + size_t i = 0U; + waypoint_t *aimwaypoint = NULL; + + player->starpostx = bestwaypoint->mobj->x >> FRACBITS; + player->starposty = bestwaypoint->mobj->y >> FRACBITS; + player->starpostz = bestwaypoint->mobj->z >> FRACBITS; + player->kartstuff[k_starpostflip] = (bestwaypoint->mobj->flags2 & MF2_OBJECTFLIP); + + // starpostangle is to the first valid nextwaypoint for simplicity + // if we reach the last waypoint and it's still not valid, just use it anyway. Someone needs to fix + // their map! + for (i = 0U; i < bestwaypoint->numnextwaypoints; i++) + { + aimwaypoint = bestwaypoint->nextwaypoints[i]; + + if ((i == bestwaypoint->numnextwaypoints - 1U) + || ((K_GetWaypointIsEnabled(aimwaypoint) == true) + && (K_GetWaypointIsSpawnpoint(aimwaypoint) == true))) + { + player->starpostangle = R_PointToAngle2( + bestwaypoint->mobj->x, bestwaypoint->mobj->y, aimwaypoint->mobj->x, aimwaypoint->mobj->y); + break; + } + } + } + } + + return bestwaypoint; +} + +static boolean K_PlayerCloserToNextWaypoints(waypoint_t *const waypoint, player_t *const player) +{ + boolean nextiscloser = true; + + if ((waypoint != NULL) && (player != NULL) && (player->mo != NULL)) + { + size_t i = 0U; + waypoint_t *currentwpcheck = NULL; + angle_t angletoplayer = ANGLE_MAX; + angle_t currentanglecheck = ANGLE_MAX; + angle_t bestangle = ANGLE_MAX; + + angletoplayer = R_PointToAngle2(waypoint->mobj->x, waypoint->mobj->y, + player->mo->x, player->mo->y); + + for (i = 0U; i < waypoint->numnextwaypoints; i++) + { + currentwpcheck = waypoint->nextwaypoints[i]; + currentanglecheck = R_PointToAngle2( + waypoint->mobj->x, waypoint->mobj->y, currentwpcheck->mobj->x, currentwpcheck->mobj->y); + + // Get delta angle + currentanglecheck = currentanglecheck - angletoplayer; + + if (currentanglecheck > ANGLE_180) + { + currentanglecheck = InvAngle(currentanglecheck); + } + + if (currentanglecheck < bestangle) + { + bestangle = currentanglecheck; + } + } + + for (i = 0U; i < waypoint->numprevwaypoints; i++) + { + currentwpcheck = waypoint->prevwaypoints[i]; + currentanglecheck = R_PointToAngle2( + waypoint->mobj->x, waypoint->mobj->y, currentwpcheck->mobj->x, currentwpcheck->mobj->y); + + // Get delta angle + currentanglecheck = currentanglecheck - angletoplayer; + + if (currentanglecheck > ANGLE_180) + { + currentanglecheck = InvAngle(currentanglecheck); + } + + if (currentanglecheck < bestangle) + { + bestangle = currentanglecheck; + nextiscloser = false; + break; + } + } + } + + return nextiscloser; +} + +/*-------------------------------------------------- + static void K_UpdateDistanceFromFinishLine(player_t *const player) + + Updates the distance a player has to the finish line. + + Input Arguments:- + player - The player the distance is being updated for + + Return:- + None +--------------------------------------------------*/ +static void K_UpdateDistanceFromFinishLine(player_t *const player) +{ + if ((player != NULL) && (player->mo != NULL)) + { + if (player->exiting) + { + player->nextwaypoint = K_GetFinishLineWaypoint(); + player->distancetofinish = 0U; + } + else + { + waypoint_t *finishline = K_GetFinishLineWaypoint(); + waypoint_t *nextwaypoint = K_GetPlayerNextWaypoint(player); + + if (nextwaypoint != NULL) + { + // If nextwaypoint is NULL, it means we don't want to update the waypoint until we touch another one. + // player->nextwaypoint will keep its previous value in this case. + player->nextwaypoint = nextwaypoint; + } + + // nextwaypoint is now the waypoint that is in front of us + if ((player->nextwaypoint != NULL) && (finishline != NULL)) + { + const boolean useshortcuts = false; + const boolean huntbackwards = false; + boolean pathfindsuccess = false; + path_t pathtofinish = {}; + + pathfindsuccess = + K_PathfindToWaypoint(player->nextwaypoint, finishline, &pathtofinish, useshortcuts, huntbackwards); + + // Update the player's distance to the finish line if a path was found. + // Using shortcuts won't find a path, so distance won't be updated until the player gets back on track + if (pathfindsuccess == true) + { + // Add euclidean distance to the next waypoint to the distancetofinish + UINT32 adddist; + fixed_t disttowaypoint = + P_AproxDistance( + (player->mo->x >> FRACBITS) - (player->nextwaypoint->mobj->x >> FRACBITS), + (player->mo->y >> FRACBITS) - (player->nextwaypoint->mobj->y >> FRACBITS)); + disttowaypoint = P_AproxDistance(disttowaypoint, (player->mo->z >> FRACBITS) - (player->nextwaypoint->mobj->z >> FRACBITS)); + + adddist = (UINT32)disttowaypoint; + + player->distancetofinish = pathtofinish.totaldist + adddist; + Z_Free(pathtofinish.array); + + // distancetofinish is currently a flat distance to the finish line, but in order to be fully + // correct we need to add to it the length of the entire circuit multiplied by the number of laps + // left after this one. This will give us the total distance to the finish line, and allow item + // distance calculation to work easily + if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == 0U) + { + const UINT8 numfulllapsleft = ((UINT8)cv_numlaps.value - player->laps); + + player->distancetofinish += numfulllapsleft * K_GetCircuitLength(); + + // An additional HACK, to fix looking backwards towards the finish line + // If the player's next waypoint is the finishline and the angle distance from player to + // connectin waypoints implies they're closer to a next waypoint, add a full track distance + if (player->nextwaypoint == finishline) + { + if (K_PlayerCloserToNextWaypoints(player->nextwaypoint, player) == true) + { + player->distancetofinish += K_GetCircuitLength(); + } + } + } + } + } + } + } +} + +// Returns false if this player being placed here causes them to collide with any other player +// Used in g_game.c for match etc. respawning +// This does not check along the z because the z is not correctly set for the spawnee at this point +boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y) +{ + INT32 i; + fixed_t p1radius = players[playernum].mo->radius; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playernum == i || !playeringame[i] || players[i].spectator || !players[i].mo || players[i].mo->health <= 0 + || players[i].playerstate != PST_LIVE || (players[i].mo->flags & MF_NOCLIP) || (players[i].mo->flags & MF_NOCLIPTHING)) + continue; + + if (abs(x - players[i].mo->x) < (p1radius + players[i].mo->radius) + && abs(y - players[i].mo->y) < (p1radius + players[i].mo->radius)) + { + return false; + } + } + return true; +} + +// countersteer is how strong the controls are telling us we are turning +// turndir is the direction the controls are telling us to turn, -1 if turning right and 1 if turning left +static INT16 K_GetKartDriftValue(player_t *player, fixed_t countersteer) +{ + INT16 basedrift, driftangle; + fixed_t driftweight = player->kartweight*14; // 12 + + // If they aren't drifting or on the ground this doesn't apply + if (player->kartstuff[k_drift] == 0 || !P_IsObjectOnGround(player->mo)) + return 0; + + if (player->kartstuff[k_driftend] != 0) + return -266*player->kartstuff[k_drift]; // Drift has ended and we are tweaking their angle back a bit + + //basedrift = 90*player->kartstuff[k_drift]; // 450 + //basedrift = 93*player->kartstuff[k_drift] - driftweight*3*player->kartstuff[k_drift]/10; // 447 - 303 + basedrift = 83*player->kartstuff[k_drift] - (driftweight - 14)*player->kartstuff[k_drift]/5; // 415 - 303 + driftangle = abs((252 - driftweight)*player->kartstuff[k_drift]/5); + + if (player->kartstuff[k_tiregrease] > 0) // Buff drift-steering while in greasemode + basedrift += (basedrift / greasetics) * player->kartstuff[k_tiregrease]; + + return basedrift + FixedMul(driftangle, countersteer); +} + +INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) +{ + fixed_t p_maxspeed = FixedMul(K_GetKartSpeed(player, false), 3*FRACUNIT); + fixed_t adjustangle = FixedDiv((p_maxspeed>>16) - (player->speed>>16), (p_maxspeed>>16) + player->kartweight); + + if (player->spectator) + return turnvalue; + + if (player->kartstuff[k_drift] != 0 && P_IsObjectOnGround(player->mo)) + { + // If we're drifting we have a completely different turning value + if (player->kartstuff[k_driftend] == 0) + { + // 800 is the max set in g_game.c with angleturn + fixed_t countersteer = FixedDiv(turnvalue*FRACUNIT, 800*FRACUNIT); + turnvalue = K_GetKartDriftValue(player, countersteer); + } + else + turnvalue = (INT16)(turnvalue + K_GetKartDriftValue(player, FRACUNIT)); + + return turnvalue; + } + + turnvalue = FixedMul(turnvalue, adjustangle); // Weight has a small effect on turning + + if (EITHERSNEAKER(player) || player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_growshrinktimer] > 0) + turnvalue = FixedMul(turnvalue, FixedDiv(5*FRACUNIT, 4*FRACUNIT)); + + return turnvalue; +} + +INT32 K_GetKartDriftSparkValue(player_t *player) +{ + UINT8 kartspeed = (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) + ? 1 + : player->kartspeed; + return (26*4 + kartspeed*2 + (9 - player->kartweight))*8; +} + +static void K_KartDrift(player_t *player, boolean onground) +{ + fixed_t minspeed = (10 * player->mo->scale); + INT32 dsone = K_GetKartDriftSparkValue(player); + INT32 dstwo = dsone*2; + INT32 dsthree = dstwo*2; + + // Drifting is actually straffing + automatic turning. + // Holding the Jump button will enable drifting. + + // Drift Release (Moved here so you can't "chain" drifts) + if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5) + // || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1)) + && player->kartstuff[k_driftcharge] < dsone + && onground) + { + player->kartstuff[k_driftcharge] = 0; + } + else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5) + // || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1)) + && (player->kartstuff[k_driftcharge] >= dsone && player->kartstuff[k_driftcharge] < dstwo) + && onground) + { + if (player->kartstuff[k_driftboost] < 20) + player->kartstuff[k_driftboost] = 20; + S_StartSound(player->mo, sfx_s23c); + //K_SpawnDashDustRelease(player); + player->kartstuff[k_driftcharge] = 0; + } + else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5) + // || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1)) + && player->kartstuff[k_driftcharge] < dsthree + && onground) + { + if (player->kartstuff[k_driftboost] < 50) + player->kartstuff[k_driftboost] = 50; + S_StartSound(player->mo, sfx_s23c); + //K_SpawnDashDustRelease(player); + player->kartstuff[k_driftcharge] = 0; + } + else if ((player->kartstuff[k_drift] != -5 && player->kartstuff[k_drift] != 5) + // || (player->kartstuff[k_drift] >= 1 && player->kartstuff[k_turndir] != 1) || (player->kartstuff[k_drift] <= -1 && player->kartstuff[k_turndir] != -1)) + && player->kartstuff[k_driftcharge] >= dsthree + && onground) + { + if (player->kartstuff[k_driftboost] < 125) + player->kartstuff[k_driftboost] = 125; + S_StartSound(player->mo, sfx_s23c); + //K_SpawnDashDustRelease(player); + player->kartstuff[k_driftcharge] = 0; + } + + // Drifting: left or right? + if ((player->cmd.driftturn > 0) && player->speed > minspeed && player->kartstuff[k_jmp] == 1 + && (player->kartstuff[k_drift] == 0 || player->kartstuff[k_driftend] == 1)) // && player->kartstuff[k_drift] != 1) + { + // Starting left drift + player->kartstuff[k_drift] = 1; + player->kartstuff[k_driftend] = player->kartstuff[k_driftcharge] = 0; + } + else if ((player->cmd.driftturn < 0) && player->speed > minspeed && player->kartstuff[k_jmp] == 1 + && (player->kartstuff[k_drift] == 0 || player->kartstuff[k_driftend] == 1)) // && player->kartstuff[k_drift] != -1) + { + // Starting right drift + player->kartstuff[k_drift] = -1; + player->kartstuff[k_driftend] = player->kartstuff[k_driftcharge] = 0; + } + else if (player->kartstuff[k_jmp] == 0) // || player->kartstuff[k_turndir] == 0) + { + // drift is not being performed so if we're just finishing set driftend and decrement counters + if (player->kartstuff[k_drift] > 0) + { + player->kartstuff[k_drift]--; + player->kartstuff[k_driftend] = 1; + } + else if (player->kartstuff[k_drift] < 0) + { + player->kartstuff[k_drift]++; + player->kartstuff[k_driftend] = 1; + } + else + player->kartstuff[k_driftend] = 0; + } + + + // Incease/decrease the drift value to continue drifting in that direction + if (player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_jmp] == 1 && onground && player->kartstuff[k_drift] != 0) + { + fixed_t driftadditive = 24; + + if (player->kartstuff[k_drift] >= 1) // Drifting to the left + { + player->kartstuff[k_drift]++; + if (player->kartstuff[k_drift] > 5) + player->kartstuff[k_drift] = 5; + + if (player->cmd.driftturn > 0) // Inward + driftadditive += abs(player->cmd.driftturn)/100; + if (player->cmd.driftturn < 0) // Outward + driftadditive -= abs(player->cmd.driftturn)/75; + } + else if (player->kartstuff[k_drift] <= -1) // Drifting to the right + { + player->kartstuff[k_drift]--; + if (player->kartstuff[k_drift] < -5) + player->kartstuff[k_drift] = -5; + + if (player->cmd.driftturn < 0) // Inward + driftadditive += abs(player->cmd.driftturn)/100; + if (player->cmd.driftturn > 0) // Outward + driftadditive -= abs(player->cmd.driftturn)/75; + } + + // Disable drift-sparks until you're going fast enough + if (player->kartstuff[k_getsparks] == 0 + || (player->kartstuff[k_offroad] + && !player->kartstuff[k_invincibilitytimer] + && !player->kartstuff[k_hyudorotimer] + && !EITHERSNEAKER(player))) + driftadditive = 0; + if (player->speed > minspeed*2) + player->kartstuff[k_getsparks] = 1; + + // This spawns the drift sparks + if (player->kartstuff[k_driftcharge] + driftadditive >= dsone) + K_SpawnDriftSparks(player); + + // Sound whenever you get a different tier of sparks + if (P_IsDisplayPlayer(player) // UGHGHGH... + && ((player->kartstuff[k_driftcharge] < dsone && player->kartstuff[k_driftcharge]+driftadditive >= dsone) + || (player->kartstuff[k_driftcharge] < dstwo && player->kartstuff[k_driftcharge]+driftadditive >= dstwo) + || (player->kartstuff[k_driftcharge] < dsthree && player->kartstuff[k_driftcharge]+driftadditive >= dsthree))) + { + //S_StartSound(player->mo, sfx_s3ka2); + S_StartSoundAtVolume(player->mo, sfx_s3ka2, 192); // Ugh... + } + + player->kartstuff[k_driftcharge] += driftadditive; + player->kartstuff[k_driftend] = 0; + } + + // Stop drifting + if (player->kartstuff[k_spinouttimer] > 0 || player->speed < minspeed) + { + player->kartstuff[k_drift] = player->kartstuff[k_driftcharge] = 0; + player->kartstuff[k_aizdriftstrat] = player->kartstuff[k_brakedrift] = 0; + player->kartstuff[k_getsparks] = 0; + } + + if ((!EITHERSNEAKER(player)) + || (!player->cmd.driftturn) + || (!player->kartstuff[k_aizdriftstrat]) + || (player->cmd.driftturn > 0) != (player->kartstuff[k_aizdriftstrat] > 0)) + { + if (!player->kartstuff[k_drift]) + player->kartstuff[k_aizdriftstrat] = 0; + else + player->kartstuff[k_aizdriftstrat] = ((player->kartstuff[k_drift] > 0) ? 1 : -1); + } + else if (player->kartstuff[k_aizdriftstrat] && !player->kartstuff[k_drift]) + K_SpawnAIZDust(player); + + if (player->kartstuff[k_drift] + && ((player->cmd.buttons & BT_BRAKE) + || !(player->cmd.buttons & BT_ACCELERATE)) + && P_IsObjectOnGround(player->mo)) + { + if (!player->kartstuff[k_brakedrift]) + K_SpawnBrakeDriftSparks(player); + player->kartstuff[k_brakedrift] = 1; + } + else + player->kartstuff[k_brakedrift] = 0; +} +// +// K_KartUpdatePosition +// +void K_KartUpdatePosition(player_t *player) +{ + fixed_t position = 1; + fixed_t oldposition = player->kartstuff[k_position]; + fixed_t i; + + if (player->spectator || !player->mo) + { + // Ensure these are reset for spectators + player->kartstuff[k_position] = 0; + player->kartstuff[k_positiondelay] = 0; + return; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || !players[i].mo) + continue; + + if (G_RaceGametype()) + { + if (player->exiting) // End of match standings + { + // Only time matters + if (players[i].realtime < player->realtime) + position++; + } + else + { + // I'm a lap behind this player OR + // My distance to the finish line is higher, so I'm behind + if ((players[i].laps > player->laps) + || (players[i].distancetofinish < player->distancetofinish)) + { + position++; + } + } + } + else if (G_BattleGametype()) + { + if (player->exiting) // End of match standings + { + // Only score matters + if (players[i].marescore > player->marescore) + position++; + } + else + { + // I have less points than but the same bumpers as this player OR + // I have less bumpers than this player + if ((players[i].kartstuff[k_bumper] == player->kartstuff[k_bumper] && players[i].marescore > player->marescore) + || (players[i].kartstuff[k_bumper] > player->kartstuff[k_bumper])) + position++; + } + } + } + + if (leveltime < starttime || oldposition == 0) + oldposition = position; + + if (oldposition != position) // Changed places? + player->kartstuff[k_positiondelay] = 10; // Position number growth + + player->kartstuff[k_position] = position; +} + +// +// K_StripItems +// +void K_StripItems(player_t *player) +{ + player->kartstuff[k_itemtype] = KITEM_NONE; + player->kartstuff[k_itemamount] = 0; + player->kartstuff[k_itemheld] = 0; + + player->kartstuff[k_rocketsneakertimer] = 0; + + if (!player->kartstuff[k_itemroulette] || player->kartstuff[k_roulettetype] != 2) + { + player->kartstuff[k_itemroulette] = 0; + player->kartstuff[k_roulettetype] = 0; + } + player->kartstuff[k_eggmanheld] = 0; + + player->kartstuff[k_hyudorotimer] = 0; + player->kartstuff[k_stealingtimer] = 0; + player->kartstuff[k_stolentimer] = 0; + + player->kartstuff[k_curshield] = 0; + //player->kartstuff[k_thunderanim] = 0; + player->kartstuff[k_bananadrag] = 0; + + player->kartstuff[k_sadtimer] = 0; + + K_UpdateHnextList(player, true); +} + +void K_StripOther(player_t *player) +{ + player->kartstuff[k_itemroulette] = 0; + player->kartstuff[k_roulettetype] = 0; + + player->kartstuff[k_invincibilitytimer] = 0; + K_RemoveGrowShrink(player); + + if (player->kartstuff[k_eggmanexplode]) + { + player->kartstuff[k_eggmanexplode] = 0; + player->kartstuff[k_eggmanblame] = -1; + } +} + +// SRB2Kart: blockmap iterate for attraction shield users +static mobj_t *attractmo; +static fixed_t attractdist; +static inline boolean PIT_AttractingRings(mobj_t *thing) +{ + if (!attractmo || P_MobjWasRemoved(attractmo)) + return false; + + if (!attractmo->player) + return false; // not a player + + if (thing->health <= 0 || !thing) + return true; // dead + + if (thing->type != MT_RING && thing->type != MT_FLINGRING) + return true; // not a ring + + if (thing->extravalue1) + return true; // in special ring animation + + if (thing->cusval) + return true; // already attracted + + // see if it went over / under + if (attractmo->z - (attractdist>>2) > thing->z + thing->height) + return true; // overhead + if (attractmo->z + attractmo->height + (attractdist>>2) < thing->z) + return true; // underneath + + if (P_AproxDistance(attractmo->x - thing->x, attractmo->y - thing->y) < attractdist) + return true; // Too far away + + // set target + P_SetTarget(&thing->tracer, attractmo); + // flag to show it's been attracted once before + thing->cusval = 1; + return true; // find other rings +} + +/** Looks for rings near a player in the blockmap. + * + * \param pmo Player object looking for rings to attract + * \sa A_AttractChase + */ +static void K_LookForRings(mobj_t *pmo) +{ + INT32 bx, by, xl, xh, yl, yh; + attractdist = FixedMul(RING_DIST, pmo->scale)>>2; + + // Use blockmap to check for nearby rings + yh = (unsigned)(pmo->y + attractdist - bmaporgy)>>MAPBLOCKSHIFT; + yl = (unsigned)(pmo->y - attractdist - bmaporgy)>>MAPBLOCKSHIFT; + xh = (unsigned)(pmo->x + attractdist - bmaporgx)>>MAPBLOCKSHIFT; + xl = (unsigned)(pmo->x - attractdist - bmaporgx)>>MAPBLOCKSHIFT; + + attractmo = pmo; + + for (by = yl; by <= yh; by++) + for (bx = xl; bx <= xh; bx++) + P_BlockThingsIterator(bx, by, PIT_AttractingRings); +} + +// +// K_MoveKartPlayer +// +void K_MoveKartPlayer(player_t *player, boolean onground) +{ + ticcmd_t *cmd = &player->cmd; + boolean ATTACK_IS_DOWN = ((cmd->buttons & BT_ATTACK) && !(player->pflags & PF_ATTACKDOWN)); + boolean HOLDING_ITEM = (player->kartstuff[k_itemheld] || player->kartstuff[k_eggmanheld]); + boolean NO_HYUDORO = (player->kartstuff[k_stolentimer] == 0 && player->kartstuff[k_stealingtimer] == 0); + + K_UpdateDistanceFromFinishLine(player); + player->pflags &= ~PF_HITFINISHLINE; + + if (!player->exiting) + { + if (player->kartstuff[k_oldposition] < player->kartstuff[k_position]) // But first, if you lost a place, + { + player->kartstuff[k_oldposition] = player->kartstuff[k_position]; // then the other player taunts. + K_RegularVoiceTimers(player); // and you can't for a bit + } + else if (player->kartstuff[k_oldposition] > player->kartstuff[k_position]) // Otherwise, + { + K_PlayOvertakeSound(player->mo); // Say "YOU'RE TOO SLOW!" + player->kartstuff[k_oldposition] = player->kartstuff[k_position]; // Restore the old position, + } + } + + if (player->kartstuff[k_positiondelay]) + player->kartstuff[k_positiondelay]--; + + // Prevent ring misfire + if (!(cmd->buttons & BT_ATTACK)) + { + if (player->kartstuff[k_itemtype] == KITEM_NONE + && NO_HYUDORO && !(HOLDING_ITEM + || player->kartstuff[k_itemamount] + || player->kartstuff[k_itemroulette] + || player->kartstuff[k_growshrinktimer] > 0 + || player->kartstuff[k_rocketsneakertimer] + || player->kartstuff[k_eggmanexplode])) + player->kartstuff[k_userings] = 1; + else + player->kartstuff[k_userings] = 0; + } + + if ((player->pflags & PF_ATTACKDOWN) && !(cmd->buttons & BT_ATTACK)) + player->pflags &= ~PF_ATTACKDOWN; + else if (cmd->buttons & BT_ATTACK) + player->pflags |= PF_ATTACKDOWN; + + if (player && player->mo && player->mo->health > 0 && !player->spectator && !(player->exiting || mapreset) && leveltime > starttime + && player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_squishedtimer] == 0 && player->kartstuff[k_respawn] == 0) + { + // First, the really specific, finicky items that function without the item being directly in your item slot. + // Karma item dropping + if (player->kartstuff[k_comebackmode] && !player->kartstuff[k_comebacktimer]) + { + if (ATTACK_IS_DOWN) + { + mobj_t *newitem; + + if (player->kartstuff[k_comebackmode] == 1) + { + newitem = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RANDOMITEM); + newitem->threshold = 69; // selected "randomly". + } + else + { + newitem = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM); + if (player->kartstuff[k_eggmanblame] >= 0 + && player->kartstuff[k_eggmanblame] < MAXPLAYERS + && playeringame[player->kartstuff[k_eggmanblame]] + && !players[player->kartstuff[k_eggmanblame]].spectator + && players[player->kartstuff[k_eggmanblame]].mo) + P_SetTarget(&newitem->target, players[player->kartstuff[k_eggmanblame]].mo); + player->kartstuff[k_eggmanblame] = -1; + } + + newitem->flags2 = (player->mo->flags2 & MF2_OBJECTFLIP); + newitem->fuse = 15*TICRATE; // selected randomly. + + player->kartstuff[k_comebackmode] = 0; + player->kartstuff[k_comebacktimer] = comebacktime; + S_StartSound(player->mo, sfx_s254); + } + } + else + { + // Ring boosting + if (player->kartstuff[k_userings]) + { + if ((player->pflags & PF_ATTACKDOWN) && !player->kartstuff[k_ringdelay] && player->kartstuff[k_rings] > 0) + { + mobj_t *ring = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RING); + P_SetMobjState(ring, S_FASTRING1); + ring->extravalue1 = 1; // Ring use animation timer + ring->extravalue2 = 1; // Ring use animation flag + ring->shadowscale = 0; + P_SetTarget(&ring->target, player->mo); // user + player->kartstuff[k_rings]--; + player->kartstuff[k_ringdelay] = 3; + } + } + // Other items + else + { + // Eggman Monitor exploding + if (player->kartstuff[k_eggmanexplode]) + { + if (ATTACK_IS_DOWN && player->kartstuff[k_eggmanexplode] <= 3*TICRATE && player->kartstuff[k_eggmanexplode] > 1) + player->kartstuff[k_eggmanexplode] = 1; + } + // Eggman Monitor throwing + else if (player->kartstuff[k_eggmanheld]) + { + if (ATTACK_IS_DOWN) + { + K_ThrowKartItem(player, false, MT_EGGMANITEM, -1, 0); + K_PlayAttackTaunt(player->mo); + player->kartstuff[k_eggmanheld] = 0; + K_UpdateHnextList(player, true); + } + } + // Rocket Sneaker usage + else if (player->kartstuff[k_rocketsneakertimer] > 1) + { + if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO) + { + K_DoSneaker(player, 2); + K_PlayBoostTaunt(player->mo); + player->kartstuff[k_rocketsneakertimer] -= 2*TICRATE; + if (player->kartstuff[k_rocketsneakertimer] < 1) + player->kartstuff[k_rocketsneakertimer] = 1; + } + } + // Grow Canceling + else if (player->kartstuff[k_growshrinktimer] > 0) + { + if (player->kartstuff[k_growcancel] >= 0) + { + if (cmd->buttons & BT_ATTACK) + { + player->kartstuff[k_growcancel]++; + if (player->kartstuff[k_growcancel] > 26) + K_RemoveGrowShrink(player); + } + else + player->kartstuff[k_growcancel] = 0; + } + else + { + if ((cmd->buttons & BT_ATTACK) || (player->pflags & PF_ATTACKDOWN)) + player->kartstuff[k_growcancel] = -1; + else + player->kartstuff[k_growcancel] = 0; + } + } + else if (player->kartstuff[k_itemamount] <= 0) + { + player->kartstuff[k_itemamount] = player->kartstuff[k_itemheld] = 0; + } + else + { + switch (player->kartstuff[k_itemtype]) + { + case KITEM_SNEAKER: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO) + { + K_DoSneaker(player, 1); + K_PlayBoostTaunt(player->mo); + player->kartstuff[k_itemamount]--; + } + break; + case KITEM_ROCKETSNEAKER: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO + && player->kartstuff[k_rocketsneakertimer] == 0) + { + INT32 moloop; + mobj_t *mo = NULL; + mobj_t *prev = player->mo; + + K_PlayBoostTaunt(player->mo); + //player->kartstuff[k_itemheld] = 1; + S_StartSound(player->mo, sfx_s3k3a); + + //K_DoSneaker(player, 2); + + player->kartstuff[k_rocketsneakertimer] = (itemtime*3); + player->kartstuff[k_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; + } + } + break; + case KITEM_INVINCIBILITY: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) // Doesn't hold your item slot hostage normally, so you're free to waste it if you have multiple + { + if (!player->kartstuff[k_invincibilitytimer]) + { + mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_INVULNFLASH); + P_SetTarget(&overlay->target, player->mo); + overlay->destscale = player->mo->scale; + P_SetScale(overlay, player->mo->scale); + } + player->kartstuff[k_invincibilitytimer] = itemtime+(2*TICRATE); // 10 seconds + P_RestoreMusic(player); + if (!P_IsDisplayPlayer(player)) + S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmi : sfx_kinvnc)); + K_PlayPowerGloatSound(player->mo); + player->kartstuff[k_itemamount]--; + } + 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); + player->kartstuff[k_itemheld] = 1; + S_StartSound(player->mo, sfx_s254); + + for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++) + { + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_BANANA_SHIELD); + if (!mo) + { + player->kartstuff[k_itemamount] = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->threshold = 10; + mo->movecount = player->kartstuff[k_itemamount]; + mo->movedir = moloop+1; + P_SetTarget(&mo->target, player->mo); + P_SetTarget(&mo->hprev, prev); + P_SetTarget(&prev->hnext, mo); + prev = mo; + } + } + else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld]) // Banana x3 thrown + { + K_ThrowKartItem(player, false, MT_BANANA, -1, 0); + K_PlayAttackTaunt(player->mo); + player->kartstuff[k_itemamount]--; + K_UpdateHnextList(player, false); + } + break; + case KITEM_EGGMAN: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + player->kartstuff[k_itemamount]--; + player->kartstuff[k_eggmanheld] = 1; + S_StartSound(player->mo, sfx_s254); + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_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); + } + } + 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); + player->kartstuff[k_itemheld] = 1; + S_StartSound(player->mo, sfx_s3k3a); + + for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++) + { + newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->kartstuff[k_itemamount]) * moloop) << FRACBITS) + ANGLE_90; + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_ORBINAUT_SHIELD); + if (!mo) + { + player->kartstuff[k_itemamount] = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->angle = newangle; + mo->threshold = 10; + mo->movecount = player->kartstuff[k_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; + } + } + else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld]) // Orbinaut x3 thrown + { + K_ThrowKartItem(player, true, MT_ORBINAUT, 1, 0); + K_PlayAttackTaunt(player->mo); + player->kartstuff[k_itemamount]--; + K_UpdateHnextList(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); + player->kartstuff[k_itemheld] = 1; + S_StartSound(player->mo, sfx_s3k3a); + + for (moloop = 0; moloop < player->kartstuff[k_itemamount]; moloop++) + { + newangle = (player->mo->angle + ANGLE_157h) + FixedAngle(((360 / player->kartstuff[k_itemamount]) * moloop) << FRACBITS) + ANGLE_90; + mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_JAWZ_SHIELD); + if (!mo) + { + player->kartstuff[k_itemamount] = moloop; + break; + } + mo->flags |= MF_NOCLIPTHING; + mo->angle = newangle; + mo->threshold = 10; + mo->movecount = player->kartstuff[k_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; + } + } + else if (ATTACK_IS_DOWN && HOLDING_ITEM && player->kartstuff[k_itemheld]) // Jawz thrown + { + if (player->kartstuff[k_throwdir] == 1 || player->kartstuff[k_throwdir] == 0) + K_ThrowKartItem(player, true, MT_JAWZ, 1, 0); + else if (player->kartstuff[k_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); + player->kartstuff[k_itemamount]--; + K_UpdateHnextList(player, false); + } + break; + case KITEM_MINE: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + player->kartstuff[k_itemheld] = 1; + 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); + } + } + else if (ATTACK_IS_DOWN && player->kartstuff[k_itemheld]) + { + K_ThrowKartItem(player, false, MT_SSMINE, 1, 1); + K_PlayAttackTaunt(player->mo); + player->kartstuff[k_itemamount]--; + player->kartstuff[k_itemheld] = 0; + K_UpdateHnextList(player, true); + } + break; + case KITEM_BALLHOG: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->kartstuff[k_itemamount]--; + K_ThrowKartItem(player, true, MT_BALLHOG, 1, 0); + K_PlayAttackTaunt(player->mo); + } + break; + case KITEM_SPB: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->kartstuff[k_itemamount]--; + K_ThrowKartItem(player, true, MT_SPB, 1, 0); + K_PlayAttackTaunt(player->mo); + } + break; + case KITEM_GROW: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO + && player->kartstuff[k_growshrinktimer] <= 0) // Grow holds the item box hostage + { + if (player->kartstuff[k_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 = (3*mapobjectscale)/2; + if (cv_kartdebugshrink.value && !modeattacking && !player->bot) + player->mo->destscale = (6*player->mo->destscale)/8; + player->kartstuff[k_growshrinktimer] = itemtime+(4*TICRATE); // 12 seconds + P_RestoreMusic(player); + if (!P_IsDisplayPlayer(player)) + S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); + S_StartSound(player->mo, sfx_kc5a); + } + player->kartstuff[k_itemamount]--; + } + break; + case KITEM_SHRINK: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + K_DoShrink(player); + player->kartstuff[k_itemamount]--; + K_PlayPowerGloatSound(player->mo); + } + break; + case KITEM_THUNDERSHIELD: + if (player->kartstuff[k_curshield] != 1) + { + 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); + S_StartSound(shield, sfx_s3k41); + player->kartstuff[k_curshield] = 1; + } + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + K_DoThunderShield(player); + player->kartstuff[k_itemamount]--; + K_PlayAttackTaunt(player->mo); + } + break; + case KITEM_HYUDORO: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->kartstuff[k_itemamount]--; + K_DoHyudoroSteal(player); // yes. yes they do. + } + break; + case KITEM_POGOSPRING: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && onground && NO_HYUDORO + && !player->kartstuff[k_pogospring]) + { + K_PlayBoostTaunt(player->mo); + K_DoPogoSpring(player->mo, 32<kartstuff[k_pogospring] = 1; + player->kartstuff[k_itemamount]--; + } + break; + case KITEM_SUPERRING: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + player->kartstuff[k_superring] += (10*3); + player->kartstuff[k_itemamount]--; + } + break; + case KITEM_KITCHENSINK: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) + { + mobj_t *mo; + player->kartstuff[k_itemheld] = 1; + 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); + } + } + else if (ATTACK_IS_DOWN && HOLDING_ITEM && player->kartstuff[k_itemheld]) // Sink thrown + { + K_ThrowKartItem(player, false, MT_SINK, 1, 2); + K_PlayAttackTaunt(player->mo); + player->kartstuff[k_itemamount]--; + player->kartstuff[k_itemheld] = 0; + K_UpdateHnextList(player, true); + } + break; + case KITEM_SAD: + if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO + && !player->kartstuff[k_sadtimer]) + { + player->kartstuff[k_sadtimer] = stealtime; + player->kartstuff[k_itemamount]--; + } + break; + default: + break; + } + } + } + } + + // No more! + if (!player->kartstuff[k_itemamount]) + { + player->kartstuff[k_itemheld] = 0; + player->kartstuff[k_itemtype] = KITEM_NONE; + } + + if (spbplace == -1 || player->kartstuff[k_position] != spbplace) + player->kartstuff[k_ringlock] = 0; // reset ring lock + + if (player->kartstuff[k_itemtype] == KITEM_THUNDERSHIELD) + { + if ((player->kartstuff[k_rings]+player->kartstuff[k_pickuprings]) < 20 && !player->kartstuff[k_ringlock]) + K_LookForRings(player->mo); + } + else + player->kartstuff[k_curshield] = 0; + + if (player->kartstuff[k_growshrinktimer] <= 0) + player->kartstuff[k_growcancel] = -1; + + if (player->kartstuff[k_itemtype] == KITEM_SPB + || player->kartstuff[k_itemtype] == KITEM_SHRINK + || player->kartstuff[k_growshrinktimer] < 0) + indirectitemcooldown = 20*TICRATE; + + if (player->kartstuff[k_hyudorotimer] > 0) + { + if (splitscreen) + { + if (leveltime & 1) + player->mo->flags2 |= MF2_DONTDRAW; + else + player->mo->flags2 &= ~MF2_DONTDRAW; + + if (player->kartstuff[k_hyudorotimer] >= (1*TICRATE/2) && player->kartstuff[k_hyudorotimer] <= hyudorotime-(1*TICRATE/2)) + { + if (player == &players[displayplayers[1]]) + player->mo->eflags |= MFE_DRAWONLYFORP2; + else if (player == &players[displayplayers[2]] && splitscreen > 1) + player->mo->eflags |= MFE_DRAWONLYFORP3; + else if (player == &players[displayplayers[3]] && splitscreen > 2) + player->mo->eflags |= MFE_DRAWONLYFORP4; + else if (player == &players[displayplayers[0]]) + player->mo->eflags |= MFE_DRAWONLYFORP1; + else + player->mo->flags2 |= MF2_DONTDRAW; + } + else + player->mo->eflags &= ~(MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4); + } + else + { + if (P_IsDisplayPlayer(player) + || (!P_IsDisplayPlayer(player) && (player->kartstuff[k_hyudorotimer] < (1*TICRATE/2) || player->kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2)))) + { + if (leveltime & 1) + player->mo->flags2 |= MF2_DONTDRAW; + else + player->mo->flags2 &= ~MF2_DONTDRAW; + } + else + player->mo->flags2 |= MF2_DONTDRAW; + } + + player->powers[pw_flashing] = player->kartstuff[k_hyudorotimer]; // We'll do this for now, let's people know about the invisible people through subtle hints + } + else if (player->kartstuff[k_hyudorotimer] == 0) + { + player->mo->flags2 &= ~MF2_DONTDRAW; + player->mo->eflags &= ~(MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4); + } + + if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) // dead in match? you da bomb + { + K_DropItems(player); //K_StripItems(player); + K_StripOther(player); + player->mo->flags2 |= MF2_SHADOW; + player->powers[pw_flashing] = player->kartstuff[k_comebacktimer]; + } + else if (G_RaceGametype() || player->kartstuff[k_bumper] > 0) + { + player->mo->flags2 &= ~MF2_SHADOW; + } + } + + if (onground) + { + fixed_t prevfriction = player->mo->friction; + + // Reduce friction after hitting a horizontal spring + if (player->kartstuff[k_tiregrease]) + player->mo->friction += ((FRACUNIT - prevfriction) / greasetics) * player->kartstuff[k_tiregrease]; + + // Friction + if (!player->kartstuff[k_offroad]) + { + if (player->speed > 0 && cmd->forwardmove == 0 && player->mo->friction == 59392) + player->mo->friction += 4608; + } + + if (player->speed > 0 && cmd->forwardmove < 0) // change friction while braking no matter what, otherwise it's not any more effective than just letting go off accel + player->mo->friction -= 2048; + + // Karma ice physics + if (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) + player->mo->friction += 1228; + + // Wipeout slowdown + if (player->kartstuff[k_spinouttimer] && player->kartstuff[k_wipeoutslow]) + { + if (player->kartstuff[k_offroad]) + player->mo->friction -= 4912; + if (player->kartstuff[k_wipeoutslow] == 1) + player->mo->friction -= 9824; + } + + // Friction was changed, so we must recalculate a bunch of stuff + if (player->mo->friction != prevfriction) + { + if (player->mo->friction > FRACUNIT) + player->mo->friction = FRACUNIT; + if (player->mo->friction < 0) + player->mo->friction = 0; + + player->mo->movefactor = FixedDiv(ORIG_FRICTION, player->mo->friction); + + if (player->mo->movefactor < FRACUNIT) + player->mo->movefactor = 19*player->mo->movefactor - 18*FRACUNIT; + else + player->mo->movefactor = FRACUNIT; + + if (player->mo->movefactor < 32) + player->mo->movefactor = 32; + } + } + + K_KartDrift(player, P_IsObjectOnGround(player->mo)); // Not using onground, since we don't want this affected by spring pads + + // Quick Turning + // You can't turn your kart when you're not moving. + // So now it's time to burn some rubber! + if (player->speed < 2 && leveltime > starttime && cmd->buttons & BT_ACCELERATE && cmd->buttons & BT_BRAKE && cmd->driftturn != 0) + { + if (leveltime % 8 == 0) + S_StartSound(player->mo, sfx_s224); + } + + // Squishing + // If a Grow player or a sector crushes you, get flattened instead of being killed. + + if (player->kartstuff[k_squishedtimer] > 0) + { + //player->mo->flags |= MF_NOCLIP; + player->mo->momx = 0; + player->mo->momy = 0; + } + + // Play the starting countdown sounds + if (player == &players[displayplayers[0]]) // Don't play louder in splitscreen + { + if ((leveltime == starttime-(3*TICRATE)) || (leveltime == starttime-(2*TICRATE)) || (leveltime == starttime-TICRATE)) + S_StartSound(NULL, sfx_s3ka7); + if (leveltime == starttime) + { + S_StartSound(NULL, sfx_s3kad); + S_StopMusic(); // The GO! sound stops the level start ambience + } + } + + // Start charging once you're given the opportunity. + if (leveltime >= starttime-(2*TICRATE) && leveltime <= starttime) + { + if (cmd->buttons & BT_ACCELERATE) + { + if (player->kartstuff[k_boostcharge] == 0) + player->kartstuff[k_boostcharge] = cmd->latency; + + player->kartstuff[k_boostcharge]++; + } + else + player->kartstuff[k_boostcharge] = 0; + } + + // Increase your size while charging your engine. + if (leveltime < starttime+10) + { + player->mo->scalespeed = mapobjectscale/12; + player->mo->destscale = mapobjectscale + (player->kartstuff[k_boostcharge]*131); + if (cv_kartdebugshrink.value && !modeattacking && !player->bot) + player->mo->destscale = (6*player->mo->destscale)/8; + } + + // Determine the outcome of your charge. + if (leveltime > starttime && player->kartstuff[k_boostcharge]) + { + // Not even trying? + if (player->kartstuff[k_boostcharge] < 35) + { + if (player->kartstuff[k_boostcharge] > 17) + S_StartSound(player->mo, sfx_cdfm00); // chosen instead of a conventional skid because it's more engine-like + } + // Get an instant boost! + else if (player->kartstuff[k_boostcharge] <= 50) + { + player->kartstuff[k_startboost] = (50-player->kartstuff[k_boostcharge])+20; + + if (player->kartstuff[k_boostcharge] <= 36) + { + player->kartstuff[k_startboost] = 0; + K_DoSneaker(player, 0); + player->kartstuff[k_sneakertimer] = 70; // PERFECT BOOST!! + + if (!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) // Let everyone hear this one + S_StartSound(player->mo, sfx_s25f); + } + else + { + K_SpawnDashDustRelease(player); // already handled for perfect boosts by K_DoSneaker + if ((!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) && P_IsDisplayPlayer(player)) + { + if (player->kartstuff[k_boostcharge] <= 40) + S_StartSound(player->mo, sfx_cdfm01); // You were almost there! + else + S_StartSound(player->mo, sfx_s23c); // Nope, better luck next time. + } + } + } + // You overcharged your engine? Those things are expensive!!! + else if (player->kartstuff[k_boostcharge] > 50) + { + player->powers[pw_nocontrol] = 40; + //S_StartSound(player->mo, sfx_kc34); + S_StartSound(player->mo, sfx_s3k83); + player->pflags |= PF_SKIDDOWN; // cheeky pflag reuse + } + + player->kartstuff[k_boostcharge] = 0; + } +} + +void K_CalculateBattleWanted(void) +{ + UINT8 numingame = 0, numplaying = 0, numwanted = 0; + SINT8 bestbumperplayer = -1, bestbumper = -1; + SINT8 camppos[MAXPLAYERS]; // who is the biggest camper + UINT8 ties = 0, nextcamppos = 0; + boolean setbumper = false; + UINT8 i, j; + + if (!G_BattleGametype()) + { + for (i = 0; i < 4; i++) + battlewanted[i] = -1; + return; + } + + wantedcalcdelay = wantedfrequency; + + for (i = 0; i < MAXPLAYERS; i++) + camppos[i] = -1; // initialize + + for (i = 0; i < MAXPLAYERS; i++) + { + UINT8 position = 1; + + if (!playeringame[i] || players[i].spectator) // Not playing + continue; + + if (players[i].exiting) // We're done, don't calculate. + return; + + numplaying++; + + if (players[i].kartstuff[k_bumper] <= 0) // Not alive, so don't do anything else + continue; + + numingame++; + + if (bestbumper == -1 || players[i].kartstuff[k_bumper] > bestbumper) + { + bestbumper = players[i].kartstuff[k_bumper]; + bestbumperplayer = i; + } + else if (players[i].kartstuff[k_bumper] == bestbumper) + bestbumperplayer = -1; // Tie, no one has best bumper. + + for (j = 0; j < MAXPLAYERS; j++) + { + if (!playeringame[j] || players[j].spectator) + continue; + if (players[j].kartstuff[k_bumper] <= 0) + continue; + if (j == i) + continue; + if (players[j].kartstuff[k_wanted] == players[i].kartstuff[k_wanted] && players[j].marescore > players[i].marescore) + position++; + else if (players[j].kartstuff[k_wanted] > players[i].kartstuff[k_wanted]) + position++; + } + + position--; // Make zero based + + while (camppos[position] != -1) // Port priority! + position++; + + camppos[position] = i; + } + + if (numplaying <= 2 || (numingame <= 2 && bestbumper == 1)) // In 1v1s then there's no need for WANTED. In bigger netgames, don't show anyone as WANTED when they're equally matched. + numwanted = 0; + else + numwanted = min(4, 1 + ((numingame-2) / 4)); + + for (i = 0; i < 4; i++) + { + if (i+1 > numwanted) // Not enough players for this slot to be wanted! + battlewanted[i] = -1; + else if (bestbumperplayer != -1 && !setbumper) // If there's a player who has an untied bumper lead over everyone else, they are the first to be wanted. + { + battlewanted[i] = bestbumperplayer; + setbumper = true; // Don't set twice + } + else + { + // Don't accidentally set the same player, if the bestbumperplayer is also a huge camper. + while (bestbumperplayer != -1 && camppos[nextcamppos] != -1 + && bestbumperplayer == camppos[nextcamppos]) + nextcamppos++; + + // Do not add *any* more people if there's too many times that are tied with others. + // This could theoretically happen very easily if people don't hit each other for a while after the start of a match. + // (I will be sincerely impressed if more than 2 people tie after people start hitting each other though) + + if (camppos[nextcamppos] == -1 // Out of entries + || ties >= (numwanted-i)) // Already counted ties + { + battlewanted[i] = -1; + continue; + } + + if (ties < (numwanted-i)) + { + ties = 0; // Reset + for (j = 0; j < 2; j++) + { + if (camppos[nextcamppos+(j+1)] == -1) // Nothing beyond, cancel + break; + if (players[camppos[nextcamppos]].kartstuff[k_wanted] == players[camppos[nextcamppos+(j+1)]].kartstuff[k_wanted]) + ties++; + } + } + + if (ties < (numwanted-i)) // Is it still low enough after counting? + { + battlewanted[i] = camppos[nextcamppos]; + nextcamppos++; + } + else + battlewanted[i] = -1; + } + } +} + +void K_CheckBumpers(void) +{ + UINT8 i; + UINT8 numingame = 0; + SINT8 winnernum = -1; + INT32 winnerscoreadd = 0; + + if (!multiplayer) + return; + + if (!G_BattleGametype()) + return; + + if (gameaction == ga_completed) + return; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) // not even in-game + continue; + + if (players[i].exiting) // we're already exiting! stop! + return; + + numingame++; + winnerscoreadd += players[i].marescore; + + if (players[i].kartstuff[k_bumper] <= 0) // if you don't have any bumpers, you're probably not a winner + continue; + else if (winnernum > -1) // TWO winners? that's dumb :V + return; + + winnernum = i; + winnerscoreadd -= players[i].marescore; + } + + if (numingame <= 1) + return; + + if (winnernum > -1 && playeringame[winnernum]) + { + players[winnernum].marescore += winnerscoreadd; + CONS_Printf(M_GetText("%s recieved %d point%s for winning!\n"), player_names[winnernum], winnerscoreadd, (winnerscoreadd == 1 ? "" : "s")); + } + + for (i = 0; i < MAXPLAYERS; i++) // This can't go in the earlier loop because winning adds points + K_KartUpdatePosition(&players[i]); + + for (i = 0; i < MAXPLAYERS; i++) // and it can't be merged with this loop because it needs to be all updated before exiting... multi-loops suck... + P_DoPlayerExit(&players[i]); +} + +void K_CheckSpectateStatus(void) +{ + UINT8 respawnlist[MAXPLAYERS]; + UINT8 i, j, numingame = 0, numjoiners = 0; + + // Maintain spectate wait timer + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + if (players[i].spectator && (players[i].pflags & PF_WANTSTOJOIN)) + players[i].kartstuff[k_spectatewait]++; + else + players[i].kartstuff[k_spectatewait] = 0; + } + + // No one's allowed to join + if (!cv_allowteamchange.value) + return; + + // Get the number of players in game, and the players to be de-spectated. + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + if (!players[i].spectator) + { + numingame++; + if (cv_ingamecap.value && numingame >= cv_ingamecap.value) // DON'T allow if you've hit the in-game player cap + return; + if (gamestate != GS_LEVEL) // Allow if you're not in a level + continue; + if (players[i].exiting) // DON'T allow if anyone's exiting + return; + if (numingame < 2 || leveltime < starttime || mapreset) // Allow if the match hasn't started yet + continue; + if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in + return; + if (G_RaceGametype() && players[i].laps >= 2) // DON'T allow if the race is at 2 laps + return; + continue; + } + else if (!(players[i].pflags & PF_WANTSTOJOIN)) + continue; + + respawnlist[numjoiners++] = i; + } + + // literally zero point in going any further if nobody is joining + if (!numjoiners) + return; + + // Organize by spectate wait timer + if (cv_ingamecap.value) + { + UINT8 oldrespawnlist[MAXPLAYERS]; + memcpy(oldrespawnlist, respawnlist, numjoiners); + for (i = 0; i < numjoiners; i++) + { + UINT8 pos = 0; + INT32 ispecwait = players[oldrespawnlist[i]].kartstuff[k_spectatewait]; + + for (j = 0; j < numjoiners; j++) + { + INT32 jspecwait = players[oldrespawnlist[j]].kartstuff[k_spectatewait]; + if (j == i) + continue; + if (jspecwait > ispecwait) + pos++; + else if (jspecwait == ispecwait && j < i) + pos++; + } + + respawnlist[pos] = oldrespawnlist[i]; + } + } + + // Finally, we can de-spectate everyone! + for (i = 0; i < numjoiners; i++) + { + if (cv_ingamecap.value && numingame+i >= cv_ingamecap.value) // Hit the in-game player cap while adding people? + break; + P_SpectatorJoinGame(&players[respawnlist[i]]); + } + + // Reset the match if you're in an empty server + if (!mapreset && gamestate == GS_LEVEL && leveltime >= starttime && (numingame < 2 && numingame+i >= 2)) // use previous i value + { + S_ChangeMusicInternal("chalng", false); // COME ON + mapreset = 3*TICRATE; // Even though only the server uses this for game logic, set for everyone for HUD + } +} + +//} + +//{ SRB2kart HUD Code + +#define NUMPOSNUMS 10 +#define NUMPOSFRAMES 7 // White, three blues, three reds +#define NUMWINFRAMES 6 // Red, yellow, green, cyan, blue, purple + +//{ Patch Definitions +static patch_t *kp_nodraw; + +static patch_t *kp_timesticker; +static patch_t *kp_timestickerwide; +static patch_t *kp_lapsticker; +static patch_t *kp_lapstickerwide; +static patch_t *kp_lapstickernarrow; +static patch_t *kp_splitlapflag; +static patch_t *kp_bumpersticker; +static patch_t *kp_bumperstickerwide; +static patch_t *kp_karmasticker; +static patch_t *kp_splitkarmabomb; +static patch_t *kp_timeoutsticker; + +static patch_t *kp_startcountdown[16]; +static patch_t *kp_racefinish[6]; + +static patch_t *kp_positionnum[NUMPOSNUMS][NUMPOSFRAMES]; +static patch_t *kp_winnernum[NUMPOSFRAMES]; + +static patch_t *kp_facenum[MAXPLAYERS+1]; +static patch_t *kp_facehighlight[8]; + +static patch_t *kp_spbminimap; + +static patch_t *kp_ringsticker[2]; +static patch_t *kp_ringstickersplit[4]; +static patch_t *kp_ring[6]; +static patch_t *kp_smallring[6]; +static patch_t *kp_ringdebtminus; +static patch_t *kp_ringdebtminussmall; +static patch_t *kp_ringspblock[16]; +static patch_t *kp_ringspblocksmall[16]; + +static patch_t *kp_speedometersticker; +static patch_t *kp_speedometerlabel[4]; + +static patch_t *kp_rankbumper; +static patch_t *kp_tinybumper[2]; +static patch_t *kp_ranknobumpers; + +static patch_t *kp_battlewin; +static patch_t *kp_battlecool; +static patch_t *kp_battlelose; +static patch_t *kp_battlewait; +static patch_t *kp_battleinfo; +static patch_t *kp_wanted; +static patch_t *kp_wantedsplit; +static patch_t *kp_wantedreticle; + +static patch_t *kp_itembg[4]; +static patch_t *kp_itemtimer[2]; +static patch_t *kp_itemmulsticker[2]; +static patch_t *kp_itemx; + +static patch_t *kp_superring[2]; +static patch_t *kp_sneaker[2]; +static patch_t *kp_rocketsneaker[2]; +static patch_t *kp_invincibility[13]; +static patch_t *kp_banana[2]; +static patch_t *kp_eggman[2]; +static patch_t *kp_orbinaut[5]; +static patch_t *kp_jawz[2]; +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_sadface[2]; + +static patch_t *kp_check[6]; + +static patch_t *kp_eggnum[4]; + +static patch_t *kp_fpview[3]; +static patch_t *kp_inputwheel[5]; + +static patch_t *kp_challenger[25]; + +static patch_t *kp_lapanim_lap[7]; +static patch_t *kp_lapanim_final[11]; +static patch_t *kp_lapanim_number[10][3]; +static patch_t *kp_lapanim_emblem[2]; +static patch_t *kp_lapanim_hand[3]; + static patch_t *kp_yougotem; -static patch_t *kp_itemminimap; - -static patch_t *kp_alagles[10]; -static patch_t *kp_blagles[6]; - -void K_LoadKartHUDGraphics(void) -{ - INT32 i, j; - char buffer[9]; - - // Null Stuff - kp_nodraw = W_CachePatchName("K_TRNULL", PU_HUDGFX); - - // Stickers - kp_timesticker = W_CachePatchName("K_STTIME", PU_HUDGFX); - kp_timestickerwide = W_CachePatchName("K_STTIMW", PU_HUDGFX); - kp_lapsticker = W_CachePatchName("K_STLAPS", PU_HUDGFX); - kp_lapstickerwide = W_CachePatchName("K_STLAPW", PU_HUDGFX); - kp_lapstickernarrow = W_CachePatchName("K_STLAPN", PU_HUDGFX); - kp_splitlapflag = W_CachePatchName("K_SPTLAP", PU_HUDGFX); - kp_bumpersticker = W_CachePatchName("K_STBALN", PU_HUDGFX); - kp_bumperstickerwide = W_CachePatchName("K_STBALW", PU_HUDGFX); - kp_karmasticker = W_CachePatchName("K_STKARM", PU_HUDGFX); - kp_splitkarmabomb = W_CachePatchName("K_SPTKRM", PU_HUDGFX); - kp_timeoutsticker = W_CachePatchName("K_STTOUT", PU_HUDGFX); - - // Starting countdown - kp_startcountdown[0] = W_CachePatchName("K_CNT3A", PU_HUDGFX); - kp_startcountdown[1] = W_CachePatchName("K_CNT2A", PU_HUDGFX); - kp_startcountdown[2] = W_CachePatchName("K_CNT1A", PU_HUDGFX); - kp_startcountdown[3] = W_CachePatchName("K_CNTGOA", PU_HUDGFX); - kp_startcountdown[4] = W_CachePatchName("K_CNT3B", PU_HUDGFX); - kp_startcountdown[5] = W_CachePatchName("K_CNT2B", PU_HUDGFX); - kp_startcountdown[6] = W_CachePatchName("K_CNT1B", PU_HUDGFX); - kp_startcountdown[7] = W_CachePatchName("K_CNTGOB", PU_HUDGFX); - // Splitscreen - kp_startcountdown[8] = W_CachePatchName("K_SMC3A", PU_HUDGFX); - kp_startcountdown[9] = W_CachePatchName("K_SMC2A", PU_HUDGFX); - kp_startcountdown[10] = W_CachePatchName("K_SMC1A", PU_HUDGFX); - kp_startcountdown[11] = W_CachePatchName("K_SMCGOA", PU_HUDGFX); - kp_startcountdown[12] = W_CachePatchName("K_SMC3B", PU_HUDGFX); - kp_startcountdown[13] = W_CachePatchName("K_SMC2B", PU_HUDGFX); - kp_startcountdown[14] = W_CachePatchName("K_SMC1B", PU_HUDGFX); - kp_startcountdown[15] = W_CachePatchName("K_SMCGOB", PU_HUDGFX); - - // Finish - kp_racefinish[0] = W_CachePatchName("K_FINA", PU_HUDGFX); - kp_racefinish[1] = W_CachePatchName("K_FINB", PU_HUDGFX); - // Splitscreen - kp_racefinish[2] = W_CachePatchName("K_SMFINA", PU_HUDGFX); - kp_racefinish[3] = W_CachePatchName("K_SMFINB", PU_HUDGFX); - // 2P splitscreen - kp_racefinish[4] = W_CachePatchName("K_2PFINA", PU_HUDGFX); - kp_racefinish[5] = W_CachePatchName("K_2PFINB", PU_HUDGFX); - - // Position numbers - sprintf(buffer, "K_POSNxx"); - for (i = 0; i < NUMPOSNUMS; i++) - { - buffer[6] = '0'+i; - for (j = 0; j < NUMPOSFRAMES; j++) - { - //sprintf(buffer, "K_POSN%d%d", i, j); - buffer[7] = '0'+j; - kp_positionnum[i][j] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - } - - sprintf(buffer, "K_POSNWx"); - for (i = 0; i < NUMWINFRAMES; i++) - { - buffer[7] = '0'+i; - kp_winnernum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - sprintf(buffer, "OPPRNKxx"); - for (i = 0; i <= MAXPLAYERS; i++) - { - buffer[6] = '0'+(i/10); - buffer[7] = '0'+(i%10); - kp_facenum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - sprintf(buffer, "K_CHILIx"); - for (i = 0; i < 8; i++) - { - buffer[7] = '0'+(i+1); - kp_facehighlight[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - // Rings & Lives - kp_ringsticker[0] = W_CachePatchName("RNGBACKA", PU_HUDGFX); - kp_ringsticker[1] = W_CachePatchName("RNGBACKB", PU_HUDGFX); - - sprintf(buffer, "K_RINGx"); - for (i = 0; i < 6; i++) - { - buffer[6] = '0'+(i+1); - kp_ring[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - kp_ringdebtminus = W_CachePatchName("RDEBTMIN", PU_HUDGFX); - - sprintf(buffer, "SPBRNGxx"); - for (i = 0; i < 16; i++) - { - buffer[6] = '0'+((i+1) / 10); - buffer[7] = '0'+((i+1) % 10); - kp_ringspblock[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - kp_ringstickersplit[0] = W_CachePatchName("SMRNGBGA", PU_HUDGFX); - kp_ringstickersplit[1] = W_CachePatchName("SMRNGBGB", PU_HUDGFX); - - sprintf(buffer, "K_SRINGx"); - for (i = 0; i < 6; i++) - { - buffer[7] = '0'+(i+1); - kp_smallring[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - kp_ringdebtminussmall = W_CachePatchName("SRDEBTMN", PU_HUDGFX); - - sprintf(buffer, "SPBRGSxx"); - for (i = 0; i < 16; i++) - { - buffer[6] = '0'+((i+1) / 10); - buffer[7] = '0'+((i+1) % 10); - kp_ringspblocksmall[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - // Speedometer - kp_speedometersticker = W_CachePatchName("K_SPDMBG", PU_HUDGFX); - - sprintf(buffer, "K_SPDMLx"); - for (i = 0; i < 4; i++) - { - buffer[7] = '0'+(i+1); - kp_speedometerlabel[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - // Extra ranking icons - kp_rankbumper = W_CachePatchName("K_BLNICO", PU_HUDGFX); - kp_tinybumper[0] = W_CachePatchName("K_BLNA", PU_HUDGFX); - kp_tinybumper[1] = W_CachePatchName("K_BLNB", PU_HUDGFX); - kp_ranknobumpers = W_CachePatchName("K_NOBLNS", PU_HUDGFX); - - // Battle graphics - kp_battlewin = W_CachePatchName("K_BWIN", PU_HUDGFX); - kp_battlecool = W_CachePatchName("K_BCOOL", PU_HUDGFX); - kp_battlelose = W_CachePatchName("K_BLOSE", PU_HUDGFX); - kp_battlewait = W_CachePatchName("K_BWAIT", PU_HUDGFX); - kp_battleinfo = W_CachePatchName("K_BINFO", PU_HUDGFX); - kp_wanted = W_CachePatchName("K_WANTED", PU_HUDGFX); - kp_wantedsplit = W_CachePatchName("4PWANTED", PU_HUDGFX); - kp_wantedreticle = W_CachePatchName("MMAPWANT", PU_HUDGFX); - - // Kart Item Windows - kp_itembg[0] = W_CachePatchName("K_ITBG", PU_HUDGFX); - kp_itembg[1] = W_CachePatchName("K_ITBGD", PU_HUDGFX); - kp_itemtimer[0] = W_CachePatchName("K_ITIMER", PU_HUDGFX); - kp_itemmulsticker[0] = W_CachePatchName("K_ITMUL", PU_HUDGFX); - kp_itemx = W_CachePatchName("K_ITX", PU_HUDGFX); - - kp_superring[0] = W_CachePatchName("K_ITRING", PU_HUDGFX); - kp_sneaker[0] = W_CachePatchName("K_ITSHOE", PU_HUDGFX); - kp_rocketsneaker[0] = W_CachePatchName("K_ITRSHE", PU_HUDGFX); - - sprintf(buffer, "K_ITINVx"); - for (i = 0; i < 7; i++) - { - buffer[7] = '1'+i; - kp_invincibility[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - kp_banana[0] = W_CachePatchName("K_ITBANA", PU_HUDGFX); - kp_eggman[0] = W_CachePatchName("K_ITEGGM", PU_HUDGFX); - sprintf(buffer, "K_ITORBx"); - for (i = 0; i < 4; i++) - { - buffer[7] = '1'+i; - kp_orbinaut[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - kp_jawz[0] = W_CachePatchName("K_ITJAWZ", PU_HUDGFX); - kp_mine[0] = W_CachePatchName("K_ITMINE", PU_HUDGFX); - kp_ballhog[0] = W_CachePatchName("K_ITBHOG", PU_HUDGFX); - kp_selfpropelledbomb[0] = W_CachePatchName("K_ITSPB", PU_HUDGFX); - kp_grow[0] = W_CachePatchName("K_ITGROW", PU_HUDGFX); - kp_shrink[0] = W_CachePatchName("K_ITSHRK", PU_HUDGFX); - kp_thundershield[0] = W_CachePatchName("K_ITTHNS", PU_HUDGFX); - kp_hyudoro[0] = W_CachePatchName("K_ITHYUD", PU_HUDGFX); - kp_pogospring[0] = W_CachePatchName("K_ITPOGO", PU_HUDGFX); - kp_kitchensink[0] = W_CachePatchName("K_ITSINK", PU_HUDGFX); - kp_sadface[0] = W_CachePatchName("K_ITSAD", PU_HUDGFX); - - // Splitscreen - kp_itembg[2] = W_CachePatchName("K_ISBG", PU_HUDGFX); - kp_itembg[3] = W_CachePatchName("K_ISBGD", PU_HUDGFX); - kp_itemtimer[1] = W_CachePatchName("K_ISIMER", PU_HUDGFX); - kp_itemmulsticker[1] = W_CachePatchName("K_ISMUL", PU_HUDGFX); - - kp_superring[1] = W_CachePatchName("K_ISRING", PU_HUDGFX); - kp_sneaker[1] = W_CachePatchName("K_ISSHOE", PU_HUDGFX); - kp_rocketsneaker[1] = W_CachePatchName("K_ISRSHE", PU_HUDGFX); - sprintf(buffer, "K_ISINVx"); - for (i = 0; i < 6; i++) - { - buffer[7] = '1'+i; - kp_invincibility[i+7] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - kp_banana[1] = W_CachePatchName("K_ISBANA", PU_HUDGFX); - kp_eggman[1] = W_CachePatchName("K_ISEGGM", PU_HUDGFX); - kp_orbinaut[4] = W_CachePatchName("K_ISORBN", PU_HUDGFX); - kp_jawz[1] = W_CachePatchName("K_ISJAWZ", PU_HUDGFX); - kp_mine[1] = W_CachePatchName("K_ISMINE", PU_HUDGFX); - kp_ballhog[1] = W_CachePatchName("K_ISBHOG", PU_HUDGFX); - kp_selfpropelledbomb[1] = W_CachePatchName("K_ISSPB", PU_HUDGFX); - kp_grow[1] = W_CachePatchName("K_ISGROW", PU_HUDGFX); - kp_shrink[1] = W_CachePatchName("K_ISSHRK", PU_HUDGFX); - kp_thundershield[1] = W_CachePatchName("K_ISTHNS", PU_HUDGFX); - kp_hyudoro[1] = W_CachePatchName("K_ISHYUD", PU_HUDGFX); - kp_pogospring[1] = W_CachePatchName("K_ISPOGO", PU_HUDGFX); - kp_kitchensink[1] = W_CachePatchName("K_ISSINK", PU_HUDGFX); - kp_sadface[1] = W_CachePatchName("K_ISSAD", PU_HUDGFX); - - // CHECK indicators - sprintf(buffer, "K_CHECKx"); - for (i = 0; i < 6; i++) - { - buffer[7] = '1'+i; - kp_check[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - // Eggman warning numbers - sprintf(buffer, "K_EGGNx"); - for (i = 0; i < 4; i++) - { - buffer[6] = '0'+i; - kp_eggnum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - // First person mode - kp_fpview[0] = W_CachePatchName("VIEWA0", PU_HUDGFX); - kp_fpview[1] = W_CachePatchName("VIEWB0D0", PU_HUDGFX); - kp_fpview[2] = W_CachePatchName("VIEWC0E0", PU_HUDGFX); - - // Input UI Wheel - sprintf(buffer, "K_WHEELx"); - for (i = 0; i < 5; i++) - { - buffer[7] = '0'+i; - kp_inputwheel[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - // HERE COMES A NEW CHALLENGER - sprintf(buffer, "K_CHALxx"); - for (i = 0; i < 25; i++) - { - buffer[6] = '0'+((i+1)/10); - buffer[7] = '0'+((i+1)%10); - kp_challenger[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - // Lap start animation - sprintf(buffer, "K_LAP0x"); - for (i = 0; i < 7; i++) - { - buffer[6] = '0'+(i+1); - kp_lapanim_lap[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - sprintf(buffer, "K_LAPFxx"); - for (i = 0; i < 11; i++) - { - buffer[6] = '0'+((i+1)/10); - buffer[7] = '0'+((i+1)%10); - kp_lapanim_final[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - sprintf(buffer, "K_LAPNxx"); - for (i = 0; i < 10; i++) - { - buffer[6] = '0'+i; - for (j = 0; j < 3; j++) - { - buffer[7] = '0'+(j+1); - kp_lapanim_number[i][j] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - } - - sprintf(buffer, "K_LAPE0x"); - for (i = 0; i < 2; i++) - { - buffer[7] = '0'+(i+1); - kp_lapanim_emblem[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - sprintf(buffer, "K_LAPH0x"); - for (i = 0; i < 3; i++) - { - buffer[7] = '0'+(i+1); - kp_lapanim_hand[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - +static patch_t *kp_itemminimap; + +static patch_t *kp_alagles[10]; +static patch_t *kp_blagles[6]; + +void K_LoadKartHUDGraphics(void) +{ + INT32 i, j; + char buffer[9]; + + // Null Stuff + kp_nodraw = W_CachePatchName("K_TRNULL", PU_HUDGFX); + + // Stickers + kp_timesticker = W_CachePatchName("K_STTIME", PU_HUDGFX); + kp_timestickerwide = W_CachePatchName("K_STTIMW", PU_HUDGFX); + kp_lapsticker = W_CachePatchName("K_STLAPS", PU_HUDGFX); + kp_lapstickerwide = W_CachePatchName("K_STLAPW", PU_HUDGFX); + kp_lapstickernarrow = W_CachePatchName("K_STLAPN", PU_HUDGFX); + kp_splitlapflag = W_CachePatchName("K_SPTLAP", PU_HUDGFX); + kp_bumpersticker = W_CachePatchName("K_STBALN", PU_HUDGFX); + kp_bumperstickerwide = W_CachePatchName("K_STBALW", PU_HUDGFX); + kp_karmasticker = W_CachePatchName("K_STKARM", PU_HUDGFX); + kp_splitkarmabomb = W_CachePatchName("K_SPTKRM", PU_HUDGFX); + kp_timeoutsticker = W_CachePatchName("K_STTOUT", PU_HUDGFX); + + // Starting countdown + kp_startcountdown[0] = W_CachePatchName("K_CNT3A", PU_HUDGFX); + kp_startcountdown[1] = W_CachePatchName("K_CNT2A", PU_HUDGFX); + kp_startcountdown[2] = W_CachePatchName("K_CNT1A", PU_HUDGFX); + kp_startcountdown[3] = W_CachePatchName("K_CNTGOA", PU_HUDGFX); + kp_startcountdown[4] = W_CachePatchName("K_CNT3B", PU_HUDGFX); + kp_startcountdown[5] = W_CachePatchName("K_CNT2B", PU_HUDGFX); + kp_startcountdown[6] = W_CachePatchName("K_CNT1B", PU_HUDGFX); + kp_startcountdown[7] = W_CachePatchName("K_CNTGOB", PU_HUDGFX); + // Splitscreen + kp_startcountdown[8] = W_CachePatchName("K_SMC3A", PU_HUDGFX); + kp_startcountdown[9] = W_CachePatchName("K_SMC2A", PU_HUDGFX); + kp_startcountdown[10] = W_CachePatchName("K_SMC1A", PU_HUDGFX); + kp_startcountdown[11] = W_CachePatchName("K_SMCGOA", PU_HUDGFX); + kp_startcountdown[12] = W_CachePatchName("K_SMC3B", PU_HUDGFX); + kp_startcountdown[13] = W_CachePatchName("K_SMC2B", PU_HUDGFX); + kp_startcountdown[14] = W_CachePatchName("K_SMC1B", PU_HUDGFX); + kp_startcountdown[15] = W_CachePatchName("K_SMCGOB", PU_HUDGFX); + + // Finish + kp_racefinish[0] = W_CachePatchName("K_FINA", PU_HUDGFX); + kp_racefinish[1] = W_CachePatchName("K_FINB", PU_HUDGFX); + // Splitscreen + kp_racefinish[2] = W_CachePatchName("K_SMFINA", PU_HUDGFX); + kp_racefinish[3] = W_CachePatchName("K_SMFINB", PU_HUDGFX); + // 2P splitscreen + kp_racefinish[4] = W_CachePatchName("K_2PFINA", PU_HUDGFX); + kp_racefinish[5] = W_CachePatchName("K_2PFINB", PU_HUDGFX); + + // Position numbers + sprintf(buffer, "K_POSNxx"); + for (i = 0; i < NUMPOSNUMS; i++) + { + buffer[6] = '0'+i; + for (j = 0; j < NUMPOSFRAMES; j++) + { + //sprintf(buffer, "K_POSN%d%d", i, j); + buffer[7] = '0'+j; + kp_positionnum[i][j] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + } + + sprintf(buffer, "K_POSNWx"); + for (i = 0; i < NUMWINFRAMES; i++) + { + buffer[7] = '0'+i; + kp_winnernum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + sprintf(buffer, "OPPRNKxx"); + for (i = 0; i <= MAXPLAYERS; i++) + { + buffer[6] = '0'+(i/10); + buffer[7] = '0'+(i%10); + kp_facenum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + sprintf(buffer, "K_CHILIx"); + for (i = 0; i < 8; i++) + { + buffer[7] = '0'+(i+1); + kp_facehighlight[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + kp_spbminimap = W_CachePatchName("SPBMMAP", PU_HUDGFX); + + // Rings & Lives + kp_ringsticker[0] = W_CachePatchName("RNGBACKA", PU_HUDGFX); + kp_ringsticker[1] = W_CachePatchName("RNGBACKB", PU_HUDGFX); + + sprintf(buffer, "K_RINGx"); + for (i = 0; i < 6; i++) + { + buffer[6] = '0'+(i+1); + kp_ring[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + kp_ringdebtminus = W_CachePatchName("RDEBTMIN", PU_HUDGFX); + + sprintf(buffer, "SPBRNGxx"); + for (i = 0; i < 16; i++) + { + buffer[6] = '0'+((i+1) / 10); + buffer[7] = '0'+((i+1) % 10); + kp_ringspblock[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + kp_ringstickersplit[0] = W_CachePatchName("SMRNGBGA", PU_HUDGFX); + kp_ringstickersplit[1] = W_CachePatchName("SMRNGBGB", PU_HUDGFX); + + sprintf(buffer, "K_SRINGx"); + for (i = 0; i < 6; i++) + { + buffer[7] = '0'+(i+1); + kp_smallring[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + kp_ringdebtminussmall = W_CachePatchName("SRDEBTMN", PU_HUDGFX); + + sprintf(buffer, "SPBRGSxx"); + for (i = 0; i < 16; i++) + { + buffer[6] = '0'+((i+1) / 10); + buffer[7] = '0'+((i+1) % 10); + kp_ringspblocksmall[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + // Speedometer + kp_speedometersticker = W_CachePatchName("K_SPDMBG", PU_HUDGFX); + + sprintf(buffer, "K_SPDMLx"); + for (i = 0; i < 4; i++) + { + buffer[7] = '0'+(i+1); + kp_speedometerlabel[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + // Extra ranking icons + kp_rankbumper = W_CachePatchName("K_BLNICO", PU_HUDGFX); + kp_tinybumper[0] = W_CachePatchName("K_BLNA", PU_HUDGFX); + kp_tinybumper[1] = W_CachePatchName("K_BLNB", PU_HUDGFX); + kp_ranknobumpers = W_CachePatchName("K_NOBLNS", PU_HUDGFX); + + // Battle graphics + kp_battlewin = W_CachePatchName("K_BWIN", PU_HUDGFX); + kp_battlecool = W_CachePatchName("K_BCOOL", PU_HUDGFX); + kp_battlelose = W_CachePatchName("K_BLOSE", PU_HUDGFX); + kp_battlewait = W_CachePatchName("K_BWAIT", PU_HUDGFX); + kp_battleinfo = W_CachePatchName("K_BINFO", PU_HUDGFX); + kp_wanted = W_CachePatchName("K_WANTED", PU_HUDGFX); + kp_wantedsplit = W_CachePatchName("4PWANTED", PU_HUDGFX); + kp_wantedreticle = W_CachePatchName("MMAPWANT", PU_HUDGFX); + + // Kart Item Windows + kp_itembg[0] = W_CachePatchName("K_ITBG", PU_HUDGFX); + kp_itembg[1] = W_CachePatchName("K_ITBGD", PU_HUDGFX); + kp_itemtimer[0] = W_CachePatchName("K_ITIMER", PU_HUDGFX); + kp_itemmulsticker[0] = W_CachePatchName("K_ITMUL", PU_HUDGFX); + kp_itemx = W_CachePatchName("K_ITX", PU_HUDGFX); + + kp_superring[0] = W_CachePatchName("K_ITRING", PU_HUDGFX); + kp_sneaker[0] = W_CachePatchName("K_ITSHOE", PU_HUDGFX); + kp_rocketsneaker[0] = W_CachePatchName("K_ITRSHE", PU_HUDGFX); + + sprintf(buffer, "K_ITINVx"); + for (i = 0; i < 7; i++) + { + buffer[7] = '1'+i; + kp_invincibility[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + kp_banana[0] = W_CachePatchName("K_ITBANA", PU_HUDGFX); + kp_eggman[0] = W_CachePatchName("K_ITEGGM", PU_HUDGFX); + sprintf(buffer, "K_ITORBx"); + for (i = 0; i < 4; i++) + { + buffer[7] = '1'+i; + kp_orbinaut[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + kp_jawz[0] = W_CachePatchName("K_ITJAWZ", PU_HUDGFX); + kp_mine[0] = W_CachePatchName("K_ITMINE", PU_HUDGFX); + kp_ballhog[0] = W_CachePatchName("K_ITBHOG", PU_HUDGFX); + kp_selfpropelledbomb[0] = W_CachePatchName("K_ITSPB", PU_HUDGFX); + kp_grow[0] = W_CachePatchName("K_ITGROW", PU_HUDGFX); + kp_shrink[0] = W_CachePatchName("K_ITSHRK", PU_HUDGFX); + kp_thundershield[0] = W_CachePatchName("K_ITTHNS", PU_HUDGFX); + kp_hyudoro[0] = W_CachePatchName("K_ITHYUD", PU_HUDGFX); + kp_pogospring[0] = W_CachePatchName("K_ITPOGO", PU_HUDGFX); + kp_kitchensink[0] = W_CachePatchName("K_ITSINK", PU_HUDGFX); + kp_sadface[0] = W_CachePatchName("K_ITSAD", PU_HUDGFX); + + // Splitscreen + kp_itembg[2] = W_CachePatchName("K_ISBG", PU_HUDGFX); + kp_itembg[3] = W_CachePatchName("K_ISBGD", PU_HUDGFX); + kp_itemtimer[1] = W_CachePatchName("K_ISIMER", PU_HUDGFX); + kp_itemmulsticker[1] = W_CachePatchName("K_ISMUL", PU_HUDGFX); + + kp_superring[1] = W_CachePatchName("K_ISRING", PU_HUDGFX); + kp_sneaker[1] = W_CachePatchName("K_ISSHOE", PU_HUDGFX); + kp_rocketsneaker[1] = W_CachePatchName("K_ISRSHE", PU_HUDGFX); + sprintf(buffer, "K_ISINVx"); + for (i = 0; i < 6; i++) + { + buffer[7] = '1'+i; + kp_invincibility[i+7] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + kp_banana[1] = W_CachePatchName("K_ISBANA", PU_HUDGFX); + kp_eggman[1] = W_CachePatchName("K_ISEGGM", PU_HUDGFX); + kp_orbinaut[4] = W_CachePatchName("K_ISORBN", PU_HUDGFX); + kp_jawz[1] = W_CachePatchName("K_ISJAWZ", PU_HUDGFX); + kp_mine[1] = W_CachePatchName("K_ISMINE", PU_HUDGFX); + kp_ballhog[1] = W_CachePatchName("K_ISBHOG", PU_HUDGFX); + kp_selfpropelledbomb[1] = W_CachePatchName("K_ISSPB", PU_HUDGFX); + kp_grow[1] = W_CachePatchName("K_ISGROW", PU_HUDGFX); + kp_shrink[1] = W_CachePatchName("K_ISSHRK", PU_HUDGFX); + kp_thundershield[1] = W_CachePatchName("K_ISTHNS", PU_HUDGFX); + kp_hyudoro[1] = W_CachePatchName("K_ISHYUD", PU_HUDGFX); + kp_pogospring[1] = W_CachePatchName("K_ISPOGO", PU_HUDGFX); + kp_kitchensink[1] = W_CachePatchName("K_ISSINK", PU_HUDGFX); + kp_sadface[1] = W_CachePatchName("K_ISSAD", PU_HUDGFX); + + // CHECK indicators + sprintf(buffer, "K_CHECKx"); + for (i = 0; i < 6; i++) + { + buffer[7] = '1'+i; + kp_check[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + // Eggman warning numbers + sprintf(buffer, "K_EGGNx"); + for (i = 0; i < 4; i++) + { + buffer[6] = '0'+i; + kp_eggnum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + // First person mode + kp_fpview[0] = W_CachePatchName("VIEWA0", PU_HUDGFX); + kp_fpview[1] = W_CachePatchName("VIEWB0D0", PU_HUDGFX); + kp_fpview[2] = W_CachePatchName("VIEWC0E0", PU_HUDGFX); + + // Input UI Wheel + sprintf(buffer, "K_WHEELx"); + for (i = 0; i < 5; i++) + { + buffer[7] = '0'+i; + kp_inputwheel[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + // HERE COMES A NEW CHALLENGER + sprintf(buffer, "K_CHALxx"); + for (i = 0; i < 25; i++) + { + buffer[6] = '0'+((i+1)/10); + buffer[7] = '0'+((i+1)%10); + kp_challenger[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + // Lap start animation + sprintf(buffer, "K_LAP0x"); + for (i = 0; i < 7; i++) + { + buffer[6] = '0'+(i+1); + kp_lapanim_lap[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + sprintf(buffer, "K_LAPFxx"); + for (i = 0; i < 11; i++) + { + buffer[6] = '0'+((i+1)/10); + buffer[7] = '0'+((i+1)%10); + kp_lapanim_final[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + sprintf(buffer, "K_LAPNxx"); + for (i = 0; i < 10; i++) + { + buffer[6] = '0'+i; + for (j = 0; j < 3; j++) + { + buffer[7] = '0'+(j+1); + kp_lapanim_number[i][j] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + } + + sprintf(buffer, "K_LAPE0x"); + for (i = 0; i < 2; i++) + { + buffer[7] = '0'+(i+1); + kp_lapanim_emblem[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + sprintf(buffer, "K_LAPH0x"); + for (i = 0; i < 3; i++) + { + buffer[7] = '0'+(i+1); + kp_lapanim_hand[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + kp_yougotem = (patch_t *) W_CachePatchName("YOUGOTEM", PU_HUDGFX); - kp_itemminimap = (patch_t *) W_CachePatchName("MMAPITEM", PU_HUDGFX); - - sprintf(buffer, "ALAGLESx"); - for (i = 0; i < 10; ++i) - { - buffer[7] = '0'+i; - kp_alagles[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - sprintf(buffer, "BLAGLESx"); - for (i = 0; i < 6; ++i) - { - buffer[7] = '0'+i; - kp_blagles[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } + kp_itemminimap = (patch_t *) W_CachePatchName("MMAPITEM", PU_HUDGFX); + + sprintf(buffer, "ALAGLESx"); + for (i = 0; i < 10; ++i) + { + buffer[7] = '0'+i; + kp_alagles[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + sprintf(buffer, "BLAGLESx"); + for (i = 0; i < 6; ++i) + { + buffer[7] = '0'+i; + kp_blagles[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } +} + +// For the item toggle menu +const char *K_GetItemPatch(UINT8 item, boolean tiny) +{ + switch (item) + { + case KITEM_SNEAKER: + case KRITEM_TRIPLESNEAKER: + 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: + case KRITEM_TRIPLEBANANA: + case KRITEM_TENFOLDBANANA: + 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: + case KRITEM_DUALJAWZ: + 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_SUPERRING: + return (tiny ? "K_ISRING" : "K_ITRING"); + case KITEM_KITCHENSINK: + return (tiny ? "K_ISSINK" : "K_ITSINK"); + case KRITEM_TRIPLEORBINAUT: + return (tiny ? "K_ISORBN" : "K_ITORB3"); + case KRITEM_QUADORBINAUT: + return (tiny ? "K_ISORBN" : "K_ITORB4"); + default: + return (tiny ? "K_ISSAD" : "K_ITSAD"); + } +} + +//} + +INT32 ITEM_X, ITEM_Y; // Item Window +INT32 TIME_X, TIME_Y; // Time Sticker +INT32 LAPS_X, LAPS_Y; // Lap Sticker +INT32 POSI_X, POSI_Y; // Position Number +INT32 FACE_X, FACE_Y; // Top-four Faces +INT32 STCD_X, STCD_Y; // Starting countdown +INT32 CHEK_Y; // CHECK graphic +INT32 MINI_X, MINI_Y; // Minimap +INT32 WANT_X, WANT_Y; // Battle WANTED poster + +// This is for the P2 and P4 side of splitscreen. Then we'll flip P1's and P2's to the bottom with V_SPLITSCREEN. +INT32 ITEM2_X, ITEM2_Y; +INT32 LAPS2_X, LAPS2_Y; +INT32 POSI2_X, POSI2_Y; + + +static void K_initKartHUD(void) +{ + /* + BASEVIDWIDTH = 320 + BASEVIDHEIGHT = 200 + + Item window graphic is 41 x 33 + + Time Sticker graphic is 116 x 11 + Time Font is a solid block of (8 x [12) x 14], equal to 96 x 14 + Therefore, timestamp is 116 x 14 altogether + + Lap Sticker is 80 x 11 + Lap flag is 22 x 20 + Lap Font is a solid block of (3 x [12) x 14], equal to 36 x 14 + Therefore, lapstamp is 80 x 20 altogether + + Position numbers are 43 x 53 + + Faces are 32 x 32 + Faces draw downscaled at 16 x 16 + Therefore, the allocated space for them is 16 x 67 altogether + + ---- + + ORIGINAL CZ64 SPLITSCREEN: + + Item window: + if (!splitscreen) { ICONX = 139; ICONY = 20; } + else { ICONX = BASEVIDWIDTH-315; ICONY = 60; } + + Time: 236, STRINGY( 12) + Lap: BASEVIDWIDTH-304, STRINGY(BASEVIDHEIGHT-189) + + */ + + // Single Screen (defaults) + // Item Window + ITEM_X = 5; // 5 + ITEM_Y = 5; // 5 + // Level Timer + TIME_X = BASEVIDWIDTH - 148; // 172 + TIME_Y = 9; // 9 + // Level Laps + LAPS_X = 9; // 9 + LAPS_Y = BASEVIDHEIGHT - 29; // 171 + // Position Number + POSI_X = BASEVIDWIDTH - 9; // 268 + POSI_Y = BASEVIDHEIGHT - 9; // 138 + // Top-Four Faces + FACE_X = 9; // 9 + FACE_Y = 92; // 92 + // Starting countdown + STCD_X = BASEVIDWIDTH/2; // 9 + STCD_Y = BASEVIDHEIGHT/2; // 92 + // CHECK graphic + CHEK_Y = BASEVIDHEIGHT; // 200 + // Minimap + MINI_X = BASEVIDWIDTH - 50; // 270 + MINI_Y = (BASEVIDHEIGHT/2)-16; // 84 + // Battle WANTED poster + WANT_X = BASEVIDWIDTH - 55; // 270 + WANT_Y = BASEVIDHEIGHT- 71; // 176 + + if (splitscreen) // Splitscreen + { + ITEM_X = 5; + ITEM_Y = 3; + + LAPS_Y = (BASEVIDHEIGHT/2)-24; + + POSI_Y = (BASEVIDHEIGHT/2)- 2; + + STCD_Y = BASEVIDHEIGHT/4; + + MINI_Y = (BASEVIDHEIGHT/2); + + if (splitscreen > 1) // 3P/4P Small Splitscreen + { + // 1P (top left) + ITEM_X = -9; + ITEM_Y = -8; + + LAPS_X = 3; + LAPS_Y = (BASEVIDHEIGHT/2)-12; + + POSI_X = 24; + POSI_Y = (BASEVIDHEIGHT/2)-26; + + // 2P (top right) + ITEM2_X = BASEVIDWIDTH-39; + ITEM2_Y = -8; + + LAPS2_X = BASEVIDWIDTH-43; + LAPS2_Y = (BASEVIDHEIGHT/2)-12; + + POSI2_X = BASEVIDWIDTH -4; + POSI2_Y = (BASEVIDHEIGHT/2)-26; + + // Reminder that 3P and 4P are just 1P and 2P splitscreen'd to the bottom. + + STCD_X = BASEVIDWIDTH/4; + + MINI_X = (3*BASEVIDWIDTH/4); + MINI_Y = (3*BASEVIDHEIGHT/4); + + if (splitscreen > 2) // 4P-only + { + MINI_X = (BASEVIDWIDTH/2); + MINI_Y = (BASEVIDHEIGHT/2); + } + } + } + + if (timeinmap > 113) + hudtrans = cv_translucenthud.value; + else if (timeinmap > 105) + hudtrans = ((((INT32)timeinmap) - 105)*cv_translucenthud.value)/(113-105); + else + hudtrans = 0; +} + +INT32 K_calcSplitFlags(INT32 snapflags) +{ + INT32 splitflags = 0; + + if (splitscreen == 0) + return snapflags; + + if (stplyr != &players[displayplayers[0]]) + { + if (splitscreen == 1 && stplyr == &players[displayplayers[1]]) + { + splitflags |= V_SPLITSCREEN; + } + else if (splitscreen > 1) + { + if (stplyr == &players[displayplayers[2]] || (splitscreen == 3 && stplyr == &players[displayplayers[3]])) + splitflags |= V_SPLITSCREEN; + if (stplyr == &players[displayplayers[1]] || (splitscreen == 3 && stplyr == &players[displayplayers[3]])) + splitflags |= V_HORZSCREEN; + } + } + + if (splitflags & V_SPLITSCREEN) + snapflags &= ~V_SNAPTOTOP; + else + snapflags &= ~V_SNAPTOBOTTOM; + + if (splitscreen > 1) + { + if (splitflags & V_HORZSCREEN) + snapflags &= ~V_SNAPTOLEFT; + else + snapflags &= ~V_SNAPTORIGHT; + } + + return (splitflags|snapflags); +} + +static void K_drawKartItem(void) +{ + // ITEM_X = BASEVIDWIDTH-50; // 270 + // ITEM_Y = 24; // 24 + + // Why write V_DrawScaledPatch calls over and over when they're all the same? + // Set to 'no item' just in case. + const UINT8 offset = ((splitscreen > 1) ? 1 : 0); + patch_t *localpatch = kp_nodraw; + patch_t *localbg = ((offset) ? kp_itembg[2] : kp_itembg[0]); + patch_t *localinv = ((offset) ? kp_invincibility[((leveltime % (6*3)) / 3) + 7] : kp_invincibility[(leveltime % (7*3)) / 3]); + INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags... + //INT32 splitflags = K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOLEFT); + const INT32 numberdisplaymin = ((!offset && stplyr->kartstuff[k_itemtype] == KITEM_ORBINAUT) ? 5 : 2); + INT32 itembar = 0; + INT32 maxl = 0; // itembar's normal highest value + const INT32 barlength = (splitscreen > 1 ? 12 : 26); + UINT8 localcolor = SKINCOLOR_NONE; + SINT8 colormode = TC_RAINBOW; + UINT8 *colmap = NULL; + boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff + + if (stplyr->kartstuff[k_itemroulette]) + { + if (stplyr->skincolor) + localcolor = stplyr->skincolor; + + switch((stplyr->kartstuff[k_itemroulette] % (15*3)) / 3) + { + // Each case is handled in threes, to give three frames of in-game time to see the item on the roulette + case 0: // Sneaker + localpatch = kp_sneaker[offset]; + //localcolor = SKINCOLOR_RASPBERRY; + break; + case 1: // Banana + localpatch = kp_banana[offset]; + //localcolor = SKINCOLOR_YELLOW; + break; + case 2: // Orbinaut + localpatch = kp_orbinaut[3+offset]; + //localcolor = SKINCOLOR_STEEL; + break; + case 3: // Mine + localpatch = kp_mine[offset]; + //localcolor = SKINCOLOR_JET; + break; + case 4: // Grow + localpatch = kp_grow[offset]; + //localcolor = SKINCOLOR_TEAL; + break; + case 5: // Hyudoro + localpatch = kp_hyudoro[offset]; + //localcolor = SKINCOLOR_STEEL; + break; + case 6: // Rocket Sneaker + localpatch = kp_rocketsneaker[offset]; + //localcolor = SKINCOLOR_TANGERINE; + break; + case 7: // Jawz + localpatch = kp_jawz[offset]; + //localcolor = SKINCOLOR_JAWZ; + break; + case 8: // Self-Propelled Bomb + localpatch = kp_selfpropelledbomb[offset]; + //localcolor = SKINCOLOR_JET; + break; + case 9: // Shrink + localpatch = kp_shrink[offset]; + //localcolor = SKINCOLOR_ORANGE; + break; + case 10: // Invincibility + localpatch = localinv; + //localcolor = SKINCOLOR_GREY; + break; + case 11: // Eggman Monitor + localpatch = kp_eggman[offset]; + //localcolor = SKINCOLOR_ROSE; + break; + case 12: // Ballhog + localpatch = kp_ballhog[offset]; + //localcolor = SKINCOLOR_LILAC; + break; + case 13: // Thunder Shield + localpatch = kp_thundershield[offset]; + //localcolor = SKINCOLOR_CYAN; + break; + case 14: // Super Ring + localpatch = kp_superring[offset]; + //localcolor = SKINCOLOR_GOLD; + break; + /*case 15: // Pogo Spring + localpatch = kp_pogospring[offset]; + localcolor = SKINCOLOR_TANGERINE; + break; + case 16: // Kitchen Sink + localpatch = kp_kitchensink[offset]; + localcolor = SKINCOLOR_STEEL; + break;*/ + default: + break; + } + } + else + { + // I'm doing this a little weird and drawing mostly in reverse order + // The only actual reason is to make sneakers line up this way in the code below + // This shouldn't have any actual baring over how it functions + // Hyudoro is first, because we're drawing it on top of the player's current item + if (stplyr->kartstuff[k_stolentimer] > 0) + { + if (leveltime & 2) + localpatch = kp_hyudoro[offset]; + else + localpatch = kp_nodraw; + } + else if ((stplyr->kartstuff[k_stealingtimer] > 0) && (leveltime & 2)) + { + localpatch = kp_hyudoro[offset]; + } + else if (stplyr->kartstuff[k_eggmanexplode] > 1) + { + if (leveltime & 1) + localpatch = kp_eggman[offset]; + else + localpatch = kp_nodraw; + } + else if (stplyr->kartstuff[k_rocketsneakertimer] > 1) + { + itembar = stplyr->kartstuff[k_rocketsneakertimer]; + maxl = (itemtime*3) - barlength; + + if (leveltime & 1) + localpatch = kp_rocketsneaker[offset]; + else + localpatch = kp_nodraw; + } + else if (stplyr->kartstuff[k_growshrinktimer] > 0) + { + if (stplyr->kartstuff[k_growcancel] > 0) + { + itembar = stplyr->kartstuff[k_growcancel]; + maxl = 26; + } + + if (leveltime & 1) + localpatch = kp_grow[offset]; + else + localpatch = kp_nodraw; + } + else if (stplyr->kartstuff[k_sadtimer] > 0) + { + if (leveltime & 2) + localpatch = kp_sadface[offset]; + else + localpatch = kp_nodraw; + } + else + { + if (stplyr->kartstuff[k_itemamount] <= 0) + return; + + switch(stplyr->kartstuff[k_itemtype]) + { + case KITEM_SNEAKER: + localpatch = kp_sneaker[offset]; + break; + case KITEM_ROCKETSNEAKER: + localpatch = kp_rocketsneaker[offset]; + break; + case KITEM_INVINCIBILITY: + localpatch = localinv; + localbg = kp_itembg[offset+1]; + break; + case KITEM_BANANA: + localpatch = kp_banana[offset]; + break; + case KITEM_EGGMAN: + localpatch = kp_eggman[offset]; + break; + case KITEM_ORBINAUT: + localpatch = kp_orbinaut[(offset ? 4 : min(stplyr->kartstuff[k_itemamount]-1, 3))]; + break; + case KITEM_JAWZ: + localpatch = kp_jawz[offset]; + break; + case KITEM_MINE: + localpatch = kp_mine[offset]; + break; + case KITEM_BALLHOG: + localpatch = kp_ballhog[offset]; + break; + case KITEM_SPB: + localpatch = kp_selfpropelledbomb[offset]; + localbg = kp_itembg[offset+1]; + break; + case KITEM_GROW: + localpatch = kp_grow[offset]; + break; + case KITEM_SHRINK: + localpatch = kp_shrink[offset]; + break; + case KITEM_THUNDERSHIELD: + localpatch = kp_thundershield[offset]; + localbg = kp_itembg[offset+1]; + break; + case KITEM_HYUDORO: + localpatch = kp_hyudoro[offset]; + break; + case KITEM_POGOSPRING: + localpatch = kp_pogospring[offset]; + break; + case KITEM_SUPERRING: + localpatch = kp_superring[offset]; + break; + case KITEM_KITCHENSINK: + localpatch = kp_kitchensink[offset]; + break; + case KITEM_SAD: + localpatch = kp_sadface[offset]; + break; + default: + return; + } + + if (stplyr->kartstuff[k_itemheld] && !(leveltime & 1)) + localpatch = kp_nodraw; + } + + if (stplyr->karthud[khud_itemblink] && (leveltime & 1)) + { + colormode = TC_BLINK; + + switch (stplyr->karthud[khud_itemblinkmode]) + { + case 2: + localcolor = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))); + break; + case 1: + localcolor = SKINCOLOR_RED; + break; + default: + localcolor = SKINCOLOR_WHITE; + break; + } + } + } + + // pain and suffering defined below + if (splitscreen < 2) // don't change shit for THIS splitscreen. + { + fx = ITEM_X; + fy = ITEM_Y; + fflags = K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOLEFT); + } + else // now we're having a fun game. + { + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... + { + fx = ITEM_X; + fy = ITEM_Y; + fflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN : V_SNAPTOTOP); // flip P3 to the bottom. + } + else // else, that means we're P2 or P4. + { + fx = ITEM2_X; + fy = ITEM2_Y; + fflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN : V_SNAPTOTOP); // flip P4 to the bottom + flipamount = true; + } + } + + if (localcolor != SKINCOLOR_NONE) + colmap = R_GetTranslationColormap(colormode, localcolor, GTC_CACHE); + + V_DrawScaledPatch(fx, fy, V_HUDTRANS|fflags, localbg); + + // Then, the numbers: + if (stplyr->kartstuff[k_itemamount] >= numberdisplaymin && !stplyr->kartstuff[k_itemroulette]) + { + V_DrawScaledPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), kp_itemmulsticker[offset]); // flip this graphic for p2 and p4 in split and shift it. + V_DrawFixedPatch(fx<kartstuff[k_itemamount])); + else + V_DrawString(fx+24, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|fflags, va("x%d", stplyr->kartstuff[k_itemamount])); + else + { + V_DrawScaledPatch(fy+28, fy+41, V_HUDTRANS|fflags, kp_itemx); + V_DrawKartString(fx+38, fy+36, V_HUDTRANS|fflags, va("%d", stplyr->kartstuff[k_itemamount])); + } + } + else + V_DrawFixedPatch(fx< 2) + { + V_DrawFill(fx+x+length, fy+y+1, 1, height, 12|fflags); // the right one + if (height == 2) + V_DrawFill(fx+x+2, fy+y+2, length-2, 1, 8|fflags); // the dulled underside + V_DrawFill(fx+x+2, fy+y+1, length-2, 1, 0|fflags); // the shine + } + } + + // Quick Eggman numbers + if (stplyr->kartstuff[k_eggmanexplode] > 1 /*&& stplyr->kartstuff[k_eggmanexplode] <= 3*TICRATE*/) + V_DrawScaledPatch(fx+17, fy+13-offset, V_HUDTRANS|fflags, kp_eggnum[min(3, G_TicsToSeconds(stplyr->kartstuff[k_eggmanexplode]))]); +} + +void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode) +{ + // TIME_X = BASEVIDWIDTH-124; // 196 + // TIME_Y = 6; // 6 + + tic_t worktime; + + INT32 splitflags = 0; + if (!mode) + { + splitflags = V_HUDTRANS|K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTORIGHT); + if (cv_timelimit.value && timelimitintics > 0) + { + if (drawtime >= timelimitintics) + drawtime = 0; + else + drawtime = timelimitintics - drawtime; + } + } + + V_DrawScaledPatch(TX, TY, splitflags, ((mode == 2) ? kp_lapstickerwide : kp_timestickerwide)); + + TX += 33; + + worktime = drawtime/(60*TICRATE); + + if (mode && !drawtime) + V_DrawKartString(TX, TY+3, splitflags, va("--'--\"--")); + else if (worktime < 100) // 99:99:99 only + { + // zero minute + if (worktime < 10) + { + V_DrawKartString(TX, TY+3, splitflags, va("0")); + // minutes time 0 __ __ + V_DrawKartString(TX+12, TY+3, splitflags, va("%d", worktime)); + } + // minutes time 0 __ __ + else + V_DrawKartString(TX, TY+3, splitflags, va("%d", worktime)); + + // apostrophe location _'__ __ + V_DrawKartString(TX+24, TY+3, splitflags, va("'")); + + worktime = (drawtime/TICRATE % 60); + + // zero second _ 0_ __ + if (worktime < 10) + { + V_DrawKartString(TX+36, TY+3, splitflags, va("0")); + // seconds time _ _0 __ + V_DrawKartString(TX+48, TY+3, splitflags, va("%d", worktime)); + } + // zero second _ 00 __ + else + V_DrawKartString(TX+36, TY+3, splitflags, va("%d", worktime)); + + // quotation mark location _ __"__ + V_DrawKartString(TX+60, TY+3, splitflags, va("\"")); + + worktime = G_TicsToCentiseconds(drawtime); + + // zero tick _ __ 0_ + if (worktime < 10) + { + V_DrawKartString(TX+72, TY+3, splitflags, va("0")); + // tics _ __ _0 + V_DrawKartString(TX+84, TY+3, splitflags, va("%d", worktime)); + } + // zero tick _ __ 00 + else + V_DrawKartString(TX+72, TY+3, splitflags, va("%d", worktime)); + } + else if ((drawtime/TICRATE) & 1) + V_DrawKartString(TX, TY+3, splitflags, va("99'59\"99")); + + if (emblemmap && (modeattacking || (mode == 1)) && !demo.playback) // emblem time! + { + INT32 workx = TX + 96, worky = TY+18; + SINT8 curemb = 0; + patch_t *emblempic[3] = {NULL, NULL, NULL}; + UINT8 *emblemcol[3] = {NULL, NULL, NULL}; + + emblem_t *emblem = M_GetLevelEmblems(emblemmap); + while (emblem) + { + char targettext[9]; + + switch (emblem->type) + { + case ET_TIME: + { + static boolean canplaysound = true; + tic_t timetoreach = emblem->var; + + if (emblem->collected) + { + emblempic[curemb] = W_CachePatchName(M_GetEmblemPatch(emblem), PU_CACHE); + emblemcol[curemb] = R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE); + if (++curemb == 3) + break; + goto bademblem; + } + + snprintf(targettext, 9, "%i'%02i\"%02i", + G_TicsToMinutes(timetoreach, false), + G_TicsToSeconds(timetoreach), + G_TicsToCentiseconds(timetoreach)); + + if (!mode) + { + if (stplyr->realtime > timetoreach) + { + splitflags = (splitflags &~ V_HUDTRANS)|V_HUDTRANSHALF; + if (canplaysound) + { + S_StartSound(NULL, sfx_s3k72); //sfx_s26d); -- you STOLE fizzy lifting drinks + canplaysound = false; + } + } + else if (!canplaysound) + canplaysound = true; + } + + targettext[8] = 0; + } + break; + default: + goto bademblem; + } + + V_DrawRightAlignedString(workx, worky, splitflags, targettext); + workx -= 67; + V_DrawSmallScaledPatch(workx + 4, worky, splitflags, W_CachePatchName("NEEDIT", PU_CACHE)); + + break; + + bademblem: + emblem = M_GetLevelEmblems(-1); + } + + if (!mode) + splitflags = (splitflags &~ V_HUDTRANSHALF)|V_HUDTRANS; + while (curemb--) + { + workx -= 12; + V_DrawSmallMappedPatch(workx + 4, worky, splitflags, emblempic[curemb], emblemcol[curemb]); + } + } +} + +static void K_DrawKartPositionNum(INT32 num) +{ + // POSI_X = BASEVIDWIDTH - 51; // 269 + // POSI_Y = BASEVIDHEIGHT- 64; // 136 + + boolean win = (stplyr->exiting && num == 1); + //INT32 X = POSI_X; + INT32 W = SHORT(kp_positionnum[0][0]->width); + fixed_t scale = FRACUNIT; + patch_t *localpatch = kp_positionnum[0][0]; + //INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTORIGHT); + INT32 fx = 0, fy = 0, fflags = 0; + boolean flipdraw = false; // flip the order we draw it in for MORE splitscreen bs. fun. + boolean flipvdraw = false; // used only for 2p splitscreen so overtaking doesn't make 1P's position fly off the screen. + boolean overtake = false; + + if (stplyr->kartstuff[k_positiondelay] || stplyr->exiting) + { + scale *= 2; + overtake = true; // this is used for splitscreen stuff in conjunction with flipdraw. + } + if (splitscreen) + scale /= 2; + + W = FixedMul(W<>FRACBITS; + + // pain and suffering defined below + if (!splitscreen) + { + fx = POSI_X; + fy = BASEVIDHEIGHT - 8; + fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT; + } + else if (splitscreen == 1) // for this splitscreen, we'll use case by case because it's a bit different. + { + fx = POSI_X; + if (stplyr == &players[displayplayers[0]]) // for player 1: display this at the top right, above the minimap. + { + fy = 30; + fflags = V_SNAPTOTOP|V_SNAPTORIGHT; + if (overtake) + flipvdraw = true; // make sure overtaking doesn't explode us + } + else // if we're not p1, that means we're p2. display this at the bottom right, below the minimap. + { + fy = BASEVIDHEIGHT - 8; + fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT; + } + } + else + { + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... + { + fx = POSI_X; + fy = POSI_Y; + fflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. + flipdraw = true; + if (num && num >= 10) + fx += W; // this seems dumb, but we need to do this in order for positions above 10 going off screen. + } + else // else, that means we're P2 or P4. + { + fx = POSI2_X; + fy = POSI2_Y; + fflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom + } + } + + // Special case for 0 + if (!num) + { + V_DrawFixedPatch(fx<= 0); // This function does not draw negative numbers + + // Draw the number + while (num) + { + if (win) // 1st place winner? You get rainbows!! + localpatch = kp_winnernum[(leveltime % (NUMWINFRAMES*3)) / 3]; + else if (stplyr->laps >= cv_numlaps.value || stplyr->exiting) // Check for the final lap, or won + { + // Alternate frame every three frames + switch (leveltime % 9) + { + case 1: case 2: case 3: + if (K_IsPlayerLosing(stplyr)) + localpatch = kp_positionnum[num % 10][4]; + else + localpatch = kp_positionnum[num % 10][1]; + break; + case 4: case 5: case 6: + if (K_IsPlayerLosing(stplyr)) + localpatch = kp_positionnum[num % 10][5]; + else + localpatch = kp_positionnum[num % 10][2]; + break; + case 7: case 8: case 9: + if (K_IsPlayerLosing(stplyr)) + localpatch = kp_positionnum[num % 10][6]; + else + localpatch = kp_positionnum[num % 10][3]; + break; + default: + localpatch = kp_positionnum[num % 10][0]; + break; + } + } + else + localpatch = kp_positionnum[num % 10][0]; + + V_DrawFixedPatch((fx<width)*scale/2) : 0), (fy<height)*scale/2) : 0), scale, V_HUDTRANSHALF|fflags, localpatch, NULL); + // ^ if we overtake as p1 or p3 in splitscren, we shift it so that it doesn't go off screen. + // ^ if we overtake as p1 in 2p splits, shift vertically so that this doesn't happen either. + + fx -= W; + num /= 10; + } +} + +static boolean K_drawKartPositionFaces(void) +{ + // FACE_X = 15; // 15 + // FACE_Y = 72; // 72 + + INT32 Y = FACE_Y+9; // +9 to offset where it's being drawn if there are more than one + INT32 i, j, ranklines, strank = -1; + boolean completed[MAXPLAYERS]; + INT32 rankplayer[MAXPLAYERS]; + INT32 bumperx, numplayersingame = 0; + UINT8 *colormap; + + ranklines = 0; + memset(completed, 0, sizeof (completed)); + memset(rankplayer, 0, sizeof (rankplayer)); + + for (i = 0; i < MAXPLAYERS; i++) + { + rankplayer[i] = -1; + + if (!playeringame[i] || players[i].spectator || !players[i].mo) + continue; + + numplayersingame++; + } + + if (numplayersingame <= 1) + return true; + +#ifdef HAVE_BLUA + if (!LUA_HudEnabled(hud_minirankings)) + return false; // Don't proceed but still return true for free play above if HUD is disabled. +#endif + + for (j = 0; j < numplayersingame; j++) + { + UINT8 lowestposition = MAXPLAYERS+1; + for (i = 0; i < MAXPLAYERS; i++) + { + if (completed[i] || !playeringame[i] || players[i].spectator || !players[i].mo) + continue; + + if (players[i].kartstuff[k_position] >= lowestposition) + continue; + + rankplayer[ranklines] = i; + lowestposition = players[i].kartstuff[k_position]; + } + + i = rankplayer[ranklines]; + + completed[i] = true; + + if (players+i == stplyr) + strank = ranklines; + + //if (ranklines == 5) + //break; // Only draw the top 5 players -- we do this a different way now... + + ranklines++; + } + + if (ranklines < 5) + Y -= (9*ranklines); + else + Y -= (9*5); + + if (G_BattleGametype() || strank <= 2) // too close to the top, or playing battle, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2) + { + i = 0; + if (ranklines > 5) // could be both... + ranklines = 5; + } + else if (strank+3 > ranklines) // too close to the bottom? + { + i = ranklines - 5; + if (i < 0) + i = 0; + } + else + { + i = strank-2; + ranklines = strank+3; + } + + for (; i < ranklines; i++) + { + if (!playeringame[rankplayer[i]]) continue; + if (players[rankplayer[i]].spectator) continue; + if (!players[rankplayer[i]].mo) continue; + + bumperx = FACE_X+19; + + if (players[rankplayer[i]].mo->color) + { + colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE); + if (players[rankplayer[i]].mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, players[rankplayer[i]].mo->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE); + + V_DrawMappedPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, facerankprefix[players[rankplayer[i]].skin], colormap); + +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_battlebumpers)) + { +#endif + if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] > 0) + { + V_DrawMappedPatch(bumperx-2, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[0], colormap); + for (j = 1; j < players[rankplayer[i]].kartstuff[k_bumper]; j++) + { + bumperx += 5; + V_DrawMappedPatch(bumperx, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[1], colormap); + } + } +#ifdef HAVE_BLUA + } // A new level of stupidity: checking if lua is enabled to close a bracket. :Fascinating: +#endif + } + + if (i == strank) + V_DrawScaledPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_facehighlight[(leveltime / 4) % 8]); + + if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] <= 0) + V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SNAPTOLEFT, kp_ranknobumpers); + else + { + INT32 pos = players[rankplayer[i]].kartstuff[k_position]; + if (pos < 0 || pos > MAXPLAYERS) + pos = 0; + // Draws the little number over the face + V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SNAPTOLEFT, kp_facenum[pos]); + } + + Y += 18; + } + + return false; +} + +// +// HU_DrawTabRankings -- moved here to take advantage of kart stuff! +// +void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol) +{ + static tic_t alagles_timer = 9; + INT32 i, rightoffset = 240; + const UINT8 *colormap; + INT32 dupadjust = (vid.width/vid.dupx), duptweak = (dupadjust - BASEVIDWIDTH)/2; + int y2; + + //this function is designed for 9 or less score lines only + //I_Assert(scorelines <= 9); -- not today bitch, kart fixed it up + + V_DrawFill(1-duptweak, 26, dupadjust-2, 1, 0); // Draw a horizontal line because it looks nice! + if (scorelines > 8) + { + V_DrawFill(160, 26, 1, 147, 0); // Draw a vertical line to separate the two sides. + V_DrawFill(1-duptweak, 173, dupadjust-2, 1, 0); // And a horizontal line near the bottom. + rightoffset = (BASEVIDWIDTH/2) - 4 - x; + } + + for (i = 0; i < scorelines; i++) + { + char strtime[MAXPLAYERNAME+1]; + + if (players[tab[i].num].spectator || !players[tab[i].num].mo) + continue; //ignore them. + + if (netgame // don't draw it offline + && ( tab[i].num != serverplayer || ! server_lagless )) + HU_drawPing(x + ((i < 8) ? -17 : rightoffset + 11), y-4, playerpingtable[tab[i].num], 0); + + STRBUFCPY(strtime, tab[i].name); + + y2 = y; + + if (tab[i].num == 0 && server_lagless) + { + y2 = ( y - 4 ); + + V_DrawScaledPatch(x + 20, y2, 0, kp_blagles[(leveltime / 3) % 6]); + // every 70 tics + if (( leveltime % 70 ) == 0) + { + alagles_timer = 9; + } + if (alagles_timer > 0) + { + V_DrawScaledPatch(x + 20, y2, 0, kp_alagles[alagles_timer]); + if (( leveltime % 2 ) == 0) + alagles_timer--; + } + else + V_DrawScaledPatch(x + 20, y2, 0, kp_alagles[0]); + + y2 += SHORT (kp_alagles[0]->height) + 1; + } + + if (scorelines > 8) + V_DrawThinString(x + 20, y2, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, strtime); + else + V_DrawString(x + 20, y2, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE, strtime); + + if (players[tab[i].num].mo->color) + { + colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE); + if (players[tab[i].num].mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, players[tab[i].num].mo->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE); + + V_DrawMappedPatch(x, y-4, 0, facerankprefix[players[tab[i].num].skin], colormap); + /*if (G_BattleGametype() && players[tab[i].num].kartstuff[k_bumper] > 0) -- not enough space for this + { + INT32 bumperx = x+19; + V_DrawMappedPatch(bumperx-2, y-4, 0, kp_tinybumper[0], colormap); + for (j = 1; j < players[tab[i].num].kartstuff[k_bumper]; j++) + { + bumperx += 5; + V_DrawMappedPatch(bumperx, y-4, 0, kp_tinybumper[1], colormap); + } + }*/ + } + + if (tab[i].num == whiteplayer) + V_DrawScaledPatch(x, y-4, 0, kp_facehighlight[(leveltime / 4) % 8]); + + if (G_BattleGametype() && players[tab[i].num].kartstuff[k_bumper] <= 0) + V_DrawScaledPatch(x-4, y-7, 0, kp_ranknobumpers); + else + { + INT32 pos = players[tab[i].num].kartstuff[k_position]; + if (pos < 0 || pos > MAXPLAYERS) + pos = 0; + // Draws the little number over the face + V_DrawScaledPatch(x-5, y+6, 0, kp_facenum[pos]); + } + + if (G_RaceGametype()) + { +#define timestring(time) va("%i'%02i\"%02i", G_TicsToMinutes(time, true), G_TicsToSeconds(time), G_TicsToCentiseconds(time)) + if (scorelines > 8) + { + if (players[tab[i].num].exiting) + V_DrawRightAlignedThinString(x+rightoffset, y-1, hilicol|V_6WIDTHSPACE, timestring(players[tab[i].num].realtime)); + else if (players[tab[i].num].pflags & PF_TIMEOVER) + V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, "NO CONTEST."); + else if (circuitmap) + V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, va("Lap %d", tab[i].count)); + } + else + { + if (players[tab[i].num].exiting) + V_DrawRightAlignedString(x+rightoffset, y, hilicol, timestring(players[tab[i].num].realtime)); + else if (players[tab[i].num].pflags & PF_TIMEOVER) + V_DrawRightAlignedThinString(x+rightoffset, y-1, 0, "NO CONTEST."); + else if (circuitmap) + V_DrawRightAlignedString(x+rightoffset, y, 0, va("Lap %d", tab[i].count)); + } +#undef timestring + } + else + V_DrawRightAlignedString(x+rightoffset, y, 0, va("%u", tab[i].count)); + + y += 18; + if (i == 7) + { + y = 33; + x = (BASEVIDWIDTH/2) + 4; + } + } +} + +#define RINGANIM_FLIPFRAME (RINGANIM_NUMFRAMES/2) + +static void K_drawKartLapsAndRings(void) +{ + SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; + INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT); + UINT8 rn[2]; + INT32 ringflip = 0; + UINT8 *ringmap = NULL; + boolean colorring = false; + INT32 ringx = 0; + + rn[0] = ((abs(stplyr->kartstuff[k_rings]) / 10) % 10); + rn[1] = (abs(stplyr->kartstuff[k_rings]) % 10); + + if (stplyr->kartstuff[k_rings] <= 0 && (leveltime/5 & 1)) // In debt + { + ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); + colorring = true; + } + else if (stplyr->kartstuff[k_rings] >= 20) // Maxed out + ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); + + if (stplyr->karthud[khud_ringframe] > RINGANIM_FLIPFRAME) + { + ringflip = V_FLIP; + ringanim_realframe = RINGANIM_NUMFRAMES-stplyr->karthud[khud_ringframe]; + ringx += SHORT((splitscreen > 1) ? kp_smallring[ringanim_realframe]->width : kp_ring[ringanim_realframe]->width); + } + + if (splitscreen > 1) + { + INT32 fx = 0, fy = 0, fr = 0; + INT32 flipflag = 0; + + // pain and suffering defined below + if (splitscreen < 2) // don't change shit for THIS splitscreen. + { + fx = LAPS_X; + fy = LAPS_Y; + } + else + { + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... + { + fx = LAPS_X; + fy = LAPS_Y; + splitflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. + } + else // else, that means we're P2 or P4. + { + fx = LAPS2_X; + fy = LAPS2_Y; + splitflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom + flipflag = V_FLIP; // make the string right aligned and other shit + } + } + + fr = fx; + + // Laps + V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|splitflags|flipflag, kp_ringstickersplit[0]); + + V_DrawScaledPatch(fx, fy, V_HUDTRANS|splitflags, kp_splitlapflag); + V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|splitflags, frameslash); + + if (cv_numlaps.value >= 10) + { + UINT8 ln[2]; + ln[0] = ((abs(stplyr->laps) / 10) % 10); + ln[1] = (abs(stplyr->laps) % 10); + + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); + V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); + + ln[0] = ((abs(cv_numlaps.value) / 10) % 10); + ln[1] = (abs(cv_numlaps.value) % 10); + + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); + V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); + } + else + { + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->laps) % 10]); + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[(cv_numlaps.value) % 10]); + } + + // Rings + if (netgame) + { + V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy-10, V_HUDTRANS|splitflags|flipflag, kp_ringstickersplit[1]); + if (flipflag) + fr += 15; + } + else + V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[0]->width) - 3) : 0), fy-10, V_HUDTRANS|splitflags|flipflag, kp_ringstickersplit[0]); + + V_DrawMappedPatch(fr+ringx, fy-13, V_HUDTRANS|splitflags|ringflip, kp_smallring[ringanim_realframe], (colorring ? ringmap : NULL)); + + if (stplyr->kartstuff[k_rings] < 0) // Draw the minus for ring debt + V_DrawMappedPatch(fr+7, fy-10, V_HUDTRANS|splitflags, kp_ringdebtminussmall, ringmap); + + V_DrawMappedPatch(fr+11, fy-10, V_HUDTRANS|splitflags, pingnum[rn[0]], ringmap); + V_DrawMappedPatch(fr+15, fy-10, V_HUDTRANS|splitflags, pingnum[rn[1]], ringmap); + + // SPB ring lock + if (stplyr->kartstuff[k_ringlock]) + V_DrawScaledPatch(fr-12, fy-23, V_HUDTRANS|splitflags, kp_ringspblocksmall[stplyr->karthud[khud_ringspblock]]); + + // Lives + if (!netgame) + { + UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); + V_DrawMappedPatch(fr+21, fy-13, V_HUDTRANS|splitflags, facemmapprefix[stplyr->skin], colormap); + V_DrawScaledPatch(fr+34, fy-10, V_HUDTRANS|splitflags, pingnum[(stplyr->lives % 10)]); // make sure this doesn't overflow + } + } + else + { + // Laps + V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_lapsticker); + + if (stplyr->exiting) + V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, "FIN"); + else + V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->laps, cv_numlaps.value)); + + // Rings + if (netgame) + V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|splitflags, kp_ringsticker[1]); + else + V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|splitflags, kp_ringsticker[0]); + + V_DrawMappedPatch(LAPS_X+ringx+7, LAPS_Y-16, V_HUDTRANS|splitflags|ringflip, kp_ring[ringanim_realframe], (colorring ? ringmap : NULL)); + + if (stplyr->kartstuff[k_rings] < 0) // Draw the minus for ring debt + { + V_DrawMappedPatch(LAPS_X+23, LAPS_Y-11, V_HUDTRANS|splitflags, kp_ringdebtminus, ringmap); + V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|splitflags, kp_facenum[rn[0]], ringmap); + V_DrawMappedPatch(LAPS_X+35, LAPS_Y-11, V_HUDTRANS|splitflags, kp_facenum[rn[1]], ringmap); + } + else + { + V_DrawMappedPatch(LAPS_X+23, LAPS_Y-11, V_HUDTRANS|splitflags, kp_facenum[rn[0]], ringmap); + V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|splitflags, kp_facenum[rn[1]], ringmap); + } + + // SPB ring lock + if (stplyr->kartstuff[k_ringlock]) + V_DrawScaledPatch(LAPS_X-5, LAPS_Y-28, V_HUDTRANS|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); + + // Lives + if (!netgame) + { + UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); + V_DrawMappedPatch(LAPS_X+46, LAPS_Y-16, V_HUDTRANS|splitflags, facerankprefix[stplyr->skin], colormap); + V_DrawScaledPatch(LAPS_X+63, LAPS_Y-11, V_HUDTRANS|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow + } + } +} + +#undef RINGANIM_NUMFRAMES +#undef RINGANIM_FLIPFRAME + +static void K_drawKartSpeedometer(void) +{ + static fixed_t convSpeed; + UINT8 labeln = 0; + UINT8 numbers[3]; + INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT); + UINT8 battleoffset = 0; + + if (!stplyr->exiting) // Keep the same speed value as when you crossed the finish line! + { + switch (cv_kartspeedometer.value) + { + case 1: // Sonic Drift 2 style percentage + default: + convSpeed = (((25*stplyr->speed)/24) * 100) / K_GetKartSpeed(stplyr, false); // Based on top speed! (cheats with the numbers due to some weird discrepancy) + labeln = 0; + break; + case 2: // Kilometers + convSpeed = FixedDiv(FixedMul(stplyr->speed, 142371), mapobjectscale)/FRACUNIT; // 2.172409058 + labeln = 1; + break; + case 3: // Miles + convSpeed = FixedDiv(FixedMul(stplyr->speed, 88465), mapobjectscale)/FRACUNIT; // 1.349868774 + labeln = 2; + break; + case 4: // Fracunits + convSpeed = FixedDiv(stplyr->speed, mapobjectscale)/FRACUNIT; // 1.0. duh. + labeln = 3; + break; + } + } + + // Don't overflow + if (convSpeed > 999) + convSpeed = 999; + + numbers[0] = ((convSpeed / 100) % 10); + numbers[1] = ((convSpeed / 10) % 10); + numbers[2] = (convSpeed % 10); + + if (G_BattleGametype()) + battleoffset = 8; + + V_DrawScaledPatch(LAPS_X, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_speedometersticker); + V_DrawScaledPatch(LAPS_X+7, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[0]]); + V_DrawScaledPatch(LAPS_X+13, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[1]]); + V_DrawScaledPatch(LAPS_X+19, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[2]]); + V_DrawScaledPatch(LAPS_X+29, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_speedometerlabel[labeln]); +} + +static void K_drawKartBumpersOrKarma(void) +{ + UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); + INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT); + + if (splitscreen > 1) + { + INT32 fx = 0, fy = 0; + INT32 flipflag = 0; + + // pain and suffering defined below + if (splitscreen < 2) // don't change shit for THIS splitscreen. + { + fx = LAPS_X; + fy = LAPS_Y; + } + else + { + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... + { + fx = LAPS_X; + fy = LAPS_Y; + splitflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. + } + else // else, that means we're P2 or P4. + { + fx = LAPS2_X; + fy = LAPS2_Y; + splitflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom + flipflag = V_FLIP; // make the string right aligned and other shit + } + } + + V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|splitflags|flipflag, kp_ringstickersplit[0]); + V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|splitflags, frameslash); + + if (stplyr->kartstuff[k_bumper] <= 0) + { + V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|splitflags, kp_splitkarmabomb, colormap); + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->kartstuff[k_comebackpoints]) % 10]); + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[2]); + } + else + { + V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|splitflags, kp_rankbumper, colormap); + + if (stplyr->kartstuff[k_bumper] > 9 || cv_kartbumpers.value > 9) + { + UINT8 ln[2]; + ln[0] = ((abs(stplyr->kartstuff[k_bumper]) / 10) % 10); + ln[1] = (abs(stplyr->kartstuff[k_bumper]) % 10); + + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); + V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); + + ln[0] = ((abs(cv_kartbumpers.value) / 10) % 10); + ln[1] = (abs(cv_kartbumpers.value) % 10); + + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); + V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); + } + else + { + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->kartstuff[k_bumper]) % 10]); + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[(cv_kartbumpers.value) % 10]); + } + } + } + else + { + if (stplyr->kartstuff[k_bumper] <= 0) + { + V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_karmasticker, colormap); + V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/2", stplyr->kartstuff[k_comebackpoints])); + } + else + { + if (stplyr->kartstuff[k_bumper] > 9 && cv_kartbumpers.value > 9) + V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_bumperstickerwide, colormap); + else + V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_bumpersticker, colormap); + V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->kartstuff[k_bumper], cv_kartbumpers.value)); + } + } +} + +static fixed_t K_FindCheckX(fixed_t px, fixed_t py, angle_t ang, fixed_t mx, fixed_t my) +{ + fixed_t dist, x; + fixed_t range = RING_DIST/3; + angle_t diff; + + range *= gamespeed+1; + + dist = abs(R_PointToDist2(px, py, mx, my)); + if (dist > range) + return -320; + + diff = R_PointToAngle2(px, py, mx, my) - ang; + + if (diff < ANGLE_90 || diff > ANGLE_270) + return -320; + else + x = (FixedMul(FINETANGENT(((diff+ANGLE_90)>>ANGLETOFINESHIFT) & 4095), 160<>FRACBITS; + + if (encoremode) + x = 320-x; + + if (splitscreen > 1) + x /= 2; + + return x; +} + +static void K_drawKartWanted(void) +{ + UINT8 i, numwanted = 0; + UINT8 *colormap = NULL; + INT32 basex = 0, basey = 0; + + if (stplyr != &players[displayplayers[0]]) + return; + + for (i = 0; i < 4; i++) + { + if (battlewanted[i] == -1) + break; + numwanted++; + } + + if (numwanted <= 0) + return; + + // set X/Y coords depending on splitscreen. + if (splitscreen < 3) // 1P and 2P use the same code. + { + basex = WANT_X; + basey = WANT_Y; + if (splitscreen == 2) + { + basey += 16; // slight adjust for 3P + basex -= 6; + } + } + else if (splitscreen == 3) // 4P splitscreen... + { + basex = BASEVIDWIDTH/2 - (SHORT(kp_wantedsplit->width)/2); // center on screen + basey = BASEVIDHEIGHT - 55; + //basey2 = 4; + } + + if (battlewanted[0] != -1) + colormap = R_GetTranslationColormap(0, players[battlewanted[0]].skincolor, GTC_CACHE); + V_DrawFixedPatch(basex< 1 ? kp_wantedsplit : kp_wanted), colormap); + /*if (basey2) + V_DrawFixedPatch(basex< 1 ? 13 : 8), y = basey+(splitscreen > 1 ? 16 : 21); + fixed_t scale = FRACUNIT/2; + player_t *p = &players[battlewanted[i]]; + + if (battlewanted[i] == -1) + break; + + if (numwanted == 1) + scale = FRACUNIT; + else + { + if (i & 1) + x += 16; + if (i > 1) + y += 16; + } + + if (players[battlewanted[i]].skincolor) + { + colormap = R_GetTranslationColormap(TC_RAINBOW, p->skincolor, GTC_CACHE); + V_DrawFixedPatch(x<skin] : facerankprefix[p->skin]), colormap); + /*if (basey2) // again with 4p stuff + V_DrawFixedPatch(x<skin] : facerankprefix[p->skin]), colormap);*/ + } + } +} + +static void K_drawKartPlayerCheck(void) +{ + INT32 i; + UINT8 *colormap; + INT32 x; + + INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM); + + if (!stplyr->mo || stplyr->spectator) + return; + + if (stplyr->awayviewtics) + return; + + if (camspin[0]) + return; + + for (i = 0; i < MAXPLAYERS; i++) + { + UINT8 pnum = 0; + + if (&players[i] == stplyr) + continue; + if (!playeringame[i] || players[i].spectator) + continue; + if (!players[i].mo) + continue; + + if ((players[i].kartstuff[k_invincibilitytimer] <= 0) && (leveltime & 2)) + pnum++; // white frames + + if (players[i].kartstuff[k_itemtype] == KITEM_GROW || players[i].kartstuff[k_growshrinktimer] > 0) + pnum += 4; + else if (players[i].kartstuff[k_itemtype] == KITEM_INVINCIBILITY || players[i].kartstuff[k_invincibilitytimer]) + pnum += 2; + + x = K_FindCheckX(stplyr->mo->x, stplyr->mo->y, stplyr->mo->angle, players[i].mo->x, players[i].mo->y); + if (x <= 320 && x >= 0) + { + if (x < 14) + x = 14; + else if (x > 306) + x = 306; + + colormap = R_GetTranslationColormap(TC_DEFAULT, players[i].mo->color, GTC_CACHE); + V_DrawMappedPatch(x, CHEK_Y, V_HUDTRANS|splitflags, kp_check[pnum], colormap); + } + } } - -// For the item toggle menu -const char *K_GetItemPatch(UINT8 item, boolean tiny) -{ - switch (item) - { - case KITEM_SNEAKER: - case KRITEM_TRIPLESNEAKER: - 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: - case KRITEM_TRIPLEBANANA: - case KRITEM_TENFOLDBANANA: - 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: - case KRITEM_DUALJAWZ: - 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_SUPERRING: - return (tiny ? "K_ISRING" : "K_ITRING"); - case KITEM_KITCHENSINK: - return (tiny ? "K_ISSINK" : "K_ITSINK"); - case KRITEM_TRIPLEORBINAUT: - return (tiny ? "K_ISORBN" : "K_ITORB3"); - case KRITEM_QUADORBINAUT: - return (tiny ? "K_ISORBN" : "K_ITORB4"); - default: - return (tiny ? "K_ISSAD" : "K_ITSAD"); - } -} - -//} - -INT32 ITEM_X, ITEM_Y; // Item Window -INT32 TIME_X, TIME_Y; // Time Sticker -INT32 LAPS_X, LAPS_Y; // Lap Sticker -INT32 POSI_X, POSI_Y; // Position Number -INT32 FACE_X, FACE_Y; // Top-four Faces -INT32 STCD_X, STCD_Y; // Starting countdown -INT32 CHEK_Y; // CHECK graphic -INT32 MINI_X, MINI_Y; // Minimap -INT32 WANT_X, WANT_Y; // Battle WANTED poster - -// This is for the P2 and P4 side of splitscreen. Then we'll flip P1's and P2's to the bottom with V_SPLITSCREEN. -INT32 ITEM2_X, ITEM2_Y; -INT32 LAPS2_X, LAPS2_Y; -INT32 POSI2_X, POSI2_Y; - - -static void K_initKartHUD(void) -{ - /* - BASEVIDWIDTH = 320 - BASEVIDHEIGHT = 200 - - Item window graphic is 41 x 33 - - Time Sticker graphic is 116 x 11 - Time Font is a solid block of (8 x [12) x 14], equal to 96 x 14 - Therefore, timestamp is 116 x 14 altogether - - Lap Sticker is 80 x 11 - Lap flag is 22 x 20 - Lap Font is a solid block of (3 x [12) x 14], equal to 36 x 14 - Therefore, lapstamp is 80 x 20 altogether - - Position numbers are 43 x 53 - - Faces are 32 x 32 - Faces draw downscaled at 16 x 16 - Therefore, the allocated space for them is 16 x 67 altogether - - ---- - - ORIGINAL CZ64 SPLITSCREEN: - - Item window: - if (!splitscreen) { ICONX = 139; ICONY = 20; } - else { ICONX = BASEVIDWIDTH-315; ICONY = 60; } - - Time: 236, STRINGY( 12) - Lap: BASEVIDWIDTH-304, STRINGY(BASEVIDHEIGHT-189) - - */ - - // Single Screen (defaults) - // Item Window - ITEM_X = 5; // 5 - ITEM_Y = 5; // 5 - // Level Timer - TIME_X = BASEVIDWIDTH - 148; // 172 - TIME_Y = 9; // 9 - // Level Laps - LAPS_X = 9; // 9 - LAPS_Y = BASEVIDHEIGHT - 29; // 171 - // Position Number - POSI_X = BASEVIDWIDTH - 9; // 268 - POSI_Y = BASEVIDHEIGHT - 9; // 138 - // Top-Four Faces - FACE_X = 9; // 9 - FACE_Y = 92; // 92 - // Starting countdown - STCD_X = BASEVIDWIDTH/2; // 9 - STCD_Y = BASEVIDHEIGHT/2; // 92 - // CHECK graphic - CHEK_Y = BASEVIDHEIGHT; // 200 - // Minimap - MINI_X = BASEVIDWIDTH - 50; // 270 - MINI_Y = (BASEVIDHEIGHT/2)-16; // 84 - // Battle WANTED poster - WANT_X = BASEVIDWIDTH - 55; // 270 - WANT_Y = BASEVIDHEIGHT- 71; // 176 - - if (splitscreen) // Splitscreen - { - ITEM_X = 5; - ITEM_Y = 3; - - LAPS_Y = (BASEVIDHEIGHT/2)-24; - - POSI_Y = (BASEVIDHEIGHT/2)- 2; - - STCD_Y = BASEVIDHEIGHT/4; - - MINI_Y = (BASEVIDHEIGHT/2); - - if (splitscreen > 1) // 3P/4P Small Splitscreen - { - // 1P (top left) - ITEM_X = -9; - ITEM_Y = -8; - - LAPS_X = 3; - LAPS_Y = (BASEVIDHEIGHT/2)-12; - - POSI_X = 24; - POSI_Y = (BASEVIDHEIGHT/2)-26; - - // 2P (top right) - ITEM2_X = BASEVIDWIDTH-39; - ITEM2_Y = -8; - - LAPS2_X = BASEVIDWIDTH-43; - LAPS2_Y = (BASEVIDHEIGHT/2)-12; - - POSI2_X = BASEVIDWIDTH -4; - POSI2_Y = (BASEVIDHEIGHT/2)-26; - - // Reminder that 3P and 4P are just 1P and 2P splitscreen'd to the bottom. - - STCD_X = BASEVIDWIDTH/4; - - MINI_X = (3*BASEVIDWIDTH/4); - MINI_Y = (3*BASEVIDHEIGHT/4); - - if (splitscreen > 2) // 4P-only - { - MINI_X = (BASEVIDWIDTH/2); - MINI_Y = (BASEVIDHEIGHT/2); - } - } - } - - if (timeinmap > 113) - hudtrans = cv_translucenthud.value; - else if (timeinmap > 105) - hudtrans = ((((INT32)timeinmap) - 105)*cv_translucenthud.value)/(113-105); - else - hudtrans = 0; -} - -INT32 K_calcSplitFlags(INT32 snapflags) -{ - INT32 splitflags = 0; - - if (splitscreen == 0) - return snapflags; - - if (stplyr != &players[displayplayers[0]]) - { - if (splitscreen == 1 && stplyr == &players[displayplayers[1]]) - { - splitflags |= V_SPLITSCREEN; - } - else if (splitscreen > 1) - { - if (stplyr == &players[displayplayers[2]] || (splitscreen == 3 && stplyr == &players[displayplayers[3]])) - splitflags |= V_SPLITSCREEN; - if (stplyr == &players[displayplayers[1]] || (splitscreen == 3 && stplyr == &players[displayplayers[3]])) - splitflags |= V_HORZSCREEN; - } - } - - if (splitflags & V_SPLITSCREEN) - snapflags &= ~V_SNAPTOTOP; - else - snapflags &= ~V_SNAPTOBOTTOM; - - if (splitscreen > 1) - { - if (splitflags & V_HORZSCREEN) - snapflags &= ~V_SNAPTOLEFT; - else - snapflags &= ~V_SNAPTORIGHT; - } - - return (splitflags|snapflags); -} - -static void K_drawKartItem(void) -{ - // ITEM_X = BASEVIDWIDTH-50; // 270 - // ITEM_Y = 24; // 24 - - // Why write V_DrawScaledPatch calls over and over when they're all the same? - // Set to 'no item' just in case. - const UINT8 offset = ((splitscreen > 1) ? 1 : 0); - patch_t *localpatch = kp_nodraw; - patch_t *localbg = ((offset) ? kp_itembg[2] : kp_itembg[0]); - patch_t *localinv = ((offset) ? kp_invincibility[((leveltime % (6*3)) / 3) + 7] : kp_invincibility[(leveltime % (7*3)) / 3]); - INT32 fx = 0, fy = 0, fflags = 0; // final coords for hud and flags... - //INT32 splitflags = K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOLEFT); - const INT32 numberdisplaymin = ((!offset && stplyr->kartstuff[k_itemtype] == KITEM_ORBINAUT) ? 5 : 2); - INT32 itembar = 0; - INT32 maxl = 0; // itembar's normal highest value - const INT32 barlength = (splitscreen > 1 ? 12 : 26); - UINT8 localcolor = SKINCOLOR_NONE; - SINT8 colormode = TC_RAINBOW; - UINT8 *colmap = NULL; - boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff - - if (stplyr->kartstuff[k_itemroulette]) - { - if (stplyr->skincolor) - localcolor = stplyr->skincolor; - - switch((stplyr->kartstuff[k_itemroulette] % (15*3)) / 3) - { - // Each case is handled in threes, to give three frames of in-game time to see the item on the roulette - case 0: // Sneaker - localpatch = kp_sneaker[offset]; - //localcolor = SKINCOLOR_RASPBERRY; - break; - case 1: // Banana - localpatch = kp_banana[offset]; - //localcolor = SKINCOLOR_YELLOW; - break; - case 2: // Orbinaut - localpatch = kp_orbinaut[3+offset]; - //localcolor = SKINCOLOR_STEEL; - break; - case 3: // Mine - localpatch = kp_mine[offset]; - //localcolor = SKINCOLOR_JET; - break; - case 4: // Grow - localpatch = kp_grow[offset]; - //localcolor = SKINCOLOR_TEAL; - break; - case 5: // Hyudoro - localpatch = kp_hyudoro[offset]; - //localcolor = SKINCOLOR_STEEL; - break; - case 6: // Rocket Sneaker - localpatch = kp_rocketsneaker[offset]; - //localcolor = SKINCOLOR_TANGERINE; - break; - case 7: // Jawz - localpatch = kp_jawz[offset]; - //localcolor = SKINCOLOR_JAWZ; - break; - case 8: // Self-Propelled Bomb - localpatch = kp_selfpropelledbomb[offset]; - //localcolor = SKINCOLOR_JET; - break; - case 9: // Shrink - localpatch = kp_shrink[offset]; - //localcolor = SKINCOLOR_ORANGE; - break; - case 10: // Invincibility - localpatch = localinv; - //localcolor = SKINCOLOR_GREY; - break; - case 11: // Eggman Monitor - localpatch = kp_eggman[offset]; - //localcolor = SKINCOLOR_ROSE; - break; - case 12: // Ballhog - localpatch = kp_ballhog[offset]; - //localcolor = SKINCOLOR_LILAC; - break; - case 13: // Thunder Shield - localpatch = kp_thundershield[offset]; - //localcolor = SKINCOLOR_CYAN; - break; - case 14: // Super Ring - localpatch = kp_superring[offset]; - //localcolor = SKINCOLOR_GOLD; - break; - /*case 15: // Pogo Spring - localpatch = kp_pogospring[offset]; - localcolor = SKINCOLOR_TANGERINE; - break; - case 16: // Kitchen Sink - localpatch = kp_kitchensink[offset]; - localcolor = SKINCOLOR_STEEL; - break;*/ - default: - break; - } - } - else - { - // I'm doing this a little weird and drawing mostly in reverse order - // The only actual reason is to make sneakers line up this way in the code below - // This shouldn't have any actual baring over how it functions - // Hyudoro is first, because we're drawing it on top of the player's current item - if (stplyr->kartstuff[k_stolentimer] > 0) - { - if (leveltime & 2) - localpatch = kp_hyudoro[offset]; - else - localpatch = kp_nodraw; - } - else if ((stplyr->kartstuff[k_stealingtimer] > 0) && (leveltime & 2)) - { - localpatch = kp_hyudoro[offset]; - } - else if (stplyr->kartstuff[k_eggmanexplode] > 1) - { - if (leveltime & 1) - localpatch = kp_eggman[offset]; - else - localpatch = kp_nodraw; - } - else if (stplyr->kartstuff[k_rocketsneakertimer] > 1) - { - itembar = stplyr->kartstuff[k_rocketsneakertimer]; - maxl = (itemtime*3) - barlength; - - if (leveltime & 1) - localpatch = kp_rocketsneaker[offset]; - else - localpatch = kp_nodraw; - } - else if (stplyr->kartstuff[k_growshrinktimer] > 0) - { - if (stplyr->kartstuff[k_growcancel] > 0) - { - itembar = stplyr->kartstuff[k_growcancel]; - maxl = 26; - } - - if (leveltime & 1) - localpatch = kp_grow[offset]; - else - localpatch = kp_nodraw; - } - else if (stplyr->kartstuff[k_sadtimer] > 0) - { - if (leveltime & 2) - localpatch = kp_sadface[offset]; - else - localpatch = kp_nodraw; - } - else - { - if (stplyr->kartstuff[k_itemamount] <= 0) - return; - - switch(stplyr->kartstuff[k_itemtype]) - { - case KITEM_SNEAKER: - localpatch = kp_sneaker[offset]; - break; - case KITEM_ROCKETSNEAKER: - localpatch = kp_rocketsneaker[offset]; - break; - case KITEM_INVINCIBILITY: - localpatch = localinv; - localbg = kp_itembg[offset+1]; - break; - case KITEM_BANANA: - localpatch = kp_banana[offset]; - break; - case KITEM_EGGMAN: - localpatch = kp_eggman[offset]; - break; - case KITEM_ORBINAUT: - localpatch = kp_orbinaut[(offset ? 4 : min(stplyr->kartstuff[k_itemamount]-1, 3))]; - break; - case KITEM_JAWZ: - localpatch = kp_jawz[offset]; - break; - case KITEM_MINE: - localpatch = kp_mine[offset]; - break; - case KITEM_BALLHOG: - localpatch = kp_ballhog[offset]; - break; - case KITEM_SPB: - localpatch = kp_selfpropelledbomb[offset]; - localbg = kp_itembg[offset+1]; - break; - case KITEM_GROW: - localpatch = kp_grow[offset]; - break; - case KITEM_SHRINK: - localpatch = kp_shrink[offset]; - break; - case KITEM_THUNDERSHIELD: - localpatch = kp_thundershield[offset]; - localbg = kp_itembg[offset+1]; - break; - case KITEM_HYUDORO: - localpatch = kp_hyudoro[offset]; - break; - case KITEM_POGOSPRING: - localpatch = kp_pogospring[offset]; - break; - case KITEM_SUPERRING: - localpatch = kp_superring[offset]; - break; - case KITEM_KITCHENSINK: - localpatch = kp_kitchensink[offset]; - break; - case KITEM_SAD: - localpatch = kp_sadface[offset]; - break; - default: - return; - } - - if (stplyr->kartstuff[k_itemheld] && !(leveltime & 1)) - localpatch = kp_nodraw; - } - - if (stplyr->karthud[khud_itemblink] && (leveltime & 1)) - { - colormode = TC_BLINK; - - switch (stplyr->karthud[khud_itemblinkmode]) - { - case 2: - localcolor = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))); - break; - case 1: - localcolor = SKINCOLOR_RED; - break; - default: - localcolor = SKINCOLOR_WHITE; - break; - } - } - } - - // pain and suffering defined below - if (splitscreen < 2) // don't change shit for THIS splitscreen. - { - fx = ITEM_X; - fy = ITEM_Y; - fflags = K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOLEFT); - } - else // now we're having a fun game. - { - if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... - { - fx = ITEM_X; - fy = ITEM_Y; - fflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN : V_SNAPTOTOP); // flip P3 to the bottom. - } - else // else, that means we're P2 or P4. - { - fx = ITEM2_X; - fy = ITEM2_Y; - fflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN : V_SNAPTOTOP); // flip P4 to the bottom - flipamount = true; - } - } - - if (localcolor != SKINCOLOR_NONE) - colmap = R_GetTranslationColormap(colormode, localcolor, GTC_CACHE); - - V_DrawScaledPatch(fx, fy, V_HUDTRANS|fflags, localbg); - - // Then, the numbers: - if (stplyr->kartstuff[k_itemamount] >= numberdisplaymin && !stplyr->kartstuff[k_itemroulette]) - { - V_DrawScaledPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|fflags|(flipamount ? V_FLIP : 0), kp_itemmulsticker[offset]); // flip this graphic for p2 and p4 in split and shift it. - V_DrawFixedPatch(fx<kartstuff[k_itemamount])); - else - V_DrawString(fx+24, fy+31, V_ALLOWLOWERCASE|V_HUDTRANS|fflags, va("x%d", stplyr->kartstuff[k_itemamount])); - else - { - V_DrawScaledPatch(fy+28, fy+41, V_HUDTRANS|fflags, kp_itemx); - V_DrawKartString(fx+38, fy+36, V_HUDTRANS|fflags, va("%d", stplyr->kartstuff[k_itemamount])); - } - } - else - V_DrawFixedPatch(fx< 2) - { - V_DrawFill(fx+x+length, fy+y+1, 1, height, 12|fflags); // the right one - if (height == 2) - V_DrawFill(fx+x+2, fy+y+2, length-2, 1, 8|fflags); // the dulled underside - V_DrawFill(fx+x+2, fy+y+1, length-2, 1, 0|fflags); // the shine - } - } - - // Quick Eggman numbers - if (stplyr->kartstuff[k_eggmanexplode] > 1 /*&& stplyr->kartstuff[k_eggmanexplode] <= 3*TICRATE*/) - V_DrawScaledPatch(fx+17, fy+13-offset, V_HUDTRANS|fflags, kp_eggnum[min(3, G_TicsToSeconds(stplyr->kartstuff[k_eggmanexplode]))]); -} - -void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode) -{ - // TIME_X = BASEVIDWIDTH-124; // 196 - // TIME_Y = 6; // 6 - - tic_t worktime; - - INT32 splitflags = 0; - if (!mode) - { - splitflags = V_HUDTRANS|K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTORIGHT); - if (cv_timelimit.value && timelimitintics > 0) - { - if (drawtime >= timelimitintics) - drawtime = 0; - else - drawtime = timelimitintics - drawtime; - } - } - - V_DrawScaledPatch(TX, TY, splitflags, ((mode == 2) ? kp_lapstickerwide : kp_timestickerwide)); - - TX += 33; - - worktime = drawtime/(60*TICRATE); - - if (mode && !drawtime) - V_DrawKartString(TX, TY+3, splitflags, va("--'--\"--")); - else if (worktime < 100) // 99:99:99 only - { - // zero minute - if (worktime < 10) - { - V_DrawKartString(TX, TY+3, splitflags, va("0")); - // minutes time 0 __ __ - V_DrawKartString(TX+12, TY+3, splitflags, va("%d", worktime)); - } - // minutes time 0 __ __ - else - V_DrawKartString(TX, TY+3, splitflags, va("%d", worktime)); - - // apostrophe location _'__ __ - V_DrawKartString(TX+24, TY+3, splitflags, va("'")); - - worktime = (drawtime/TICRATE % 60); - - // zero second _ 0_ __ - if (worktime < 10) - { - V_DrawKartString(TX+36, TY+3, splitflags, va("0")); - // seconds time _ _0 __ - V_DrawKartString(TX+48, TY+3, splitflags, va("%d", worktime)); - } - // zero second _ 00 __ - else - V_DrawKartString(TX+36, TY+3, splitflags, va("%d", worktime)); - - // quotation mark location _ __"__ - V_DrawKartString(TX+60, TY+3, splitflags, va("\"")); - - worktime = G_TicsToCentiseconds(drawtime); - - // zero tick _ __ 0_ - if (worktime < 10) - { - V_DrawKartString(TX+72, TY+3, splitflags, va("0")); - // tics _ __ _0 - V_DrawKartString(TX+84, TY+3, splitflags, va("%d", worktime)); - } - // zero tick _ __ 00 - else - V_DrawKartString(TX+72, TY+3, splitflags, va("%d", worktime)); - } - else if ((drawtime/TICRATE) & 1) - V_DrawKartString(TX, TY+3, splitflags, va("99'59\"99")); - - if (emblemmap && (modeattacking || (mode == 1)) && !demo.playback) // emblem time! - { - INT32 workx = TX + 96, worky = TY+18; - SINT8 curemb = 0; - patch_t *emblempic[3] = {NULL, NULL, NULL}; - UINT8 *emblemcol[3] = {NULL, NULL, NULL}; - - emblem_t *emblem = M_GetLevelEmblems(emblemmap); - while (emblem) - { - char targettext[9]; - - switch (emblem->type) - { - case ET_TIME: - { - static boolean canplaysound = true; - tic_t timetoreach = emblem->var; - - if (emblem->collected) - { - emblempic[curemb] = W_CachePatchName(M_GetEmblemPatch(emblem), PU_CACHE); - emblemcol[curemb] = R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE); - if (++curemb == 3) - break; - goto bademblem; - } - - snprintf(targettext, 9, "%i'%02i\"%02i", - G_TicsToMinutes(timetoreach, false), - G_TicsToSeconds(timetoreach), - G_TicsToCentiseconds(timetoreach)); - - if (!mode) - { - if (stplyr->realtime > timetoreach) - { - splitflags = (splitflags &~ V_HUDTRANS)|V_HUDTRANSHALF; - if (canplaysound) - { - S_StartSound(NULL, sfx_s3k72); //sfx_s26d); -- you STOLE fizzy lifting drinks - canplaysound = false; - } - } - else if (!canplaysound) - canplaysound = true; - } - - targettext[8] = 0; - } - break; - default: - goto bademblem; - } - - V_DrawRightAlignedString(workx, worky, splitflags, targettext); - workx -= 67; - V_DrawSmallScaledPatch(workx + 4, worky, splitflags, W_CachePatchName("NEEDIT", PU_CACHE)); - - break; - - bademblem: - emblem = M_GetLevelEmblems(-1); - } - - if (!mode) - splitflags = (splitflags &~ V_HUDTRANSHALF)|V_HUDTRANS; - while (curemb--) - { - workx -= 12; - V_DrawSmallMappedPatch(workx + 4, worky, splitflags, emblempic[curemb], emblemcol[curemb]); - } - } -} - -static void K_DrawKartPositionNum(INT32 num) -{ - // POSI_X = BASEVIDWIDTH - 51; // 269 - // POSI_Y = BASEVIDHEIGHT- 64; // 136 - - boolean win = (stplyr->exiting && num == 1); - //INT32 X = POSI_X; - INT32 W = SHORT(kp_positionnum[0][0]->width); - fixed_t scale = FRACUNIT; - patch_t *localpatch = kp_positionnum[0][0]; - //INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTORIGHT); - INT32 fx = 0, fy = 0, fflags = 0; - boolean flipdraw = false; // flip the order we draw it in for MORE splitscreen bs. fun. - boolean flipvdraw = false; // used only for 2p splitscreen so overtaking doesn't make 1P's position fly off the screen. - boolean overtake = false; - - if (stplyr->kartstuff[k_positiondelay] || stplyr->exiting) - { - scale *= 2; - overtake = true; // this is used for splitscreen stuff in conjunction with flipdraw. - } - if (splitscreen) - scale /= 2; - - W = FixedMul(W<>FRACBITS; - - // pain and suffering defined below - if (!splitscreen) - { - fx = POSI_X; - fy = BASEVIDHEIGHT - 8; - fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT; - } - else if (splitscreen == 1) // for this splitscreen, we'll use case by case because it's a bit different. - { - fx = POSI_X; - if (stplyr == &players[displayplayers[0]]) // for player 1: display this at the top right, above the minimap. - { - fy = 30; - fflags = V_SNAPTOTOP|V_SNAPTORIGHT; - if (overtake) - flipvdraw = true; // make sure overtaking doesn't explode us - } - else // if we're not p1, that means we're p2. display this at the bottom right, below the minimap. - { - fy = BASEVIDHEIGHT - 8; - fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT; - } - } - else - { - if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... - { - fx = POSI_X; - fy = POSI_Y; - fflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. - flipdraw = true; - if (num && num >= 10) - fx += W; // this seems dumb, but we need to do this in order for positions above 10 going off screen. - } - else // else, that means we're P2 or P4. - { - fx = POSI2_X; - fy = POSI2_Y; - fflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom - } - } - - // Special case for 0 - if (!num) - { - V_DrawFixedPatch(fx<= 0); // This function does not draw negative numbers - - // Draw the number - while (num) - { - if (win) // 1st place winner? You get rainbows!! - localpatch = kp_winnernum[(leveltime % (NUMWINFRAMES*3)) / 3]; - else if (stplyr->laps >= cv_numlaps.value || stplyr->exiting) // Check for the final lap, or won - { - // Alternate frame every three frames - switch (leveltime % 9) - { - case 1: case 2: case 3: - if (K_IsPlayerLosing(stplyr)) - localpatch = kp_positionnum[num % 10][4]; - else - localpatch = kp_positionnum[num % 10][1]; - break; - case 4: case 5: case 6: - if (K_IsPlayerLosing(stplyr)) - localpatch = kp_positionnum[num % 10][5]; - else - localpatch = kp_positionnum[num % 10][2]; - break; - case 7: case 8: case 9: - if (K_IsPlayerLosing(stplyr)) - localpatch = kp_positionnum[num % 10][6]; - else - localpatch = kp_positionnum[num % 10][3]; - break; - default: - localpatch = kp_positionnum[num % 10][0]; - break; - } - } - else - localpatch = kp_positionnum[num % 10][0]; - - V_DrawFixedPatch((fx<width)*scale/2) : 0), (fy<height)*scale/2) : 0), scale, V_HUDTRANSHALF|fflags, localpatch, NULL); - // ^ if we overtake as p1 or p3 in splitscren, we shift it so that it doesn't go off screen. - // ^ if we overtake as p1 in 2p splits, shift vertically so that this doesn't happen either. - - fx -= W; - num /= 10; - } -} - -static boolean K_drawKartPositionFaces(void) -{ - // FACE_X = 15; // 15 - // FACE_Y = 72; // 72 - - INT32 Y = FACE_Y+9; // +9 to offset where it's being drawn if there are more than one - INT32 i, j, ranklines, strank = -1; - boolean completed[MAXPLAYERS]; - INT32 rankplayer[MAXPLAYERS]; - INT32 bumperx, numplayersingame = 0; - UINT8 *colormap; - - ranklines = 0; - memset(completed, 0, sizeof (completed)); - memset(rankplayer, 0, sizeof (rankplayer)); - - for (i = 0; i < MAXPLAYERS; i++) - { - rankplayer[i] = -1; - - if (!playeringame[i] || players[i].spectator || !players[i].mo) - continue; - - numplayersingame++; - } - - if (numplayersingame <= 1) - return true; - -#ifdef HAVE_BLUA - if (!LUA_HudEnabled(hud_minirankings)) - return false; // Don't proceed but still return true for free play above if HUD is disabled. -#endif - - for (j = 0; j < numplayersingame; j++) - { - UINT8 lowestposition = MAXPLAYERS+1; - for (i = 0; i < MAXPLAYERS; i++) - { - if (completed[i] || !playeringame[i] || players[i].spectator || !players[i].mo) - continue; - - if (players[i].kartstuff[k_position] >= lowestposition) - continue; - - rankplayer[ranklines] = i; - lowestposition = players[i].kartstuff[k_position]; - } - - i = rankplayer[ranklines]; - - completed[i] = true; - - if (players+i == stplyr) - strank = ranklines; - - //if (ranklines == 5) - //break; // Only draw the top 5 players -- we do this a different way now... - - ranklines++; - } - - if (ranklines < 5) - Y -= (9*ranklines); - else - Y -= (9*5); - - if (G_BattleGametype() || strank <= 2) // too close to the top, or playing battle, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2) - { - i = 0; - if (ranklines > 5) // could be both... - ranklines = 5; - } - else if (strank+3 > ranklines) // too close to the bottom? - { - i = ranklines - 5; - if (i < 0) - i = 0; - } - else - { - i = strank-2; - ranklines = strank+3; - } - - for (; i < ranklines; i++) - { - if (!playeringame[rankplayer[i]]) continue; - if (players[rankplayer[i]].spectator) continue; - if (!players[rankplayer[i]].mo) continue; - - bumperx = FACE_X+19; - - if (players[rankplayer[i]].mo->color) - { - colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE); - if (players[rankplayer[i]].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[rankplayer[i]].mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE); - - V_DrawMappedPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, facerankprefix[players[rankplayer[i]].skin], colormap); - -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_battlebumpers)) - { -#endif - if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] > 0) - { - V_DrawMappedPatch(bumperx-2, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[0], colormap); - for (j = 1; j < players[rankplayer[i]].kartstuff[k_bumper]; j++) - { - bumperx += 5; - V_DrawMappedPatch(bumperx, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[1], colormap); - } - } -#ifdef HAVE_BLUA - } // A new level of stupidity: checking if lua is enabled to close a bracket. :Fascinating: -#endif - } - - if (i == strank) - V_DrawScaledPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_facehighlight[(leveltime / 4) % 8]); - - if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] <= 0) - V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SNAPTOLEFT, kp_ranknobumpers); - else - { - INT32 pos = players[rankplayer[i]].kartstuff[k_position]; - if (pos < 0 || pos > MAXPLAYERS) - pos = 0; - // Draws the little number over the face - V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SNAPTOLEFT, kp_facenum[pos]); - } - - Y += 18; - } - - return false; -} - -// -// HU_DrawTabRankings -- moved here to take advantage of kart stuff! -// -void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol) -{ - static tic_t alagles_timer = 9; - INT32 i, rightoffset = 240; - const UINT8 *colormap; - INT32 dupadjust = (vid.width/vid.dupx), duptweak = (dupadjust - BASEVIDWIDTH)/2; - int y2; - - //this function is designed for 9 or less score lines only - //I_Assert(scorelines <= 9); -- not today bitch, kart fixed it up - - V_DrawFill(1-duptweak, 26, dupadjust-2, 1, 0); // Draw a horizontal line because it looks nice! - if (scorelines > 8) - { - V_DrawFill(160, 26, 1, 147, 0); // Draw a vertical line to separate the two sides. - V_DrawFill(1-duptweak, 173, dupadjust-2, 1, 0); // And a horizontal line near the bottom. - rightoffset = (BASEVIDWIDTH/2) - 4 - x; - } - - for (i = 0; i < scorelines; i++) - { - char strtime[MAXPLAYERNAME+1]; - - if (players[tab[i].num].spectator || !players[tab[i].num].mo) - continue; //ignore them. - - if (netgame // don't draw it offline - && ( tab[i].num != serverplayer || ! server_lagless )) - HU_drawPing(x + ((i < 8) ? -17 : rightoffset + 11), y-4, playerpingtable[tab[i].num], 0); - - STRBUFCPY(strtime, tab[i].name); - - y2 = y; - - if (tab[i].num == 0 && server_lagless) - { - y2 = ( y - 4 ); - - V_DrawScaledPatch(x + 20, y2, 0, kp_blagles[(leveltime / 3) % 6]); - // every 70 tics - if (( leveltime % 70 ) == 0) - { - alagles_timer = 9; - } - if (alagles_timer > 0) - { - V_DrawScaledPatch(x + 20, y2, 0, kp_alagles[alagles_timer]); - if (( leveltime % 2 ) == 0) - alagles_timer--; - } - else - V_DrawScaledPatch(x + 20, y2, 0, kp_alagles[0]); - - y2 += SHORT (kp_alagles[0]->height) + 1; - } - - if (scorelines > 8) - V_DrawThinString(x + 20, y2, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, strtime); - else - V_DrawString(x + 20, y2, ((tab[i].num == whiteplayer) ? hilicol : 0)|V_ALLOWLOWERCASE, strtime); - - if (players[tab[i].num].mo->color) - { - colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE); - if (players[tab[i].num].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[tab[i].num].mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE); - - V_DrawMappedPatch(x, y-4, 0, facerankprefix[players[tab[i].num].skin], colormap); - /*if (G_BattleGametype() && players[tab[i].num].kartstuff[k_bumper] > 0) -- not enough space for this - { - INT32 bumperx = x+19; - V_DrawMappedPatch(bumperx-2, y-4, 0, kp_tinybumper[0], colormap); - for (j = 1; j < players[tab[i].num].kartstuff[k_bumper]; j++) - { - bumperx += 5; - V_DrawMappedPatch(bumperx, y-4, 0, kp_tinybumper[1], colormap); - } - }*/ - } - - if (tab[i].num == whiteplayer) - V_DrawScaledPatch(x, y-4, 0, kp_facehighlight[(leveltime / 4) % 8]); - - if (G_BattleGametype() && players[tab[i].num].kartstuff[k_bumper] <= 0) - V_DrawScaledPatch(x-4, y-7, 0, kp_ranknobumpers); - else - { - INT32 pos = players[tab[i].num].kartstuff[k_position]; - if (pos < 0 || pos > MAXPLAYERS) - pos = 0; - // Draws the little number over the face - V_DrawScaledPatch(x-5, y+6, 0, kp_facenum[pos]); - } - - if (G_RaceGametype()) - { -#define timestring(time) va("%i'%02i\"%02i", G_TicsToMinutes(time, true), G_TicsToSeconds(time), G_TicsToCentiseconds(time)) - if (scorelines > 8) - { - if (players[tab[i].num].exiting) - V_DrawRightAlignedThinString(x+rightoffset, y-1, hilicol|V_6WIDTHSPACE, timestring(players[tab[i].num].realtime)); - else if (players[tab[i].num].pflags & PF_TIMEOVER) - V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, "NO CONTEST."); - else if (circuitmap) - V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, va("Lap %d", tab[i].count)); - } - else - { - if (players[tab[i].num].exiting) - V_DrawRightAlignedString(x+rightoffset, y, hilicol, timestring(players[tab[i].num].realtime)); - else if (players[tab[i].num].pflags & PF_TIMEOVER) - V_DrawRightAlignedThinString(x+rightoffset, y-1, 0, "NO CONTEST."); - else if (circuitmap) - V_DrawRightAlignedString(x+rightoffset, y, 0, va("Lap %d", tab[i].count)); - } -#undef timestring - } - else - V_DrawRightAlignedString(x+rightoffset, y, 0, va("%u", tab[i].count)); - - y += 18; - if (i == 7) - { - y = 33; - x = (BASEVIDWIDTH/2) + 4; - } - } -} - -#define RINGANIM_FLIPFRAME (RINGANIM_NUMFRAMES/2) - -static void K_drawKartLapsAndRings(void) -{ - SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; - INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT); - UINT8 rn[2]; - INT32 ringflip = 0; - UINT8 *ringmap = NULL; - boolean colorring = false; - INT32 ringx = 0; - - rn[0] = ((abs(stplyr->kartstuff[k_rings]) / 10) % 10); - rn[1] = (abs(stplyr->kartstuff[k_rings]) % 10); - - if (stplyr->kartstuff[k_rings] <= 0 && (leveltime/5 & 1)) // In debt - { - ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); - colorring = true; - } - else if (stplyr->kartstuff[k_rings] >= 20) // Maxed out - ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); - - if (stplyr->karthud[khud_ringframe] > RINGANIM_FLIPFRAME) - { - ringflip = V_FLIP; - ringanim_realframe = RINGANIM_NUMFRAMES-stplyr->karthud[khud_ringframe]; - ringx += SHORT((splitscreen > 1) ? kp_smallring[ringanim_realframe]->width : kp_ring[ringanim_realframe]->width); - } - - if (splitscreen > 1) - { - INT32 fx = 0, fy = 0, fr = 0; - INT32 flipflag = 0; - - // pain and suffering defined below - if (splitscreen < 2) // don't change shit for THIS splitscreen. - { - fx = LAPS_X; - fy = LAPS_Y; - } - else - { - if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... - { - fx = LAPS_X; - fy = LAPS_Y; - splitflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. - } - else // else, that means we're P2 or P4. - { - fx = LAPS2_X; - fy = LAPS2_Y; - splitflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom - flipflag = V_FLIP; // make the string right aligned and other shit - } - } - - fr = fx; - - // Laps - V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|splitflags|flipflag, kp_ringstickersplit[0]); - - V_DrawScaledPatch(fx, fy, V_HUDTRANS|splitflags, kp_splitlapflag); - V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|splitflags, frameslash); - - if (cv_numlaps.value >= 10) - { - UINT8 ln[2]; - ln[0] = ((abs(stplyr->laps) / 10) % 10); - ln[1] = (abs(stplyr->laps) % 10); - - V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); - V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); - - ln[0] = ((abs(cv_numlaps.value) / 10) % 10); - ln[1] = (abs(cv_numlaps.value) % 10); - - V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); - V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); - } - else - { - V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->laps) % 10]); - V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[(cv_numlaps.value) % 10]); - } - - // Rings - if (netgame) - { - V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy-10, V_HUDTRANS|splitflags|flipflag, kp_ringstickersplit[1]); - if (flipflag) - fr += 15; - } - else - V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[0]->width) - 3) : 0), fy-10, V_HUDTRANS|splitflags|flipflag, kp_ringstickersplit[0]); - - V_DrawMappedPatch(fr+ringx, fy-13, V_HUDTRANS|splitflags|ringflip, kp_smallring[ringanim_realframe], (colorring ? ringmap : NULL)); - - if (stplyr->kartstuff[k_rings] < 0) // Draw the minus for ring debt - V_DrawMappedPatch(fr+7, fy-10, V_HUDTRANS|splitflags, kp_ringdebtminussmall, ringmap); - - V_DrawMappedPatch(fr+11, fy-10, V_HUDTRANS|splitflags, pingnum[rn[0]], ringmap); - V_DrawMappedPatch(fr+15, fy-10, V_HUDTRANS|splitflags, pingnum[rn[1]], ringmap); - - // SPB ring lock - if (stplyr->kartstuff[k_ringlock]) - V_DrawScaledPatch(fr-12, fy-23, V_HUDTRANS|splitflags, kp_ringspblocksmall[stplyr->karthud[khud_ringspblock]]); - - // Lives - if (!netgame) - { - UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); - V_DrawMappedPatch(fr+21, fy-13, V_HUDTRANS|splitflags, facemmapprefix[stplyr->skin], colormap); - V_DrawScaledPatch(fr+34, fy-10, V_HUDTRANS|splitflags, pingnum[(stplyr->lives % 10)]); // make sure this doesn't overflow - } - } - else - { - // Laps - V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_lapsticker); - - if (stplyr->exiting) - V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, "FIN"); - else - V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->laps, cv_numlaps.value)); - - // Rings - if (netgame) - V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|splitflags, kp_ringsticker[1]); - else - V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|splitflags, kp_ringsticker[0]); - - V_DrawMappedPatch(LAPS_X+ringx+7, LAPS_Y-16, V_HUDTRANS|splitflags|ringflip, kp_ring[ringanim_realframe], (colorring ? ringmap : NULL)); - - if (stplyr->kartstuff[k_rings] < 0) // Draw the minus for ring debt - { - V_DrawMappedPatch(LAPS_X+23, LAPS_Y-11, V_HUDTRANS|splitflags, kp_ringdebtminus, ringmap); - V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|splitflags, kp_facenum[rn[0]], ringmap); - V_DrawMappedPatch(LAPS_X+35, LAPS_Y-11, V_HUDTRANS|splitflags, kp_facenum[rn[1]], ringmap); - } - else - { - V_DrawMappedPatch(LAPS_X+23, LAPS_Y-11, V_HUDTRANS|splitflags, kp_facenum[rn[0]], ringmap); - V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|splitflags, kp_facenum[rn[1]], ringmap); - } - - // SPB ring lock - if (stplyr->kartstuff[k_ringlock]) - V_DrawScaledPatch(LAPS_X-5, LAPS_Y-28, V_HUDTRANS|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); - - // Lives - if (!netgame) - { - UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); - V_DrawMappedPatch(LAPS_X+46, LAPS_Y-16, V_HUDTRANS|splitflags, facerankprefix[stplyr->skin], colormap); - V_DrawScaledPatch(LAPS_X+63, LAPS_Y-11, V_HUDTRANS|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow - } - } -} - -#undef RINGANIM_NUMFRAMES -#undef RINGANIM_FLIPFRAME - -static void K_drawKartSpeedometer(void) -{ - static fixed_t convSpeed; - UINT8 labeln = 0; - UINT8 numbers[3]; - INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT); - UINT8 battleoffset = 0; - - if (!stplyr->exiting) // Keep the same speed value as when you crossed the finish line! - { - switch (cv_kartspeedometer.value) - { - case 1: // Sonic Drift 2 style percentage - default: - convSpeed = (((25*stplyr->speed)/24) * 100) / K_GetKartSpeed(stplyr, false); // Based on top speed! (cheats with the numbers due to some weird discrepancy) - labeln = 0; - break; - case 2: // Kilometers - convSpeed = FixedDiv(FixedMul(stplyr->speed, 142371), mapobjectscale)/FRACUNIT; // 2.172409058 - labeln = 1; - break; - case 3: // Miles - convSpeed = FixedDiv(FixedMul(stplyr->speed, 88465), mapobjectscale)/FRACUNIT; // 1.349868774 - labeln = 2; - break; - case 4: // Fracunits - convSpeed = FixedDiv(stplyr->speed, mapobjectscale)/FRACUNIT; // 1.0. duh. - labeln = 3; - break; - } - } - - // Don't overflow - if (convSpeed > 999) - convSpeed = 999; - - numbers[0] = ((convSpeed / 100) % 10); - numbers[1] = ((convSpeed / 10) % 10); - numbers[2] = (convSpeed % 10); - - if (G_BattleGametype()) - battleoffset = 8; - - V_DrawScaledPatch(LAPS_X, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_speedometersticker); - V_DrawScaledPatch(LAPS_X+7, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[0]]); - V_DrawScaledPatch(LAPS_X+13, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[1]]); - V_DrawScaledPatch(LAPS_X+19, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[2]]); - V_DrawScaledPatch(LAPS_X+29, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_speedometerlabel[labeln]); -} - -static void K_drawKartBumpersOrKarma(void) -{ - UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); - INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT); - - if (splitscreen > 1) - { - INT32 fx = 0, fy = 0; - INT32 flipflag = 0; - - // pain and suffering defined below - if (splitscreen < 2) // don't change shit for THIS splitscreen. - { - fx = LAPS_X; - fy = LAPS_Y; - } - else - { - if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... - { - fx = LAPS_X; - fy = LAPS_Y; - splitflags = V_SNAPTOLEFT|((stplyr == &players[displayplayers[2]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P3 to the bottom. - } - else // else, that means we're P2 or P4. - { - fx = LAPS2_X; - fy = LAPS2_Y; - splitflags = V_SNAPTORIGHT|((stplyr == &players[displayplayers[3]]) ? V_SPLITSCREEN|V_SNAPTOBOTTOM : 0); // flip P4 to the bottom - flipflag = V_FLIP; // make the string right aligned and other shit - } - } - - V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|splitflags|flipflag, kp_ringstickersplit[0]); - V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|splitflags, frameslash); - - if (stplyr->kartstuff[k_bumper] <= 0) - { - V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|splitflags, kp_splitkarmabomb, colormap); - V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->kartstuff[k_comebackpoints]) % 10]); - V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[2]); - } - else - { - V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|splitflags, kp_rankbumper, colormap); - - if (stplyr->kartstuff[k_bumper] > 9 || cv_kartbumpers.value > 9) - { - UINT8 ln[2]; - ln[0] = ((abs(stplyr->kartstuff[k_bumper]) / 10) % 10); - ln[1] = (abs(stplyr->kartstuff[k_bumper]) % 10); - - V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); - V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); - - ln[0] = ((abs(cv_kartbumpers.value) / 10) % 10); - ln[1] = (abs(cv_kartbumpers.value) % 10); - - V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, pingnum[ln[0]]); - V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, pingnum[ln[1]]); - } - else - { - V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|splitflags, kp_facenum[(stplyr->kartstuff[k_bumper]) % 10]); - V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[(cv_kartbumpers.value) % 10]); - } - } - } - else - { - if (stplyr->kartstuff[k_bumper] <= 0) - { - V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_karmasticker, colormap); - V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/2", stplyr->kartstuff[k_comebackpoints])); - } - else - { - if (stplyr->kartstuff[k_bumper] > 9 && cv_kartbumpers.value > 9) - V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_bumperstickerwide, colormap); - else - V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_bumpersticker, colormap); - V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", stplyr->kartstuff[k_bumper], cv_kartbumpers.value)); - } - } -} - -static fixed_t K_FindCheckX(fixed_t px, fixed_t py, angle_t ang, fixed_t mx, fixed_t my) -{ - fixed_t dist, x; - fixed_t range = RING_DIST/3; - angle_t diff; - - range *= gamespeed+1; - - dist = abs(R_PointToDist2(px, py, mx, my)); - if (dist > range) - return -320; - - diff = R_PointToAngle2(px, py, mx, my) - ang; - - if (diff < ANGLE_90 || diff > ANGLE_270) - return -320; - else - x = (FixedMul(FINETANGENT(((diff+ANGLE_90)>>ANGLETOFINESHIFT) & 4095), 160<>FRACBITS; - - if (encoremode) - x = 320-x; - - if (splitscreen > 1) - x /= 2; - - return x; -} - -static void K_drawKartWanted(void) -{ - UINT8 i, numwanted = 0; - UINT8 *colormap = NULL; - INT32 basex = 0, basey = 0; - - if (stplyr != &players[displayplayers[0]]) - return; - - for (i = 0; i < 4; i++) - { - if (battlewanted[i] == -1) - break; - numwanted++; - } - - if (numwanted <= 0) - return; - - // set X/Y coords depending on splitscreen. - if (splitscreen < 3) // 1P and 2P use the same code. - { - basex = WANT_X; - basey = WANT_Y; - if (splitscreen == 2) - { - basey += 16; // slight adjust for 3P - basex -= 6; - } - } - else if (splitscreen == 3) // 4P splitscreen... - { - basex = BASEVIDWIDTH/2 - (SHORT(kp_wantedsplit->width)/2); // center on screen - basey = BASEVIDHEIGHT - 55; - //basey2 = 4; - } - - if (battlewanted[0] != -1) - colormap = R_GetTranslationColormap(0, players[battlewanted[0]].skincolor, GTC_CACHE); - V_DrawFixedPatch(basex< 1 ? kp_wantedsplit : kp_wanted), colormap); - /*if (basey2) - V_DrawFixedPatch(basex< 1 ? 13 : 8), y = basey+(splitscreen > 1 ? 16 : 21); - fixed_t scale = FRACUNIT/2; - player_t *p = &players[battlewanted[i]]; - - if (battlewanted[i] == -1) - break; - - if (numwanted == 1) - scale = FRACUNIT; - else - { - if (i & 1) - x += 16; - if (i > 1) - y += 16; - } - - if (players[battlewanted[i]].skincolor) - { - colormap = R_GetTranslationColormap(TC_RAINBOW, p->skincolor, GTC_CACHE); - V_DrawFixedPatch(x<skin] : facerankprefix[p->skin]), colormap); - /*if (basey2) // again with 4p stuff - V_DrawFixedPatch(x<skin] : facerankprefix[p->skin]), colormap);*/ - } - } -} - -static void K_drawKartPlayerCheck(void) -{ - INT32 i; - UINT8 *colormap; - INT32 x; - - INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM); - - if (!stplyr->mo || stplyr->spectator) - return; - - if (stplyr->awayviewtics) - return; - - if (camspin[0]) - return; - - for (i = 0; i < MAXPLAYERS; i++) - { - UINT8 pnum = 0; - - if (&players[i] == stplyr) - continue; - if (!playeringame[i] || players[i].spectator) - continue; - if (!players[i].mo) - continue; - - if ((players[i].kartstuff[k_invincibilitytimer] <= 0) && (leveltime & 2)) - pnum++; // white frames - - if (players[i].kartstuff[k_itemtype] == KITEM_GROW || players[i].kartstuff[k_growshrinktimer] > 0) - pnum += 4; - else if (players[i].kartstuff[k_itemtype] == KITEM_INVINCIBILITY || players[i].kartstuff[k_invincibilitytimer]) - pnum += 2; - - x = K_FindCheckX(stplyr->mo->x, stplyr->mo->y, stplyr->mo->angle, players[i].mo->x, players[i].mo->y); - if (x <= 320 && x >= 0) - { - if (x < 14) - x = 14; - else if (x > 306) - x = 306; - - colormap = R_GetTranslationColormap(TC_DEFAULT, players[i].mo->color, GTC_CACHE); - V_DrawMappedPatch(x, CHEK_Y, V_HUDTRANS|splitflags, kp_check[pnum], colormap); - } - } -} - + static void K_drawKartMinimapIcon(fixed_t objx, fixed_t objy, INT32 hudx, INT32 hudy, INT32 flags, patch_t *icon, UINT8 *colormap, patch_t *AutomapPic) { // amnum xpos & ypos are the icon's speed around the HUD. @@ -9409,14 +9768,15 @@ static void K_drawKartMinimap(void) UINT8 skin = 0; UINT8 *colormap = NULL; SINT8 localplayers[4]; - SINT8 numlocalplayers = 0; + SINT8 numlocalplayers = 0; + mobj_t *mobj, *next; // for SPB drawing (or any other item(s) we may wanna draw, I dunno!) // Draw the HUD only when playing in a level. // hu_stuff needs this, unlike st_stuff. if (gamestate != GS_LEVEL) return; - - // Only draw for the first player + + // Only draw for the first player // Maybe move this somewhere else where this won't be a concern? if (stplyr != &players[displayplayers[0]]) return; @@ -9557,12 +9917,32 @@ static void K_drawKartMinimap(void) colormap = NULL; K_drawKartMinimapIcon(players[i].mo->x, players[i].mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); - // Target reticule - if ((G_RaceGametype() && players[i].kartstuff[k_position] == spbplace) - || (G_BattleGametype() && K_IsPlayerWanted(&players[i]))) + // Target reticule + if ((G_RaceGametype() && players[i].kartstuff[k_position] == spbplace) + || (G_BattleGametype() && K_IsPlayerWanted(&players[i]))) K_drawKartMinimapIcon(players[i].mo->x, players[i].mo->y, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); } } + + // draw SPB(s?) + for (mobj = kitemcap; mobj; mobj = next) + { + next = mobj->itnext; + if (mobj->type == MT_SPB) + { + colormap = NULL; + + if (mobj->target && !P_MobjWasRemoved(mobj->target)) + { + if (mobj->player && mobj->player->skincolor) + colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->player->skincolor, GTC_CACHE); + else if (mobj->color) + colormap = R_GetTranslationColormap(TC_RAINBOW, mobj->color, GTC_CACHE); + } + + K_drawKartMinimapIcon(mobj->x, mobj->y, x, y, splitflags, kp_spbminimap, colormap, AutomapPic); + } + } // draw our local players here, opaque. splitflags &= ~V_HUDTRANSHALF; @@ -9588,894 +9968,897 @@ static void K_drawKartMinimap(void) else colormap = NULL; - K_drawKartMinimapIcon(players[localplayers[i]].mo->x, players[localplayers[i]].mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); - + K_drawKartMinimapIcon(players[localplayers[i]].mo->x, players[localplayers[i]].mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); + // Target reticule - if ((G_RaceGametype() && players[localplayers[i]].kartstuff[k_position] == spbplace) + if ((G_RaceGametype() && players[localplayers[i]].kartstuff[k_position] == spbplace) || (G_BattleGametype() && K_IsPlayerWanted(&players[localplayers[i]]))) K_drawKartMinimapIcon(players[localplayers[i]].mo->x, players[localplayers[i]].mo->y, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); } } - -static void K_drawKartStartCountdown(void) -{ - INT32 pnum = 0, splitflags = K_calcSplitFlags(0); // 3 - - if (leveltime >= starttime-(2*TICRATE)) // 2 - pnum++; - if (leveltime >= starttime-TICRATE) // 1 - pnum++; - if (leveltime >= starttime) // GO! - pnum++; - if ((leveltime % (2*5)) / 5) // blink - pnum += 4; - if (splitscreen) // splitscreen - pnum += 8; - - V_DrawScaledPatch(STCD_X - (SHORT(kp_startcountdown[pnum]->width)/2), STCD_Y - (SHORT(kp_startcountdown[pnum]->height)/2), splitflags, kp_startcountdown[pnum]); -} - -static void K_drawKartFinish(void) -{ - INT32 pnum = 0, splitflags = K_calcSplitFlags(0); - - if (!stplyr->karthud[khud_cardanimation] || stplyr->karthud[khud_cardanimation] >= 2*TICRATE) - return; - - if ((stplyr->karthud[khud_cardanimation] % (2*5)) / 5) // blink - pnum = 1; - - if (splitscreen > 1) // 3/4p, stationary FIN - { - pnum += 2; - V_DrawScaledPatch(STCD_X - (SHORT(kp_racefinish[pnum]->width)/2), STCD_Y - (SHORT(kp_racefinish[pnum]->height)/2), splitflags, kp_racefinish[pnum]); - return; - } - - //else -- 1/2p, scrolling FINISH - { - INT32 x, xval; - - if (splitscreen) // wide splitscreen - pnum += 4; - - x = ((vid.width<width)<karthud[khud_cardanimation])*(xval > x ? xval : x))/TICRATE; - - if (splitscreen && stplyr == &players[displayplayers[1]]) - x = -x; - - V_DrawFixedPatch(x + (STCD_X<>1), - (STCD_Y<height)<<(FRACBITS-1)), - FRACUNIT, - splitflags, kp_racefinish[pnum], NULL); - } -} - -static void K_drawBattleFullscreen(void) -{ - INT32 x = BASEVIDWIDTH/2; - INT32 y = -64+(stplyr->karthud[khud_cardanimation]); // card animation goes from 0 to 164, 164 is the middle of the screen - INT32 splitflags = V_SNAPTOTOP; // I don't feel like properly supporting non-green resolutions, so you can have a misuse of SNAPTO instead - fixed_t scale = FRACUNIT; - boolean drawcomebacktimer = true; // lazy hack because it's cleaner in the long run. -#ifdef HAVE_BLUA - if (!LUA_HudEnabled(hud_battlecomebacktimer)) - drawcomebacktimer = false; -#endif - - if (splitscreen) - { - if ((splitscreen == 1 && stplyr == &players[displayplayers[1]]) - || (splitscreen > 1 && (stplyr == &players[displayplayers[2]] - || (stplyr == &players[displayplayers[3]] && splitscreen > 2)))) - { - y = 232-(stplyr->karthud[khud_cardanimation]/2); - splitflags = V_SNAPTOBOTTOM; - } - else - y = -32+(stplyr->karthud[khud_cardanimation]/2); - - if (splitscreen > 1) - { - scale /= 2; - - if (stplyr == &players[displayplayers[1]] - || (stplyr == &players[displayplayers[3]] && splitscreen > 2)) - x = 3*BASEVIDWIDTH/4; - else - x = BASEVIDWIDTH/4; - } - else - { - if (stplyr->exiting) - { - if (stplyr == &players[displayplayers[1]]) - x = BASEVIDWIDTH-96; - else - x = 96; - } - else - scale /= 2; - } - } - - if (stplyr->exiting) - { - if (stplyr == &players[displayplayers[0]]) - V_DrawFadeScreen(0xFF00, 16); - if (stplyr->exiting < 6*TICRATE && !stplyr->spectator) - { - if (stplyr->kartstuff[k_position] == 1) - V_DrawFixedPatch(x<kartstuff[k_bumper] <= 0 && stplyr->kartstuff[k_comebacktimer] && comeback && !stplyr->spectator && drawcomebacktimer) - { - UINT16 t = stplyr->kartstuff[k_comebacktimer]/(10*TICRATE); - INT32 txoff, adjust = (splitscreen > 1) ? 4 : 6; // normal string is 8, kart string is 12, half of that for ease - INT32 ty = (BASEVIDHEIGHT/2)+66; - - txoff = adjust; - - while (t) - { - txoff += adjust; - t /= 10; - } - - if (splitscreen) - { - if (splitscreen > 1) - ty = (BASEVIDHEIGHT/4)+33; - if ((splitscreen == 1 && stplyr == &players[displayplayers[1]]) - || (stplyr == &players[displayplayers[2]] && splitscreen > 1) - || (stplyr == &players[displayplayers[3]] && splitscreen > 2)) - ty += (BASEVIDHEIGHT/2); - } - else - V_DrawFadeScreen(0xFF00, 16); - - if (!comebackshowninfo) - V_DrawFixedPatch(x< 1) - V_DrawString(x-txoff, ty, 0, va("%d", stplyr->kartstuff[k_comebacktimer]/TICRATE)); - else - { - V_DrawFixedPatch(x<kartstuff[k_comebacktimer]/TICRATE)); - } - } - - if (netgame && !stplyr->spectator && timeinmap > 113) // FREE PLAY? - { - UINT8 i; - - // check to see if there's anyone else at all - for (i = 0; i < MAXPLAYERS; i++) - { - if (i == displayplayers[0]) - continue; - if (playeringame[i] && !stplyr->spectator) - return; - } - -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_freeplay)) -#endif - K_drawKartFreePlay(leveltime); - } -} - -static void K_drawKartFirstPerson(void) -{ - static INT32 pnum[4], turn[4], drift[4]; - INT32 pn = 0, tn = 0, dr = 0; - INT32 target = 0, splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM); - INT32 x = BASEVIDWIDTH/2, y = BASEVIDHEIGHT; - fixed_t scale; - UINT8 *colmap = NULL; - ticcmd_t *cmd = &stplyr->cmd; - - if (stplyr->spectator || !stplyr->mo || (stplyr->mo->flags2 & MF2_DONTDRAW)) - return; - - if (stplyr == &players[displayplayers[1]] && splitscreen) - { pn = pnum[1]; tn = turn[1]; dr = drift[1]; } - else if (stplyr == &players[displayplayers[2]] && splitscreen > 1) - { pn = pnum[2]; tn = turn[2]; dr = drift[2]; } - else if (stplyr == &players[displayplayers[3]] && splitscreen > 2) - { pn = pnum[3]; tn = turn[3]; dr = drift[3]; } - else - { pn = pnum[0]; tn = turn[0]; dr = drift[0]; } - - if (splitscreen) - { - y >>= 1; - if (splitscreen > 1) - x >>= 1; - } - - { - if (stplyr->speed < (20*stplyr->mo->scale) && (leveltime & 1) && !splitscreen) - y++; - // the following isn't EXPLICITLY right, it just gets the result we want, but i'm too lazy to look up the right way to do it - if (stplyr->mo->flags2 & MF2_SHADOW) - splitflags |= FF_TRANS80; - else if (stplyr->mo->frame & FF_TRANSMASK) - splitflags |= (stplyr->mo->frame & FF_TRANSMASK); - } - - if (cmd->driftturn > 400) // strong left turn - target = 2; - else if (cmd->driftturn < -400) // strong right turn - target = -2; - else if (cmd->driftturn > 0) // weak left turn - target = 1; - else if (cmd->driftturn < 0) // weak right turn - target = -1; - else // forward - target = 0; - - if (encoremode) - target = -target; - - if (pn < target) - pn++; - else if (pn > target) - pn--; - - if (pn < 0) - splitflags |= V_FLIP; // right turn - - target = abs(pn); - if (target > 2) - target = 2; - - x <<= FRACBITS; - y <<= FRACBITS; - - if (tn != cmd->driftturn/50) - tn -= (tn - (cmd->driftturn/50))/8; - - if (dr != stplyr->kartstuff[k_drift]*16) - dr -= (dr - (stplyr->kartstuff[k_drift]*16))/8; - - if (splitscreen == 1) - { - scale = (2*FRACUNIT)/3; - y += FRACUNIT/(vid.dupx < vid.dupy ? vid.dupx : vid.dupy); // correct a one-pixel gap on the screen view (not the basevid view) - } - else if (splitscreen) - scale = FRACUNIT/2; - else - scale = FRACUNIT; - - if (stplyr->mo) - { - INT32 dsone = K_GetKartDriftSparkValue(stplyr); - INT32 dstwo = dsone*2; - INT32 dsthree = dstwo*2; - -#ifndef DONTLIKETOASTERSFPTWEAKS - { - const angle_t ang = R_PointToAngle2(0, 0, stplyr->rmomx, stplyr->rmomy) - stplyr->frameangle; - // yes, the following is correct. no, you do not need to swap the x and y. - fixed_t xoffs = -P_ReturnThrustY(stplyr->mo, ang, (BASEVIDWIDTH<<(FRACBITS-2))/2); - fixed_t yoffs = -(P_ReturnThrustX(stplyr->mo, ang, 4*FRACUNIT) - 4*FRACUNIT); - - if (splitscreen) - xoffs = FixedMul(xoffs, scale); - - xoffs -= (tn)*scale; - xoffs -= (dr)*scale; - - if (stplyr->frameangle == stplyr->mo->angle) - { - const fixed_t mag = FixedDiv(stplyr->speed, 10*stplyr->mo->scale); - - if (mag < FRACUNIT) - { - xoffs = FixedMul(xoffs, mag); - if (!splitscreen) - yoffs = FixedMul(yoffs, mag); - } - } - - if (stplyr->mo->momz > 0) // TO-DO: Draw more of the kart so we can remove this if! - yoffs += stplyr->mo->momz/3; - - if (encoremode) - x -= xoffs; - else - x += xoffs; - if (!splitscreen) - y += yoffs; - } - - // drift sparks! - if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dsthree)) - colmap = R_GetTranslationColormap(TC_RAINBOW, (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))), GTC_CACHE); - else if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dstwo)) - colmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_KETCHUP, GTC_CACHE); - else if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dsone)) - colmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_SAPPHIRE, GTC_CACHE); - else -#endif - // invincibility/grow/shrink! - if (stplyr->mo->colorized && stplyr->mo->color) - colmap = R_GetTranslationColormap(TC_RAINBOW, stplyr->mo->color, GTC_CACHE); - } - - V_DrawFixedPatch(x, y, scale, splitflags, kp_fpview[target], colmap); - - if (stplyr == &players[displayplayers[1]] && splitscreen) - { pnum[1] = pn; turn[1] = tn; drift[1] = dr; } - else if (stplyr == &players[displayplayers[2]] && splitscreen > 1) - { pnum[2] = pn; turn[2] = tn; drift[2] = dr; } - else if (stplyr == &players[displayplayers[3]] && splitscreen > 2) - { pnum[3] = pn; turn[3] = tn; drift[3] = dr; } - else - { pnum[0] = pn; turn[0] = tn; drift[0] = dr; } -} - -// doesn't need to ever support 4p -static void K_drawInput(void) -{ - static INT32 pn = 0; - INT32 target = 0, splitflags = (V_SNAPTOBOTTOM|V_SNAPTORIGHT); - INT32 x = BASEVIDWIDTH - 32, y = BASEVIDHEIGHT-24, offs, col; - const INT32 accent1 = splitflags|colortranslations[stplyr->skincolor][5]; - const INT32 accent2 = splitflags|colortranslations[stplyr->skincolor][9]; - ticcmd_t *cmd = &stplyr->cmd; - - if (timeinmap <= 105) - return; - - if (timeinmap < 113) - { - INT32 count = ((INT32)(timeinmap) - 105); - offs = 64; - while (count-- > 0) - offs >>= 1; - x += offs; - } - -#define BUTTW 8 -#define BUTTH 11 - -#define drawbutt(xoffs, butt, symb)\ - if (stplyr->cmd.buttons & butt)\ - {\ - offs = 2;\ - col = accent1;\ - }\ - else\ - {\ - offs = 0;\ - col = accent2;\ - V_DrawFill(x+(xoffs), y+BUTTH, BUTTW-1, 2, splitflags|31);\ - }\ - V_DrawFill(x+(xoffs), y+offs, BUTTW-1, BUTTH, col);\ - V_DrawFixedPatch((x+1+(xoffs))<driftturn) // no turn - target = 0; - else // turning of multiple strengths! - { - target = ((abs(cmd->driftturn) - 1)/125)+1; - if (target > 4) - target = 4; - if (cmd->driftturn < 0) - target = -target; - } - - if (pn != target) - { - if (abs(pn - target) == 1) - pn = target; - else if (pn < target) - pn += 2; - else //if (pn > target) - pn -= 2; - } - - if (pn < 0) - { - splitflags |= V_FLIP; // right turn - x--; - } - - target = abs(pn); - if (target > 4) - target = 4; - - if (!stplyr->skincolor) - V_DrawFixedPatch(x<skincolor, GTC_CACHE); - V_DrawFixedPatch(x<karthud[khud_lapanimation]; - UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); - - V_DrawFixedPatch((BASEVIDWIDTH/2 + (32*max(0, stplyr->karthud[khud_lapanimation]-76)))*FRACUNIT, - (48 - (32*max(0, progress-76)))*FRACUNIT, - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - (modeattacking ? kp_lapanim_emblem[1] : kp_lapanim_emblem[0]), colormap); - - if (stplyr->karthud[khud_laphand] >= 1 && stplyr->karthud[khud_laphand] <= 3) - { - V_DrawFixedPatch((BASEVIDWIDTH/2 + (32*max(0, stplyr->karthud[khud_lapanimation]-76)))*FRACUNIT, - (48 - (32*max(0, progress-76)) - + 4 - abs((signed)((leveltime % 8) - 4)))*FRACUNIT, - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_hand[stplyr->karthud[khud_laphand]-1], NULL); - } - - if (stplyr->laps == (UINT8)(cv_numlaps.value)) - { - V_DrawFixedPatch((62 - (32*max(0, progress-76)))*FRACUNIT, // 27 - 30*FRACUNIT, // 24 - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_final[min(progress/2, 10)], NULL); - - if (progress/2-12 >= 0) - { - V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194 - 30*FRACUNIT, // 24 - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_lap[min(progress/2-12, 6)], NULL); - } - } - else - { - V_DrawFixedPatch((82 - (32*max(0, progress-76)))*FRACUNIT, // 61 - 30*FRACUNIT, // 24 - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_lap[min(progress/2, 6)], NULL); - - if (progress/2-8 >= 0) - { - V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194 - 30*FRACUNIT, // 24 - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_number[(((UINT32)stplyr->laps) / 10)][min(progress/2-8, 2)], NULL); - - if (progress/2-10 >= 0) - { - V_DrawFixedPatch((208 + (32*max(0, progress-76)))*FRACUNIT, // 221 - 30*FRACUNIT, // 24 - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_number[(((UINT32)stplyr->laps) % 10)][min(progress/2-10, 2)], NULL); - } - } - } -} - -void K_drawKartFreePlay(UINT32 flashtime) -{ - // no splitscreen support because it's not FREE PLAY if you have more than one player in-game - // (you fool, you can take splitscreen online. :V) - - if ((flashtime % TICRATE) < TICRATE/2) - return; - - V_DrawKartString((BASEVIDWIDTH - (LAPS_X+1)) - (12*9), // mirror the laps thingy - LAPS_Y+3, V_HUDTRANS|V_SNAPTOBOTTOM|V_SNAPTORIGHT, "FREE PLAY"); -} - -static void K_drawDistributionDebugger(void) -{ - patch_t *items[NUMKARTRESULTS] = { - kp_sadface[1], - kp_sneaker[1], - kp_rocketsneaker[1], - kp_invincibility[7], - kp_banana[1], - kp_eggman[1], - kp_orbinaut[4], - kp_jawz[1], - kp_mine[1], - kp_ballhog[1], - kp_selfpropelledbomb[1], - kp_grow[1], - kp_shrink[1], - kp_thundershield[1], - kp_hyudoro[1], - kp_pogospring[1], - kp_superring[1], - kp_kitchensink[1], - - kp_sneaker[1], - kp_banana[1], - kp_banana[1], - kp_orbinaut[4], - kp_orbinaut[4], - kp_jawz[1] - }; - INT32 useodds = 0; - INT32 pingame = 0, bestbumper = 0; - INT32 pdis = 0; - INT32 i; - INT32 x = -9, y = -9; - boolean spbrush = false; - - if (stplyr != &players[displayplayers[0]]) // only for p1 - return; - - // The only code duplication from the Kart, just to avoid the actual item function from calculating pingame twice - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - pingame++; - if (players[i].kartstuff[k_bumper] > bestbumper) - bestbumper = players[i].kartstuff[k_bumper]; - } - - // lovely double loop...... - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator && players[i].mo - && players[i].kartstuff[k_position] < stplyr->kartstuff[k_position]) - pdis += P_AproxDistance(P_AproxDistance(players[i].mo->x - stplyr->mo->x, - players[i].mo->y - stplyr->mo->y), - players[i].mo->z - stplyr->mo->z) / mapobjectscale - * (pingame - players[i].kartstuff[k_position]) - / max(1, ((pingame - 1) * (pingame + 1) / 3)); - } - - if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items - pdis = (15 * pdis) / 14; - - if (spbplace != -1 && stplyr->kartstuff[k_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 = ((28 + (8-pingame)) * pdis) / 28; // scale with player count - - useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper, spbrush); - - for (i = 1; i < NUMKARTRESULTS; i++) - { - const INT32 itemodds = K_KartGetItemOdds(useodds, i, 0, spbrush); - if (itemodds <= 0) - continue; - - V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOTOP, items[i]); - V_DrawThinString(x+11, y+31, V_HUDTRANS|V_SNAPTOTOP, va("%d", itemodds)); - - // Display amount for multi-items - if (i >= NUMKARTITEMS) - { - INT32 amount; - switch (i) - { - case KRITEM_TENFOLDBANANA: - amount = 10; - break; - case KRITEM_QUADORBINAUT: - amount = 4; - break; - case KRITEM_DUALJAWZ: - amount = 2; - break; - default: - amount = 3; - break; - } - V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", amount)); - } - - x += 32; - if (x >= 297) - { - x = -9; - y += 32; - } - } - - V_DrawString(0, 0, V_HUDTRANS|V_SNAPTOTOP, va("USEODDS %d", useodds)); -} - -static void K_drawCheckpointDebugger(void) -{ - if (stplyr != &players[displayplayers[0]]) // only for p1 - return; - - if (stplyr->starpostnum == numstarposts) - V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Can finish)", stplyr->starpostnum, numstarposts)); - else - V_DrawString(8, 184, 0, va("Checkpoint: %d / %d", stplyr->starpostnum, numstarposts)); -} - -static void K_DrawWaypointDebugger(void) -{ - if ((cv_kartdebugwaypoints.value != 0) && (stplyr == &players[displayplayers[0]])) - { - V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish)); - } -} - -void K_drawKartHUD(void) -{ - boolean isfreeplay = false; - boolean battlefullscreen = false; - UINT8 i; - - // Define the X and Y for each drawn object - // This is handled by console/menu values - K_initKartHUD(); - - // Draw that fun first person HUD! Drawn ASAP so it looks more "real". - for (i = 0; i <= splitscreen; i++) - { - if (stplyr == &players[displayplayers[i]] && !camera[i].chase) - K_drawKartFirstPerson(); - } - - // Draw full screen stuff that turns off the rest of the HUD - if (mapreset && stplyr == &players[displayplayers[0]]) - { - K_drawChallengerScreen(); - return; - } - - battlefullscreen = ((G_BattleGametype()) - && (stplyr->exiting - || (stplyr->kartstuff[k_bumper] <= 0 - && stplyr->kartstuff[k_comebacktimer] - && comeback - && stplyr->playerstate == PST_LIVE))); - - if (!demo.title && (!battlefullscreen || splitscreen)) - { - // Draw the CHECK indicator before the other items, so it's overlapped by everything else - if (cv_kartcheck.value && !splitscreen && !players[displayplayers[0]].exiting) - K_drawKartPlayerCheck(); - - // Draw WANTED status - if (G_BattleGametype()) - { -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_wanted)) -#endif - K_drawKartWanted(); - } - - if (cv_kartminimap.value) - { -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_minimap)) -#endif - K_drawKartMinimap(); - } - } - - if (battlefullscreen) - { -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_battlefullscreen)) -#endif - K_drawBattleFullscreen(); - return; - } - - // Draw the item window -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_item)) -#endif - K_drawKartItem(); - - // If not splitscreen, draw... - if (!splitscreen && !demo.title) - { - // Draw the timestamp -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_time)) -#endif - K_drawKartTimestamp(stplyr->realtime, TIME_X, TIME_Y, gamemap, 0); - - if (!modeattacking) - { - // The top-four faces on the left - /*#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_minirankings)) - #endif*/ - isfreeplay = K_drawKartPositionFaces(); - } - } - - if (!stplyr->spectator) // Bottom of the screen elements, don't need in spectate mode - { - // Draw the speedometer - if (cv_kartspeedometer.value && !splitscreen) - { -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_speedometer)) -#endif - K_drawKartSpeedometer(); - } - - if (demo.title) // Draw title logo instead in demo.titles - { - INT32 x = BASEVIDWIDTH - 32, y = 128, offs; - - if (splitscreen == 3) - { - x = BASEVIDWIDTH/2 + 10; - y = BASEVIDHEIGHT/2 - 30; - } - - if (timeinmap < 113) - { - INT32 count = ((INT32)(timeinmap) - 104); - offs = 256; - while (count-- > 0) - offs >>= 1; - x += offs; - } - - V_DrawTinyScaledPatch(x-54, y, 0, W_CachePatchName("TTKBANNR", PU_CACHE)); - V_DrawTinyScaledPatch(x-54, y+25, 0, W_CachePatchName("TTKART", PU_CACHE)); - } - else if (G_RaceGametype()) // Race-only elements - { - // Draw the lap counter -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_gametypeinfo)) -#endif - K_drawKartLapsAndRings(); - - if (isfreeplay) - ; - else if (!modeattacking) - { - // Draw the numerical position -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_position)) -#endif - K_DrawKartPositionNum(stplyr->kartstuff[k_position]); - } - else //if (!(demo.playback && hu_showscores)) - { - // Draw the input UI -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_position)) -#endif - K_drawInput(); - } - } - else if (G_BattleGametype()) // Battle-only - { - // Draw the hits left! -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_gametypeinfo)) -#endif - K_drawKartBumpersOrKarma(); - } - } - - // Draw the countdowns after everything else. - if (leveltime >= starttime-(3*TICRATE) - && leveltime < starttime+TICRATE) - K_drawKartStartCountdown(); - else if (countdown && (!splitscreen || !stplyr->exiting)) - { - char *countstr = va("%d", countdown/TICRATE); - - if (splitscreen > 1) - V_DrawCenteredString(BASEVIDWIDTH/4, LAPS_Y+1, K_calcSplitFlags(0), countstr); - else - { - INT32 karlen = strlen(countstr)*6; // half of 12 - V_DrawKartString((BASEVIDWIDTH/2)-karlen, LAPS_Y+3, K_calcSplitFlags(0), countstr); - } - } - - // Race overlays - if (G_RaceGametype()) - { - if (stplyr->exiting) - K_drawKartFinish(); - else if (stplyr->karthud[khud_lapanimation] && !splitscreen) - K_drawLapStartAnim(); - } - - if (modeattacking) // everything after here is MP and debug only - return; - - if (G_BattleGametype() && !splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM * - V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); - - // Draw FREE PLAY. - if (isfreeplay && !stplyr->spectator && timeinmap > 113) - { -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_freeplay)) -#endif - K_drawKartFreePlay(leveltime); - } - - if (cv_kartdebugdistribution.value) - K_drawDistributionDebugger(); - - if (cv_kartdebugcheckpoint.value) - K_drawCheckpointDebugger(); - - if (cv_kartdebugnodes.value) - { - UINT8 p; - for (p = 0; p < MAXPLAYERS; p++) - V_DrawString(8, 64+(8*p), V_YELLOWMAP, va("%d - %d (%dl)", p, playernode[p], players[p].cmd.latency)); - } - - if (cv_kartdebugcolorize.value && stplyr->mo && stplyr->mo->skin) - { - INT32 x = 0, y = 0; - UINT8 c; - - for (c = 1; c < MAXSKINCOLORS; c++) - { - UINT8 *cm = R_GetTranslationColormap(TC_RAINBOW, c, GTC_CACHE); - V_DrawFixedPatch(x<>1, 0, facewantprefix[stplyr->skin], cm); - - x += 16; - if (x > BASEVIDWIDTH-16) - { - x = 0; - y += 16; - } - } - } - - K_DrawWaypointDebugger(); -} - -//} + +static void K_drawKartStartCountdown(void) +{ + INT32 pnum = 0, splitflags = K_calcSplitFlags(0); // 3 + + if (leveltime >= starttime-(2*TICRATE)) // 2 + pnum++; + if (leveltime >= starttime-TICRATE) // 1 + pnum++; + if (leveltime >= starttime) // GO! + pnum++; + if ((leveltime % (2*5)) / 5) // blink + pnum += 4; + if (splitscreen) // splitscreen + pnum += 8; + + V_DrawScaledPatch(STCD_X - (SHORT(kp_startcountdown[pnum]->width)/2), STCD_Y - (SHORT(kp_startcountdown[pnum]->height)/2), splitflags, kp_startcountdown[pnum]); +} + +static void K_drawKartFinish(void) +{ + INT32 pnum = 0, splitflags = K_calcSplitFlags(0); + + if (!stplyr->karthud[khud_cardanimation] || stplyr->karthud[khud_cardanimation] >= 2*TICRATE) + return; + + if ((stplyr->karthud[khud_cardanimation] % (2*5)) / 5) // blink + pnum = 1; + + if (splitscreen > 1) // 3/4p, stationary FIN + { + pnum += 2; + V_DrawScaledPatch(STCD_X - (SHORT(kp_racefinish[pnum]->width)/2), STCD_Y - (SHORT(kp_racefinish[pnum]->height)/2), splitflags, kp_racefinish[pnum]); + return; + } + + //else -- 1/2p, scrolling FINISH + { + INT32 x, xval; + + if (splitscreen) // wide splitscreen + pnum += 4; + + x = ((vid.width<width)<karthud[khud_cardanimation])*(xval > x ? xval : x))/TICRATE; + + if (splitscreen && stplyr == &players[displayplayers[1]]) + x = -x; + + V_DrawFixedPatch(x + (STCD_X<>1), + (STCD_Y<height)<<(FRACBITS-1)), + FRACUNIT, + splitflags, kp_racefinish[pnum], NULL); + } +} + +static void K_drawBattleFullscreen(void) +{ + INT32 x = BASEVIDWIDTH/2; + INT32 y = -64+(stplyr->karthud[khud_cardanimation]); // card animation goes from 0 to 164, 164 is the middle of the screen + INT32 splitflags = V_SNAPTOTOP; // I don't feel like properly supporting non-green resolutions, so you can have a misuse of SNAPTO instead + fixed_t scale = FRACUNIT; + boolean drawcomebacktimer = true; // lazy hack because it's cleaner in the long run. +#ifdef HAVE_BLUA + if (!LUA_HudEnabled(hud_battlecomebacktimer)) + drawcomebacktimer = false; +#endif + + if (splitscreen) + { + if ((splitscreen == 1 && stplyr == &players[displayplayers[1]]) + || (splitscreen > 1 && (stplyr == &players[displayplayers[2]] + || (stplyr == &players[displayplayers[3]] && splitscreen > 2)))) + { + y = 232-(stplyr->karthud[khud_cardanimation]/2); + splitflags = V_SNAPTOBOTTOM; + } + else + y = -32+(stplyr->karthud[khud_cardanimation]/2); + + if (splitscreen > 1) + { + scale /= 2; + + if (stplyr == &players[displayplayers[1]] + || (stplyr == &players[displayplayers[3]] && splitscreen > 2)) + x = 3*BASEVIDWIDTH/4; + else + x = BASEVIDWIDTH/4; + } + else + { + if (stplyr->exiting) + { + if (stplyr == &players[displayplayers[1]]) + x = BASEVIDWIDTH-96; + else + x = 96; + } + else + scale /= 2; + } + } + + if (stplyr->exiting) + { + if (stplyr == &players[displayplayers[0]]) + V_DrawFadeScreen(0xFF00, 16); + if (stplyr->exiting < 6*TICRATE && !stplyr->spectator) + { + if (stplyr->kartstuff[k_position] == 1) + V_DrawFixedPatch(x<kartstuff[k_bumper] <= 0 && stplyr->kartstuff[k_comebacktimer] && comeback && !stplyr->spectator && drawcomebacktimer) + { + UINT16 t = stplyr->kartstuff[k_comebacktimer]/(10*TICRATE); + INT32 txoff, adjust = (splitscreen > 1) ? 4 : 6; // normal string is 8, kart string is 12, half of that for ease + INT32 ty = (BASEVIDHEIGHT/2)+66; + + txoff = adjust; + + while (t) + { + txoff += adjust; + t /= 10; + } + + if (splitscreen) + { + if (splitscreen > 1) + ty = (BASEVIDHEIGHT/4)+33; + if ((splitscreen == 1 && stplyr == &players[displayplayers[1]]) + || (stplyr == &players[displayplayers[2]] && splitscreen > 1) + || (stplyr == &players[displayplayers[3]] && splitscreen > 2)) + ty += (BASEVIDHEIGHT/2); + } + else + V_DrawFadeScreen(0xFF00, 16); + + if (!comebackshowninfo) + V_DrawFixedPatch(x< 1) + V_DrawString(x-txoff, ty, 0, va("%d", stplyr->kartstuff[k_comebacktimer]/TICRATE)); + else + { + V_DrawFixedPatch(x<kartstuff[k_comebacktimer]/TICRATE)); + } + } + + if (netgame && !stplyr->spectator && timeinmap > 113) // FREE PLAY? + { + UINT8 i; + + // check to see if there's anyone else at all + for (i = 0; i < MAXPLAYERS; i++) + { + if (i == displayplayers[0]) + continue; + if (playeringame[i] && !stplyr->spectator) + return; + } + +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_freeplay)) +#endif + K_drawKartFreePlay(leveltime); + } +} + +static void K_drawKartFirstPerson(void) +{ + static INT32 pnum[4], turn[4], drift[4]; + INT32 pn = 0, tn = 0, dr = 0; + INT32 target = 0, splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM); + INT32 x = BASEVIDWIDTH/2, y = BASEVIDHEIGHT; + fixed_t scale; + UINT8 *colmap = NULL; + ticcmd_t *cmd = &stplyr->cmd; + + if (stplyr->spectator || !stplyr->mo || (stplyr->mo->flags2 & MF2_DONTDRAW)) + return; + + if (stplyr == &players[displayplayers[1]] && splitscreen) + { pn = pnum[1]; tn = turn[1]; dr = drift[1]; } + else if (stplyr == &players[displayplayers[2]] && splitscreen > 1) + { pn = pnum[2]; tn = turn[2]; dr = drift[2]; } + else if (stplyr == &players[displayplayers[3]] && splitscreen > 2) + { pn = pnum[3]; tn = turn[3]; dr = drift[3]; } + else + { pn = pnum[0]; tn = turn[0]; dr = drift[0]; } + + if (splitscreen) + { + y >>= 1; + if (splitscreen > 1) + x >>= 1; + } + + { + if (stplyr->speed < (20*stplyr->mo->scale) && (leveltime & 1) && !splitscreen) + y++; + // the following isn't EXPLICITLY right, it just gets the result we want, but i'm too lazy to look up the right way to do it + if (stplyr->mo->flags2 & MF2_SHADOW) + splitflags |= FF_TRANS80; + else if (stplyr->mo->frame & FF_TRANSMASK) + splitflags |= (stplyr->mo->frame & FF_TRANSMASK); + } + + if (cmd->driftturn > 400) // strong left turn + target = 2; + else if (cmd->driftturn < -400) // strong right turn + target = -2; + else if (cmd->driftturn > 0) // weak left turn + target = 1; + else if (cmd->driftturn < 0) // weak right turn + target = -1; + else // forward + target = 0; + + if (encoremode) + target = -target; + + if (pn < target) + pn++; + else if (pn > target) + pn--; + + if (pn < 0) + splitflags |= V_FLIP; // right turn + + target = abs(pn); + if (target > 2) + target = 2; + + x <<= FRACBITS; + y <<= FRACBITS; + + if (tn != cmd->driftturn/50) + tn -= (tn - (cmd->driftturn/50))/8; + + if (dr != stplyr->kartstuff[k_drift]*16) + dr -= (dr - (stplyr->kartstuff[k_drift]*16))/8; + + if (splitscreen == 1) + { + scale = (2*FRACUNIT)/3; + y += FRACUNIT/(vid.dupx < vid.dupy ? vid.dupx : vid.dupy); // correct a one-pixel gap on the screen view (not the basevid view) + } + else if (splitscreen) + scale = FRACUNIT/2; + else + scale = FRACUNIT; + + if (stplyr->mo) + { + INT32 dsone = K_GetKartDriftSparkValue(stplyr); + INT32 dstwo = dsone*2; + INT32 dsthree = dstwo*2; + +#ifndef DONTLIKETOASTERSFPTWEAKS + { + const angle_t ang = R_PointToAngle2(0, 0, stplyr->rmomx, stplyr->rmomy) - stplyr->frameangle; + // yes, the following is correct. no, you do not need to swap the x and y. + fixed_t xoffs = -P_ReturnThrustY(stplyr->mo, ang, (BASEVIDWIDTH<<(FRACBITS-2))/2); + fixed_t yoffs = -(P_ReturnThrustX(stplyr->mo, ang, 4*FRACUNIT) - 4*FRACUNIT); + + if (splitscreen) + xoffs = FixedMul(xoffs, scale); + + xoffs -= (tn)*scale; + xoffs -= (dr)*scale; + + if (stplyr->frameangle == stplyr->mo->angle) + { + const fixed_t mag = FixedDiv(stplyr->speed, 10*stplyr->mo->scale); + + if (mag < FRACUNIT) + { + xoffs = FixedMul(xoffs, mag); + if (!splitscreen) + yoffs = FixedMul(yoffs, mag); + } + } + + if (stplyr->mo->momz > 0) // TO-DO: Draw more of the kart so we can remove this if! + yoffs += stplyr->mo->momz/3; + + if (encoremode) + x -= xoffs; + else + x += xoffs; + if (!splitscreen) + y += yoffs; + } + + // drift sparks! + if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dsthree)) + colmap = R_GetTranslationColormap(TC_RAINBOW, (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))), GTC_CACHE); + else if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dstwo)) + colmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_KETCHUP, GTC_CACHE); + else if ((leveltime & 1) && (stplyr->kartstuff[k_driftcharge] >= dsone)) + colmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_SAPPHIRE, GTC_CACHE); + else +#endif + // invincibility/grow/shrink! + if (stplyr->mo->colorized && stplyr->mo->color) + colmap = R_GetTranslationColormap(TC_RAINBOW, stplyr->mo->color, GTC_CACHE); + } + + V_DrawFixedPatch(x, y, scale, splitflags, kp_fpview[target], colmap); + + if (stplyr == &players[displayplayers[1]] && splitscreen) + { pnum[1] = pn; turn[1] = tn; drift[1] = dr; } + else if (stplyr == &players[displayplayers[2]] && splitscreen > 1) + { pnum[2] = pn; turn[2] = tn; drift[2] = dr; } + else if (stplyr == &players[displayplayers[3]] && splitscreen > 2) + { pnum[3] = pn; turn[3] = tn; drift[3] = dr; } + else + { pnum[0] = pn; turn[0] = tn; drift[0] = dr; } +} + +// doesn't need to ever support 4p +static void K_drawInput(void) +{ + static INT32 pn = 0; + INT32 target = 0, splitflags = (V_SNAPTOBOTTOM|V_SNAPTORIGHT); + INT32 x = BASEVIDWIDTH - 32, y = BASEVIDHEIGHT-24, offs, col; + const INT32 accent1 = splitflags|colortranslations[stplyr->skincolor][5]; + const INT32 accent2 = splitflags|colortranslations[stplyr->skincolor][9]; + ticcmd_t *cmd = &stplyr->cmd; + + if (timeinmap <= 105) + return; + + if (timeinmap < 113) + { + INT32 count = ((INT32)(timeinmap) - 105); + offs = 64; + while (count-- > 0) + offs >>= 1; + x += offs; + } + +#define BUTTW 8 +#define BUTTH 11 + +#define drawbutt(xoffs, butt, symb)\ + if (stplyr->cmd.buttons & butt)\ + {\ + offs = 2;\ + col = accent1;\ + }\ + else\ + {\ + offs = 0;\ + col = accent2;\ + V_DrawFill(x+(xoffs), y+BUTTH, BUTTW-1, 2, splitflags|31);\ + }\ + V_DrawFill(x+(xoffs), y+offs, BUTTW-1, BUTTH, col);\ + V_DrawFixedPatch((x+1+(xoffs))<driftturn) // no turn + target = 0; + else // turning of multiple strengths! + { + target = ((abs(cmd->driftturn) - 1)/125)+1; + if (target > 4) + target = 4; + if (cmd->driftturn < 0) + target = -target; + } + + if (pn != target) + { + if (abs(pn - target) == 1) + pn = target; + else if (pn < target) + pn += 2; + else //if (pn > target) + pn -= 2; + } + + if (pn < 0) + { + splitflags |= V_FLIP; // right turn + x--; + } + + target = abs(pn); + if (target > 4) + target = 4; + + if (!stplyr->skincolor) + V_DrawFixedPatch(x<skincolor, GTC_CACHE); + V_DrawFixedPatch(x<karthud[khud_lapanimation]; + UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); + + V_DrawFixedPatch((BASEVIDWIDTH/2 + (32*max(0, stplyr->karthud[khud_lapanimation]-76)))*FRACUNIT, + (48 - (32*max(0, progress-76)))*FRACUNIT, + FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, + (modeattacking ? kp_lapanim_emblem[1] : kp_lapanim_emblem[0]), colormap); + + if (stplyr->karthud[khud_laphand] >= 1 && stplyr->karthud[khud_laphand] <= 3) + { + V_DrawFixedPatch((BASEVIDWIDTH/2 + (32*max(0, stplyr->karthud[khud_lapanimation]-76)))*FRACUNIT, + (48 - (32*max(0, progress-76)) + + 4 - abs((signed)((leveltime % 8) - 4)))*FRACUNIT, + FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, + kp_lapanim_hand[stplyr->karthud[khud_laphand]-1], NULL); + } + + if (stplyr->laps == (UINT8)(cv_numlaps.value)) + { + V_DrawFixedPatch((62 - (32*max(0, progress-76)))*FRACUNIT, // 27 + 30*FRACUNIT, // 24 + FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, + kp_lapanim_final[min(progress/2, 10)], NULL); + + if (progress/2-12 >= 0) + { + V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194 + 30*FRACUNIT, // 24 + FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, + kp_lapanim_lap[min(progress/2-12, 6)], NULL); + } + } + else + { + V_DrawFixedPatch((82 - (32*max(0, progress-76)))*FRACUNIT, // 61 + 30*FRACUNIT, // 24 + FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, + kp_lapanim_lap[min(progress/2, 6)], NULL); + + if (progress/2-8 >= 0) + { + V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194 + 30*FRACUNIT, // 24 + FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, + kp_lapanim_number[(((UINT32)stplyr->laps) / 10)][min(progress/2-8, 2)], NULL); + + if (progress/2-10 >= 0) + { + V_DrawFixedPatch((208 + (32*max(0, progress-76)))*FRACUNIT, // 221 + 30*FRACUNIT, // 24 + FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, + kp_lapanim_number[(((UINT32)stplyr->laps) % 10)][min(progress/2-10, 2)], NULL); + } + } + } +} + +void K_drawKartFreePlay(UINT32 flashtime) +{ + // no splitscreen support because it's not FREE PLAY if you have more than one player in-game + // (you fool, you can take splitscreen online. :V) + + if ((flashtime % TICRATE) < TICRATE/2) + return; + + V_DrawKartString((BASEVIDWIDTH - (LAPS_X+1)) - (12*9), // mirror the laps thingy + LAPS_Y+3, V_HUDTRANS|V_SNAPTOBOTTOM|V_SNAPTORIGHT, "FREE PLAY"); +} + +static void K_drawDistributionDebugger(void) +{ + patch_t *items[NUMKARTRESULTS] = { + kp_sadface[1], + kp_sneaker[1], + kp_rocketsneaker[1], + kp_invincibility[7], + kp_banana[1], + kp_eggman[1], + kp_orbinaut[4], + kp_jawz[1], + kp_mine[1], + kp_ballhog[1], + kp_selfpropelledbomb[1], + kp_grow[1], + kp_shrink[1], + kp_thundershield[1], + kp_hyudoro[1], + kp_pogospring[1], + kp_superring[1], + kp_kitchensink[1], + + kp_sneaker[1], + kp_banana[1], + kp_banana[1], + kp_orbinaut[4], + kp_orbinaut[4], + kp_jawz[1] + }; + UINT8 useodds = 0; + UINT8 pingame = 0, bestbumper = 0; + UINT32 pdis = 0; + INT32 i; + INT32 x = -9, y = -9; + boolean spbrush = false; + + if (stplyr != &players[displayplayers[0]]) // only for p1 + return; + + // The only code duplication from the Kart, just to avoid the actual item function from calculating pingame twice + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + pingame++; + if (players[i].kartstuff[k_bumper] > bestbumper) + bestbumper = players[i].kartstuff[k_bumper]; + } + + // lovely double loop...... + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator + && players[i].kartstuff[k_position] == 1) + { + // This player is first! Yay! + pdis = stplyr->distancetofinish - players[i].distancetofinish; + break; + } + } + + if (franticitems) // Frantic items make the distances between everyone artifically higher, for crazier items + pdis = (15 * pdis) / 14; + + if (spbplace != -1 && stplyr->kartstuff[k_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 = ((28 + (8-pingame)) * pdis) / 28; // scale with player count + + useodds = K_FindUseodds(stplyr, 0, pdis, bestbumper, spbrush); + + for (i = 1; i < NUMKARTRESULTS; i++) + { + const INT32 itemodds = K_KartGetItemOdds(useodds, i, 0, spbrush); + if (itemodds <= 0) + continue; + + V_DrawScaledPatch(x, y, V_HUDTRANS|V_SNAPTOTOP, items[i]); + V_DrawThinString(x+11, y+31, V_HUDTRANS|V_SNAPTOTOP, va("%d", itemodds)); + + // Display amount for multi-items + if (i >= NUMKARTITEMS) + { + INT32 amount; + switch (i) + { + case KRITEM_TENFOLDBANANA: + amount = 10; + break; + case KRITEM_QUADORBINAUT: + amount = 4; + break; + case KRITEM_DUALJAWZ: + amount = 2; + break; + default: + amount = 3; + break; + } + V_DrawString(x+24, y+31, V_ALLOWLOWERCASE|V_HUDTRANS|V_SNAPTOTOP, va("x%d", amount)); + } + + x += 32; + if (x >= 297) + { + x = -9; + y += 32; + } + } + + V_DrawString(0, 0, V_HUDTRANS|V_SNAPTOTOP, va("USEODDS %d", useodds)); +} + +static void K_drawCheckpointDebugger(void) +{ + if (stplyr != &players[displayplayers[0]]) // only for p1 + return; + + if (stplyr->starpostnum == numstarposts) + V_DrawString(8, 184, 0, va("Checkpoint: %d / %d (Can finish)", stplyr->starpostnum, numstarposts)); + else + V_DrawString(8, 184, 0, va("Checkpoint: %d / %d", stplyr->starpostnum, numstarposts)); +} + +static void K_DrawWaypointDebugger(void) +{ + if ((cv_kartdebugwaypoints.value != 0) && (stplyr == &players[displayplayers[0]])) + { + V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish)); + } +} + +void K_drawKartHUD(void) +{ + boolean isfreeplay = false; + boolean battlefullscreen = false; + UINT8 i; + + // Define the X and Y for each drawn object + // This is handled by console/menu values + K_initKartHUD(); + + // Draw that fun first person HUD! Drawn ASAP so it looks more "real". + for (i = 0; i <= splitscreen; i++) + { + if (stplyr == &players[displayplayers[i]] && !camera[i].chase) + K_drawKartFirstPerson(); + } + + // Draw full screen stuff that turns off the rest of the HUD + if (mapreset && stplyr == &players[displayplayers[0]]) + { + K_drawChallengerScreen(); + return; + } + + battlefullscreen = ((G_BattleGametype()) + && (stplyr->exiting + || (stplyr->kartstuff[k_bumper] <= 0 + && stplyr->kartstuff[k_comebacktimer] + && comeback + && stplyr->playerstate == PST_LIVE))); + + if (!demo.title && (!battlefullscreen || splitscreen)) + { + // Draw the CHECK indicator before the other items, so it's overlapped by everything else + if (cv_kartcheck.value && !splitscreen && !players[displayplayers[0]].exiting) + K_drawKartPlayerCheck(); + + // Draw WANTED status + if (G_BattleGametype()) + { +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_wanted)) +#endif + K_drawKartWanted(); + } + + if (cv_kartminimap.value) + { +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_minimap)) +#endif + K_drawKartMinimap(); + } + } + + if (battlefullscreen) + { +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_battlefullscreen)) +#endif + K_drawBattleFullscreen(); + return; + } + + // Draw the item window +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_item)) +#endif + K_drawKartItem(); + + // If not splitscreen, draw... + if (!splitscreen && !demo.title) + { + // Draw the timestamp +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_time)) +#endif + K_drawKartTimestamp(stplyr->realtime, TIME_X, TIME_Y, gamemap, 0); + + if (!modeattacking) + { + // The top-four faces on the left + /*#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_minirankings)) + #endif*/ + isfreeplay = K_drawKartPositionFaces(); + } + } + + if (!stplyr->spectator) // Bottom of the screen elements, don't need in spectate mode + { + // Draw the speedometer + if (cv_kartspeedometer.value && !splitscreen) + { +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_speedometer)) +#endif + K_drawKartSpeedometer(); + } + + if (demo.title) // Draw title logo instead in demo.titles + { + INT32 x = BASEVIDWIDTH - 32, y = 128, offs; + + if (splitscreen == 3) + { + x = BASEVIDWIDTH/2 + 10; + y = BASEVIDHEIGHT/2 - 30; + } + + if (timeinmap < 113) + { + INT32 count = ((INT32)(timeinmap) - 104); + offs = 256; + while (count-- > 0) + offs >>= 1; + x += offs; + } + + V_DrawTinyScaledPatch(x-54, y, 0, W_CachePatchName("TTKBANNR", PU_CACHE)); + V_DrawTinyScaledPatch(x-54, y+25, 0, W_CachePatchName("TTKART", PU_CACHE)); + } + else if (G_RaceGametype()) // Race-only elements + { + // Draw the lap counter +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_gametypeinfo)) +#endif + K_drawKartLapsAndRings(); + + if (isfreeplay) + ; + else if (!modeattacking) + { + // Draw the numerical position +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_position)) +#endif + K_DrawKartPositionNum(stplyr->kartstuff[k_position]); + } + else //if (!(demo.playback && hu_showscores)) + { + // Draw the input UI +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_position)) +#endif + K_drawInput(); + } + } + else if (G_BattleGametype()) // Battle-only + { + // Draw the hits left! +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_gametypeinfo)) +#endif + K_drawKartBumpersOrKarma(); + } + } + + // Draw the countdowns after everything else. + if (leveltime >= starttime-(3*TICRATE) + && leveltime < starttime+TICRATE) + K_drawKartStartCountdown(); + else if (countdown && (!splitscreen || !stplyr->exiting)) + { + char *countstr = va("%d", countdown/TICRATE); + + if (splitscreen > 1) + V_DrawCenteredString(BASEVIDWIDTH/4, LAPS_Y+1, K_calcSplitFlags(0), countstr); + else + { + INT32 karlen = strlen(countstr)*6; // half of 12 + V_DrawKartString((BASEVIDWIDTH/2)-karlen, LAPS_Y+3, K_calcSplitFlags(0), countstr); + } + } + + // Race overlays + if (G_RaceGametype()) + { + if (stplyr->exiting) + K_drawKartFinish(); + else if (stplyr->karthud[khud_lapanimation] && !splitscreen) + K_drawLapStartAnim(); + } + + if (modeattacking) // everything after here is MP and debug only + return; + + if (G_BattleGametype() && !splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM * + V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); + + // Draw FREE PLAY. + if (isfreeplay && !stplyr->spectator && timeinmap > 113) + { +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_freeplay)) +#endif + K_drawKartFreePlay(leveltime); + } + + if (stplyr->kartstuff[k_wrongway] && ((leveltime / 8) & 1)) + V_DrawCenteredString(BASEVIDWIDTH>>1, 176, V_REDMAP|V_SNAPTOBOTTOM, "WRONG WAY"); + + if (cv_kartdebugdistribution.value) + K_drawDistributionDebugger(); + + if (cv_kartdebugcheckpoint.value) + K_drawCheckpointDebugger(); + + if (cv_kartdebugnodes.value) + { + UINT8 p; + for (p = 0; p < MAXPLAYERS; p++) + V_DrawString(8, 64+(8*p), V_YELLOWMAP, va("%d - %d (%dl)", p, playernode[p], players[p].cmd.latency)); + } + + if (cv_kartdebugcolorize.value && stplyr->mo && stplyr->mo->skin) + { + INT32 x = 0, y = 0; + UINT8 c; + + for (c = 1; c < MAXSKINCOLORS; c++) + { + UINT8 *cm = R_GetTranslationColormap(TC_RAINBOW, c, GTC_CACHE); + V_DrawFixedPatch(x<>1, 0, facewantprefix[stplyr->skin], cm); + + x += 16; + if (x > BASEVIDWIDTH-16) + { + x = 0; + y += 16; + } + } + } + + K_DrawWaypointDebugger(); +} + +//} diff --git a/src/k_kart.h b/src/k_kart.h index b91e8c8a1..ff6f6fafc 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -27,6 +27,7 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) void K_KartPainEnergyFling(player_t *player); void K_FlipFromObject(mobj_t *mo, mobj_t *master); void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master); +void K_DoIngameRespawn(player_t *player); void K_RespawnChecker(player_t *player); void K_KartMoveAnimation(player_t *player); void K_KartPlayerHUDUpdate(player_t *player); diff --git a/src/k_waypoint.c b/src/k_waypoint.c index 6673cec97..849c0e5c7 100644 --- a/src/k_waypoint.c +++ b/src/k_waypoint.c @@ -11,9 +11,9 @@ static const UINT32 SPARKLES_PER_CONNECTION = 16U; // Some defaults for the size of the dynamically allocated sets for pathfinding. These are kept for the purpose of // allocating a size that is less likely to need reallocating again during the pathfinding. -static const size_t OPENSET_BASE_SIZE = 16U; -static const size_t CLOSEDSET_BASE_SIZE = 256U; -static const size_t NODESARRAY_BASE_SIZE = 256U; +#define OPENSET_BASE_SIZE (16U) +#define CLOSEDSET_BASE_SIZE (256U) +#define NODESARRAY_BASE_SIZE (256U) static waypoint_t *waypointheap = NULL; static waypoint_t *firstwaypoint = NULL; @@ -221,8 +221,11 @@ waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj) for (i = 0; i < numwaypoints; i++) { checkwaypoint = &waypointheap[i]; - checkdist = P_AproxDistance(mobj->x - checkwaypoint->mobj->x, mobj->y - checkwaypoint->mobj->y); - checkdist = P_AproxDistance(checkdist, mobj->z - checkwaypoint->mobj->z); + + checkdist = P_AproxDistance( + (mobj->x / FRACUNIT) - (checkwaypoint->mobj->x / FRACUNIT), + (mobj->y / FRACUNIT) - (checkwaypoint->mobj->y / FRACUNIT)); + checkdist = P_AproxDistance(checkdist, (mobj->z / FRACUNIT) - (checkwaypoint->mobj->z / FRACUNIT)); if (checkdist < closestdist) { @@ -235,6 +238,52 @@ waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj) return closestwaypoint; } +/*-------------------------------------------------- + waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj) + + See header file for description. +--------------------------------------------------*/ +waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj) +{ + waypoint_t *bestwaypoint = NULL; + + if ((mobj == NULL) || P_MobjWasRemoved(mobj)) + { + CONS_Debug(DBG_GAMELOGIC, "NULL mobj in K_GetBestWaypointForMobj.\n"); + } + else + { + size_t i = 0U; + waypoint_t *checkwaypoint = NULL; + fixed_t closestdist = INT32_MAX; + fixed_t checkdist = INT32_MAX; + + for (i = 0; i < numwaypoints; i++) + { + checkwaypoint = &waypointheap[i]; + + checkdist = P_AproxDistance( + (mobj->x / FRACUNIT) - (checkwaypoint->mobj->x / FRACUNIT), + (mobj->y / FRACUNIT) - (checkwaypoint->mobj->y / FRACUNIT)); + checkdist = P_AproxDistance(checkdist, ((mobj->z / FRACUNIT) - (checkwaypoint->mobj->z / FRACUNIT)) * 4); + + if (checkdist < closestdist) + { + if (!P_CheckSight(mobj, checkwaypoint->mobj)) + { + // Save sight checks for the end, so we only do it if we have to + continue; + } + + bestwaypoint = checkwaypoint; + closestdist = checkdist; + } + } + } + + return bestwaypoint; +} + /*-------------------------------------------------- size_t K_GetWaypointHeapIndex(waypoint_t *waypoint) @@ -378,11 +427,11 @@ void K_DebugWaypointsVisualise(void) } else if (waypoint->numnextwaypoints == 0 || waypoint->numprevwaypoints == 0) { - debugmobj->color = SKINCOLOR_ORANGE; + debugmobj->color = SKINCOLOR_YELLOW; } else if (waypoint == players[displayplayers[0]].nextwaypoint) { - debugmobj->color = SKINCOLOR_YELLOW; + debugmobj->color = SKINCOLOR_GREEN; } else { diff --git a/src/k_waypoint.h b/src/k_waypoint.h index d79045119..058ff6882 100644 --- a/src/k_waypoint.h +++ b/src/k_waypoint.h @@ -151,6 +151,22 @@ UINT32 K_GetCircuitLength(void); waypoint_t *K_GetClosestWaypointToMobj(mobj_t *const mobj); +/*-------------------------------------------------- + waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj) + + Similar to K_GetClosestWaypointToMobj, but prioritizes horizontal distance over vertical distance, and + sight checks to ensure that the waypoint and mobj are the in same area. Can potentially return NULL if + there are no visible waypoints. + + Input Arguments:- + mobj - mobj to get the waypoint for. + + Return:- + The best waypoint for the mobj, or NULL if there were no matches +--------------------------------------------------*/ +waypoint_t *K_GetBestWaypointForMobj(mobj_t *const mobj); + + /*-------------------------------------------------- boolean K_PathfindToWaypoint( waypoint_t *const sourcewaypoint, diff --git a/src/m_cheat.c b/src/m_cheat.c index c24a8014b..d61d22cd0 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -292,9 +292,18 @@ void Command_CheatNoClip_f(void) REQUIRE_NOULTIMATE; plyr = &players[consoleplayer]; + + if (!plyr->mo || P_MobjWasRemoved(plyr->mo)) + return; + plyr->pflags ^= PF_NOCLIP; CONS_Printf(M_GetText("No Clipping %s\n"), plyr->pflags & PF_NOCLIP ? M_GetText("On") : M_GetText("Off")); + if (plyr->pflags & PF_NOCLIP) + plyr->mo->flags |= MF_NOCLIP; + else + plyr->mo->flags &= ~MF_NOCLIP; + G_SetGameModified(multiplayer, true); } diff --git a/src/p_enemy.c b/src/p_enemy.c index 96423f6da..521d44b1b 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -8451,7 +8451,7 @@ static void SpawnSPBTrailRings(mobj_t *actor) { if (leveltime % 6 == 0) { - mobj_t *ring = P_SpawnMobj(actor->x - actor->momx, actor->y - actor->momx, + mobj_t *ring = P_SpawnMobj(actor->x - actor->momx, actor->y - actor->momy, actor->z - actor->momz + (24*mapobjectscale), MT_RING); ring->threshold = 10; ring->fuse = 120*TICRATE; @@ -8462,11 +8462,15 @@ static void SpawnSPBTrailRings(mobj_t *actor) void A_SPBChase(mobj_t *actor) { player_t *player = NULL; + player_t *scplayer = NULL; // secondary target for seeking UINT8 i; UINT8 bestrank = UINT8_MAX; fixed_t dist; angle_t hang, vang; fixed_t wspeed, xyspeed, zspeed; + fixed_t pdist = 1536<threshold) // Just fired, go straight. { actor->lastlook = -1; + actor->cusval = -1; spbplace = -1; P_InstaThrust(actor, actor->angle, wspeed); - actor->flags &= ~MF_NOCLIPTHING; // just in case. return; } @@ -8506,18 +8510,22 @@ void A_SPBChase(mobj_t *actor) } } + // lastlook = last player num targetted + // cvmem = stored speed + // cusval = next waypoint heap index + // extravalue1 = SPB movement mode + // extravalue2 = mode misc option + if (actor->extravalue1 == 1) // MODE: TARGETING { + actor->cusval = -1; // Reset waypoint + if (actor->tracer && actor->tracer->health) { - fixed_t defspeed = wspeed; fixed_t range = (160*actor->tracer->scale); fixed_t cx = 0, cy =0; - // we're tailing a player, now's a good time to regain our damage properties - actor->flags &= ~MF_NOCLIPTHING; - // Play the intimidating gurgle if (!S_SoundPlaying(actor, actor->info->activesound)) S_StartSound(actor, actor->info->activesound); @@ -8648,9 +8656,7 @@ void A_SPBChase(mobj_t *actor) else if (actor->extravalue1 == 2) // MODE: WAIT... { actor->momx = actor->momy = actor->momz = 0; // Stoooop - - // don't hurt players that have nothing to do with this: - actor->flags |= MF_NOCLIPTHING; + actor->cusval = -1; // Reset waypoint if (actor->lastlook != -1 && playeringame[actor->lastlook] @@ -8676,8 +8682,11 @@ void A_SPBChase(mobj_t *actor) } else // MODE: SEEKING { - waypoint_t *closestwaypoint = NULL; - waypoint_t *nextwaypoint = NULL; + waypoint_t *lastwaypoint = NULL; + waypoint_t *bestwaypoint = NULL; + waypoint_t *nextwaypoint = NULL; + waypoint_t *tempwaypoint = NULL; + actor->lastlook = -1; // Just make sure this is reset if (!player || !player->mo || player->mo->health <= 0 || player->kartstuff[k_respawn]) @@ -8690,17 +8699,35 @@ void A_SPBChase(mobj_t *actor) } // Found someone, now get close enough to initiate the slaughter... - - // Seeking SPB can now hurt people - actor->flags &= ~MF_NOCLIPTHING; - P_SetTarget(&actor->tracer, player->mo); spbplace = bestrank; - dist = P_AproxDistance(P_AproxDistance(actor->x-actor->tracer->x, actor->y-actor->tracer->y), actor->z-actor->tracer->z); - closestwaypoint = K_GetClosestWaypointToMobj(actor); - if (closestwaypoint != NULL) + // Move along the waypoints until you get close enough + if (actor->cusval > -1 && actor->extravalue2 > 0) + { + // Previously set nextwaypoint + lastwaypoint = K_GetWaypointFromIndex((size_t)actor->cusval); + tempwaypoint = K_GetBestWaypointForMobj(actor); + // check if the tempwaypoint corresponds to lastwaypoint's next ID at least; + // This is to avoid situations where the SPB decides to suicide jump down a bridge because it found a COMPLETELY unrelated waypoint down there. + + if (K_GetWaypointID(tempwaypoint) == K_GetWaypointNextID(lastwaypoint) || K_GetWaypointID(tempwaypoint) == K_GetWaypointID(lastwaypoint)) + // either our previous or curr waypoint ID, sure, take it + bestwaypoint = tempwaypoint; + else + bestwaypoint = K_GetWaypointFromIndex((size_t)actor->extravalue2); // keep going from the PREVIOUS wp. + } + else + bestwaypoint = K_GetBestWaypointForMobj(actor); + + if (bestwaypoint == NULL && lastwaypoint == NULL) + { + // We have invalid waypoints all around, so use closest to try and make it non-NULL. + bestwaypoint = K_GetClosestWaypointToMobj(actor); + } + + if (bestwaypoint != NULL) { const boolean huntbackwards = false; boolean useshortcuts = false; @@ -8712,15 +8739,25 @@ void A_SPBChase(mobj_t *actor) } nextwaypoint = K_GetNextWaypointToDestination( - closestwaypoint, player->nextwaypoint, useshortcuts, huntbackwards); + bestwaypoint, player->nextwaypoint, useshortcuts, huntbackwards); + } + + if (nextwaypoint == NULL && lastwaypoint != NULL) + { + // Restore to the last nextwaypoint + nextwaypoint = lastwaypoint; } if (nextwaypoint != NULL) { const fixed_t xywaypointdist = P_AproxDistance( actor->x - nextwaypoint->mobj->x, actor->y - nextwaypoint->mobj->y); + hang = R_PointToAngle2(actor->x, actor->y, nextwaypoint->mobj->x, nextwaypoint->mobj->y); vang = R_PointToAngle2(0, actor->z, xywaypointdist, nextwaypoint->mobj->z); + + actor->cusval = (INT32)K_GetWaypointHeapIndex(nextwaypoint); + actor->extravalue2 = (INT32)K_GetWaypointHeapIndex(bestwaypoint); // save our last best, used above. } else { @@ -8736,13 +8773,13 @@ void A_SPBChase(mobj_t *actor) if (invert) input = InvAngle(input); + input = FixedAngle(AngleFixed(input)/8); + // Slow down when turning; it looks better and makes U-turns not unfair xyspeed = FixedMul(wspeed, max(0, (((180<angle += input; // Smoothly rotate vert angle @@ -8751,13 +8788,13 @@ void A_SPBChase(mobj_t *actor) if (invert) input = InvAngle(input); + input = FixedAngle(AngleFixed(input)/8); + // Slow down when turning; might as well do it for momz, since we do it above too zspeed = FixedMul(wspeed, max(0, (((180<movedir += input; } @@ -8765,18 +8802,49 @@ void A_SPBChase(mobj_t *actor) actor->momy = FixedMul(FixedMul(xyspeed, FINESINE(actor->angle>>ANGLETOFINESHIFT)), FINECOSINE(actor->movedir>>ANGLETOFINESHIFT)); actor->momz = FixedMul(zspeed, FINESINE(actor->movedir>>ANGLETOFINESHIFT)); + // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh! + for (i=0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || players[i].exiting) + continue; // not in-game + + if (R_PointToDist2(actor->x, actor->y, players[i].mo->x, players[i].mo->y) < pdist) + { + pdist = R_PointToDist2(actor->x, actor->y, players[i].mo->x, players[i].mo->y); + scplayer = &players[i]; // it doesn't matter if we override this guy now. + } + } + + // different player from our main target, try and ram into em~! + if (scplayer && scplayer != player) + { + pangle = actor->angle - R_PointToAngle2(actor->x, actor->y,scplayer->mo->x, scplayer->mo->y); + // check if the angle wouldn't make us LOSE speed... + if ((INT32)pangle/ANG1 >= -80 && (INT32)pangle/ANG1 <= 80) // allow for around 80 degrees + { + // Thrust us towards the guy, try to screw em up! + P_Thrust(actor, R_PointToAngle2(actor->x, actor->y, scplayer->mo->x, scplayer->mo->y), actor->movefactor/4); // not too fast though. + } + } + // Spawn a trail of rings behind the SPB! SpawnSPBTrailRings(actor); - if (dist <= (3072*actor->tracer->scale)) // Close enough to target? + if (dist <= (1024*actor->tracer->scale)) // Close enough to target? { - S_StartSound(actor, actor->info->attacksound); // Siren sound; might not need this anymore, but I'm keeping it for now just for debugging. + S_StartSound(actor, actor->info->attacksound); actor->extravalue1 = 1; // TARGET ACQUIRED actor->extravalue2 = 7*TICRATE; actor->cvmem = wspeed; } } + // Finally, no matter what, the spb should not be able to be under the ground, or above the ceiling; + if (actor->z < actor->floorz) + actor->z = actor->floorz; + else if (actor->z > actor->ceilingz - actor->height) + actor->z = actor->ceilingz - actor->height; + return; } @@ -10618,8 +10686,8 @@ void A_RemoteDamage(mobj_t *actor) if (locvar2 == 1) // Kill mobj! { - if (target->player) // players die using P_DamageMobj instead for some reason - P_DamageMobj(target, source, source, 10000); + if (target->player) + K_DoIngameRespawn(target->player); else P_KillMobj(target, source, source); } diff --git a/src/p_local.h b/src/p_local.h index 1358bf195..23760eaef 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -180,7 +180,6 @@ boolean P_LookForEnemies(player_t *player); void P_NukeEnemies(mobj_t *inflictor, mobj_t *source, fixed_t radius); void P_HomingAttack(mobj_t *source, mobj_t *enemy); /// \todo doesn't belong in p_user //boolean P_SuperReady(player_t *player); -boolean P_AnalogMove(player_t *player); /*boolean P_TransferToNextMare(player_t *player); UINT8 P_FindLowestMare(void);*/ UINT8 P_FindLowestLap(void); diff --git a/src/p_map.c b/src/p_map.c index 38ebdf293..0d5a76c70 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -129,8 +129,6 @@ boolean P_TeleportMove(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z) // MOVEMENT ITERATOR FUNCTIONS // ========================================================================= -//#define TELEPORTJANK - // For our intermediate buffer, remove any duplicate entries by adding each one to // a temprary buffer if it's not already in there, copy the temporary buffer back over the intermediate afterwards static void spechitint_removedups(void) @@ -225,6 +223,8 @@ static boolean P_SpecialIsLinedefCrossType(UINT16 ldspecial) return linedefcrossspecial; } +//#define TELEPORTJANK + boolean P_DoSpring(mobj_t *spring, mobj_t *object) { //INT32 pflags; @@ -256,7 +256,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) object->eflags |= MFE_SPRUNG; // apply this flag asap! spring->flags &= ~(MF_SOLID|MF_SPECIAL); // De-solidify -#ifdef TELEPORTJANK +#if 0 if (horizspeed && vertispeed) // Mimic SA { object->momx = object->momy = 0; @@ -1007,12 +1007,20 @@ static boolean PIT_CheckThing(mobj_t *thing) if (thing->type == MT_PLAYER) { - S_StartSound(NULL, sfx_bsnipe); //let all players hear it. + mobj_t *explosion; + + S_StartSound(NULL, sfx_bsnipe); // let all players hear it. + HU_SetCEchoFlags(0); HU_SetCEchoDuration(5); HU_DoCEcho(va("%s\\was hit by a kitchen sink.\\\\\\\\", player_names[thing->player-players])); I_OutputMsg("%s was hit by a kitchen sink.\n", player_names[thing->player-players]); - P_DamageMobj(thing, tmthing, tmthing->target, 10000); + + explosion = P_SpawnMobj(thing->x, thing->y, thing->z, MT_SPBEXPLOSION); + explosion->extravalue1 = 1; // Tell K_ExplodePlayer to use extra knockback + if (tmthing->target && !P_MobjWasRemoved(tmthing->target)) + P_SetTarget(&explosion->target, tmthing->target); + P_KillMobj(tmthing, thing, thing); } @@ -1271,15 +1279,23 @@ static boolean PIT_CheckThing(mobj_t *thing) } else if (thing->type == MT_SINK) { + mobj_t *explosion; + if ((thing->target == tmthing) && (thing->threshold > 0)) return true; - S_StartSound(NULL, sfx_cgot); //let all players hear it. + S_StartSound(NULL, sfx_bsnipe); // let all players hear it. + HU_SetCEchoFlags(0); HU_SetCEchoDuration(5); HU_DoCEcho(va("%s\\was hit by a kitchen sink.\\\\\\\\", player_names[tmthing->player-players])); I_OutputMsg("%s was hit by a kitchen sink.\n", player_names[tmthing->player-players]); - P_DamageMobj(tmthing, thing, thing->target, 10000); + + explosion = P_SpawnMobj(tmthing->x, tmthing->y, tmthing->z, MT_SPBEXPLOSION); + explosion->extravalue1 = 1; // Tell K_ExplodePlayer to use extra knockback + if (thing->target && !P_MobjWasRemoved(thing->target)) + P_SetTarget(&explosion->target, thing->target); + P_KillMobj(thing, tmthing, tmthing); } @@ -1419,7 +1435,7 @@ static boolean PIT_CheckThing(mobj_t *thing) thing->angle = tmthing->angle; - if (!demo.playback || P_AnalogMove(thing->player)) + if (!demo.playback) { if (thing->player == &players[consoleplayer]) localangle[0] = thing->angle; @@ -1664,7 +1680,7 @@ static boolean PIT_CheckThing(mobj_t *thing) { // Objects kill you if it falls from above. if (thing != tmthing->target) - P_DamageMobj(thing, tmthing, tmthing->target, 10000); + K_DoIngameRespawn(thing->player); tmthing->momz = -tmthing->momz/2; // Bounce, just for fun! // The tmthing->target allows the pusher of the object @@ -2082,7 +2098,7 @@ static boolean PIT_CheckLine(line_t *ld) if (P_BoxOnLineSide(tmbbox, ld) != -1) return true; -if (tmthing->flags & MF_PAPERCOLLISION) // Caution! Turning whilst up against a wall will get you stuck. You probably shouldn't give the player this flag. + if (tmthing->flags & MF_PAPERCOLLISION) // Caution! Turning whilst up against a wall will get you stuck. You probably shouldn't give the player this flag. { fixed_t cosradius, sinradius; cosradius = FixedMul(tmthing->radius, FINECOSINE(tmthing->angle>>ANGLETOFINESHIFT)); @@ -2883,8 +2899,10 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff) // reset this to 0 at the start of each trymove call as it's only used here numspechitint = 0U; - if (radius < MAXRADIUS/2) - radius = MAXRADIUS/2; + // This makes sure that there are no freezes from computing extremely small movements. + // Originally was MAXRADIUS/2, but that causes some inconsistencies for small players. + if (radius < mapobjectscale) + radius = mapobjectscale; do { if (thing->flags & MF_NOCLIP) { diff --git a/src/p_mobj.c b/src/p_mobj.c index b3a0a6f88..4ef291c82 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -47,6 +47,7 @@ consvar_t cv_splats = {"splats", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0 actioncache_t actioncachehead; static mobj_t *overlaycap = NULL; +mobj_t *kitemcap = NULL; // Used for Kart offensive items (the ones that can get removed by sizedown) mobj_t *waypointcap = NULL; void P_InitCachedActions(void) @@ -1655,18 +1656,11 @@ void P_XYMovement(mobj_t *mo) { mo->health--; if (mo->health == 0) - mo->destscale = 1; - } - else - { - if (mo->scale < mapobjectscale/16) - { - P_RemoveMobj(mo); - return; - } + mo->destscale = 0; } } //} + if (!P_TryMove(mo, mo->x + xmove, mo->y + ymove, true) && !(mo->eflags & MFE_SPRUNG)) { // blocked move @@ -6105,6 +6099,71 @@ static boolean P_AddShield(mobj_t *thing) return true; }*/ + +// Kartitem stuff. +boolean P_IsKartItem(INT32 type) +{ + if (type == MT_EGGMANITEM || type == MT_EGGMANITEM_SHIELD || + type == MT_BANANA || type == MT_BANANA_SHIELD || + type == MT_ORBINAUT || type == MT_ORBINAUT_SHIELD || + type == MT_JAWZ || type == MT_JAWZ_DUD || type == MT_JAWZ_SHIELD || + type == MT_SSMINE || type == MT_SSMINE_SHIELD || + type == MT_SINK || type == MT_SINK_SHIELD || + type == MT_SPB) + return true; + else + return false; +} + +// Called when a kart item "thinks" +void P_AddKartItem(mobj_t *thing) +{ + I_Assert(thing != NULL); + + if (kitemcap == NULL) + P_SetTarget(&kitemcap, thing); + else { + mobj_t *mo; + for (mo = kitemcap; mo && mo->itnext; mo = mo->itnext) + ; + + I_Assert(mo != NULL); + I_Assert(mo->itnext == NULL); + + P_SetTarget(&mo->itnext, thing); + } + P_SetTarget(&thing->itnext, NULL); +} + +// Called only when a kart item is removed +// Keeps the hnext list from corrupting. +static void P_RemoveKartItem(mobj_t *thing) +{ + mobj_t *mo; + for (mo = kitemcap; mo; mo = mo->itnext) + if (mo->itnext == thing) + { + P_SetTarget(&mo->itnext, thing->itnext); + P_SetTarget(&thing->itnext, NULL); + return; + } +} + +// Doesn't actually do anything since items have their own thinkers, +// but this is necessary for the sole purpose of updating kitemcap +void P_RunKartItems(void) +{ + mobj_t *mobj, *next; + + for (mobj = kitemcap; mobj; mobj = next) + { + next = mobj->itnext; + P_SetTarget(&mobj->itnext, NULL); + } + P_SetTarget(&kitemcap, NULL); +} + + void P_RunOverlays(void) { // run overlays @@ -6538,16 +6597,25 @@ void P_MobjThinker(mobj_t *mobj) mobj->z -= mobj->height - oldheight; if (mobj->scale == mobj->destscale) + { /// \todo Lua hook for "reached destscale"? - switch(mobj->type) + + if (mobj->scale == 0) { - case MT_EGGMOBILE_FIRE: - mobj->destscale = FRACUNIT; - mobj->scalespeed = FRACUNIT>>4; - break; - default: - break; + P_RemoveMobj(mobj); + return; } + + switch (mobj->type) + { + case MT_EGGMOBILE_FIRE: + mobj->destscale = FRACUNIT; + mobj->scalespeed = FRACUNIT>>4; + break; + default: + break; + } + } } if (mobj->type == MT_GHOST && mobj->fuse > 0 // Not guaranteed to be MF_SCENERY or not MF_SCENERY! @@ -9728,6 +9796,12 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s } } + if (P_MobjWasRemoved(mobj)) + return; // obligatory paranoia check + + if (P_IsKartItem(mobj->type)) // mobj is a kart item we want on the list: + P_AddKartItem(mobj); // add to kitem list + // Can end up here if a player dies. if (mobj->player) P_CyclePlayerMobjState(mobj); @@ -10408,6 +10482,10 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) } } break; + case MT_BOSS3WAYPOINT: + // Remove before release + CONS_Alert(CONS_WARNING, "Boss waypoints are deprecated. Did you forget to remove the old checkpoints, too?\n"); + break; default: break; } @@ -10563,6 +10641,9 @@ void P_RemoveMobj(mobj_t *mobj) if (mobj->type == MT_SPB) spbplace = -1; + if (P_IsKartItem(mobj->type)) + P_RemoveKartItem(mobj); + mobj->health = 0; // Just because // unlink from sector and block lists @@ -11148,6 +11229,9 @@ void P_SpawnPlayer(INT32 playernum) // Spawn with a pity shield if necessary. //P_DoPityCheck(p); + if (p->kartstuff[k_respawn] != 0) + p->mo->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY; + if (G_BattleGametype()) // SRB2kart { mobj_t *overheadarrow = P_SpawnMobj(mobj->x, mobj->y, mobj->z + P_GetPlayerHeight(p)+16*FRACUNIT, MT_PLAYERARROW); @@ -11364,11 +11448,10 @@ void P_MovePlayerToStarpost(INT32 playernum) sector->ceilingheight; if (mobj->player->kartstuff[k_starpostflip]) - z = (p->starpostz<height; + z = (p->starpostz<height; else - z = (p->starpostz<starpostz<starpostz + 128) << FRACBITS; // reverse gravity exists, pls mobj->player->kartstuff[k_starpostflip] = 0; if (z < floor) @@ -11962,7 +12045,7 @@ ML_NOCLIMB : Direction not controllable case MT_WAYPOINT: { size_t line; - mobj->radius = 256*FRACUNIT; + mobj->radius = 384*FRACUNIT; // Same reason as for MT_SPINMACEPOINT we can't use the function to find the linedef for (line = 0; line < numlines; line++) { diff --git a/src/p_mobj.h b/src/p_mobj.h index 03a19870c..5cc93a56d 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -319,6 +319,9 @@ typedef struct mobj_s struct mobj_s *hnext; struct mobj_s *hprev; + // One last pointer for kart item lists + struct mobj_s *itnext; + INT32 health; // for player this is rings + 1 // Movement direction, movement generation (zig-zagging). @@ -437,12 +440,18 @@ typedef struct actioncache_s extern actioncache_t actioncachehead; +extern mobj_t *kitemcap; extern mobj_t *waypointcap; void P_InitCachedActions(void); void P_RunCachedActions(void); void P_AddCachedAction(mobj_t *mobj, INT32 statenum); +// kartitem stuff: Returns true if the specified 'type' is one of the kart item constants we want in the kitemcap list +boolean P_IsKartItem(INT32 type); +void P_AddKartItem(mobj_t *thing); // needs to be called in k_kart.c +void P_RunKartItems(void); + // check mobj against water content, before movement code void P_MobjCheckWater(mobj_t *mobj); diff --git a/src/p_saveg.c b/src/p_saveg.c index d51240521..4767b09d1 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -957,9 +957,11 @@ typedef enum MD2_HNEXT = 1<<7, MD2_HPREV = 1<<8, MD2_COLORIZED = 1<<9, - MD2_WAYPOINTCAP = 1<<10 + MD2_WAYPOINTCAP = 1<<10, + MD2_KITEMCAP = 1<<11, + MD2_ITNEXT = 1<<12 #ifdef ESLOPE - , MD2_SLOPE = 1<<11 + , MD2_SLOPE = 1<<13 #endif } mobj_diff2_t; @@ -1151,6 +1153,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) diff2 |= MD2_HNEXT; if (mobj->hprev) diff2 |= MD2_HPREV; + if (mobj->itnext) + diff2 |= MD2_ITNEXT; #ifdef ESLOPE if (mobj->standingslope) diff2 |= MD2_SLOPE; @@ -1159,6 +1163,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) diff2 |= MD2_COLORIZED; if (mobj == waypointcap) diff2 |= MD2_WAYPOINTCAP; + if (mobj == kitemcap) + diff2 |= MD2_KITEMCAP; if (diff2 != 0) diff |= MD_MORE; @@ -1274,6 +1280,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) WRITEUINT32(save_p, mobj->hnext->mobjnum); if (diff2 & MD2_HPREV) WRITEUINT32(save_p, mobj->hprev->mobjnum); + if (diff2 & MD2_ITNEXT) + WRITEUINT32(save_p, mobj->itnext->mobjnum); #ifdef ESLOPE if (diff2 & MD2_SLOPE) WRITEUINT16(save_p, mobj->standingslope->id); @@ -2151,6 +2159,8 @@ static void LoadMobjThinker(actionf_p1 thinker) mobj->hnext = (mobj_t *)(size_t)READUINT32(save_p); if (diff2 & MD2_HPREV) mobj->hprev = (mobj_t *)(size_t)READUINT32(save_p); + if (diff2 & MD2_ITNEXT) + mobj->itnext = (mobj_t *)(size_t)READUINT32(save_p); #ifdef ESLOPE if (diff2 & MD2_SLOPE) { @@ -2192,6 +2202,9 @@ static void LoadMobjThinker(actionf_p1 thinker) if (diff2 & MD2_WAYPOINTCAP) P_SetTarget(&waypointcap, mobj); + if (diff2 & MD2_KITEMCAP) + P_SetTarget(&kitemcap, mobj); + mobj->info = (mobjinfo_t *)next; // temporarily, set when leave this function } @@ -3044,6 +3057,13 @@ static void P_RelinkPointers(void) if (!(mobj->hprev = P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "hprev not found on %d\n", mobj->type); } + if (mobj->itnext) + { + temp = (UINT32)(size_t)mobj->itnext; + mobj->itnext = NULL; + if (!(mobj->itnext = P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "itnext not found on %d\n", mobj->type); + } if (mobj->player && mobj->player->capsule) { temp = (UINT32)(size_t)mobj->player->capsule; @@ -3324,7 +3344,7 @@ static void P_NetArchiveMisc(void) WRITEUINT32(save_p, hyubgone); WRITEUINT32(save_p, mapreset); - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < MAXPLAYERS; i++) WRITEINT16(save_p, nospectategrief[i]); WRITEUINT8(save_p, thwompsactive); @@ -3447,7 +3467,7 @@ static inline boolean P_NetUnArchiveMisc(void) hyubgone = READUINT32(save_p); mapreset = READUINT32(save_p); - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < MAXPLAYERS; i++) nospectategrief[i] = READINT16(save_p); thwompsactive = (boolean)READUINT8(save_p); diff --git a/src/p_sight.c b/src/p_sight.c index 626f8bbef..f230f40f6 100644 --- a/src/p_sight.c +++ b/src/p_sight.c @@ -2,7 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 1998-2000 by DooM Legacy Team. -// Copyright (C) 1999-2018 by Sonic Team Junior. +// Copyright (C) 1999-2020 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,6 +14,7 @@ #include "doomdef.h" #include "doomstat.h" #include "p_local.h" +#include "p_slopes.h" #include "r_main.h" #include "r_state.h" @@ -103,12 +104,20 @@ static fixed_t P_InterceptVector2(divline_t *v2, divline_t *v1) static boolean P_CrossSubsecPolyObj(polyobj_t *po, register los_t *los) { size_t i; + sector_t *polysec; + + if (!(po->flags & POF_RENDERALL)) + return true; // the polyobject isn't visible, so we can ignore it + + polysec = po->lines[0]->backsector; for (i = 0; i < po->numLines; ++i) { line_t *line = po->lines[i]; divline_t divl; const vertex_t *v1,*v2; + fixed_t frac; + fixed_t topslope, bottomslope; // already checked other side? if (line->validcount == validcount) @@ -140,7 +149,22 @@ static boolean P_CrossSubsecPolyObj(polyobj_t *po, register los_t *los) continue; // stop because it is not two sided - return false; + //if (!(po->flags & POF_TESTHEIGHT)) + //return false; + + frac = P_InterceptVector2(&los->strace, &divl); + + // get slopes of top and bottom of this polyobject line + topslope = FixedDiv(polysec->ceilingheight - los->sightzstart , frac); + bottomslope = FixedDiv(polysec->floorheight - los->sightzstart , frac); + + if (topslope >= los->topslope && bottomslope <= los->bottomslope) + return false; // view completely blocked + + // TODO: figure out if it's worth considering partially blocked cases or not? + // maybe to adjust los's top/bottom slopes if needed + //if (los->topslope <= los->bottomslope) + //return false; } return true; @@ -193,6 +217,15 @@ static boolean P_CrossSubsector(size_t num, register los_t *los) const sector_t *front, *back; const vertex_t *v1,*v2; fixed_t frac; + fixed_t frontf, backf, frontc, backc; +#ifdef ESLOPE + fixed_t fracx, fracy; +#endif + + /* SRB2Kart doesn't have this? + if (seg->glseg) + continue; + */ // already checked other side? if (line->validcount == validcount) @@ -227,36 +260,51 @@ static boolean P_CrossSubsector(size_t num, register los_t *los) if (!(line->flags & ML_TWOSIDED)) return false; + // calculate fractional intercept (how far along we are divided by how far we are from t2) + frac = P_InterceptVector2(&los->strace, &divl); + + front = seg->frontsector; + back = seg->backsector; +#ifdef ESLOPE + // calculate position at intercept + fracx = los->strace.x + FixedMul(los->strace.dx, frac); + fracy = los->strace.y + FixedMul(los->strace.dy, frac); + // calculate sector heights + frontf = (front->f_slope) ? P_GetZAt(front->f_slope, fracx, fracy) : front->floorheight; + frontc = (front->c_slope) ? P_GetZAt(front->c_slope, fracx, fracy) : front->ceilingheight; + backf = (back->f_slope) ? P_GetZAt(back->f_slope, fracx, fracy) : back->floorheight; + backc = (back->c_slope) ? P_GetZAt(back->c_slope, fracx, fracy) : back->ceilingheight; +#else + frontf = front->floorheight; + frontc = front->ceilingheight; + backf = back->floorheight; + backc = back->ceilingheight; +#endif // crosses a two sided line // no wall to block sight with? - if ((front = seg->frontsector)->floorheight == - (back = seg->backsector)->floorheight && - front->ceilingheight == back->ceilingheight) + if (frontf == backf && frontc == backc + && !front->ffloors & !back->ffloors) // (and no FOFs) continue; // possible occluder // because of ceiling height differences - popentop = front->ceilingheight < back->ceilingheight ? - front->ceilingheight : back->ceilingheight ; + popentop = min(frontc, backc); // because of floor height differences - popenbottom = front->floorheight > back->floorheight ? - front->floorheight : back->floorheight ; + popenbottom = max(frontf, backf); // quick test for totally closed doors if (popenbottom >= popentop) return false; - frac = P_InterceptVector2(&los->strace, &divl); - - if (front->floorheight != back->floorheight) + if (frontf != backf) { fixed_t slope = FixedDiv(popenbottom - los->sightzstart , frac); if (slope > los->bottomslope) los->bottomslope = slope; } - if (front->ceilingheight != back->ceilingheight) + if (frontc != backc) { fixed_t slope = FixedDiv(popentop - los->sightzstart , frac); if (slope < los->topslope) @@ -265,6 +313,58 @@ static boolean P_CrossSubsector(size_t num, register los_t *los) if (los->topslope <= los->bottomslope) return false; + + // Monster Iestyn: check FOFs! + if (front->ffloors || back->ffloors) + { + ffloor_t *rover; + fixed_t topslope, bottomslope; + fixed_t topz, bottomz; + // check front sector's FOFs first + for (rover = front->ffloors; rover; rover = rover->next) + { + if (!(rover->flags & FF_EXISTS) + || !(rover->flags & FF_RENDERSIDES) || rover->flags & FF_TRANSLUCENT) + { + continue; + } + +#ifdef ESLOPE + topz = (*rover->t_slope) ? P_GetZAt(*rover->t_slope, fracx, fracy) : *rover->topheight; + bottomz = (*rover->b_slope) ? P_GetZAt(*rover->b_slope, fracx, fracy) : *rover->bottomheight; +#else + topz = *rover->topheight; + bottomz = *rover->bottomheight; +#endif + topslope = FixedDiv(topz - los->sightzstart , frac); + bottomslope = FixedDiv(bottomz - los->sightzstart , frac); + if (topslope >= los->topslope && bottomslope <= los->bottomslope) + return false; // view completely blocked + } + // check back sector's FOFs as well + for (rover = back->ffloors; rover; rover = rover->next) + { + if (!(rover->flags & FF_EXISTS) + || !(rover->flags & FF_RENDERSIDES) || rover->flags & FF_TRANSLUCENT) + { + continue; + } + +#ifdef ESLOPE + topz = (*rover->t_slope) ? P_GetZAt(*rover->t_slope, fracx, fracy) : *rover->topheight; + bottomz = (*rover->b_slope) ? P_GetZAt(*rover->b_slope, fracx, fracy) : *rover->bottomheight; +#else + topz = *rover->topheight; + bottomz = *rover->bottomheight; +#endif + topslope = FixedDiv(topz - los->sightzstart , frac); + bottomslope = FixedDiv(bottomz - los->sightzstart , frac); + if (topslope >= los->topslope && bottomslope <= los->bottomslope) + return false; // view completely blocked + } + // TODO: figure out if it's worth considering partially blocked cases or not? + // maybe to adjust los's top/bottom slopes if needed + } } // passed the subsector ok @@ -375,6 +475,8 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2) if (s1 == s2) // Both sectors are the same. { ffloor_t *rover; + fixed_t topz1, bottomz1; // top, bottom heights at t1's position + fixed_t topz2, bottomz2; // likewise but for t2 for (rover = s1->ffloors; rover; rover = rover->next) { @@ -382,14 +484,35 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2) /// \todo Improve by checking fog density/translucency /// and setting a sight limit. if (!(rover->flags & FF_EXISTS) - || !(rover->flags & FF_RENDERPLANES) || rover->flags & FF_TRANSLUCENT) + || !(rover->flags & FF_RENDERPLANES) /*|| (rover->flags & FF_TRANSLUCENT)*/) { continue; } +#ifdef ESLOPE + if (*rover->t_slope) + { + topz1 = P_GetZAt(*rover->t_slope, t1->x, t1->y); + topz2 = P_GetZAt(*rover->t_slope, t2->x, t2->y); + } + else + topz1 = topz2 = *rover->topheight; + + if (*rover->b_slope) + { + bottomz1 = P_GetZAt(*rover->b_slope, t1->x, t1->y); + bottomz2 = P_GetZAt(*rover->b_slope, t2->x, t2->y); + } + else + bottomz1 = bottomz2 = *rover->bottomheight; +#else + topz1 = topz2 = *rover->topheight; + bottomz1 = bottomz2 = *rover->bottomheight; +#endif + // Check for blocking floors here. - if ((los.sightzstart < *rover->bottomheight && t2->z >= *rover->topheight) - || (los.sightzstart >= *rover->topheight && t2->z + t2->height < *rover->bottomheight)) + if ((los.sightzstart < bottomz1 && t2->z >= topz2) + || (los.sightzstart >= topz1 && t2->z + t2->height < bottomz2)) { // no way to see through that return false; @@ -400,19 +523,19 @@ boolean P_CheckSight(mobj_t *t1, mobj_t *t2) if (!(rover->flags & FF_INVERTPLANES)) { - if (los.sightzstart >= *rover->topheight && t2->z + t2->height < *rover->topheight) + if (los.sightzstart >= topz1 && t2->z + t2->height < topz2) return false; // blocked by upper outside plane - if (los.sightzstart < *rover->bottomheight && t2->z >= *rover->bottomheight) + if (los.sightzstart < bottomz1 && t2->z >= bottomz2) return false; // blocked by lower outside plane } if (rover->flags & FF_INVERTPLANES || rover->flags & FF_BOTHPLANES) { - if (los.sightzstart < *rover->topheight && t2->z >= *rover->topheight) + if (los.sightzstart < topz1 && t2->z >= topz2) return false; // blocked by upper inside plane - if (los.sightzstart >= *rover->bottomheight && t2->z + t2->height < *rover->bottomheight) + if (los.sightzstart >= bottomz1 && t2->z + t2->height < bottomz2) return false; // blocked by lower inside plane } } diff --git a/src/p_spec.c b/src/p_spec.c index 1f92ecd5a..c4070ef5c 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1705,41 +1705,6 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller if (!(ALL7EMERALDS(emeralds))) return false; } - else if (GETSECSPECIAL(caller->special, 2) == 7) // SRB2Kart: reusing for Race Lap executor - { - UINT8 lap; - - if (actor && actor->player && triggerline->flags & ML_EFFECT4) - { - /*if (maptol & TOL_NIGHTS) - lap = actor->player->mare; - else*/ - lap = actor->player->laps; - } - else - { - /*if (maptol & TOL_NIGHTS) - lap = P_FindLowestMare(); - else*/ - lap = P_FindLowestLap(); - } - - if (triggerline->flags & ML_NOCLIMB) // Need higher than or equal to - { - if (lap < (sides[triggerline->sidenum[0]].textureoffset >> FRACBITS)) - return false; - } - else if (triggerline->flags & ML_BLOCKMONSTERS) // Need lower than or equal to - { - if (lap > (sides[triggerline->sidenum[0]].textureoffset >> FRACBITS)) - return false; - } - else // Need equal to - { - if (lap != (sides[triggerline->sidenum[0]].textureoffset >> FRACBITS)) - return false; - } - } // If we were not triggered by a sector type especially for the purpose, // a Linedef Executor linedef trigger is not handling sector triggers properly, return. @@ -1937,15 +1902,17 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller else // These special types work only once if (specialtype == 302 // Once - || specialtype == 304 // Ring count - Once - || specialtype == 307 // Character ability - Once - || specialtype == 308 // Race only - Once - || specialtype == 315 // No of pushables - Once - || specialtype == 318 // Unlockable trigger - Once - || specialtype == 320 // Unlockable - Once - || specialtype == 321 || specialtype == 322 // Trigger on X calls - Continuous + Each Time - || specialtype == 328 // Encore Load - || specialtype == 399) // Level Load + || specialtype == 304 // Ring count - Once + || specialtype == 307 // Character ability - Once + || specialtype == 308 // Race only - Once + || specialtype == 315 // No of pushables - Once + || specialtype == 318 // Unlockable trigger - Once + || specialtype == 320 // Unlockable - Once + || specialtype == 321 || specialtype == 322 // Trigger on X calls - Continuous + Each Time + || specialtype == 328 // Encore Load + || specialtype == 399 // Level Load + || specialtype == 2002 // SRB2Kart Race Lap + ) triggerline->special = 0; // Clear it out return true; @@ -1981,6 +1948,7 @@ void P_LinedefExecute(INT16 tag, mobj_t *actor, sector_t *caller) if (lines[masterline].special == 313 || lines[masterline].special == 399 || lines[masterline].special == 328 + || lines[masterline].special == 2002 // SRB2Kart race lap trigger // Each-time executors handle themselves, too || lines[masterline].special == 301 // Each time || lines[masterline].special == 306 // Character ability - Each time @@ -2096,7 +2064,7 @@ static void K_HandleLapIncrement(player_t *player) { if ((player->starpostnum == numstarposts) || (player->laps == 0)) { - UINT8 i = 0; + size_t i = 0; UINT8 nump = 0; for (i = 0; i < MAXPLAYERS; i++) @@ -2172,17 +2140,42 @@ static void K_HandleLapIncrement(player_t *player) P_SetupSignExit(player); } - //player->starpostangle = player->starposttime = player->starpostnum = 0; - //player->starpostx = player->starposty = player->starpostz = 0; - - // Play the starpost sound for 'consistency' - // S_StartSound(player->mo, sfx_strpst); - - // Figure out how many are playing on the last lap, to prevent spectate griefing - if (!nospectategrief && player->laps > (UINT8)(cv_numlaps.value)) - nospectategrief = nump; - thwompsactive = true; // Lap 2 effects + + for (i = 0; i < numlines; i++) + { + if (lines[i].special == 2002) // Race lap trigger + { + UINT8 lap; + + if (lines[i].flags & ML_EFFECT4) + { + lap = player->laps; + } + else + { + lap = P_FindLowestLap(); + } + + if (lines[i].flags & ML_NOCLIMB) // Need higher than or equal to + { + if (lap < (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS)) + continue; + } + else if (lines[i].flags & ML_BLOCKMONSTERS) // Need lower than or equal to + { + if (lap > (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS)) + continue; + } + else // Need equal to + { + if (lap != (sides[lines[i].sidenum[0]].textureoffset >> FRACBITS)) + continue; + } + + P_RunTriggerLinedef(&lines[i], player->mo, NULL); + } + } } else if (player->starpostnum) { @@ -2196,7 +2189,7 @@ static void K_HandleLapDecrement(player_t *player) { if (player) { - if (player->laps > 0) + if ((player->starpostnum == 0) && (player->laps > 0)) { player->starpostnum = numstarposts; player->laps--; @@ -2220,7 +2213,7 @@ void P_CrossSpecialLine(line_t *line, INT32 side, mobj_t *thing) { case 2001: // Finish Line { - if (G_RaceGametype() && !(player->exiting)) + if (G_RaceGametype() && !(player->exiting) && !(player->pflags & PF_HITFINISHLINE)) { if (((line->flags & (ML_NOCLIMB)) && (side == 0)) || (!(line->flags & (ML_NOCLIMB)) && (side == 1))) // crossed from behind to infront @@ -2231,6 +2224,8 @@ void P_CrossSpecialLine(line_t *line, INT32 side, mobj_t *thing) { K_HandleLapDecrement(player); } + + player->pflags |= PF_HITFINISHLINE; } } break; @@ -3776,10 +3771,10 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers case 6: // Death Pit (Camera Mod) case 7: // Death Pit (No Camera Mod) if (roversector || P_MobjReadyToTrigger(player->mo, sector)) - P_DamageMobj(player->mo, NULL, NULL, 10000); + K_DoIngameRespawn(player); break; case 8: // Instant Kill - P_DamageMobj(player->mo, NULL, NULL, 10000); + K_DoIngameRespawn(player); break; case 9: // Ring Drainer (Floor Touch) case 10: // Ring Drainer (No Floor Touch) @@ -4017,7 +4012,7 @@ DoneSection2: if (player->mo->scale > mapobjectscale) linespeed = FixedMul(linespeed, mapobjectscale + (player->mo->scale - mapobjectscale)); - if (!demo.playback || P_AnalogMove(player)) + if (!demo.playback) { if (player == &players[consoleplayer]) localangle[0] = player->mo->angle; @@ -5782,6 +5777,8 @@ void P_SpawnSpecials(INT32 fromnetsave) switch(GETSECSPECIAL(sector->special, 4)) { case 10: // Circuit finish line (Unused) + // Remove before release + CONS_Alert(CONS_WARNING, "Finish line sector type is deprecated.\n"); break; } } @@ -6712,12 +6709,15 @@ void P_SpawnSpecials(INT32 fromnetsave) sectors[s].midmap = lines[i].frontsector->midmap; break; + // SRB2Kart case 2000: // Waypoint Parameters break; case 2001: // Finish Line if (G_RaceGametype()) circuitmap = true; break; + case 2002: // Linedef Trigger: Race Lap + break; default: break; } @@ -6938,8 +6938,8 @@ void T_Scroll(scroll_t *s) height = P_GetSpecialBottomZ(thing, sec, psec); - if (!(thing->flags & MF_NOCLIP)) // Thing must be clipped - if (!(thing->flags & MF_NOGRAVITY || thing->z+thing->height != height)) // Thing must a) be non-floating and have z+height == height + if (!(thing->flags & MF_NOCLIP) && // Thing must be clipped + (!(thing->flags & MF_NOGRAVITY || thing->z+thing->height != height))) // Thing must a) be non-floating and have z+height == height { // Move objects only if on floor // non-floating, and clipped. @@ -7014,8 +7014,8 @@ void T_Scroll(scroll_t *s) height = P_GetSpecialTopZ(thing, sec, psec); - if (!(thing->flags & MF_NOCLIP)) // Thing must be clipped - if (!(thing->flags & MF_NOGRAVITY || thing->z != height))// Thing must a) be non-floating and have z == height + if (!(thing->flags & MF_NOCLIP) && // Thing must be clipped + (!(thing->flags & MF_NOGRAVITY || thing->z != height))) // Thing must a) be non-floating and have z == height { // Move objects only if on floor or underwater, // non-floating, and clipped. @@ -7910,7 +7910,7 @@ void T_Pusher(pusher_t *p) thing->player->pflags |= PF_SLIDING; thing->angle = R_PointToAngle2 (0, 0, xspeed<<(FRACBITS-PUSH_FACTOR), yspeed<<(FRACBITS-PUSH_FACTOR)); - if (!demo.playback || P_AnalogMove(thing->player)) + if (!demo.playback) { if (thing->player == &players[consoleplayer]) { diff --git a/src/p_tick.c b/src/p_tick.c index 31fb95937..1c87218b9 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -173,6 +173,7 @@ void P_InitThinkers(void) { thinkercap.prev = thinkercap.next = &thinkercap; waypointcap = NULL; + kitemcap = NULL; } // @@ -629,6 +630,9 @@ void P_Ticker(boolean run) if (runemeraldmanager) P_EmeraldManager(); // Power stone mode*/ + // formality so kitemcap gets updated properly each frame. + P_RunKartItems(); + if (run) { P_RunThinkers(); diff --git a/src/p_user.c b/src/p_user.c index bfd953f3e..6267559e0 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -3716,11 +3716,6 @@ void P_Telekinesis(player_t *player, fixed_t thrust, fixed_t range) player->pflags |= PF_THOKKED; } -boolean P_AnalogMove(player_t *player) -{ - return player->pflags & PF_ANALOGMODE; -} - // // P_GetPlayerControlDirection // @@ -3763,14 +3758,6 @@ boolean P_AnalogMove(player_t *player) origtempangle = tempangle = 0; // relative to the axis rather than the player! controlplayerdirection = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); } - else if (P_AnalogMove(player) && thiscam->chase) - { - if (player->awayviewtics) - origtempangle = tempangle = player->awayviewmobj->angle; - else - origtempangle = tempangle = thiscam->angle; - controlplayerdirection = player->mo->angle; - } else { origtempangle = tempangle = player->mo->angle; @@ -3994,7 +3981,6 @@ static void P_3dMovement(player_t *player) angle_t dangle; // replaces old quadrants bits //boolean dangleflip = false; // SRB2kart - toaster //fixed_t normalspd = FixedMul(player->normalspeed, player->mo->scale); - boolean analogmove = false; fixed_t oldMagnitude, newMagnitude; #ifdef ESLOPE vector3_t totalthrust; @@ -4006,8 +3992,6 @@ static void P_3dMovement(player_t *player) // Get the old momentum; this will be needed at the end of the function! -SH oldMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0); - analogmove = P_AnalogMove(player); - cmd = &player->cmd; if ((player->exiting || mapreset) || player->pflags & PF_STASIS || player->kartstuff[k_spinouttimer]) // pw_introcam? @@ -4020,19 +4004,13 @@ static void P_3dMovement(player_t *player) if (!(player->pflags & PF_FORCESTRAFE) && !player->kartstuff[k_pogospring]) cmd->sidemove = 0; - if (analogmove) - { - movepushangle = (cmd->angleturn<<16 /* not FRACBITS */); - } + if (player->kartstuff[k_drift] != 0) + movepushangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift]; + else if (player->kartstuff[k_spinouttimer] || player->kartstuff[k_wipeoutslow]) // if spun out, use the boost angle + movepushangle = (angle_t)player->kartstuff[k_boostangle]; else - { - if (player->kartstuff[k_drift] != 0) - movepushangle = player->mo->angle-(ANGLE_45/5)*player->kartstuff[k_drift]; - else if (player->kartstuff[k_spinouttimer] || player->kartstuff[k_wipeoutslow]) // if spun out, use the boost angle - movepushangle = (angle_t)player->kartstuff[k_boostangle]; - else - movepushangle = player->mo->angle; - } + movepushangle = player->mo->angle; + movepushsideangle = movepushangle-ANGLE_90; // cmomx/cmomy stands for the conveyor belt speed. @@ -6191,69 +6169,6 @@ static void P_MovePlayer(player_t *player) player->pflags &= ~PF_STARTDASH; */ - ////////////////// - //ANALOG CONTROL// - ////////////////// - -#if 0 - // This really looks like it should be moved to P_3dMovement. -Red - if (P_AnalogMove(player) - && (cmd->forwardmove != 0 || cmd->sidemove != 0) && !player->climbing && !twodlevel && !(player->mo->flags2 & MF2_TWOD)) - { - // If travelling slow enough, face the way the controls - // point and not your direction of movement. - if (player->speed < FixedMul(5*FRACUNIT, player->mo->scale) || player->pflags & PF_GLIDING || !onground) - { - angle_t tempangle; - - tempangle = (cmd->angleturn << 16); - -#ifdef REDSANALOG // Ease to it. Chillax. ~Red - tempangle += R_PointToAngle2(0, 0, cmd->forwardmove*FRACUNIT, -cmd->sidemove*FRACUNIT); - { - fixed_t tweenvalue = max(abs(cmd->forwardmove), abs(cmd->sidemove)); - - if (tweenvalue < 10 && (cmd->buttons & (BT_FORWARD|BT_BACKWARD)) == (BT_FORWARD|BT_BACKWARD)) { - tempangle = (cmd->angleturn << 16); - tweenvalue = 16; - } - - tweenvalue *= tweenvalue*tweenvalue*1536; - - //if (player->pflags & PF_GLIDING) - //tweenvalue >>= 1; - - tempangle -= player->mo->angle; - - if (tempangle < ANGLE_180 && tempangle > tweenvalue) - player->mo->angle += tweenvalue; - else if (tempangle >= ANGLE_180 && InvAngle(tempangle) > tweenvalue) - player->mo->angle -= tweenvalue; - else - player->mo->angle += tempangle; - } -#else - // Less math this way ~Red - player->mo->angle = R_PointToAngle2(0, 0, cmd->forwardmove*FRACUNIT, -cmd->sidemove*FRACUNIT)+tempangle; -#endif - } - // Otherwise, face the direction you're travelling. - else if (player->panim == PA_WALK || player->panim == PA_RUN || player->panim == PA_ROLL - /*|| ((player->mo->state >= &states[S_PLAY_ABL1] && player->mo->state <= &states[S_PLAY_SPC4]) && player->charability == CA_FLY)*/) // SRB2kart - idk - player->mo->angle = R_PointToAngle2(0, 0, player->rmomx, player->rmomy); - - // Update the local angle control. - if (player == &players[consoleplayer]) - localangle[0] = player->mo->angle; - else if (player == &players[displayplayers[1]]) - localangle[1] = player->mo->angle; - else if (player == &players[displayplayers[2]]) - localangle[2] = player->mo->angle; - else if (player == &players[displayplayers[3]]) - localangle[3] = player->mo->angle; - } -#endif - /////////////////////////// //BOMB SHIELD ACTIVATION,// //HOMING, AND OTHER COOL // @@ -8132,12 +8047,6 @@ void P_PlayerThink(player_t *player) // The timer might've reached zero, but we'll run the remote view camera anyway by setting it to -1. } - /// \note do this in the cheat code - if (player->pflags & PF_NOCLIP) - player->mo->flags |= MF_NOCLIP; - else - player->mo->flags &= ~MF_NOCLIP; - cmd = &player->cmd; // SRB2kart @@ -8370,26 +8279,7 @@ void P_PlayerThink(player_t *player) player->mo->reactiontime--; else if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT) { - // SRB2kart - don't need no rope hangin' - //if (player->pflags & PF_ROPEHANG) - //{ - // if (!P_AnalogMove(player)) - // player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */); - - // ticruned++; - // if ((cmd->angleturn & TICCMD_RECEIVED) == 0) - // ticmiss++; - - // P_DoRopeHang(player); - // P_SetPlayerMobjState(player->mo, S_PLAY_CARRY); - // P_DoJumpStuff(player, &player->cmd); - //} - //else - { - P_DoZoomTube(player); - //if (!(player->panim == PA_ROLL) && player->charability2 == CA2_SPINDASH) // SRB2kart - // P_SetPlayerMobjState(player->mo, S_PLAY_ATK1); - } + P_DoZoomTube(player); player->rmomx = player->rmomy = 0; // no actual momentum from your controls P_ResetScore(player); }