diff --git a/.travis.yml b/.travis.yml index 789426be7..9abe8ee2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -568,7 +568,7 @@ matrix: - p7zip-full - gcc-4.8 compiler: gcc-4.8 - dist: xenial + dist: trusty if: env(DPL_ENABLED) = "1" AND (env(_DPL_JOB_ENABLED) = "1" OR env(DPL_JOB_ENABLE_ALL) = "1") AND (branch =~ /^.*deployer.*$/ OR (tag IS present AND env(DPL_TAG_ENABLED) = "1")) AND env(DPL_TERMINATE_MAIN) != "1" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 130d0a325..b04068609 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,7 +3,6 @@ # Core sources set(SRB2_CORE_SOURCES am_map.c - b_bot.c command.c comptime.c console.c @@ -20,6 +19,7 @@ set(SRB2_CORE_SOURCES g_game.c g_input.c g_splitscreen.c + font.c hu_stuff.c i_tcp.c info.c @@ -37,6 +37,7 @@ set(SRB2_CORE_SOURCES m_random.c md5.c mserv.c + http-mserv.c s_sound.c screen.c sounds.c @@ -51,7 +52,6 @@ set(SRB2_CORE_SOURCES set(SRB2_CORE_HEADERS am_map.h - b_bot.h byteptr.h command.h console.h @@ -77,6 +77,7 @@ set(SRB2_CORE_HEADERS g_game.h g_input.h g_state.h + font.h hu_stuff.h i_joy.h i_net.h @@ -102,6 +103,7 @@ set(SRB2_CORE_HEADERS m_swap.h md5.h mserv.h + http-mserv.h p5prof.h s_sound.h screen.h @@ -176,6 +178,9 @@ set(SRB2_CORE_GAME_SOURCES k_bot.c k_botitem.c k_botsearch.c + k_respawn.c + k_grandprix.c + k_hud.c p_local.h p_maputl.h @@ -196,6 +201,9 @@ set(SRB2_CORE_GAME_SOURCES k_waypoint.h k_color.h k_bot.h + k_respawn.h + k_grandprix.h + k_hud.h ) if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) diff --git a/src/Makefile b/src/Makefile index 2249f6b95..67bb8e605 100644 --- a/src/Makefile +++ b/src/Makefile @@ -446,17 +446,10 @@ OBJS:=$(i_main_o) \ $(OBJDIR)/am_map.o \ $(OBJDIR)/command.o \ $(OBJDIR)/console.o \ + $(OBJDIR)/font.o \ $(OBJDIR)/hu_stuff.o \ $(OBJDIR)/y_inter.o \ $(OBJDIR)/st_stuff.o \ - $(OBJDIR)/k_kart.o \ - $(OBJDIR)/k_collide.o\ - $(OBJDIR)/k_color.o \ - $(OBJDIR)/k_battle.o \ - $(OBJDIR)/k_pwrlv.o \ - $(OBJDIR)/k_waypoint.o\ - $(OBJDIR)/k_pathfind.o\ - $(OBJDIR)/k_bheap.o \ $(OBJDIR)/m_aatree.o \ $(OBJDIR)/m_anigif.o \ $(OBJDIR)/m_argv.o \ @@ -510,9 +503,20 @@ OBJS:=$(i_main_o) \ $(OBJDIR)/i_tcp.o \ $(OBJDIR)/lzf.o \ $(OBJDIR)/vid_copy.o \ - $(OBJDIR)/k_bot.o \ - $(OBJDIR)/k_botitem.o \ - $(OBJDIR)/k_botsearch.o \ + $(OBJDIR)/k_kart.o \ + $(OBJDIR)/k_respawn.o\ + $(OBJDIR)/k_collide.o\ + $(OBJDIR)/k_color.o \ + $(OBJDIR)/k_battle.o \ + $(OBJDIR)/k_pwrlv.o \ + $(OBJDIR)/k_waypoint.o\ + $(OBJDIR)/k_pathfind.o\ + $(OBJDIR)/k_bheap.o \ + $(OBJDIR)/k_bot.o \ + $(OBJDIR)/k_botitem.o\ + $(OBJDIR)/k_botsearch.o\ + $(OBJDIR)/k_grandprix.o\ + $(OBJDIR)/k_hud.o \ $(i_cdmus_o) \ $(i_net_o) \ $(i_system_o) \ diff --git a/src/Makefile.cfg b/src/Makefile.cfg index 019b8c8d4..55455677c 100644 --- a/src/Makefile.cfg +++ b/src/Makefile.cfg @@ -228,6 +228,9 @@ endif ifndef GCC295 WFLAGS+=-Wendif-labels endif +ifdef GCC40 + WFLAGS+=-std=gnu89 +endif ifdef GCC41 WFLAGS+=-Wshadow endif diff --git a/src/console.c b/src/console.c index 06c04e6f7..a94ea0251 100644 --- a/src/console.c +++ b/src/console.c @@ -1472,6 +1472,8 @@ void CONS_Printf(const char *fmt, ...) Lock_state(); + Lock_state(); + // make sure new text is visible con_scrollup = 0; startup = con_startup; diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 7c49e37b8..29b320219 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -51,6 +51,7 @@ #include "k_battle.h" #include "k_pwrlv.h" #include "k_bot.h" +#include "k_grandprix.h" #ifndef NONET // cl loading screen @@ -532,14 +533,11 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i) for (j = 0; j < NUMPOWERS; ++j) rsp->powers[j] = (UINT16)SHORT(players[i].powers[j]); - for (j = 0; j < NUMKARTSTUFF; ++j) - rsp->kartstuff[j] = LONG(players[i].kartstuff[j]); // SRB2kart - - rsp->frameangle = (angle_t)LONG(players[i].frameangle); // SRB2kart // Score is resynched in the rspfirm resync packet rsp->rings = SHORT(players[i].rings); rsp->lives = players[i].lives; + rsp->lostlife = players[i].lostlife; rsp->continues = players[i].continues; rsp->scoreadd = players[i].scoreadd; rsp->xtralife = players[i].xtralife; @@ -574,13 +572,7 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i) rsp->weapondelay = LONG(players[i].weapondelay); rsp->tossdelay = LONG(players[i].tossdelay); - rsp->starpostx = SHORT(players[i].starpostx); - rsp->starposty = SHORT(players[i].starposty); - rsp->starpostz = SHORT(players[i].starpostz); rsp->starpostnum = LONG(players[i].starpostnum); - rsp->starposttime = (tic_t)LONG(players[i].starposttime); - rsp->starpostangle = (angle_t)LONG(players[i].starpostangle); - rsp->starpostscale = (fixed_t)LONG(players[i].starpostscale); rsp->maxlink = LONG(players[i].maxlink); rsp->dashspeed = (fixed_t)LONG(players[i].dashspeed); @@ -608,8 +600,28 @@ static inline void resynch_write_player(resynch_pak *rsp, const size_t i) rsp->splitscreenindex = players[i].splitscreenindex; + // SRB2kart + for (j = 0; j < NUMKARTSTUFF; ++j) + rsp->kartstuff[j] = LONG(players[i].kartstuff[j]); + + rsp->frameangle = (angle_t)LONG(players[i].frameangle); + rsp->airtime = (tic_t)LONG(players[i].airtime); + + // respawnvars_t + rsp->respawn_state = players[i].respawn.state; + rsp->respawn_pointx = (fixed_t)LONG(players[i].respawn.pointx); + rsp->respawn_pointy = (fixed_t)LONG(players[i].respawn.pointy); + rsp->respawn_pointz = (fixed_t)LONG(players[i].respawn.pointz); + rsp->respawn_flip = players[i].respawn.flip; + rsp->respawn_timer = (tic_t)LONG(players[i].respawn.timer); + rsp->respawn_distanceleft = (UINT32)LONG(players[i].respawn.distanceleft); + rsp->respawn_dropdash = (tic_t)LONG(players[i].respawn.dropdash); + + // botvars_t rsp->bot = players[i].bot; rsp->bot_difficulty = players[i].botvars.difficulty; + rsp->bot_diffincrease = players[i].botvars.diffincrease; + rsp->bot_rival = players[i].botvars.rival; rsp->bot_itemdelay = players[i].botvars.itemdelay; rsp->bot_itemconfirm = players[i].botvars.itemconfirm; rsp->bot_turnconfirm = players[i].botvars.turnconfirm; @@ -668,14 +680,11 @@ static void resynch_read_player(resynch_pak *rsp) for (j = 0; j < NUMPOWERS; ++j) players[i].powers[j] = (UINT16)SHORT(rsp->powers[j]); - for (j = 0; j < NUMKARTSTUFF; ++j) - players[i].kartstuff[j] = LONG(rsp->kartstuff[j]); // SRB2kart - - players[i].frameangle = (angle_t)LONG(rsp->frameangle); // SRB2kart // Score is resynched in the rspfirm resync packet players[i].rings = SHORT(rsp->rings); players[i].lives = rsp->lives; + players[i].lostlife = rsp->lostlife; players[i].continues = rsp->continues; players[i].scoreadd = rsp->scoreadd; players[i].xtralife = rsp->xtralife; @@ -709,13 +718,7 @@ static void resynch_read_player(resynch_pak *rsp) players[i].weapondelay = LONG(rsp->weapondelay); players[i].tossdelay = LONG(rsp->tossdelay); - players[i].starpostx = SHORT(rsp->starpostx); - players[i].starposty = SHORT(rsp->starposty); - players[i].starpostz = SHORT(rsp->starpostz); players[i].starpostnum = LONG(rsp->starpostnum); - players[i].starposttime = (tic_t)LONG(rsp->starposttime); - players[i].starpostangle = (angle_t)LONG(rsp->starpostangle); - players[i].starpostscale = (fixed_t)LONG(rsp->starpostscale); players[i].maxlink = LONG(rsp->maxlink); players[i].dashspeed = (fixed_t)LONG(rsp->dashspeed); @@ -743,8 +746,28 @@ static void resynch_read_player(resynch_pak *rsp) players[i].splitscreenindex = rsp->splitscreenindex; + // SRB2kart + for (j = 0; j < NUMKARTSTUFF; ++j) + players[i].kartstuff[j] = LONG(rsp->kartstuff[j]); + + players[i].frameangle = (angle_t)LONG(rsp->frameangle); + players[i].airtime = (tic_t)LONG(rsp->airtime); + + // respawnvars_t + players[i].respawn.state = rsp->respawn_state; + players[i].respawn.pointx = (fixed_t)LONG(rsp->respawn_pointx); + players[i].respawn.pointy = (fixed_t)LONG(rsp->respawn_pointy); + players[i].respawn.pointz = (fixed_t)LONG(rsp->respawn_pointz); + players[i].respawn.flip = (boolean)rsp->respawn_flip; + players[i].respawn.timer = (tic_t)LONG(rsp->respawn_timer); + players[i].respawn.distanceleft = (UINT32)LONG(rsp->respawn_distanceleft); + players[i].respawn.dropdash = (tic_t)LONG(rsp->respawn_dropdash); + + // botvars_t players[i].bot = rsp->bot; players[i].botvars.difficulty = rsp->bot_difficulty; + players[i].botvars.diffincrease = rsp->bot_diffincrease; + players[i].botvars.rival = rsp->bot_rival; players[i].botvars.itemdelay = rsp->bot_itemdelay; players[i].botvars.itemconfirm = rsp->bot_itemconfirm; players[i].botvars.turnconfirm = rsp->bot_turnconfirm; @@ -1269,7 +1292,7 @@ static boolean CL_AskFileList(INT32 firstfile) netbuffer->packettype = PT_TELLFILESNEEDED; netbuffer->u.filesneedednum = firstfile; - return HSendPacket(servernode, true, 0, sizeof (INT32)); + return HSendPacket(servernode, false, 0, sizeof (INT32)); } /** Sends a special packet to declare how many players in local @@ -2043,11 +2066,11 @@ static boolean CL_FinishedFileList(void) CL_Reset(); D_StartTitle(); M_StartMessage(M_GetText( - "You have WAD files loaded or have\n" - "modified the game in some way, and\n" - "your file list does not match\n" - "the server's file list.\n" - "Please restart SRB2Kart before connecting.\n\n" + "You have the wrong addons loaded.\n\n" + "To play on this server, restart\n" + "the game and don't load any addons.\n" + "SRB2Kart will automatically add\n" + "everything you need when you join.\n\n" "Press ESC\n" ), NULL, MM_NOTHING); return false; @@ -2068,11 +2091,12 @@ static boolean CL_FinishedFileList(void) CL_Reset(); D_StartTitle(); M_StartMessage(M_GetText( - "You cannot connect to this server\n" - "because you cannot download the files\n" - "that you are missing from the server.\n\n" - "See the console or log file for\n" - "more details.\n\n" + "An error occured when trying to\n" + "download missing addons.\n" + "(This is almost always a problem\n" + "with the server, not your game.)\n\n" + "See the console or log file\n" + "for additional details.\n\n" "Press ESC\n" ), NULL, MM_NOTHING); return false; @@ -2798,8 +2822,8 @@ void CL_RemovePlayer(INT32 playernum, kickreason_t reason) RemoveAdminPlayer(playernum); // don't stay admin after you're gone } - if (playernum == g_localplayers[0] && !demo.playback) - g_localplayers[0] = consoleplayer; // don't look through someone's view who isn't there + if (playernum == displayplayers[0] && !demo.playback) + displayplayers[0] = consoleplayer; // don't look through someone's view who isn't there LUA_InvalidatePlayer(&players[playernum]); @@ -4458,7 +4482,6 @@ static void HandlePacketFromAwayNode(SINT8 node) static boolean CheckForSpeedHacks(UINT8 p) { if (netcmds[maketic%TICQUEUE][p].forwardmove > MAXPLMOVE || netcmds[maketic%TICQUEUE][p].forwardmove < -MAXPLMOVE - || netcmds[maketic%TICQUEUE][p].sidemove > MAXPLMOVE || netcmds[maketic%TICQUEUE][p].sidemove < -MAXPLMOVE || netcmds[maketic%TICQUEUE][p].driftturn > KART_FULLTURN || netcmds[maketic%TICQUEUE][p].driftturn < -KART_FULLTURN) { CONS_Alert(CONS_WARNING, M_GetText("Illegal movement value received from node %d\n"), playernode[p]); @@ -5450,6 +5473,12 @@ static void SV_Maketic(void) if (!playeringame[i]) continue; + if (K_PlayerUsesBotMovement(&players[i])) + { + K_BuildBotTiccmd(&players[i], &netcmds[maketic%TICQUEUE][i]); + continue; + } + // We didn't receive this tic if ((netcmds[maketic % TICQUEUE][i].angleturn & TICCMD_RECEIVED) == 0) { @@ -5909,6 +5938,18 @@ INT32 D_NumPlayers(void) return num; } +/** Return whether a player is a real person (not a CPU) and not spectating. + */ +boolean D_IsPlayerHumanAndGaming (INT32 player_number) +{ + player_t * player = &players[player_number]; + return ( + playeringame[player_number] && + ! player->spectator && + ! player->bot + ); +} + tic_t GetLag(INT32 node) { return gametic - nettics[node]; diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 9604deb7e..120da1515 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -214,12 +214,10 @@ typedef struct angle_t aiming; UINT16 powers[NUMPOWERS]; - INT32 kartstuff[NUMKARTSTUFF]; // SRB2kart - angle_t frameangle; // SRB2kart - // Score is resynched in the confirm resync packet INT16 rings; SINT8 lives; + boolean lostlife; SINT8 continues; UINT8 scoreadd; SINT8 xtralife; @@ -254,13 +252,7 @@ typedef struct INT32 weapondelay; INT32 tossdelay; - INT16 starpostx; - INT16 starposty; - INT16 starpostz; INT32 starpostnum; - tic_t starposttime; - angle_t starpostangle; - fixed_t starpostscale; INT32 maxlink; fixed_t dashspeed; @@ -287,8 +279,26 @@ typedef struct UINT8 splitscreenindex; + // SRB2kart + INT32 kartstuff[NUMKARTSTUFF]; + angle_t frameangle; + tic_t airtime; + + // respawnvars_t + UINT8 respawn_state; + fixed_t respawn_pointx; + fixed_t respawn_pointy; + fixed_t respawn_pointz; + boolean respawn_flip; + tic_t respawn_timer; + UINT32 respawn_distanceleft; + tic_t respawn_dropdash; + + // botvars_t boolean bot; UINT8 bot_difficulty; + UINT8 bot_diffincrease; + boolean bot_rival; tic_t bot_itemdelay; tic_t bot_itemconfirm; SINT8 bot_turnconfirm; @@ -664,6 +674,7 @@ extern UINT8 playernode[MAXPLAYERS]; extern UINT8 playerconsole[MAXPLAYERS]; INT32 D_NumPlayers(void); +boolean D_IsPlayerHumanAndGaming(INT32 player_number); void D_ResetTiccmds(void); tic_t GetLag(INT32 node); diff --git a/src/d_main.c b/src/d_main.c index 44ae1bd09..72d9ffc13 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -65,9 +65,12 @@ #include "m_cond.h" // condition initialization #include "fastcmp.h" #include "keys.h" -#include "filesrch.h" // refreshdirmenu, mainwadstally +#include "filesrch.h" // refreshdirmenu #include "g_input.h" // tutorial mode control scheming +// SRB2Kart +#include "k_grandprix.h" + #ifdef CMAKECONFIG #include "config.h" #else @@ -166,35 +169,6 @@ UINT8 shiftdown = 0; // 0x1 left, 0x2 right UINT8 ctrldown = 0; // 0x1 left, 0x2 right UINT8 altdown = 0; // 0x1 left, 0x2 right boolean capslock = 0; // gee i wonder what this does. -// -// D_ModifierKeyResponder -// Sets global shift/ctrl/alt variables, never actually eats events -// -static inline void D_ModifierKeyResponder(event_t *ev) -{ - if (ev->type == ev_keydown || ev->type == ev_console) switch (ev->data1) - { - case KEY_LSHIFT: shiftdown |= 0x1; return; - case KEY_RSHIFT: shiftdown |= 0x2; return; - case KEY_LCTRL: ctrldown |= 0x1; return; - case KEY_RCTRL: ctrldown |= 0x2; return; - case KEY_LALT: altdown |= 0x1; return; - case KEY_RALT: altdown |= 0x2; return; - case KEY_CAPSLOCK: capslock = !capslock; return; - - default: return; - } - else if (ev->type == ev_keyup) switch (ev->data1) - { - case KEY_LSHIFT: shiftdown &= ~0x1; return; - case KEY_RSHIFT: shiftdown &= ~0x2; return; - case KEY_LCTRL: ctrldown &= ~0x1; return; - case KEY_RCTRL: ctrldown &= ~0x2; return; - case KEY_LALT: altdown &= ~0x1; return; - case KEY_RALT: altdown &= ~0x2; return; - default: return; - } -} // // D_ProcessEvents @@ -1000,6 +974,9 @@ void D_StartTitle(void) modeattacking = ATTACKING_NONE; marathonmode = 0; + // Reset GP + memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + // empty maptol so mario/etc sounds don't play in sound test when they shouldn't maptol = 0; @@ -1382,6 +1359,43 @@ void D_SRB2Main(void) if (M_CheckParm("-server") || dedicated) netgame = server = true; + if (M_CheckParm("-warp") && M_IsNextParm()) + { + const char *word = M_GetNextParm(); + char ch; // use this with sscanf to catch non-digits with + if (fastncmp(word, "MAP", 3)) // MAPxx name + pstartmap = M_MapNumber(word[3], word[4]); + else if (sscanf(word, "%d%c", &pstartmap, &ch) != 1) // a plain number + I_Error("Cannot warp to map %s (invalid map name)\n", word); + // Don't check if lump exists just yet because the wads haven't been loaded! + // Just do a basic range check here. + if (pstartmap < 1 || pstartmap > NUMMAPS) + I_Error("Cannot warp to map %d (out of range)\n", pstartmap); + else + { + if (!M_CheckParm("-server")) + { + G_SetGameModified(true, true); + + // Start up a "minor" grand prix session + memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); + + grandprixinfo.gamespeed = KARTSPEED_NORMAL; + grandprixinfo.encore = false; + grandprixinfo.masterbots = false; + + grandprixinfo.gp = true; + grandprixinfo.roundnum = 0; + grandprixinfo.cup = NULL; + grandprixinfo.wonround = false; + + grandprixinfo.initalize = true; + } + + autostart = true; + } + } + // adapt tables to SRB2's needs, including extra slots for dehacked file support P_PatchInfoTables(); @@ -1803,18 +1817,46 @@ void D_SRB2Main(void) INT16 newskill = -1; const char *sskill = M_GetNextParm(); - for (j = 0; kartspeed_cons_t[j].strvalue; j++) - if (!strcasecmp(kartspeed_cons_t[j].strvalue, sskill)) + const UINT8 master = KARTSPEED_HARD+1; + const char *masterstr = "Master"; + + if (!strcasecmp(masterstr, sskill)) + { + newskill = master; + } + else + { + for (j = 0; kartspeed_cons_t[j].strvalue; j++) { - newskill = (INT16)kartspeed_cons_t[j].value; - break; + if (!strcasecmp(kartspeed_cons_t[j].strvalue, sskill)) + { + newskill = (INT16)kartspeed_cons_t[j].value; + break; + } } - if (!kartspeed_cons_t[j].strvalue) // reached end of the list with no match + if (!kartspeed_cons_t[j].strvalue) // reached end of the list with no match + { + j = atoi(sskill); // assume they gave us a skill number, which is okay too + if (j >= KARTSPEED_EASY && j <= master) + newskill = (INT16)j; + } + } + + if (grandprixinfo.gp == true) { - j = atoi(sskill); // assume they gave us a skill number, which is okay too - if (j >= KARTSPEED_EASY && j <= KARTSPEED_HARD) - newskill = (INT16)j; + if (newskill == master) + { + grandprixinfo.masterbots = true; + newskill = KARTSPEED_HARD; + } + + grandprixinfo.gamespeed = newskill; + } + else if (newskill == master) + { + grandprixinfo.masterbots = true; + newskill = KARTSPEED_HARD; } if (newskill != -1) diff --git a/src/d_net.h b/src/d_net.h index 031ca7597..7f50706ee 100644 --- a/src/d_net.h +++ b/src/d_net.h @@ -19,7 +19,9 @@ #define __D_NET__ // Max computers in a game -#define MAXNETNODES 64 +// 127 is probably as high as this can go, because +// SINT8 is used for nodes sometimes >:( +#define MAXNETNODES 127 #define BROADCASTADDR MAXNETNODES #define NETSPLITSCREEN // Kart's splitscreen netgame feature diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 80fe6b817..57f01384c 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -54,6 +54,8 @@ #include "k_pwrlv.h" #include "y_inter.h" #include "k_color.h" +#include "k_respawn.h" +#include "k_grandprix.h" #ifdef NETGAME_DEVMODE #define CV_RESTRICT CV_NETVAR @@ -112,6 +114,16 @@ static void Skin_OnChange(void); static void Skin2_OnChange(void); static void Skin3_OnChange(void); static void Skin4_OnChange(void); + +static void Follower_OnChange(void); +static void Follower2_OnChange(void); +static void Follower3_OnChange(void); +static void Follower4_OnChange(void); +static void Followercolor_OnChange(void); +static void Followercolor2_OnChange(void); +static void Followercolor3_OnChange(void); +static void Followercolor4_OnChange(void); + static void Color_OnChange(void); static void Color2_OnChange(void); static void Color3_OnChange(void); @@ -270,6 +282,23 @@ consvar_t cv_skin[MAXSPLITSCREENPLAYERS] = { {"skin4", DEFAULTSKIN4, CV_SAVE|CV_CALL|CV_NOINIT, NULL, Skin4_OnChange, 0, NULL, NULL, 0, 0, NULL} }; +// player's followers. Also saved. +consvar_t cv_follower = {"follower", "-1", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Follower_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_follower2 = {"follower2", "-1", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Follower2_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_follower3 = {"follower3", "-1", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Follower3_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_follower4 = {"follower4", "-1", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Follower4_OnChange, 0, NULL, NULL, 0, 0, NULL}; + +// player's follower colors... Also saved... +consvar_t cv_followercolor = {"followercolor", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_followercolor2 = {"followercolor2", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor2_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_followercolor3 = {"followercolor3", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor3_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_followercolor4 = {"followercolor4", "Match", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor4_OnChange, 0, NULL, NULL, 0, 0, NULL}; + + +// Follower toggle +static CV_PossibleValue_t followers_cons_t[] = {{0, "Yours only"}, {1, "Everyone's"}, {0, NULL}}; +consvar_t cv_showfollowers = {"showfollowers", "Everyone's", CV_SAVE, followers_cons_t, 0, 0, NULL, NULL, 0, 0, NULL}; + consvar_t cv_skipmapcheck = {"skipmapcheck", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; INT32 cv_debug; @@ -330,6 +359,7 @@ consvar_t cv_hyudoro = {"hyudoro", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NU consvar_t cv_pogospring = {"pogospring", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_kitchensink = {"kitchensink", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_dualsneaker = {"dualsneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_triplesneaker = {"triplesneaker", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_triplebanana = {"triplebanana", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_decabanana = {"decabanana", "On", CV_NETVAR|CV_CHEAT, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -732,16 +762,22 @@ void D_RegisterClientCommands(void) for (i = 0; i < numskincolors; i++) { - Color_cons_t[i].value = i; - Color_cons_t[i].strvalue = skincolors[i].name; + Color_cons_t[i].value = Followercolor_cons_t[i].value = i; + Color_cons_t[i].strvalue = Followercolor_cons_t[i].strvalue = skincolors[i].name; } - Color_cons_t[numskincolors].value = 0; - Color_cons_t[numskincolors].strvalue = NULL; + Color_cons_t[MAXSKINCOLORS].value = Followercolor_cons_t[MAXSKINCOLORS+2].value = 0; + Color_cons_t[MAXSKINCOLORS].strvalue = Followercolor_cons_t[MAXSKINCOLORS+2].strvalue = NULL; + + Followercolor_cons_t[MAXSKINCOLORS].value = MAXSKINCOLORS; + Followercolor_cons_t[MAXSKINCOLORS].strvalue = "Match"; // Add "Match" option, which will make the follower color match the player's + + Followercolor_cons_t[MAXSKINCOLORS+1].value = MAXSKINCOLORS+1; + Followercolor_cons_t[MAXSKINCOLORS+1].strvalue = "Opposite"; // Add "Opposite" option, ...which is like "Match", but for coloropposite. // Set default player names // Monster Iestyn (12/08/19): not sure where else I could have actually put this, but oh well for (i = 0; i < MAXPLAYERS; i++) - sprintf(player_names[i], "Player %d", 1 + i); + sprintf(player_names[i], "Player %c", 'A' + i); // SRB2Kart: Letters like Sonic 3! if (dedicated) return; @@ -816,11 +852,16 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_playername[i]); CV_RegisterVar(&cv_playercolor[i]); CV_RegisterVar(&cv_skin[i]); + CV_RegisterVar(&cv_follower[i]); + CV_RegisterVar(&cv_followercolor[i]); } // preferred number of players CV_RegisterVar(&cv_splitplayers); + // Display other players' followers + CV_RegisterVar(&cv_showfollowers); + #ifdef SEENAMES CV_RegisterVar(&cv_seenames); #endif @@ -1326,7 +1367,7 @@ static void SendNameAndColor(UINT8 n) const INT32 playernum = g_localplayers[n]; player_t *player = &players[playernum]; - char buf[MAXPLAYERNAME+6]; + char buf[MAXPLAYERNAME+9]; char *p; if (splitscreen < playernum) @@ -1353,6 +1394,14 @@ static void SendNameAndColor(UINT8 n) } } + // ditto for follower colour: + if (!cv_followercolor[n].value) + CV_StealthSet(&cv_followercolor[n], "Match"); // set it to "Match". I don't care about your stupidity! + + // so like, this is sent before we even use anything like cvars or w/e so it's possible that follower is set to a pretty yikes value, so let's fix that before we send garbage that could crash the game: + if (cv_follower[n].value > numfollowers-1 || cv_follower[n].value < -1) + CV_StealthSet(&cv_follower[n], "-1"); + if (!strcmp(cv_playername[n].string, player_names[playernum]) && cv_playercolor[n].value == player->skincolor && !strcmp(cv_skin[n].string, skins[player->skin].name)) @@ -1377,6 +1426,10 @@ static void SendNameAndColor(UINT8 n) if (player->mo && !player->powers[pw_dye]) player->mo->color = player->skincolor; + // Update follower for local games: + if (cv_follower[n].value >= -1 && cv_follower[n].value != player->followerskin) + SetFollower(playernum, cv_follower[n].value); + if (metalrecording && n == 0) { // Starring Metal Sonic as themselves, obviously. SetPlayerSkinByNum(playernum, 5); @@ -1425,6 +1478,8 @@ static void SendNameAndColor(UINT8 n) WRITEUINT32(p, (UINT32)player->availabilities); WRITEUINT16(p, (UINT16)cv_playercolor[n].value); WRITEUINT8(p, (UINT8)cv_skin[n].value); + WRITESINT8(p, (UINT8)cv_follower[n].value); + WRITESINT8(p, (UINT8)cv_followercolor[n].value); SendNetXCmdForPlayer(n, XD_NAMEANDCOLOR, buf, p - buf); } @@ -2259,6 +2314,12 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r if ((netgame || multiplayer) && !((gametype == newgametype) && (gametypedefaultrules[newgametype] & GTR_CAMPAIGN))) FLS = false; + if (grandprixinfo.gp == true) + { + // Too lazy to change the input value for every instance of this function....... + pencoremode = grandprixinfo.encore; + } + if (delay != 2) { UINT8 flags = 0; @@ -2297,6 +2358,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r } chmappending++; + if (netgame) WRITEUINT32(buf_p, M_RandomizedSeed()); // random seed SendNetXCmd(XD_MAP, buf, buf_p - buf); @@ -2469,7 +2531,7 @@ static void Command_Map_f(void) mustmodifygame = !(netgame || multiplayer) && !majormods; - if (mustmodifygame && !option_force) + if (mustmodifygame) { /* May want to be more descriptive? */ CONS_Printf(M_GetText("Sorry, level change disabled in single player.\n")); @@ -2526,6 +2588,7 @@ static void Command_Map_f(void) if (mustmodifygame && option_force) { G_SetGameModified(multiplayer, true); + startgp = true; } // new gametype value @@ -2580,6 +2643,69 @@ static void Command_Map_f(void) } } + if (startgp) + { + i = COM_CheckParm("-skill"); + + grandprixinfo.gamespeed = (cv_kartspeed.value == KARTSPEED_AUTO ? KARTSPEED_NORMAL : cv_kartspeed.value); + grandprixinfo.masterbots = false; + + if (i) + { + const UINT8 master = KARTSPEED_HARD+1; + const char *masterstr = "Master"; + const char *skillname = COM_Argv(i+1); + INT32 newskill = -1; + INT32 j; + + if (!strcasecmp(masterstr, skillname)) + { + newskill = master; + } + else + { + for (j = 0; kartspeed_cons_t[j].strvalue; j++) + { + if (!strcasecmp(kartspeed_cons_t[j].strvalue, skillname)) + { + newskill = (INT16)kartspeed_cons_t[j].value; + break; + } + } + + if (!kartspeed_cons_t[j].strvalue) // reached end of the list with no match + { + j = atoi(COM_Argv(i+1)); // assume they gave us a skill number, which is okay too + if (j >= KARTSPEED_EASY && j <= master) + newskill = (INT16)j; + } + } + + if (newskill != -1) + { + if (newskill == master) + { + grandprixinfo.gamespeed = KARTSPEED_HARD; + grandprixinfo.masterbots = true; + } + else + { + grandprixinfo.gamespeed = newskill; + grandprixinfo.masterbots = false; + } + } + } + + grandprixinfo.encore = newencoremode; + + grandprixinfo.gp = true; + grandprixinfo.roundnum = 0; + grandprixinfo.cup = NULL; + grandprixinfo.wonround = false; + + grandprixinfo.initalize = true; + } + if (!option_force && newgametype == gametype) // SRB2Kart newresetplayers = false; // if not forcing and gametypes is the same @@ -4087,14 +4213,16 @@ static void PointLimit_OnChange(void) static void NumLaps_OnChange(void) { - if (!(gametyperules & GTR_CIRCUIT) || (modeattacking || demo.playback)) + if (K_CanChangeRules() == false) + { return; + } - if (server && Playing() - && (netgame || multiplayer) - && (mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) - && (cv_numlaps.value > mapheaderinfo[gamemap - 1]->numlaps)) + if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) + && (cv_numlaps.value > mapheaderinfo[gamemap - 1]->numlaps)) + { CV_StealthSetValue(&cv_numlaps, mapheaderinfo[gamemap - 1]->numlaps); + } // Just don't be verbose if (gametyperules & GTR_CIRCUIT) @@ -4636,14 +4764,14 @@ void Command_ExitGame_f(void) void Command_Retry_f(void) { - if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING)) + if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)) + { CONS_Printf(M_GetText("You must be in a level to use this.\n")); - else if (netgame || multiplayer) - CONS_Printf(M_GetText("This only works in single player.\n")); - /*else if (!&players[consoleplayer] || players[consoleplayer].lives <= 1) - CONS_Printf(M_GetText("You can't retry without any lives remaining!\n")); - else if (G_IsSpecialStage(gamemap)) - CONS_Printf(M_GetText("You can't retry special stages!\n"));*/ + } + else if (grandprixinfo.gp == false) + { + CONS_Printf(M_GetText("This only works in Grand Prix.\n")); + } else { M_ClearMenus(true); @@ -4898,6 +5026,198 @@ static void Name4_OnChange(void) SendNameAndColor(3); } +// sends the follower change for players +static void Follower_OnChange(void) +{ + char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; + INT32 num; + char set[10]; // This isn't Lua and mixed declarations in the middle of code make caveman compilers scream. + + // there is a slight chance that we will actually use a string instead so... + // let's investigate the string... + strcpy(str, cv_follower.string); + strcpy(cpy, cv_follower.string); + strlwr(str); + if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... + { + if (stricmp(cpy, "None") == 0) + { + CV_StealthSet(&cv_follower, "-1"); + + if (!Playing()) + return; // don't send anything there. + + SendNameAndColor(); + return; + } + + num = R_FollowerAvailable(str); + + if (num == -1) // that's an error. + CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); + + sprintf(set, "%d", num); + CV_StealthSet(&cv_follower, set); // set it to a number. It's easier for us to send later :) + } + + if (!Playing()) + return; // don't send anything there. + + SendNameAndColor(); +} + +// About the same as Color_OnChange but for followers. +static void Followercolor_OnChange(void) +{ + + if (!Playing()) + return; // do whatever you want if you aren't in the game or don't have a follower. + + if (!P_PlayerMoving(consoleplayer)) + { + // Color change menu scrolling fix is no longer necessary + SendNameAndColor(); + } +} + +// repeat for the 3 other players + +static void Follower2_OnChange(void) +{ + char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; + if (!Playing() || !splitscreen) + return; // do whatever you want + + strcpy(str, cv_follower2.string); + strcpy(cpy, cv_follower2.string); + strlwr(str); + if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... + { + + if (stricmp(cpy, "None") == 0) + { + CV_StealthSet(&cv_follower2, "-1"); + SendNameAndColor2(); + return; + } + + + { + INT32 num = R_FollowerAvailable(str); + char set[10]; + if (num == -1) // that's an error. + CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); + + sprintf(set, "%d", num); + CV_StealthSet(&cv_follower2, set); // set it to a number. It's easier for us to send later :) + } + } + SendNameAndColor2(); +} + +static void Followercolor2_OnChange(void) +{ + + if (!Playing()) + return; // do whatever you want if you aren't in the game or don't have a follower. + + if (!P_PlayerMoving(g_localplayers[1])) + { + // Color change menu scrolling fix is no longer necessary + SendNameAndColor2(); + } +} + +static void Follower3_OnChange(void) +{ + char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; + if (!Playing() || !splitscreen) + return; // do whatever you want + + strcpy(str, cv_follower3.string); + strcpy(cpy, cv_follower3.string); + strlwr(str); + if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... + { + + if (stricmp(cpy, "None") == 0) + { + CV_StealthSet(&cv_follower3, "-1"); + SendNameAndColor3(); + return; + } + + { + INT32 num = R_FollowerAvailable(str); + char set[10]; + if (num == -1) // that's an error. + CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); + + sprintf(set, "%d", num); + CV_StealthSet(&cv_follower3, set); // set it to a number. It's easier for us to send later :) + } + } + SendNameAndColor3(); +} + +static void Followercolor3_OnChange(void) +{ + + if (!Playing()) + return; // do whatever you want if you aren't in the game or don't have a follower. + + if (!P_PlayerMoving(g_localplayers[2])) + { + // Color change menu scrolling fix is no longer necessary + SendNameAndColor3(); + } +} + +static void Follower4_OnChange(void) +{ + char str[SKINNAMESIZE+1], cpy[SKINNAMESIZE+1]; + if (!Playing() || !splitscreen) + return; // do whatever you want + + strcpy(str, cv_follower4.string); + strcpy(cpy, cv_follower4.string); + strlwr(str); + if (stricmp(cpy,"0") !=0 && !atoi(cpy)) // yep, that's a string alright... + { + + if (stricmp(cpy, "None") == 0) + { + CV_StealthSet(&cv_follower4, "-1"); + SendNameAndColor4(); + return; + } + + { + INT32 num = R_FollowerAvailable(str); + char set[10]; + if (num == -1) // that's an error. + CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); + + sprintf(set, "%d", num); + CV_StealthSet(&cv_follower4, set); // set it to a number. It's easier for us to send later :) + } + } + SendNameAndColor4(); +} + +static void Followercolor4_OnChange(void) +{ + + if (!Playing()) + return; // do whatever you want if you aren't in the game or don't have a follower. + + if (!P_PlayerMoving(g_localplayers[3])) + { + // Color change menu scrolling fix is no longer necessary + SendNameAndColor4(); + } +} + /** Sends a skin change for the console player, unless that player is moving. * \sa cv_skin, Skin2_OnChange, Color_OnChange * \author Graue @@ -5156,24 +5476,35 @@ static void Command_ShowTime_f(void) // SRB2Kart: On change messages static void BaseNumLaps_OnChange(void) { - if (gametyperules & GTR_CIRCUIT && gamestate == GS_LEVEL) + if (K_CanChangeRules() == true) { - if (cv_basenumlaps.value) - CONS_Printf(M_GetText("Number of laps will be changed to %d next round.\n"), cv_basenumlaps.value); - else - CONS_Printf(M_GetText("Number of laps will be changed to map defaults next round.\n")); + const char *str = va("%d", cv_basenumlaps.value); + + if (cv_basenumlaps.value == 0) + { + str = "map defaults"; + } + + CONS_Printf(M_GetText("Number of laps will be changed to %s next round.\n"), str); } } static void KartFrantic_OnChange(void) { - if ((boolean)cv_kartfrantic.value != franticitems && gamestate == GS_LEVEL && leveltime > starttime) - CONS_Printf(M_GetText("Frantic items will be turned %s next round.\n"), cv_kartfrantic.value ? M_GetText("on") : M_GetText("off")); + if (K_CanChangeRules() == false) + { + return; + } + + if (leveltime < starttime) + { + CONS_Printf(M_GetText("Frantic items has been set to %s.\n"), cv_kartfrantic.value ? M_GetText("on") : M_GetText("off")); + franticitems = (boolean)cv_kartfrantic.value; + } else { - CONS_Printf(M_GetText("Frantic items has been turned %s.\n"), cv_kartfrantic.value ? M_GetText("on") : M_GetText("off")); - franticitems = (boolean)cv_kartfrantic.value; + CONS_Printf(M_GetText("Frantic items will be turned %s next round.\n"), cv_kartfrantic.value ? M_GetText("on") : M_GetText("off")); } } @@ -5186,47 +5517,56 @@ static void KartSpeed_OnChange(void) return; } - if ((gametyperules & GTR_CIRCUIT)) + if (K_CanChangeRules() == false) { - if ((gamestate == GS_LEVEL && leveltime < starttime) && (cv_kartspeed.value != KARTSPEED_AUTO)) - { - CONS_Printf(M_GetText("Game speed has been changed to \"%s\".\n"), cv_kartspeed.string); - gamespeed = (UINT8)cv_kartspeed.value; - } - else if (cv_kartspeed.value != (signed)gamespeed) - { - CONS_Printf(M_GetText("Game speed will be changed to \"%s\" next round.\n"), cv_kartspeed.string); - } + return; + } + + if (leveltime < starttime && cv_kartspeed.value != KARTSPEED_AUTO) + { + CONS_Printf(M_GetText("Game speed has been changed to \"%s\".\n"), cv_kartspeed.string); + gamespeed = (UINT8)cv_kartspeed.value; + } + else + { + CONS_Printf(M_GetText("Game speed will be changed to \"%s\" next round.\n"), cv_kartspeed.string); } } static void KartEncore_OnChange(void) { - if ((gametyperules & GTR_CIRCUIT)) + if (K_CanChangeRules() == false) { - if ((cv_kartencore.value == 1) != encoremode && gamestate == GS_LEVEL /*&& leveltime > starttime*/) - CONS_Printf(M_GetText("Encore Mode will be set to %s next round.\n"), cv_kartencore.string); - else - CONS_Printf(M_GetText("Encore Mode has been set to %s.\n"), cv_kartencore.string); + return; } + + CONS_Printf(M_GetText("Encore Mode will be set to %s next round.\n"), cv_kartencore.string); } static void KartComeback_OnChange(void) { - if (gametyperules & GTR_KARMA) + if (K_CanChangeRules() == false) { - if ((boolean)cv_kartcomeback.value != comeback && gamestate == GS_LEVEL && leveltime > starttime) - CONS_Printf(M_GetText("Karma Comeback will be turned %s next round.\n"), cv_kartcomeback.value ? M_GetText("on") : M_GetText("off")); - else - { - CONS_Printf(M_GetText("Karma Comeback has been turned %s.\n"), cv_kartcomeback.value ? M_GetText("on") : M_GetText("off")); - comeback = (boolean)cv_kartcomeback.value; - } + return; + } + + if (leveltime < starttime) + { + CONS_Printf(M_GetText("Karma Comeback has been turned %s.\n"), cv_kartcomeback.value ? M_GetText("on") : M_GetText("off")); + comeback = (boolean)cv_kartcomeback.value; + } + else + { + CONS_Printf(M_GetText("Karma Comeback will be turned %s next round.\n"), cv_kartcomeback.value ? M_GetText("on") : M_GetText("off")); } } static void KartEliminateLast_OnChange(void) { - if ((gametyperules & GTR_CIRCUIT) && cv_karteliminatelast.value) - P_CheckRacers(); + if (K_CanChangeRules() == false) + { + CV_StealthSet(&cv_karteliminatelast, cv_karteliminatelast.defaultvalue); + } + + P_CheckRacers(); } diff --git a/src/d_netcmd.h b/src/d_netcmd.h index eaf2c9f82..d9616218a 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -21,9 +21,14 @@ extern consvar_t cv_playername[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_playercolor[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_skin[MAXSPLITSCREENPLAYERS]; +extern consvar_t cv_follower[MAXSPLITSCREENPLAYERS]; +extern consvar_t cv_followercolor[MAXSPLITSCREENPLAYERS]; + // preferred number of players extern consvar_t cv_splitplayers; +extern consvar_t cv_showfollowers; + #ifdef SEENAMES extern consvar_t cv_seenames, cv_allowseenames; #endif @@ -69,7 +74,7 @@ extern consvar_t cv_ballhog, cv_selfpropelledbomb, cv_grow, cv_shrink; extern consvar_t cv_thundershield, cv_bubbleshield, cv_flameshield; extern consvar_t cv_hyudoro, cv_pogospring, cv_kitchensink; -extern consvar_t cv_triplesneaker, cv_triplebanana, cv_decabanana; +extern consvar_t cv_dualsneaker, cv_triplesneaker, cv_triplebanana, cv_decabanana; extern consvar_t cv_tripleorbinaut, cv_quadorbinaut, cv_dualjawz; extern consvar_t cv_kartminimap; diff --git a/src/d_netfil.c b/src/d_netfil.c index b28bac212..66b0ff650 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -1651,7 +1651,7 @@ void CURLPrepareFile(const char* url, int dfilenum) // Only allow HTTP and HTTPS curl_easy_setopt(http_handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP|CURLPROTO_HTTPS); - curl_easy_setopt(http_handle, CURLOPT_USERAGENT, va("SRB2Kart/v%d.%d.%d", VERSION/100, VERSION%100, SUBVERSION)); // Set user agent as some servers won't accept invalid user agents. + curl_easy_setopt(http_handle, CURLOPT_USERAGENT, va("SRB2Kart/v%d.%d", VERSION, SUBVERSION)); // Set user agent as some servers won't accept invalid user agents. // Follow a redirect request, if sent by the server. curl_easy_setopt(http_handle, CURLOPT_FOLLOWLOCATION, 1L); diff --git a/src/d_player.h b/src/d_player.h index b649b933a..a70456355 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -270,7 +270,8 @@ typedef enum NUMKARTITEMS, // Additional roulette numbers, only used for K_KartGetItemResult - KRITEM_TRIPLESNEAKER = NUMKARTITEMS, + KRITEM_DUALSNEAKER = NUMKARTITEMS, + KRITEM_TRIPLESNEAKER, KRITEM_TRIPLEBANANA, KRITEM_TENFOLDBANANA, KRITEM_TRIPLEORBINAUT, @@ -297,9 +298,6 @@ typedef enum k_position, // Used for Kart positions, mostly for deterministic stuff k_oldposition, // Used for taunting when you pass someone k_positiondelay, // Used for position number, so it can grow when passing/being passed - k_starpostflip, // the last starpost we hit requires flipping? - k_respawn, // Timer for the DEZ laser respawn effect - k_dropdash, // Charge up for respawn Drop Dash k_throwdir, // Held dir of controls; 1 = forward, 0 = none, -1 = backward (was "player->heldDir") k_instashield, // Instashield no-damage animation timer @@ -323,13 +321,16 @@ typedef enum k_jmp, // In Mario Kart, letting go of the jump button stops the drift k_offroad, // In Super Mario Kart, going offroad has lee-way of about 1 second before you start losing speed k_pogospring, // Pogo spring bounce effect - k_brakestop, // Wait until you've made a complete stop for a few tics before letting brake go in reverse. + k_spindash, // Spindash charge timer + k_spindashspeed, // Spindash release speed + k_spindashboost, // Spindash release boost timer k_waterskip, // Water skipping counter k_dashpadcooldown, // Separate the vanilla SA-style dash pads from using pw_flashing k_numboosts, // Count of how many boosts are being stacked, for after image spawning k_boostpower, // Base boost value, for offroad k_speedboost, // Boost value smoothing for max speed k_accelboost, // Boost value smoothing for acceleration + k_handleboost, // Boost value smoothing for handling k_draftpower, // Drafting power (from 0 to FRACUNIT), doubles your top speed & acceleration at max k_draftleeway, // Leniency timer before removing draft power k_lastdraft, // Last player being drafted @@ -352,8 +353,8 @@ typedef enum k_stealingtimer, // You are stealing an item, this is your timer k_stolentimer, // You are being stolen from, this is your timer k_superring, // Spawn rings on top of you every tic! - k_sneakertimer, // Duration of the Sneaker Boost itself - k_levelbooster, // Duration of a level booster's boost (same as sneaker, but separated for boost stacking) + k_sneakertimer, // Duration of a Sneaker Boost (from Sneakers or level boosters) + k_numsneakers, // Number of stacked sneaker effects k_growshrinktimer, // > 0 = Big, < 0 = small k_squishedtimer, // Squished frame timer k_rocketsneakertimer, // Rocket Sneaker duration timer @@ -410,6 +411,9 @@ typedef enum khud_lapanimation, // Used to show the lap start wing logo animation khud_laphand, // Lap hand gfx to use; 0 = none, 1 = :ok_hand:, 2 = :thumbs_up:, 3 = :thumps_down: + // Start + khud_fault, // Set when faulting during the starting countdown + // Camera khud_boostcam, // Camera push forward on boost khud_destboostcam, // Ditto @@ -427,9 +431,6 @@ typedef enum NUMKARTHUD } karthudtype_t; -// QUICKLY GET EITHER SNEAKER OR LEVEL BOOSTER SINCE THEY ARE FUNCTIONALLY IDENTICAL -#define EITHERSNEAKER(p) (p->kartstuff[k_sneakertimer] || p->kartstuff[k_levelbooster]) - // QUICKLY GET RING TOTAL, INCLUDING RINGS CURRENTLY IN THE PICKUP ANIMATION #define RINGTOTAL(p) (p->kartstuff[k_rings] + p->kartstuff[k_pickuprings]) @@ -453,15 +454,31 @@ typedef enum RW_RAIL = 32 } ringweapons_t; +// player_t struct for all respawn variables +typedef struct respawnvars_s +{ + UINT8 state; // 0: not respawning, 1: heading towards respawn point, 2: about to drop + waypoint_t *wp; // Waypoint that we're going towards, NULL if the position isn't linked to one + fixed_t pointx; // Respawn position coords to go towards + fixed_t pointy; + fixed_t pointz; + boolean flip; // Flip upside down or not + tic_t timer; // Time left on respawn animation once you're there + UINT32 distanceleft; // How far along the course to respawn you + tic_t dropdash; // Drop Dash charge timer +} respawnvars_t; + // player_t struct for all bot variables typedef struct botvars_s { - UINT8 difficulty; + UINT8 difficulty; // Bot's difficulty setting + UINT8 diffincrease; // In GP: bot difficulty will increase this much next round + boolean rival; // If true, they're the GP rival - tic_t itemdelay; - tic_t itemconfirm; + tic_t itemdelay; // Delay before using item at all + tic_t itemconfirm; // When high enough, they will use their item - SINT8 turnconfirm; + SINT8 turnconfirm; // Confirm turn direction } botvars_t; // ======================================================================== @@ -514,6 +531,8 @@ typedef struct player_s INT16 rturn_max[MAXPREDICTTICS]; // Ditto but for full-right UINT32 distancetofinish; waypoint_t *nextwaypoint; + respawnvars_t respawn; // Respawn info + tic_t airtime; // Keep track of how long you've been in the air // Bit flags. // See pflags_t, above. @@ -538,6 +557,12 @@ typedef struct player_s // SRB2kart UINT8 kartspeed; // Kart speed stat between 1 and 9 UINT8 kartweight; // Kart weight stat between 1 and 9 + + INT32 followerskin; // Kart: This player's follower "skin" + boolean followerready; // Kart: Used to know when we can have a follower or not. (This is set on the first NameAndColor follower update) + UINT8 followercolor; // Kart: Used to store the follower colour the player wishes to use + mobj_t *follower; // Kart: This is the follower object we have. (If any) + // UINT32 charflags; // Extra abilities/settings for skins (combinable stuff) @@ -546,7 +571,8 @@ typedef struct player_s mobjtype_t followitem; // Object # to spawn for Smiles mobj_t *followmobj; // Smiles all around - SINT8 lives; // number of lives + SINT8 lives; + boolean lostlife; SINT8 continues; // continues that player has acquired SINT8 xtralife; // Ring Extra Life counter @@ -583,6 +609,7 @@ typedef struct player_s INT16 totalring; // Total number of rings obtained for Race Mode tic_t realtime; // integer replacement for leveltime UINT8 laps; // Number of laps (optional) + INT32 starpostnum; // The number of the last starpost you hit //////////////////// // CTF Mode Stuff // @@ -593,15 +620,6 @@ typedef struct player_s INT32 weapondelay; // Delay (if any) to fire the weapon again INT32 tossdelay; // Delay (if any) to toss a flag/emeralds again - // Starpost information - INT16 starpostx; - INT16 starposty; - INT16 starpostz; - INT32 starpostnum; // The number of the last starpost you hit - tic_t starposttime; // Your time when you hit the starpost - angle_t starpostangle; // Angle that the starpost is facing - you respawn facing this way - fixed_t starpostscale; // Scale of the player; if negative, player is gravflipped - ///////////////// // NiGHTS Stuff// ///////////////// diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index 5848c98f6..d4a8013ca 100644 --- a/src/d_ticcmd.h +++ b/src/d_ticcmd.h @@ -34,6 +34,8 @@ typedef enum BT_BACKWARD = 1<<6, // Aim Item Backward BT_LOOKBACK = 1<<7, // Look Backward + BT_EBRAKEMASK = (BT_ACCELERATE|BT_BRAKE), + // free: 1<<8 to 1<<12 // Lua garbage @@ -58,7 +60,6 @@ typedef enum typedef struct { SINT8 forwardmove; // -MAXPLMOVE to MAXPLMOVE (50) - SINT8 sidemove; // -MAXPLMOVE to MAXPLMOVE (50) INT16 angleturn; // <<16 for angle delta - saved as 1 byte into demos INT16 aiming; // vertical aiming, see G_BuildTicCmd UINT16 buttons; diff --git a/src/dehacked.c b/src/dehacked.c index 03acbe65a..2b07a846b 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -38,6 +38,7 @@ #include "lua_script.h" #include "lua_hook.h" #include "d_clisrv.h" +#include "r_things.h" // for followers #include "m_cond.h" @@ -634,6 +635,271 @@ static void readfreeslots(MYFILE *f) Z_Free(s); } +// This here is our current only way to make followers. +INT32 numfollowers = 0; + +static void readfollower(MYFILE *f) +{ + char *s; + char *word, *word2, dname[SKINNAMESIZE+1]; + char *tmp; + char testname[SKINNAMESIZE]; + + boolean nameset; + INT32 fallbackstate = 0; + INT32 res; + INT32 i; + + if (numfollowers > MAXSKINS) + { + deh_warning("Error: Too many followers, cannot add anymore.\n"); + return; + } + + s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); + + // Ready the default variables for followers. We will overwrite them as we go! We won't set the name or states RIGHT HERE as this is handled down instead. + followers[numfollowers].scale = FRACUNIT; + followers[numfollowers].bubblescale = 0; // No bubble by default + followers[numfollowers].atangle = 230; + followers[numfollowers].dist = 32; // changed from 16 to 32 to better account for ogl models + followers[numfollowers].height = 16; + followers[numfollowers].zoffs = 32; + followers[numfollowers].horzlag = 2; + followers[numfollowers].vertlag = 6; + followers[numfollowers].bobspeed = TICRATE*2; + followers[numfollowers].bobamp = 4; + followers[numfollowers].hitconfirmtime = TICRATE; + followers[numfollowers].defaultcolor = 1; + + do + { + if (myfgets(s, MAXLINELEN, f)) + { + if (s[0] == '\n') + break; + + tmp = strchr(s, '#'); + if (tmp) + *tmp = '\0'; + if (s == tmp) + continue; // Skip comment lines, but don't break. + + word = strtok(s, " "); + if (word) + strupr(word); + else + break; + + word2 = strtok(NULL, " = "); + + if (!word2) + break; + + if (word2[strlen(word2)-1] == '\n') + word2[strlen(word2)-1] = '\0'; + + if (fastcmp(word, "NAME")) + { + DEH_WriteUndoline(word, va("%s", followers[numfollowers].name), UNDO_NONE); + strcpy(followers[numfollowers].name, word2); + nameset = true; + } + else if (fastcmp(word, "DEFAULTCOLOR")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].defaultcolor), UNDO_NONE); + followers[numfollowers].defaultcolor = (UINT8)get_number(word2); + } + + else if (fastcmp(word, "SCALE")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].scale), UNDO_NONE); + followers[numfollowers].scale = get_number(word2); + } + else if (fastcmp(word, "BUBBLESCALE")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].bubblescale), UNDO_NONE); + followers[numfollowers].bubblescale = get_number(word2); + } + else if (fastcmp(word, "ATANGLE")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].atangle), UNDO_NONE); + followers[numfollowers].atangle = (INT32)atoi(word2); + } + else if (fastcmp(word, "HORZLAG")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].horzlag), UNDO_NONE); + followers[numfollowers].horzlag = (INT32)atoi(word2); + } + else if (fastcmp(word, "VERTLAG")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].vertlag), UNDO_NONE); + followers[numfollowers].vertlag = (INT32)atoi(word2); + } + else if (fastcmp(word, "BOBSPEED")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].bobspeed), UNDO_NONE); + followers[numfollowers].bobspeed = (INT32)atoi(word2); + } + else if (fastcmp(word, "BOBAMP")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].bobamp), UNDO_NONE); + followers[numfollowers].bobamp = (INT32)atoi(word2); + } + else if (fastcmp(word, "ZOFFSET") || (fastcmp(word, "ZOFFS"))) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].zoffs), UNDO_NONE); + followers[numfollowers].zoffs = (INT32)atoi(word2); + } + else if (fastcmp(word, "DISTANCE") || (fastcmp(word, "DIST"))) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].dist), UNDO_NONE); + followers[numfollowers].dist = (INT32)atoi(word2); + } + else if (fastcmp(word, "HEIGHT")) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].height), UNDO_NONE); + followers[numfollowers].height = (INT32)atoi(word2); + } + else if (fastcmp(word, "IDLESTATE")) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].idlestate), UNDO_NONE); + followers[numfollowers].idlestate = get_number(word2); + fallbackstate = followers[numfollowers].idlestate; + } + else if (fastcmp(word, "FOLLOWSTATE")) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].followstate), UNDO_NONE); + followers[numfollowers].followstate = get_number(word2); + } + else if (fastcmp(word, "HURTSTATE")) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].hurtstate), UNDO_NONE); + followers[numfollowers].hurtstate = get_number(word2); + } + else if (fastcmp(word, "LOSESTATE")) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].losestate), UNDO_NONE); + followers[numfollowers].losestate = get_number(word2); + } + else if (fastcmp(word, "WINSTATE")) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].winstate), UNDO_NONE); + followers[numfollowers].winstate = get_number(word2); + } + else if (fastcmp(word, "HITSTATE") || (fastcmp(word, "HITCONFIRMSTATE"))) + { + if (word2) + strupr(word2); + DEH_WriteUndoline(word, va("%d", followers[numfollowers].hitconfirmstate), UNDO_NONE); + followers[numfollowers].hitconfirmstate = get_number(word2); + } + else if (fastcmp(word, "HITTIME") || (fastcmp(word, "HITCONFIRMTIME"))) + { + DEH_WriteUndoline(word, va("%d", followers[numfollowers].hitconfirmtime), UNDO_NONE); + followers[numfollowers].hitconfirmtime = (INT32)atoi(word2); + } + else + deh_warning("Follower %d: unknown word '%s'", numfollowers, word); + } + } while (!myfeof(f)); // finish when the line is empty + + if (!nameset) // well this is problematic. + { + strcpy(followers[numfollowers].name, va("Follower%d", numfollowers)); // this is lazy, so what + } + + // set skin name (this is just the follower's name in lowercases): + // but before we do, let's... actually check if another follower isn't doing the same shit... + + strcpy(testname, followers[numfollowers].name); + + // lower testname for skin checks... + strlwr(testname); + res = R_FollowerAvailable(testname); + if (res > -1) // yikes, someone else has stolen our name already + { + INT32 startlen = strlen(testname); + char cpy[2]; + //deh_warning("There was already a follower with the same name. (%s)", testname); This warning probably isn't necessary anymore? + sprintf(cpy, "%d", numfollowers); + memcpy(&testname[startlen], cpy, 2); + // in that case, we'll be very lazy and copy numfollowers to the end of our skin name. + } + + strcpy(followers[numfollowers].skinname, testname); + strcpy(dname, followers[numfollowers].skinname); // display name, just used for printing succesful stuff or errors later down the line. + + // now that the skin name is ready, post process the actual name to turn the underscores into spaces! + for (i = 0; followers[numfollowers].name[i]; i++) + { + if (followers[numfollowers].name[i] == '_') + followers[numfollowers].name[i] = ' '; + } + + // fallbacks for variables + // Print a warning if the variable is on a weird value and set it back to the minimum available if that's the case. +#define FALLBACK(field, field2, threshold, set) \ +if (followers[numfollowers].field < threshold) \ +{ \ + followers[numfollowers].field = set; \ + deh_warning("Follower '%s': Value for '%s' is too low! Minimum should be %d. Value was overwritten to %d.", dname, field2, set, set); \ +} \ + + FALLBACK(dist, "DIST", 0, 0); + FALLBACK(height, "HEIGHT", 1, 1); + FALLBACK(zoffs, "ZOFFS", 0, 0); + FALLBACK(horzlag, "HORZLAG", 1, 1); + FALLBACK(vertlag, "VERTLAG", 1, 1); + FALLBACK(bobamp, "BOBAMP", 0, 0); + FALLBACK(bobspeed, "BOBSPEED", 0, 0); + FALLBACK(hitconfirmtime, "HITCONFIRMTIME", 1, 1); + FALLBACK(scale, "SCALE", 1, 1); // No null/negative scale + FALLBACK(bubblescale, "BUBBLESCALE", 0, 0); // No negative scale + + // Special case for color I suppose + if (followers[numfollowers].defaultcolor > MAXSKINCOLORS-1) + { + followers[numfollowers].defaultcolor = 1; + deh_warning("Follower \'%s\': Value for 'color' should be between 1 and %d.\n", dname, MAXSKINCOLORS-1); + } + +#undef FALLBACK + + // also check if we forgot states. If we did, we will set any missing state to the follower's idlestate. + // Print a warning in case we don't have a fallback and set the state to S_INVISIBLE (rather than S_NULL) if unavailable. + +#define NOSTATE(field, field2) \ +if (!followers[numfollowers].field) \ +{ \ + followers[numfollowers].field = fallbackstate ? fallbackstate : S_INVISIBLE; \ + if (!fallbackstate) \ + deh_warning("Follower '%s' is missing state definition for '%s', no idlestate fallback was found", dname, field2); \ +} \ + + NOSTATE(idlestate, "IDLESTATE"); + NOSTATE(followstate, "FOLLOWSTATE"); + NOSTATE(hurtstate, "HURTSTATE"); + NOSTATE(losestate, "LOSESTATE"); + NOSTATE(winstate, "WINSTATE"); + NOSTATE(hitconfirmstate, "HITCONFIRMSTATE"); +#undef NOSTATE + + CONS_Printf("Added follower '%s'\n", dname); + numfollowers++; // add 1 follower + Z_Free(s); +} + static void readthing(MYFILE *f, INT32 num) { char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); @@ -1875,6 +2141,113 @@ static void readlevelheader(MYFILE *f, INT32 num) #undef MAXFLICKIES +static void readcupheader(MYFILE *f, cupheader_t *cup) +{ + char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL); + char *word; + char *word2; + char *tmp; + INT32 i; + + do + { + if (myfgets(s, MAXLINELEN, f)) + { + if (s[0] == '\n') + break; + + // First remove trailing newline, if there is one + tmp = strchr(s, '\n'); + if (tmp) + *tmp = '\0'; + + tmp = strchr(s, '#'); + if (tmp) + *tmp = '\0'; + if (s == tmp) + continue; // Skip comment lines, but don't break. + + // Set / reset word, because some things (Lua.) move it + word = s; + + // Get the part before the " = " + tmp = strchr(s, '='); + if (tmp) + *(tmp-1) = '\0'; + else + break; + strupr(word); + + // Now get the part after + word2 = tmp += 2; + i = atoi(word2); // used for numerical settings + strupr(word2); + + if (fastcmp(word, "ICON")) + { + deh_strlcpy(cup->icon, word2, + sizeof(cup->icon), va("%s Cup: icon", cup->name)); + } + else if (fastcmp(word, "LEVELLIST")) + { + cup->numlevels = 0; + + tmp = strtok(word2,","); + do { + INT32 map = atoi(tmp); + + if (tmp[0] >= 'A' && tmp[0] <= 'Z' && tmp[2] == '\0') + map = M_MapNumber(tmp[0], tmp[1]); + + if (!map) + break; + + if (cup->numlevels >= MAXLEVELLIST) + { + deh_warning("%s Cup: reached max levellist (%d)\n", cup->name, MAXLEVELLIST); + break; + } + + cup->levellist[cup->numlevels] = map - 1; + cup->numlevels++; + } while((tmp = strtok(NULL,",")) != NULL); + } + else if (fastcmp(word, "BONUSGAME")) + { + // Convert to map number + if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0') + i = M_MapNumber(word2[0], word2[1]); + cup->bonusgame = (INT16)i - 1; + } + else if (fastcmp(word, "SPECIALSTAGE")) + { + // Convert to map number + if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0') + i = M_MapNumber(word2[0], word2[1]); + cup->specialstage = (INT16)i - 1; + } + else if (fastcmp(word, "EMERALDNUM")) + { + if (i >= 0 && i <= 14) + cup->emeraldnum = (UINT8)i; + else + deh_warning("%s Cup: invalid emerald number %d", cup->name, i); + } + else if (fastcmp(word, "UNLOCKABLE")) + { + if (i >= 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something + cup->unlockrequired = (SINT8)i - 1; + else + deh_warning("%s Cup: invalid unlockable number %d", cup->name, i); + } + else + deh_warning("%s Cup: unknown word '%s'", cup->name, word); + } + } while (!myfeof(f)); // finish when the line is empty + + Z_Free(s); +} + static void readcutscenescene(MYFILE *f, INT32 num, INT32 scenenum) { char *s = Z_Calloc(MAXLINELEN, PU_STATIC, NULL); @@ -4484,6 +4857,13 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) readwipes(f); continue; } + else if (fastcmp(word, "FOLLOWER")) + { + readfollower(f); // at the same time this will be our only way to ADD followers for now. Yikes. + DEH_WriteUndoline(word, "", UNDO_HEADER); + // This is not a major mod either. + continue; // continue so that we don't error. + } word2 = strtok(NULL, " "); if (word2) { strupr(word2); @@ -4682,6 +5062,42 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) } } } + else if (fastcmp(word, "CUP")) + { + cupheader_t *cup = kartcupheaders; + cupheader_t *prev = NULL; + + while (cup) + { + if (fastcmp(cup->name, word2)) + { + // mark as a major mod if it replaces an already-existing cup + G_SetGameModified(multiplayer, true); + break; + } + + prev = cup; + cup = cup->next; + } + + // Nothing found, add to the end. + if (!cup) + { + cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL); + cup->id = numkartcupheaders; + deh_strlcpy(cup->name, word2, + sizeof(cup->name), va("Cup header %s: name", word2)); + if (prev != NULL) + prev->next = cup; + if (kartcupheaders == NULL) + kartcupheaders = cup; + numkartcupheaders++; + CONS_Printf("Added cup %d ('%s')\n", cup->id, cup->name); + } + + readcupheader(f, cup); + DEH_WriteUndoline(word, word2, UNDO_HEADER); + } else if (fastcmp(word, "CUTSCENE")) { if (i > 0 && i < 129) @@ -8143,6 +8559,39 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit "S_DRIFTEXPLODE2", "S_DRIFTEXPLODE3", "S_DRIFTEXPLODE4", + "S_DRIFTEXPLODE5", + "S_DRIFTEXPLODE6", + "S_DRIFTEXPLODE7", + "S_DRIFTEXPLODE8", + + // Drift boost clip + "S_DRIFTCLIPA1", + "S_DRIFTCLIPA2", + "S_DRIFTCLIPA3", + "S_DRIFTCLIPA4", + "S_DRIFTCLIPA5", + "S_DRIFTCLIPA6", + "S_DRIFTCLIPA7", + "S_DRIFTCLIPA8", + "S_DRIFTCLIPA9", + "S_DRIFTCLIPA10", + "S_DRIFTCLIPA11", + "S_DRIFTCLIPA12", + "S_DRIFTCLIPA13", + "S_DRIFTCLIPA14", + "S_DRIFTCLIPA15", + "S_DRIFTCLIPA16", + "S_DRIFTCLIPB1", + "S_DRIFTCLIPB2", + "S_DRIFTCLIPB3", + "S_DRIFTCLIPB4", + "S_DRIFTCLIPB5", + "S_DRIFTCLIPB6", + "S_DRIFTCLIPB7", + "S_DRIFTCLIPB8", + + // Drift boost clip spark + "S_DRIFTCLIPSPARK", // Sneaker boost effect "S_BOOSTFLAME", @@ -9038,6 +9487,31 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit "S_OPAQUESMOKE4", "S_OPAQUESMOKE5", + "S_FOLLOWERBUBBLE_FRONT", + "S_FOLLOWERBUBBLE_BACK", + + "S_GCHAOIDLE", + "S_GCHAOFLY", + "S_GCHAOSAD1", + "S_GCHAOSAD2", + "S_GCHAOSAD3", + "S_GCHAOSAD4", + "S_GCHAOHAPPY1", + "S_GCHAOHAPPY2", + "S_GCHAOHAPPY3", + "S_GCHAOHAPPY4", + + "S_CHEESEIDLE", + "S_CHEESEFLY", + "S_CHEESESAD1", + "S_CHEESESAD2", + "S_CHEESESAD3", + "S_CHEESESAD4", + "S_CHEESEHAPPY1", + "S_CHEESEHAPPY2", + "S_CHEESEHAPPY3", + "S_CHEESEHAPPY4", + "S_RINGDEBT", "S_RINGSPARKS1", "S_RINGSPARKS2", @@ -9074,6 +9548,25 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit "S_BATTLECAPSULE_SUPPORT", "S_BATTLECAPSULE_SUPPORTFLY", + "S_EGOORB", + + "S_WATERTRAIL1", + "S_WATERTRAIL2", + "S_WATERTRAIL3", + "S_WATERTRAIL4", + "S_WATERTRAIL5", + "S_WATERTRAIL6", + "S_WATERTRAIL7", + "S_WATERTRAIL8", + "S_WATERTRAILUNDERLAY1", + "S_WATERTRAILUNDERLAY2", + "S_WATERTRAILUNDERLAY3", + "S_WATERTRAILUNDERLAY4", + "S_WATERTRAILUNDERLAY5", + "S_WATERTRAILUNDERLAY6", + "S_WATERTRAILUNDERLAY7", + "S_WATERTRAILUNDERLAY8", + #ifdef SEENAMES "S_NAMECHECK", #endif @@ -9888,6 +10381,8 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s "MT_FASTLINE", "MT_FASTDUST", "MT_DRIFTEXPLODE", + "MT_DRIFTCLIP", + "MT_DRIFTCLIPSPARK", "MT_BOOSTFLAME", "MT_BOOSTSMOKE", "MT_SNEAKERTRAIL", @@ -10167,6 +10662,13 @@ static const char *const MOBJTYPE_LIST[] = { // array length left dynamic for s "MT_BATTLECAPSULE", "MT_BATTLECAPSULE_PIECE", + "MT_FOLLOWER", + "MT_FOLLOWERBUBBLE_FRONT", + "MT_FOLLOWERBUBBLE_BACK", + + "MT_WATERTRAIL", + "MT_WATERTRAILUNDERLAY", + #ifdef SEENAMES "MT_NAMECHECK", #endif @@ -10212,7 +10714,7 @@ static const char *const MOBJFLAG2_LIST[] = { "AXIS", // It's a NiGHTS axis! (For faster checking) "TWOD", // Moves like it's in a 2D level "DONTRESPAWN", // Don't respawn this object! - "DONTDRAW", // Don't generate a vissprite + "\x01", // free: 1<<3 (name un-matchable) "AUTOMATIC", // Thrown ring has automatic properties "RAILRING", // Thrown ring has rail properties "BOUNCERING", // Thrown ring has bounce properties @@ -10228,7 +10730,7 @@ static const char *const MOBJFLAG2_LIST[] = { "JUSTATTACKED", // can be pushed by other moving mobjs "FIRING", // turret fire "SUPERFIRE", // Firing something with Super Sonic-stopping properties. Or, if mobj has MF_MISSILE, this is the actual fire from it. - "SHADOW", // Fuzzy draw, makes targeting harder. + "\x01", // free: 1<<20 (name un-matchable) "STRONGBOX", // Flag used for "strong" random monitors. "OBJECTFLIP", // Flag for objects that always have flipped gravity. "SKULLFLY", // Special handling: skull in flight. @@ -10256,10 +10758,6 @@ static const char *const MOBJEFLAG_LIST[] = { "APPLYPMOMZ", // Platform movement "TRACERANGLE", // Compute and trigger on mobj angle relative to tracer "JUSTBOUNCEDWALL", - "DRAWONLYFORP1", // SRB2Kart: Splitscreen sprite draw flags - "DRAWONLYFORP2", - "DRAWONLYFORP3", - "DRAWONLYFORP4", NULL }; @@ -10607,9 +11105,6 @@ static const char *const KARTSTUFF_LIST[] = { "POSITION", "OLDPOSITION", "POSITIONDELAY", - "STARPOSTFLIP", - "RESPAWN", - "DROPDASH", "THROWDIR", "INSTASHIELD", @@ -10633,13 +11128,16 @@ static const char *const KARTSTUFF_LIST[] = { "JMP", "OFFROAD", "POGOSPRING", - "BRAKESTOP", + "SPINDASH", + "SPINDASHSPEED", + "SPINDASHBOOST", "WATERSKIP", "DASHPADCOOLDOWN", "NUMBOOSTS", "BOOSTPOWER", "SPEEDBOOST", "ACCELBOOST", + "HANDLEBOOST", "DRAFTPOWER", "DRAFTLEEWAY", "LASTDRAFT", @@ -10661,7 +11159,7 @@ static const char *const KARTSTUFF_LIST[] = { "STOLENTIMER", "SUPERRING", "SNEAKERTIMER", - "LEVELBOOSTER", + "NUMSNEAKERS", "GROWSHRINKTIMER", "SQUISHEDTIMER", "ROCKETSNEAKERTIMER", @@ -11248,7 +11746,6 @@ struct { {"DI_SOUTHEAST",DI_SOUTHEAST}, {"NUMDIRS",NUMDIRS}, - // Sprite rotation axis (rotaxis_t) {"ROTAXIS_X",ROTAXIS_X}, {"ROTAXIS_Y",ROTAXIS_Y}, @@ -11340,7 +11837,7 @@ struct { {"V_WRAPY",V_WRAPY}, {"V_NOSCALESTART",V_NOSCALESTART}, {"V_SPLITSCREEN",V_SPLITSCREEN}, - {"V_HORZSCREEN",V_HORZSCREEN}, + {"V_SLIDEIN",V_SLIDEIN}, {"V_PARAMMASK",V_PARAMMASK}, {"V_SCALEPATCHMASK",V_SCALEPATCHMASK}, @@ -11380,7 +11877,8 @@ struct { KART_ITEM_ITERATOR, // Actual items (can be set for k_itemtype) #undef FOREACH {"NUMKARTITEMS",NUMKARTITEMS}, - {"KRITEM_TRIPLESNEAKER",KRITEM_TRIPLESNEAKER}, // Additional roulette IDs (not usable for much in Lua besides K_GetItemPatch) + {"KRITEM_DUALSNEAKER",KRITEM_DUALSNEAKER}, // Additional roulette IDs (not usable for much in Lua besides K_GetItemPatch) + {"KRITEM_TRIPLESNEAKER",KRITEM_TRIPLESNEAKER}, {"KRITEM_TRIPLEBANANA",KRITEM_TRIPLEBANANA}, {"KRITEM_TENFOLDBANANA",KRITEM_TENFOLDBANANA}, {"KRITEM_TRIPLEORBINAUT",KRITEM_TRIPLEORBINAUT}, @@ -11395,6 +11893,37 @@ struct { {"KSHIELD_FLAME",KSHIELD_FLAME}, {"NUMKARTSHIELDS",NUMKARTSHIELDS}, + // translation colormaps + {"TC_DEFAULT",TC_DEFAULT}, + {"TC_BOSS",TC_BOSS}, + {"TC_METALSONIC",TC_METALSONIC}, + {"TC_ALLWHITE",TC_ALLWHITE}, + {"TC_RAINBOW",TC_RAINBOW}, + {"TC_BLINK",TC_BLINK}, + + // MFD_ draw flag enum + {"MFD_DONTDRAWP1",MFD_DONTDRAWP1}, + {"MFD_DONTDRAWP2",MFD_DONTDRAWP2}, + {"MFD_DONTDRAWP3",MFD_DONTDRAWP3}, + {"MFD_DONTDRAWP4",MFD_DONTDRAWP4}, + {"MFD_TRANS10",MFD_TRANS10}, + {"MFD_TRANS20",MFD_TRANS20}, + {"MFD_TRANS30",MFD_TRANS30}, + {"MFD_TRANS40",MFD_TRANS40}, + {"MFD_TRANS50",MFD_TRANS50}, + {"MFD_TRANS60",MFD_TRANS60}, + {"MFD_TRANS70",MFD_TRANS70}, + {"MFD_TRANS80",MFD_TRANS80}, + {"MFD_TRANS90",MFD_TRANS90}, + {"MFD_TRANSMASK",MFD_TRANSMASK}, + {"MFD_FULLBRIGHT",MFD_FULLBRIGHT}, + {"MFD_SEMIBRIGHT",MFD_SEMIBRIGHT}, + {"MFD_NOBRIGHT",MFD_NOBRIGHT}, + {"MFD_BRIGHTMASK",MFD_BRIGHTMASK}, + {"MFD_DONTDRAW",MFD_DONTDRAW}, + {"MFD_SHADOW",MFD_SHADOW}, + {"MFD_TRANSSHIFT",MFD_TRANSSHIFT}, + {NULL,0} }; diff --git a/src/doomstat.h b/src/doomstat.h index 63c0a38e3..fcc2990a9 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -425,8 +425,30 @@ typedef struct extern mapheader_t* mapheaderinfo[NUMMAPS]; +// This could support more, but is that a good idea? +// Keep in mind that it may encourage people making overly long cups just because they "can", and would be a waste of memory. +#define MAXLEVELLIST 5 + +typedef struct cupheader_s +{ + UINT16 id; ///< Cup ID + char name[15]; ///< Cup title (14 chars) + char icon[9]; ///< Name of the icon patch + INT16 levellist[MAXLEVELLIST]; ///< List of levels that belong to this cup + UINT8 numlevels; ///< Number of levels defined in levellist + INT16 bonusgame; ///< Map number to use for bonus game + INT16 specialstage; ///< Map number to use for special stage + UINT8 emeraldnum; ///< ID of Emerald to use for special stage (1-7 for Chaos Emeralds, 8-14 for Super Emeralds, 0 for no emerald) + SINT8 unlockrequired; ///< An unlockable is required to select this cup. -1 for no unlocking required. + struct cupheader_s *next; ///< Next cup in linked list +} cupheader_t; + +extern cupheader_t *kartcupheaders; // Start of cup linked list +extern UINT16 numkartcupheaders; + // Gametypes #define NUMGAMETYPEFREESLOTS 128 + enum GameType { GT_RACE = 0, @@ -590,8 +612,13 @@ extern UINT16 nightslinktics; // SRB2kart extern tic_t introtime; extern tic_t starttime; + +extern const tic_t bulbtime; +extern UINT8 numbulbs; + extern tic_t raceexittime; extern tic_t battleexittime; + extern INT32 hyudorotime; extern INT32 stealtime; extern INT32 sneakertime; @@ -645,6 +672,7 @@ extern tic_t hyubgone; extern tic_t mapreset; extern boolean thwompsactive; extern SINT8 spbplace; +extern boolean rainbowstartavailable; extern tic_t bombflashtimer; // Used to avoid causing seizures if multiple mines explode close to you :) extern boolean legitimateexit; diff --git a/src/f_wipe.c b/src/f_wipe.c index 70bac96e6..530536377 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.c @@ -24,6 +24,7 @@ #include "z_zone.h" #include "i_system.h" +#include "i_threads.h" #include "m_menu.h" #include "console.h" #include "d_main.h" @@ -477,7 +478,15 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r I_UpdateNoBlit(); if (drawMenu) + { +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif M_Drawer(); // menu is drawn even on top of wipes +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif + } I_FinishUpdate(); // page flip or blit buffer diff --git a/src/font.c b/src/font.c new file mode 100644 index 000000000..aeaabd018 --- /dev/null +++ b/src/font.c @@ -0,0 +1,77 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1999-2018 by Sonic Team Junior. +// Copyright (C) 2019 by Kart Krew. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file font.c +/// \brief Font setup + +#include "doomdef.h" +#include "hu_stuff.h" +#include "font.h" +#include "z_zone.h" + +font_t fontv[MAX_FONTS]; +int fontc; + +static void +FontCache (font_t *fnt) +{ + int i; + int c; + + c = fnt->start; + for (i = 0; i < fnt->size; ++i, ++c) + { + fnt->font[i] = HU_CachePatch( + "%s%.*d", + fnt->prefix, + fnt->digits, + c); + } +} + +void +Font_Load (void) +{ + int i; + for (i = 0; i < fontc; ++i) + { + FontCache(&fontv[i]); + } +} + +int +Font_DumbRegister (const font_t *sfnt) +{ + font_t *fnt; + + if (fontc == MAX_FONTS) + return -1; + + fnt = &fontv[fontc]; + + memcpy(fnt, sfnt, sizeof (font_t)); + + if (!( fnt->font = ZZ_Alloc(sfnt->size * sizeof (patch_t *)) )) + return -1; + + return fontc++; +} + +int +Font_Register (const font_t *sfnt) +{ + int d; + + d = Font_DumbRegister(sfnt); + + if (d >= 0) + FontCache(&fontv[d]); + + return d; +} diff --git a/src/font.h b/src/font.h new file mode 100644 index 000000000..88bedb44f --- /dev/null +++ b/src/font.h @@ -0,0 +1,49 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 1999-2018 by Sonic Team Junior. +// Copyright (C) 2019 by Kart Krew. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file font.h +/// \brief Font setup + +#ifndef __FONT_H__ +#define __FONT_H__ + +#define MAX_FONTS 32 + +typedef struct font font_t; + +struct font +{ + patch_t **font; + + UINT8 start; + UINT8 size; + + char prefix[8];/* 7 used at most */ + unsigned digits : 2; +}; + +extern font_t fontv[MAX_FONTS]; +extern int fontc; + +/* +Reloads already registered fonts. +*/ +void Font_Load (void); + +/* +Registers and loads a new font. +*/ +int Font_Register (const font_t *); + +/* +Register a new font, but do not load it yet. +*/ +int Font_DumbRegister (const font_t *); + +#endif diff --git a/src/g_game.c b/src/g_game.c index e579fed03..15e2b8f0b 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -55,6 +55,8 @@ #include "k_battle.h" #include "k_pwrlv.h" #include "k_color.h" +#include "k_respawn.h" +#include "k_grandprix.h" gameaction_t gameaction; @@ -185,6 +187,10 @@ struct quake quake; // Map Header Information mapheader_t* mapheaderinfo[NUMMAPS] = {NULL}; +// Kart cup definitions +cupheader_t *kartcupheaders = NULL; +UINT16 numkartcupheaders = 0; + static boolean exitgame = false; static boolean retrying = false; static boolean retryingmodeattack = false; @@ -241,10 +247,15 @@ INT32 gameovertics = 15*TICRATE; UINT8 ammoremovaltics = 2*TICRATE; // SRB2kart -tic_t introtime = 108+5; // plus 5 for white fade -tic_t starttime = 6*TICRATE + (3*TICRATE/4); +tic_t introtime = 0; +tic_t starttime = 0; + +const tic_t bulbtime = TICRATE/2; +UINT8 numbulbs = 0; + tic_t raceexittime = 5*TICRATE + (2*TICRATE/3); tic_t battleexittime = 8*TICRATE; + INT32 hyudorotime = 7*TICRATE; INT32 stealtime = TICRATE/2; INT32 sneakertime = TICRATE + (TICRATE/3); @@ -306,6 +317,7 @@ tic_t hyubgone; // Cooldown before hyudoro is allowed to be rerolled tic_t mapreset; // Map reset delay when enough players have joined an empty game boolean thwompsactive; // Thwomps activate on lap 2 SINT8 spbplace; // SPB exists, give the person behind better items +boolean rainbowstartavailable; // Boolean, keeps track of if the rainbow start was gotten // Client-sided, unsynched variables (NEVER use in anything that needs to be synced with other players) tic_t bombflashtimer = 0; // Cooldown before another FlashPal can be intialized by a bomb exploding near a displayplayer. Avoids seizures. @@ -984,24 +996,24 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) cmd->buttons |= BT_BRAKE; axis = PlayerJoyAxis(ssplayer, AXISAIM); if (PlayerInputDown(ssplayer, gc_aimforward) || (usejoystick && axis < 0)) - forward += forwardmove[1]; + forward += forwardmove; if (PlayerInputDown(ssplayer, gc_aimbackward) || (usejoystick && axis > 0)) - forward -= forwardmove[1]; + forward -= forwardmove; } else { // forward with key or button // SRB2kart - we use an accel/brake instead of forward/backward. axis = PlayerJoyAxis(ssplayer, AXISMOVE); - if (PlayerInputDown(ssplayer, gc_accelerate) || (gamepadjoystickmove && axis > 0) || EITHERSNEAKER(player)) + if (PlayerInputDown(ssplayer, gc_accelerate) || (gamepadjoystickmove && axis > 0) || player->kartstuff[k_sneakertimer]) { cmd->buttons |= BT_ACCELERATE; - forward = forwardmove[1]; // 50 + forward = forwardmove; // 50 } else if (analogjoystickmove && axis > 0) { cmd->buttons |= BT_ACCELERATE; // JOYAXISRANGE is supposed to be 1023 (divide by 1024) - forward += ((axis * forwardmove[1]) >> 10)*2; + forward += ((axis * forwardmove) >> 10); } axis = PlayerJoyAxis(ssplayer, AXISBRAKE); @@ -1009,14 +1021,14 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) { cmd->buttons |= BT_BRAKE; if (cmd->buttons & BT_ACCELERATE || cmd->forwardmove <= 0) - forward -= forwardmove[0]; // 25 - Halved value so clutching is possible + forward -= forwardmove; } else if (analogjoystickmove && axis > 0) { cmd->buttons |= BT_BRAKE; // JOYAXISRANGE is supposed to be 1023 (divide by 1024) if (cmd->buttons & BT_ACCELERATE || cmd->forwardmove <= 0) - forward -= ((axis * forwardmove[0]) >> 10); + forward -= ((axis * forwardmove) >> 10); } // But forward/backward IS used for aiming. @@ -1064,7 +1076,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) INT32 player_invert = invertmouse ? -1 : 1; INT32 screen_invert = (player->mo && (player->mo->eflags & MFE_VERTICALFLIP) - && (!thiscam->chase || player->pflags & PF_FLIPCAM)) //because chasecam's not inverted + && (!thiscam->chase)) //because chasecam's not inverted ? -1 : 1; // set to -1 or 1 to multiply INT32 configlookaxis = ssplayer == 1 ? cv_lookaxis.value : cv_lookaxis2.value; @@ -1111,15 +1123,12 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) mousex = mousey = mlooky = 0; - if (forward > MAXPLMOVE) - forward = MAXPLMOVE; - else if (forward < -MAXPLMOVE) - forward = -MAXPLMOVE; + cmd->forwardmove += (SINT8)forward; - if (forward != 0) - { - cmd->forwardmove = (SINT8)(cmd->forwardmove + forward); - } + if (cmd->forwardmove > MAXPLMOVE) + cmd->forwardmove = MAXPLMOVE; + else if (cmd->forwardmove < -MAXPLMOVE) + cmd->forwardmove = -MAXPLMOVE; //{ SRB2kart - Drift support // Not grouped with the rest of turn stuff because it needs to know what buttons you're pressing for rubber-burn turn @@ -1139,11 +1148,12 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) cmd->angleturn *= realtics; - // SRB2kart - no additional angle if not moving - if ((player->mo && player->speed > 0) // Moving - || (leveltime > starttime && (cmd->buttons & BT_ACCELERATE && cmd->buttons & BT_BRAKE)) // Rubber-burn turn - || (player->kartstuff[k_respawn]) // Respawning - || (player->spectator || objectplacing)) // Not a physical player + *lang += (cmd->angleturn<<16); + + cmd->angleturn = (INT16)(*lang >> 16); + cmd->latency = modeattacking ? 0 : (leveltime & 0xFF); // Send leveltime when this tic was generated to the server for control lag calculations + + if (!hu_stopped) { *lang += (cmd->angleturn<<16); } @@ -1165,7 +1175,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) LUAh_PlayerCmd(player, cmd); //Reset away view if a command is given. - if ((cmd->forwardmove || cmd->sidemove || cmd->buttons) + if ((cmd->forwardmove || cmd->buttons) && !r_splitscreen && displayplayers[0] != consoleplayer && ssplayer == 1) { // Call ViewpointSwitch hooks here. @@ -1252,14 +1262,6 @@ void G_DoLoadLevel(boolean resetplayer) P_FindEmerald(); - g_localplayers[0] = consoleplayer; // view the guy you are playing - - for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) - { - if (i > 0 && r_splitscreen < i) - g_localplayers[i] = consoleplayer; - } - gameaction = ga_nothing; #ifdef PARANOIA Z_CheckHeap(-2); @@ -1856,30 +1858,19 @@ void G_Ticker(boolean run) if (gamestate == GS_LEVEL) { // Or, alternatively, retry. - if (!(netgame || multiplayer) && G_GetRetryFlag()) + if (G_GetRetryFlag()) { G_ClearRetryFlag(); - if (modeattacking) + for (i = 0; i < MAXPLAYERS; i++) { - pausedelay = INT32_MIN; - M_ModeAttackRetry(0); - } - /* - else - { - // Costs a life to retry ... unless the player in question is dead already, or you haven't even touched the first starpost in marathon run. - if (marathonmode && gamemap == spmarathon_start && !players[consoleplayer].starposttime) + if (playeringame[i]) { - marathonmode |= MA_INIT; - marathontime = 0; + K_PlayerLoseLife(&players[i]); } - else if (G_GametypeUsesLives() && players[consoleplayer].playerstate == PST_LIVE && players[consoleplayer].lives != INFLIVES) - players[consoleplayer].lives -= 1; - - G_DoReborn(consoleplayer); } - */ + + D_MapChange(gamemap, gametype, (cv_kartencore.value == 1), false, 1, false, false); } for (i = 0; i < MAXPLAYERS; i++) @@ -1911,16 +1902,18 @@ void G_Ticker(boolean run) if (playeringame[i]) { - if (K_PlayerUsesBotMovement(&players[i])) + G_CopyTiccmd(cmd, &netcmds[buf][i], 1); + + // Use the leveltime sent in the player's ticcmd to determine control lag + if (modeattacking || K_PlayerUsesBotMovement(&players[i])) { - K_BuildBotTiccmd(&players[i], cmd); + // Never has lag cmd->latency = 0; } else { - G_CopyTiccmd(cmd, &netcmds[buf][i], 1); - // Use the leveltime sent in the player's ticcmd to determine control lag - cmd->latency = modeattacking ? 0 : min(((leveltime & 0xFF) - cmd->latency) & 0xFF, MAXPREDICTTICS-1); //@TODO add a cvar to allow setting this max + //@TODO add a cvar to allow setting this max + cmd->latency = min(((leveltime & 0xFF) - cmd->latency) & 0xFF, MAXPREDICTTICS-1); } players[i].angleturn += players[i].cmd.angleturn - players[i].oldrelangleturn; @@ -1934,7 +1927,7 @@ void G_Ticker(boolean run) switch (gamestate) { case GS_LEVEL: - if (titledemo) + if (demo.title) F_TitleDemoTicker(); P_Ticker(run); // tic the game ST_Ticker(run); @@ -2058,15 +2051,11 @@ static inline void G_PlayerFinishLevel(INT32 player) memset(p->kartstuff, 0, sizeof (p->kartstuff)); // SRB2kart p->ringweapons = 0; - p->mo->flags2 &= ~MF2_SHADOW; // cancel invisibility + p->mo->drawflags &= ~(MFD_TRANSMASK|MFD_BRIGHTMASK); // cancel invisibility P_FlashPal(p, 0, 0); // Resets - p->starpostscale = 0; - p->starpostangle = 0; - p->starposttime = 0; - p->starpostx = 0; - p->starposty = 0; - p->starpostz = 0; + p->starpostnum = 0; + memset(&p->respawn, 0, sizeof (p->respawn)); // SRB2kart: Increment the "matches played" counter. if (player == consoleplayer) @@ -2092,10 +2081,17 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) player_t *p; INT32 score, marescore; INT32 lives; + boolean lostlife; INT32 continues; UINT8 kartspeed; UINT8 kartweight; + + boolean followerready; + INT32 followerskin; + UINT8 followercolor; + mobj_t *follower; // old follower, will probably be removed by the time we're dead but you never know. + INT32 charflags; UINT32 followitem; @@ -2103,14 +2099,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT32 ctfteam; - INT32 starposttime; - INT16 starpostx; - INT16 starposty; - INT16 starpostz; INT32 starpostnum; - INT32 starpostangle; - fixed_t starpostscale; - INT32 exiting; tic_t dashmode; INT16 numboxes; @@ -2133,7 +2122,13 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT16 playerangleturn; INT16 oldrelangleturn; + UINT8 botdiffincrease; + boolean botrival; + + SINT8 xtralife; + // SRB2kart + respawnvars_t respawn; INT32 itemtype; INT32 itemamount; INT32 itemroulette; @@ -2143,12 +2138,12 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT32 comebackpoints; INT32 wanted; INT32 rings; - INT32 respawnflip; boolean songcredit = false; score = players[player].score; marescore = players[player].marescore; lives = players[player].lives; + lostlife = players[player].lostlife; continues = players[player].continues; ctfteam = players[player].ctfteam; exiting = players[player].exiting; @@ -2184,25 +2179,27 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) kartspeed = players[player].kartspeed; kartweight = players[player].kartweight; + follower = players[player].follower; + followerready = players[player].followerready; + followercolor = players[player].followercolor; + followerskin = players[player].followerskin; + availabilities = players[player].availabilities; charflags = players[player].charflags; - starposttime = players[player].starposttime; - starpostx = players[player].starpostx; - starposty = players[player].starposty; - starpostz = players[player].starpostz; starpostnum = players[player].starpostnum; - respawnflip = players[player].kartstuff[k_starpostflip]; //SRB2KART - starpostangle = players[player].starpostangle; - starpostscale = players[player].starpostscale; - followitem = players[player].followitem; mare = players[player].mare; bot = players[player].bot; botdifficulty = players[player].botvars.difficulty; + botdiffincrease = players[player].botvars.diffincrease; + botrival = players[player].botvars.rival; + + xtralife = players[player].xtralife; + // SRB2kart if (betweenmaps || leveltime < starttime) { @@ -2244,12 +2241,18 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) wanted = players[player].kartstuff[k_wanted]; } + // Obliterate follower from existence + P_SetTarget(&players[player].follower, NULL); + + memcpy(&respawn, &players[player].respawn, sizeof (respawn)); + p = &players[player]; memset(p, 0, sizeof (*p)); p->score = score; p->marescore = marescore; p->lives = lives; + p->lostlife = lostlife; p->continues = continues; p->pflags = pflags; p->ctfteam = ctfteam; @@ -2270,14 +2273,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->availabilities = availabilities; p->followitem = followitem; - p->starposttime = starposttime; - p->starpostx = starpostx; - p->starposty = starposty; - p->starpostz = starpostz; p->starpostnum = starpostnum; - p->starpostangle = starpostangle; - p->starpostscale = starpostscale; - p->exiting = exiting; p->dashmode = dashmode; @@ -2290,6 +2286,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->bot = bot; p->botvars.difficulty = botdifficulty; p->rings = rings; + p->botvars.diffincrease = botdiffincrease; + p->botvars.rival = botrival; + p->xtralife = xtralife; // SRB2kart p->kartstuff[k_itemroulette] = itemroulette; @@ -2304,7 +2303,18 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->kartstuff[k_wanted] = wanted; p->kartstuff[k_eggmanblame] = -1; p->kartstuff[k_lastdraft] = -1; - p->kartstuff[k_starpostflip] = respawnflip; + + memcpy(&p->respawn, &respawn, sizeof (p->respawn)); + + if (follower) + P_RemoveMobj(follower); + + p->followerready = followerready; + p->followerskin = followerskin; + p->followercolor = followercolor; + //p->follower = NULL; // respawn a new one with you, it looks better. + // ^ Not necessary anyway since it will be respawned regardless considering it doesn't exist anymore. + // Don't do anything immediately p->pflags |= PF_USEDOWN; @@ -2362,7 +2372,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) S_ShowMusicCredit(); if (leveltime > (starttime + (TICRATE/2)) && !p->spectator) - p->kartstuff[k_respawn] = 48; // Respawn effect + { + K_DoIngameRespawn(p); + } } // @@ -2778,10 +2790,7 @@ void G_AddPlayer(INT32 playernum) p->playerstate = PST_REBORN; - demo_extradata[playernum] |= DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN; // Set everything - - if (G_GametypeUsesLives()) - p->lives = 3; + demo_extradata[playernum] |= DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything if ((countplayers && !notexiting) || G_IsSpecialStage(gamemap)) P_DoPlayerExit(p); @@ -2791,6 +2800,47 @@ void G_ExitLevel(void) { if (gamestate == GS_LEVEL) { + if (grandprixinfo.gp == true && grandprixinfo.wonround != true) + { + UINT8 i; + + // You didn't win... + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && !players[i].bot) + { + if (players[i].lives > 0) + { + break; + } + } + } + + if (i == MAXPLAYERS) + { + // GAME OVER, try again from the start! + + if (netgame) + { + ; // restart cup here if we do online GP + } + else + { + D_QuitNetGame(); + CL_Reset(); + D_StartTitle(); + } + } + else + { + // Go redo this course. + G_SetRetryFlag(); + } + + return; + } + gameaction = ga_completed; lastdraw = true; @@ -3073,11 +3123,13 @@ boolean G_IsSpecialStage(INT32 mapnum) // boolean G_GametypeUsesLives(void) { - if ((gametyperules & GTR_LIVES) - && !(modeattacking || metalrecording) // No lives in Time Attack - && !battlecapsules // No lives in bonus game - && !G_IsSpecialStage(gamemap)) // No lives in special stage + if ((grandprixinfo.gp == true) // In Grand Prix + && (gametype == GT_RACE) // NOT in bonus round + && !G_IsSpecialStage(gamemap) // NOT in special stage + && !(modeattacking || metalrecording)) // NOT in Record Attack + { return true; + } return false; } @@ -3460,11 +3512,7 @@ static void G_HandleSaveLevel(void) static void G_DoCompleted(void) { INT32 i, j = 0; - boolean gottoken = false; - SINT8 powertype = PWRLV_DISABLED; - boolean spec = G_IsSpecialStage(gamemap); - - tokenlist = 0; // Reset the list + SINT8 powertype = K_UsingPowerLevels(); if (modeattacking && pausedelay) pausedelay = 0; @@ -3482,10 +3530,21 @@ static void G_DoCompleted(void) // SRB2Kart: exitlevel shouldn't get you the points if (!players[i].exiting && !(players[i].pflags & PF_TIMEOVER)) { - players[i].pflags |= PF_TIMEOVER; - if (P_IsLocalPlayer(&players[i])) - j++; + if (players[i].bot) + { + K_FakeBotResults(&players[i]); + } + else + { + players[i].pflags |= PF_TIMEOVER; + + if (P_IsLocalPlayer(&players[i])) + { + j++; + } + } } + G_PlayerFinishLevel(i); // take away cards and stuff } @@ -3505,11 +3564,30 @@ static void G_DoCompleted(void) // go to next level // nextmap is 0-based, unlike gamemap if (nextmapoverride != 0) + { nextmap = (INT16)(nextmapoverride-1); else if (marathonmode && mapheaderinfo[gamemap-1]->marathonnext) nextmap = (INT16)(mapheaderinfo[gamemap-1]->marathonnext-1); - else if (mapheaderinfo[gamemap-1]->nextlevel == 1101) // SRB2Kart: !!! WHENEVER WE GET GRAND PRIX, GO TO AWARDS MAP INSTEAD !!! - nextmap = (INT16)(mapheaderinfo[gamemap] ? gamemap : (spstage_start-1)); // (gamemap-1)+1 == gamemap :V + else if (grandprixinfo.gp == true) + { + if (grandprixinfo.roundnum == 0 || grandprixinfo.cup == NULL) // Single session + { + nextmap = prevmap; // Same map + } + else + { + if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map + { + nextmap = 1101; // ceremonymap + } + else + { + // Proceed to next map + nextmap = grandprixinfo.cup->levellist[grandprixinfo.roundnum]; + grandprixinfo.roundnum++; + } + } + } else { nextmap = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1); @@ -3517,11 +3595,15 @@ static void G_DoCompleted(void) nextmap = 1100-1; // No infinite loop for you } + // Remember last map for when you come out of the special stage. + if (!G_IsSpecialStage(gamemap)) + lastmap = nextmap; + // If nextmap is actually going to get used, make sure it points to // a map of the proper gametype -- skip levels that don't support // the current gametype. (Helps avoid playing boss levels in Race, // for instance). - if (!spec) + if (!modeattacking && grandprixinfo.gp == false && (nextmap >= 0 && nextmap < NUMMAPS)) { if (nextmap >= 0 && nextmap < NUMMAPS) { @@ -3576,35 +3658,18 @@ static void G_DoCompleted(void) lastmap = nextmap; // Remember last map for when you come out of the special stage. } - if ((gottoken = ((gametyperules & GTR_SPECIALSTAGES) && token))) - { - token--; - - for (i = 0; i < 7; i++) - if (!(emeralds & (1<lives = (ultimatemode) ? 1 : startinglivesbalance[numgameovers]; + pl->lives = 3; D_MapChange(gamemap, gametype, false, false, 0, false, false); @@ -4382,6 +4439,7 @@ void G_DeferedInitNew(boolean pencoremode, const char *mapname, INT32 pickedchar { INT32 i; UINT16 color = SKINCOLOR_NONE; + paused = false; if (demo.playback) @@ -4395,11 +4453,7 @@ void G_DeferedInitNew(boolean pencoremode, const char *mapname, INT32 pickedchar // this leave the actual game if needed SV_StartSinglePlayerServer(); - if (savedata.lives > 0) - { - color = savedata.skincolor; - } - else if (splitscreen != ssplayers) + if (splitscreen != ssplayers) { splitscreen = ssplayers; SplitScreen_OnChange(); @@ -4444,62 +4498,40 @@ void G_InitNew(UINT8 pencoremode, const char *mapname, boolean resetplayer, bool if (!demo.playback && !netgame) // Netgame sets random seed elsewhere, demo playback sets seed just before us! P_SetRandSeed(M_RandomizedSeed()); // Use a more "Random" random seed - //SRB2Kart - Score is literally the only thing you SHOULDN'T reset at all times - //if (resetplayer) + // Clear a bunch of variables + redscore = bluescore = lastmap = 0; + racecountdown = exitcountdown = mapreset = exitfadestarted = 0; + + for (i = 0; i < MAXPLAYERS; i++) { - // Clear a bunch of variables - numgameovers = tokenlist = token = sstimer = redscore = bluescore = lastmap = 0; - racecountdown = exitcountdown = mapreset = exitfadestarted = 0; + players[i].playerstate = PST_REBORN; + players[i].starpostnum = 0; + memset(&players[i].respawn, 0, sizeof (players[i].respawn)); - for (i = 0; i < MAXPLAYERS; i++) + // The latter two should clear by themselves, but just in case + players[i].pflags &= ~(PF_GAMETYPEOVER|PF_FULLSTASIS); + + // Clear cheatcodes too, just in case. + players[i].pflags &= ~(PF_GODMODE|PF_NOCLIP|PF_INVIS); + + players[i].marescore = 0; + + if (resetplayer && !(multiplayer && demo.playback)) // SRB2Kart { - players[i].playerstate = PST_REBORN; - players[i].starpostscale = players[i].starpostangle = players[i].starpostnum = players[i].starposttime = 0; - players[i].starpostx = players[i].starposty = players[i].starpostz = 0; - -#if 0 - if (netgame || multiplayer) - { - if (!FLS || (players[i].lives < 1)) - players[i].lives = cv_startinglives.value; - players[i].continues = 0; - } - else - { - players[i].lives = (pultmode) ? 1 : startinglivesbalance[0]; - players[i].continues = (pultmode) ? 0 : 1; - } - + players[i].lives = 3; players[i].xtralife = 0; -#else - players[i].lives = 1; // SRB2Kart -#endif - - if (!((netgame || multiplayer) && (FLS))) - players[i].score = 0; - - // The latter two should clear by themselves, but just in case - players[i].pflags &= ~(PF_TAGIT|PF_GAMETYPEOVER|PF_FULLSTASIS); - - // Clear cheatcodes too, just in case. - players[i].pflags &= ~(PF_GODMODE|PF_NOCLIP|PF_INVIS); - - players[i].marescore = 0; - - if (resetplayer && !(multiplayer && demo.playback)) // SRB2Kart - { - players[i].score = 0; - } + players[i].totalring = 0; + players[i].score = 0; } - - // Reset unlockable triggers - unlocktriggers = 0; - - // clear itemfinder, just in case - if (!dedicated) // except in dedicated servers, where it is not registered and can actually I_Error debug builds - CV_StealthSetValue(&cv_itemfinder, 0); } + // Reset unlockable triggers + unlocktriggers = 0; + + // clear itemfinder, just in case + if (!dedicated) // except in dedicated servers, where it is not registered and can actually I_Error debug builds + CV_StealthSetValue(&cv_itemfinder, 0); + // internal game map // well this check is useless because it is done before (d_netcmd.c::command_map_f) // but in case of for demos.... diff --git a/src/g_game.h b/src/g_game.h index 0473afa47..90df573fd 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -187,7 +187,6 @@ void G_UpdateGametypeSelections(void); void G_AddTOL(UINT32 newtol, const char *tolname); void G_AddGametypeTOL(INT16 gtype, UINT32 newtol); void G_SetGametypeDescription(INT16 gtype, char *descriptiontext, UINT8 leftcolor, UINT8 rightcolor); - INT32 G_GetGametypeByName(const char *gametypestr); boolean G_IsSpecialStage(INT32 mapnum); boolean G_GametypeUsesLives(void); diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c index 10fdf5893..e3e54693c 100644 --- a/src/hardware/hw_draw.c +++ b/src/hardware/hw_draw.c @@ -30,6 +30,7 @@ #include "../st_stuff.h" #include "../p_local.h" // stplyr #include "../g_game.h" // players +#include "../k_hud.h" #include #include "../i_video.h" // for rendermode != render_glide @@ -199,80 +200,6 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t cy -= offsety; } - if (option & V_SPLITSCREEN) - cy += FIXED_TO_FLOAT((BASEVIDHEIGHT/2)< 1) // 3 or 4 players - { - float adjustx = ((option & V_NOSCALESTART) ? vid.width : BASEVIDWIDTH)/2.0f; - fscalew /= 2; - cx /= 2; - if (stplyr == &players[displayplayers[0]]) - { - if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle |= 1; - if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT))) - perplayershuffle |= 4; - option &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT; - } - else if (stplyr == &players[displayplayers[1]]) - { - if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle |= 1; - if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT))) - perplayershuffle |= 8; - cx += adjustx; - option &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT; - } - else if (stplyr == &players[displayplayers[2]]) - { - if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle |= 2; - if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT))) - perplayershuffle |= 4; - cy += adjusty; - option &= ~V_SNAPTOTOP|V_SNAPTORIGHT; - } - else //if (stplyr == &players[displayplayers[3]]) - { - if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle |= 2; - if (!(option & (V_SNAPTOLEFT|V_SNAPTORIGHT))) - perplayershuffle |= 8; - cx += adjustx; - cy += adjusty; - option &= ~V_SNAPTOTOP|V_SNAPTOLEFT; - } - } - else -#endif - // 2 players - { - if (stplyr == &players[displayplayers[0]]) - { - if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle = 1; - option &= ~V_SNAPTOBOTTOM; - } - else //if (stplyr == &players[displayplayers[1]]) - { - if (!(option & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle = 2; - cy += adjusty; - option &= ~V_SNAPTOTOP; - } - } - } - if (!(option & V_NOSCALESTART)) { cx = cx * dupx; @@ -280,44 +207,12 @@ void HWR_DrawStretchyFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t if (!(option & V_SCALEPATCHMASK)) { - // if it's meant to cover the whole screen, black out the rest (ONLY IF TOP LEFT ISN'T TRANSPARENT) - // cx and cy are possibly *slightly* off from float maths - // This is done before here compared to software because we directly alter cx and cy to centre - if (cx >= -0.1f && cx <= 0.1f && SHORT(gpatch->width) == BASEVIDWIDTH && cy >= -0.1f && cy <= 0.1f && SHORT(gpatch->height) == BASEVIDHEIGHT) - { - // Need to temporarily cache the real patch to get the colour of the top left pixel - patch_t *realpatch = W_CacheSoftwarePatchNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC); - const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0])); - if (!column->topdelta) - { - const UINT8 *source = (const UINT8 *)(column) + 3; - HWR_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0])); - } - Z_Free(realpatch); - } - // centre screen - if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f) - { - if (option & V_SNAPTORIGHT) - cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)); - else if (!(option & V_SNAPTOLEFT)) - cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/2; - if (perplayershuffle & 4) - cx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/4; - else if (perplayershuffle & 8) - cx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx))/4; - } - if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f) - { - if (option & V_SNAPTOBOTTOM) - cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)); - else if (!(option & V_SNAPTOTOP)) - cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/2; - if (perplayershuffle & 1) - cy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/4; - else if (perplayershuffle & 2) - cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/4; - } + INT32 intx, inty; + intx = (INT32)cx; + inty = (INT32)cy; + K_AdjustXYWithSnap(&intx, &inty, option, dupx, dupy); + cx = (float)intx; + cy = (float)inty; } } @@ -1263,40 +1158,18 @@ void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color, UINT32 if (!(color & V_NOSCALESTART)) { float dupx = (float)vid.dupx, dupy = (float)vid.dupy; + INT32 intx, inty; fx *= dupx; fy *= dupy; fw *= dupx; fh *= dupy; - if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f) - { - if (color & V_SNAPTORIGHT) - fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)); - else if (!(color & V_SNAPTOLEFT)) - fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 2; - if (perplayershuffle & 4) - fx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4; - else if (perplayershuffle & 8) - fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4; - } - if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f) - { - // same thing here - if (color & V_SNAPTOBOTTOM) - fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)); - else if (!(color & V_SNAPTOTOP)) - fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 2; - if (perplayershuffle & 1) - fy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4; - else if (perplayershuffle & 2) - fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4; - } - if (options & V_SPLITSCREEN) - fy += ((float)BASEVIDHEIGHT * dupy)/2; - if (options & V_HORZSCREEN) - fx += ((float)BASEVIDWIDTH * dupx)/2; - + intx = (INT32)fx; + inty = (INT32)fy; + K_AdjustXYWithSnap(&intx, &inty, options, dupx, dupy); + fx = (float)intx; + fy = (float)inty; } if (fx >= vid.width || fy >= vid.height) @@ -1434,6 +1307,7 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color) if (!(color & V_NOSCALESTART)) { float dupx = (float)vid.dupx, dupy = (float)vid.dupy; + INT32 intx, inty; if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT) { @@ -1452,34 +1326,11 @@ void HWR_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 color) fw *= dupx; fh *= dupy; - if (fabsf((float)vid.width - (float)BASEVIDWIDTH * dupx) > 1.0E-36f) - { - if (color & V_SNAPTORIGHT) - fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)); - else if (!(color & V_SNAPTOLEFT)) - fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 2; - if (perplayershuffle & 4) - fx -= ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4; - else if (perplayershuffle & 8) - fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 4; - } - if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f) - { - // same thing here - if (color & V_SNAPTOBOTTOM) - fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)); - else if (!(color & V_SNAPTOTOP)) - fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 2; - if (perplayershuffle & 1) - fy -= ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4; - else if (perplayershuffle & 2) - fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 4; - } - if (color & V_SPLITSCREEN) - fy += ((float)BASEVIDHEIGHT * dupy)/2; - if (color & V_HORZSCREEN) - fx += ((float)BASEVIDWIDTH * dupx)/2; - + intx = (INT32)fx; + inty = (INT32)fy; + K_AdjustXYWithSnap(&intx, &inty, color, dupx, dupy); + fx = (float)intx; + fy = (float)inty; } if (fx >= vid.width || fy >= vid.height) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 9a85aa1eb..49a6a14c5 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -3571,8 +3571,6 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) groundz = R_GetShadowZ(thing, &groundslope); - //if (abs(groundz - gl_viewz) / tz > 4) return; // Prevent stretchy shadows and possible crashes - floordiff = abs((flip < 0 ? thing->height : 0) + thingzpos - groundz); alpha = floordiff / (4*FRACUNIT) + 75; @@ -3700,6 +3698,7 @@ static void HWR_SplitSprite(gl_vissprite_t *spr) FBITFIELD occlusion; boolean use_linkdraw_hack = false; UINT8 alpha; + UINT8 brightmode = 0; INT32 i; float realtop, realbot, top, bot; @@ -3799,17 +3798,8 @@ static void HWR_SplitSprite(gl_vissprite_t *spr) else occlusion = PF_Occlude; - if (!cv_translucency.value) // translucency disabled - { - Surf.PolyColor.s.alpha = 0xFF; - blend = PF_Translucent|occlusion; - if (!occlusion) use_linkdraw_hack = true; - } - else if (spr->mobj->flags2 & MF2_SHADOW) - { - Surf.PolyColor.s.alpha = 0x40; - blend = PF_Translucent; - } + if (spr->mobj->drawflags & MFD_TRANSMASK) + blend = HWR_TranstableToAlpha((spr->mobj->drawflags & MFD_TRANSMASK)>>MFD_TRANSSHIFT, &Surf); else if (spr->mobj->frame & FF_TRANSMASK) blend = HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf); else @@ -3825,31 +3815,44 @@ static void HWR_SplitSprite(gl_vissprite_t *spr) alpha = Surf.PolyColor.s.alpha; + if (spr->mobj->drawflags & MFD_BRIGHTMASK) + { + if (spr->mobj->drawflags & MFD_FULLBRIGHT) + brightmode = 1; + else if (spr->mobj->drawflags & MFD_SEMIBRIGHT) + brightmode = 2; + } + else + { + if (spr->mobj->frame & FF_FULLBRIGHT) + brightmode = 1; + else if (spr->mobj->frame & FF_SEMIBRIGHT) + brightmode = 2; + } + // Start with the lightlevel and colormap from the top of the sprite - lightlevel = *list[sector->numlights - 1].lightlevel; colormap = *list[sector->numlights - 1].extra_colormap; i = 0; temp = FLOAT_TO_FIXED(realtop); - if (spr->mobj->frame & FF_FULLBRIGHT) - lightlevel = 255; + if (brightmode != 1) + lightlevel = *list[sector->numlights - 1].lightlevel; for (i = 1; i < sector->numlights; i++) { fixed_t h = P_GetLightZAt(§or->lightlist[i], spr->mobj->x, spr->mobj->y); if (h <= temp) { - if (!(spr->mobj->frame & FF_FULLBRIGHT)) - lightlevel = *list[i-1].lightlevel > 255 ? 255 : *list[i-1].lightlevel; - - if (spr->mobj->frame & FF_SEMIBRIGHT) - lightlevel = 128 + (lightlevel>>1); - + if (brightmode != 1) + lightlevel = *list[i-1].lightlevel; colormap = *list[i-1].extra_colormap; break; } } + if (brightmode == 2) + lightlevel = 128 + (lightlevel>>1); + for (i = 0; i < sector->numlights; i++) { if (endtop < endrealbot && top < realbot) @@ -3858,13 +3861,13 @@ static void HWR_SplitSprite(gl_vissprite_t *spr) // even if we aren't changing colormap or lightlevel, we still need to continue drawing down the sprite if (!(list[i].flags & FF_NOSHADE) && (list[i].flags & FF_CUTSPRITES)) { - if (!(spr->mobj->frame & FF_FULLBRIGHT)) - lightlevel = *list[i].lightlevel > 255 ? 255 : *list[i].lightlevel; - - if (spr->mobj->frame & FF_SEMIBRIGHT) - lightlevel = 128 + (lightlevel>>1); - - colormap = *list[i].extra_colormap; + if (brightmode != 1) + { + lightlevel = *list[i].lightlevel; + if (brightmode == 2) + lightlevel = 128 + (lightlevel>>1); + } + colormap = list[i].extra_colormap; } if (i + 1 < sector->numlights) @@ -4076,12 +4079,28 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) { sector_t *sector = spr->mobj->subsector->sector; UINT8 lightlevel = 255; + UINT8 brightmode = 0; extracolormap_t *colormap = sector->extra_colormap; - if (!(spr->mobj->frame & FF_FULLBRIGHT)) - lightlevel = sector->lightlevel > 255 ? 255 : sector->lightlevel; + if (spr->mobj->drawflags & MFD_BRIGHTMASK) + { + if (spr->mobj->drawflags & MFD_FULLBRIGHT) + brightmode = 1; + else if (spr->mobj->drawflags & MFD_SEMIBRIGHT) + brightmode = 2; + } + else + { + if (spr->mobj->frame & FF_FULLBRIGHT) + brightmode = 1; + else if (spr->mobj->frame & FF_SEMIBRIGHT) + brightmode = 2; + } - if (spr->mobj->frame & FF_SEMIBRIGHT) + if (brightmode != 1) + lightlevel = sector->lightlevel; + + if (brightmode == 2) lightlevel = 128 + (lightlevel>>1); HWR_Lighting(&Surf, lightlevel, colormap); @@ -4099,17 +4118,8 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) else occlusion = PF_Occlude; - if (!cv_translucency.value) // translucency disabled - { - Surf.PolyColor.s.alpha = 0xFF; - blend = PF_Translucent|occlusion; - if (!occlusion) use_linkdraw_hack = true; - } - else if (spr->mobj->flags2 & MF2_SHADOW) - { - Surf.PolyColor.s.alpha = 0x40; - blend = PF_Translucent; - } + if (spr->mobj->drawflags & MFD_TRANSMASK) + blend = HWR_TranstableToAlpha((spr->mobj->drawflags & MFD_TRANSMASK)>>MFD_TRANSSHIFT, &Surf); else if (spr->mobj->frame & FF_TRANSMASK) blend = HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf); else @@ -4182,43 +4192,53 @@ static inline void HWR_DrawPrecipitationSprite(gl_vissprite_t *spr) { sector_t *sector = spr->mobj->subsector->sector; UINT8 lightlevel = 255; + UINT8 brightmode = 0; extracolormap_t *colormap = sector->extra_colormap; + if (spr->mobj->drawflags & MFD_BRIGHTMASK) + { + if (spr->mobj->drawflags & MFD_FULLBRIGHT) + brightmode = 1; + else if (spr->mobj->drawflags & MFD_SEMIBRIGHT) + brightmode = 2; + } + else + { + if (spr->mobj->frame & FF_FULLBRIGHT) + brightmode = 1; + else if (spr->mobj->frame & FF_SEMIBRIGHT) + brightmode = 2; + } + if (sector->numlights) { INT32 light; light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false); // Always use the light at the top instead of whatever I was doing before - if (!(spr->mobj->frame & FF_FULLBRIGHT)) - lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel; - - if (spr->mobj->frame & FF_SEMIBRIGHT) - lightlevel = 128 + (lightlevel>>1); + if (brightmode != 1) + lightlevel = *sector->lightlist[light].lightlevel; if (*sector->lightlist[light].extra_colormap) colormap = *sector->lightlist[light].extra_colormap; } else { - if (!(spr->mobj->frame & FF_FULLBRIGHT)) - lightlevel = sector->lightlevel > 255 ? 255 : sector->lightlevel; - - if (spr->mobj->frame & FF_SEMIBRIGHT) - lightlevel = 128 + (lightlevel>>1); + if (brightmode != 1) + lightlevel = sector->lightlevel; if (sector->extra_colormap) colormap = sector->extra_colormap; } + if (brightmode == 2) + lightlevel = 128 + (lightlevel>>1); + HWR_Lighting(&Surf, lightlevel, colormap); } - if (spr->mobj->flags2 & MF2_SHADOW) - { - Surf.PolyColor.s.alpha = 0x40; - blend = PF_Translucent; - } + if (spr->mobj->drawflags & MFD_TRANSMASK) + blend = HWR_TranstableToAlpha((spr->mobj->drawflags & MFD_TRANSMASK)>>MFD_TRANSSHIFT, &Surf); else if (spr->mobj->frame & FF_TRANSMASK) blend = HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf); else @@ -4259,6 +4279,12 @@ static int CompareVisSprites(const void *p1, const void *p2) int transparency1; int transparency2; + int drawflags1; + int drawflags2; + + int frame1; + int frame2; + // check for precip first, because then sprX->mobj is actually a precipmobj_t and does not have flags2 or tracer int linkdraw1 = !spr1->precip && (spr1->mobj->flags2 & MF2_LINKDRAW) && spr1->mobj->tracer; int linkdraw2 = !spr2->precip && (spr2->mobj->flags2 & MF2_LINKDRAW) && spr2->mobj->tracer; @@ -4271,34 +4297,48 @@ static int CompareVisSprites(const void *p1, const void *p2) if (linkdraw1) { tz1 = spr1->tracertz; - transparency1 = (spr1->mobj->tracer->flags2 & MF2_SHADOW) || (spr1->mobj->tracer->frame & FF_TRANSMASK); + drawflags1 = spr1->mobj->tracer->drawflags; + frame1 = spr1->mobj->tracer->frame; } else { tz1 = spr1->tz; - transparency1 = (!spr1->precip && (spr1->mobj->flags2 & MF2_SHADOW)) || (spr1->mobj->frame & FF_TRANSMASK); + drawflags1 = (spr1->precip ? 0 : spr1->mobj->drawflags); + frame1 = spr1->mobj->frame; } if (linkdraw2) { tz2 = spr2->tracertz; - transparency2 = (spr2->mobj->tracer->flags2 & MF2_SHADOW) || (spr2->mobj->tracer->frame & FF_TRANSMASK); + drawflags2 = spr2->mobj->tracer->drawflags; + frame2 = spr2->mobj->tracer->frame; } else { tz2 = spr2->tz; - transparency2 = (!spr2->precip && (spr2->mobj->flags2 & MF2_SHADOW)) || (spr2->mobj->frame & FF_TRANSMASK); + drawflags2 = (spr2->precip ? 0 : spr2->mobj->drawflags); + frame2 = spr2->mobj->frame; } } else { tz1 = spr1->tz; - transparency1 = (!spr1->precip && (spr1->mobj->flags2 & MF2_SHADOW)) || (spr1->mobj->frame & FF_TRANSMASK); + drawflags1 = (spr1->precip ? 0 : spr1->mobj->drawflags); + frame1 = spr1->mobj->frame; tz2 = spr2->tz; - transparency2 = (!spr2->precip && (spr2->mobj->flags2 & MF2_SHADOW)) || (spr2->mobj->frame & FF_TRANSMASK); + drawflags2 = (spr2->precip ? 0 : spr2->mobj->drawflags); + frame2 = spr2->mobj->frame; } // first compare transparency flags, then compare tz, then compare dispoffset + transparency1 = (drawflags1 & FF_TRANSMASK) ? + ((drawflags1 & FF_TRANSMASK)>>MFD_TRANSSHIFT) : + ((frame1 & FF_TRANSMASK)>>FF_TRANSSHIFT); + + transparency2 = (drawflags2 & FF_TRANSMASK) ? + ((drawflags2 & FF_TRANSMASK)>>MFD_TRANSSHIFT) : + ((frame2 & FF_TRANSMASK)>>FF_TRANSSHIFT); + idiff = transparency1 - transparency2; if (idiff != 0) return idiff; diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index d1106749e..7e3a7db25 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -1260,6 +1260,23 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) sector_t *sector = spr->mobj->subsector->sector; UINT8 lightlevel = 255; extracolormap_t *colormap = NULL; + UINT8 brightmode = 0; + + + if (spr->mobj->drawflags & MFD_BRIGHTMASK) + { + if (spr->mobj->drawflags & MFD_FULLBRIGHT) + brightmode = 1; + else if (spr->mobj->drawflags & MFD_SEMIBRIGHT) + brightmode = 2; + } + else + { + if (spr->mobj->frame & FF_FULLBRIGHT) + brightmode = 1; + else if (spr->mobj->frame & FF_SEMIBRIGHT) + brightmode = 2; + } if (sector->numlights) { @@ -1267,21 +1284,24 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) light = R_GetPlaneLight(sector, spr->mobj->z + spr->mobj->height, false); // Always use the light at the top instead of whatever I was doing before - if (!(spr->mobj->frame & FF_FULLBRIGHT)) - lightlevel = *sector->lightlist[light].lightlevel > 255 ? 255 : *sector->lightlist[light].lightlevel; + if (brightmode != 1) + lightlevel = *sector->lightlist[light].lightlevel; if (*sector->lightlist[light].extra_colormap) colormap = *sector->lightlist[light].extra_colormap; } else { - if (!(spr->mobj->frame & FF_FULLBRIGHT)) - lightlevel = sector->lightlevel > 255 ? 255 : sector->lightlevel; + if (brightmode != 1) + lightlevel = sector->lightlevel; if (sector->extra_colormap) colormap = sector->extra_colormap; } + if (brightmode == 2) + lightlevel = 128 + (lightlevel>>1); + HWR_Lighting(&Surf, lightlevel, colormap); } else @@ -1311,8 +1331,8 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) //if (tics > durs) //durs = tics; - if (spr->mobj->flags2 & MF2_SHADOW) - Surf.PolyColor.s.alpha = 0x40; + if (spr->mobj->drawflags & MFD_TRANSMASK) + HWR_TranstableToAlpha((spr->mobj->drawflags & MFD_TRANSMASK)>>MFD_TRANSSHIFT, &Surf); else if (spr->mobj->frame & FF_TRANSMASK) HWR_TranstableToAlpha((spr->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT, &Surf); else diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c index 0466b7a0c..35a5ca412 100644 --- a/src/hardware/r_opengl/r_opengl.c +++ b/src/hardware/r_opengl/r_opengl.c @@ -140,7 +140,7 @@ static const GLfloat byte2float[256] = { // -----------------+ #ifdef DEBUG_TO_FILE -FILE *gllogstream; +FILE *gllogstream = NULL; #endif FUNCPRINTF void GL_DBG_Printf(const char *format, ...) @@ -149,14 +149,14 @@ FUNCPRINTF void GL_DBG_Printf(const char *format, ...) char str[4096] = ""; va_list arglist; - if (!gllogstream) - gllogstream = fopen("ogllog.txt", "w"); + if (gllogstream) + { + va_start(arglist, format); + vsnprintf(str, 4096, format, arglist); + va_end(arglist); - va_start(arglist, format); - vsnprintf(str, 4096, format, arglist); - va_end(arglist); - - fwrite(str, strlen(str), 1, gllogstream); + fwrite(str, strlen(str), 1, gllogstream); + } #else (void)format; #endif diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 3f18b7360..165735f2d 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -14,6 +14,7 @@ #include "doomdef.h" #include "byteptr.h" #include "hu_stuff.h" +#include "font.h" #include "m_menu.h" // gametype_cons_t #include "m_cond.h" // emblems @@ -52,6 +53,7 @@ #include "s_sound.h" // song credits #include "k_kart.h" +#include "k_color.h" // coords are scaled #define HU_INPUTX 0 @@ -63,24 +65,9 @@ //------------------------------------------- // heads up font //------------------------------------------- -patch_t *hu_font[HU_FONTSIZE]; -patch_t *kart_font[KART_FONTSIZE]; // SRB2kart -patch_t *tny_font[HU_FONTSIZE]; -patch_t *tallnum[10]; // 0-9 -patch_t *nightsnum[10]; // 0-9 - -// Level title and credits fonts -patch_t *lt_font[LT_FONTSIZE]; -patch_t *cred_font[CRED_FONTSIZE]; -patch_t *ttlnum[10]; // act numbers (0-9) - -// Name tag fonts -patch_t *ntb_font[NT_FONTSIZE]; -patch_t *nto_font[NT_FONTSIZE]; // ping font // Note: I'd like to adress that at this point we might *REALLY* want to work towards a common drawString function that can take any font we want because this is really turning into a MESS. :V -Lat' -patch_t *pingnum[10]; patch_t *pinggfx[5]; // small ping graphic patch_t *mping[5]; // smaller ping graphic @@ -193,128 +180,25 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum); void HU_LoadGraphics(void) { - char buffer[9]; - INT32 i, j; + INT32 i; if (dedicated) return; - j = HU_FONTSTART; - for (i = 0; i < HU_FONTSIZE; i++, j++) - { - // cache the heads-up font for entire game execution - sprintf(buffer, "STCFN%.3d", j); - if (W_CheckNumForName(buffer) == LUMPERROR) - hu_font[i] = NULL; - else - hu_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - - // tiny version of the heads-up font - sprintf(buffer, "TNYFN%.3d", j); - if (W_CheckNumForName(buffer) == LUMPERROR) - tny_font[i] = NULL; - else - tny_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - } - - // SRB2kart - j = KART_FONTSTART; - for (i = 0; i < KART_FONTSIZE; i++, j++) - { - // cache the heads-up font for entire game execution - sprintf(buffer, "MKFNT%.3d", j); - if (W_CheckNumForName(buffer) == LUMPERROR) - kart_font[i] = NULL; - else - kart_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - } - // - - j = LT_FONTSTART; - for (i = 0; i < LT_FONTSIZE; i++) - { - sprintf(buffer, "LTFNT%.3d", j); - j++; - - if (W_CheckNumForName(buffer) == LUMPERROR) - lt_font[i] = NULL; - else - lt_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - } - - // cache the credits font for entire game execution (why not?) - j = CRED_FONTSTART; - for (i = 0; i < CRED_FONTSIZE; i++) - { - sprintf(buffer, "CRFNT%.3d", j); - j++; - - if (W_CheckNumForName(buffer) == LUMPERROR) - cred_font[i] = NULL; - else - cred_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - } - - //cache numbers too! - for (i = 0; i < 10; i++) - { - sprintf(buffer, "STTNUM%d", i); - tallnum[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - sprintf(buffer, "NGTNUM%d", i); - nightsnum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - sprintf(buffer, "PINGN%d", i); - pingnum[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } + Font_Load(); // minus for negative tallnums - tallminus = (patch_t *)W_CachePatchName("STTMINUS", PU_HUDGFX); - tallinfin = (patch_t *)W_CachePatchName("STTINFIN", PU_HUDGFX); - - // cache act numbers for level titles - for (i = 0; i < 10; i++) - { - sprintf(buffer, "TTL%.2d", i); - ttlnum[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - } - - // cache the base name tag font for entire game execution - j = NT_FONTSTART; - for (i = 0; i < NT_FONTSIZE; i++) - { - sprintf(buffer, "NTFNT%.3d", j); - j++; - - if (W_CheckNumForName(buffer) == LUMPERROR) - ntb_font[i] = NULL; - else - ntb_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - } - - // cache the outline name tag font for entire game execution - j = NT_FONTSTART; - for (i = 0; i < NT_FONTSIZE; i++) - { - sprintf(buffer, "NTFNO%.3d", j); - j++; - - if (W_CheckNumForName(buffer) == LUMPERROR) - nto_font[i] = NULL; - else - nto_font[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - } + tallminus = HU_CachePatch("STTMINUS"); // cache the crosshairs, don't bother to know which one is being used, // just cache all 3, they're so small anyway. for (i = 0; i < HU_CROSSHAIRS; i++) { - sprintf(buffer, "CROSHAI%c", '1'+i); - crosshair[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); + crosshair[i] = HU_CachePatch("CROSHAI%c", '1'+i); } - emblemicon = W_CachePatchName("EMBLICON", PU_HUDGFX); - tokenicon = W_CachePatchName("TOKNICON", PU_HUDGFX); - - exiticon = W_CachePatchName("EXITICON", PU_HUDGFX); + emblemicon = HU_CachePatch("EMBLICON"); + tokenicon = HU_CachePatch("TOKNICON"); emeraldpics[0][0] = W_CachePatchName("CHAOS1", PU_HUDGFX); emeraldpics[0][1] = W_CachePatchName("CHAOS2", PU_HUDGFX); @@ -343,20 +227,18 @@ void HU_LoadGraphics(void) emeraldpics[2][6] = W_CachePatchName("EMBOX7", PU_HUDGFX); //emeraldpics[2][7] = W_CachePatchName("EMBOX8", PU_HUDGFX); -- unused - songcreditbg = W_CachePatchName("K_SONGCR", PU_HUDGFX); + songcreditbg = HU_CachePatch("K_SONGCR"); // cache ping gfx: for (i = 0; i < 5; i++) { - sprintf(buffer, "PINGGFX%d", i+1); - pinggfx[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); - sprintf(buffer, "MPING%d", i+1); - mping[i] = (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); + pinggfx[i] = HU_CachePatch("PINGGFX%d", i+1); + mping[i] = HU_CachePatch("MPING%d", i+1); } // fps stuff - framecounter = W_CachePatchName("FRAMER", PU_HUDGFX); - frameslash = W_CachePatchName("FRAMESL", PU_HUDGFX);; + framecounter = HU_CachePatch("FRAMER"); + frameslash = HU_CachePatch("FRAMESL");; } // Initialise Heads up @@ -364,6 +246,8 @@ void HU_LoadGraphics(void) // void HU_Init(void) { + font_t font; + #ifndef NONET COM_AddCommand("say", Command_Say_f); COM_AddCommand("sayto", Command_Sayto_f); @@ -375,9 +259,78 @@ void HU_Init(void) // set shift translation table shiftxform = english_shiftxform; + /* + Setup fonts + */ + + if (!dedicated) + { +#define DIM( s, n ) ( font.start = s, font.size = n ) +#define ADIM( name ) DIM (name ## _FONTSTART, name ## _FONTSIZE) +#define PR( s ) strcpy(font.prefix, s) +#define DIG( n ) ( font.digits = n ) +#define REG Font_DumbRegister(&font) + + DIG (3); + + ADIM (HU); + + PR ("STCFN"); + REG; + + PR ("TNYFN"); + REG; + + ADIM (KART); + PR ("MKFNT"); + REG; + + ADIM (LT); + PR ("LTFNT"); + REG; + + ADIM (CRED); + PR ("CRFNT"); + REG; + + DIG (1); + + DIM (0, 10); + + PR ("STTNUM"); + REG; + + PR ("NGTNUM"); + REG; + + PR ("PINGN"); + REG; + +#undef REG +#undef DIG +#undef PR +#undef ADMIN +#undef DIM + } + HU_LoadGraphics(); } +patch_t *HU_CachePatch(const char *format, ...) +{ + va_list ap; + char buffer[9]; + + va_start (ap, format); + vsprintf(buffer, format, ap); + va_end (ap); + + if (W_CheckNumForName(buffer) == LUMPERROR) + return NULL; + else + return (patch_t *)W_CachePatchName(buffer, PU_HUDGFX); +} + static inline void HU_Stop(void) { headsupactive = false; @@ -780,62 +733,40 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) { const char *prefix = "", *cstart = "", *cend = "", *adminchar = "\x82~\x83", *remotechar = "\x82@\x83", *fmt2, *textcolor = "\x80"; char *tempchar = NULL; + char color_prefix[2]; - // player is a spectator? - if (players[playernum].spectator) + if (players[playernum].spectator) { - cstart = "\x86"; // grey name - textcolor = "\x86"; + // grey text + cstart = textcolor = "\x86"; } else if (target == -1) // say team { - if (players[playernum].ctfteam == 1) // red + if (players[playernum].ctfteam == 1) { - cstart = "\x85"; - textcolor = "\x85"; + // red text + cstart = textcolor = "\x85"; } - else // blue + else { - cstart = "\x84"; - textcolor = "\x84"; + // blue text + cstart = textcolor = "\x84"; } } else { UINT16 chatcolor = skincolors[players[playernum].skincolor].chatcolor; - if (!chatcolor || chatcolor%0x1000 || chatcolor>V_TANMAP) - cstart = "\x80"; - else if (chatcolor == V_PURPLEMAP) - cstart = "\x81"; - else if (chatcolor == V_YELLOWMAP) - cstart = "\x82"; - else if (chatcolor == V_GREENMAP) - cstart = "\x83"; - else if (chatcolor == V_BLUEMAP) - cstart = "\x84"; - else if (chatcolor == V_REDMAP) - cstart = "\x85"; - else if (chatcolor == V_GRAYMAP) - cstart = "\x86"; - else if (chatcolor == V_ORANGEMAP) - cstart = "\x87"; - else if (chatcolor == V_SKYMAP) - cstart = "\x88"; - else if (chatcolor == V_LAVENDERMAP) - cstart = "\x89"; - else if (chatcolor == V_GOLDMAP) - cstart = "\x8a"; - else if (chatcolor == V_AQUAMAP) - cstart = "\x8b"; - else if (chatcolor == V_MAGENTAMAP) - cstart = "\x8c"; - else if (chatcolor == V_PINKMAP) - cstart = "\x8d"; - else if (chatcolor == V_BROWNMAP) - cstart = "\x8e"; - else if (chatcolor == V_TANMAP) - cstart = "\x8f"; + if (chatcolor > V_TANMAP) + { + sprintf(color_prefix, "%c", '\x80'); + } + else + { + sprintf(color_prefix, "%c", '\x80' + (chatcolor >> V_CHARCOLORSHIFT)); + } + + cstart = color_prefix; } prefix = cstart; @@ -844,6 +775,7 @@ static void Got_Saycmd(UINT8 **p, INT32 playernum) tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(adminchar) + 1, PU_STATIC, NULL); else if (IsPlayerAdmin(playernum)) tempchar = (char *)Z_Calloc(strlen(cstart) + strlen(remotechar) + 1, PU_STATIC, NULL); + if (tempchar) { if (playernum == serverplayer) @@ -911,7 +843,7 @@ static inline boolean HU_keyInChatString(char *s, char ch) { size_t l; - if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && hu_font[ch-HU_FONTSTART]) + if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && fontv[HU_FONT].font[ch-HU_FONTSTART]) || ch == ' ') // Allow spaces, of course { l = strlen(s); @@ -1353,7 +1285,7 @@ static char *CHAT_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) c = toupper(c); c -= HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) { chw = spacewidth; lastusablespace = i; @@ -2309,11 +2241,17 @@ void HU_drawMiniPing (INT32 x, INT32 y, UINT32 ping, INT32 flags) { patch_t *patch; + INT32 w = BASEVIDWIDTH; + + if (r_splitscreen > 1) + { + w /= 2; + } patch = mping[Ping_gfx_num(ping)]; if (( flags & V_SNAPTORIGHT )) - x += ( BASEVIDWIDTH - SHORT (patch->width) ); + x += ( w - SHORT (patch->width) ); V_DrawScaledPatch(x, y, flags, patch); } diff --git a/src/hu_stuff.h b/src/hu_stuff.h index c2f419ea5..b7d510f89 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -17,6 +17,7 @@ #include "d_event.h" #include "w_wad.h" #include "r_defs.h" +#include "font.h" //------------------------------------ // heads up font @@ -42,11 +43,22 @@ #define CRED_FONTEND 'Z' // the last font character #define CRED_FONTSIZE (CRED_FONTEND - CRED_FONTSTART + 1) -// Name tag font -// Used by base and outline font set -#define NT_FONTSTART '!' // the first font character -#define NT_FONTEND 'Z' // the last font character -#define NT_FONTSIZE (NT_FONTEND - NT_FONTSTART + 1) +#define X( name ) name ## _FONT +/* fonts */ +enum +{ + X (HU), + X (TINY), + X (KART), + + X (LT), + X (CRED), + + X (TALLNUM), + X (NIGHTSNUM), + X (PINGNUM), +}; +#undef X #define HU_CROSSHAIRS 3 // maximum of 9 - see HU_Init(); @@ -84,19 +96,10 @@ void HU_AddChatText(const char *text, boolean playsound); // set true when entering a chat message extern boolean chat_on; -extern patch_t *hu_font[HU_FONTSIZE], *kart_font[KART_FONTSIZE], *tny_font[HU_FONTSIZE]; // SRB2kart -extern patch_t *tallnum[10]; -extern patch_t *pingnum[10]; extern patch_t *pinggfx[5]; -extern patch_t *nightsnum[10]; extern patch_t *framecounter; extern patch_t *frameslash; -extern patch_t *lt_font[LT_FONTSIZE]; -extern patch_t *cred_font[CRED_FONTSIZE]; -extern patch_t *ntb_font[NT_FONTSIZE]; -extern patch_t *nto_font[NT_FONTSIZE]; -extern patch_t *ttlnum[10]; -extern patch_t *emeraldpics[3][8]; + extern patch_t *rflagico; extern patch_t *bflagico; extern patch_t *rmatcico; @@ -114,6 +117,9 @@ void HU_Init(void); void HU_LoadGraphics(void); +// Load a HUDGFX patch or NULL. +patch_t *HU_CachePatch(const char *format, ...); + // reset heads up when consoleplayer respawns. void HU_Start(void); diff --git a/src/info.c b/src/info.c index 08f29d725..d0d3e24d3 100644 --- a/src/info.c +++ b/src/info.c @@ -532,7 +532,9 @@ char sprnames[NUMSPRITES + 1][5] = "ICEB","CNDL","DOCH","DUCK","GTRE","CHES","CHIM","DRGN","LZMN","PGSS", "ZTCH","MKMA","MKMP","RTCH","BOWL","BOWH","BRRL","BRRR","HRSE","TOAH", "BFRT","OFRT","RFRT","PFRT","ASPK","HBST","HBSO","HBSF","WBLZ","WBLN", - "FWRK","MXCL","RGSP","DRAF","GRES","OTFG","DBOS","XMS4","XMS5","VIEW" + + "FWRK","MXCL","RGSP","DRAF","GRES","OTFG","DBOS","EGOO","WTRL","XMS4", + "XMS5","FBUB","GCHA","CHEZ","VIEW","DBCL","DBNC","DBST", }; char spr2names[NUMPLAYERSPRITES][5] = @@ -3849,10 +3851,42 @@ state_t states[NUMSTATES] = {SPR_DSHR, FF_PAPERSPRITE|5, 1, {NULL}, 0, 0, S_FASTDUST7}, // S_FASTDUST6 {SPR_DSHR, FF_PAPERSPRITE|6, 1, {NULL}, 0, 0, S_NULL}, // S_FASTDUST7 - {SPR_DBOS, FF_FULLBRIGHT, 2, {NULL}, 6, 1, S_DRIFTEXPLODE2}, // S_DRIFTEXPLODE1 - {SPR_DBOS, FF_FULLBRIGHT|1, 2, {NULL}, 6, 1, S_DRIFTEXPLODE3}, // S_DRIFTEXPLODE2 - {SPR_DBOS, FF_FULLBRIGHT|2, 2, {NULL}, 6, 1, S_DRIFTEXPLODE4}, // S_DRIFTEXPLODE3 - {SPR_DBOS, FF_FULLBRIGHT|3, 2, {NULL}, 6, 1, S_DRIFTEXPLODE1}, // S_DRIFTEXPLODE4 + {SPR_DBOS, FF_FULLBRIGHT, 1, {NULL}, 6, 1, S_DRIFTEXPLODE2}, // S_DRIFTEXPLODE1 + {SPR_DBST, FF_PAPERSPRITE|FF_FULLBRIGHT, 1, {NULL}, 6, 1, S_DRIFTEXPLODE3}, // S_DRIFTEXPLODE2 + {SPR_DBOS, FF_FULLBRIGHT|1, 1, {NULL}, 6, 1, S_DRIFTEXPLODE4}, // S_DRIFTEXPLODE3 + {SPR_DBST, FF_PAPERSPRITE|FF_FULLBRIGHT|1, 1, {NULL}, 6, 1, S_DRIFTEXPLODE5}, // S_DRIFTEXPLODE4 + {SPR_DBOS, FF_FULLBRIGHT|2, 1, {NULL}, 6, 1, S_DRIFTEXPLODE6}, // S_DRIFTEXPLODE5 + {SPR_DBST, FF_PAPERSPRITE|FF_FULLBRIGHT|2, 1, {NULL}, 6, 1, S_DRIFTEXPLODE7}, // S_DRIFTEXPLODE6 + {SPR_DBOS, FF_FULLBRIGHT|3, 1, {NULL}, 6, 1, S_DRIFTEXPLODE8}, // S_DRIFTEXPLODE7 + {SPR_DBST, FF_PAPERSPRITE|FF_FULLBRIGHT|3, 1, {NULL}, 6, 1, S_DRIFTEXPLODE1}, // S_DRIFTEXPLODE8 + + {SPR_DBCL, FF_FULLBRIGHT|0x0, 1, {NULL}, 6, 1, S_DRIFTCLIPA2}, // S_DRIFTCLIPA1 + {SPR_DBCL, FF_FULLBRIGHT|0x8, 1, {NULL}, 6, 1, S_DRIFTCLIPA3}, // S_DRIFTCLIPA2 + {SPR_DBCL, FF_FULLBRIGHT|0x1, 1, {NULL}, 6, 1, S_DRIFTCLIPA4}, // S_DRIFTCLIPA3 + {SPR_DBCL, FF_FULLBRIGHT|0x9, 1, {NULL}, 6, 1, S_DRIFTCLIPA5}, // S_DRIFTCLIPA4 + {SPR_DBCL, FF_FULLBRIGHT|0x2, 1, {NULL}, 6, 1, S_DRIFTCLIPA6}, // S_DRIFTCLIPA5 + {SPR_DBCL, FF_FULLBRIGHT|0xA, 1, {NULL}, 6, 1, S_DRIFTCLIPA7}, // S_DRIFTCLIPA6 + {SPR_DBCL, FF_FULLBRIGHT|0x3, 1, {NULL}, 6, 1, S_DRIFTCLIPA8}, // S_DRIFTCLIPA7 + {SPR_DBCL, FF_FULLBRIGHT|0xB, 1, {NULL}, 6, 1, S_DRIFTCLIPA9}, // S_DRIFTCLIPA8 + {SPR_DBCL, FF_FULLBRIGHT|0x4, 1, {NULL}, 6, 1, S_DRIFTCLIPA10}, // S_DRIFTCLIPA9 + {SPR_DBCL, FF_FULLBRIGHT|0xC, 1, {NULL}, 6, 1, S_DRIFTCLIPA11}, // S_DRIFTCLIPA10 + {SPR_DBCL, FF_FULLBRIGHT|0x5, 1, {NULL}, 6, 1, S_DRIFTCLIPA12}, // S_DRIFTCLIPA11 + {SPR_DBCL, FF_FULLBRIGHT|0xD, 1, {NULL}, 6, 1, S_DRIFTCLIPA13}, // S_DRIFTCLIPA12 + {SPR_DBCL, FF_FULLBRIGHT|0x6, 1, {NULL}, 6, 1, S_DRIFTCLIPA14}, // S_DRIFTCLIPA13 + {SPR_DBCL, FF_FULLBRIGHT|0xE, 1, {NULL}, 6, 1, S_DRIFTCLIPA15}, // S_DRIFTCLIPA14 + {SPR_DBCL, FF_FULLBRIGHT|0x7, 1, {NULL}, 6, 1, S_DRIFTCLIPA16}, // S_DRIFTCLIPA15 + {SPR_DBCL, FF_FULLBRIGHT|0xF, 1, {NULL}, 6, 1, S_DRIFTCLIPB1}, // S_DRIFTCLIPA16 + + {SPR_DBCL, FF_FULLBRIGHT, 2, {NULL}, 6, 1, S_DRIFTCLIPB2}, // S_DRIFTCLIPB1 + {SPR_DBCL, FF_FULLBRIGHT|1, 2, {NULL}, 6, 1, S_DRIFTCLIPB3}, // S_DRIFTCLIPB2 + {SPR_DBCL, FF_FULLBRIGHT|2, 2, {NULL}, 6, 1, S_DRIFTCLIPB4}, // S_DRIFTCLIPB3 + {SPR_DBCL, FF_FULLBRIGHT|3, 2, {NULL}, 6, 1, S_DRIFTCLIPB5}, // S_DRIFTCLIPB4 + {SPR_DBCL, FF_FULLBRIGHT|4, 2, {NULL}, 6, 1, S_DRIFTCLIPB6}, // S_DRIFTCLIPB5 + {SPR_DBCL, FF_FULLBRIGHT|5, 2, {NULL}, 6, 1, S_DRIFTCLIPB7}, // S_DRIFTCLIPB6 + {SPR_DBCL, FF_FULLBRIGHT|6, 2, {NULL}, 6, 1, S_DRIFTCLIPB8}, // S_DRIFTCLIPB7 + {SPR_DBCL, FF_FULLBRIGHT|7, 2, {NULL}, 6, 1, S_DRIFTCLIPB1}, // S_DRIFTCLIPB8 + + {SPR_DBNC, FF_FULLBRIGHT|FF_ANIMATE, 14, {NULL}, 6, 1, S_NULL}, // S_DRIFTCLIPSPARK {SPR_BOST, FF_FULLBRIGHT|FF_ANIMATE, TICRATE, {NULL}, 6, 1, S_BOOSTSMOKESPAWNER}, // S_BOOSTFLAME {SPR_NULL, 0, TICRATE/2, {NULL}, 0, 0, S_NULL}, // S_BOOSTSMOKESPAWNER @@ -4752,6 +4786,37 @@ state_t states[NUMSTATES] = {SPR_SMOK, 3, 7, {NULL}, 0, 0, S_OPAQUESMOKE5}, // S_OPAQUESMOKE4 {SPR_SMOK, 4, 8, {NULL}, 0, 0, S_NULL}, // S_OPAQUESMOKE5 + + // followers: + + // bubble + {SPR_FBUB, 11|FF_ANIMATE|FF_TRANS70|FF_FULLBRIGHT, -1, {NULL}, 10, 3, S_FOLLOWERBUBBLE_FRONT}, // S_FOLLOWERBUBBLE_FRONT + {SPR_FBUB, FF_ANIMATE|0|FF_FULLBRIGHT, -1, {NULL}, 10, 3, S_FOLLOWERBUBBLE_BACK}, // S_FOLLOWERBUBBLE_BACK + + // generic chao: + {SPR_GCHA, FF_ANIMATE, -1, {NULL}, 1, 4, S_GCHAOIDLE}, //S_GCHAOIDLE + {SPR_GCHA, 2|FF_ANIMATE, -1, {NULL}, 1, 2, S_GCHAOFLY}, //S_GCHAOFLY + {SPR_GCHA, 7, 5, {NULL}, 0, 0, S_GCHAOSAD2}, //S_GCHAOSAD1 + {SPR_GCHA, 8, 3, {NULL}, 0, 0, S_GCHAOSAD3}, //S_GCHAOSAD2 + {SPR_GCHA, 9, 6, {NULL}, 0, 0, S_GCHAOSAD4}, //S_GCHAOSAD3 + {SPR_GCHA, 8, 3, {NULL}, 0, 0, S_GCHAOSAD1}, //S_GCHAOSAD4 + {SPR_GCHA, 4, 8, {NULL}, 0, 0, S_GCHAOHAPPY2}, //S_GCHAOHAPPY1 + {SPR_GCHA, 5, 4, {NULL}, 0, 0, S_GCHAOHAPPY3}, //S_GCHAOHAPPY2 + {SPR_GCHA, 6, 8, {NULL}, 0, 0, S_GCHAOHAPPY4}, //S_GCHAOHAPPY3 + {SPR_GCHA, 5, 4, {NULL}, 0, 0, S_GCHAOHAPPY1}, //S_GCHAOHAPPY4 + + // cheese: + {SPR_CHEZ, FF_ANIMATE, -1, {NULL}, 1, 4, S_CHEESEIDLE}, //S_CHEESEIDLE + {SPR_CHEZ, 2|FF_ANIMATE, -1, {NULL}, 1, 2, S_CHEESEFLY}, //S_CHEESEFLY + {SPR_CHEZ, 7, 5, {NULL}, 0, 0, S_CHEESESAD2}, //S_CHEESESAD1 + {SPR_CHEZ, 8, 3, {NULL}, 0, 0, S_CHEESESAD3}, //S_CHEESESAD2 + {SPR_CHEZ, 9, 6, {NULL}, 0, 0, S_CHEESESAD4}, //S_CHEESESAD3 + {SPR_CHEZ, 8, 3, {NULL}, 0, 0, S_CHEESESAD1}, //S_CHEESESAD4 + {SPR_CHEZ, 4, 8, {NULL}, 0, 0, S_CHEESEHAPPY2}, //S_CHEESEHAPPY1 + {SPR_CHEZ, 5, 4, {NULL}, 0, 0, S_CHEESEHAPPY3}, //S_CHEESEHAPPY2 + {SPR_CHEZ, 6, 8, {NULL}, 0, 0, S_CHEESEHAPPY4}, //S_CHEESEHAPPY3 + {SPR_CHEZ, 5, 4, {NULL}, 0, 0, S_CHEESEHAPPY1}, //S_CHEESEHAPPY4 + {SPR_MXCL, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_RINGDEBT {SPR_RGSP, FF_PAPERSPRITE|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_RINGSPARKS2}, // S_RINGSPARKS1 @@ -4789,6 +4854,26 @@ state_t states[NUMSTATES] = {SPR_CAPS, 4, -1, {NULL}, 0, 0, S_NULL}, // S_BATTLECAPSULE_SUPPORT {SPR_CAPS, FF_ANIMATE|5, -1, {NULL}, 3, 1, S_NULL}, // S_BATTLECAPSULE_SUPPORTFLY + {SPR_EGOO, 0, 1, {NULL}, 0, 0, S_NULL}, // S_EGOORB + + // Water Trail + {SPR_WTRL, FF_PAPERSPRITE , 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAIL1 + {SPR_WTRL, FF_PAPERSPRITE|1, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAIL2 + {SPR_WTRL, FF_PAPERSPRITE|2, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAIL3 + {SPR_WTRL, FF_PAPERSPRITE|3, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAIL4 + {SPR_WTRL, FF_PAPERSPRITE|4, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAIL5 + {SPR_WTRL, FF_PAPERSPRITE|5, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAIL6 + {SPR_WTRL, FF_PAPERSPRITE|6, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAIL7 + {SPR_WTRL, FF_PAPERSPRITE|7, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAIL8 + {SPR_WTRL, FF_TRANS50|FF_PAPERSPRITE|8, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAILUNDERLAY1 + {SPR_WTRL, FF_TRANS50|FF_PAPERSPRITE|9, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAILUNDERLAY2 + {SPR_WTRL, FF_TRANS50|FF_PAPERSPRITE|10, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAILUNDERLAY3 + {SPR_WTRL, FF_TRANS50|FF_PAPERSPRITE|11, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAILUNDERLAY4 + {SPR_WTRL, FF_TRANS50|FF_PAPERSPRITE|12, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAILUNDERLAY5 + {SPR_WTRL, FF_TRANS50|FF_PAPERSPRITE|13, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAILUNDERLAY6 + {SPR_WTRL, FF_TRANS50|FF_PAPERSPRITE|14, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAILUNDERLAY7 + {SPR_WTRL, FF_TRANS50|FF_PAPERSPRITE|15, 2, {NULL}, 0, 0, S_NULL}, // S_WATERTRAILUNDERLAY8 + #ifdef SEENAMES {SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL}, // S_NAMECHECK #endif @@ -22706,6 +22791,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_DRIFTCLIP + -1, // doomednum + S_DRIFTCLIPA1, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 105, // speed + 32*FRACUNIT, // radius + 64*FRACUNIT, // height + 1, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_DONTENCOREMAP|MF_GRENADEBOUNCE|MF_BOUNCE, // flags + S_NULL // raisestate + }, + + { // MT_DRIFTCLIPSPARK + -1, // doomednum + S_DRIFTCLIPSPARK, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 8, // speed + 32*FRACUNIT, // radius + 64*FRACUNIT, // height + 1, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_DONTENCOREMAP, // flags + S_NULL // raisestate + }, + { // MT_BOOSTFLAME -1, // doomednum S_BOOSTFLAME, // spawnstate @@ -27946,6 +28085,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_RINGSPARKS -1, // doomednum S_RINGSPARKS1, // spawnstate @@ -28189,6 +28329,144 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_FOLLOWER + -1, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 8<destscale = mo->scale/4; mo->frame += ((leveltime/4) % 8); /*if (battleovertime.enabled < 10*TICRATE) - mo->flags2 |= MF2_SHADOW;*/ + mo->drawflags |= MFD_SHADOW;*/ mo->angle = R_PointToAngle2(mo->x, mo->y, battleovertime.x, battleovertime.y) + ANGLE_90; mo->z += P_RandomRange(0,48) * mo->scale; break; diff --git a/src/k_bheap.c b/src/k_bheap.c index 40c652b5e..495c8ba0f 100644 --- a/src/k_bheap.c +++ b/src/k_bheap.c @@ -469,6 +469,10 @@ boolean K_BHeapPop(bheap_t *const heap, bheapitem_t *const returnitemstorage) heap->array[0] = heap->array[heap->count]; heap->array[0].heapindex = 0U; memset(&heap->array[heap->count], 0x00, sizeof(bheapitem_t)); + if (heap->array[0].indexchanged != NULL) + { + heap->array[0].indexchanged(heap->array[0].data, heap->array[0].heapindex); + } K_BHeapSortDown(heap, &heap->array[0]); popsuccess = true; diff --git a/src/k_bot.c b/src/k_bot.c index 9bd5da972..a586f80c4 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -1,5 +1,6 @@ // SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour // Copyright (C) 2018-2020 by Kart Krew // // This program is free software distributed under the @@ -295,11 +296,17 @@ boolean K_BotCanTakeCut(player_t *player) --------------------------------------------------*/ static UINT32 K_BotRubberbandDistance(player_t *player) { - const UINT32 spacing = 2048; + const UINT32 spacing = FixedDiv(512 * FRACUNIT, K_GetKartGameSpeedScalar(gamespeed)) / FRACUNIT; const UINT8 portpriority = player - players; UINT8 pos = 0; UINT8 i; + if (player->botvars.rival) + { + // The rival should always try to be the front runner for the race. + return 0; + } + for (i = 0; i < MAXPLAYERS; i++) { if (i == portpriority) @@ -312,15 +319,15 @@ static UINT32 K_BotRubberbandDistance(player_t *player) // First check difficulty levels, then score, then settle it with port priority! if (player->botvars.difficulty < players[i].botvars.difficulty) { - pos++; + pos += 3; } else if (player->score < players[i].score) { - pos++; + pos += 2; } else if (i < portpriority) { - pos++; + pos += 1; } } } @@ -336,11 +343,13 @@ static UINT32 K_BotRubberbandDistance(player_t *player) fixed_t K_BotRubberband(player_t *player) { fixed_t rubberband = FRACUNIT; + fixed_t max, min; player_t *firstplace = NULL; UINT8 i; if (player->exiting) { + // You're done, we don't need to rubberband anymore. return FRACUNIT; } @@ -372,8 +381,8 @@ fixed_t K_BotRubberband(player_t *player) if (wanteddist > player->distancetofinish) { - // Whoa, you're too far ahead! - rubberband += (MAXBOTDIFFICULTY - player->botvars.difficulty) * distdiff; + // Whoa, you're too far ahead! Slow back down a little. + rubberband += (MAXBOTDIFFICULTY - player->botvars.difficulty) * (distdiff / 3); } else { @@ -382,13 +391,23 @@ fixed_t K_BotRubberband(player_t *player) } } - if (rubberband > 2*FRACUNIT) + // Lv. 1: x1.0 max + // Lv. 5: x1.5 max + // Lv. 9: x2.0 max + max = FRACUNIT + ((FRACUNIT * (player->botvars.difficulty - 1)) / (MAXBOTDIFFICULTY - 1)); + + // Lv. 1: x0.75 min + // Lv. 5: x0.875 min + // Lv. 9: x1.0 min + min = FRACUNIT - (((FRACUNIT/4) * (MAXBOTDIFFICULTY - player->botvars.difficulty)) / (MAXBOTDIFFICULTY - 1)); + + if (rubberband > max) { - rubberband = 2*FRACUNIT; + rubberband = max; } - else if (rubberband < 7*FRACUNIT/8) + else if (rubberband < min) { - rubberband = 7*FRACUNIT/8; + rubberband = min; } return rubberband; @@ -447,6 +466,32 @@ fixed_t K_BotTopSpeedRubberband(player_t *player) return rubberband; } +/*-------------------------------------------------- + fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict) + + See header file for description. +--------------------------------------------------*/ +fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict) +{ + fixed_t rubberband = K_BotRubberband(player) - FRACUNIT; + fixed_t newfrict; + + if (rubberband <= 0) + { + // Never get stronger than normal friction + return frict; + } + + newfrict = FixedDiv(frict, FRACUNIT + (rubberband / 2)); + + if (newfrict < 0) + newfrict = 0; + if (newfrict > FRACUNIT) + newfrict = FRACUNIT; + + return newfrict; +} + /*-------------------------------------------------- fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy) @@ -637,7 +682,7 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) // Remove any existing controls memset(cmd, 0, sizeof(ticcmd_t)); - cmd->angleturn = (player->mo->angle >> 16) | TICCMD_RECEIVED; + cmd->angleturn = (player->mo->angle >> 16); if (gamestate != GS_LEVEL) { @@ -658,13 +703,16 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) // Start boost handler if (leveltime <= starttime) { - tic_t boosthold = starttime - TICRATE; + tic_t length = (TICRATE/6); + tic_t boosthold = starttime - K_GetSpindashChargeTime(player); - boosthold -= (MAXBOTDIFFICULTY - player->botvars.difficulty); + cmd->buttons |= BT_EBRAKEMASK; + + boosthold -= (MAXBOTDIFFICULTY - player->botvars.difficulty) * length; if (leveltime >= boosthold) { - cmd->buttons |= BT_ACCELERATE; + cmd->buttons |= BT_DRIFT; } return; @@ -701,7 +749,7 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) if (anglediff > 90) { // Wrong way! - cmd->forwardmove = -25; + cmd->forwardmove = -MAXPLMOVE; cmd->buttons |= BT_BRAKE; } else @@ -735,7 +783,7 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd) cmd->buttons |= BT_ACCELERATE; // Full speed ahead! - cmd->forwardmove = 50; + cmd->forwardmove = MAXPLMOVE; if (dirdist <= rad) { diff --git a/src/k_bot.h b/src/k_bot.h index 48472b5fd..2cb1ae460 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -1,5 +1,6 @@ // SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour // Copyright (C) 2018-2020 by Kart Krew // // This program is free software distributed under the @@ -95,6 +96,23 @@ fixed_t K_BotRubberband(player_t *player); fixed_t K_BotTopSpeedRubberband(player_t *player); +/*-------------------------------------------------- + fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict); + + Gives a multiplier for a bot's rubberbanding. + Adjusted from K_BotRubberband to be used for friction. + + Input Arguments:- + player - Player to check. + frict - Friction value to adjust. + + Return:- + The new friction value. +--------------------------------------------------*/ + +fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict); + + /*-------------------------------------------------- fixed_t K_DistanceOfLineFromPoint(fixed_t v1x, fixed_t v1y, fixed_t v2x, fixed_t v2y, fixed_t cx, fixed_t cy); @@ -228,5 +246,4 @@ void K_BuildBotTiccmd(player_t *player, ticcmd_t *cmd); void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt); - #endif diff --git a/src/k_botitem.c b/src/k_botitem.c index 55f8e15e4..94f0b8e10 100644 --- a/src/k_botitem.c +++ b/src/k_botitem.c @@ -1,5 +1,6 @@ // SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour // Copyright (C) 2018-2020 by Kart Krew // // This program is free software distributed under the diff --git a/src/k_botsearch.c b/src/k_botsearch.c index 28e08f3cf..4dd55cc42 100644 --- a/src/k_botsearch.c +++ b/src/k_botsearch.c @@ -1,5 +1,6 @@ // SONIC ROBO BLAST 2 KART //----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour // Copyright (C) 2018-2020 by Kart Krew // // This program is free software distributed under the diff --git a/src/k_color.c b/src/k_color.c index 050b8d1f1..7372b2227 100644 --- a/src/k_color.c +++ b/src/k_color.c @@ -22,7 +22,6 @@ See header file for description. --------------------------------------------------*/ - UINT8 K_ColorRelativeLuminance(UINT8 r, UINT8 g, UINT8 b) { UINT32 redweight = 1063 * r; @@ -48,7 +47,6 @@ UINT16 K_RainbowColor(tic_t time) See header file for description. --------------------------------------------------*/ - void K_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor) { INT32 i; @@ -92,14 +90,11 @@ void K_RainbowColormap(UINT8 *dest_colormap, UINT8 skincolor) } } -/** \brief Generates a translation colormap for Kart, to replace R_GenerateTranslationColormap in r_draw.c +/*-------------------------------------------------- + void K_GenerateKartColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color) - \param dest_colormap colormap to populate - \param skinnum number of skin, TC_DEFAULT or TC_BOSS - \param color translation color - - \return void -*/ + See header file for description. +--------------------------------------------------*/ void K_GenerateKartColormap(UINT8 *dest_colormap, INT32 skinnum, UINT8 color) { INT32 i; diff --git a/src/k_color.h b/src/k_color.h index 02f659ae9..f08b249cf 100644 --- a/src/k_color.h +++ b/src/k_color.h @@ -19,6 +19,21 @@ #define DEFAULT_STARTTRANSCOLOR 96 #define NUM_PALETTE_ENTRIES 256 +/*-------------------------------------------------- + INT32 K_SkincolorToTextColor(UINT8 skincolor); + + Gives you the "text" color (V_ constants) from a skincolor (SKINCOLOR_ constants). + Used primarily by the chat system. + + Input Arguments:- + skincolor - SKINCOLOR_ constant + + Return:- + V_ constant for font coloring +--------------------------------------------------*/ +INT32 K_SkincolorToTextColor(UINT8 skincolor); + + /*-------------------------------------------------- UINT8 K_ColorRelativeLuminance(UINT8 r, UINT8 g, UINT8 b); diff --git a/src/k_grandprix.c b/src/k_grandprix.c new file mode 100644 index 000000000..a4882147a --- /dev/null +++ b/src/k_grandprix.c @@ -0,0 +1,605 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_grandprix.c +/// \brief Grand Prix mode game logic & bot behaviors + +#include "k_grandprix.h" +#include "doomdef.h" +#include "d_player.h" +#include "g_game.h" +#include "k_bot.h" +#include "k_kart.h" +#include "m_random.h" +#include "r_things.h" + +struct grandprixinfo grandprixinfo; + +/*-------------------------------------------------- + UINT8 K_BotStartingDifficulty(SINT8 value) + + See header file for description. +--------------------------------------------------*/ +UINT8 K_BotStartingDifficulty(SINT8 value) +{ + // startingdifficulty: Easy = 3, Normal = 6, Hard = 9 + SINT8 difficulty = (value + 1) * 3; + + if (difficulty > MAXBOTDIFFICULTY) + { + difficulty = MAXBOTDIFFICULTY; + } + else if (difficulty < 1) + { + difficulty = 1; + } + + return difficulty; +} + +/*-------------------------------------------------- + INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) + + See header file for description. +--------------------------------------------------*/ +INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) +{ + INT16 points; + + if (position >= numplayers || position == 0) + { + // Invalid position, no points + return 0; + } + + points = numplayers - position; + + // Give bonus to high-ranking players, depending on player count + // This rounds out the point gain when you get 1st every race, + // and gives bots able to catch up in points if a player gets an early lead. + // The maximum points you can get in a cup is: ((number of players - 1) + (max extra points)) * (number of races) + // 8P: (7 + 5) * 5 = 60 maximum points + // 12P: (11 + 5) * 5 = 80 maximum points + // 16P: (15 + 5) * 5 = 100 maximum points + switch (numplayers) + { + case 0: case 1: case 2: // 1v1 + break; // No bonus needed. + case 3: case 4: // 3-4P + if (position == 1) { points += 1; } // 1st gets +1 extra point + break; + case 5: case 6: // 5-6P + if (position == 1) { points += 3; } // 1st gets +3 extra points + else if (position == 2) { points += 1; } // 2nd gets +1 extra point + break; + default: // Normal matches + if (position == 1) { points += 5; } // 1st gets +5 extra points + else if (position == 2) { points += 3; } // 2nd gets +3 extra points + else if (position == 3) { points += 1; } // 3rd gets +1 extra point + break; + } + + // somehow underflowed? + if (points < 0) + { + points = 0; + } + + return points; +} + +/*-------------------------------------------------- + void K_InitGrandPrixBots(void) + + See header file for description. +--------------------------------------------------*/ +void K_InitGrandPrixBots(void) +{ + const char *defaultbotskinname = "eggrobo"; + SINT8 defaultbotskin = R_SkinAvailable(defaultbotskinname); + + const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed); + UINT8 difficultylevels[MAXPLAYERS]; + + UINT8 playercount = 8; + UINT8 wantedbots = 0; + + UINT8 numplayers = 0; + UINT8 competitors[MAXSPLITSCREENPLAYERS]; + + boolean skinusable[MAXSKINS]; + UINT8 botskinlist[MAXPLAYERS]; + UINT8 botskinlistpos = 0; + + UINT8 newplayernum = 0; + UINT8 i, j; + + if (defaultbotskin == -1) + { + // This shouldn't happen, but just in case + defaultbotskin = 0; + } + + memset(competitors, MAXPLAYERS, sizeof (competitors)); + memset(botskinlist, defaultbotskin, sizeof (botskinlist)); + + // init usable bot skins list + for (i = 0; i < MAXSKINS; i++) + { + if (i < numskins) + { + skinusable[i] = true; + } + else + { + skinusable[i] = false; + } + } + +#if MAXPLAYERS != 16 + I_Error("GP bot difficulty levels need rebalacned for the new player count!\n"); +#endif + + if (grandprixinfo.masterbots) + { + // Everyone is max difficulty!! + memset(difficultylevels, MAXBOTDIFFICULTY, sizeof (difficultylevels)); + } + else + { + // init difficulty levels list + difficultylevels[0] = max(1, startingdifficulty); + difficultylevels[1] = max(1, startingdifficulty-1); + difficultylevels[2] = max(1, startingdifficulty-2); + difficultylevels[3] = max(1, startingdifficulty-3); + difficultylevels[4] = max(1, startingdifficulty-3); + difficultylevels[5] = max(1, startingdifficulty-4); + difficultylevels[6] = max(1, startingdifficulty-4); + difficultylevels[7] = max(1, startingdifficulty-4); + difficultylevels[8] = max(1, startingdifficulty-5); + difficultylevels[9] = max(1, startingdifficulty-5); + difficultylevels[10] = max(1, startingdifficulty-5); + difficultylevels[11] = max(1, startingdifficulty-6); + difficultylevels[12] = max(1, startingdifficulty-6); + difficultylevels[13] = max(1, startingdifficulty-6); + difficultylevels[14] = max(1, startingdifficulty-7); + difficultylevels[15] = max(1, startingdifficulty-7); + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i]) + { + if (numplayers < MAXSPLITSCREENPLAYERS && !players[i].spectator) + { + competitors[numplayers] = i; + skinusable[players[i].skin] = false; + numplayers++; + } + else + { + players[i].spectator = true; // force spectate for all other players, if they happen to exist? + } + } + } + + if (numplayers > 2) + { + // Add 3 bots per player beyond 2P + playercount += (numplayers-2) * 3; + } + + wantedbots = playercount - numplayers; + + // Create rival list + if (numplayers > 0) + { + for (i = 0; i < SKINRIVALS; i++) + { + for (j = 0; j < numplayers; j++) + { + player_t *p = &players[competitors[j]]; + char *rivalname = skins[p->skin].rivals[i]; + SINT8 rivalnum = R_SkinAvailable(rivalname); + + if (rivalnum != -1 && skinusable[rivalnum]) + { + botskinlist[botskinlistpos] = rivalnum; + skinusable[rivalnum] = false; + botskinlistpos++; + } + } + } + } + + // Pad the remaining list with random skins if we need to + if (botskinlistpos < wantedbots) + { + for (i = botskinlistpos; i < wantedbots; i++) + { + UINT8 val = M_RandomKey(numskins); + UINT8 loops = 0; + + while (!skinusable[val]) + { + if (loops >= numskins) + { + // no more skins + break; + } + + val++; + + if (val >= numskins) + { + val = 0; + } + + loops++; + } + + if (loops >= numskins) + { + // leave the rest of the table as the default skin + break; + } + + botskinlist[i] = val; + skinusable[val] = false; + } + } + + for (i = 0; i < wantedbots; i++) + { + if (!K_AddBot(botskinlist[i], difficultylevels[i], &newplayernum)) + { + break; + } + } +} + +/*-------------------------------------------------- + static INT16 K_RivalScore(player_t *bot) + + Creates a "rival score" for a bot, used to determine which bot is the + most deserving of the rival status. + + Input Arguments:- + bot - Player to check. + + Return:- + "Rival score" value. +--------------------------------------------------*/ +static INT16 K_RivalScore(player_t *bot) +{ + const UINT16 difficulty = bot->botvars.difficulty; + const UINT16 score = bot->score; + SINT8 roundnum = 1, roundsleft = 1; + UINT16 lowestscore = UINT16_MAX; + UINT8 lowestdifficulty = MAXBOTDIFFICULTY; + UINT8 i; + + if (grandprixinfo.cup != NULL) + { + roundnum = grandprixinfo.roundnum; + roundsleft = grandprixinfo.cup->numlevels - grandprixinfo.roundnum; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + if (players[i].score < lowestscore) + { + lowestscore = players[i].score; + } + + if (players[i].bot == true && players[i].botvars.difficulty < lowestdifficulty) + { + lowestdifficulty = players[i].botvars.difficulty; + } + } + + // In the early game, difficulty is more important. + // This will try to influence the higher difficulty bots to get rival more often & get even more points. + // However, when we're running low on matches left, we need to focus more on raw score! + + return ((difficulty - lowestdifficulty) * roundsleft) + ((score - lowestscore) * roundnum); +} + +/*-------------------------------------------------- + void K_UpdateGrandPrixBots(void) + + See header file for description. +--------------------------------------------------*/ +void K_UpdateGrandPrixBots(void) +{ + player_t *oldrival = NULL; + player_t *newrival = NULL; + UINT16 newrivalscore = 0; + UINT8 i; + + // Find the rival. + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || !players[i].bot) + { + continue; + } + + if (players[i].botvars.diffincrease) + { + players[i].botvars.difficulty += players[i].botvars.diffincrease; + + if (players[i].botvars.difficulty > MAXBOTDIFFICULTY) + { + players[i].botvars.difficulty = MAXBOTDIFFICULTY; + } + + players[i].botvars.diffincrease = 0; + } + + if (players[i].botvars.rival) + { + if (oldrival == NULL) + { + // Record the old rival to compare with our calculated new rival + oldrival = &players[i]; + } + else + { + // Somehow 2 rivals were set?! + // Let's quietly fix our mess... + players[i].botvars.rival = false; + } + } + } + + // Find the bot with the best average of score & difficulty. + for (i = 0; i < MAXPLAYERS; i++) + { + UINT16 ns = 0; + + if (!playeringame[i] || players[i].spectator || !players[i].bot) + { + continue; + } + + ns = K_RivalScore(&players[i]); + + if (ns > newrivalscore) + { + newrival = &players[i]; + newrivalscore = ns; + } + } + + // Even if there's a new rival, we want to make sure that they're a better fit than the current one. + if (oldrival != newrival) + { + if (oldrival != NULL) + { + UINT16 os = K_RivalScore(oldrival); + + if (newrivalscore < os + 5) + { + // This rival's only *slightly* better, no need to fire the old one. + // Our current rival's just fine, thank you very much. + return; + } + + // Hand over your badge. + oldrival->botvars.rival = false; + } + + // Set our new rival! + newrival->botvars.rival = true; + } +} + +/*-------------------------------------------------- + static UINT8 K_BotExpectedStanding(player_t *bot) + + Predicts what placement a bot was expected to be in. + Used for determining if a bot's difficulty should raise. + + Input Arguments:- + bot - Player to check. + + Return:- + Position number the bot was expected to be in. +--------------------------------------------------*/ +static UINT8 K_BotExpectedStanding(player_t *bot) +{ + UINT8 pos = 1; + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (i == (bot - players)) + { + continue; + } + + if (playeringame[i] && !players[i].spectator) + { + if (players[i].bot) + { + if (players[i].botvars.difficulty > bot->botvars.difficulty) + { + pos++; + } + else if (players[i].score > bot->score) + { + pos++; + } + } + else + { + // Human player, always increment + pos++; + } + } + } + + return pos; +} + +/*-------------------------------------------------- + void K_IncreaseBotDifficulty(player_t *bot) + + See header file for description. +--------------------------------------------------*/ +void K_IncreaseBotDifficulty(player_t *bot) +{ + UINT8 expectedstanding; + INT16 standingdiff; + + if (bot->botvars.difficulty >= MAXBOTDIFFICULTY) + { + // Already at max difficulty, don't need to increase + return; + } + + // Increment bot difficulty based on what position you were meant to come in! + expectedstanding = K_BotExpectedStanding(bot); + standingdiff = expectedstanding - bot->kartstuff[k_position]; + + if (standingdiff >= -2) + { + UINT8 increase; + + if (standingdiff > 5) + { + increase = 3; + } + else if (standingdiff > 2) + { + increase = 2; + } + else + { + increase = 1; + } + + bot->botvars.diffincrease = increase; + } + else + { + bot->botvars.diffincrease = 0; + } +} + +/*-------------------------------------------------- + void K_FakeBotResults(player_t *bot) + + See header file for description. +--------------------------------------------------*/ +void K_FakeBotResults(player_t *bot) +{ + const UINT32 distfactor = FixedMul(32 * bot->mo->scale, K_GetKartGameSpeedScalar(gamespeed)) / FRACUNIT; + UINT32 worstdist = 0; + tic_t besttime = UINT32_MAX; + UINT8 numplayers = 0; + UINT8 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator) + { + numplayers++; + + if (players[i].exiting && players[i].realtime < besttime) + { + besttime = players[i].realtime; + } + + if (players[i].distancetofinish > worstdist) + { + worstdist = players[i].distancetofinish; + } + } + } + + if (besttime == UINT32_MAX // No one finished, so you don't finish either. + || bot->distancetofinish >= worstdist) // Last place, you aren't going to finish. + { + bot->pflags |= PF_TIMEOVER; + return; + } + + // hey, you "won" + bot->exiting = 2; + bot->realtime += (bot->distancetofinish / distfactor); + bot->distancetofinish = 0; + K_IncreaseBotDifficulty(bot); +} + +/*-------------------------------------------------- + void K_PlayerLoseLife(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void K_PlayerLoseLife(player_t *player) +{ + if (!G_GametypeUsesLives()) + { + return; + } + + if (player->spectator || player->exiting || player->bot || player->lostlife) + { + return; + } + + player->lives--; + player->lostlife = true; + +#if 0 + if (player->lives <= 0) + { + if (P_IsLocalPlayer(player)) + { + S_StopMusic(); + S_ChangeMusicInternal("gmover", false); + } + } +#endif +} + +/*-------------------------------------------------- + boolean K_CanChangeRules(void) + + See header file for description. +--------------------------------------------------*/ +boolean K_CanChangeRules(void) +{ + if (demo.playback) + { + // We've already got our important settings! + return false; + } + + if (grandprixinfo.gp == true && grandprixinfo.roundnum > 0) + { + // Don't cheat the rules of the GP! + return false; + } + + if (modeattacking == true) + { + // Don't cheat the rules of Time Trials! + return false; + } + + return true; +} diff --git a/src/k_grandprix.h b/src/k_grandprix.h new file mode 100644 index 000000000..9f27b485b --- /dev/null +++ b/src/k_grandprix.h @@ -0,0 +1,144 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_grandprix.h +/// \brief Grand Prix mode game logic & bot behaviors + +#ifndef __K_GRANDPRIX__ +#define __K_GRANDPRIX__ + +#include "doomdef.h" +#include "doomstat.h" + +extern struct grandprixinfo +{ + boolean gp; ///< If true, then we are in a Grand Prix. + UINT8 roundnum; ///< Round number. If 0, this is a single session from the warp command. + cupheader_t *cup; ///< Which cup are we playing? + UINT8 gamespeed; ///< Copy of gamespeed, just to make sure you can't cheat it with cvars + boolean encore; ///< Ditto, but for encore mode + boolean masterbots; ///< If true, all bots should be max difficulty (Master Mode) + boolean initalize; ///< If true, we need to initialize a new session. + boolean wonround; ///< If false, then we retry the map instead of going to the next. +} grandprixinfo; + + +/*-------------------------------------------------- + UINT8 K_BotStartingDifficulty(SINT8 value); + + Determines the starting difficulty of the bots + for a specific game speed. + + Input Arguments:- + value - Game speed value to use. + + Return:- + Bot difficulty level. +--------------------------------------------------*/ + +UINT8 K_BotStartingDifficulty(SINT8 value); + + +/*-------------------------------------------------- + INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers); + + Calculates the number of points that a player would + recieve if they won the round. + + Input Arguments:- + position - Finishing position. + numplayers - Number of players in the game. + + Return:- + Number of points to give. +--------------------------------------------------*/ + +INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers); + + +/*-------------------------------------------------- + void K_InitGrandPrixBots(void); + + Spawns bots specifically tailored for Grand Prix mode. +--------------------------------------------------*/ + +void K_InitGrandPrixBots(void); + + +/*-------------------------------------------------- + void K_UpdateGrandPrixBots(void); + + Updates bot settings based on the the results of the race. +--------------------------------------------------*/ + +void K_UpdateGrandPrixBots(void); + + +/*-------------------------------------------------- + void K_IncreaseBotDifficulty(player_t *bot); + + Increases the difficulty of this bot when they finish the race. + + Input Arguments:- + bot - Player to do this for. + + Return:- + None +--------------------------------------------------*/ + +void K_IncreaseBotDifficulty(player_t *bot); + + +/*-------------------------------------------------- + void K_FakeBotResults(player_t *bot); + + Fakes the results of the race, when all human players have + already finished and only bots were remaining. + + Input Arguments:- + bot - Player to do this for. + + Return:- + None +--------------------------------------------------*/ + +void K_FakeBotResults(player_t *bot); + + +/*-------------------------------------------------- + void K_PlayerLoseLife(player_t *player); + + Removes a life from a human player. + + Input Arguments:- + player - Player to do this for. + + Return:- + None +--------------------------------------------------*/ + +void K_PlayerLoseLife(player_t *player); + + +/*-------------------------------------------------- + boolean K_CanChangeRules(void); + + Returns whenver or not the server is allowed + to change the game rules. + + Input Arguments:- + None + + Return:- + None +--------------------------------------------------*/ + +boolean K_CanChangeRules(void); + +#endif diff --git a/src/k_hud.c b/src/k_hud.c new file mode 100644 index 000000000..4ceb24dd9 --- /dev/null +++ b/src/k_hud.c @@ -0,0 +1,4122 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_hud.c +/// \brief HUD drawing functions exclusive to Kart + +#include "k_hud.h" +#include "k_kart.h" +#include "k_battle.h" +#include "k_color.h" +#include "screen.h" +#include "doomtype.h" +#include "doomdef.h" +#include "hu_stuff.h" +#include "d_netcmd.h" +#include "v_video.h" +#include "r_draw.h" +#include "st_stuff.h" +#include "lua_hud.h" +#include "doomstat.h" +#include "d_clisrv.h" +#include "g_game.h" +#include "p_local.h" +#include "z_zone.h" +#include "m_cond.h" +#include "r_main.h" +#include "s_sound.h" +#include "r_things.h" + +#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_capsulesticker; +static patch_t *kp_capsulestickerwide; +static patch_t *kp_karmasticker; +static patch_t *kp_splitkarmabomb; +static patch_t *kp_timeoutsticker; + +static patch_t *kp_prestartbulb[15]; +static patch_t *kp_prestartletters[7]; + +static patch_t *kp_prestartbulb_split[15]; +static patch_t *kp_prestartletters_split[7]; + +static patch_t *kp_startcountdown[20]; +static patch_t *kp_racefault[6]; +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_rankcapsule; + +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_bubbleshield[2]; +static patch_t *kp_flameshield[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_rival[2]; +static patch_t *kp_localtag[4][2]; + +static patch_t *kp_eggnum[4]; + +static patch_t *kp_flameshieldmeter[104][2]; +static patch_t *kp_flameshieldmeter_bg[16][2]; + +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]; + +static patch_t *kp_cpu; + +static patch_t *kp_nametagstem; + +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_capsulesticker = W_CachePatchName("K_STCAPN", PU_HUDGFX); + kp_capsulestickerwide = W_CachePatchName("K_STCAPW", 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); + + // Pre-start countdown bulbs + sprintf(buffer, "K_BULBxx"); + for (i = 0; i < 15; i++) + { + buffer[6] = '0'+((i+1)/10); + buffer[7] = '0'+((i+1)%10); + kp_prestartbulb[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + sprintf(buffer, "K_SBLBxx"); + for (i = 0; i < 15; i++) + { + buffer[6] = '0'+((i+1)/10); + buffer[7] = '0'+((i+1)%10); + kp_prestartbulb_split[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + // Pre-start position letters + kp_prestartletters[0] = W_CachePatchName("K_PL_P", PU_HUDGFX); + kp_prestartletters[1] = W_CachePatchName("K_PL_O", PU_HUDGFX); + kp_prestartletters[2] = W_CachePatchName("K_PL_S", PU_HUDGFX); + kp_prestartletters[3] = W_CachePatchName("K_PL_I", PU_HUDGFX); + kp_prestartletters[4] = W_CachePatchName("K_PL_T", PU_HUDGFX); + kp_prestartletters[5] = W_CachePatchName("K_PL_N", PU_HUDGFX); + kp_prestartletters[6] = W_CachePatchName("K_PL_EX", PU_HUDGFX); + + kp_prestartletters_split[0] = W_CachePatchName("K_SPL_P", PU_HUDGFX); + kp_prestartletters_split[1] = W_CachePatchName("K_SPL_O", PU_HUDGFX); + kp_prestartletters_split[2] = W_CachePatchName("K_SPL_S", PU_HUDGFX); + kp_prestartletters_split[3] = W_CachePatchName("K_SPL_I", PU_HUDGFX); + kp_prestartletters_split[4] = W_CachePatchName("K_SPL_T", PU_HUDGFX); + kp_prestartletters_split[5] = W_CachePatchName("K_SPL_N", PU_HUDGFX); + kp_prestartletters_split[6] = W_CachePatchName("K_SPL_EX", 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_DUEL1", PU_HUDGFX); + kp_startcountdown[5] = W_CachePatchName("K_CNT3B", PU_HUDGFX); + kp_startcountdown[6] = W_CachePatchName("K_CNT2B", PU_HUDGFX); + kp_startcountdown[7] = W_CachePatchName("K_CNT1B", PU_HUDGFX); + kp_startcountdown[8] = W_CachePatchName("K_CNTGOB", PU_HUDGFX); + kp_startcountdown[9] = W_CachePatchName("K_DUEL2", PU_HUDGFX); + // Splitscreen + kp_startcountdown[10] = W_CachePatchName("K_SMC3A", PU_HUDGFX); + kp_startcountdown[11] = W_CachePatchName("K_SMC2A", PU_HUDGFX); + kp_startcountdown[12] = W_CachePatchName("K_SMC1A", PU_HUDGFX); + kp_startcountdown[13] = W_CachePatchName("K_SMCGOA", PU_HUDGFX); + kp_startcountdown[14] = W_CachePatchName("K_SDUEL1", PU_HUDGFX); + kp_startcountdown[15] = W_CachePatchName("K_SMC3B", PU_HUDGFX); + kp_startcountdown[16] = W_CachePatchName("K_SMC2B", PU_HUDGFX); + kp_startcountdown[17] = W_CachePatchName("K_SMC1B", PU_HUDGFX); + kp_startcountdown[18] = W_CachePatchName("K_SMCGOB", PU_HUDGFX); + kp_startcountdown[19] = W_CachePatchName("K_SDUEL2", PU_HUDGFX); + + // Fault + kp_racefault[0] = W_CachePatchName("K_FAULTA", PU_HUDGFX); + kp_racefault[1] = W_CachePatchName("K_FAULTB", PU_HUDGFX); + // Splitscreen + kp_racefault[2] = W_CachePatchName("K_SMFLTA", PU_HUDGFX); + kp_racefault[3] = W_CachePatchName("K_SMFLTB", PU_HUDGFX); + // 2P splitscreen + kp_racefault[4] = W_CachePatchName("K_2PFLTA", PU_HUDGFX); + kp_racefault[5] = W_CachePatchName("K_2PFLTB", 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); + kp_rankcapsule = W_CachePatchName("K_CAPICO", 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_bubbleshield[0] = W_CachePatchName("K_ITBUBS", PU_HUDGFX); + kp_flameshield[0] = W_CachePatchName("K_ITFLMS", 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); + + sprintf(buffer, "FSMFGxxx"); + for (i = 0; i < 104; i++) + { + buffer[5] = '0'+((i+1)/100); + buffer[6] = '0'+(((i+1)/10)%10); + buffer[7] = '0'+((i+1)%10); + kp_flameshieldmeter[i][0] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + sprintf(buffer, "FSMBG0xx"); + for (i = 0; i < 16; i++) + { + buffer[6] = '0'+((i+1)/10); + buffer[7] = '0'+((i+1)%10); + kp_flameshieldmeter_bg[i][0] = (patch_t *) W_CachePatchName(buffer, 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_bubbleshield[1] = W_CachePatchName("K_ISBUBS", PU_HUDGFX); + kp_flameshield[1] = W_CachePatchName("K_ISFLMS", 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); + + sprintf(buffer, "FSMFSxxx"); + for (i = 0; i < 104; i++) + { + buffer[5] = '0'+((i+1)/100); + buffer[6] = '0'+(((i+1)/10)%10); + buffer[7] = '0'+((i+1)%10); + kp_flameshieldmeter[i][1] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + sprintf(buffer, "FSMBS0xx"); + for (i = 0; i < 16; i++) + { + buffer[6] = '0'+((i+1)/10); + buffer[7] = '0'+((i+1)%10); + kp_flameshieldmeter_bg[i][1] = (patch_t *) W_CachePatchName(buffer, 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); + } + + // Rival indicators + sprintf(buffer, "K_RIVALx"); + for (i = 0; i < 2; i++) + { + buffer[7] = '1'+i; + kp_rival[i] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); + } + + // Rival indicators + sprintf(buffer, "K_SSPLxx"); + for (i = 0; i < 4; i++) + { + buffer[6] = 'A'+i; + for (j = 0; j < 2; j++) + { + buffer[7] = '1'+j; + kp_localtag[i][j] = (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_cpu = (patch_t *) W_CachePatchName("K_CPU", PU_HUDGFX); + + kp_nametagstem = (patch_t *) W_CachePatchName("K_NAMEST", PU_HUDGFX); +} + +// For the item toggle menu +const char *K_GetItemPatch(UINT8 item, boolean tiny) +{ + switch (item) + { + case KITEM_SNEAKER: + case KRITEM_DUALSNEAKER: + 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_BUBBLESHIELD: + return (tiny ? "K_ISBUBS" : "K_ITBUBS"); + case KITEM_FLAMESHIELD: + return (tiny ? "K_ISFLMS" : "K_ITFLMS"); + 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; + + +void K_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 dupy) +{ + // dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx + INT32 screenwidth = vid.width; + INT32 screenheight = vid.height; + INT32 basewidth = BASEVIDWIDTH * dupx; + INT32 baseheight = BASEVIDHEIGHT * dupy; + SINT8 player = -1; + UINT8 i; + + if (options & V_SPLITSCREEN) + { + if (r_splitscreen > 0) + { + screenheight /= 2; + baseheight /= 2; + } + + if (r_splitscreen > 1) + { + screenwidth /= 2; + basewidth /= 2; + } + } + + for (i = 0; i <= r_splitscreen; i++) + { + if (stplyr == &players[displayplayers[i]]) + { + player = i; + break; + } + } + + if (vid.width != (BASEVIDWIDTH * dupx)) + { + if (options & V_SNAPTORIGHT) + *x += (screenwidth - basewidth); + else if (!(options & V_SNAPTOLEFT)) + *x += (screenwidth - basewidth) / 2; + } + + if (vid.height != (BASEVIDHEIGHT * dupy)) + { + if (options & V_SNAPTOBOTTOM) + *y += (screenheight - baseheight); + else if (!(options & V_SNAPTOTOP)) + *y += (screenheight - baseheight) / 2; + } + + if (options & V_SPLITSCREEN) + { + if (r_splitscreen == 1) + { + if (player == 1) + *y += screenheight; + } + else if (r_splitscreen > 1) + { + if (player == 1 || player == 3) + *x += screenwidth; + + if (player == 2 || player == 3) + *y += screenheight; + } + } + + if (options & V_SLIDEIN) + { + tic_t length = TICRATE/2; + + if (leveltime < introtime + length) + { + INT32 offset = screenwidth - (((leveltime - introtime) * screenwidth) / length); + boolean slidefromright = false; + + if (r_splitscreen > 1) + { + if (stplyr == &players[displayplayers[1]] || stplyr == &players[displayplayers[3]]) + slidefromright = true; + } + + if (options & V_SNAPTORIGHT) + slidefromright = true; + else if (options & V_SNAPTOLEFT) + slidefromright = false; + + if (slidefromright == true) + { + offset = -offset; + } + + *x -= offset; + } + } +} + +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 (r_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 (r_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/2)-39; + ITEM2_Y = -8; + + LAPS2_X = (BASEVIDWIDTH/2)-43; + LAPS2_Y = (BASEVIDHEIGHT/2)-12; + + POSI2_X = (BASEVIDWIDTH/2)-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 (r_splitscreen > 2) // 4P-only + { + MINI_X = (BASEVIDWIDTH/2); + MINI_Y = (BASEVIDHEIGHT/2); + } + } + } + + if (timeinmap > 105) + hudtrans = cv_translucenthud.value; + else + hudtrans = 0; +} + +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 = ((r_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... + 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 = (r_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_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_BUBBLESHIELD: + localpatch = kp_bubbleshield[offset]; + localbg = kp_itembg[offset+1]; + break; + case KITEM_FLAMESHIELD: + localpatch = kp_flameshield[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 (r_splitscreen < 2) // don't change shit for THIS splitscreen. + { + fx = ITEM_X; + fy = ITEM_Y; + fflags = V_SNAPTOTOP|V_SNAPTOLEFT|V_SPLITSCREEN; + } + 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|V_SNAPTOTOP|V_SPLITSCREEN; + } + else // else, that means we're P2 or P4. + { + fx = ITEM2_X; + fy = ITEM2_Y; + fflags = V_SNAPTORIGHT|V_SNAPTOTOP|V_SPLITSCREEN; + flipamount = true; + } + } + + if (localcolor != SKINCOLOR_NONE) + colmap = R_GetTranslationColormap(colormode, localcolor, GTC_CACHE); + + V_DrawScaledPatch(fx, fy, V_HUDTRANS|V_SLIDEIN|fflags, localbg); + + // Then, the numbers: + if (stplyr->kartstuff[k_itemamount] >= numberdisplaymin && !stplyr->kartstuff[k_itemroulette]) + { + V_DrawScaledPatch(fx + (flipamount ? 48 : 0), fy, V_HUDTRANS|V_SLIDEIN|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|V_SLIDEIN|fflags, va("x%d", stplyr->kartstuff[k_itemamount])); + else + { + V_DrawScaledPatch(fy+28, fy+41, V_HUDTRANS|V_SLIDEIN|fflags, kp_itemx); + V_DrawKartString(fx+38, fy+36, V_HUDTRANS|V_SLIDEIN|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|V_SLIDEIN|fflags, kp_eggnum[min(3, G_TicsToSeconds(stplyr->kartstuff[k_eggmanexplode]))]); + + if (stplyr->kartstuff[k_itemtype] == KITEM_FLAMESHIELD && stplyr->kartstuff[k_flamelength] > 0) + { + INT32 numframes = 104; + INT32 absolutemax = 16 * flameseg; + INT32 flamemax = stplyr->kartstuff[k_flamelength] * flameseg; + INT32 flamemeter = min(stplyr->kartstuff[k_flamemeter], flamemax); + + INT32 bf = 16 - stplyr->kartstuff[k_flamelength]; + INT32 ff = numframes - ((flamemeter * numframes) / absolutemax); + INT32 fmin = (8 * (bf-1)); + + INT32 xo = 6, yo = 4; + INT32 flip = 0; + + if (offset) + { + xo++; + + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // Flip for P1 and P3 (yes, that's correct) + { + xo -= 62; + flip = V_FLIP; + } + } + + if (ff < fmin) + ff = fmin; + + if (bf >= 0 && bf < 16) + V_DrawScaledPatch(fx-xo, fy-yo, V_HUDTRANS|V_SLIDEIN|fflags|flip, kp_flameshieldmeter_bg[bf][offset]); + + if (ff >= 0 && ff < numframes && stplyr->kartstuff[k_flamemeter] > 0) + { + if ((stplyr->kartstuff[k_flamemeter] > flamemax) && (leveltime & 1)) + { + UINT8 *fsflash = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE); + V_DrawMappedPatch(fx-xo, fy-yo, V_HUDTRANS|V_SLIDEIN|fflags|flip, kp_flameshieldmeter[ff][offset], fsflash); + } + else + { + V_DrawScaledPatch(fx-xo, fy-yo, V_HUDTRANS|V_SLIDEIN|fflags|flip, kp_flameshieldmeter[ff][offset]); + } + } + } +} + +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|V_SLIDEIN|V_SNAPTOTOP|V_SNAPTORIGHT|V_SPLITSCREEN; + 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 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 (r_splitscreen) + scale /= 2; + + W = FixedMul(W<>FRACBITS; + + // pain and suffering defined below + if (!r_splitscreen) + { + fx = POSI_X; + fy = BASEVIDHEIGHT - 8; + fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN; + } + else if (r_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|V_SPLITSCREEN; + 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/2) - 8; + fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN; + } + } + 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|V_SNAPTOBOTTOM|V_SPLITSCREEN; + 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|V_SNAPTOBOTTOM|V_SPLITSCREEN; + } + } + + // 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|V_SLIDEIN|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_SLIDEIN|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_SLIDEIN|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_SLIDEIN|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_SLIDEIN|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_SLIDEIN|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_SLIDEIN|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 ping offline + { + if (players[tab[i].num].bot) + { + V_DrawScaledPatch(x + ((i < 8) ? -25 : rightoffset + 3), y-2, 0, kp_cpu); + } + else if (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 (netgame && playerconsole[tab[i].num] == 0 && server_lagless && !players[tab[i].num].bot) + { + 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) +{ + const boolean uselives = G_GametypeUsesLives(); + SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; + INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; + 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((r_splitscreen > 1) ? kp_smallring[ringanim_realframe]->width : kp_ring[ringanim_realframe]->width); + } + + if (r_splitscreen > 1) + { + INT32 fx = 0, fy = 0, fr = 0; + INT32 flipflag = 0; + + // pain and suffering defined below + if (r_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|V_SNAPTOBOTTOM|V_SPLITSCREEN; + } + else // else, that means we're P2 or P4. + { + fx = LAPS2_X; + fy = LAPS2_Y; + splitflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; + 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|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); + + V_DrawScaledPatch(fx, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_splitlapflag); + V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|V_SLIDEIN|splitflags, frameslash); + + if (cv_numlaps.value >= 10) + { + UINT8 ln[2]; + ln[0] = ((stplyr->laps / 10) % 10); + ln[1] = (stplyr->laps % 10); + + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); + + ln[0] = ((abs(cv_numlaps.value) / 10) % 10); + ln[1] = (abs(cv_numlaps.value) % 10); + + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); + } + else + { + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->laps) % 10]); + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(cv_numlaps.value) % 10]); + } + + // Rings + if (!uselives) + { + V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy-10, V_HUDTRANS|V_SLIDEIN|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|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); + + V_DrawMappedPatch(fr+ringx, fy-13, V_HUDTRANS|V_SLIDEIN|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|V_SLIDEIN|splitflags, kp_ringdebtminussmall, ringmap); + + V_DrawMappedPatch(fr+11, fy-10, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[0]], ringmap); + V_DrawMappedPatch(fr+15, fy-10, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[1]], ringmap); + + // SPB ring lock + if (stplyr->kartstuff[k_ringlock]) + V_DrawScaledPatch(fr-12, fy-23, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblocksmall[stplyr->karthud[khud_ringspblock]]); + + // Lives + if (uselives) + { + UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); + V_DrawMappedPatch(fr+21, fy-13, V_HUDTRANS|V_SLIDEIN|splitflags, facemmapprefix[stplyr->skin], colormap); + V_DrawScaledPatch(fr+34, fy-10, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[(stplyr->lives % 10)]); // make sure this doesn't overflow + } + } + else + { + // Laps + V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_lapsticker); + + if (stplyr->exiting) + V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, "FIN"); + else + V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", stplyr->laps, cv_numlaps.value)); + + // Rings + if (!uselives) + V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[1]); + else + V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[0]); + + V_DrawMappedPatch(LAPS_X+ringx+7, LAPS_Y-16, V_HUDTRANS|V_SLIDEIN|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|V_SLIDEIN|splitflags, kp_ringdebtminus, ringmap); + V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); + V_DrawMappedPatch(LAPS_X+35, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); + } + else + { + V_DrawMappedPatch(LAPS_X+23, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); + V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); + } + + // SPB ring lock + if (stplyr->kartstuff[k_ringlock]) + V_DrawScaledPatch(LAPS_X-5, LAPS_Y-28, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); + + // Lives + if (uselives) + { + UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); + V_DrawMappedPatch(LAPS_X+46, LAPS_Y-16, V_HUDTRANS|V_SLIDEIN|splitflags, facerankprefix[stplyr->skin], colormap); + V_DrawScaledPatch(LAPS_X+63, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow + } + } +} + +#undef RINGANIM_FLIPFRAME + +static void K_drawKartSpeedometer(void) +{ + static fixed_t convSpeed; + UINT8 labeln = 0; + UINT8 numbers[3]; + INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; + 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|V_SLIDEIN|splitflags, kp_speedometersticker); + V_DrawScaledPatch(LAPS_X+7, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[0]]); + V_DrawScaledPatch(LAPS_X+13, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[1]]); + V_DrawScaledPatch(LAPS_X+19, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[2]]); + V_DrawScaledPatch(LAPS_X+29, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometerlabel[labeln]); +} + +static void K_drawKartBumpersOrKarma(void) +{ + UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); + INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; + + if (r_splitscreen > 1) + { + INT32 fx = 0, fy = 0; + INT32 flipflag = 0; + + // pain and suffering defined below + if (r_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|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); + V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|V_SLIDEIN|splitflags, frameslash); + + if (battlecapsules) + { + V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_rankcapsule, NULL); + + if (numtargets > 9 || maptargets > 9) + { + UINT8 ln[2]; + ln[0] = ((numtargets / 10) % 10); + ln[1] = (numtargets % 10); + + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); + + ln[0] = ((maptargets / 10) % 10); + ln[1] = (maptargets % 10); + + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); + } + else + { + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numtargets % 10]); + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[maptargets % 10]); + } + } + else + { + if (stplyr->kartstuff[k_bumper] <= 0) + { + V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_splitkarmabomb, colormap); + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->kartstuff[k_comebackpoints]) % 10]); + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[2]); + } + else + { + INT32 maxbumper = K_StartingBumperCount(); + V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_rankbumper, colormap); + + if (stplyr->kartstuff[k_bumper] > 9 || maxbumper > 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|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); + + ln[0] = ((abs(maxbumper) / 10) % 10); + ln[1] = (abs(maxbumper) % 10); + + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); + V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); + } + else + { + V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->kartstuff[k_bumper]) % 10]); + V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(maxbumper) % 10]); + } + } + } + } + else + { + if (battlecapsules) + { + if (numtargets > 9 && maptargets > 9) + V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_capsulestickerwide, NULL); + else + V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_capsulesticker, NULL); + V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", numtargets, maptargets)); + } + else + { + if (stplyr->kartstuff[k_bumper] <= 0) + { + V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_karmasticker, colormap); + V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/2", stplyr->kartstuff[k_comebackpoints])); + } + else + { + INT32 maxbumper = K_StartingBumperCount(); + + if (stplyr->kartstuff[k_bumper] > 9 && maxbumper > 9) + V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bumperstickerwide, colormap); + else + V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bumpersticker, colormap); + + V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", stplyr->kartstuff[k_bumper], maxbumper)); + } + } + } +} + +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 (r_splitscreen < 3) // 1P and 2P use the same code. + { + basex = WANT_X; + basey = WANT_Y; + if (r_splitscreen == 2) + { + basey += 16; // slight adjust for 3P + basex -= 6; + } + } + else if (r_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+(r_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_ObjectTracking(fixed_t *hud_x, fixed_t *hud_y, vertex_t *campos, angle_t camang, angle_t camaim, vertex_t *point) +{ + const INT32 swhalf = (BASEVIDWIDTH / 2); + const fixed_t swhalffixed = swhalf * FRACUNIT; + + const INT32 shhalf = (BASEVIDHEIGHT / 2); + const fixed_t shhalffixed = shhalf * FRACUNIT; + + + const UINT8 precisionloss = 4; + + fixed_t anglediff = (AngleFixed(camang) / precisionloss) - (AngleFixed(R_PointToAngle2(campos->x, campos->y, point->x, point->y)) / precisionloss); + + fixed_t distance = R_PointToDist2(campos->x, campos->y, point->x, point->y); + fixed_t factor = INT32_MAX; + + const fixed_t fov = cv_fov.value; + fixed_t intendedfov = 90*FRACUNIT; + fixed_t fovmul = FRACUNIT; + + if (r_splitscreen == 1) // Splitscreen FOV should be adjusted to maintain expected vertical view + { + intendedfov = 17 * intendedfov / 10; + } + + fovmul = FixedDiv(fov, intendedfov); + + anglediff = FixedMul(anglediff, fovmul) * precisionloss; + + if (abs(anglediff) > 90*FRACUNIT) + { + if (hud_x != NULL) + { + *hud_x = -1000 * FRACUNIT; + } + + if (hud_y != NULL) + { + *hud_y = -1000 * FRACUNIT; + } + + //*hud_scale = 0; + return; + } + + anglediff = FixedAngle(anglediff); + + factor = max(1, FINECOSINE(anglediff >> ANGLETOFINESHIFT)); + +#define NEWTAN(n) FINETANGENT(((n + ANGLE_90) >> ANGLETOFINESHIFT) & 4095) + + if (hud_x != NULL) + { + *hud_x = FixedMul(NEWTAN(anglediff), swhalffixed) + swhalffixed; + + if (*hud_x < 0 || *hud_x > BASEVIDWIDTH * FRACUNIT) + { + *hud_x = -1000 * FRACUNIT; + } + else + { + if (encoremode) + { + *hud_x = (BASEVIDWIDTH * FRACUNIT) - *hud_x; + } + + if (r_splitscreen >= 2) + { + *hud_x /= 2; + } + } + } + + if (hud_y != NULL) + { + *hud_y = campos->z - point->z; + *hud_y = FixedDiv(*hud_y, FixedMul(factor, distance)); + *hud_y = (*hud_y * swhalf) + shhalffixed; + *hud_y = *hud_y + NEWTAN(camaim) * swhalf; + + if (*hud_y < 0 || *hud_y > BASEVIDHEIGHT * FRACUNIT) + { + *hud_y = -1000 * FRACUNIT; + } + else + { + if (r_splitscreen >= 1) + { + *hud_y /= 2; + } + } + } + + //*hud_scale = FixedDiv(swhalffixed, FixedMul(factor, distance)); + +#undef NEWTAN +} + +static void K_drawKartPlayerCheck(void) +{ + const fixed_t maxdistance = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); + camera_t *thiscam; + vertex_t c; + UINT8 cnum = 0; + UINT8 i; + INT32 splitflags = V_SNAPTOBOTTOM|V_SPLITSCREEN; + + if (stplyr == NULL || stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo)) + { + return; + } + + if (stplyr->spectator || stplyr->awayviewtics) + { + return; + } + + if (stplyr->cmd.buttons & BT_LOOKBACK) + { + return; + } + + if (r_splitscreen) + { + for (i = 1; i <= r_splitscreen; i++) + { + if (stplyr == &players[displayplayers[i]]) + { + cnum = i; + break; + } + } + } + + thiscam = &camera[cnum]; + + c.x = stplyr->mo->x; + c.y = stplyr->mo->y; + c.z = stplyr->mo->z; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *checkplayer = &players[i]; + fixed_t distance = maxdistance+1; + UINT8 *colormap = NULL; + UINT8 pnum = 0; + fixed_t x = 0; + vertex_t v; + + if (!playeringame[i] || checkplayer->spectator) + { + // Not in-game + continue; + } + + if (checkplayer->mo == NULL || P_MobjWasRemoved(checkplayer->mo)) + { + // No object + continue; + } + + if (checkplayer == stplyr) + { + // This is you! + continue; + } + + v.x = checkplayer->mo->x; + v.y = checkplayer->mo->y; + v.z = checkplayer->mo->z; + + distance = R_PointToDist2(c.x, c.y, v.x, v.y); + + if (distance > maxdistance) + { + // Too far away + continue; + } + + if ((checkplayer->kartstuff[k_invincibilitytimer] <= 0) && (leveltime & 2)) + { + pnum++; // white frames + } + + if (checkplayer->kartstuff[k_itemtype] == KITEM_GROW || checkplayer->kartstuff[k_growshrinktimer] > 0) + { + pnum += 4; + } + else if (checkplayer->kartstuff[k_itemtype] == KITEM_INVINCIBILITY || checkplayer->kartstuff[k_invincibilitytimer]) + { + pnum += 2; + } + + K_ObjectTracking(&x, NULL, &c, thiscam->angle + ANGLE_180, 0, &v); + + colormap = R_GetTranslationColormap(TC_DEFAULT, checkplayer->mo->color, GTC_CACHE); + V_DrawFixedPatch(x, CHEK_Y * FRACUNIT, FRACUNIT, V_HUDTRANS|V_SPLITSCREEN|splitflags, kp_check[pnum], colormap); + } +} + +static boolean K_ShowPlayerNametag(player_t *p) +{ + if (demo.playback == true && demo.freecam == true) + { + return true; + } + + if (stplyr == p) + { + return false; + } + + if (G_RaceGametype()) + { + if ((p->kartstuff[k_position] < stplyr->kartstuff[k_position]-2) + || (p->kartstuff[k_position] > stplyr->kartstuff[k_position]+2)) + { + return false; + } + } + + return true; +} + +static void K_DrawLocalTagForPlayer(fixed_t x, fixed_t y, player_t *p, UINT8 id) +{ + UINT8 blink = ((leveltime / 7) & 1); + UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, p->skincolor, GTC_CACHE); + V_DrawFixedPatch(x, y, FRACUNIT, V_HUDTRANS|V_SPLITSCREEN, kp_localtag[id][blink], colormap); +} + +static void K_DrawRivalTagForPlayer(fixed_t x, fixed_t y) +{ + UINT8 blink = ((leveltime / 7) & 1); + V_DrawFixedPatch(x, y, FRACUNIT, V_HUDTRANS|V_SPLITSCREEN, kp_rival[blink], NULL); +} + +static void K_DrawNameTagForPlayer(fixed_t x, fixed_t y, player_t *p, UINT8 cnum) +{ + INT32 namelen = V_ThinStringWidth(player_names[p - players], V_6WIDTHSPACE|V_ALLOWLOWERCASE); + INT32 clr = K_SkincolorToTextColor(p->skincolor); + UINT8 *colormap = V_GetStringColormap(clr); + INT32 barx = 0, bary = 0, barw = 0; + + // Since there's no "V_DrawFixedFill", and I don't feel like making it, + // fuck it, we're gonna just V_NOSCALESTART hack it + if (cnum & 1) + { + x += (BASEVIDWIDTH/2) * FRACUNIT; + } + + if ((r_splitscreen == 1 && cnum == 1) + || (r_splitscreen > 1 && cnum > 1)) + { + y += (BASEVIDHEIGHT/2) * FRACUNIT; + } + + barw = (namelen * vid.dupx); + + barx = (x * vid.dupx) / FRACUNIT; + bary = (y * vid.dupy) / FRACUNIT; + + barx += (6 * vid.dupx); + bary -= (16 * vid.dupx); + + // Center it if necessary + if (vid.width != BASEVIDWIDTH * vid.dupx) + { + barx += (vid.width - (BASEVIDWIDTH * vid.dupx)) / 2; + } + + if (vid.height != BASEVIDHEIGHT * vid.dupy) + { + bary += (vid.height - (BASEVIDHEIGHT * vid.dupy)) / 2; + } + + // Lat: 10/06/2020: colormap can be NULL on the frame you join a game, just arbitrarily use palette indexes 31 and 0 instead of whatever the colormap would give us instead to avoid crashes. + V_DrawFill(barx, bary, barw, (3 * vid.dupy), (colormap ? colormap[31] : 31)|V_NOSCALESTART); + V_DrawFill(barx, bary + vid.dupy, barw, vid.dupy, (colormap ? colormap[0] : 0)|V_NOSCALESTART); + // END DRAWFILL DUMBNESS + + // Draw the stem + V_DrawFixedPatch(x, y, FRACUNIT, 0, kp_nametagstem, colormap); + + // Draw the name itself + V_DrawThinStringAtFixed(x + (5*FRACUNIT), y - (26*FRACUNIT), V_6WIDTHSPACE|V_ALLOWLOWERCASE|clr, player_names[p - players]); +} + +static void K_drawKartNameTags(void) +{ + const fixed_t maxdistance = 8192*mapobjectscale; + camera_t *thiscam; + vertex_t c; + UINT8 cnum = 0; + UINT8 tobesorted[MAXPLAYERS]; + fixed_t sortdist[MAXPLAYERS]; + UINT8 sortlen = 0; + UINT8 i, j; + + if (stplyr == NULL || stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo)) + { + return; + } + + if (stplyr->awayviewtics) + { + return; + } + + if (r_splitscreen) + { + for (i = 1; i <= r_splitscreen; i++) + { + if (stplyr == &players[displayplayers[i]]) + { + cnum = i; + break; + } + } + } + + thiscam = &camera[cnum]; + + c.x = thiscam->x; + c.y = thiscam->y; + c.z = thiscam->z; + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *ntplayer = &players[i]; + fixed_t distance = maxdistance+1; + vertex_t v; + + if (!playeringame[i] || ntplayer->spectator) + { + // Not in-game + continue; + } + + if (ntplayer->mo == NULL || P_MobjWasRemoved(ntplayer->mo)) + { + // No object + continue; + } + + if (!P_CheckSight(stplyr->mo, ntplayer->mo)) + { + // Can't see + continue; + } + + v.x = ntplayer->mo->x; + v.y = ntplayer->mo->y; + v.z = ntplayer->mo->z; + + if (!(ntplayer->mo->eflags & MFE_VERTICALFLIP)) + { + v.z += ntplayer->mo->height; + } + + distance = R_PointToDist2(c.x, c.y, v.x, v.y); + + if (distance > maxdistance) + { + // Too far away + continue; + } + + tobesorted[sortlen] = ntplayer - players; + sortdist[sortlen] = distance; + sortlen++; + } + + if (sortlen > 0) + { + UINT8 sortedplayers[sortlen]; + + for (i = 0; i < sortlen; i++) + { + UINT8 pos = 0; + + for (j = 0; j < sortlen; j++) + { + if (j == i) + { + continue; + } + + if (sortdist[i] < sortdist[j] + || (sortdist[i] == sortdist[j] && i > j)) + { + pos++; + } + } + + sortedplayers[pos] = tobesorted[i]; + } + + for (i = 0; i < sortlen; i++) + { + player_t *ntplayer = &players[sortedplayers[i]]; + + fixed_t x = -BASEVIDWIDTH * FRACUNIT; + fixed_t y = -BASEVIDWIDTH * FRACUNIT; + + SINT8 localindicator = -1; + vertex_t v; + + v.x = ntplayer->mo->x; + v.y = ntplayer->mo->y; + v.z = ntplayer->mo->z; + + if (!(ntplayer->mo->eflags & MFE_VERTICALFLIP)) + { + v.z += ntplayer->mo->height; + } + + K_ObjectTracking(&x, &y, &c, thiscam->angle, thiscam->aiming, &v); + + /* + if ((x < 0 || x > BASEVIDWIDTH * FRACUNIT) + || (y < 0 || y > BASEVIDHEIGHT * FRACUNIT)) + { + // Off-screen + continue; + } + */ + + if (!(demo.playback == true && demo.freecam == true)) + { + for (j = 0; j <= r_splitscreen; j++) + { + if (ntplayer == &players[displayplayers[j]]) + { + break; + } + } + + if (j <= r_splitscreen && j != cnum) + { + localindicator = j; + } + } + + if (localindicator >= 0) + { + K_DrawLocalTagForPlayer(x, y, ntplayer, localindicator); + } + else if (ntplayer->bot) + { + if (ntplayer->botvars.rival == true) + { + K_DrawRivalTagForPlayer(x, y); + } + } + else if (netgame || demo.playback) + { + if (K_ShowPlayerNametag(ntplayer) == true) + { + K_DrawNameTagForPlayer(x, y, ntplayer, cnum); + } + } + } + } +} + +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. + // The number being divided by is for how fast it moves. + // The higher the number, the slower it moves. + + // am xpos & ypos are the icon's starting position. Withouht + // it, they wouldn't 'spawn' on the top-right side of the HUD. + + fixed_t amnumxpos, amnumypos; + INT32 amxpos, amypos; + + node_t *bsp = &nodes[numnodes-1]; + fixed_t maxx, minx, maxy, miny; + + fixed_t mapwidth, mapheight; + fixed_t xoffset, yoffset; + fixed_t xscale, yscale, zoom; + + maxx = maxy = INT32_MAX; + minx = miny = INT32_MIN; + minx = bsp->bbox[0][BOXLEFT]; + maxx = bsp->bbox[0][BOXRIGHT]; + miny = bsp->bbox[0][BOXBOTTOM]; + maxy = bsp->bbox[0][BOXTOP]; + + if (bsp->bbox[1][BOXLEFT] < minx) + minx = bsp->bbox[1][BOXLEFT]; + if (bsp->bbox[1][BOXRIGHT] > maxx) + maxx = bsp->bbox[1][BOXRIGHT]; + if (bsp->bbox[1][BOXBOTTOM] < miny) + miny = bsp->bbox[1][BOXBOTTOM]; + if (bsp->bbox[1][BOXTOP] > maxy) + maxy = bsp->bbox[1][BOXTOP]; + + // You might be wondering why these are being bitshift here + // it's because mapwidth and height would otherwise overflow for maps larger than half the size possible... + // map boundaries and sizes will ALWAYS be whole numbers thankfully + // later calculations take into consideration that these are actually not in terms of FRACUNIT though + minx >>= FRACBITS; + maxx >>= FRACBITS; + miny >>= FRACBITS; + maxy >>= FRACBITS; + + mapwidth = maxx - minx; + mapheight = maxy - miny; + + // These should always be small enough to be bitshift back right now + xoffset = (minx + mapwidth/2)<width, mapwidth); + yscale = FixedDiv(AutomapPic->height, mapheight); + zoom = FixedMul(min(xscale, yscale), FRACUNIT-FRACUNIT/20); + + amnumxpos = (FixedMul(objx, zoom) - FixedMul(xoffset, zoom)); + amnumypos = -(FixedMul(objy, zoom) - FixedMul(yoffset, zoom)); + + if (encoremode) + amnumxpos = -amnumxpos; + + amxpos = amnumxpos + ((hudx + AutomapPic->width/2 - (icon->width/2))<height/2 - (icon->height/2))<width/2 + (icon->width/2))<width/2); + y = MINI_Y - (AutomapPic->height/2); + + if (timeinmap > 105) + { + minimaptrans = cv_kartminimap.value; + if (timeinmap <= 113) + minimaptrans = ((((INT32)timeinmap) - 105)*minimaptrans)/(113-105); + if (!minimaptrans) + return; + } + else + return; + + minimaptrans = ((10-minimaptrans)<width), y, splitflags|V_FLIP, AutomapPic); + else + V_DrawScaledPatch(x, y, splitflags, AutomapPic); + + if (r_splitscreen != 2) + { + splitflags &= ~minimaptrans; + splitflags |= V_HUDTRANSHALF; + } + + // let offsets transfer to the heads, too! + if (encoremode) + x += SHORT(AutomapPic->leftoffset); + else + x -= SHORT(AutomapPic->leftoffset); + y -= SHORT(AutomapPic->topoffset); + + // Draw the super item in Battle + if (G_BattleGametype() && battleovertime.enabled) + { + if (battleovertime.enabled >= 10*TICRATE || (battleovertime.enabled & 1)) + { + const INT32 prevsplitflags = splitflags; + splitflags &= ~V_HUDTRANSHALF; + splitflags |= V_HUDTRANS; + colormap = R_GetTranslationColormap(TC_RAINBOW, (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))), GTC_CACHE); + K_drawKartMinimapIcon(battleovertime.x, battleovertime.y, x, y, splitflags, kp_itemminimap, colormap, AutomapPic); + splitflags = prevsplitflags; + } + } + + // initialize + for (i = 0; i < 4; i++) + localplayers[i] = -1; + + if (G_RaceGametype()) + hyu *= 2; // double in race + + // Player's tiny icons on the Automap. (drawn opposite direction so player 1 is drawn last in splitscreen) + if (ghosts) + { + demoghost *g = ghosts; + while (g) + { + if (g->mo->skin) + skin = ((skin_t*)g->mo->skin)-skins; + else + skin = 0; + if (g->mo->color) + { + if (g->mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, g->mo->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(skin, g->mo->color, GTC_CACHE); + } + else + colormap = NULL; + K_drawKartMinimapIcon(g->mo->x, g->mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); + g = g->next; + } + + if (!stplyr->mo || stplyr->spectator || stplyr->exiting) + return; + + localplayers[numlocalplayers] = stplyr-players; + numlocalplayers++; + } + else + { + for (i = MAXPLAYERS-1; i >= 0; i--) + { + if (!playeringame[i]) + continue; + if (!players[i].mo || players[i].spectator || players[i].exiting) + continue; + + if (i != displayplayers[0] || r_splitscreen) + { + if (G_BattleGametype() && players[i].kartstuff[k_bumper] <= 0) + continue; + + if (players[i].kartstuff[k_hyudorotimer] > 0) + { + if (!((players[i].kartstuff[k_hyudorotimer] < TICRATE/2 + || players[i].kartstuff[k_hyudorotimer] > hyu-(TICRATE/2)) + && !(leveltime & 1))) + continue; + } + } + + if (i == displayplayers[0] || i == displayplayers[1] || i == displayplayers[2] || i == displayplayers[3]) + { + // Draw display players on top of everything else + localplayers[numlocalplayers] = i; + numlocalplayers++; + continue; + } + + if (players[i].mo->skin) + skin = ((skin_t*)players[i].mo->skin)-skins; + else + skin = 0; + + if (players[i].mo->color) + { + if (players[i].mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, players[i].mo->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(skin, players[i].mo->color, GTC_CACHE); + } + else + 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]))) + 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; + splitflags |= V_HUDTRANS; + + for (i = 0; i < numlocalplayers; i++) + { + if (i == -1) + continue; // this doesn't interest us + + if (players[localplayers[i]].mo->skin) + skin = ((skin_t*)players[localplayers[i]].mo->skin)-skins; + else + skin = 0; + + if (players[localplayers[i]].mo->color) + { + if (players[localplayers[i]].mo->colorized) + colormap = R_GetTranslationColormap(TC_RAINBOW, players[localplayers[i]].mo->color, GTC_CACHE); + else + colormap = R_GetTranslationColormap(skin, players[localplayers[i]].mo->color, GTC_CACHE); + } + else + colormap = NULL; + + 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) + || (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_drawKartStartBulbs(void) +{ + const UINT8 start_animation[14] = { + 1, 2, 3, 4, 5, 6, 7, 8, + 7, 6, + 9, 10, 11, 12 + }; + + const UINT8 loop_animation[4] = { + 12, 13, 12, 14 + }; + + const UINT8 chillloop_animation[2] = { + 11, 12 + }; + + const UINT8 letters_order[10] = { + 0, 1, 2, 3, 4, 3, 1, 5, 6, 6 + }; + + const UINT8 letters_transparency[40] = { + 0, 2, 4, 6, 8, + 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, + 10, 8, 6, 4, 2 + }; + + fixed_t spacing = 24*FRACUNIT; + + fixed_t startx = (BASEVIDWIDTH/2)*FRACUNIT; + fixed_t starty = 48*FRACUNIT; + fixed_t x, y; + + UINT8 numperrow = numbulbs/2; + UINT8 i; + + if (r_splitscreen >= 1) + { + spacing /= 2; + starty /= 3; + + if (r_splitscreen > 1) + { + startx /= 2; + } + } + + startx += (spacing/2); + + if (numbulbs <= 10) + { + // No second row + numperrow = numbulbs; + } + else + { + if (numbulbs & 1) + { + numperrow++; + } + + starty -= (spacing/2); + } + + startx -= (spacing/2) * numperrow; + + x = startx; + y = starty; + + for (i = 0; i < numbulbs; i++) + { + UINT8 patchnum = 0; + INT32 bulbtic = (leveltime - introtime - TICRATE) - (bulbtime * i); + + if (i == numperrow) + { + y += spacing; + x = startx + (spacing/2); + } + + if (bulbtic > 0) + { + if (bulbtic < 14) + { + patchnum = start_animation[bulbtic]; + } + else + { + const INT32 length = (bulbtime * 3); + + bulbtic -= 14; + + if (bulbtic > length) + { + bulbtic -= length; + patchnum = chillloop_animation[bulbtic % 2]; + } + else + { + patchnum = loop_animation[bulbtic % 4]; + } + } + } + + V_DrawFixedPatch(x, y, FRACUNIT, V_SNAPTOTOP|V_SPLITSCREEN, + (r_splitscreen ? kp_prestartbulb_split[patchnum] : kp_prestartbulb[patchnum]), NULL); + x += spacing; + } + + x = 70*FRACUNIT; + y = starty; + + if (r_splitscreen == 1) + { + x = 106*FRACUNIT; + } + else if (r_splitscreen > 1) + { + x = 28*FRACUNIT; + } + + for (i = 0; i < 10; i++) + { + UINT8 patchnum = letters_order[i]; + INT32 transflag = letters_transparency[(leveltime - i) % 40]; + patch_t *patch = (r_splitscreen ? kp_prestartletters_split[patchnum] : kp_prestartletters[patchnum]); + + if (transflag >= 10) + ; + else + { + if (transflag != 0) + transflag = transflag << FF_TRANSSHIFT; + + V_DrawFixedPatch(x, y, FRACUNIT, V_SNAPTOTOP|V_SPLITSCREEN|transflag, patch, NULL); + } + + if (i < 9) + { + x += (SHORT(patch->width)) * FRACUNIT/2; + + patchnum = letters_order[i+1]; + patch = (r_splitscreen ? kp_prestartletters_split[patchnum] : kp_prestartletters[patchnum]); + x += (SHORT(patch->width)) * FRACUNIT/2; + + if (r_splitscreen) + x -= FRACUNIT; + } + } +} + +static void K_drawKartStartCountdown(void) +{ + INT32 pnum = 0; + + if (stplyr->karthud[khud_fault] != 0) + { + INT32 x, xval; + + if (r_splitscreen > 1) // 3/4p, stationary FIN + { + pnum += 2; + } + else if (r_splitscreen == 1) // wide splitscreen + { + pnum += 4; + } + + if ((leveltime % (2*5)) / 5) // blink + pnum += 1; + + if (r_splitscreen == 0) + { + x = ((vid.width<width)<karthud[khud_fault])*(xval > x ? xval : x))/TICRATE; + + V_DrawFixedPatch(x + (STCD_X<>1), + (STCD_Y<height)<<(FRACBITS-1)), + FRACUNIT, + V_SPLITSCREEN, kp_racefault[pnum], NULL); + } + else + { + V_DrawScaledPatch(STCD_X - (SHORT(kp_racefault[pnum]->width)/2), STCD_Y - (SHORT(kp_racefault[pnum]->height)/2), V_SPLITSCREEN, kp_racefault[pnum]); + } + } + else if (leveltime >= introtime && leveltime < starttime-(3*TICRATE)) + { + K_drawKartStartBulbs(); + } + else + { + + if (leveltime >= starttime-(2*TICRATE)) // 2 + pnum++; + if (leveltime >= starttime-TICRATE) // 1 + pnum++; + + if (leveltime >= starttime) // GO! + { + UINT8 i; + UINT8 numplayers = 0; + + pnum++; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator) + numplayers++; + + if (numplayers > 2) + break; + } + + if (numplayers == 2) + { + pnum++; // DUEL + } + } + + if ((leveltime % (2*5)) / 5) // blink + pnum += 5; + if (r_splitscreen) // splitscreen + pnum += 10; + + V_DrawScaledPatch(STCD_X - (SHORT(kp_startcountdown[pnum]->width)/2), STCD_Y - (SHORT(kp_startcountdown[pnum]->height)/2), V_SPLITSCREEN, kp_startcountdown[pnum]); + } +} + +static void K_drawKartFinish(void) +{ + INT32 pnum = 0, splitflags = V_SPLITSCREEN; + + if (!stplyr->karthud[khud_cardanimation] || stplyr->karthud[khud_cardanimation] >= 2*TICRATE) + return; + + if ((stplyr->karthud[khud_cardanimation] % (2*5)) / 5) // blink + pnum = 1; + + if (r_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 (r_splitscreen) // wide splitscreen + pnum += 4; + + x = ((vid.width<width)<karthud[khud_cardanimation])*(xval > x ? xval : x))/TICRATE; + + if (r_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 (r_splitscreen) + { + if ((r_splitscreen == 1 && stplyr == &players[displayplayers[1]]) + || (r_splitscreen > 1 && (stplyr == &players[displayplayers[2]] + || (stplyr == &players[displayplayers[3]] && r_splitscreen > 2)))) + { + y = 232-(stplyr->karthud[khud_cardanimation]/2); + splitflags = V_SNAPTOBOTTOM; + } + else + y = -32+(stplyr->karthud[khud_cardanimation]/2); + + if (r_splitscreen > 1) + { + scale /= 2; + + if (stplyr == &players[displayplayers[1]] + || (stplyr == &players[displayplayers[3]] && r_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) + { + patch_t *p = kp_battlecool; + + if (K_IsPlayerLosing(stplyr)) + p = kp_battlelose; + else if (stplyr->kartstuff[k_position] == 1) + p = kp_battlewin; + + 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 = (r_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 (r_splitscreen) + { + if (r_splitscreen > 1) + ty = (BASEVIDHEIGHT/4)+33; + if ((r_splitscreen == 1 && stplyr == &players[displayplayers[1]]) + || (stplyr == &players[displayplayers[2]] && r_splitscreen > 1) + || (stplyr == &players[displayplayers[3]] && r_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 = V_SNAPTOBOTTOM|V_SPLITSCREEN; + INT32 x = BASEVIDWIDTH/2, y = BASEVIDHEIGHT; + fixed_t scale; + UINT8 *colmap = NULL; + ticcmd_t *cmd = &stplyr->cmd; + + if (stplyr->spectator || !stplyr->mo || (stplyr->mo->drawflags & MFD_DONTDRAW)) + return; + + if (stplyr == &players[displayplayers[1]] && r_splitscreen) + { pn = pnum[1]; tn = turn[1]; dr = drift[1]; } + else if (stplyr == &players[displayplayers[2]] && r_splitscreen > 1) + { pn = pnum[2]; tn = turn[2]; dr = drift[2]; } + else if (stplyr == &players[displayplayers[3]] && r_splitscreen > 2) + { pn = pnum[3]; tn = turn[3]; dr = drift[3]; } + else + { pn = pnum[0]; tn = turn[0]; dr = drift[0]; } + + if (r_splitscreen) + { + y >>= 1; + if (r_splitscreen > 1) + x >>= 1; + } + + { + if (stplyr->speed < (20*stplyr->mo->scale) && (leveltime & 1) && !r_splitscreen) + y++; + + if (stplyr->mo->drawflags & MFD_TRANSMASK) + splitflags |= ((stplyr->mo->drawflags & MFD_TRANSMASK) >> MFD_TRANSSHIFT) << FF_TRANSSHIFT; + 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 (r_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 (r_splitscreen) + scale = FRACUNIT/2; + else + scale = FRACUNIT; + + if (stplyr->mo) + { + UINT8 driftcolor = K_DriftSparkColor(stplyr, stplyr->kartstuff[k_driftcharge]); + 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 (r_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 (!r_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 (!r_splitscreen) + y += yoffs; + + + if ((leveltime & 1) && (driftcolor != SKINCOLOR_NONE)) // drift sparks! + colmap = R_GetTranslationColormap(TC_RAINBOW, driftcolor, GTC_CACHE); + else if (stplyr->mo->colorized && stplyr->mo->color) // invincibility/grow/shrink! + 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]] && r_splitscreen) + { pnum[1] = pn; turn[1] = tn; drift[1] = dr; } + else if (stplyr == &players[displayplayers[2]] && r_splitscreen > 1) + { pnum[2] = pn; turn[2] = tn; drift[2] = dr; } + else if (stplyr == &players[displayplayers[3]] && r_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_SLIDEIN|V_SNAPTOBOTTOM|V_SNAPTORIGHT, "FREE PLAY"); +} + +static void +Draw_party_ping (int ss, INT32 snap) +{ + HU_drawMiniPing(0, 0, playerpingtable[displayplayers[ss]], V_HUDTRANS|V_SPLITSCREEN|V_SNAPTOTOP|snap); +} + +static void +K_drawMiniPing (void) +{ + UINT32 f = V_SNAPTORIGHT; + UINT8 i; + + if (!cv_showping.value) + { + return; + } + + for (i = 0; i <= r_splitscreen; i++) + { + if (stplyr == &players[displayplayers[i]]) + { + if (r_splitscreen > 1 && !(i & 1)) + { + f = V_SNAPTOLEFT; + } + + Draw_party_ping(i, f); + break; + } + } +} + +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_bubbleshield[1], + kp_flameshield[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; + } + + if (stplyr->bot && stplyr->botvars.rival) + { + // Rival has better odds :) + pdis = (15 * pdis) / 14; + } + + 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, stplyr->bot, (stplyr->bot && stplyr->botvars.rival)); + if (itemodds <= 0) + continue; + + V_DrawScaledPatch(x, y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, items[i]); + V_DrawThinString(x+11, y+31, V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP, va("%d", itemodds)); + + // 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_SLIDEIN|V_SNAPTOTOP, va("x%d", amount)); + } + + x += 32; + if (x >= 297) + { + x = -9; + y += 32; + } + } + + V_DrawString(0, 0, V_HUDTRANS|V_SLIDEIN|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, 166, 0, va("'Best' Waypoint ID: %d", K_GetWaypointID(stplyr->nextwaypoint))); + V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish)); + } +} + +void K_drawKartHUD(void) +{ + boolean isfreeplay = false; + boolean battlefullscreen = false; + boolean freecam = demo.freecam; //disable some hud elements w/ freecam + 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 <= r_splitscreen; i++) + { + if (stplyr == &players[displayplayers[i]] && !camera[i].chase && !freecam) + 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 || r_splitscreen)) + { + // Draw the CHECK indicator before the other items, so it's overlapped by everything else +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_check)) // delete lua when? +#endif + if (cv_kartcheck.value && !splitscreen && !players[displayplayers[0]].exiting && !freecam) + K_drawKartPlayerCheck(); + + // nametags +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_names)) +#endif + K_drawKartNameTags(); + + // 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 && !freecam) + { +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_battlefullscreen)) +#endif + K_drawBattleFullscreen(); + return; + } + + // Draw the item window +#ifdef HAVE_BLUA + if (LUA_HudEnabled(hud_item) && !freecam) +#endif + K_drawKartItem(); + + // If not splitscreen, draw... + if (!r_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 && !demo.freecam) // Bottom of the screen elements, don't need in spectate mode + { + // Draw the speedometer + if (cv_kartspeedometer.value && !r_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 (r_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 >= introtime + && leveltime < starttime+TICRATE) + { + K_drawKartStartCountdown(); + } + else if (racecountdown && (!r_splitscreen || !stplyr->exiting)) + { + char *countstr = va("%d", racecountdown/TICRATE); + + if (r_splitscreen > 1) + V_DrawCenteredString(BASEVIDWIDTH/4, LAPS_Y+1, V_SPLITSCREEN, countstr); + else + { + INT32 karlen = strlen(countstr)*6; // half of 12 + V_DrawKartString((BASEVIDWIDTH/2)-karlen, LAPS_Y+3, V_SPLITSCREEN, countstr); + } + } + + // Race overlays + if (G_RaceGametype() && !freecam) + { + if (stplyr->exiting) + K_drawKartFinish(); + else if (stplyr->karthud[khud_lapanimation] && !r_splitscreen) + K_drawLapStartAnim(); + } + + if (modeattacking || freecam) // everything after here is MP and debug only + return; + + if (G_BattleGametype() && !r_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 (r_splitscreen == 0 && stplyr->kartstuff[k_wrongway] && ((leveltime / 8) & 1)) + { + V_DrawCenteredString(BASEVIDWIDTH>>1, 176, V_REDMAP|V_SNAPTOBOTTOM, "WRONG WAY"); + } + + if (netgame && r_splitscreen) + { + K_drawMiniPing(); + } + + 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_hud.h b/src/k_hud.h new file mode 100644 index 000000000..27f21bd23 --- /dev/null +++ b/src/k_hud.h @@ -0,0 +1,28 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_hud.h +/// \brief HUD drawing functions exclusive to Kart + +#include "doomtype.h" +#include "doomstat.h" + +#ifndef __K_HUD__ +#define __K_HUD__ + +#define RINGANIM_NUMFRAMES 10 +#define RINGANIM_DELAYMAX 5 + +void K_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 dupy); +const char *K_GetItemPatch(UINT8 item, boolean tiny); +void K_LoadKartHUDGraphics(void); +void K_drawKartHUD(void); +void K_drawKartFreePlay(UINT32 flashtime); +void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode); + +#endif diff --git a/src/k_kart.c b/src/k_kart.c index d3687bc47..76caa85ef 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8,6 +8,7 @@ #include "k_battle.h" #include "k_pwrlv.h" #include "k_color.h" +#include "k_respawn.h" #include "doomdef.h" #include "hu_stuff.h" #include "g_game.h" @@ -26,9 +27,11 @@ #include "f_finale.h" #include "lua_hud.h" // For Lua hud checks #include "lua_hook.h" // For MobjDamage and ShouldDamage +#include "m_cheat.h" // objectplacing #include "k_waypoint.h" #include "k_bot.h" +#include "k_hud.h" // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // gamespeed is cc (0 for easy, 1 for normal, 2 for hard) @@ -39,6 +42,22 @@ // indirectitemcooldown is timer before anyone's allowed another Shrink/SPB // mapreset is set when enough players fill an empty server +UINT16 K_GetPlayerDontDrawFlag(player_t *player) +{ + UINT16 flag = 0; + + if (player == &players[displayplayers[0]]) + flag = MFD_DONTDRAWP1; + else if (r_splitscreen >= 1 && player == &players[displayplayers[1]]) + flag = MFD_DONTDRAWP2; + else if (r_splitscreen >= 2 && player == &players[displayplayers[2]]) + flag = MFD_DONTDRAWP3; + else if (r_splitscreen >= 3 && player == &players[displayplayers[3]]) + flag = MFD_DONTDRAWP4; + + return flag; +} + player_t *K_GetItemBoxPlayer(mobj_t *mobj) { fixed_t closest = INT32_MAX; @@ -77,6 +96,48 @@ player_t *K_GetItemBoxPlayer(mobj_t *mobj) return player; } +// Angle reflection used by springs & speed pads +angle_t K_ReflectAngle(angle_t yourangle, angle_t theirangle, fixed_t yourspeed, fixed_t theirspeed) +{ + INT32 angoffset; + boolean subtract = false; + + angoffset = yourangle - theirangle; + + if ((angle_t)angoffset > ANGLE_180) + { + // Flip on wrong side + angoffset = InvAngle((angle_t)angoffset); + subtract = !subtract; + } + + // Fix going directly against the spring's angle sending you the wrong way + if ((angle_t)angoffset > ANGLE_90) + { + angoffset = ANGLE_180 - angoffset; + } + + // Offset is reduced to cap it (90 / 2 = max of 45 degrees) + angoffset /= 2; + + // Reduce further based on how slow your speed is compared to the spring's speed + // (set both to 0 to ignore this) + if (theirspeed != 0 && yourspeed != 0) + { + if (theirspeed > yourspeed) + { + angoffset = FixedDiv(angoffset, FixedDiv(theirspeed, yourspeed)); + } + } + + if (subtract) + angoffset = (signed)(theirangle) - angoffset; + else + angoffset = (signed)(theirangle) + angoffset; + + return (angle_t)angoffset; +} + //{ SRB2kart Net Variables void K_RegisterKartStuff(void) @@ -101,6 +162,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_superring); CV_RegisterVar(&cv_kitchensink); + CV_RegisterVar(&cv_dualsneaker); CV_RegisterVar(&cv_triplesneaker); CV_RegisterVar(&cv_triplebanana); CV_RegisterVar(&cv_decabanana); @@ -200,6 +262,7 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_pogospring, &cv_superring, &cv_kitchensink, + &cv_dualsneaker, &cv_triplesneaker, &cv_triplebanana, &cv_decabanana, @@ -216,23 +279,24 @@ static INT32 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = //P-Odds 0 1 2 3 4 5 6 7 /*Sneaker*/ { 0, 0, 2, 4, 6, 0, 0, 0 }, // Sneaker /*Rocket Sneaker*/ { 0, 0, 0, 0, 0, 2, 4, 6 }, // Rocket Sneaker - /*Invincibility*/ { 0, 0, 0, 0, 1, 4, 7, 9 }, // Invincibility - /*Banana*/ { 7, 3, 2, 0, 0, 0, 0, 0 }, // Banana + /*Invincibility*/ { 0, 0, 0, 0, 2, 4, 6, 9 }, // Invincibility + /*Banana*/ { 7, 3, 1, 0, 0, 0, 0, 0 }, // Banana /*Eggman Monitor*/ { 3, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor - /*Orbinaut*/ { 7, 4, 3, 2, 0, 0, 0, 0 }, // Orbinaut + /*Orbinaut*/ { 7, 4, 2, 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 + /*Mine*/ { 0, 2, 3, 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, 1, 2, 3, 0, 0 }, // Grow /*Shrink*/ { 0, 0, 0, 0, 0, 0, 2, 0 }, // Shrink /*Thunder Shield*/ { 1, 2, 0, 0, 0, 0, 0, 0 }, // Thunder Shield - /*Bubble Shield*/ { 0, 2, 3, 3, 1, 0, 0, 0 }, // Bubble Shield + /*Bubble Shield*/ { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield /*Flame Shield*/ { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield - /*Hyudoro*/ { 0, 0, 0, 1, 2, 0, 0, 0 }, // Hyudoro + /*Hyudoro*/ { 0, 0, 0, 1, 1, 0, 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 x2*/ { 0, 0, 2, 2, 1, 0, 0, 0 }, // Sneaker x2 /*Sneaker x3*/ { 0, 0, 0, 2, 6,10, 5, 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 @@ -263,6 +327,7 @@ static INT32 K_KartItemOddsBattle[NUMKARTRESULTS-1][6] = /*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 x2*/ { 0, 0, 0, 0, 0, 0 }, // Sneaker x2 /*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 @@ -305,6 +370,10 @@ static void K_KartGetItemResult(player_t *player, SINT8 getitem) switch (getitem) { // Special roulettes first, then the generic ones are handled by default + case KRITEM_DUALSNEAKER: // Sneaker x2 + player->kartstuff[k_itemtype] = KITEM_SNEAKER; + player->kartstuff[k_itemamount] = 2; + break; case KRITEM_TRIPLESNEAKER: // Sneaker x3 player->kartstuff[k_itemtype] = KITEM_SNEAKER; player->kartstuff[k_itemamount] = 3; @@ -350,7 +419,7 @@ static void K_KartGetItemResult(player_t *player, SINT8 getitem) \return void */ -static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush, boolean bot) +INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush, boolean bot, boolean rival) { INT32 newodds; INT32 i; @@ -428,10 +497,12 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp #define POWERITEMODDS(odds) {\ if (franticitems) \ - odds <<= 1; \ - odds = FixedMul(odds<> FRACBITS; \ + odds *= 2; \ + if (rival) \ + odds *= 2; \ + odds = FixedMul(odds * FRACUNIT, FRACUNIT + ((PLAYERSCALING * FRACUNIT) / 25)) / FRACUNIT; \ if (mashed > 0) \ - odds = FixedDiv(odds<> FRACBITS; \ + odds = FixedDiv(odds * FRACUNIT, FRACUNIT + mashed) / FRACUNIT; \ } #define COOLDOWNONSTART (leveltime < (30*TICRATE)+starttime) @@ -462,6 +533,7 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp case KITEM_THUNDERSHIELD: case KITEM_BUBBLESHIELD: case KITEM_FLAMESHIELD: + case KRITEM_DUALSNEAKER: case KRITEM_TRIPLESNEAKER: case KRITEM_TRIPLEBANANA: case KRITEM_TENFOLDBANANA: @@ -544,7 +616,7 @@ static INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean sp //{ SRB2kart Roulette Code - Distance Based, yes waypoints -static UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) +UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush) { UINT8 i; UINT8 n = 0; @@ -567,7 +639,7 @@ static UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 for (j = 1; j < NUMKARTRESULTS; j++) { - if (K_KartGetItemOdds(i, j, mashed, spbrush, player->bot) > 0) + if (K_KartGetItemOdds(i, j, mashed, spbrush, player->bot, (player->bot && player->botvars.rival)) > 0) { available = true; break; @@ -729,7 +801,9 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) pdis = FixedDiv(pdis * FRACUNIT, mapobjectscale) / FRACUNIT; 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 { @@ -737,6 +811,12 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) spbrush = true; } + if (player->bot && player->botvars.rival) + { + // Rival has better odds :) + pdis = (15 * pdis) / 14; + } + pdis = ((28 + (8-pingame)) * pdis) / 28; // scale with player count // SPECIAL CASE No. 1: @@ -862,7 +942,7 @@ static void K_KartItemRoulette(player_t *player, ticcmd_t *cmd) useodds = K_FindUseodds(player, mashed, pdis, bestbumper, spbrush); for (i = 1; i < NUMKARTRESULTS; i++) - spawnchance[i] = (totalspawnchance += K_KartGetItemOdds(useodds, i, mashed, spbrush, player->bot)); + spawnchance[i] = (totalspawnchance += K_KartGetItemOdds(useodds, i, mashed, spbrush, player->bot, (player->bot && player->botvars.rival))); // Award the player whatever power is rolled if (totalspawnchance > 0) @@ -1004,8 +1084,8 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) || (mobj2->player && mobj2->player->playerstate != PST_LIVE)) return; - if ((mobj1->player && mobj1->player->kartstuff[k_respawn]) - || (mobj2->player && mobj2->player->kartstuff[k_respawn])) + if ((mobj1->player && mobj1->player->respawn.state != RESPAWNST_NONE) + || (mobj2->player && mobj2->player->respawn.state != RESPAWNST_NONE)) return; { // Don't bump if you're flashing @@ -1061,10 +1141,13 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) { // 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); + fixed_t nx, ny; dist = dist ? dist : 1; + + nx = FixedDiv(distx, dist); + ny = FixedDiv(disty, dist); + distx = FixedMul(mobj1->radius+mobj2->radius, nx); disty = FixedMul(mobj1->radius+mobj2->radius, ny); @@ -1140,6 +1223,7 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) mobj1->player->rmomx = mobj1->momx - mobj1->player->cmomx; mobj1->player->rmomy = mobj1->momy - mobj1->player->cmomy; mobj1->player->kartstuff[k_justbumped] = bumptime; + mobj1->player->kartstuff[k_spindash] = 0; if (mobj1->player->kartstuff[k_spinouttimer]) { @@ -1165,6 +1249,7 @@ void K_KartBouncing(mobj_t *mobj1, mobj_t *mobj2, boolean bounce, boolean solid) mobj2->player->rmomx = mobj2->momx - mobj2->player->cmomx; mobj2->player->rmomy = mobj2->momy - mobj2->player->cmomy; mobj2->player->kartstuff[k_justbumped] = bumptime; + mobj2->player->kartstuff[k_spindash] = 0; if (mobj2->player->kartstuff[k_spinouttimer]) { @@ -1274,15 +1359,19 @@ static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t cur 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; + band->drawflags |= MFD_SHADOW; + + band->drawflags |= MFD_DONTDRAW & ~(K_GetPlayerDontDrawFlag(player) | K_GetPlayerDontDrawFlag(victim)); } curx += stepx; @@ -1353,6 +1442,10 @@ static void K_UpdateDraft(player_t *player) if (players[i].speed < 20*players[i].mo->scale) continue; + // No tethering off of the guy who got the starting bonus :P + if (players[i].kartstuff[k_startboost] > 0) + 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); @@ -1503,7 +1596,6 @@ void K_FlipFromObject(mobj_t *mo, mobj_t *master) 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 @@ -1511,14 +1603,22 @@ void K_MatchGenericExtraFlags(mobj_t *mo, mobj_t *master) 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); + mo->drawflags = (master->drawflags & MFD_DONTDRAW); } -static void K_SpawnDashDustRelease(player_t *player) +// same as above, but does not adjust Z height when flipping +void K_GenericExtraFlagsNoZAdjust(mobj_t *mo, mobj_t *master) +{ + // flipping + mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP)|(master->eflags & MFE_VERTICALFLIP); + mo->flags2 = (mo->flags2 & ~MF2_OBJECTFLIP)|(master->flags2 & MF2_OBJECTFLIP); + + // visibility (usually for hyudoro) + mo->drawflags = (master->drawflags & MFD_DONTDRAW); +} + + +void K_SpawnDashDustRelease(player_t *player) { fixed_t newx; fixed_t newy; @@ -1533,7 +1633,7 @@ static void K_SpawnDashDustRelease(player_t *player) if (!P_IsObjectOnGround(player->mo)) return; - if (!player->speed && !player->kartstuff[k_startboost]) + if (!player->speed && !player->kartstuff[k_startboost] && !player->kartstuff[k_spindash]) return; travelangle = player->mo->angle; @@ -1574,389 +1674,54 @@ static void K_SpawnBrakeDriftSparks(player_t *player) // Be sure to update the m P_SetTarget(&sparks->target, player->mo); P_SetScale(sparks, (sparks->destscale = player->mo->scale)); K_MatchGenericExtraFlags(sparks, player->mo); - sparks->flags2 |= MF2_DONTDRAW; + sparks->drawflags |= MFD_DONTDRAW; } -/** \brief Preps a player to respawn - - \param player player to respawn - - \return void -*/ -void K_DoIngameRespawn(player_t *player) +static fixed_t K_RandomFlip(fixed_t f) { - 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; - UINT8 starttype = 0; - - if (gametyperules & GTR_TEAMSTARTS) - { - if (player->ctfteam == 1) - { - starttype = 3; - numstarts = numredctfstarts; - } - else if (player->ctfteam == 2) - { - starttype = 4; - numstarts = numbluectfstarts; - } - else - { - starttype = 2; - numstarts = numdmstarts; - } - } - else if (gametyperules & GTR_BATTLESTARTS) - { - starttype = 2; - numstarts = numdmstarts; - } - else - { - starttype = 1; - numstarts = numcoopstarts; - } - - if (starttype > 0 && numstarts > 0) - { - UINT8 i = 0; - - for (i = 0; i < numstarts; i++) - { - UINT32 dist = UINT32_MAX; - mapthing_t *checkstart = NULL; - - switch (starttype) - { - case 4: - checkstart = bluectfstarts[i]; - break; - case 3: - checkstart = redctfstarts[i]; - break; - case 2: - checkstart = deathmatchstarts[i]; - break; - case 1: - checkstart = playerstarts[i]; - break; - } - - I_Assert(checkstart != NULL); - - 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; + return ( ( leveltime & 1 ) ? f : -f ); } -/** \brief Calculates the respawn timer and drop-boosting - - \param player player object passed from K_KartPlayerThink - - \return void -*/ -void K_RespawnChecker(player_t *player) +void K_SpawnDriftBoostClip(player_t *player) { - ticcmd_t *cmd = &player->cmd; + mobj_t *clip; + fixed_t scale = 115*FRACUNIT/100; + fixed_t z; - if (player->spectator) - return; + if (( player->mo->eflags & MFE_VERTICALFLIP )) + z = player->mo->z; + else + z = player->mo->z + player->mo->height; - if (player->kartstuff[k_respawn] > 1) - { - fixed_t destx = 0, desty = 0, destz = 0; + clip = P_SpawnMobj(player->mo->x, player->mo->y, z, MT_DRIFTCLIP); - player->mo->momx = player->mo->momy = player->mo->momz = 0; - player->powers[pw_flashing] = 2; - player->powers[pw_nocontrol] = 2; + P_SetTarget(&clip->target, player->mo); + P_SetScale(clip, ( clip->destscale = FixedMul(scale, player->mo->scale) )); + K_MatchGenericExtraFlags(clip, player->mo); - if (leveltime % 8 == 0 && !mapreset) - S_StartSound(player->mo, sfx_s3kcas); + clip->fuse = 105; + clip->momz = 7 * P_MobjFlip(clip) * clip->scale; - destx = (player->starpostx << FRACBITS); - desty = (player->starposty << FRACBITS); - destz = (player->starpostz << FRACBITS); + if (player->mo->momz > 0) + clip->momz += player->mo->momz; - if (player->kartstuff[k_starpostflip]) - { - // This variable is set from the settings of the best waypoint, thus this waypoint is FLIPPED as well. - // So we should flip the player in advance for it as well. - player->mo->flags2 |= MF2_OBJECTFLIP; - player->mo->eflags |= MFE_VERTICALFLIP; - destz -= (128 * mapobjectscale) + (player->mo->height); - } - else - { - // Ditto, but this waypoint isn't flipped, so make sure the player also isn't flipped! - player->mo->flags2 &= ~MF2_OBJECTFLIP; - player->mo->eflags &= ~MFE_VERTICALFLIP; - destz += (128 * mapobjectscale); - } + P_InstaThrust(clip, player->mo->angle + + K_RandomFlip(P_RandomRange(FRACUNIT/2, FRACUNIT)), + FixedMul(scale, player->speed)); +} - 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); +void K_SpawnDriftBoostClipSpark(mobj_t *clip) +{ + mobj_t *spark; - 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; + spark = P_SpawnMobj(clip->x, clip->y, clip->z, MT_DRIFTCLIPSPARK); - // 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_SetTarget(&spark->target, clip); + P_SetScale(spark, ( spark->destscale = clip->scale )); + K_MatchGenericExtraFlags(spark, clip); - 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 * player->mo->scale); - newy = player->mo->y + P_ReturnThrustY(player->mo, newangle, 31 * player->mo->scale); - if (player->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 * player->mo->scale) * P_MobjFlip(player->mo); - P_SetScale(laser, (laser->destscale = player->mo->scale)); - } - } - } - } - } - 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_spinouttimer]) // Lat: Since we're letting players spin out on respawn, don't let them charge a dropdash in this state. (It wouldn't work anyway) - 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->kartstuff[k_respawn] = 0; - - //P_PlayRinglossSound(player->mo); - P_PlayerRingBurst(player, 3); - - if (gametyperules & GTR_BUMPERS) - { - 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(); - } - } - } + spark->momx = clip->momx/2; + spark->momy = clip->momx/2; } /** \brief Handles the state changing for moving players, moved here to eliminate duplicate code @@ -2226,6 +1991,13 @@ void K_PlayPainSound(mobj_t *source) void K_PlayHitEmSound(mobj_t *source) { + + if (source->player->follower) + { + follower_t fl = followers[source->player->followerskin]; + source->player->follower->movecount = fl.hitconfirmtime; // movecount is used to play the hitconfirm animation for followers. + } + if (cv_kartvoices.value) S_StartSound(source, sfx_khitem); else @@ -2262,7 +2034,7 @@ void K_MomentumToFacing(player_t *player) boolean K_ApplyOffroad(player_t *player) { - if (player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_hyudorotimer] || EITHERSNEAKER(player)) + if (player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_hyudorotimer] || player->kartstuff[k_sneakertimer]) return false; return true; } @@ -2273,11 +2045,38 @@ static fixed_t K_FlameShieldDashVar(INT32 val) return (3*FRACUNIT/4) + (((val * FRACUNIT) / TICRATE) / 2); } -// sets k_boostpower, k_speedboost, and k_accelboost to whatever we need it to be +tic_t K_GetSpindashChargeTime(player_t *player) +{ + // more charge time for higher speed + // Tails = 2s, Mighty = 3s, Fang = 4s, Metal = 4s + return (player->kartspeed + 4) * (TICRATE/3); +} + +fixed_t K_GetSpindashChargeSpeed(player_t *player) +{ + // more speed for higher weight & speed + // Tails = +6.25%, Fang = +20.31%, Mighty = +20.31%, Metal = +25% + // (can be higher than this value when overcharged) + return (player->kartspeed + player->kartweight) * (FRACUNIT/64); +} + +// Light weights have stronger boost stacking -- aka, better metabolism than heavies XD +#define METABOLISM + +// sets k_boostpower, k_speedboost, k_accelboost, and k_handleboost to whatever we need it to be static void K_GetKartBoostPower(player_t *player) { +#ifdef METABOLISM + const fixed_t maxmetabolismincrease = FRACUNIT/2; + const fixed_t metabolism = FRACUNIT - ((9-player->kartweight) * maxmetabolismincrease / 8); +#endif // METABOLISM + + // v2 almost broke sliptiding when it fixed turning bugs! + // This value is fine-tuned to feel like v1 again without reverting any of those changes. + const fixed_t sliptidehandling = 7*FRACUNIT/10; + fixed_t boostpower = FRACUNIT; - fixed_t speedboost = 0, accelboost = 0; + fixed_t speedboost = 0, accelboost = 0, handleboost = 0; UINT8 numboosts = 0; if (player->kartstuff[k_spinouttimer] && player->kartstuff[k_wipeoutslow] == 1) // Slow down after you've been bumped @@ -2293,35 +2092,86 @@ static void K_GetKartBoostPower(player_t *player) if (player->kartstuff[k_bananadrag] > TICRATE) boostpower = (4*boostpower)/5; -#define ADDBOOST(s,a) { \ +#ifdef METABOLISM + +#define ADDBOOST(s,a,h) { \ numboosts++; \ - speedboost += (s) / numboosts; \ - accelboost += (a) / numboosts; \ + speedboost += FixedDiv(s, FRACUNIT + (metabolism * (numboosts-1))); \ + accelboost += FixedDiv(a, FRACUNIT + (metabolism * (numboosts-1))); \ + handleboost = max(h, handleboost); \ } - if (player->kartstuff[k_levelbooster]) // Level boosters - ADDBOOST(FRACUNIT/2, 8*FRACUNIT); // + 50% top speed, + 800% acceleration +#else + +#define ADDBOOST(s,a,h) { \ + numboosts++; \ + speedboost += s / numboosts; \ + accelboost += a / numboosts; \ + handleboost = max(h, handleboost); \ +} + +#endif // METABOLISM if (player->kartstuff[k_sneakertimer]) // Sneaker - ADDBOOST(FRACUNIT/2, 8*FRACUNIT); // + 50% top speed, + 800% acceleration + { + UINT8 i; + for (i = 0; i < player->kartstuff[k_numsneakers]; i++) + { + ADDBOOST(FRACUNIT/2, 8*FRACUNIT, sliptidehandling); // + 50% top speed, + 800% acceleration, +70% handling + } + } if (player->kartstuff[k_invincibilitytimer]) // Invincibility - ADDBOOST((3*FRACUNIT)/8, 3*FRACUNIT); // + 37.5% top speed, + 300% acceleration + { + ADDBOOST(3*FRACUNIT/8, 3*FRACUNIT, sliptidehandling/3); // + 37.5% top speed, + 300% acceleration, +23% handling + } + + if (player->kartstuff[k_growshrinktimer] > 0) // Grow + { + ADDBOOST(0, 0, sliptidehandling/3); // + 0% top speed, + 0% acceleration, +23% handling + } if (player->kartstuff[k_flamedash]) // Flame Shield dash - ADDBOOST(K_FlameShieldDashVar(player->kartstuff[k_flamedash]), 3*FRACUNIT); // + infinite top speed, + 300% acceleration + { + fixed_t dash = K_FlameShieldDashVar(player->kartstuff[k_flamedash]); + ADDBOOST( + dash, // + infinite top speed + 3*FRACUNIT, // + 300% acceleration + FixedMul(FixedDiv(dash, FRACUNIT/2), sliptidehandling/3) // + infinite handling + ); + } + + if (player->kartstuff[k_spindashboost]) // Spindash boost + { + const fixed_t MAXCHARGESPEED = K_GetSpindashChargeSpeed(player); + + // character & charge dependent + ADDBOOST( + FixedMul(MAXCHARGESPEED, player->kartstuff[k_spindashspeed]), // + 0 to K_GetSpindashChargeSpeed()% top speed + (4*FRACUNIT) + (36*player->kartstuff[k_spindashspeed]), // + 400% to 4000% acceleration + 0 // + 0% handling + ); + } if (player->kartstuff[k_startboost]) // Startup Boost - ADDBOOST(FRACUNIT/4, 6*FRACUNIT); // + 25% top speed, + 600% acceleration + { + ADDBOOST(FRACUNIT/2, 4*FRACUNIT, 0); // + 50% top speed, + 400% acceleration, +0% handling + } if (player->kartstuff[k_driftboost]) // Drift Boost - ADDBOOST(FRACUNIT/4, 4*FRACUNIT); // + 25% top speed, + 400% acceleration + { + ADDBOOST(FRACUNIT/4, 4*FRACUNIT, 0); // + 25% top speed, + 400% acceleration, +0% handling + } if (player->kartstuff[k_ringboost]) // Ring Boost - ADDBOOST(FRACUNIT/5, 4*FRACUNIT); // + 20% top speed, + 400% acceleration + { + ADDBOOST(FRACUNIT/5, 4*FRACUNIT, 0); // + 20% top speed, + 400% acceleration, +0% handling + } if (player->kartstuff[k_eggmanexplode]) // Ready-to-explode - ADDBOOST(FRACUNIT/5, FRACUNIT); // + 20% top speed, + 100% acceleration + { + ADDBOOST(3*FRACUNIT/20, FRACUNIT, 0); // + 15% top speed, + 100% acceleration, +0% handling + } if (player->kartstuff[k_draftpower] > 0) // Drafting { @@ -2334,11 +2184,17 @@ static void K_GetKartBoostPower(player_t *player) // 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_handleboost] = handleboost; + player->kartstuff[k_numboosts] = numboosts; } @@ -2370,6 +2226,12 @@ fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower) { // Give top speed a buff for bots, since it's a fairly weak stat without drifting fixed_t speedmul = ((kartspeed-1) * FRACUNIT / 8) / 10; // +10% for speed 9 + + if (player->botvars.rival == true) + { + speedmul += FRACUNIT/10; // +10% for rival + } + finalspeed = FixedMul(finalspeed, FRACUNIT + speedmul); } @@ -2400,9 +2262,12 @@ fixed_t K_GetKartAccel(player_t *player) //k_accel += 3 * (9 - kartspeed); // 36 - 60 k_accel += 4 * (9 - kartspeed); // 32 - 64 + if (K_PlayerUsesBotMovement(player)) { - k_accel = FixedMul(k_accel, K_BotRubberband(player)); + // Rubberbanding acceleration is waekened since it makes hits feel more meaningful + fixed_t rubberband = K_BotRubberband(player) - FRACUNIT; + k_accel = FixedMul(k_accel, FRACUNIT + (rubberband/2)); } return FixedMul(k_accel, FRACUNIT+player->kartstuff[k_accelboost]); @@ -2415,29 +2280,59 @@ UINT16 K_GetKartFlashing(player_t *player) if (!player) return tics; - if (gametyperules & GTR_BUMPERS) - tics *= 2; + tics += (tics/8) * (player->kartspeed); - tics += (flashingtics/8) * (player->kartspeed); + if (G_BattleGametype()) + tics *= 2; return tics; } -fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove) +SINT8 K_GetForwardMove(player_t *player) { - fixed_t accelmax = 4000; + SINT8 forwardmove = player->cmd.forwardmove; + + if ((player->pflags & PF_STASIS) || (player->pflags & PF_SLIDING)) + { + return 0; + } + + if (player->kartstuff[k_sneakertimer] || player->kartstuff[k_spindashboost]) + { + return MAXPLMOVE; + } + + if (player->kartstuff[k_spinouttimer] || K_PlayerEBrake(player)) + { + return 0; + } + + return forwardmove; +} + +fixed_t K_3dKartMovement(player_t *player, boolean onground) +{ + const fixed_t accelmax = 4000; + const fixed_t p_speed = K_GetKartSpeed(player, true); + const fixed_t p_accel = K_GetKartAccel(player); fixed_t newspeed, oldspeed, finalspeed; - fixed_t p_speed = K_GetKartSpeed(player, true); - fixed_t p_accel = K_GetKartAccel(player); + fixed_t movemul = FRACUNIT; + fixed_t orig = ORIG_FRICTION; + SINT8 forwardmove = K_GetForwardMove(player); if (!onground) return 0; // If the player isn't on the ground, there is no change in speed + if (K_PlayerUsesBotMovement(player)) + { + orig = K_BotFrictionRubberband(player, ORIG_FRICTION); + } + // 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); + newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), orig); if (player->kartstuff[k_pogospring]) // Pogo Spring minimum/maximum thrust { @@ -2452,26 +2347,20 @@ fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove } finalspeed = newspeed - oldspeed; + movemul = abs(forwardmove * FRACUNIT) / 50; // forwardmove is: // 50 while accelerating, - // 25 while clutching, // 0 with no gas, and // -25 when only braking. - - if (EITHERSNEAKER(player)) - forwardmove = 50; - - 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; + if (forwardmove >= 0) + { + finalspeed = FixedMul(finalspeed, movemul); + } + else + { + finalspeed = FixedMul(-mapobjectscale/2, movemul); + } return finalspeed; } @@ -2536,7 +2425,7 @@ void K_SpinPlayer(player_t *player, mobj_t *source, INT32 type, mobj_t *inflicto K_PlayHitEmSound(source); player->kartstuff[k_sneakertimer] = 0; - //player->kartstuff[k_levelbooster] = 0; + player->kartstuff[k_numsneakers] = 0; player->kartstuff[k_driftboost] = 0; player->kartstuff[k_ringboost] = 0; @@ -2677,7 +2566,7 @@ void K_SquishPlayer(player_t *player, mobj_t *source, mobj_t *inflictor) return; player->kartstuff[k_sneakertimer] = 0; - player->kartstuff[k_levelbooster] = 0; + player->kartstuff[k_numsneakers] = 0; player->kartstuff[k_driftboost] = 0; player->kartstuff[k_ringboost] = 0; @@ -2797,7 +2686,7 @@ void K_ExplodePlayer(player_t *player, mobj_t *source, mobj_t *inflictor) // A b player->mo->momx = player->mo->momy = 0; player->kartstuff[k_sneakertimer] = 0; - player->kartstuff[k_levelbooster] = 0; + player->kartstuff[k_numsneakers] = 0; player->kartstuff[k_driftboost] = 0; player->kartstuff[k_ringboost] = 0; @@ -3159,13 +3048,10 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I 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); + K_FlipFromObject(th, source); + th->flags2 |= flags2; th->threshold = 10; @@ -3360,6 +3246,7 @@ static void K_SpawnDriftSparks(player_t *player) { // transition P_SetScale(spark, (spark->destscale = spark->scale*3/2)); + S_StartSound(player->mo, sfx_cock); } else { @@ -3450,6 +3337,9 @@ static void K_SpawnAIZDust(player_t *player) if (!P_IsObjectOnGround(player->mo)) return; + if (player->speed <= K_GetKartSpeed(player, false)) + return; + travelangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); //S_StartSound(player->mo, sfx_s3k47); @@ -3601,7 +3491,7 @@ void K_SpawnWipeoutTrail(mobj_t *mo, boolean translucent) } if (translucent) - dust->flags2 |= MF2_SHADOW; + dust->drawflags |= MFD_SHADOW; } void K_SpawnDraftDust(mobj_t *mo) @@ -3710,7 +3600,7 @@ void K_DriftDustHandling(mobj_t *spawner) if (spawner->player->speed < 5*spawner->scale) return; - if (spawner->player->cmd.forwardmove < 0) + if (K_GetForwardMove(spawner->player) < 0) playerangle += ANGLE_180; anglediff = abs((signed)(playerangle - R_PointToAngle2(0, 0, spawner->player->rmomx, spawner->player->rmomy))); @@ -3904,8 +3794,8 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map if (player->mo->eflags & MFE_VERTICALFLIP) { mo->z -= player->mo->height; - mo->flags2 |= MF2_OBJECTFLIP; mo->eflags |= MFE_VERTICALFLIP; + mo->flags2 |= (player->mo->flags2 & MF2_OBJECTFLIP); } mo->threshold = 10; @@ -3935,8 +3825,8 @@ static mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t map if (player->mo->eflags & MFE_VERTICALFLIP) { throwmo->z -= player->mo->height; - throwmo->flags2 |= MF2_OBJECTFLIP; throwmo->eflags |= MFE_VERTICALFLIP; + mo->flags2 |= (player->mo->flags2 & MF2_OBJECTFLIP); } throwmo->movecount = 0; // above player @@ -4031,7 +3921,8 @@ void K_PuntMine(mobj_t *thismine, mobj_t *punter) if (!thismine || P_MobjWasRemoved(thismine)) return; - if (thismine->type == MT_SSMINE_SHIELD) // Create a new mine + //This guarantees you hit a mine being dragged + if (thismine->type == MT_SSMINE_SHIELD) // Create a new mine, and clean up the old one { mine = P_SpawnMobj(thismine->x, thismine->y, thismine->z, MT_SSMINE); P_SetTarget(&mine->target, thismine->target); @@ -4039,7 +3930,19 @@ void K_PuntMine(mobj_t *thismine, mobj_t *punter) mine->flags2 = thismine->flags2; mine->floorz = thismine->floorz; mine->ceilingz = thismine->ceilingz; + + //Since we aren't using P_KillMobj, we need to clean up the hnext reference + { + P_SetTarget(&thismine->target->hnext, NULL); //target is the player who owns the mine + thismine->target->player->kartstuff[k_bananadrag] = 0; + thismine->target->player->kartstuff[k_itemheld] = 0; + + if (--thismine->target->player->kartstuff[k_itemamount] <= 0) + thismine->target->player->kartstuff[k_itemtype] = KITEM_NONE; + } + P_RemoveMobj(thismine); + } else mine = thismine; @@ -4222,13 +4125,28 @@ void K_DoSneaker(player_t *player, INT32 type) if (!player->kartstuff[k_floorboost] || player->kartstuff[k_floorboost] == 3) { - S_StartSound(player->mo, sfx_cdfm01); + const sfxenum_t normalsfx = sfx_cdfm01; + const sfxenum_t smallsfx = sfx_cdfm40; + sfxenum_t sfx = normalsfx; + + if (player->kartstuff[k_numsneakers]) + { + // Use a less annoying sound when stacking sneakers. + sfx = smallsfx; + } + + S_StopSoundByID(player->mo, normalsfx); + S_StopSoundByID(player->mo, smallsfx); + S_StartSound(player->mo, sfx); + K_SpawnDashDustRelease(player); if (intendedboost > player->kartstuff[k_speedboost]) player->karthud[khud_destboostcam] = FixedMul(FRACUNIT, FixedDiv((intendedboost - player->kartstuff[k_speedboost]), intendedboost)); + + player->kartstuff[k_numsneakers]++; } - if (!EITHERSNEAKER(player)) + if (!player->kartstuff[k_sneakertimer]) { if (type == 2) { @@ -4262,10 +4180,10 @@ void K_DoSneaker(player_t *player, INT32 type) { player->pflags |= PF_ATTACKDOWN; K_PlayBoostTaunt(player->mo); - player->kartstuff[k_sneakertimer] = sneakertime; + } - else - player->kartstuff[k_levelbooster] = sneakertime; + + player->kartstuff[k_sneakertimer] = sneakertime; // set angle for spun out players: player->kartstuff[k_boostangle] = (INT32)player->mo->angle; @@ -4371,7 +4289,7 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound) thrust = 72<player->kartstuff[k_pogospring] != 2) { - if (EITHERSNEAKER(mo->player)) + if (mo->player->kartstuff[k_sneakertimer]) thrust = FixedMul(thrust, (5*FRACUNIT)/4); else if (mo->player->kartstuff[k_invincibilitytimer]) thrust = FixedMul(thrust, (9*FRACUNIT)/8); @@ -4531,6 +4449,7 @@ void K_DropHnextList(player_t *player, boolean keepshields) dropwork->flags |= MF_NOCLIPTHING; dropwork->flags2 = work->flags2; + dropwork->eflags = work->eflags; dropwork->floorz = work->floorz; dropwork->ceilingz = work->ceilingz; @@ -4637,12 +4556,70 @@ void K_DropItems(player_t *player) drop->threshold = player->kartstuff[k_itemtype]; drop->movecount = player->kartstuff[k_itemamount]; + K_FlipFromObject(drop, player->mo); + drop->flags |= MF_NOCLIPTHING; } K_StripItems(player); } +void K_DropRocketSneaker(player_t *player) +{ + mobj_t *shoe = player->mo; + fixed_t flingangle; + boolean leftshoe = true; //left shoe is first + + if (!(player->mo && !P_MobjWasRemoved(player->mo) && player->mo->hnext && !P_MobjWasRemoved(player->mo->hnext))) + return; + + while ((shoe = shoe->hnext) && !P_MobjWasRemoved(shoe)) + { + if (shoe->type != MT_ROCKETSNEAKER) + return; //woah, not a rocketsneaker, bail! safeguard in case this gets used when you're holding non-rocketsneakers + + shoe->flags2 &= ~MF2_DONTDRAW; + shoe->flags &= ~MF_NOGRAVITY; + shoe->angle += ANGLE_45; + + if (shoe->eflags & MFE_VERTICALFLIP) + shoe->z -= shoe->height; + else + shoe->z += shoe->height; + + //left shoe goes off tot eh left, right shoe off to the right + if (leftshoe) + flingangle = -(ANG60); + else + flingangle = ANG60; + + S_StartSound(shoe, shoe->info->deathsound); + P_SetObjectMomZ(shoe, 8*FRACUNIT, false); + P_InstaThrust(shoe, R_PointToAngle2(shoe->target->x, shoe->target->y, shoe->x, shoe->y)+flingangle, 16*FRACUNIT); + shoe->momx += shoe->target->momx; + shoe->momy += shoe->target->momy; + shoe->momz += shoe->target->momz; + shoe->extravalue2 = 1; + + leftshoe = false; + } + P_SetTarget(&player->mo->hnext, NULL); + player->kartstuff[k_rocketsneakertimer] = 0; +} + +void K_DropKitchenSink(player_t *player) +{ + if (!(player->mo && !P_MobjWasRemoved(player->mo) && player->mo->hnext && !P_MobjWasRemoved(player->mo->hnext))) + return; + + if (player->mo->hnext->type != MT_SINK_SHIELD) + return; //so we can just call this function regardless of what is being held + + P_KillMobj(player->mo->hnext, NULL, NULL); + + P_SetTarget(&player->mo->hnext, NULL); +} + // When an item in the hnext chain dies. void K_RepairOrbitChain(mobj_t *orbit) { @@ -4982,6 +4959,9 @@ static void K_MoveHeldObjects(player_t *player) cur->flags &= ~MF_NOCLIPTHING; + if ((player->mo->eflags & MFE_VERTICALFLIP) != (cur->eflags & MFE_VERTICALFLIP)) + K_FlipFromObject(cur, player->mo); + if (!cur->health) { cur = cur->hnext; @@ -5062,9 +5042,9 @@ static void K_MoveHeldObjects(player_t *player) cur->flags &= ~MF_NOCLIPTHING; if (player->kartstuff[k_rocketsneakertimer] <= TICRATE && (leveltime & 1)) - cur->flags2 |= MF2_DONTDRAW; + cur->drawflags |= MFD_DONTDRAW; else - cur->flags2 &= ~MF2_DONTDRAW; + cur->drawflags &= ~MFD_DONTDRAW; if (num & 1) P_SetMobjStateNF(cur, (vibrate ? S_ROCKETSNEAKER_LVIBRATE : S_ROCKETSNEAKER_L)); @@ -5275,10 +5255,12 @@ static void K_UpdateEngineSounds(player_t *player, ticcmd_t *cmd) #endif return; - if ((leveltime >= starttime-(2*TICRATE) && leveltime <= starttime) || (player->kartstuff[k_respawn] == 1)) // Startup boosts + if (player->respawn.state == RESPAWNST_DROP) // Dropdashing targetsnd = ((cmd->buttons & BT_ACCELERATE) ? 12 : 0); + else if (K_PlayerEBrake(player) == true) // Spindashing + targetsnd = ((cmd->buttons & BT_DRIFT) ? 12 : 0); else - targetsnd = (((6*cmd->forwardmove)/25) + ((player->speed / mapobjectscale)/5))/2; + targetsnd = (((6*K_GetForwardMove(player))/25) + ((player->speed / mapobjectscale)/5))/2; if (targetsnd < 0) targetsnd = 0; @@ -5371,9 +5353,6 @@ static void K_UpdateInvincibilitySounds(player_t *player) #undef STOPTHIS } -#define RINGANIM_NUMFRAMES 10 -#define RINGANIM_DELAYMAX 5 - void K_KartPlayerHUDUpdate(player_t *player) { if (player->karthud[khud_lapanimation]) @@ -5388,7 +5367,12 @@ void K_KartPlayerHUDUpdate(player_t *player) if (player->karthud[khud_tauntvoices]) player->karthud[khud_tauntvoices]--; - if (gametype == GT_RACE) + if (!(player->pflags & PF_SKIDDOWN)) + player->karthud[khud_fault] = 0; + else if (player->karthud[khud_fault] > 0 && player->karthud[khud_fault] < 2*TICRATE) + player->karthud[khud_fault]++; + + if (G_RaceGametype()) { // 0 is the fast spin animation, set at 30 tics of ring boost or higher! if (player->kartstuff[k_ringboost] >= 30) @@ -5574,7 +5558,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->speed > 0) { // Speed lines - if (EITHERSNEAKER(player) || player->kartstuff[k_ringboost] + if (player->kartstuff[k_sneakertimer] || player->kartstuff[k_ringboost] || player->kartstuff[k_driftboost] || player->kartstuff[k_startboost] || player->kartstuff[k_eggmanexplode]) { @@ -5613,7 +5597,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) //ghost->momy = (3*player->mo->momy)/4; //ghost->momz = (3*player->mo->momz)/4; if (leveltime & 1) - ghost->flags2 |= MF2_DONTDRAW; + ghost->drawflags |= MFD_DONTDRAW; } if (P_IsObjectOnGround(player->mo)) @@ -5640,16 +5624,20 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) { 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; + + debtflag->drawflags = K_GetPlayerDontDrawFlag(player); } if (player->kartstuff[k_springstars] && (leveltime & 1)) @@ -5676,7 +5664,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) } } - if (player->playerstate == PST_DEAD || player->kartstuff[k_respawn] > 1) // Ensure these are set correctly here + if (player->playerstate == PST_DEAD || (player->respawn.state == RESPAWNST_MOVE)) // Ensure these are set correctly here { player->mo->colorized = false; player->mo->color = player->skincolor; @@ -5790,7 +5778,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) { if ((P_IsObjectOnGround(player->mo) || (player->kartstuff[k_spinouttype] != 0)) - && (!EITHERSNEAKER(player))) + && (!player->kartstuff[k_sneakertimer])) { player->kartstuff[k_spinouttimer]--; if (player->kartstuff[k_wipeoutslow] > 1) @@ -5829,15 +5817,19 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->kartstuff[k_ringboost]--; if (player->kartstuff[k_sneakertimer]) + { player->kartstuff[k_sneakertimer]--; - if (player->kartstuff[k_levelbooster]) - player->kartstuff[k_levelbooster]--; + if (player->kartstuff[k_sneakertimer] <= 0) + { + player->kartstuff[k_numsneakers] = 0; + } + } if (player->kartstuff[k_flamedash]) player->kartstuff[k_flamedash]--; - if (EITHERSNEAKER(player) && player->kartstuff[k_wipeoutslow] > 0 && player->kartstuff[k_wipeoutslow] < wipeoutslowtime+1) + if (player->kartstuff[k_sneakertimer] && player->kartstuff[k_wipeoutslow] > 0 && player->kartstuff[k_wipeoutslow] < wipeoutslowtime+1) player->kartstuff[k_wipeoutslow] = wipeoutslowtime+1; if (player->kartstuff[k_floorboost]) @@ -5849,10 +5841,20 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->kartstuff[k_startboost]) player->kartstuff[k_startboost]--; + if (player->kartstuff[k_spindashboost]) + { + player->kartstuff[k_spindashboost]--; + + if (player->kartstuff[k_spindashboost] <= 0) + { + player->kartstuff[k_spindashspeed] = player->kartstuff[k_spindashboost] = 0; + } + } + if (player->kartstuff[k_invincibilitytimer]) player->kartstuff[k_invincibilitytimer]--; - if (!player->kartstuff[k_respawn] && player->kartstuff[k_growshrinktimer] != 0) + if ((player->respawn.state == RESPAWNST_NONE) && player->kartstuff[k_growshrinktimer] != 0) { if (player->kartstuff[k_growshrinktimer] > 0) player->kartstuff[k_growshrinktimer]--; @@ -5905,7 +5907,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) } } - if (player->kartstuff[k_justbumped]) + if (player->kartstuff[k_justbumped] > 0) player->kartstuff[k_justbumped]--; if (player->kartstuff[k_tiregrease]) @@ -5922,7 +5924,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (gametype == GT_BATTLE && player->kartstuff[k_bumper] > 0 && !player->kartstuff[k_spinouttimer] && !player->kartstuff[k_squishedtimer] - && !player->kartstuff[k_respawn] && !player->powers[pw_flashing]) + && (player->respawn.state == RESPAWNST_DROP) && !player->powers[pw_flashing]) { player->kartstuff[k_wanted]++; if (battleovertime.enabled >= 10*TICRATE) @@ -6008,10 +6010,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) else player->kartstuff[k_jmp] = 0; - // Respawn Checker - if (player->kartstuff[k_respawn]) - K_RespawnChecker(player); - // Roulette Code K_KartItemRoulette(player, cmd); @@ -6268,36 +6266,13 @@ static waypoint_t *K_GetPlayerNextWaypoint(player_t *player) // Respawn point should only be updated when we're going to a nextwaypoint if ((updaterespawn) && + (player->respawn.state == RESPAWNST_NONE) && (bestwaypoint != NULL) && (bestwaypoint != player->nextwaypoint) && - (player->kartstuff[k_respawn] == 0) && - (K_GetWaypointIsSpawnpoint(bestwaypoint)) && // Don't try to respawn on waypoints that are marked with no respawn - (K_GetWaypointIsShortcut(bestwaypoint) == false) && (K_GetWaypointIsEnabled(bestwaypoint) == true)) + (K_GetWaypointIsSpawnpoint(bestwaypoint)) && + (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; - } - } + player->respawn.wp = bestwaypoint; } } @@ -6381,7 +6356,17 @@ void K_UpdateDistanceFromFinishLine(player_t *const player) if ((player != NULL) && (player->mo != NULL)) { waypoint_t *finishline = K_GetFinishLineWaypoint(); - waypoint_t *nextwaypoint = K_GetPlayerNextWaypoint(player); + waypoint_t *nextwaypoint = NULL; + + if (player->spectator) + { + // Don't update waypoints while spectating + nextwaypoint = finishline; + } + else + { + nextwaypoint = K_GetPlayerNextWaypoint(player); + } if (nextwaypoint != NULL) { @@ -6391,7 +6376,7 @@ void K_UpdateDistanceFromFinishLine(player_t *const player) } // nextwaypoint is now the waypoint that is in front of us - if (player->exiting) + if (player->exiting || player->spectator) { // Player has finished, we don't need to calculate this player->distancetofinish = 0U; @@ -6505,7 +6490,7 @@ static INT16 K_GetKartDriftValue(player_t *player, fixed_t countersteer) if (player->mo->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)) { - countersteer = 3*countersteer/2; + countersteer = FixedMul(countersteer, 3*FRACUNIT/2); } return basedrift + (FixedMul(driftadjust * FRACUNIT, countersteer) / FRACUNIT); @@ -6513,27 +6498,47 @@ static INT16 K_GetKartDriftValue(player_t *player, fixed_t countersteer) INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) { - fixed_t p_maxspeed = K_GetKartSpeed(player, false); - fixed_t p_speed = min(player->speed, (p_maxspeed * 2)); - fixed_t weightadjust = FixedDiv((p_maxspeed * 3) - p_speed, (p_maxspeed * 3) + (player->kartweight * FRACUNIT)); + fixed_t p_maxspeed; + fixed_t p_speed; + fixed_t weightadjust; + fixed_t turnfixed = turnvalue * FRACUNIT; - if (player->spectator) + if ((player->mo == NULL || P_MobjWasRemoved(player->mo))) + { + return 0; + } + + if (player->spectator || objectplacing) { return turnvalue; } + if (leveltime < introtime) + { + return 0; + } + + // SRB2kart - no additional angle if not moving + if ((player->speed <= 0) // Not moving + && ((player->cmd.buttons & BT_EBRAKEMASK) != BT_EBRAKEMASK) // not e-braking + && (player->respawn.state == RESPAWNST_NONE)) // Not respawning + { + return 0; + } + + p_maxspeed = K_GetKartSpeed(player, false); + p_speed = min(player->speed, (p_maxspeed * 2)); + weightadjust = FixedDiv((p_maxspeed * 3) - p_speed, (p_maxspeed * 3) + (player->kartweight * FRACUNIT)); + if (K_PlayerUsesBotMovement(player)) { - turnvalue = 5*turnvalue/4; // Base increase to turning - turnvalue = FixedMul( - turnvalue * FRACUNIT, - K_BotRubberband(player) - ) / FRACUNIT; + turnfixed = FixedMul(turnfixed, 5*FRACUNIT/4); // Base increase to turning + turnfixed = FixedMul(turnfixed, K_BotRubberband(player)); } if (player->kartstuff[k_drift] != 0 && P_IsObjectOnGround(player->mo)) { - fixed_t countersteer = FixedDiv(turnvalue*FRACUNIT, KART_FULLTURN*FRACUNIT); + fixed_t countersteer = FixedDiv(turnfixed, KART_FULLTURN*FRACUNIT); // If we're drifting we have a completely different turning value @@ -6542,32 +6547,23 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) countersteer = FRACUNIT; } - turnvalue = K_GetKartDriftValue(player, countersteer); - - return turnvalue; + return K_GetKartDriftValue(player, countersteer); } - if (EITHERSNEAKER(player) || player->kartstuff[k_invincibilitytimer] || player->kartstuff[k_growshrinktimer] > 0) + if (player->kartstuff[k_handleboost] > 0) { - turnvalue = 5*turnvalue/4; - } - - if (player->kartstuff[k_flamedash] > 0) - { - fixed_t multiplier = K_FlameShieldDashVar(player->kartstuff[k_flamedash]); - multiplier = FRACUNIT + (FixedDiv(multiplier, FRACUNIT/2) / 4); - turnvalue = FixedMul(turnvalue * FRACUNIT, multiplier) / FRACUNIT; + turnfixed = FixedMul(turnfixed, FRACUNIT + player->kartstuff[k_handleboost]); } if (player->mo->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)) { - turnvalue = 3*turnvalue/2; + turnfixed = FixedMul(turnfixed, 3*FRACUNIT/2); } // Weight has a small effect on turning - turnvalue = FixedMul(turnvalue * FRACUNIT, weightadjust) / FRACUNIT; + turnfixed = FixedMul(turnfixed, weightadjust); - return turnvalue; + return (turnfixed / FRACUNIT); } INT32 K_GetKartDriftSparkValue(player_t *player) @@ -6578,6 +6574,45 @@ INT32 K_GetKartDriftSparkValue(player_t *player) return (26*4 + kartspeed*2 + (9 - player->kartweight))*8; } +/* +Stage 1: red sparks +Stage 2: blue sparks +Stage 3: big large rainbow sparks +*/ +void K_SpawnDriftBoostExplosion(player_t *player, int stage) +{ + mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_DRIFTEXPLODE); + + P_SetTarget(&overlay->target, player->mo); + P_SetScale(overlay, (overlay->destscale = player->mo->scale)); + K_FlipFromObject(overlay, player->mo); + + switch (stage) + { + case 1: + overlay->color = SKINCOLOR_KETCHUP; + overlay->fuse = 16; + break; + + case 2: + overlay->color = SKINCOLOR_SAPPHIRE; + overlay->fuse = 32; + + S_StartSound(player->mo, sfx_kc5b); + break; + + case 3: + overlay->color = SKINCOLOR_SILVER; + overlay->fuse = 120; + + S_StartSound(player->mo, sfx_kc5b); + S_StartSound(player->mo, sfx_s3kc4l); + break; + } + + overlay->extravalue1 = stage; +} + static void K_KartDrift(player_t *player, boolean onground) { fixed_t minspeed = (10 * player->mo->scale); @@ -6594,10 +6629,6 @@ static void K_KartDrift(player_t *player, boolean onground) if (player->kartstuff[k_driftcharge] < 0 || player->kartstuff[k_driftcharge] >= dsone) { angle_t pushdir = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); - //mobj_t *overlay = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_DRIFTEXPLODE); - //P_SetTarget(&overlay->target, player->mo); - //P_SetScale(overlay, (overlay->destscale = player->mo->scale)); - //K_FlipFromObject(overlay, player->mo); S_StartSound(player->mo, sfx_s23c); //K_SpawnDashDustRelease(player); @@ -6610,9 +6641,6 @@ static void K_KartDrift(player_t *player, boolean onground) if (player->kartstuff[k_driftboost] < 15) player->kartstuff[k_driftboost] = 15; - - //overlay->color = SKINCOLOR_GOLD; - //overlay->fuse = 8; } else if (player->kartstuff[k_driftcharge] >= dsone && player->kartstuff[k_driftcharge] < dstwo) { @@ -6623,8 +6651,7 @@ static void K_KartDrift(player_t *player, boolean onground) if (player->kartstuff[k_driftboost] < 20) player->kartstuff[k_driftboost] = 20; - //overlay->color = SKINCOLOR_KETCHUP; - //overlay->fuse = 16; + K_SpawnDriftBoostExplosion(player, 1); } else if (player->kartstuff[k_driftcharge] < dsthree) { @@ -6635,8 +6662,7 @@ static void K_KartDrift(player_t *player, boolean onground) if (player->kartstuff[k_driftboost] < 50) player->kartstuff[k_driftboost] = 50; - //overlay->color = SKINCOLOR_SAPPHIRE; - //overlay->fuse = 32; + K_SpawnDriftBoostExplosion(player, 2); } else if (player->kartstuff[k_driftcharge] >= dsthree) { @@ -6647,8 +6673,7 @@ static void K_KartDrift(player_t *player, boolean onground) if (player->kartstuff[k_driftboost] < 125) player->kartstuff[k_driftboost] = 125; - //overlay->color = SKINCOLOR_SILVER; - //overlay->fuse = 120; + K_SpawnDriftBoostExplosion(player, 3); } } @@ -6786,7 +6811,7 @@ static void K_KartDrift(player_t *player, boolean onground) player->kartstuff[k_driftend] = 0; } - if ((!EITHERSNEAKER(player)) + if ((player->kartstuff[k_handleboost] == 0) || (!player->cmd.driftturn) || (!player->kartstuff[k_aizdriftstrat]) || (player->cmd.driftturn > 0) != (player->kartstuff[k_aizdriftstrat] > 0)) @@ -6885,12 +6910,12 @@ void K_KartUpdatePosition(player_t *player) // void K_StripItems(player_t *player) { + K_DropRocketSneaker(player); + K_DropKitchenSink(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; @@ -6950,10 +6975,95 @@ static INT32 K_FlameShieldMax(player_t *player) } disttofinish = player->distancetofinish - disttofinish; - distv = FixedDiv(distv * FRACUNIT, mapobjectscale) / FRACUNIT; + distv = FixedMul(distv * FRACUNIT, mapobjectscale) / FRACUNIT; return min(16, 1 + (disttofinish / distv)); } +boolean K_PlayerEBrake(player_t *player) +{ + return (player->cmd.buttons & BT_EBRAKEMASK) == BT_EBRAKEMASK + && P_IsObjectOnGround(player->mo) == true + && player->kartstuff[k_drift] == 0 + && player->kartstuff[k_spinouttimer] == 0 + && player->kartstuff[k_justbumped] == 0 + && player->kartstuff[k_spindash] >= 0 + && player->kartstuff[k_spindashboost] == 0 + && player->powers[pw_nocontrol] == 0; +} + +static void K_KartSpindash(player_t *player) +{ + const tic_t MAXCHARGETIME = K_GetSpindashChargeTime(player); + ticcmd_t *cmd = &player->cmd; + + if (player->kartstuff[k_spindash] > 0 && (cmd->buttons & (BT_DRIFT|BT_BRAKE)) != (BT_DRIFT|BT_BRAKE)) + { + player->kartstuff[k_spindashspeed] = (player->kartstuff[k_spindash] * FRACUNIT) / MAXCHARGETIME; + player->kartstuff[k_spindashboost] = TICRATE; + + if (!player->kartstuff[k_tiregrease]) + { + UINT8 i; + for (i = 0; i < 2; i++) + { + mobj_t *grease; + grease = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_TIREGREASE); + P_SetTarget(&grease->target, player->mo); + grease->angle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); + grease->extravalue1 = i; + } + } + + player->kartstuff[k_tiregrease] = 2*TICRATE; + + player->kartstuff[k_spindash] = 0; + S_StartSound(player->mo, sfx_s23c); + } + + if (K_PlayerEBrake(player) == false) + { + player->kartstuff[k_spindash] = 0; + return; + } + + if (player->speed < 6*mapobjectscale && player->powers[pw_flashing] == 0) + { + if (cmd->driftturn != 0 && leveltime % 8 == 0) + S_StartSound(player->mo, sfx_ruburn); + + if ((cmd->buttons & (BT_DRIFT|BT_BRAKE)) == (BT_DRIFT|BT_BRAKE)) + { + INT16 chargetime = MAXCHARGETIME - ++player->kartstuff[k_spindash]; + if (chargetime > 0) + { + UINT16 soundcharge = 0; + UINT8 add = 0; + while ((soundcharge += ++add) < chargetime); + if (soundcharge == chargetime) + { + K_SpawnDashDustRelease(player); + S_StartSound(player->mo, sfx_s3kab); + } + } + else if (chargetime < -TICRATE) + K_SpinPlayer(player, NULL, 0, NULL, false); + else + { + if (player->kartstuff[k_spindash] % 4 == 0) + { + K_SpawnDashDustRelease(player); + K_FlameDashLeftoverSmoke(player->mo); + } + } + } + } + else + { + if (leveltime % 4 == 0) + S_StartSound(player->mo, sfx_kc2b); + } +} + // // K_MoveKartPlayer // @@ -7002,8 +7112,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) else if (cmd->buttons & BT_ATTACK) player->pflags |= PF_ATTACKDOWN; - if (player && player->mo && player->mo->health > 0 && !player->spectator && !mapreset && leveltime > starttime - && player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_squishedtimer] == 0 && player->kartstuff[k_respawn] == 0) + if (player && player->mo && player->mo->health > 0 && !player->spectator && !mapreset && leveltime > introtime + && player->kartstuff[k_spinouttimer] == 0 && player->kartstuff[k_squishedtimer] == 0 && (player->respawn.state == RESPAWNST_NONE)) { // First, the really specific, finicky items that function without the item being directly in your item slot. // Karma item dropping @@ -7082,7 +7192,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { K_DoSneaker(player, 2); K_PlayBoostTaunt(player->mo); - player->kartstuff[k_rocketsneakertimer] -= 2*TICRATE; + player->kartstuff[k_rocketsneakertimer] -= 3*TICRATE; if (player->kartstuff[k_rocketsneakertimer] < 1) player->kartstuff[k_rocketsneakertimer] = 1; } @@ -7148,9 +7258,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetScale(overlay, player->mo->scale); } player->kartstuff[k_invincibilitytimer] = itemtime+(2*TICRATE); // 10 seconds - if (P_IsDisplayPlayer(player)) + if (P_IsLocalPlayer(player)) S_ChangeMusicSpecial("kinvnc"); - else + if (! P_IsDisplayPlayer(player)) S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kinvnc)); P_RestoreMusic(player); K_PlayPowerGloatSound(player->mo); @@ -7204,6 +7314,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) mo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_EGGMANITEM_SHIELD); if (mo) { + K_FlipFromObject(mo, player->mo); mo->flags |= MF_NOCLIPTHING; mo->threshold = 10; mo->movecount = 1; @@ -7352,9 +7463,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (cv_kartdebugshrink.value && !modeattacking && !player->bot) player->mo->destscale = (6*player->mo->destscale)/8; player->kartstuff[k_growshrinktimer] = itemtime+(4*TICRATE); // 12 seconds - if (P_IsDisplayPlayer(player)) + if (P_IsLocalPlayer(player)) S_ChangeMusicSpecial("kgrow"); - else + if (! P_IsDisplayPlayer(player)) S_StartSound(player->mo, (cv_kartinvinsfx.value ? sfx_alarmg : sfx_kgrow)); P_RestoreMusic(player); S_StartSound(player->mo, sfx_kc5a); @@ -7603,64 +7714,40 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (gametype == GT_RACE) hyu *= 2; // double in race - if (r_splitscreen) + if (leveltime & 1) { - if (leveltime & 1) - player->mo->flags2 |= MF2_DONTDRAW; - else - player->mo->flags2 &= ~MF2_DONTDRAW; - - if (player->kartstuff[k_hyudorotimer] >= (TICRATE/2) && player->kartstuff[k_hyudorotimer] <= hyu-(TICRATE/2)) - { - if (player == &players[displayplayers[1]]) - player->mo->eflags |= MFE_DRAWONLYFORP2; - else if (player == &players[displayplayers[2]] && r_splitscreen > 1) - player->mo->eflags |= MFE_DRAWONLYFORP3; - else if (player == &players[displayplayers[3]] && r_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); + player->mo->drawflags |= MFD_DONTDRAW; } else { - if (P_IsDisplayPlayer(player) - || (!P_IsDisplayPlayer(player) && (player->kartstuff[k_hyudorotimer] < (TICRATE/2) || player->kartstuff[k_hyudorotimer] > hyu-(TICRATE/2)))) - { - if (leveltime & 1) - player->mo->flags2 |= MF2_DONTDRAW; - else - player->mo->flags2 &= ~MF2_DONTDRAW; - } + if (player->kartstuff[k_hyudorotimer] >= (TICRATE/2) && player->kartstuff[k_hyudorotimer] <= hyu-(TICRATE/2)) + player->mo->drawflags &= ~K_GetPlayerDontDrawFlag(player); else - player->mo->flags2 |= MF2_DONTDRAW; + player->mo->drawflags &= ~MFD_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); + player->mo->drawflags &= ~MFD_DONTDRAW; } if (gametype == GT_BATTLE && 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->mo->drawflags |= MFD_SHADOW; player->powers[pw_flashing] = player->kartstuff[k_comebacktimer]; } else if (gametype == GT_RACE || player->kartstuff[k_bumper] > 0) { - player->mo->flags2 &= ~MF2_SHADOW; + player->mo->drawflags &= ~(MFD_TRANSMASK|MFD_BRIGHTMASK); } } + K_KartDrift(player, P_IsObjectOnGround(player->mo)); // Not using onground, since we don't want this affected by spring pads + if (onground) { fixed_t prevfriction = player->mo->friction; @@ -7669,15 +7756,12 @@ void K_MoveKartPlayer(player_t *player, boolean onground) 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; + /* + if (K_PlayerEBrake(player) == true) + player->mo->friction -= 1024; + else if (player->speed > 0 && cmd->forwardmove < 0) + player->mo->friction -= 512; + */ // Karma ice physics if (gametype == GT_BATTLE && player->kartstuff[k_bumper] <= 0) @@ -7695,14 +7779,15 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->mo->friction -= 9824; } - // Friction was changed, so we must recalculate a bunch of stuff + // Cap between intended values + if (player->mo->friction > FRACUNIT) + player->mo->friction = FRACUNIT; + if (player->mo->friction < 0) + player->mo->friction = 0; + + // Friction was changed, so we must recalculate movefactor 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) @@ -7713,18 +7798,16 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->mo->movefactor < 32) player->mo->movefactor = 32; } + + // Don't go too far above your top speed when rubberbanding + // Down here, because we do NOT want to modify movefactor + if (K_PlayerUsesBotMovement(player)) + { + player->mo->friction = K_BotFrictionRubberband(player, player->mo->friction); + } } - 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); - } + K_KartSpindash(player); // Squishing // If a Grow player or a sector crushes you, get flattened instead of being killed. @@ -7747,76 +7830,6 @@ void K_MoveKartPlayer(player_t *player, boolean onground) 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_CheckSpectateStatus(void) @@ -7914,3296 +7927,3 @@ void K_CheckSpectateStatus(void) } //} - -//{ 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_capsulesticker; -static patch_t *kp_capsulestickerwide; -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_rankcapsule; - -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_bubbleshield[2]; -static patch_t *kp_flameshield[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_flameshieldmeter[104][2]; -static patch_t *kp_flameshieldmeter_bg[16][2]; - -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_capsulesticker = W_CachePatchName("K_STCAPN", PU_HUDGFX); - kp_capsulestickerwide = W_CachePatchName("K_STCAPW", 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); - kp_rankcapsule = W_CachePatchName("K_CAPICO", 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_bubbleshield[0] = W_CachePatchName("K_ITBUBS", PU_HUDGFX); - kp_flameshield[0] = W_CachePatchName("K_ITFLMS", 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); - - sprintf(buffer, "FSMFGxxx"); - for (i = 0; i < 104; i++) - { - buffer[5] = '0'+((i+1)/100); - buffer[6] = '0'+(((i+1)/10)%10); - buffer[7] = '0'+((i+1)%10); - kp_flameshieldmeter[i][0] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - sprintf(buffer, "FSMBG0xx"); - for (i = 0; i < 16; i++) - { - buffer[6] = '0'+((i+1)/10); - buffer[7] = '0'+((i+1)%10); - kp_flameshieldmeter_bg[i][0] = (patch_t *) W_CachePatchName(buffer, 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_bubbleshield[1] = W_CachePatchName("K_ISBUBS", PU_HUDGFX); - kp_flameshield[1] = W_CachePatchName("K_ISFLMS", 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); - - sprintf(buffer, "FSMFSxxx"); - for (i = 0; i < 104; i++) - { - buffer[5] = '0'+((i+1)/100); - buffer[6] = '0'+(((i+1)/10)%10); - buffer[7] = '0'+((i+1)%10); - kp_flameshieldmeter[i][1] = (patch_t *) W_CachePatchName(buffer, PU_HUDGFX); - } - - sprintf(buffer, "FSMBS0xx"); - for (i = 0; i < 16; i++) - { - buffer[6] = '0'+((i+1)/10); - buffer[7] = '0'+((i+1)%10); - kp_flameshieldmeter_bg[i][1] = (patch_t *) W_CachePatchName(buffer, 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); - } -} - -// 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_BUBBLESHIELD: - return (tiny ? "K_ISBUBS" : "K_ITBUBS"); - case KITEM_FLAMESHIELD: - return (tiny ? "K_ISFLMS" : "K_ITFLMS"); - 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 (r_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 (r_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 (r_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 (r_splitscreen == 0) - return snapflags; - - if (stplyr != &players[displayplayers[0]]) - { - if (r_splitscreen == 1 && stplyr == &players[displayplayers[1]]) - { - splitflags |= V_SPLITSCREEN; - } - else if (r_splitscreen > 1) - { - if (stplyr == &players[displayplayers[2]] || (r_splitscreen == 3 && stplyr == &players[displayplayers[3]])) - splitflags |= V_SPLITSCREEN; - if (stplyr == &players[displayplayers[1]] || (r_splitscreen == 3 && stplyr == &players[displayplayers[3]])) - splitflags |= V_HORZSCREEN; - } - } - - if (splitflags & V_SPLITSCREEN) - snapflags &= ~V_SNAPTOTOP; - else - snapflags &= ~V_SNAPTOBOTTOM; - - if (r_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 = ((r_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 = (r_splitscreen > 1 ? 12 : 26); - UINT16 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_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_BUBBLESHIELD: - localpatch = kp_bubbleshield[offset]; - localbg = kp_itembg[offset+1]; - break; - case KITEM_FLAMESHIELD: - localpatch = kp_flameshield[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 = K_RainbowColor(leveltime); - break; - case 1: - localcolor = SKINCOLOR_RED; - break; - default: - localcolor = SKINCOLOR_WHITE; - break; - } - } - } - - // pain and suffering defined below - if (r_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]))]); - - if (stplyr->kartstuff[k_itemtype] == KITEM_FLAMESHIELD && stplyr->kartstuff[k_flamelength] > 0) - { - INT32 numframes = 104; - INT32 absolutemax = 16 * flameseg; - INT32 flamemax = stplyr->kartstuff[k_flamelength] * flameseg; - INT32 flamemeter = min(stplyr->kartstuff[k_flamemeter], flamemax); - - INT32 bf = 16 - stplyr->kartstuff[k_flamelength]; - INT32 ff = numframes - ((flamemeter * numframes) / absolutemax); - INT32 fmin = (8 * (bf-1)); - - INT32 xo = 6, yo = 4; - INT32 flip = 0; - - if (offset) - { - xo++; - - if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // Flip for P1 and P3 (yes, that's correct) - { - xo -= 62; - flip = V_FLIP; - } - } - - if (ff < fmin) - ff = fmin; - - if (bf >= 0 && bf < 16) - V_DrawScaledPatch(fx-xo, fy-yo, V_HUDTRANS|fflags|flip, kp_flameshieldmeter_bg[bf][offset]); - - if (ff >= 0 && ff < numframes && stplyr->kartstuff[k_flamemeter] > 0) - { - if ((stplyr->kartstuff[k_flamemeter] > flamemax) && (leveltime & 1)) - { - UINT8 *fsflash = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE); - V_DrawMappedPatch(fx-xo, fy-yo, V_HUDTRANS|fflags|flip, kp_flameshieldmeter[ff][offset], fsflash); - } - else - { - V_DrawScaledPatch(fx-xo, fy-yo, V_HUDTRANS|fflags|flip, kp_flameshieldmeter[ff][offset]); - } - } - } -} - -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 (r_splitscreen) - scale /= 2; - - W = FixedMul(W<>FRACBITS; - - // pain and suffering defined below - if (!r_splitscreen) - { - fx = POSI_X; - fy = BASEVIDHEIGHT - 8; - fflags = V_SNAPTOBOTTOM|V_SNAPTORIGHT; - } - else if (r_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; - - if (!LUA_HudEnabled(hud_minirankings)) - return false; // Don't proceed but still return true for free play above if HUD is disabled. - - 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 (gametype == GT_BATTLE || 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); - - if (LUA_HudEnabled(hud_battlebumpers)) - { - if (gametype == GT_BATTLE && 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); - } - } - } - } - - if (i == strank) - V_DrawScaledPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_facehighlight[(leveltime / 4) % 8]); - - if (gametype == GT_BATTLE && 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 ping offline - { - if (players[tab[i].num].bot) - { - ; // TODO: Put a graphic here to indicate this player is a bot! - } - else if (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 (netgame && playerconsole[tab[i].num] == 0 && server_lagless && !players[tab[i].num].bot) - { - 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 (gametype == GT_BATTLE && 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 (gametype == GT_BATTLE && 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 (gametype == GT_RACE) - { -#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((r_splitscreen > 1) ? kp_smallring[ringanim_realframe]->width : kp_ring[ringanim_realframe]->width); - } - - if (r_splitscreen > 1) - { - INT32 fx = 0, fy = 0, fr = 0; - INT32 flipflag = 0; - - // pain and suffering defined below - if (r_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] = ((stplyr->laps / 10) % 10); - ln[1] = (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 (gametype == GT_BATTLE) - 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 (r_splitscreen > 1) - { - INT32 fx = 0, fy = 0; - INT32 flipflag = 0; - - // pain and suffering defined below - if (r_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 (battlecapsules) - { - V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|splitflags, kp_rankcapsule, NULL); - - if (numtargets > 9 || maptargets > 9) - { - UINT8 ln[2]; - ln[0] = ((numtargets / 10) % 10); - ln[1] = (numtargets % 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] = ((maptargets / 10) % 10); - ln[1] = (maptargets % 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[numtargets % 10]); - V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, kp_facenum[maptargets % 10]); - } - } - else - { - 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 - { - INT32 maxbumper = K_StartingBumperCount(); - V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|splitflags, kp_rankbumper, colormap); - - if (stplyr->kartstuff[k_bumper] > 9 || maxbumper > 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(maxbumper) / 10) % 10); - ln[1] = (abs(maxbumper) % 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[(maxbumper) % 10]); - } - } - } - } - else - { - if (battlecapsules) - { - if (numtargets > 9 && maptargets > 9) - V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_capsulestickerwide, NULL); - else - V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|splitflags, kp_capsulesticker, NULL); - V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|splitflags, va("%d/%d", numtargets, maptargets)); - } - 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 - { - INT32 maxbumper = K_StartingBumperCount(); - - if (stplyr->kartstuff[k_bumper] > 9 && maxbumper > 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], maxbumper)); - } - } - } -} - -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 (r_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 (r_splitscreen < 3) // 1P and 2P use the same code. - { - basex = WANT_X; - basey = WANT_Y; - if (r_splitscreen == 2) - { - basey += 16; // slight adjust for 3P - basex -= 6; - } - } - else if (r_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+(r_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 (( stplyr->cmd.buttons & BT_LOOKBACK )) - 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. - // The number being divided by is for how fast it moves. - // The higher the number, the slower it moves. - - // am xpos & ypos are the icon's starting position. Withouht - // it, they wouldn't 'spawn' on the top-right side of the HUD. - - fixed_t amnumxpos, amnumypos; - INT32 amxpos, amypos; - - node_t *bsp = &nodes[numnodes-1]; - fixed_t maxx, minx, maxy, miny; - - fixed_t mapwidth, mapheight; - fixed_t xoffset, yoffset; - fixed_t xscale, yscale, zoom; - - maxx = maxy = INT32_MAX; - minx = miny = INT32_MIN; - minx = bsp->bbox[0][BOXLEFT]; - maxx = bsp->bbox[0][BOXRIGHT]; - miny = bsp->bbox[0][BOXBOTTOM]; - maxy = bsp->bbox[0][BOXTOP]; - - if (bsp->bbox[1][BOXLEFT] < minx) - minx = bsp->bbox[1][BOXLEFT]; - if (bsp->bbox[1][BOXRIGHT] > maxx) - maxx = bsp->bbox[1][BOXRIGHT]; - if (bsp->bbox[1][BOXBOTTOM] < miny) - miny = bsp->bbox[1][BOXBOTTOM]; - if (bsp->bbox[1][BOXTOP] > maxy) - maxy = bsp->bbox[1][BOXTOP]; - - // You might be wondering why these are being bitshift here - // it's because mapwidth and height would otherwise overflow for maps larger than half the size possible... - // map boundaries and sizes will ALWAYS be whole numbers thankfully - // later calculations take into consideration that these are actually not in terms of FRACUNIT though - minx >>= FRACBITS; - maxx >>= FRACBITS; - miny >>= FRACBITS; - maxy >>= FRACBITS; - - mapwidth = maxx - minx; - mapheight = maxy - miny; - - // These should always be small enough to be bitshift back right now - xoffset = (minx + mapwidth/2)<width, mapwidth); - yscale = FixedDiv(AutomapPic->height, mapheight); - zoom = FixedMul(min(xscale, yscale), FRACUNIT-FRACUNIT/20); - - amnumxpos = (FixedMul(objx, zoom) - FixedMul(xoffset, zoom)); - amnumypos = -(FixedMul(objy, zoom) - FixedMul(yoffset, zoom)); - - if (encoremode) - amnumxpos = -amnumxpos; - - amxpos = amnumxpos + ((hudx + AutomapPic->width/2 - (icon->width/2))<height/2 - (icon->height/2))<width/2 + (icon->width/2))<width/2); - y = MINI_Y - (AutomapPic->height/2); - - if (timeinmap > 105) - { - minimaptrans = cv_kartminimap.value; - if (timeinmap <= 113) - minimaptrans = ((((INT32)timeinmap) - 105)*minimaptrans)/(113-105); - if (!minimaptrans) - return; - } - else - return; - - minimaptrans = ((10-minimaptrans)<width), y, splitflags|V_FLIP, AutomapPic); - else - V_DrawScaledPatch(x, y, splitflags, AutomapPic); - - if (!(r_splitscreen == 2)) - { - splitflags &= ~minimaptrans; - splitflags |= V_HUDTRANSHALF; - } - - // let offsets transfer to the heads, too! - if (encoremode) - x += SHORT(AutomapPic->leftoffset); - else - x -= SHORT(AutomapPic->leftoffset); - y -= SHORT(AutomapPic->topoffset); - - // Draw the super item in Battle - if (gametype == GT_BATTLE && battleovertime.enabled) - { - if (battleovertime.enabled >= 10*TICRATE || (battleovertime.enabled & 1)) - { - const INT32 prevsplitflags = splitflags; - splitflags &= ~V_HUDTRANSHALF; - splitflags |= V_HUDTRANS; - colormap = R_GetTranslationColormap(TC_RAINBOW, K_RainbowColor(leveltime), GTC_CACHE); - K_drawKartMinimapIcon(battleovertime.x, battleovertime.y, x, y, splitflags, kp_itemminimap, colormap, AutomapPic); - splitflags = prevsplitflags; - } - } - - // initialize - for (i = 0; i < 4; i++) - localplayers[i] = -1; - - // Player's tiny icons on the Automap. (drawn opposite direction so player 1 is drawn last in splitscreen) - if (ghosts) - { - demoghost *g = ghosts; - while (g) - { - if (g->mo->skin) - skin = ((skin_t*)g->mo->skin)-skins; - else - skin = 0; - if (g->mo->color) - { - if (g->mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, g->mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(skin, g->mo->color, GTC_CACHE); - } - else - colormap = NULL; - K_drawKartMinimapIcon(g->mo->x, g->mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); - g = g->next; - } - - if (!stplyr->mo || stplyr->spectator || stplyr->exiting) - return; - - localplayers[numlocalplayers] = stplyr-players; - numlocalplayers++; - } - else - { - for (i = MAXPLAYERS-1; i >= 0; i--) - { - if (!playeringame[i]) - continue; - if (!players[i].mo || players[i].spectator || players[i].exiting) - continue; - - if (i != displayplayers[0] || r_splitscreen) - { - if (gametype == GT_BATTLE && players[i].kartstuff[k_bumper] <= 0) - continue; - - if (players[i].kartstuff[k_hyudorotimer] > 0) - { - if (!((players[i].kartstuff[k_hyudorotimer] < 1*TICRATE/2 - || players[i].kartstuff[k_hyudorotimer] > hyudorotime-(1*TICRATE/2)) - && !(leveltime & 1))) - continue; - } - } - - if (i == displayplayers[0] || i == displayplayers[1] || i == displayplayers[2] || i == displayplayers[3]) - { - // Draw display players on top of everything else - localplayers[numlocalplayers] = i; - numlocalplayers++; - continue; - } - - if (players[i].mo->skin) - skin = ((skin_t*)players[i].mo->skin)-skins; - else - skin = 0; - - if (players[i].mo->color) - { - if (players[i].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[i].mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(skin, players[i].mo->color, GTC_CACHE); - } - else - colormap = NULL; - - K_drawKartMinimapIcon(players[i].mo->x, players[i].mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); - // Target reticule - if ((gametype == GT_RACE && players[i].kartstuff[k_position] == spbplace) - || (gametype == GT_BATTLE && 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; - splitflags |= V_HUDTRANS; - - for (i = 0; i < numlocalplayers; i++) - { - if (i == -1) - continue; // this doesn't interest us - - if (players[localplayers[i]].mo->skin) - skin = ((skin_t*)players[localplayers[i]].mo->skin)-skins; - else - skin = 0; - - if (players[localplayers[i]].mo->color) - { - if (players[localplayers[i]].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[localplayers[i]].mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(skin, players[localplayers[i]].mo->color, GTC_CACHE); - } - else - colormap = NULL; - - K_drawKartMinimapIcon(players[localplayers[i]].mo->x, players[localplayers[i]].mo->y, x, y, splitflags, facemmapprefix[skin], colormap, AutomapPic); - - // Target reticule - if ((gametype == GT_RACE && players[localplayers[i]].kartstuff[k_position] == spbplace) - || (gametype == GT_BATTLE && 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 (r_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 (r_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 (r_splitscreen) // wide splitscreen - pnum += 4; - - x = ((vid.width<width)<karthud[khud_cardanimation])*(xval > x ? xval : x))/TICRATE; - - if (r_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. - - if (!LUA_HudEnabled(hud_battlecomebacktimer)) - drawcomebacktimer = false; - - if (r_splitscreen) - { - if ((r_splitscreen == 1 && stplyr == &players[displayplayers[1]]) - || (r_splitscreen > 1 && (stplyr == &players[displayplayers[2]] - || (stplyr == &players[displayplayers[3]] && r_splitscreen > 2)))) - { - y = 232-(stplyr->karthud[khud_cardanimation]/2); - splitflags = V_SNAPTOBOTTOM; - } - else - y = -32+(stplyr->karthud[khud_cardanimation]/2); - - if (r_splitscreen > 1) - { - scale /= 2; - - if (stplyr == &players[displayplayers[1]] - || (stplyr == &players[displayplayers[3]] && r_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) - { - patch_t *p = kp_battlecool; - - if (K_IsPlayerLosing(stplyr)) - p = kp_battlelose; - else if (stplyr->kartstuff[k_position] == 1) - p = kp_battlewin; - - 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 = (r_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 (r_splitscreen) - { - if (r_splitscreen > 1) - ty = (BASEVIDHEIGHT/4)+33; - if ((r_splitscreen == 1 && stplyr == &players[displayplayers[1]]) - || (stplyr == &players[displayplayers[2]] && r_splitscreen > 1) - || (stplyr == &players[displayplayers[3]] && r_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; - } - - if (LUA_HudEnabled(hud_freeplay)) - 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]] && r_splitscreen) - { pn = pnum[1]; tn = turn[1]; dr = drift[1]; } - else if (stplyr == &players[displayplayers[2]] && r_splitscreen > 1) - { pn = pnum[2]; tn = turn[2]; dr = drift[2]; } - else if (stplyr == &players[displayplayers[3]] && r_splitscreen > 2) - { pn = pnum[3]; tn = turn[3]; dr = drift[3]; } - else - { pn = pnum[0]; tn = turn[0]; dr = drift[0]; } - - if (r_splitscreen) - { - y >>= 1; - if (r_splitscreen > 1) - x >>= 1; - } - - { - if (stplyr->speed < (20*stplyr->mo->scale) && (leveltime & 1) && !r_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 (r_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 (r_splitscreen) - scale = FRACUNIT/2; - else - scale = FRACUNIT; - - if (stplyr->mo) - { - UINT8 driftcolor = K_DriftSparkColor(stplyr, stplyr->kartstuff[k_driftcharge]); - 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 (r_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 (!r_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 (!r_splitscreen) - y += yoffs; - - - if ((leveltime & 1) && (driftcolor != SKINCOLOR_NONE)) // drift sparks! - colmap = R_GetTranslationColormap(TC_RAINBOW, driftcolor, GTC_CACHE); - else if (stplyr->mo->colorized && stplyr->mo->color) // invincibility/grow/shrink! - 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]] && r_splitscreen) - { pnum[1] = pn; turn[1] = tn; drift[1] = dr; } - else if (stplyr == &players[displayplayers[2]] && r_splitscreen > 1) - { pnum[2] = pn; turn[2] = tn; drift[2] = dr; } - else if (stplyr == &players[displayplayers[3]] && r_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|skincolors[stplyr->skincolor].ramp[5]; - const INT32 accent2 = splitflags|skincolors[stplyr->skincolor].ramp[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 -Draw_party_ping (int ss, INT32 snap) -{ - HU_drawMiniPing(0, 0, playerpingtable[displayplayers[ss]], V_HUDTRANS|snap); -} - -static void -K_drawMiniPing (void) -{ - if (cv_showping.value) - { - switch (r_splitscreen) - { - case 3: - Draw_party_ping(3, V_SNAPTORIGHT|V_SPLITSCREEN); - /*FALLTHRU*/ - case 2: - Draw_party_ping(2, V_SPLITSCREEN); - Draw_party_ping(1, V_SNAPTORIGHT); - Draw_party_ping(0, 0); - break; - case 1: - Draw_party_ping(1, V_SNAPTORIGHT|V_SPLITSCREEN); - Draw_party_ping(0, V_SNAPTORIGHT); - break; - } - } -} - -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_bubbleshield[1], - kp_flameshield[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, stplyr->bot); - 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, 166, 0, va("'Best' Waypoint ID: %d", K_GetWaypointID(stplyr->nextwaypoint))); - V_DrawString(8, 176, 0, va("Finishline Distance: %d", stplyr->distancetofinish)); - } -} - -void K_drawKartHUD(void) -{ - boolean isfreeplay = false; - boolean battlefullscreen = false; - boolean freecam = demo.freecam; //disable some hud elements w/ freecam - 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 <= r_splitscreen; i++) - { - if (stplyr == &players[displayplayers[i]] && !camera[i].chase && !freecam) - K_drawKartFirstPerson(); - } - - // Draw full screen stuff that turns off the rest of the HUD - if (mapreset && stplyr == &players[displayplayers[0]]) - { - K_drawChallengerScreen(); - return; - } - - battlefullscreen = ((gametype == GT_BATTLE) - && (stplyr->exiting - || (stplyr->kartstuff[k_bumper] <= 0 - && stplyr->kartstuff[k_comebacktimer] - && comeback - && stplyr->playerstate == PST_LIVE))); - - if (!demo.title && (!battlefullscreen || r_splitscreen)) - { - // Draw the CHECK indicator before the other items, so it's overlapped by everything else - if (LUA_HudEnabled(hud_check)) // delete lua when? - if (cv_kartcheck.value && !splitscreen && !players[displayplayers[0]].exiting && !freecam) - K_drawKartPlayerCheck(); - - // Draw WANTED status - if (gametype == GT_BATTLE) - { - if (LUA_HudEnabled(hud_wanted)) - K_drawKartWanted(); - } - - if (cv_kartminimap.value) - { - if (LUA_HudEnabled(hud_minimap)) - K_drawKartMinimap(); - } - } - - if (battlefullscreen && !freecam) - { - if (LUA_HudEnabled(hud_battlefullscreen)) - K_drawBattleFullscreen(); - return; - } - - // Draw the item window - if (LUA_HudEnabled(hud_item) && !freecam) - K_drawKartItem(); - - // If not splitscreen, draw... - if (!r_splitscreen && !demo.title) - { - // Draw the timestamp - if (LUA_HudEnabled(hud_time)) - 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 && !demo.freecam) // Bottom of the screen elements, don't need in spectate mode - { - // Draw the speedometer - if (cv_kartspeedometer.value && !r_splitscreen) - { - if (LUA_HudEnabled(hud_speedometer)) - K_drawKartSpeedometer(); - } - - if (demo.title) // Draw title logo instead in demo.titles - { - INT32 x = BASEVIDWIDTH - 32, y = 128, offs; - - if (r_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 (gametype == GT_RACE) // Race-only elements - { - // Draw the lap counter - if (LUA_HudEnabled(hud_gametypeinfo)) - K_drawKartLapsAndRings(); - - if (isfreeplay) - ; - else if (!modeattacking) - { - // Draw the numerical position - if (LUA_HudEnabled(hud_position)) - K_DrawKartPositionNum(stplyr->kartstuff[k_position]); - } - else //if (!(demo.playback && hu_showscores)) - { - // Draw the input UI - if (LUA_HudEnabled(hud_position)) - K_drawInput(); - } - } - else if (gametype == GT_BATTLE) // Battle-only - { - // Draw the hits left! - if (LUA_HudEnabled(hud_gametypeinfo)) - K_drawKartBumpersOrKarma(); - } - } - - // Draw the countdowns after everything else. - if (leveltime >= starttime-(3*TICRATE) - && leveltime < starttime+TICRATE) - K_drawKartStartCountdown(); - else if (racecountdown && (!r_splitscreen || !stplyr->exiting)) - { - char *countstr = va("%d", racecountdown/TICRATE); - - if (r_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 (gametype == GT_RACE && !freecam) - { - if (stplyr->exiting) - K_drawKartFinish(); - else if (stplyr->karthud[khud_lapanimation] && !r_splitscreen) - K_drawLapStartAnim(); - } - - if (modeattacking || freecam) // everything after here is MP and debug only - return; - - if (gametype == GT_BATTLE && !r_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) - { - if (LUA_HudEnabled(hud_freeplay)) - K_drawKartFreePlay(leveltime); - } - - if (r_splitscreen == 0 && stplyr->kartstuff[k_wrongway] && ((leveltime / 8) & 1)) - { - V_DrawCenteredString(BASEVIDWIDTH>>1, 176, V_REDMAP|V_SNAPTOBOTTOM, "WRONG WAY"); - } - - if (netgame && r_splitscreen) - { - K_drawMiniPing(); - } - - 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 < numskincolors; 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 1bbfce1b9..67d7a3495 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -13,22 +13,28 @@ #define KART_FULLTURN 800 player_t *K_GetItemBoxPlayer(mobj_t *mobj); +angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t yourspeed); void K_RegisterKartStuff(void); +UINT16 K_GetPlayerDontDrawFlag(player_t *player); boolean K_IsPlayerLosing(player_t *player); fixed_t K_GetKartGameSpeedScalar(SINT8 value); extern consvar_t *KartItemCVars[NUMKARTRESULTS-1]; +UINT8 K_FindUseodds(player_t *player, fixed_t mashed, UINT32 pdis, UINT8 bestbumper, boolean spbrush); +INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush, boolean bot, boolean rival); INT32 K_GetShieldFromItem(INT32 item); fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against); 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_GenericExtraFlagsNoZAdjust(mobj_t *mo, mobj_t *master); +void K_SpawnDashDustRelease(player_t *player); +void K_SpawnDriftBoostClip(player_t *player); +void K_SpawnDriftBoostClipSpark(mobj_t *clip); void K_KartMoveAnimation(player_t *player); void K_KartPlayerHUDUpdate(player_t *player); void K_KartPlayerThink(player_t *player, ticcmd_t *cmd); @@ -59,17 +65,23 @@ void K_UpdateDistanceFromFinishLine(player_t *const player); boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y); INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue); INT32 K_GetKartDriftSparkValue(player_t *player); +void K_SpawnDriftBoostExplosion(player_t *player, int stage); void K_KartUpdatePosition(player_t *player); void K_DropItems(player_t *player); +void K_DropRocketSneaker(player_t *player); void K_StripItems(player_t *player); void K_StripOther(player_t *player); void K_MomentumToFacing(player_t *player); boolean K_ApplyOffroad(player_t *player); +tic_t K_GetSpindashChargeTime(player_t *player); +fixed_t K_GetSpindashChargeSpeed(player_t *player); fixed_t K_GetKartSpeedFromStat(UINT8 kartspeed); fixed_t K_GetKartSpeed(player_t *player, boolean doboostpower); fixed_t K_GetKartAccel(player_t *player); UINT16 K_GetKartFlashing(player_t *player); -fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove); +SINT8 K_GetForwardMove(player_t *player); +fixed_t K_3dKartMovement(player_t *player, boolean onground); +boolean K_PlayerEBrake(player_t *player); void K_MoveKartPlayer(player_t *player, boolean onground); void K_CheckSpectateStatus(void); @@ -81,12 +93,5 @@ void K_PlayPainSound(mobj_t *source); void K_PlayHitEmSound(mobj_t *source); void K_PlayPowerGloatSound(mobj_t *source); -const char *K_GetItemPatch(UINT8 item, boolean tiny); -INT32 K_calcSplitFlags(INT32 snapflags); -void K_LoadKartHUDGraphics(void); -void K_drawKartHUD(void); -void K_drawKartFreePlay(UINT32 flashtime); -void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode); - // ========================================================================= #endif // __K_KART__ diff --git a/src/k_pathfind.c b/src/k_pathfind.c index 8cccd1e81..316eab418 100644 --- a/src/k_pathfind.c +++ b/src/k_pathfind.c @@ -472,13 +472,44 @@ boolean K_PathfindAStar(path_t *const path, pathfindsetup_t *const pathfindsetup // Reallocate nodesarray if it's full if (nodesarraycount >= pathfindsetup->nodesarraycapacity) { + pathfindnode_t *nodesarrayrealloc = NULL; pathfindsetup->nodesarraycapacity = pathfindsetup->nodesarraycapacity * 2; - nodesarray = Z_Realloc(nodesarray, pathfindsetup->nodesarraycapacity * sizeof(pathfindnode_t), PU_STATIC, NULL); + nodesarrayrealloc = Z_Realloc(nodesarray, pathfindsetup->nodesarraycapacity * sizeof(pathfindnode_t), PU_STATIC, NULL); - if (nodesarray == NULL) + if (nodesarrayrealloc == NULL) { I_Error("K_PathfindAStar: Out of memory reallocating nodes array."); } + + // Need to update pointers in closedset, openset, and node "camefrom" if nodesarray moved. + if (nodesarray != nodesarrayrealloc) + { + size_t j = 0U; + size_t arrayindex = 0U; + for (j = 0U; j < closedsetcount; j++) + { + arrayindex = closedset[j] - nodesarray; + closedset[j] = &nodesarrayrealloc[arrayindex]; + } + for (j = 0U; j < openset.count; j++) + { + arrayindex = ((pathfindnode_t *)(openset.array[j].data)) - nodesarray; + openset.array[j].data = &nodesarrayrealloc[arrayindex]; + } + for (j = 0U; j < nodesarraycount; j++) + { + if (nodesarrayrealloc[j].camefrom != NULL) + { + arrayindex = nodesarrayrealloc[j].camefrom - nodesarray; + nodesarrayrealloc[j].camefrom = &nodesarrayrealloc[arrayindex]; + } + } + + arrayindex = currentnode - nodesarray; + currentnode = &nodesarrayrealloc[arrayindex]; + } + + nodesarray = nodesarrayrealloc; } // Create the new node and add it to the nodes array and open set diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 405bc4743..7c08816cf 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -8,6 +8,7 @@ #include "m_random.h" #include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems #include "p_tick.h" // leveltime +#include "k_grandprix.h" // Online rankings for the main gametypes. // This array is saved to the gamedata. @@ -25,6 +26,27 @@ INT16 nospectategrief[MAXPLAYERS]; SINT8 speedscramble = -1; SINT8 encorescramble = -1; +SINT8 K_UsingPowerLevels(void) +{ + SINT8 pt = PWRLV_DISABLED; + + if (!cv_kartusepwrlv.value || !netgame || grandprixinfo.gp == true) + { + return PWRLV_DISABLED; + } + + if (G_RaceGametype()) + { + pt = PWRLV_RACE; + } + else if (G_BattleGametype()) + { + pt = PWRLV_BATTLE; + } + + return pt; +} + void K_ClearClientPowerLevels(void) { UINT8 i, j; diff --git a/src/k_pwrlv.h b/src/k_pwrlv.h index dfa300114..579e298af 100644 --- a/src/k_pwrlv.h +++ b/src/k_pwrlv.h @@ -21,6 +21,7 @@ extern UINT16 vspowerlevel[PWRLV_NUMTYPES]; extern UINT16 clientpowerlevels[MAXPLAYERS][PWRLV_NUMTYPES]; extern INT16 nospectategrief[MAXPLAYERS]; +SINT8 K_UsingPowerLevels(void); void K_ClearClientPowerLevels(void); INT16 K_CalculatePowerLevelInc(INT16 diff); INT16 K_CalculatePowerLevelAvg(void); diff --git a/src/k_respawn.c b/src/k_respawn.c new file mode 100644 index 000000000..be55e2389 --- /dev/null +++ b/src/k_respawn.c @@ -0,0 +1,752 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_respawn.c +/// \brief Respawning logic + +#include "k_respawn.h" +#include "doomdef.h" +#include "d_player.h" +#include "k_kart.h" +#include "k_battle.h" +#include "g_game.h" +#include "p_local.h" +#include "p_tick.h" +#include "p_setup.h" +#include "r_main.h" +#include "s_sound.h" +#include "p_slopes.h" +#include "r_defs.h" + +/*-------------------------------------------------- + fixed_t K_RespawnOffset(player_t *player, boolean flip) + + See header file for description. +--------------------------------------------------*/ +fixed_t K_RespawnOffset(player_t *player, boolean flip) +{ + fixed_t z = 0; + + if (flip == true) + { + // Lat 24/7/20: Okay so before we even think about applying this flag, check if the sector we're in doesn't already have reverse gravity for that. + // Otherwise, we would reverse the reverse gravity and cancel it out. Yes, this is absolutely fucking dumb. + // I'm honestly not sure if this flag is even necessary anymore but we'll keep it just in case. + + if (P_GetMobjGravity(player->mo) < 0) + player->mo->flags2 |= MF2_OBJECTFLIP; + + player->mo->eflags |= MFE_VERTICALFLIP; + z -= ((128 * mapobjectscale) + (player->mo->height)); + } + else + { + player->mo->flags2 &= ~MF2_OBJECTFLIP; + player->mo->eflags &= ~MFE_VERTICALFLIP; + z += (128 * mapobjectscale); + } + + return z; +} + +/*-------------------------------------------------- + static void K_RespawnAtWaypoint(player_t *player, waypoint_t *waypoint) + + Updates a player's respawn variables to go to the provided waypoint. + + Input Arguments:- + player - Player to preform for. + waypoint - Waypoint to respawn to. + + Return:- + None +--------------------------------------------------*/ +static void K_RespawnAtWaypoint(player_t *player, waypoint_t *waypoint) +{ + if (player == NULL || player->mo == NULL || P_MobjWasRemoved(player->mo)) + { + return; + } + + if (waypoint == NULL || waypoint->mobj == NULL || P_MobjWasRemoved(waypoint->mobj)) + { + return; + } + + player->respawn.pointx = waypoint->mobj->x; + player->respawn.pointy = waypoint->mobj->y; + player->respawn.pointz = waypoint->mobj->z; + player->respawn.flip = (waypoint->mobj->flags2 & MF2_OBJECTFLIP) ? true : false; // K_RespawnOffset wants a boolean! + player->respawn.pointz += K_RespawnOffset(player, player->respawn.flip); +} + +/*-------------------------------------------------- + void K_DoIngameRespawn(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void K_DoIngameRespawn(player_t *player) +{ + if (!player->mo || P_MobjWasRemoved(player->mo)) + { + return; + } + + if (player->respawn.state != RESPAWNST_NONE) + { + return; + } + + if (leveltime < introtime) + { + return; + } + + if (leveltime < starttime) // FAULT + { + player->powers[pw_nocontrol] = (starttime - leveltime) + 50; + player->pflags |= PF_SKIDDOWN; // cheeky pflag reuse + S_StartSound(player->mo, sfx_s3k83); + player->karthud[khud_fault] = 1; + } + + 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; + + // Set up respawn position if invalid + if (player->respawn.wp != NULL && leveltime >= starttime) + { + const UINT32 dist = RESPAWN_DIST + (player->airtime * 48); + player->respawn.distanceleft = (dist * mapobjectscale) / FRACUNIT; + K_RespawnAtWaypoint(player, player->respawn.wp); + } + else + { + 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"); + player->respawn.pointx = 0; + player->respawn.pointy = 0; + player->respawn.pointz = 0; + player->respawn.flip = false; + } + else + { + sector_t *s; + fixed_t z = (beststart->options >> ZSHIFT) * FRACUNIT; + + player->respawn.pointx = beststart->x << FRACBITS; + player->respawn.pointy = beststart->y << FRACBITS; + + s = R_PointInSubsector(beststart->x << FRACBITS, beststart->y << FRACBITS)->sector; + + player->respawn.flip = (beststart->options & MTF_OBJECTFLIP); + + if (player->respawn.flip == true) + { + player->respawn.pointz = ( +#ifdef ESLOPE + s->c_slope ? P_GetZAt(s->c_slope, player->respawn.pointx, player->respawn.pointy) : +#endif + s->ceilingheight); + + if (z != 0) + { + player->respawn.pointz -= z; + } + } + else + { + player->respawn.pointz = ( +#ifdef ESLOPE + s->f_slope ? P_GetZAt(s->f_slope, player->respawn.pointx, player->respawn.pointy) : +#endif + s->floorheight); + + if (z) + { + player->respawn.pointz += z; + } + } + } + + player->respawn.pointz += K_RespawnOffset(player, player->respawn.flip); + player->respawn.distanceleft = 0; + } + + player->respawn.timer = RESPAWN_TIME; + player->respawn.state = RESPAWNST_MOVE; +} + +/*-------------------------------------------------- + static size_t K_NextRespawnWaypointIndex(waypoint_t *waypoint) + + Returns the index for the next respawn waypoint. + + Input Arguments:- + waypoint - Waypoint to look after. + + Return:- + An table index for waypoint_t -> nextwaypoints. +--------------------------------------------------*/ +static size_t K_NextRespawnWaypointIndex(waypoint_t *waypoint) +{ + size_t i = 0U; + size_t newwaypoint = SIZE_MAX; + + // Set to the first valid nextwaypoint, for simplicity's sake. + // 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 < waypoint->numnextwaypoints; i++) + { + newwaypoint = i; + + if ((i == waypoint->numnextwaypoints - 1U) + || ((K_GetWaypointIsEnabled(waypoint->nextwaypoints[newwaypoint]) == true) + && (K_GetWaypointIsSpawnpoint(waypoint->nextwaypoints[newwaypoint]) == true))) + { + break; + } + } + + return newwaypoint; +} + +/*-------------------------------------------------- + static void K_MovePlayerToRespawnPoint(player_t *player) + + Handles the movement state of the respawn animation. + + Input Arguments:- + player - Player to preform for. + + Return:- + None +--------------------------------------------------*/ +static void K_MovePlayerToRespawnPoint(player_t *player) +{ + const fixed_t realstepamt = (64 * mapobjectscale); + fixed_t stepamt = realstepamt; + + vertex_t dest, step, laser; + angle_t stepha, stepva; + fixed_t dist, fulldist; + + UINT8 lasersteps = 4; + UINT32 laserdist; + waypoint_t *laserwp; + boolean laserflip; + + player->mo->momx = player->mo->momy = player->mo->momz = 0; + + player->powers[pw_flashing] = 2; + player->powers[pw_nocontrol] = max(2, player->powers[pw_nocontrol]); + + if (leveltime % 8 == 0 && !mapreset) + { + S_StartSound(player->mo, sfx_s3kcas); + } + + dest.x = player->respawn.pointx; + dest.y = player->respawn.pointy; + dest.z = player->respawn.pointz; + + dist = P_AproxDistance(P_AproxDistance( + player->mo->x - dest.x, + player->mo->y - dest.y), + player->mo->z - dest.z + ); + + if (dist <= stepamt) + { + // Reduce by the amount we needed to get to this waypoint + stepamt -= dist; + + // We've reached the destination point, + P_UnsetThingPosition(player->mo); + player->mo->x = dest.x; + player->mo->y = dest.y; + player->mo->z = dest.z; + P_SetThingPosition(player->mo); + + // Find the next waypoint to head towards + if (player->respawn.wp != NULL) + { + size_t nwp = K_NextRespawnWaypointIndex(player->respawn.wp); + + if (nwp == SIZE_MAX) + { + player->respawn.state = RESPAWNST_DROP; + return; + } + + // Set angle, regardless of if we're done or not + player->frameangle = R_PointToAngle2( + player->mo->x, player->mo->y, + dest.x, dest.y + ); + + if ((player->respawn.distanceleft == 0 && K_GetWaypointIsSpawnpoint(player->respawn.wp) == true) + || (player->respawn.wp == K_GetFinishLineWaypoint() + || player->respawn.wp->nextwaypoints[nwp] == K_GetFinishLineWaypoint())) // Try not to allow you to pass the finish line while respawning, because it's janky + { + // Alright buddy, that's the end of the ride. + player->respawn.state = RESPAWNST_DROP; + return; + } + + if (player->respawn.distanceleft > player->respawn.wp->nextwaypointdistances[nwp]) + { + player->respawn.distanceleft -= player->respawn.wp->nextwaypointdistances[nwp]; + } + else + { + player->respawn.distanceleft = 0; + } + + player->respawn.wp = player->respawn.wp->nextwaypoints[nwp]; + K_RespawnAtWaypoint(player, player->respawn.wp); + + dest.x = player->respawn.pointx; + dest.y = player->respawn.pointy; + dest.z = player->respawn.pointz; + } + else + { + // We can now drop! + player->respawn.state = RESPAWNST_DROP; + return; + } + } + + stepha = R_PointToAngle2( + player->mo->x, player->mo->y, + dest.x, dest.y + ); + + stepva = R_PointToAngle2( + 0, player->mo->z, + P_AproxDistance(player->mo->x - dest.x, player->mo->y - dest.y), dest.z + ); + + // Move toward the respawn point + player->frameangle = stepha; + + step.x = FixedMul(FixedMul(FINECOSINE(stepha >> ANGLETOFINESHIFT), stepamt), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + step.y = FixedMul(FixedMul(FINESINE(stepha >> ANGLETOFINESHIFT), stepamt), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + step.z = FixedMul(FINESINE(stepva >> ANGLETOFINESHIFT), 2*stepamt); + + if (stepamt > 0) + { + player->mo->momx = step.x; + player->mo->momy = step.y; + player->mo->momz = step.z; + } + + // NOW THEN, time for loads of dumb duplication! + // "Emulate" the rest of the path, that way we can spawn a particle a certain distance ahead of you. + + if (stepamt != realstepamt) + { + // Reset back to default + stepamt = realstepamt; + + step.x = FixedMul(FixedMul(FINECOSINE(stepha >> ANGLETOFINESHIFT), stepamt), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + step.y = FixedMul(FixedMul(FINESINE(stepha >> ANGLETOFINESHIFT), stepamt), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + step.z = FixedMul(FINESINE(stepva >> ANGLETOFINESHIFT), 2*stepamt); + } + + laserdist = player->respawn.distanceleft; + laserwp = player->respawn.wp; + laserflip = player->respawn.flip; + + laser.x = player->mo->x + (step.x / 2); + laser.y = player->mo->y + (step.y / 2); + laser.z = player->mo->z + (step.z / 2); + + dist = P_AproxDistance(P_AproxDistance( + laser.x - dest.x, + laser.y - dest.y), + laser.z - dest.z + ); + fulldist = dist + (laserdist * FRACUNIT); + + while (lasersteps > 0) + { + if (fulldist <= stepamt) + { + break; + } + + if (dist <= stepamt) + { + size_t lnwp; + + laser.x = dest.x; + laser.y = dest.y; + laser.z = dest.z; + + if (laserdist <= 0) + { + break; + } + + lnwp = K_NextRespawnWaypointIndex(laserwp); + if (lnwp == SIZE_MAX) + { + break; + } + + if (laserdist > laserwp->nextwaypointdistances[lnwp]) + { + laserdist -= laserwp->nextwaypointdistances[lnwp]; + } + else + { + laserdist = 0; + } + + laserwp = laserwp->nextwaypoints[lnwp]; + + dest.x = laserwp->mobj->x; + dest.y = laserwp->mobj->y; + dest.z = laserwp->mobj->z; + laserflip = (laserwp->mobj->flags2 & MF2_OBJECTFLIP); + + if (laserflip == true) + { + dest.z -= (128 * mapobjectscale) - (player->mo->height); + } + else + { + dest.z += (128 * mapobjectscale); + } + + stepamt -= dist; + + stepha = R_PointToAngle2(laser.x, laser.y, dest.x, dest.y); + stepva = R_PointToAngle2(0, laser.z, P_AproxDistance(laser.x - dest.x, laser.y - dest.y), dest.z); + + step.x = FixedMul(FixedMul(FINECOSINE(stepha >> ANGLETOFINESHIFT), stepamt), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + step.y = FixedMul(FixedMul(FINESINE(stepha >> ANGLETOFINESHIFT), stepamt), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + step.z = FixedMul(FINESINE(stepva >> ANGLETOFINESHIFT), 2*stepamt); + } + else if (stepamt != realstepamt) + { + // Reset back to default + stepamt = realstepamt; + + step.x = FixedMul(FixedMul(FINECOSINE(stepha >> ANGLETOFINESHIFT), stepamt), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + step.y = FixedMul(FixedMul(FINESINE(stepha >> ANGLETOFINESHIFT), stepamt), FINECOSINE(stepva >> ANGLETOFINESHIFT)); + step.z = FixedMul(FINESINE(stepva >> ANGLETOFINESHIFT), 2*stepamt); + } + + if (stepamt > 0) + { + laser.x += step.x; + laser.y += step.y; + laser.z += step.z; + } + + dist = P_AproxDistance(P_AproxDistance( + laser.x - dest.x, + laser.y - dest.y), + laser.z - dest.z + ); + fulldist = dist + (laserdist * FRACUNIT); + + lasersteps--; + } + + if (lasersteps == 0) // Don't spawn them beyond the respawn point. + { + mobj_t *lasermo = P_SpawnMobj(laser.x, laser.y, laser.z + (player->mo->height / 2), MT_DEZLASER); + + if (lasermo && !P_MobjWasRemoved(lasermo)) + { + P_SetMobjState(lasermo, S_DEZLASER_TRAIL1); + + if (player->mo->eflags & MFE_VERTICALFLIP) + { + lasermo->eflags |= MFE_VERTICALFLIP; + } + + P_SetTarget(&lasermo->target, player->mo); + + lasermo->angle = stepha + ANGLE_90; + P_SetScale(lasermo, (lasermo->destscale = player->mo->scale)); + } + } +} + +/*-------------------------------------------------- + static void K_HandleDropDash(player_t *player) + + Handles the visuals for the waiting period, + before you're allowed to Drop Dash. + + Input Arguments:- + player - Player to preform for. + + Return:- + None +--------------------------------------------------*/ +static void K_DropDashWait(player_t *player) +{ + if (player->powers[pw_nocontrol] == 0) + player->respawn.timer--; + + if (leveltime % 8 == 0) + { + const UINT8 ns = 8; + const angle_t sidediff = FixedAngle((360 / ns) * FRACUNIT); + UINT8 i; + + if (!mapreset) + { + S_StartSound(player->mo, sfx_s3kcas); + } + + for (i = 0; i < ns; i++) + { + const angle_t newangle = sidediff * i; + vertex_t spawn; + mobj_t *laser; + + spawn.x = player->mo->x + P_ReturnThrustX(player->mo, newangle, 31 * player->mo->scale); + spawn.y = player->mo->y + P_ReturnThrustY(player->mo, newangle, 31 * player->mo->scale); + + if (player->mo->eflags & MFE_VERTICALFLIP) + { + spawn.z = player->mo->z + player->mo->height; + } + else + { + spawn.z = player->mo->z; + } + + laser = P_SpawnMobj(spawn.x, spawn.y, spawn.z, 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 * player->mo->scale) * P_MobjFlip(player->mo); + P_SetScale(laser, (laser->destscale = player->mo->scale)); + } + } + } +} + + +/*-------------------------------------------------- + static void K_HandleDropDash(player_t *player) + + Handles input for the Drop Dash maneuver. + + Input Arguments:- + player - Player to preform for. + + Return:- + None +--------------------------------------------------*/ +static void K_HandleDropDash(player_t *player) +{ + ticcmd_t *cmd = &player->cmd; + + 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)) + { + if (mapreset) + { + return; + } + + player->powers[pw_flashing] = K_GetKartFlashing(player); + + // 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_spinouttimer]) // Since we're letting players spin out on respawn, don't let them charge a dropdash in this state. (It wouldn't work anyway) + { + player->respawn.dropdash++; + } + else + { + player->respawn.dropdash = 0; + } + + if (player->respawn.dropdash == TICRATE/4) + { + S_StartSound(player->mo, sfx_ddash); + } + + if ((player->respawn.dropdash >= TICRATE/4) && (player->respawn.dropdash & 1)) + { + player->mo->colorized = true; + } + else + { + player->mo->colorized = false; + } + } + else + { + if ((cmd->buttons & BT_ACCELERATE) && (player->respawn.dropdash >= TICRATE/4)) + { + S_StartSound(player->mo, sfx_s23c); + player->kartstuff[k_startboost] = 50; + K_SpawnDashDustRelease(player); + } + + player->mo->colorized = false; + player->respawn.dropdash = 0; + + //P_PlayRinglossSound(player->mo); + P_PlayerRingBurst(player, 3); + + if (G_BattleGametype()) + { + 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->respawn.state = RESPAWNST_NONE; + } +} + +/*-------------------------------------------------- + void K_RespawnChecker(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void K_RespawnChecker(player_t *player) +{ + if (player->respawn.state == RESPAWNST_NONE) + { + return; + } + + if (player->spectator) + { + player->respawn.state = RESPAWNST_NONE; + return; + } + + switch (player->respawn.state) + { + case RESPAWNST_MOVE: + player->mo->momx = player->mo->momy = player->mo->momz = 0; + K_MovePlayerToRespawnPoint(player); + return; + case RESPAWNST_DROP: + player->mo->momx = player->mo->momy = 0; + if (player->respawn.timer > 0) + { + player->mo->momz = 0; + K_DropDashWait(player); + } + else + { + K_HandleDropDash(player); + } + return; + default: + player->respawn.state = RESPAWNST_NONE; + return; + } +} diff --git a/src/k_respawn.h b/src/k_respawn.h new file mode 100644 index 000000000..81b5cea8d --- /dev/null +++ b/src/k_respawn.h @@ -0,0 +1,71 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2020 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file k_respawn.h +/// \brief Respawning logic + +#ifndef __K_RESPAWN__ +#define __K_RESPAWN__ + +#include "k_waypoint.h" +#include "d_player.h" + +#define RESPAWN_DIST 1024 +#define RESPAWN_TIME 48 +#define RESPAWNST_NONE 0 +#define RESPAWNST_MOVE 1 +#define RESPAWNST_DROP 2 + +/*-------------------------------------------------- + fixed_t K_RespawnOffset(player_t *player, boolean flip); + + Updates the player's flip flags, and returns a + Z offset for respawning. + + Input Arguments:- + player - Player to preform this for. + flip - false for normal, true for gravity flip. + + Return:- + Z position offset. +--------------------------------------------------*/ + +fixed_t K_RespawnOffset(player_t *player, boolean flip); + + +/*-------------------------------------------------- + void K_DoIngameRespawn(player_t *player); + + Starts the respawning animation for the specified player, + updating their respawn variables in preparation. + + Input Arguments:- + player - Player to preform this for. + + Return:- + None +--------------------------------------------------*/ + +void K_DoIngameRespawn(player_t *player); + + +/*-------------------------------------------------- + void K_RespawnChecker(player_t *player); + + Thinker for the respawning animation. + + Input Arguments:- + player - Player to preform this for. + + Return:- + None +--------------------------------------------------*/ + +void K_RespawnChecker(player_t *player); + +#endif diff --git a/src/k_waypoint.c b/src/k_waypoint.c index 69b488dd6..7d119498a 100644 --- a/src/k_waypoint.c +++ b/src/k_waypoint.c @@ -474,7 +474,7 @@ static void K_DebugWaypointsSpawnLine(waypoint_t *const waypoint1, waypoint_t *c spawnedmobj->state->tics = 1; spawnedmobj->frame = spawnedmobj->frame & ~FF_TRANSMASK; spawnedmobj->color = linkcolours[linkcolour]; - spawnedmobj->scale = FixedMul(FRACUNIT/4, FixedDiv((15 - ((leveltime + n) % 16))*FRACUNIT, 15*FRACUNIT)); + spawnedmobj->scale = FixedMul(spawnedmobj->scale, FixedMul(FRACUNIT/4, FixedDiv((15 - ((leveltime + n) % 16))*FRACUNIT, 15*FRACUNIT))); } x += stepx; @@ -483,6 +483,48 @@ static void K_DebugWaypointsSpawnLine(waypoint_t *const waypoint1, waypoint_t *c } while (n--); } +/*-------------------------------------------------- + void K_DebugWaypointDrawRadius(waypoint_t *const waypoint) + + Draw a debugging circle to represent a waypoint's radius + + Input Arguments:- + waypoint - A waypoint to draw the radius of +--------------------------------------------------*/ +static void K_DebugWaypointDrawRadius(waypoint_t *const waypoint) +{ + mobj_t *radiusOrb; + mobj_t *waypointmobj; + const INT32 numRadiusMobjs = 64; + INT32 i = 0; + angle_t spawnAngle = 0U; + fixed_t spawnX= 0; + fixed_t spawnY= 0; + fixed_t spawnZ= 0; + + I_Assert(waypoint != NULL); + I_Assert(waypoint->mobj != NULL); + + waypointmobj = waypoint->mobj; + + for (i = 0; i < numRadiusMobjs; i++) + { + spawnAngle = (ANGLE_MAX / numRadiusMobjs) * i; + + spawnZ = waypointmobj->z; + spawnX = waypointmobj->x + P_ReturnThrustX(waypointmobj, spawnAngle, waypointmobj->radius); + spawnY = waypointmobj->y + P_ReturnThrustY(waypointmobj, spawnAngle, waypointmobj->radius); + + radiusOrb = P_SpawnMobj(spawnX, spawnY, spawnZ, MT_SPARK); + P_SetMobjState(radiusOrb, S_THOK); + radiusOrb->state->nextstate = S_NULL; + radiusOrb->state->tics = 1; + radiusOrb->frame = radiusOrb->frame & ~FF_TRANSMASK; + radiusOrb->color = SKINCOLOR_PURPLE; + radiusOrb->scale = radiusOrb->scale / 4; + } +} + /*-------------------------------------------------- void K_DebugWaypointsVisualise(void) @@ -523,15 +565,20 @@ void K_DebugWaypointsVisualise(void) { if (waypoint->numnextwaypoints == 0 && waypoint->numprevwaypoints == 0) { + P_SetMobjState(debugmobj, S_EGOORB); debugmobj->color = SKINCOLOR_RED; + debugmobj->colorized = true; } else if (waypoint->numnextwaypoints == 0 || waypoint->numprevwaypoints == 0) { + P_SetMobjState(debugmobj, S_EGOORB); debugmobj->color = SKINCOLOR_YELLOW; + debugmobj->colorized = true; } else if (waypoint == players[displayplayers[0]].nextwaypoint) { debugmobj->color = SKINCOLOR_GREEN; + K_DebugWaypointDrawRadius(waypoint); } else { diff --git a/src/lua_baselib.c b/src/lua_baselib.c index d70ed8228..2285b2732 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -29,6 +29,7 @@ #include "k_kart.h" // SRB2Kart #include "k_battle.h" #include "k_color.h" +#include "k_hud.h" #include "d_netcmd.h" // IsPlayerAdmin #include "m_menu.h" // Player Setup menu color stuff diff --git a/src/lua_hud.h b/src/lua_hud.h index 0221f991b..63fd36d30 100644 --- a/src/lua_hud.h +++ b/src/lua_hud.h @@ -19,6 +19,7 @@ enum hud { hud_minimap, hud_item, hud_position, + hud_names, // online nametags hud_check, // "CHECK" f-zero indicator hud_minirankings, // Rankings to the left hud_battlebumpers, // mini rankings battle bumpers. diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index 9b5656db3..f83d11ee9 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -23,6 +23,7 @@ #include "v_video.h" #include "w_wad.h" #include "z_zone.h" +#include "hu_stuff.h" #include "lua_script.h" #include "lua_libs.h" diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index 291945e87..dcf04fa39 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -56,6 +56,7 @@ enum mobj_e { mobj_flags, mobj_flags2, mobj_eflags, + mobj_drawflags, mobj_skin, mobj_color, mobj_bnext, @@ -129,6 +130,7 @@ static const char *const mobj_opt[] = { "flags", "flags2", "eflags", + "drawflags", "skin", "color", "bnext", @@ -285,6 +287,9 @@ static int mobj_get(lua_State *L) case mobj_eflags: lua_pushinteger(L, mo->eflags); break; + case mobj_drawflags: + lua_pushinteger(L, mo->drawflags); + break; case mobj_skin: // skin name or nil, not struct if (!mo->skin) return 0; @@ -599,7 +604,10 @@ static int mobj_set(lua_State *L) mo->flags2 = (UINT32)luaL_checkinteger(L, 3); break; case mobj_eflags: - mo->eflags = (UINT32)luaL_checkinteger(L, 3); + mo->eflags = (UINT16)luaL_checkinteger(L, 3); + break; + case mobj_drawflags: + mo->drawflags = (UINT16)luaL_checkinteger(L, 3); break; case mobj_skin: // set skin by name { diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 2123df8ba..cb5832857 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -118,7 +118,7 @@ static int lib_iterateDisplayplayers(lua_State *L) for (i++; i < MAXSPLITSCREENPLAYERS; i++) { - if (!playeringame[displayplayers[i]] || i > splitscreen) + if (i > splitscreen || !playeringame[displayplayers[i]]) return 0; // Stop! There are no more players for us to go through. There will never be a player gap in displayplayers. if (!players[displayplayers[i]].mo) @@ -139,6 +139,8 @@ static int lib_getDisplayplayers(lua_State *L) lua_Integer i = luaL_checkinteger(L, 2); if (i < 0 || i >= MAXSPLITSCREENPLAYERS) return luaL_error(L, "displayplayers[] index %d out of range (0 - %d)", i, MAXSPLITSCREENPLAYERS-1); + if (i > splitscreen) + return 0; if (!playeringame[displayplayers[i]]) return 0; if (!players[displayplayers[i]].mo) @@ -217,6 +219,8 @@ static int player_get(lua_State *L) LUA_PushUserdata(L, plr->kartstuff, META_KARTSTUFF); else if (fastcmp(field,"frameangle")) lua_pushangle(L, plr->frameangle); + else if (fastcmp(field,"airtime")) + lua_pushinteger(L, plr->airtime); else if (fastcmp(field,"pflags")) lua_pushinteger(L, plr->pflags); else if (fastcmp(field,"panim")) @@ -238,6 +242,14 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->kartspeed); else if (fastcmp(field,"kartweight")) lua_pushinteger(L, plr->kartweight); + else if (fastcmp(field,"followerskin")) + lua_pushinteger(L, plr->followerskin); + else if (fastcmp(field,"followerready")) + lua_pushboolean(L, plr->followerready); + else if (fastcmp(field,"followercolor")) + lua_pushinteger(L, plr->followercolor); + else if (fastcmp(field,"follower")) + LUA_PushUserdata(L, plr->follower, META_MOBJ); // else if (fastcmp(field,"charflags")) lua_pushinteger(L, plr->charflags); @@ -247,6 +259,8 @@ static int player_get(lua_State *L) LUA_PushUserdata(L, plr->followmobj, META_MOBJ); else if (fastcmp(field,"lives")) lua_pushinteger(L, plr->lives); + else if (fastcmp(field,"lostlife")) + lua_pushboolean(L, plr->lostlife); else if (fastcmp(field,"continues")) lua_pushinteger(L, plr->continues); else if (fastcmp(field,"xtralife")) @@ -299,20 +313,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->weapondelay); else if (fastcmp(field,"tossdelay")) lua_pushinteger(L, plr->tossdelay); - else if (fastcmp(field,"starpostx")) - lua_pushinteger(L, plr->starpostx); - else if (fastcmp(field,"starposty")) - lua_pushinteger(L, plr->starposty); - else if (fastcmp(field,"starpostz")) - lua_pushinteger(L, plr->starpostz); else if (fastcmp(field,"starpostnum")) lua_pushinteger(L, plr->starpostnum); - else if (fastcmp(field,"starposttime")) - lua_pushinteger(L, plr->starposttime); - else if (fastcmp(field,"starpostangle")) - lua_pushangle(L, plr->starpostangle); - else if (fastcmp(field,"starpostscale")) - lua_pushfixed(L, plr->starpostscale); else if (fastcmp(field,"angle_pos")) lua_pushangle(L, plr->angle_pos); else if (fastcmp(field,"old_angle_pos")) @@ -405,6 +407,7 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->awayviewtics); else if (fastcmp(field,"awayviewaiming")) lua_pushangle(L, plr->awayviewaiming); + else if (fastcmp(field,"spectator")) lua_pushboolean(L, plr->spectator); else if (fastcmp(field,"bot")) @@ -514,10 +517,20 @@ static int player_set(lua_State *L) return NOSET; else if (fastcmp(field,"frameangle")) plr->frameangle = luaL_checkangle(L, 3); + else if (fastcmp(field,"airtime")) + plr->airtime = (tic_t)luaL_checkinteger(L, 3); else if (fastcmp(field,"kartspeed")) plr->kartspeed = (UINT8)luaL_checkinteger(L, 3); else if (fastcmp(field,"kartweight")) plr->kartweight = (UINT8)luaL_checkinteger(L, 3); + else if (fastcmp(field,"followerskin")) + plr->followerskin = luaL_checkinteger(L, 3); + else if (fastcmp(field,"followercolor")) + plr->followercolor = luaL_checkinteger(L, 3); + else if (fastcmp(field,"followerready")) + plr->followerready = luaL_checkboolean(L, 3); + else if (fastcmp(field,"follower")) // it's probably best we don't allow the follower mobj to change. + return NOSET; // else if (fastcmp(field,"charflags")) plr->charflags = (UINT32)luaL_checkinteger(L, 3); @@ -532,6 +545,8 @@ static int player_set(lua_State *L) } else if (fastcmp(field,"lives")) plr->lives = (SINT8)luaL_checkinteger(L, 3); + else if (fastcmp(field,"lostlife")) + plr->lostlife = luaL_checkboolean(L, 3); else if (fastcmp(field,"continues")) plr->continues = (SINT8)luaL_checkinteger(L, 3); else if (fastcmp(field,"xtralife")) @@ -584,20 +599,8 @@ static int player_set(lua_State *L) plr->weapondelay = (INT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"tossdelay")) plr->tossdelay = (INT32)luaL_checkinteger(L, 3); - else if (fastcmp(field,"starpostx")) - plr->starpostx = (INT16)luaL_checkinteger(L, 3); - else if (fastcmp(field,"starposty")) - plr->starposty = (INT16)luaL_checkinteger(L, 3); - else if (fastcmp(field,"starpostz")) - plr->starpostz = (INT16)luaL_checkinteger(L, 3); else if (fastcmp(field,"starpostnum")) plr->starpostnum = (INT32)luaL_checkinteger(L, 3); - else if (fastcmp(field,"starposttime")) - plr->starposttime = (tic_t)luaL_checkinteger(L, 3); - else if (fastcmp(field,"starpostangle")) - plr->starpostangle = luaL_checkangle(L, 3); - else if (fastcmp(field,"starpostscale")) - plr->starpostscale = luaL_checkfixed(L, 3); else if (fastcmp(field,"angle_pos")) plr->angle_pos = luaL_checkangle(L, 3); else if (fastcmp(field,"old_angle_pos")) @@ -843,8 +846,6 @@ static int ticcmd_get(lua_State *L) if (fastcmp(field,"forwardmove")) lua_pushinteger(L, cmd->forwardmove); - else if (fastcmp(field,"sidemove")) - lua_pushinteger(L, cmd->sidemove); else if (fastcmp(field,"angleturn")) lua_pushinteger(L, cmd->angleturn); else if (fastcmp(field,"aiming")) @@ -873,8 +874,6 @@ static int ticcmd_set(lua_State *L) if (fastcmp(field,"forwardmove")) cmd->forwardmove = (SINT8)luaL_checkinteger(L, 3); - else if (fastcmp(field,"sidemove")) - cmd->sidemove = (SINT8)luaL_checkinteger(L, 3); else if (fastcmp(field,"angleturn")) cmd->angleturn = (INT16)luaL_checkinteger(L, 3); else if (fastcmp(field,"aiming")) diff --git a/src/m_cheat.c b/src/m_cheat.c index a62ab2e69..542860afc 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -833,19 +833,11 @@ void Command_Savecheckpoint_f(void) REQUIRE_INLEVEL; REQUIRE_SINGLEPLAYER; - players[consoleplayer].starposttime = players[consoleplayer].realtime; - players[consoleplayer].starpostx = players[consoleplayer].mo->x>>FRACBITS; - players[consoleplayer].starposty = players[consoleplayer].mo->y>>FRACBITS; - players[consoleplayer].starpostz = players[consoleplayer].mo->floorz>>FRACBITS; - players[consoleplayer].starpostangle = players[consoleplayer].mo->angle; - players[consoleplayer].starpostscale = players[consoleplayer].mo->destscale; - if (players[consoleplayer].mo->flags2 & MF2_OBJECTFLIP) - { - players[consoleplayer].starpostscale *= -1; - players[consoleplayer].starpostz += players[consoleplayer].mo->height; - } + players[consoleplayer].respawn.pointx = players[consoleplayer].mo->x; + players[consoleplayer].respawn.pointy = players[consoleplayer].mo->y; + players[consoleplayer].respawn.pointz = players[consoleplayer].mo->floorz; - CONS_Printf(M_GetText("Temporary checkpoint created at %d, %d, %d\n"), players[consoleplayer].starpostx, players[consoleplayer].starposty, players[consoleplayer].starpostz); + CONS_Printf(M_GetText("Temporary checkpoint created at %d, %d, %d\n"), players[consoleplayer].respawn.pointx, players[consoleplayer].respawn.pointy, players[consoleplayer].respawn.pointz); } // Like M_GetAllEmeralds() but for console devmode junkies. diff --git a/src/m_menu.c b/src/m_menu.c index 0a0159e6a..f2c5076ff 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -32,6 +32,7 @@ #include "sounds.h" #include "s_sound.h" #include "i_system.h" +#include "i_threads.h" // Addfile #include "filesrch.h" @@ -126,6 +127,12 @@ typedef enum NUM_QUITMESSAGES } text_enum; +#ifdef HAVE_THREADS +I_mutex m_menu_mutex; +#endif + +M_waiting_mode_t m_waiting_mode = M_NOT_WAITING; + const char *quitmsg[NUM_QUITMESSAGES]; // Stuff for customizing the player select screen Tails 09-22-2003 @@ -1073,7 +1080,7 @@ enum FIRSTSERVERLINE }; -static menuitem_t MP_RoomMenu[] = +menuitem_t MP_RoomMenu[] = { {IT_STRING | IT_CALL, NULL, "", M_ChooseRoom, 9}, {IT_DISABLED, NULL, "", M_ChooseRoom, 18}, @@ -2893,7 +2900,6 @@ boolean M_Responder(event_t *ev) //make sure the game doesn't still think we're in a netgame. if (!Playing() && netgame && multiplayer) { - MSCloseUDPSocket(); // Clean up so we can re-open the connection later. netgame = false; multiplayer = false; } @@ -3305,6 +3311,30 @@ void M_SetupNextMenu(menu_t *menudef) { INT16 i; +#ifdef HAVE_THREADS + if (currentMenu == &MP_RoomDef || currentMenu == &MP_ConnectDef) + { + I_lock_mutex(&ms_QueryId_mutex); + { + ms_QueryId++; + } + I_unlock_mutex(ms_QueryId_mutex); + } + + if (currentMenu == &MP_ConnectDef) + { + I_lock_mutex(&ms_ServerList_mutex); + { + if (ms_ServerList) + { + free(ms_ServerList); + ms_ServerList = NULL; + } + } + I_unlock_mutex(ms_ServerList_mutex); + } +#endif/*HAVE_THREADS*/ + if (currentMenu->quitroutine) { // If you're going from a menu to itself, why are you running the quitroutine? You're not quitting it! -SH @@ -3364,6 +3394,19 @@ void M_Ticker(void) if (--vidm_testingmode == 0) setmodeneeded = vidm_previousmode + 1; } + +#ifdef HAVE_THREADS + I_lock_mutex(&ms_ServerList_mutex); + { + if (ms_ServerList) + { + CL_QueryServerList(ms_ServerList); + free(ms_ServerList); + ms_ServerList = NULL; + } + } + I_unlock_mutex(ms_ServerList_mutex); +#endif } // @@ -4844,7 +4887,7 @@ static boolean M_AddonsRefresh(void) else if (majormods && !prevmajormods) { S_StartSound(NULL, sfx_s221); - message = va("%c%s\x80\nGameplay has now been modified.\nIf you wish to play Record Attack mode, restart the game to clear existing addons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); + message = va("%c%s\x80\nYou've loaded a gameplay-modifying addon.\n\nRecord Attack has been disabled, but you\ncan still play alone in local Multiplayer.\n\nIf you wish to play Record Attack mode, restart the game to disable loaded addons.\n\n(Press a key)\n", ('\x80' + (highlightflags>>V_CHARCOLORSHIFT)), refreshdirname); prevmajormods = majormods; } @@ -8455,22 +8498,65 @@ static INT32 menuRoomIndex = 0; static void M_DrawRoomMenu(void) { + static int frame = -12; + int dot_frame; + char text[4]; + const char *rmotd; + const char *waiting_message; + + int dots; + + if (m_waiting_mode) + { + dot_frame = frame / 4; + dots = dot_frame + 3; + + strcpy(text, " "); + + if (dots > 0) + { + if (dot_frame < 0) + dot_frame = 0; + + strncpy(&text[dot_frame], "...", min(dots, 3 - dot_frame)); + } + + if (++frame == 12) + frame = -12; + + currentMenu->menuitems[0].text = text; + } // use generic drawer for cursor, items and title M_DrawGenericMenu(); V_DrawString(currentMenu->x - 16, currentMenu->y, highlightflags, M_GetText("Select a room")); - M_DrawTextBox(144, 24, 20, 20); + if (m_waiting_mode == M_NOT_WAITING) + { + M_DrawTextBox(144, 24, 20, 20); - if (itemOn == 0) - rmotd = M_GetText("Don't connect to the Master Server."); - else - rmotd = room_list[itemOn-1].motd; + if (itemOn == 0) + rmotd = M_GetText("Don't connect to the Master Server."); + else + rmotd = room_list[itemOn-1].motd; - rmotd = V_WordWrap(0, 20*8, 0, rmotd); - V_DrawString(144+8, 32, V_ALLOWLOWERCASE|V_RETURN8, rmotd); + rmotd = V_WordWrap(0, 20*8, 0, rmotd); + V_DrawString(144+8, 32, V_ALLOWLOWERCASE|V_RETURN8, rmotd); + } + + if (m_waiting_mode) + { + // Display a little "please wait" message. + M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3); + if (m_waiting_mode == M_WAITING_VERSION) + waiting_message = "Checking for updates..."; + else + waiting_message = "Fetching room info..."; + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, waiting_message); + V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait."); + } } static void M_DrawConnectMenu(void) @@ -8550,6 +8636,14 @@ static void M_DrawConnectMenu(void) localservercount = serverlistcount; M_DrawGenericMenu(); + + if (m_waiting_mode) + { + // Display a little "please wait" message. + M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Searching for servers..."); + V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait."); + } } static boolean M_CancelConnect(void) @@ -8631,10 +8725,10 @@ void M_SortServerList(void) #ifndef NONET #ifdef UPDATE_ALERT -static boolean M_CheckMODVersion(void) +static boolean M_CheckMODVersion(int id) { char updatestring[500]; - const char *updatecheck = GetMODVersion(); + const char *updatecheck = GetMODVersion(id); if(updatecheck) { sprintf(updatestring, UPDATE_ALERT_STRING, VERSIONSTRING, updatecheck); @@ -8643,7 +8737,62 @@ static boolean M_CheckMODVersion(void) } else return true; } -#endif + +#ifdef HAVE_THREADS +static void +Check_new_version_thread (int *id) +{ + int hosting; + int okay; + + okay = 0; + + if (M_CheckMODVersion(*id)) + { + I_lock_mutex(&ms_QueryId_mutex); + { + okay = ( *id == ms_QueryId ); + } + I_unlock_mutex(ms_QueryId_mutex); + + if (okay) + { + I_lock_mutex(&m_menu_mutex); + { + m_waiting_mode = M_WAITING_ROOMS; + hosting = ( currentMenu->prevMenu == &MP_ServerDef ); + } + I_unlock_mutex(m_menu_mutex); + + GetRoomsList(hosting, *id); + } + } + else + { + I_lock_mutex(&ms_QueryId_mutex); + { + okay = ( *id == ms_QueryId ); + } + I_unlock_mutex(ms_QueryId_mutex); + } + + if (okay) + { + I_lock_mutex(&m_menu_mutex); + { + if (m_waiting_mode) + { + m_waiting_mode = M_NOT_WAITING; + MP_RoomMenu[0].text = ""; + } + } + I_unlock_mutex(m_menu_mutex); + } + + free(id); +} +#endif/*HAVE_THREADS*/ +#endif/*UPDATE_ALERT*/ static void M_ConnectMenu(INT32 choice) { @@ -8672,18 +8821,21 @@ static void M_ConnectMenuModChecks(INT32 choice) if (modifiedgame) { - M_StartMessage(M_GetText("Addons are currently loaded.\n\nYou will only be able to join a server if\nit has the same ones loaded in the same order, which may be unlikely.\n\nIf you wish to play on other servers,\nrestart the game to clear existing addons.\n\n(Press a key)\n"),M_ConnectMenu,MM_EVENTHANDLER); + M_StartMessage(M_GetText("You have addons loaded.\nYou won't be able to join netgames!\n\nTo play online, restart the game\nand don't load any addons.\nSRB2Kart will automatically add\neverything you need when you join.\n\n(Press a key)\n"),M_ConnectMenu,MM_EVENTHANDLER); return; } M_ConnectMenu(-1); } -static UINT32 roomIds[NUM_LIST_ROOMS]; +UINT32 roomIds[NUM_LIST_ROOMS]; static void M_RoomMenu(INT32 choice) { INT32 i; +#ifdef HAVE_THREADS + int *id; +#endif (void)choice; @@ -8696,34 +8848,47 @@ static void M_RoomMenu(INT32 choice) if (rendermode == render_soft) I_FinishUpdate(); // page flip or blit buffer - if (GetRoomsList(currentMenu == &MP_ServerDef) < 0) - return; - -#ifdef UPDATE_ALERT - if (!M_CheckMODVersion()) - return; -#endif - for (i = 1; i < NUM_LIST_ROOMS+1; ++i) MP_RoomMenu[i].status = IT_DISABLED; memset(roomIds, 0, sizeof(roomIds)); - for (i = 0; room_list[i].header.buffer[0]; i++) - { - if(*room_list[i].name != '\0') - { - MP_RoomMenu[i+1].text = room_list[i].name; - roomIds[i] = room_list[i].id; - MP_RoomMenu[i+1].status = IT_STRING|IT_CALL; - } - } - MP_RoomDef.prevMenu = currentMenu; M_SetupNextMenu(&MP_RoomDef); + +#ifdef UPDATE_ALERT +#ifdef HAVE_THREADS + m_waiting_mode = M_WAITING_VERSION; + MP_RoomMenu[0].text = ""; + + id = malloc(sizeof *id); + + I_lock_mutex(&ms_QueryId_mutex); + { + *id = ms_QueryId; + } + I_unlock_mutex(ms_QueryId_mutex); + + I_spawn_thread("check-new-version", + (I_thread_fn)Check_new_version_thread, id); +#else/*HAVE_THREADS*/ + if (M_CheckMODVersion(0)) + { + GetRoomsList(currentMenu->prevMenu == &MP_ServerDef, 0); + } +#endif/*HAVE_THREADS*/ +#endif/*UPDATE_ALERT*/ } static void M_ChooseRoom(INT32 choice) { +#ifdef HAVE_THREADS + I_lock_mutex(&ms_QueryId_mutex); + { + ms_QueryId++; + } + I_unlock_mutex(ms_QueryId_mutex); +#endif + if (choice == 0) ms_RoomId = -1; else diff --git a/src/m_menu.h b/src/m_menu.h index 90cd10823..8b5582d66 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -21,7 +21,7 @@ #include "f_finale.h" // for ttmode_enum #include "i_threads.h" #include "mserv.h" -#include "r_things.h" // for SKINNAMESIZE +#include "r_skins.h" // for SKINNAMESIZE // Compatibility with old-style named NiGHTS replay files. #define OLDNREPLAYNAME @@ -339,6 +339,9 @@ typedef struct menuitem_s extern menuitem_t MP_RoomMenu[]; extern UINT32 roomIds[NUM_LIST_ROOMS]; +extern menuitem_t MP_RoomMenu[]; +extern UINT32 roomIds[NUM_LIST_ROOMS]; + typedef struct menu_s { UINT32 menuid; // ID to encode menu type and hierarchy diff --git a/src/p_enemy.c b/src/p_enemy.c index a2a752881..fb895759c 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -28,6 +28,7 @@ #include "k_kart.h" // SRB2kart #include "k_waypoint.h" #include "k_battle.h" +#include "k_respawn.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -943,10 +944,10 @@ void A_Look(mobj_t *actor) if (LUA_CallAction("A_Look", actor)) return; - if (!P_LookForPlayers(actor, locvar1 & 65535, false , FixedMul((locvar1 >> 16)*FRACUNIT, actor->scale))) + if (leveltime < starttime) // SRB2kart - no looking before race starts return; - if (leveltime < starttime) // SRB2kart - no looking before race starts + if (!P_LookForPlayers(actor, locvar1 & 65535, false , FixedMul((locvar1 >> 16)*FRACUNIT, actor->scale))) return; // go into chase state @@ -4478,10 +4479,10 @@ void A_BubbleSpawn(mobj_t *actor) if (!(actor->eflags & MFE_UNDERWATER)) { // Don't draw or spawn bubbles above water - actor->flags2 |= MF2_DONTDRAW; + actor->drawflags |= MFD_DONTDRAW; return; } - actor->flags2 &= ~MF2_DONTDRAW; + actor->drawflags &= ~MFD_DONTDRAW; if (!(actor->flags2 & MF2_AMBUSH)) { @@ -4611,9 +4612,9 @@ void A_BubbleCheck(mobj_t *actor) return; if (actor->eflags & MFE_UNDERWATER) - actor->flags2 &= ~MF2_DONTDRAW; // underwater so draw + actor->drawflags &= ~MFD_DONTDRAW; // underwater so draw else - actor->flags2 |= MF2_DONTDRAW; // above water so don't draw + actor->drawflags |= MFD_DONTDRAW; // above water so don't draw } // Function: A_AttractChase @@ -4716,9 +4717,9 @@ void A_AttractChase(mobj_t *actor) // Rings flicker before disappearing if (actor->fuse && actor->fuse < 5*TICRATE && (leveltime & 1)) - actor->flags2 |= MF2_DONTDRAW; + actor->drawflags |= MFD_DONTDRAW; else - actor->flags2 &= ~MF2_DONTDRAW; + actor->drawflags &= ~MFD_DONTDRAW; // spilled rings have ghost trails and get capped to a certain speed if (actor->type == (mobjtype_t)actor->info->reactiontime) @@ -4952,9 +4953,9 @@ void A_ThrownRing(mobj_t *actor) // spilled rings (and thrown bounce) flicker before disappearing if (leveltime & 1 && actor->fuse > 0 && actor->fuse < 2*TICRATE && actor->type != MT_THROWNGRENADE) - actor->flags2 |= MF2_DONTDRAW; + actor->drawflags |= MFD_DONTDRAW; else - actor->flags2 &= ~MF2_DONTDRAW; + actor->drawflags &= ~MFD_DONTDRAW; if (actor->tracer && actor->tracer->health <= 0) P_SetTarget(&actor->tracer, NULL); @@ -6665,11 +6666,8 @@ void A_MixUp(mobj_t *actor) INT32 transspeed; //player speed // Starpost stuff - INT16 starpostx, starposty, starpostz; + fixed_t starpostx, starposty, starpostz; INT32 starpostnum; - tic_t starposttime; - angle_t starpostangle; - fixed_t starpostscale; INT32 mflags2; @@ -6713,24 +6711,21 @@ void A_MixUp(mobj_t *actor) angle = players[one].mo->angle; drawangle = players[one].drawangle; - starpostx = players[one].starpostx; - starposty = players[one].starposty; - starpostz = players[one].starpostz; - starpostangle = players[one].starpostangle; - starpostscale = players[one].starpostscale; + starpostx = players[one].respawn.pointx; + starposty = players[one].respawn.pointy; + starpostz = players[one].respawn.pointz; starpostnum = players[one].starpostnum; - starposttime = players[one].starposttime; mflags2 = players[one].mo->flags2; P_MixUp(players[one].mo, players[two].mo->x, players[two].mo->y, players[two].mo->z, players[two].mo->angle, - players[two].starpostx, players[two].starposty, players[two].starpostz, - players[two].starpostnum, players[two].starposttime, players[two].starpostangle, - players[two].starpostscale, players[two].drawangle, players[two].mo->flags2); + players[two].respawn.pointx, players[two].respawn.pointy, players[two].respawn.pointz, + players[two].starpostnum, 0, 0, + FRACUNIT, players[two].drawangle, players[two].mo->flags2); P_MixUp(players[two].mo, x, y, z, angle, starpostx, starposty, starpostz, - starpostnum, starposttime, starpostangle, - starpostscale, drawangle, mflags2); + starpostnum, 0, 0, + FRACUNIT, drawangle, mflags2); //carry set after mixup. Stupid P_ResetPlayer() takes away some of the stuff we look for... //but not all of it! So we need to make sure they aren't set wrong or anything. @@ -6753,11 +6748,8 @@ void A_MixUp(mobj_t *actor) INT32 transspeed[MAXPLAYERS]; //player speed // Star post stuff - INT16 spposition[MAXPLAYERS][3]; + fixed_t spposition[MAXPLAYERS][3]; INT32 starpostnum[MAXPLAYERS]; - tic_t starposttime[MAXPLAYERS]; - angle_t starpostangle[MAXPLAYERS]; - fixed_t starpostscale[MAXPLAYERS]; INT32 flags2[MAXPLAYERS]; @@ -6789,13 +6781,10 @@ void A_MixUp(mobj_t *actor) transspeed[counter] = players[i].speed; transtracer[counter] = players[i].mo->tracer; - spposition[counter][0] = players[i].starpostx; - spposition[counter][1] = players[i].starposty; - spposition[counter][2] = players[i].starpostz; + spposition[counter][0] = players[i].respawn.pointx; + spposition[counter][1] = players[i].respawn.pointy; + spposition[counter][2] = players[i].respawn.pointz; starpostnum[counter] = players[i].starpostnum; - starposttime[counter] = players[i].starposttime; - starpostangle[counter] = players[i].starpostangle; - starpostscale[counter] = players[i].starpostscale; flags2[counter] = players[i].mo->flags2; @@ -6835,8 +6824,8 @@ void A_MixUp(mobj_t *actor) P_MixUp(players[i].mo, position[teleportfrom][0], position[teleportfrom][1], position[teleportfrom][2], anglepos[teleportfrom][0], spposition[teleportfrom][0], spposition[teleportfrom][1], spposition[teleportfrom][2], - starpostnum[teleportfrom], starposttime[teleportfrom], starpostangle[teleportfrom], - starpostscale[teleportfrom], anglepos[teleportfrom][1], flags2[teleportfrom]); + starpostnum[teleportfrom], 0, 0, + FRACUNIT, anglepos[teleportfrom][1], flags2[teleportfrom]); //...carry after. same reasoning. players[i].powers[pw_carry] = transcarry[teleportfrom]; @@ -9812,7 +9801,7 @@ void A_SPBChase(mobj_t *actor) if (players[i].mo->health <= 0) continue; // dead - if (players[i].kartstuff[k_respawn]) + if (players[i].respawn.state != RESPAWNST_NONE) continue;*/ // respawning if (players[i].kartstuff[k_position] < bestrank) @@ -9989,7 +9978,7 @@ void A_SPBChase(mobj_t *actor) actor->lastlook = -1; // Just make sure this is reset - if (!player || !player->mo || player->mo->health <= 0 || player->kartstuff[k_respawn]) + if (!player || !player->mo || player->mo->health <= 0 || (player->respawn.state != RESPAWNST_NONE)) { // No one there? Completely STOP. actor->momx = actor->momy = actor->momz = 0; @@ -10274,7 +10263,7 @@ void A_RandomShadowFrame(mobj_t *actor) fake->destscale = FRACUNIT*3/2; fake->angle = actor->angle; fake->tics = -1; - actor->flags2 |= MF2_DONTDRAW; + actor->drawflags |= MFD_DONTDRAW; actor->extravalue1 = 1; } diff --git a/src/p_inter.c b/src/p_inter.c index e82aae171..8b90dc1c6 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -33,6 +33,7 @@ #include "k_kart.h" #include "k_battle.h" #include "k_pwrlv.h" +#include "k_grandprix.h" // CTF player names #define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : "" @@ -902,71 +903,146 @@ void P_CheckPointLimit(void) // Checks whether or not to end a race netgame. boolean P_CheckRacers(void) { - INT32 i, j, numplayersingame = 0, numexiting = 0; + UINT8 i; + UINT8 numplayersingame = 0; + UINT8 numexiting = 0; + boolean eliminatelast = cv_karteliminatelast.value; + boolean everyonedone = true; + boolean eliminatebots = false; boolean griefed = false; // Check if all the players in the race have finished. If so, end the level. for (i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[i] || players[i].spectator || players[i].exiting || players[i].bot || !players[i].lives) - continue; + if (nospectategrief[i] != -1) // prevent spectate griefing + { + griefed = true; + } - break; + if (!playeringame[i] || players[i].spectator || players[i].lives <= 0) // Not playing + { + // Y'all aren't even playing + continue; + } + + numplayersingame++; + + if (players[i].exiting || (players[i].pflags & PF_TIMEOVER)) + { + numexiting++; + } + else + { + if (players[i].bot) + { + // Isn't a human, thus doesn't matter. (Sorry, robots.) + // Set this so that we can check for bots that need to get eliminated, though! + eliminatebots = true; + continue; + } + + everyonedone = false; + } } - if (i == MAXPLAYERS) // finished + // If we returned here with bots left, then the last place bot may have a chance to finish the map and NOT get time over. + // Not that it affects anything, they didn't make the map take longer or even get any points from it. But... it's a bit inconsistent! + // So if there's any bots, we'll let the game skip this, continue onto calculating eliminatelast, THEN we return true anyway. + if (everyonedone && !eliminatebots) { + // Everyone's finished, we're done here! racecountdown = exitcountdown = 0; return true; } - for (j = 0; j < MAXPLAYERS; j++) + if (numplayersingame <= 1) { - if (nospectategrief[j] != -1) // prevent spectate griefing - griefed = true; - if (!playeringame[j] || players[j].spectator) - continue; - numplayersingame++; - if (players[j].exiting) - numexiting++; + // Never do this without enough players. + eliminatelast = false; + } + else + { + if (grandprixinfo.gp == true) + { + // Always do this in GP + eliminatelast = true; + } + else if (griefed) + { + // Don't do this if someone spectated + eliminatelast = false; + } } - if (cv_karteliminatelast.value && numplayersingame > 1 && !griefed) + if (eliminatelast == true && (numplayersingame <= numexiting-1)) { - // check if we just got unlucky and there was only one guy who was a problem - for (j = i+1; j < MAXPLAYERS; j++) + // Everyone's done playing but one guy apparently. + // Just kill everyone who is still playing. + + for (i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[j] || players[j].spectator || players[j].exiting || !players[j].lives) + if (!playeringame[i] || players[i].spectator || players[i].lives <= 0) // Not playing + { + // Y'all aren't even playing continue; - break; + } + + if (players[i].exiting || (players[i].pflags & PF_TIMEOVER)) + { + // You're done, you're free to go. + continue; + } + + P_DoTimeOver(&players[i]); } - if (j == MAXPLAYERS) // finish anyways, force a time over - { - P_DoTimeOver(&players[i]); - racecountdown = exitcountdown = 0; - return true; - } + // Everyone should be done playing at this point now. + racecountdown = exitcountdown = 0; + return true; } - if (!racecountdown) // Check to see if the winners have finished, to set countdown. + if (everyonedone) { + // See above: there might be bots that are still going, but all players are done, so we can exit now. + racecountdown = exitcountdown = 0; + return true; + } + + // SO, we're not done playing. + // Let's see if it's time to start the death counter! + + if (!racecountdown) + { + // If the winners are all done, then start the death timer. UINT8 winningpos = 1; winningpos = max(1, numplayersingame/2); if (numplayersingame % 2) // any remainder? + { winningpos++; + } if (numexiting >= winningpos) - racecountdown = (((netgame || multiplayer) ? cv_countdowntime.value : 30)*TICRATE) + 1; // 30 seconds to finish, get going! + { + tic_t countdown = 30*TICRATE; // 30 seconds left to finish, get going! + + if (netgame) + { + // Custom timer + countdown = cv_countdowntime.value * TICRATE; + } + + racecountdown = countdown + 1; + } } - if (numplayersingame < 2) // reset nospectategrief in free play + // We're still playing, but no one else is, so we need to reset spectator griefing. + if (numplayersingame <= 1) { - for (j = 0; j < MAXPLAYERS; j++) - nospectategrief[j] = -1; + memset(nospectategrief, -1, sizeof (nospectategrief)); } + // Turns out we're still having a good time & playing the game, we didn't have to do anything :) return false; } @@ -1068,7 +1144,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget { if (metalrecording) // Ack! Metal Sonic shouldn't die! Cut the tape, end recording! G_StopMetalRecording(true); - target->flags2 &= ~MF2_DONTDRAW; + + target->drawflags &= ~MFD_DONTDRAW; } // if killed by a player @@ -1076,12 +1153,28 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget { if (target->flags & MF_MONITOR || target->type == MT_RANDOMITEM) { + UINT8 i; + P_SetTarget(&target->target, source); source->player->numboxes++; - if (cv_itemrespawn.value && (netgame || multiplayer)) + for (i = 0; i < MAXPLAYERS; i++) { - target->fuse = cv_itemrespawntime.value*TICRATE + 2; // Random box generation + if (&players[i] == source->player) + { + continue; + } + + if (playeringame[i] && !players[i].spectator && players[i].lives != 0) + { + break; + } + } + + if (i < MAXPLAYERS) + { + // Respawn items in multiplayer, don't respawn them when alone + target->fuse = 2*TICRATE + 2; } } } @@ -1096,44 +1189,6 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget target->standingslope = NULL; target->pmomz = 0; - if (!target->player->bot && !G_IsSpecialStage(gamemap) && G_GametypeUsesLives()) - { - if (!(target->player->pflags & PF_FINISHED)) - target->player->lives -= 1; // Lose a life Tails 03-11-2000 - - if (target->player->lives <= 0) // Tails 03-14-2000 - { - boolean gameovermus = false; - if ((netgame || multiplayer) && G_GametypeUsesCoopLives() && (cv_cooplives.value != 1)) - { - INT32 i; - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - continue; - - if (players[i].lives > 0) - break; - } - if (i == MAXPLAYERS) - gameovermus = true; - } - else if (P_IsLocalPlayer(target->player)) - gameovermus = true; - - if (gameovermus) // Yousa dead now, Okieday? Tails 03-14-2000 - S_ChangeMusicEx("_gover", 0, 0, 0, (2*MUSICRATE) - (MUSICRATE/25), 0); // 1.96 seconds - //P_PlayJingle(target->player, JT_GOVER); // can't be used because incompatible with track fadeout - - if (!(netgame || multiplayer || demo.playback || demo.recording || metalrecording || modeattacking) && numgameovers < maxgameovers) - { - numgameovers++; - if ((!modifiedgame || savemoddata) && cursaveslot > 0) - G_SaveGameOver((UINT32)cursaveslot, (target->player->continues <= 0)); - } - } - } - target->player->playerstate = PST_DEAD; if (target->player == &players[consoleplayer]) @@ -1619,7 +1674,7 @@ static void P_KillPlayer(player_t *player, mobj_t *source, INT32 damage) { mobj_t *boom; player->mo->flags |= (MF_NOGRAVITY|MF_NOCLIP); - player->mo->flags2 |= MF2_DONTDRAW; + player->mo->drawflags |= MFD_DONTDRAW; boom = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_FZEROBOOM); boom->scale = player->mo->scale; boom->angle = player->mo->angle; @@ -1783,6 +1838,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da || inflictor->type == MT_SMK_THWOMP || inflictor->player)) { player->kartstuff[k_sneakertimer] = 0; + player->kartstuff[k_numsneakers] = 0; K_SpinPlayer(player, source, 1, inflictor, false); K_KartPainEnergyFling(player); diff --git a/src/p_map.c b/src/p_map.c index 4c6715983..5f2bcc7dc 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -26,6 +26,7 @@ #include "w_wad.h" #include "k_kart.h" // SRB2kart 011617 #include "k_collide.h" +#include "k_respawn.h" #include "hu_stuff.h" // SRB2kart #include "i_system.h" // SRB2kart @@ -329,39 +330,10 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) // This makes it a bit more interesting & unique than just being a speed boost in a pre-defined direction if (savemomx || savemomy) { - angle_t momang; - INT32 angoffset; - boolean subtract = false; - - momang = R_PointToAngle2(0, 0, savemomx, savemomy); - - angoffset = momang; - angoffset -= spring->angle; // Subtract - - // Flip on wrong side - if ((angle_t)angoffset > ANGLE_180) - { - angoffset = InvAngle((angle_t)angoffset); - subtract = !subtract; - } - - // Fix going directly against the spring's angle sending you the wrong way - if ((spring->angle - momang) > ANGLE_90) - angoffset = ANGLE_180 - angoffset; - - // Offset is reduced to cap it (90 / 2 = max of 45 degrees) - angoffset /= 2; - - // Reduce further based on how slow your speed is compared to the spring's speed - if (finalSpeed > objectSpeed) - angoffset = FixedDiv(angoffset, FixedDiv(finalSpeed, objectSpeed)); - - if (subtract) - angoffset = (signed)(spring->angle) - angoffset; - else - angoffset = (signed)(spring->angle) + angoffset; - - finalAngle = angoffset; + finalAngle = K_ReflectAngle( + R_PointToAngle2(0, 0, savemomx, savemomy), finalAngle, + objectSpeed, finalSpeed + ); } } @@ -2823,6 +2795,100 @@ boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y) return true; } +// +// PTR_GetSpecialLines +// +static boolean PTR_GetSpecialLines(intercept_t *in) +{ + line_t *ld; + + I_Assert(in->isaline); + + ld = in->d.line; + + if (!ld->backsector) + { + return true; + } + + if (P_SpecialIsLinedefCrossType(ld->special)) + { + add_spechit(ld); + } + + return true; +} + +// +// P_HitSpecialLines +// Finds all special lines in the provided path and tries to cross them. +// For zoom tubes and respawning, which noclip but need to cross finish lines. +// +void P_HitSpecialLines(mobj_t *thing, fixed_t x, fixed_t y, fixed_t momx, fixed_t momy) +{ + fixed_t leadx, leady; + fixed_t trailx, traily; + line_t *ld = NULL; + INT32 side = 0, oldside = 0; + + I_Assert(thing != NULL); +#ifdef PARANOIA + if (P_MobjWasRemoved(thing)) + I_Error("Previously-removed Thing of type %u crashes P_CheckPosition!", thing->type); +#endif + + // reset special lines + numspechitint = 0U; + numspechit = 0U; + + // trace along the three leading corners + if (momx > 0) + { + leadx = x + thing->radius; + trailx = x - thing->radius; + } + else + { + leadx = x - thing->radius; + trailx = x + thing->radius; + } + + if (momy > 0) + { + leady = y + thing->radius; + traily = y - thing->radius; + } + else + { + leady = y - thing->radius; + traily = y + thing->radius; + } + + P_PathTraverse(leadx, leady, leadx + momx, leady + momy, PT_ADDLINES, PTR_GetSpecialLines); + P_PathTraverse(trailx, leady, trailx + momx, leady + momy, PT_ADDLINES, PTR_GetSpecialLines); + P_PathTraverse(leadx, traily, leadx + momx, traily + momy, PT_ADDLINES, PTR_GetSpecialLines); + + spechitint_copyinto(); + + // remove any duplicates that may be in spechitint + spechitint_removedups(); + + // handle any of the special lines that were crossed + while (numspechitint--) + { + ld = &lines[spechitint[numspechitint]]; + side = P_PointOnLineSide(x + momx, y + momy, ld); + oldside = P_PointOnLineSide(x, y, ld); + if (side != oldside) + { + if (ld->special) + { + P_CrossSpecialLine(ld, oldside, thing); + } + } + } +} + // // P_ThingHeightClip // Takes a valid thing and adjusts the thing->floorz, diff --git a/src/p_maputl.h b/src/p_maputl.h index 08b606833..1c071c2cd 100644 --- a/src/p_maputl.h +++ b/src/p_maputl.h @@ -53,6 +53,7 @@ void P_UnsetPrecipThingPosition(precipmobj_t *thing); void P_SetPrecipitationThingPosition(precipmobj_t *thing); void P_CreatePrecipSecNodeList(precipmobj_t *thing, fixed_t x,fixed_t y); boolean P_SceneryTryMove(mobj_t *thing, fixed_t x, fixed_t y); +void P_HitSpecialLines(mobj_t *thing, fixed_t x, fixed_t y, fixed_t momx, fixed_t momy); extern fixed_t opentop, openbottom, openrange, lowfloor, highceiling; extern pslope_t *opentopslope, *openbottomslope; diff --git a/src/p_mobj.c b/src/p_mobj.c index 080c5f1aa..c21a573ba 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -38,6 +38,7 @@ #include "k_kart.h" #include "k_battle.h" #include "k_color.h" +#include "k_respawn.h" #include "k_bot.h" static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}}; @@ -1278,8 +1279,8 @@ static void P_XYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy) player = mo->player; if (player) // valid only if player avatar { - if (FixedHypot(player->rmomx, player->rmomy) < FixedMul(STOPSPEED, mo->scale) && player->cmd.forwardmove == 0 - && !(player->mo->standingslope && (!(player->mo->standingslope->flags & SL_NOPHYSICS)) && (abs(player->mo->standingslope->zdelta) >= FRACUNIT/2))) + if (FixedHypot(player->rmomx, player->rmomy) < FixedMul(STOPSPEED, mo->scale) && K_GetForwardMove(player) == 0 + && !(player->mo->standingslope && (!(player->mo->standingslope->flags & SL_NOPHYSICS)) /*&& (abs(player->mo->standingslope->zdelta) >= FRACUNIT/2)*/)) { // if in a walking frame, stop moving if (player->panim == PA_WALK) @@ -1292,16 +1293,8 @@ static void P_XYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy) } else if (!(mo->eflags & MFE_SPRUNG)) { - if (oldx == mo->x && oldy == mo->y) // didn't go anywhere - { - mo->momx = FixedMul(mo->momx, ORIG_FRICTION); - mo->momy = FixedMul(mo->momy, ORIG_FRICTION); - } - else - { - mo->momx = FixedMul(mo->momx, mo->friction); - mo->momy = FixedMul(mo->momy, mo->friction); - } + mo->momx = FixedMul(mo->momx, mo->friction); + mo->momy = FixedMul(mo->momy, mo->friction); mo->friction = ORIG_FRICTION; } @@ -1785,7 +1778,7 @@ void P_XYMovement(mobj_t *mo) if (mo->type == MT_FLINGRING || mo->type == MT_BALLHOG || mo->type == MT_BUBBLESHIELDTRAP) return; - if (mo->player && (mo->player->kartstuff[k_spinouttimer] && !mo->player->kartstuff[k_wipeoutslow]) && mo->player->speed <= K_GetKartSpeed(mo->player, false)/2) + if (player && (player->kartstuff[k_spinouttimer] && !player->kartstuff[k_wipeoutslow]) && player->speed <= FixedDiv(20*mapobjectscale, player->kartstuff[k_offroad] + FRACUNIT)) return; //} @@ -2293,6 +2286,17 @@ boolean P_ZMovement(mobj_t *mo) mom.z = P_MobjFlip(mo)*FixedMul(5*FRACUNIT, mo->scale); else if (mo->type == MT_SPINFIRE) // elemental shield fire is another exception here ; + else if (mo->type == MT_DRIFTCLIP) + { + mom.z = -mom.z/2; + if (abs(mom.z) > 4 * mo->scale / 3) + { + K_SpawnDriftBoostClipSpark(mo); + S_StartSound(mo, sfx_tink); + } + else + mo->flags2 ^= MFD_DONTDRAW; + } else if (mo->flags & MF_MISSILE) { if (!(mo->flags & MF_NOCLIP)) @@ -2673,7 +2677,6 @@ void P_PlayerZMovement(mobj_t *mo) boolean clipmomz = !(P_CheckDeathPitCollide(mo)); mo->pmomz = 0; // We're on a new floor, don't keep doing platform movement. - mo->eflags |= MFE_JUSTHITFLOOR; // Spin Attack clipmomz = P_PlayerHitFloor(mo->player, true); @@ -3503,14 +3506,12 @@ static void P_PlayerMobjThinker(mobj_t *mobj) // momentum movement mobj->eflags &= ~MFE_JUSTSTEPPEDDOWN; - if (mobj->state-states == S_PLAY_BOUNCE_LANDING) - goto animonly; // no need for checkposition - doesn't move at ALL - // Zoom tube - if (mobj->tracer) + if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer)) { - if (mobj->player->powers[pw_carry] == CR_ZOOMTUBE || mobj->player->powers[pw_carry] == CR_ROPEHANG) + if (mobj->player->powers[pw_carry] == CR_ZOOMTUBE || mobj->player->respawn.state == RESPAWNST_MOVE) { + P_HitSpecialLines(mobj, mobj->x, mobj->y, mobj->momx, mobj->momy); P_UnsetThingPosition(mobj); mobj->x += mobj->momx; mobj->y += mobj->momy; @@ -3521,13 +3522,6 @@ static void P_PlayerMobjThinker(mobj_t *mobj) mobj->ceilingz = tmceilingz; goto animonly; } - else if (mobj->player->powers[pw_carry] == CR_MACESPIN) - { - P_CheckPosition(mobj, mobj->x, mobj->y); - mobj->floorz = tmfloorz; - mobj->ceilingz = tmceilingz; - goto animonly; - } } // Needed for gravity boots @@ -3703,16 +3697,16 @@ static void P_RingThinker(mobj_t *mobj) if (mobj->fuse < TICRATE*3) { if (leveltime & 1) - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; else - mobj->flags2 &= ~MF2_DONTDRAW; + mobj->drawflags &= ~MFD_DONTDRAW; } if (!mobj->fuse) { if (!LUAh_MobjFuse(mobj)) { - mobj->flags2 &= ~MF2_DONTDRAW; + mobj->drawflags &= ~MFD_DONTDRAW; spark = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SIGNSPARKLE); // Spawn a fancy sparkle K_MatchGenericExtraFlags(spark, mobj); spark->colorized = true; @@ -7606,6 +7600,26 @@ static void P_MobjSceneryThink(mobj_t *mobj) if (mobj->tics > 0) mobj->flags2 ^= MF2_DONTDRAW; break; + case MT_FOLLOWER: + // small thinker for follower: + // We cleanse ourselves from existence if our target player doesn't exist for whatever reason. (generally players leaving) + if (!mobj->target || P_MobjWasRemoved(mobj->target) || !mobj->target->player || mobj->target->player->spectator || mobj->target->player->followerskin < 0) + { + // Remove possible hnext list (bubble) + mobj_t *bub = mobj->hnext; + mobj_t *tmp; + + while (bub && !P_MobjWasRemoved(bub)) + { + tmp = bub->hnext; + P_RemoveMobj(bub); + bub = tmp; + } + + P_RemoveMobj(mobj); + } + + return; case MT_VWREF: case MT_VWREB: { @@ -11425,13 +11439,13 @@ void P_SceneryThinker(mobj_t *mobj) && mobj->extravalue1 > 0 && mobj->extravalue2 >= 2) { if (mobj->extravalue2 == 2) // I don't know why the normal logic doesn't work for this. - mobj->flags2 ^= MF2_DONTDRAW; + mobj->drawflags ^= MFD_DONTDRAW; else { if (mobj->fuse == mobj->extravalue2) - mobj->flags2 &= ~MF2_DONTDRAW; + mobj->drawflags &= ~MFD_DONTDRAW; else - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; } } @@ -11564,6 +11578,9 @@ static void P_DefaultMobjShadowScale(mobj_t *thing) case MT_FLOATINGITEM: thing->shadowscale = FRACUNIT/2; break; + case MT_DRIFTCLIP: + thing->shadowscale = FRACUNIT/3; + break; default: if (thing->flags & (MF_ENEMY|MF_BOSS)) thing->shadowscale = FRACUNIT; @@ -12633,18 +12650,20 @@ void P_RespawnSpecials(void) return; else if (pcount > 1) { - time = (180 - (pcount * 10))*TICRATE; + time = (120 - ((pcount-2) * 10))*TICRATE; // If the map is longer or shorter than 3 laps, then adjust ring respawn to account for this. // 5 lap courses would have more retreaded ground, while 2 lap courses would have less. if ((mapheaderinfo[gamemap-1]->numlaps != 3) && !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE)) time = (time * 3) / max(1, mapheaderinfo[gamemap-1]->numlaps); - } - // only respawn items when cv_itemrespawn is on - //if (!cv_itemrespawn.value) // TODO: remove this cvar - //return; + if (time < 10*TICRATE) + { + // Ensure it doesn't go into absurdly low values + time = 10*TICRATE; + } + } // nothing left to respawn? if (iquehead == iquetail) @@ -12801,6 +12820,8 @@ void P_SpawnPlayer(INT32 playernum) p->awayviewmobj = NULL; p->awayviewtics = 0; + P_SetTarget(&p->follower, NULL); // cleanse follower from existence + // set the scale to the mobj's destscale so settings get correctly set. if we don't, they sometimes don't. if (cv_kartdebugshrink.value && !modeattacking && !p->bot) mobj->destscale = 6*mobj->destscale/8; @@ -12824,14 +12845,11 @@ void P_SpawnPlayer(INT32 playernum) p->nightstime = sstimer; } - if (p->kartstuff[k_respawn] != 0) - p->mo->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY; - if ((gametyperules & GTR_BUMPERS)) // SRB2kart { mobj_t *overheadarrow = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height + 16*FRACUNIT, MT_PLAYERARROW); P_SetTarget(&overheadarrow->target, mobj); - overheadarrow->flags2 |= MF2_DONTDRAW; + overheadarrow->drawflags |= MFD_DONTDRAW; P_SetScale(overheadarrow, mobj->destscale); if (p->spectator && pcount > 1) // HEY! No being cheap... @@ -12857,10 +12875,10 @@ void P_SpawnPlayer(INT32 playernum) P_SetTarget(&mo->target, mobj); mo->angle = (diff * (i-1)); mo->color = mobj->color; - if (mobj->flags2 & MF2_DONTDRAW) - mo->flags2 |= MF2_DONTDRAW; + if (mobj->drawflags & MFD_DONTDRAW) + mo->drawflags |= MFD_DONTDRAW; else - mo->flags2 &= ~MF2_DONTDRAW; + mo->drawflags &= ~MFD_DONTDRAW; } } } @@ -12970,6 +12988,11 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing) else z = floor; + if (p->respawn.state != RESPAWNST_NONE) + { + z += K_RespawnOffset(p, (mthing->options & MTF_OBJECTFLIP)); + } + if (z < floor) z = floor; else if (z > ceilingspawn) @@ -13007,18 +13030,18 @@ void P_MovePlayerToStarpost(INT32 playernum) mobj_t *mobj = p->mo; I_Assert(mobj != NULL); + K_DoIngameRespawn(p); + P_UnsetThingPosition(mobj); - mobj->x = p->starpostx << FRACBITS; - mobj->y = p->starposty << FRACBITS; + mobj->x = p->respawn.pointx; + mobj->y = p->respawn.pointy; P_SetThingPosition(mobj); sector = R_PointInSubsector(mobj->x, mobj->y)->sector; floor = P_GetSectorFloorZAt (sector, mobj->x, mobj->y); ceiling = P_GetSectorCeilingZAt(sector, mobj->x, mobj->y); - z = p->starpostz << FRACBITS; - - P_SetScale(mobj, (mobj->destscale = abs(p->starpostscale))); + z = p->respawn.pointz; if (p->starpostscale < 0) { @@ -13041,12 +13064,7 @@ void P_MovePlayerToStarpost(INT32 playernum) mobj->z = z; - mobj->angle = p->starpostangle; - P_AfterPlayerSpawn(playernum); - - if (!(netgame || multiplayer)) - leveltime = p->starposttime; } fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale) diff --git a/src/p_mobj.h b/src/p_mobj.h index 7e36fda90..8ea2f2279 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -108,7 +108,7 @@ typedef enum // Don't use the blocklinks (inert but displayable) MF_NOBLOCKMAP = 1<<4, // Thin, paper-like collision bound (for visual equivalent, see FF_PAPERSPRITE) - MF_PAPERCOLLISION = 1<<5, + MF_PAPERCOLLISION = 1<<5, // You can push this object. It can activate switches and things by pushing it on top. MF_PUSHABLE = 1<<6, // Object is a boss. @@ -159,7 +159,7 @@ typedef enum MF_GRENADEBOUNCE = 1<<28, // Run the action thinker on spawn. MF_RUNSPAWNFUNC = 1<<29, - // Don't remap in Encore mode. + // Don't remap in Encore mode. (Not a drawflag so that it's settable by mobjinfo.) MF_DONTENCOREMAP = 1<<30, // free: 1<<31 } mobjflag_t; @@ -169,7 +169,7 @@ typedef enum MF2_AXIS = 1, // It's a NiGHTS axis! (For faster checking) MF2_TWOD = 1<<1, // Moves like it's in a 2D level MF2_DONTRESPAWN = 1<<2, // Don't respawn this object! - MF2_DONTDRAW = 1<<3, // Don't generate a vissprite + // free: 1<<3 MF2_AUTOMATIC = 1<<4, // Thrown ring has automatic properties MF2_RAILRING = 1<<5, // Thrown ring has rail properties MF2_BOUNCERING = 1<<6, // Thrown ring has bounce properties @@ -185,7 +185,7 @@ typedef enum MF2_JUSTATTACKED = 1<<16, // can be pushed by other moving mobjs MF2_FIRING = 1<<17, // turret fire MF2_SUPERFIRE = 1<<18, // Firing something with Super Sonic-stopping properties. Or, if mobj has MF_MISSILE, this is the actual fire from it. - MF2_SHADOW = 1<<19, // Fuzzy draw, makes targeting harder. + // free: 1<<19 MF2_STRONGBOX = 1<<20, // Flag used for "strong" random monitors. MF2_OBJECTFLIP = 1<<21, // Flag for objects that always have flipped gravity. MF2_SKULLFLY = 1<<22, // Special handling: skull in flight. @@ -248,14 +248,43 @@ typedef enum MFE_TRACERANGLE = 1<<11, // SRB2Kart: The mobj just hit & bounced off a wall, this is cleared on next frame MFE_JUSTBOUNCEDWALL = 1<<12, - // SRB2Kart: Splitscreen sprite display; very wasteful but I couldn't think of another way to do it... - MFE_DRAWONLYFORP1 = 1<<13, - MFE_DRAWONLYFORP2 = 1<<14, - MFE_DRAWONLYFORP3 = 1<<15, - MFE_DRAWONLYFORP4 = 1<<15, // WELP, just merge drawflags later so this isn't a problem :V // free: to and including 1<<15 } mobjeflag_t; +// +// Mobj drawing flags +// Set by hex, to make masking shenanigans easier to keep track of. +// +typedef enum +{ + // Don't generate a vissprite for individual screens + MFD_DONTDRAWP1 = 0x0001, + MFD_DONTDRAWP2 = 0x0002, + MFD_DONTDRAWP3 = 0x0004, + MFD_DONTDRAWP4 = 0x0008, + // Transparency override flags + MFD_TRANS10 = 0x0010, + MFD_TRANS20 = 0x0020, + MFD_TRANS30 = 0x0030, + MFD_TRANS40 = 0x0040, + MFD_TRANS50 = 0x0050, + MFD_TRANS60 = 0x0060, + MFD_TRANS70 = 0x0070, + MFD_TRANS80 = 0x0080, + MFD_TRANS90 = 0x0090, + MFD_TRANSMASK = 0x00F0, + // Brightness override flags + MFD_FULLBRIGHT = 0x0100, + MFD_SEMIBRIGHT = 0x0200, + MFD_NOBRIGHT = 0x0300, + MFD_BRIGHTMASK = 0x0F00, + // Shortcuts + MFD_DONTDRAW = MFD_DONTDRAWP1|MFD_DONTDRAWP2|MFD_DONTDRAWP3|MFD_DONTDRAWP4, + MFD_SHADOW = MFD_TRANS80|MFD_FULLBRIGHT, + MFD_TRANSSHIFT = 4, + // free: to and including 0x8000 +} mobjdflag_t; + // // PRECIPITATION flags ?! ?! ?! // @@ -315,6 +344,7 @@ typedef struct mobj_s UINT32 flags; // flags from mobjinfo tables UINT32 flags2; // MF2_ flags UINT16 eflags; // extra flags + UINT16 drawflags; // Rendering-related flags. These should not be used for game logic. void *skin; // overrides 'sprite' when non-NULL (for player bodies to 'remember' the skin) // Player and mobj sprites in multiplayer modes are modified diff --git a/src/p_saveg.c b/src/p_saveg.c index aa9b2c2fd..883238faf 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -54,14 +54,9 @@ UINT8 *save_p; // than an UINT16 typedef enum { -// RFLAGPOINT = 0x01, -// BFLAGPOINT = 0x02, - CAPSULE = 0x04, - AWAYVIEW = 0x08, - FIRSTAXIS = 0x10, - SECONDAXIS = 0x20, - FOLLOW = 0x40, - DRONE = 0x80, + AWAYVIEW = 0x01, + FOLLOWITEM = 0x02, + FOLLOWER = 0x04, } player_saveflags; static inline void P_ArchivePlayer(void) @@ -119,10 +114,6 @@ static void P_NetArchivePlayers(void) for (j = 0; j < NUMPOWERS; j++) WRITEUINT16(save_p, players[i].powers[j]); - for (j = 0; j < NUMKARTSTUFF; j++) - WRITEINT32(save_p, players[i].kartstuff[j]); - - WRITEANGLE(save_p, players[i].frameangle); WRITEUINT8(save_p, players[i].playerstate); WRITEUINT32(save_p, players[i].pflags); @@ -135,6 +126,7 @@ static void P_NetArchivePlayers(void) WRITEUINT32(save_p, players[i].score); WRITEFIXED(save_p, players[i].dashspeed); WRITESINT8(save_p, players[i].lives); + WRITEUINT8(save_p, players[i].lostlife); WRITESINT8(save_p, players[i].continues); WRITESINT8(save_p, players[i].xtralife); WRITEUINT8(save_p, players[i].gotcontinue); @@ -165,6 +157,7 @@ static void P_NetArchivePlayers(void) WRITEINT16(save_p, players[i].totalring); WRITEUINT32(save_p, players[i].realtime); WRITEUINT8(save_p, players[i].laps); + WRITEINT32(save_p, players[i].starpostnum); //////////////////// // CTF Mode Stuff // @@ -172,17 +165,6 @@ static void P_NetArchivePlayers(void) WRITEINT32(save_p, players[i].ctfteam); WRITEUINT16(save_p, players[i].gotflag); - WRITEINT32(save_p, players[i].weapondelay); - WRITEINT32(save_p, players[i].tossdelay); - - WRITEUINT32(save_p, players[i].starposttime); - WRITEINT16(save_p, players[i].starpostx); - WRITEINT16(save_p, players[i].starposty); - WRITEINT16(save_p, players[i].starpostz); - WRITEINT32(save_p, players[i].starpostnum); - WRITEANGLE(save_p, players[i].starpostangle); - WRITEFIXED(save_p, players[i].starpostscale); - WRITEANGLE(save_p, players[i].angle_pos); WRITEANGLE(save_p, players[i].old_angle_pos); @@ -219,23 +201,14 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].texttimer); WRITEUINT8(save_p, players[i].textvar); - if (players[i].capsule) - flags |= CAPSULE; - if (players[i].awayviewmobj) flags |= AWAYVIEW; - if (players[i].axis1) - flags |= FIRSTAXIS; - - if (players[i].axis2) - flags |= SECONDAXIS; - if (players[i].followmobj) - flags |= FOLLOW; + flags |= FOLLOWITEM; - if (players[i].drone) - flags |= DRONE; + if (players[i].follower) + flags |= FOLLOWER; WRITEINT16(save_p, players[i].lastsidehit); WRITEINT16(save_p, players[i].lastlinehit); @@ -269,18 +242,45 @@ static void P_NetArchivePlayers(void) // SRB2kart WRITEUINT8(save_p, players[i].kartspeed); WRITEUINT8(save_p, players[i].kartweight); + + WRITEUINT8(save_p, players[i].followerskin); + WRITEUINT8(save_p, players[i].followerready); // booleans are really just numbers eh?? + WRITEUINT8(save_p, players[i].followercolor); + if (flags & FOLLOWER) + WRITEUINT32(save_p, players[i].follower->mobjnum); + + // + for (j = 0; j < NUMKARTSTUFF; j++) + WRITEINT32(save_p, players[i].kartstuff[j]); + for (j = 0; j < MAXPREDICTTICS; j++) { WRITEINT16(save_p, players[i].lturn_max[j]); WRITEINT16(save_p, players[i].rturn_max[j]); } + WRITEANGLE(save_p, players[i].frameangle); WRITEUINT32(save_p, players[i].distancetofinish); WRITEUINT32(save_p, K_GetWaypointHeapIndex(players[i].nextwaypoint)); + WRITEUINT32(save_p, players[i].airtime); + // respawnvars_t + WRITEUINT8(save_p, players[i].respawn.state); + WRITEUINT32(save_p, K_GetWaypointHeapIndex(players[i].respawn.wp)); + WRITEFIXED(save_p, players[i].respawn.pointx); + WRITEFIXED(save_p, players[i].respawn.pointy); + WRITEFIXED(save_p, players[i].respawn.pointz); + WRITEUINT8(save_p, players[i].respawn.flip); + WRITEUINT32(save_p, players[i].respawn.timer); + WRITEUINT32(save_p, players[i].respawn.distanceleft); + WRITEUINT32(save_p, players[i].respawn.dropdash); + + // botvars_t WRITEUINT8(save_p, players[i].botvars.difficulty); + WRITEUINT8(save_p, players[i].botvars.diffincrease); + WRITEUINT8(save_p, players[i].botvars.rival); WRITEUINT32(save_p, players[i].botvars.itemdelay); WRITEUINT32(save_p, players[i].botvars.itemconfirm); WRITESINT8(save_p, players[i].botvars.turnconfirm); @@ -323,10 +323,6 @@ static void P_NetUnArchivePlayers(void) for (j = 0; j < NUMPOWERS; j++) players[i].powers[j] = READUINT16(save_p); - for (j = 0; j < NUMKARTSTUFF; j++) - players[i].kartstuff[j] = READINT32(save_p); - - players[i].frameangle = READANGLE(save_p); players[i].playerstate = READUINT8(save_p); players[i].pflags = READUINT32(save_p); @@ -339,6 +335,7 @@ static void P_NetUnArchivePlayers(void) players[i].score = READUINT32(save_p); players[i].dashspeed = READFIXED(save_p); // dashing speed players[i].lives = READSINT8(save_p); + players[i].lostlife = (boolean)READUINT8(save_p); players[i].continues = READSINT8(save_p); // continues that player has acquired players[i].xtralife = READSINT8(save_p); // Ring Extra Life counter players[i].gotcontinue = READUINT8(save_p); // got continue from stage @@ -369,6 +366,7 @@ static void P_NetUnArchivePlayers(void) players[i].totalring = READINT16(save_p); // Total number of rings obtained for Race Mode players[i].realtime = READUINT32(save_p); // integer replacement for leveltime players[i].laps = READUINT8(save_p); // Number of laps (optional) + players[i].starpostnum = READINT32(save_p); //////////////////// // CTF Mode Stuff // @@ -379,14 +377,6 @@ static void P_NetUnArchivePlayers(void) players[i].weapondelay = READINT32(save_p); players[i].tossdelay = READINT32(save_p); - players[i].starposttime = READUINT32(save_p); - players[i].starpostx = READINT16(save_p); - players[i].starposty = READINT16(save_p); - players[i].starpostz = READINT16(save_p); - players[i].starpostnum = READINT32(save_p); - players[i].starpostangle = READANGLE(save_p); - players[i].starpostscale = READFIXED(save_p); - players[i].angle_pos = READANGLE(save_p); players[i].old_angle_pos = READANGLE(save_p); @@ -465,18 +455,44 @@ static void P_NetUnArchivePlayers(void) // SRB2kart players[i].kartspeed = READUINT8(save_p); players[i].kartweight = READUINT8(save_p); + + players[i].followerskin = READUINT8(save_p); + players[i].followerready = READUINT8(save_p); + players[i].followercolor = READUINT8(save_p); + if (flags & FOLLOWER) + players[i].follower = (mobj_t *)(size_t)READUINT32(save_p); + // + for (j = 0; j < NUMKARTSTUFF; j++) + players[i].kartstuff[j] = READINT32(save_p); + for (j = 0; j < MAXPREDICTTICS; j++) { players[i].lturn_max[j] = READINT16(save_p); players[i].rturn_max[j] = READINT16(save_p); } + players[i].frameangle = READANGLE(save_p); players[i].distancetofinish = READUINT32(save_p); players[i].nextwaypoint = (waypoint_t *)(size_t)READUINT32(save_p); + players[i].airtime = READUINT32(save_p); + // respawnvars_t + players[i].respawn.state = READUINT8(save_p); + players[i].respawn.wp = (waypoint_t *)(size_t)READUINT32(save_p); + players[i].respawn.pointx = READFIXED(save_p); + players[i].respawn.pointy = READFIXED(save_p); + players[i].respawn.pointz = READFIXED(save_p); + players[i].respawn.flip = (boolean)READUINT8(save_p); + players[i].respawn.timer = READUINT32(save_p); + players[i].respawn.distanceleft = READUINT32(save_p); + players[i].respawn.dropdash = READUINT32(save_p); + + // botvars_t players[i].botvars.difficulty = READUINT8(save_p); + players[i].botvars.diffincrease = READUINT8(save_p); + players[i].botvars.rival = (boolean)READUINT8(save_p); players[i].botvars.itemdelay = READUINT32(save_p); players[i].botvars.itemconfirm = READUINT32(save_p); players[i].botvars.turnconfirm = READSINT8(save_p); @@ -1391,9 +1407,10 @@ typedef enum MD2_MIRRORED = 1<<13, MD2_ROLLANGLE = 1<<14, MD2_SHADOWSCALE = 1<<15, - MD2_WAYPOINTCAP = 1<<16, - MD2_KITEMCAP = 1<<17, - MD2_ITNEXT = 1<<18, + MD2_DRAWFLAGS = 1<<16, + MD2_WAYPOINTCAP = 1<<17, + MD2_KITEMCAP = 1<<18, + MD2_ITNEXT = 1<<19, } mobj_diff2_t; typedef enum @@ -1606,6 +1623,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) diff2 |= MD2_ROLLANGLE; if (mobj->shadowscale) diff2 |= MD2_SHADOWSCALE; + if (mobj->drawflags) + diff2 |= MD2_DRAWFLAGS; if (mobj == waypointcap) diff2 |= MD2_WAYPOINTCAP; if (mobj == kitemcap) @@ -1755,7 +1774,21 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) if (diff2 & MD2_ROLLANGLE) WRITEANGLE(save_p, mobj->rollangle); if (diff2 & MD2_SHADOWSCALE) + { WRITEFIXED(save_p, mobj->shadowscale); + WRITEUINT8(save_p, mobj->whiteshadow); + } + if (diff2 & MD2_DRAWFLAGS) + { + UINT16 df = mobj->drawflags; + + if ((mobj->drawflags & MFD_DONTDRAW) != MFD_DONTDRAW) + { + df = (mobj->drawflags & ~MFD_DONTDRAW); + } + + WRITEUINT16(save_p, df); + } WRITEUINT32(save_p, mobj->mobjnum); } @@ -2814,7 +2847,12 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) if (diff2 & MD2_ROLLANGLE) mobj->rollangle = READANGLE(save_p); if (diff2 & MD2_SHADOWSCALE) + { mobj->shadowscale = READFIXED(save_p); + mobj->whiteshadow = READUINT8(save_p); + } + if (diff2 & MD2_DRAWFLAGS) + mobj->drawflags = READUINT16(save_p); if (diff & MD_REDFLAG) { @@ -3843,56 +3881,47 @@ static void P_RelinkPointers(void) if (!(mobj->itnext = P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "itnext not found on %d\n", mobj->type); } - if (mobj->player && mobj->player->capsule) + if (mobj->player) { - temp = (UINT32)(size_t)mobj->player->capsule; - mobj->player->capsule = NULL; - if (!P_SetTarget(&mobj->player->capsule, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "capsule not found on %d\n", mobj->type); - } - if (mobj->player && mobj->player->axis1) - { - temp = (UINT32)(size_t)mobj->player->axis1; - mobj->player->axis1 = NULL; - if (!P_SetTarget(&mobj->player->axis1, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "axis1 not found on %d\n", mobj->type); - } - if (mobj->player && mobj->player->axis2) - { - temp = (UINT32)(size_t)mobj->player->axis2; - mobj->player->axis2 = NULL; - if (!P_SetTarget(&mobj->player->axis2, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "axis2 not found on %d\n", mobj->type); - } - if (mobj->player && mobj->player->awayviewmobj) - { - temp = (UINT32)(size_t)mobj->player->awayviewmobj; - mobj->player->awayviewmobj = NULL; - if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type); - } - if (mobj->player && mobj->player->nextwaypoint) - { - temp = (UINT32)(size_t)mobj->player->nextwaypoint; - mobj->player->nextwaypoint = K_GetWaypointFromIndex(temp); - if (mobj->player->nextwaypoint == NULL) + if ( mobj->player->awayviewmobj) { - CONS_Debug(DBG_GAMELOGIC, "nextwaypoint not found on %d\n", mobj->type); + temp = (UINT32)(size_t)mobj->player->awayviewmobj; + mobj->player->awayviewmobj = NULL; + if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type); + } + if (mobj->player->followmobj) + { + temp = (UINT32)(size_t)mobj->player->followmobj; + mobj->player->followmobj = NULL; + if (!P_SetTarget(&mobj->player->followmobj, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "followmobj not found on %d\n", mobj->type); + } + if (mobj->player->follower) + { + temp = (UINT32)(size_t)mobj->player->follower; + mobj->player->follower = NULL; + if (!P_SetTarget(&mobj->player->follower, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "follower not found on %d\n", mobj->type); + } + if (mobj->player->nextwaypoint) + { + temp = (UINT32)(size_t)mobj->player->nextwaypoint; + mobj->player->nextwaypoint = K_GetWaypointFromIndex(temp); + if (mobj->player->nextwaypoint == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "nextwaypoint not found on %d\n", mobj->type); + } + } + if (mobj->player->respawn.wp) + { + temp = (UINT32)(size_t)mobj->player->respawn.wp; + mobj->player->respawn.wp = K_GetWaypointFromIndex(temp); + if (mobj->player->respawn.wp == NULL) + { + CONS_Debug(DBG_GAMELOGIC, "respawn.wp not found on %d\n", mobj->type); + } } - } - if (mobj->player && mobj->player->followmobj) - { - temp = (UINT32)(size_t)mobj->player->followmobj; - mobj->player->followmobj = NULL; - if (!P_SetTarget(&mobj->player->followmobj, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "followmobj not found on %d\n", mobj->type); - } - if (mobj->player && mobj->player->drone) - { - temp = (UINT32)(size_t)mobj->player->drone; - mobj->player->drone = NULL; - if (!P_SetTarget(&mobj->player->drone, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "drone not found on %d\n", mobj->type); } } } @@ -4144,6 +4173,10 @@ static void P_NetArchiveMisc(void) WRITEUINT8(save_p, thwompsactive); WRITESINT8(save_p, spbplace); + WRITEUINT8(save_p, rainbowstartavailable); + + WRITEUINT32(save_p, introtime); + WRITEUINT32(save_p, starttime); // Is it paused? if (paused) @@ -4274,6 +4307,10 @@ static inline boolean P_NetUnArchiveMisc(void) thwompsactive = (boolean)READUINT8(save_p); spbplace = READSINT8(save_p); + rainbowstartavailable = (boolean)READUINT8(save_p); + + introtime = READUINT32(save_p); + starttime = READUINT32(save_p); // Is it paused? if (READUINT8(save_p) == 0x2f) diff --git a/src/p_setup.c b/src/p_setup.c index f1cfb3674..7fd1a6b98 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -87,6 +87,7 @@ #include "k_pwrlv.h" #include "k_waypoint.h" #include "k_bot.h" +#include "k_grandprix.h" // // Map MD5, calculated on level load. @@ -3294,7 +3295,7 @@ lumpnum_t lastloadedmaplumpnum; // for comparative savegame static void P_InitLevelSettings(void) { INT32 i; - boolean canresetlives = true; + UINT8 p = 0; leveltime = 0; @@ -3355,14 +3356,24 @@ static void P_InitLevelSettings(void) for (i = 0; i < MAXPLAYERS; i++) { - players[i].lives = 1; // SRB2Kart + if (playeringame[i] && !players[i].spectator) + p++; + + if (grandprixinfo.gp == false) + { + players[i].lives = 3; + players[i].xtralife = 0; + players[i].totalring = 0; + } players[i].realtime = racecountdown = exitcountdown = 0; curlap = bestlap = 0; // SRB2Kart + players[i].lostlife = false; players[i].gotcontinue = false; - players[i].xtralife = players[i].deadtimer = players[i].numboxes = players[i].totalring = players[i].laps = 0; + players[i].deadtimer = players[i].numboxes = players[i].laps = 0; + players[i].health = 1; players[i].aiming = 0; players[i].pflags &= ~PF_TIMEOVER; } @@ -3388,11 +3399,52 @@ static void P_InitLevelSettings(void) // hit these too players[i].pflags &= ~(PF_GAMETYPEOVER); + + // Wipe follower from existence to avoid crashes + players[i].follower = NULL; } - // SRB2Kart: map load variables - if (modeattacking) // Just play it safe and set everything + rainbowstartavailable = false; + + if (p >= 2) + rainbowstartavailable = true; + + if (p <= 2) { + introtime = 0; // No intro in Record Attack / 1v1 + } + else + { + introtime = (108) + 5; // 108 for rotation, + 5 for white fade + } + + numbulbs = 5; + + if (p > 2) + { + numbulbs += (p-2); + } + + starttime = (introtime + (3*TICRATE)) + ((2*TICRATE) + (numbulbs * bulbtime)); // Start countdown time, + buffer time + + // SRB2Kart: map load variables + if (grandprixinfo.gp == true) + { + if (G_BattleGametype()) + { + gamespeed = KARTSPEED_EASY; + } + else + { + gamespeed = grandprixinfo.gamespeed; + } + + franticitems = false; + comeback = true; + } + else if (modeattacking) + { + // Just play it safe and set everything gamespeed = KARTSPEED_HARD; franticitems = false; comeback = true; @@ -3417,11 +3469,6 @@ static void P_InitLevelSettings(void) memset(&battleovertime, 0, sizeof(struct battleovertime)); speedscramble = encorescramble = -1; - - if (!modeattacking) - { - K_UpdateMatchRaceBots(); - } } // Respawns all the mapthings and mobjs in the map from the already loaded map data. @@ -4195,6 +4242,17 @@ boolean P_LoadLevel(boolean fromnetsave) // clear special respawning que iquehead = iquetail = 0; + // Fab : 19-07-98 : start cd music for this level (note: can be remapped) + I_PlayCD((UINT8)(gamemap), false); + + // preload graphics +#ifdef HWRENDER // not win32 only 19990829 by Kin + if (rendermode != render_soft && rendermode != render_none) + { + HWR_PrepLevelCache(numtextures); + } +#endif + P_MapEnd(); // Remove the loading shit from the screen @@ -4247,6 +4305,25 @@ boolean P_LoadLevel(boolean fromnetsave) // NOW you can try to spawn in the Battle capsules, if there's not enough players for a match K_SpawnBattleCapsules(); + if (grandprixinfo.gp == true) + { + if (grandprixinfo.initalize == true) + { + K_InitGrandPrixBots(); + grandprixinfo.initalize = false; + } + else if (grandprixinfo.wonround == true) + { + K_UpdateGrandPrixBots(); + grandprixinfo.wonround = false; + } + } + else if (!modeattacking) + { + // We're in a Match Race, use simplistic randomized bots. + K_UpdateMatchRaceBots(); + } + // No render mode, stop here. if (rendermode == render_none) return true; diff --git a/src/p_slopes.c b/src/p_slopes.c index 6991ce828..df7ef45c0 100644 --- a/src/p_slopes.c +++ b/src/p_slopes.c @@ -22,6 +22,7 @@ #include "r_main.h" #include "p_maputl.h" #include "w_wad.h" +#include "k_kart.h" // K_PlayerEBrake pslope_t *slopelist = NULL; UINT16 slopecount = 0; @@ -883,6 +884,10 @@ void P_ButteredSlope(mobj_t *mo) return; // don't slide down slopes if you can't touch them or you're not affected by gravity if (mo->player) { + // SRB2Kart - spindash negates slopes + if (K_PlayerEBrake(mo->player)) + return; + // Changed in kart to only not apply physics on very slight slopes (I think about 4 degree angles) if (abs(mo->standingslope->zdelta) < FRACUNIT/21 && !(mo->player->pflags & PF_SPINNING)) return; // Don't slide on non-steep slopes unless spinning diff --git a/src/p_spec.c b/src/p_spec.c index 31ece0383..b33980f55 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -40,6 +40,7 @@ #include "k_kart.h" // SRB2kart #include "console.h" // CON_LogMessage +#include "k_respawn.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -2071,7 +2072,12 @@ static void K_HandleLapIncrement(player_t *player) { if (player) { - if ((player->starpostnum == numstarposts) || (player->laps == 0)) + if (leveltime < starttime) + { + // Will fault the player + K_DoIngameRespawn(player); + } + else if ((player->starpostnum == numstarposts) || (player->laps == 0)) { size_t i = 0; UINT8 nump = 0; @@ -2106,6 +2112,14 @@ static void K_HandleLapIncrement(player_t *player) player->karthud[khud_lapanimation] = 80; } + if (rainbowstartavailable == true) + { + S_StartSound(player->mo, sfx_s23c); + player->kartstuff[k_startboost] = 125; + K_SpawnDriftBoostExplosion(player, 3); + rainbowstartavailable = false; + } + if (netgame && player->laps >= (UINT8)cv_numlaps.value) CON_LogMessage(va(M_GetText("%s has finished the race.\n"), player_names[player-players])); @@ -2117,7 +2131,6 @@ static void K_HandleLapIncrement(player_t *player) curlap = 0; } - player->starposttime = player->realtime; player->starpostnum = 0; if (P_IsDisplayPlayer(player)) @@ -4715,19 +4728,19 @@ DoneSection2: break; case 5: // Speed pad - if (player->kartstuff[k_dashpadcooldown] != 0) + if (player->kartstuff[k_floorboost] != 0) + { + player->kartstuff[k_floorboost] = 2; break; + } i = P_FindSpecialLineFromTag(4, sector->tag, -1); if (i != -1) { - angle_t lineangle; - fixed_t linespeed; - fixed_t sfxnum; - - lineangle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y); - linespeed = sides[lines[i].sidenum[0]].textureoffset; + angle_t lineangle = R_PointToAngle2(lines[i].v1->x, lines[i].v1->y, lines[i].v2->x, lines[i].v2->y); + fixed_t linespeed = P_AproxDistance(lines[i].v2->x-lines[i].v1->x, lines[i].v2->y-lines[i].v1->y); + fixed_t playerspeed = P_AproxDistance(player->mo->momx, player->mo->momy); if (linespeed == 0) { @@ -4738,36 +4751,21 @@ DoneSection2: // SRB2Kart: Scale the speed you get from them! // This is scaled differently from other horizontal speed boosts from stuff like springs, because of how this is used for some ramp jumps. if (player->mo->scale > mapobjectscale) - linespeed = FixedMul(linespeed, mapobjectscale + (player->mo->scale - mapobjectscale)); - - if (!(lines[i].flags & ML_EFFECT4)) { - P_UnsetThingPosition(player->mo); - if (roversector) // make FOF speed pads work - { - player->mo->x = roversector->soundorg.x; - player->mo->y = roversector->soundorg.y; - } - else - { - player->mo->x = sector->soundorg.x; - player->mo->y = sector->soundorg.y; - } - P_SetThingPosition(player->mo); + linespeed = FixedMul(linespeed, mapobjectscale + (player->mo->scale - mapobjectscale)); } - P_InstaThrust(player->mo, lineangle, linespeed); + lineangle = K_ReflectAngle( + R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy), lineangle, + playerspeed, linespeed + ); + + P_InstaThrust(player->mo, lineangle, max(linespeed, 2*playerspeed)); player->kartstuff[k_dashpadcooldown] = TICRATE/3; player->kartstuff[k_pogospring] = 0; - S_StartSound(player->mo, sfx_spdpad); - - { - sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons - if (cv_kartvoices.value) - S_StartSound(player->mo, sfx_kbost1+pick); - //K_TauntVoiceTimers(player); - } + player->kartstuff[k_floorboost] = 2; + S_StartSound(player->mo, sfx_cdfm62); } break; diff --git a/src/p_telept.c b/src/p_telept.c index ba0fcbf2a..9df3c71d6 100644 --- a/src/p_telept.c +++ b/src/p_telept.c @@ -38,6 +38,9 @@ void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, const INT32 takeflags2 = MF2_TWOD|MF2_OBJECTFLIP; UINT8 i; + (void)starposttime; + (void)starpostangle; + // the move is ok, // so link the thing into its new position P_UnsetThingPosition(thing); @@ -80,12 +83,9 @@ void P_MixUp(mobj_t *thing, fixed_t x, fixed_t y, fixed_t z, angle_t angle, thing->player->speed = 0; // Starpost information - thing->player->starpostx = starpostx; - thing->player->starposty = starposty; - thing->player->starpostz = starpostz; - thing->player->starposttime = starposttime; - thing->player->starpostangle = starpostangle; - thing->player->starpostscale = starpostscale; + thing->player->respawn.pointx = starpostx; + thing->player->respawn.pointy = starposty; + thing->player->respawn.pointz = starpostz; thing->player->starpostnum = starpostnum; thing->player->drawangle = drawangle; diff --git a/src/p_tick.c b/src/p_tick.c index b4a810679..46e69c6cc 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -641,9 +641,9 @@ void P_Ticker(boolean run) if (exitcountdown > 1) exitcountdown--; - if (indirectitemcooldown > 1) + if (indirectitemcooldown > 0) indirectitemcooldown--; - if (hyubgone > 1) + if (hyubgone > 0) hyubgone--; if ((gametyperules & GTR_BUMPERS)) diff --git a/src/p_user.c b/src/p_user.c index 306685a4a..17c25e915 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -40,7 +40,6 @@ #include "st_stuff.h" #include "lua_script.h" #include "lua_hook.h" -#include "k_bot.h" // Objectplace #include "m_cheat.h" // Thok camera snap (ctrl-f "chalupa") @@ -49,7 +48,11 @@ // SRB2kart #include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems #include "k_kart.h" +#include "k_color.h" // KartColor_Opposite #include "console.h" // CON_LogMessage +#include "k_respawn.h" +#include "k_bot.h" +#include "k_grandprix.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -1197,7 +1200,7 @@ void P_SpawnShieldOrb(player_t *player) if (shieldobj->info->painstate) P_SetMobjState(shieldobj,shieldobj->info->painstate); else - shieldobj->flags2 |= MF2_SHADOW; + shieldobj->drawflags |= MFD_SHADOW; } } } @@ -1282,8 +1285,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj) ghost->sprite2 = mobj->sprite2; ghost->frame = mobj->frame; ghost->tics = -1; - ghost->frame &= ~FF_TRANSMASK; - ghost->frame |= tr_trans50<drawflags |= tr_trans50 << MFD_TRANSSHIFT; ghost->fuse = ghost->info->damage; ghost->skin = mobj->skin; ghost->standingslope = mobj->standingslope; @@ -1318,12 +1320,20 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj) // Player exits the map via sector trigger void P_DoPlayerExit(player_t *player) { + const boolean losing = K_IsPlayerLosing(player); + if (player->exiting || mapreset) return; if (P_IsLocalPlayer(player) && (!player->spectator && !demo.playback)) legitimateexit = true; + if (G_GametypeUsesLives() && losing) + { + // Remove a life from the losing player + K_PlayerLoseLife(player); + } + if ((gametyperules & GTR_CIRCUIT)) // If in Race Mode, allow { player->exiting = raceexittime+2; @@ -1334,7 +1344,7 @@ void P_DoPlayerExit(player_t *player) if (P_IsDisplayPlayer(player)) { sfxenum_t sfx_id; - if (K_IsPlayerLosing(player)) + if (losing) sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_klose].skinsound]; else sfx_id = ((skin_t *)player->mo->skin)->soundsid[S_sfx[sfx_kwin].skinsound]; @@ -1342,7 +1352,7 @@ void P_DoPlayerExit(player_t *player) } else { - if (K_IsPlayerLosing(player)) + if (losing) S_StartSound(player->mo, sfx_klose); else S_StartSound(player->mo, sfx_kwin); @@ -1352,10 +1362,6 @@ void P_DoPlayerExit(player_t *player) if (cv_inttime.value > 0) P_EndingMusic(player); - // SRB2kart 120217 - //if (!exitcountdown) - //exitcountdown = racecountdown + 8*TICRATE; - if (P_CheckRacers()) player->exiting = raceexittime+1; } @@ -1367,15 +1373,38 @@ void P_DoPlayerExit(player_t *player) else player->exiting = raceexittime+2; // Accidental death safeguard??? - //player->pflags &= ~PF_GLIDING; - /* // SRB2kart - don't need - if (player->climbing) + if (grandprixinfo.gp == true) { - player->climbing = 0; - player->pflags |= PF_JUMPED; - P_SetPlayerMobjState(player->mo, S_PLAY_ATK1); + if (player->bot) + { + // Bots are going to get harder... :) + K_IncreaseBotDifficulty(player); + } + else if (!losing) + { + const UINT8 lifethreshold = 20; + UINT8 extra = 0; + + // YOU WIN + grandprixinfo.wonround = true; + + // Increase your total rings + if (RINGTOTAL(player) > 0) + { + player->totalring += RINGTOTAL(player); + + extra = player->totalring / lifethreshold; + + if (extra > player->xtralife) + { + P_GivePlayerLives(player, extra - player->xtralife); + S_StartSound(NULL, sfx_cdfm73); + player->xtralife = extra; + } + } + } } - */ + player->powers[pw_underwater] = 0; player->powers[pw_spacetime] = 0; player->karthud[khud_cardanimation] = 0; // srb2kart: reset battle animation @@ -1966,29 +1995,25 @@ static void P_DoBubbleBreath(player_t *player) static void P_3dMovement(player_t *player) { ticcmd_t *cmd; - angle_t movepushangle, movepushsideangle; - fixed_t movepushforward = 0, movepushside = 0; + angle_t movepushangle; // Analog + //INT32 topspeed, acceleration, thrustfactor; + fixed_t movepushforward = 0; angle_t dangle; // replaces old quadrants bits //boolean dangleflip = false; // SRB2kart - toaster + //fixed_t normalspd = FixedMul(player->normalspeed, player->mo->scale); fixed_t oldMagnitude, newMagnitude; +#ifdef ESLOPE vector3_t totalthrust; totalthrust.x = totalthrust.y = 0; // I forget if this is needed totalthrust.z = FRACUNIT*P_MobjFlip(player->mo)/3; // A bit of extra push-back on slopes +#endif // ESLOPE // 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); cmd = &player->cmd; - if (player->pflags & PF_STASIS || player->kartstuff[k_spinouttimer]) // pw_introcam? - { - cmd->forwardmove = cmd->sidemove = 0; - } - - if (!(player->pflags & PF_FORCESTRAFE) && !player->kartstuff[k_pogospring]) - cmd->sidemove = 0; - 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 @@ -1996,8 +2021,6 @@ static void P_3dMovement(player_t *player) else movepushangle = player->mo->angle; - movepushsideangle = movepushangle-ANGLE_90; - // cmomx/cmomy stands for the conveyor belt speed. if (player->onconveyor == 2) // Wind/Current { @@ -2007,7 +2030,11 @@ static void P_3dMovement(player_t *player) } else if (player->onconveyor == 4 && !P_IsObjectOnGround(player->mo)) // Actual conveyor belt player->cmomx = player->cmomy = 0; - else if (player->onconveyor != 2 && player->onconveyor != 4 && player->onconveyor != 1) + else if (player->onconveyor != 2 && player->onconveyor != 4 +#ifdef POLYOBJECTS + && player->onconveyor != 1 +#endif + ) player->cmomx = player->cmomy = 0; player->rmomx = player->mo->momx - player->cmomx; @@ -2046,39 +2073,20 @@ static void P_3dMovement(player_t *player) */ //} - // When sliding, don't allow forward/back - if (player->pflags & PF_SLIDING) - cmd->forwardmove = 0; - // Do not let the player control movement if not onground. // SRB2Kart: pogo spring and speed bumps are supposed to control like you're on the ground onground = (P_IsObjectOnGround(player->mo) || (player->kartstuff[k_pogospring])); - P_SetPlayerAngle(player, player->mo->angle); + player->aiming = cmd->aiming<forwardmove); + movepushforward = K_3dKartMovement(player, onground); if (player->mo->movefactor != FRACUNIT) // Friction-scaled acceleration... movepushforward = FixedMul(movepushforward, player->mo->movefactor); - if (cmd->buttons & BT_BRAKE && !cmd->forwardmove) // SRB2kart - braking isn't instant - movepushforward /= 64; - - if (cmd->forwardmove > 0) - player->kartstuff[k_brakestop] = 0; - else if (player->kartstuff[k_brakestop] < 6) // Don't start reversing with brakes until you've made a stop first - { - if (player->speed < 8*FRACUNIT) - player->kartstuff[k_brakestop]++; - movepushforward = 0; - } - // allow very small movement while in air for gameplay - else if (!onground) - movepushforward >>= 2; // proper air movement - totalthrust.x += P_ReturnThrustX(player->mo, movepushangle, movepushforward); totalthrust.y += P_ReturnThrustY(player->mo, movepushangle, movepushforward); } @@ -2087,18 +2095,6 @@ static void P_3dMovement(player_t *player) K_MomentumToFacing(player); } - // Sideways movement - if (cmd->sidemove != 0 && !((player->exiting || mapreset) || player->kartstuff[k_spinouttimer])) - { - if (cmd->sidemove > 0) - movepushside = (cmd->sidemove * FRACUNIT/128) + FixedDiv(player->speed, K_GetKartSpeed(player, true)); - else - movepushside = (cmd->sidemove * FRACUNIT/128) - FixedDiv(player->speed, K_GetKartSpeed(player, true)); - - totalthrust.x += P_ReturnThrustX(player->mo, movepushsideangle, movepushside); - totalthrust.y += P_ReturnThrustY(player->mo, movepushsideangle, movepushside); - } - if ((totalthrust.x || totalthrust.y) && player->mo->standingslope && (!(player->mo->standingslope->flags & SL_NOPHYSICS)) && abs(player->mo->standingslope->zdelta) > FRACUNIT/2) { // Factor thrust to slope, but only for the part pushing up it! @@ -2129,23 +2125,21 @@ static void P_3dMovement(player_t *player) const fixed_t airspeedcap = (50*mapobjectscale); const fixed_t speed = R_PointToDist2(0, 0, player->mo->momx, player->mo->momy); + // If you're going too fast in the air, ease back down to a certain speed. + // Helps lots of jumps from breaking when using speed items, since you can't move in the air. if (speed > airspeedcap) { fixed_t div = 32*FRACUNIT; fixed_t newspeed; + // Make rubberbanding bots slow down faster if (K_PlayerUsesBotMovement(player)) { - fixed_t baserubberband = K_BotRubberband(player); - fixed_t rubberband = FixedMul(baserubberband, - FixedMul(baserubberband, - FixedMul(baserubberband, - baserubberband - ))); // This looks extremely goofy, but we need this really high, but at the same time, proportional. + fixed_t rubberband = K_BotRubberband(player) - FRACUNIT; - if (rubberband > FRACUNIT) + if (rubberband > 0) { - div = FixedMul(div, rubberband); + div = FixedDiv(div, FRACUNIT + (rubberband * 2)); } } @@ -2282,18 +2276,10 @@ void P_MovePlayer(player_t *player) boolean add_delta = true; // Kart: store the current turn range for later use - if ((player->mo && player->speed > 0) // Moving - || (leveltime > starttime && (cmd->buttons & BT_ACCELERATE && cmd->buttons & BT_BRAKE)) // Rubber-burn turn - || (player->kartstuff[k_respawn]) // Respawning - || (player->spectator || objectplacing)) // Not a physical player - { - player->lturn_max[leveltime%MAXPREDICTTICS] = K_GetKartTurnValue(player, KART_FULLTURN)+1; - player->rturn_max[leveltime%MAXPREDICTTICS] = K_GetKartTurnValue(player, -KART_FULLTURN)-1; - } else { - player->lturn_max[leveltime%MAXPREDICTTICS] = player->rturn_max[leveltime%MAXPREDICTTICS] = 0; - } + player->lturn_max[leveltime%MAXPREDICTTICS] = K_GetKartTurnValue(player, KART_FULLTURN)+1; + player->rturn_max[leveltime%MAXPREDICTTICS] = K_GetKartTurnValue(player, -KART_FULLTURN)-1; - if (leveltime >= starttime) + if (leveltime >= introtime) { // KART: Don't directly apply angleturn! It may have been either A) forged by a malicious client, or B) not be a smooth turn due to a player dropping frames. // Instead, turn the player only up to the amount they're supposed to turn accounting for latency. Allow exactly 1 extra turn unit to try to keep old replays synced. @@ -2409,6 +2395,7 @@ void P_MovePlayer(player_t *player) ////////////////// // If you're running fast enough, you can create splashes as you run in shallow water. +#if 0 if (!player->climbing && ((!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z + player->mo->height >= player->mo->watertop && player->mo->z <= player->mo->watertop) || (player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height >= player->mo->waterbottom && player->mo->z <= player->mo->waterbottom)) @@ -2432,6 +2419,83 @@ void P_MovePlayer(player_t *player) water->destscale = player->mo->scale; P_SetScale(water, player->mo->scale); } +#endif + + if (!player->climbing + && ((!(player->mo->eflags & MFE_VERTICALFLIP) && player->mo->z + player->mo->height >= player->mo->watertop && player->mo->z <= player->mo->watertop) + || (player->mo->eflags & MFE_VERTICALFLIP && player->mo->z + player->mo->height >= player->mo->waterbottom && player->mo->z <= player->mo->waterbottom)) + && (player->speed > runspd || (player->pflags & PF_STARTDASH)) + && player->mo->momz == 0 && !(player->pflags & PF_SLIDING) && !player->spectator) + { + fixed_t trailScale = FixedMul(FixedDiv(player->speed - runspd, K_GetKartSpeed(player, false) - runspd), mapobjectscale); + fixed_t playerTopSpeed = K_GetKartSpeed(player, false); + + if (playerTopSpeed > runspd) + trailScale = FixedMul(FixedDiv(player->speed - runspd, playerTopSpeed - runspd), mapobjectscale); + else + trailScale = mapobjectscale; // Scaling is based off difference between runspeed and top speed + + if (trailScale > 0) + { + const angle_t forwardangle = R_PointToAngle2(0, 0, player->mo->momx, player->mo->momy); + const fixed_t playerVisualRadius = player->mo->radius + 8*FRACUNIT; + const size_t numFrames = S_WATERTRAIL8 - S_WATERTRAIL1; + const statenum_t curOverlayFrame = S_WATERTRAIL1 + (leveltime % numFrames); + const statenum_t curUnderlayFrame = S_WATERTRAILUNDERLAY1 + (leveltime % numFrames); + fixed_t x1, x2, y1, y2; + mobj_t *water; + + x1 = player->mo->x + player->mo->momx + P_ReturnThrustX(player->mo, forwardangle + ANGLE_90, playerVisualRadius); + y1 = player->mo->y + player->mo->momy + P_ReturnThrustY(player->mo, forwardangle + ANGLE_90, playerVisualRadius); + x1 = x1 + P_ReturnThrustX(player->mo, forwardangle, playerVisualRadius); + y1 = y1 + P_ReturnThrustY(player->mo, forwardangle, playerVisualRadius); + + x2 = player->mo->x + player->mo->momx + P_ReturnThrustX(player->mo, forwardangle - ANGLE_90, playerVisualRadius); + y2 = player->mo->y + player->mo->momy + P_ReturnThrustY(player->mo, forwardangle - ANGLE_90, playerVisualRadius); + x2 = x2 + P_ReturnThrustX(player->mo, forwardangle, playerVisualRadius); + y2 = y2 + P_ReturnThrustY(player->mo, forwardangle, playerVisualRadius); + + // Left + // underlay + water = P_SpawnMobj(x1, y1, + ((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAILUNDERLAY].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAILUNDERLAY); + water->angle = forwardangle - ANGLE_180 - ANGLE_22h; + water->destscale = trailScale; + P_SetScale(water, trailScale); + P_SetMobjState(water, curUnderlayFrame); + + // overlay + water = P_SpawnMobj(x1, y1, + ((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAIL].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAIL); + water->angle = forwardangle - ANGLE_180 - ANGLE_22h; + water->destscale = trailScale; + P_SetScale(water, trailScale); + P_SetMobjState(water, curOverlayFrame); + + // Right + // Underlay + water = P_SpawnMobj(x2, y2, + ((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAILUNDERLAY].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAILUNDERLAY); + water->angle = forwardangle - ANGLE_180 + ANGLE_22h; + water->destscale = trailScale; + P_SetScale(water, trailScale); + P_SetMobjState(water, curUnderlayFrame); + + // Overlay + water = P_SpawnMobj(x2, y2, + ((player->mo->eflags & MFE_VERTICALFLIP) ? player->mo->waterbottom - FixedMul(mobjinfo[MT_WATERTRAIL].height, player->mo->scale) : player->mo->watertop), MT_WATERTRAIL); + water->angle = forwardangle - ANGLE_180 + ANGLE_22h; + water->destscale = trailScale; + P_SetScale(water, trailScale); + P_SetMobjState(water, curOverlayFrame); + + if (!S_SoundPlaying(player->mo, sfx_s3kdbs)) + { + const INT32 volume = (min(trailScale, FRACUNIT) * 255) / FRACUNIT; + S_StartSoundAtVolume(player->mo, sfx_s3kdbs, volume); + } + } + } // Little water sound while touching water - just a nicety. if ((player->mo->eflags & MFE_TOUCHWATER) && !(player->mo->eflags & MFE_UNDERWATER) && !player->spectator) @@ -2445,7 +2509,7 @@ void P_MovePlayer(player_t *player) //////////////////////////// // SRB2kart - Drifting smoke and fire - if ((EITHERSNEAKER(player) || player->kartstuff[k_flamedash]) + if ((player->kartstuff[k_sneakertimer] || player->kartstuff[k_flamedash]) && onground && (leveltime & 1)) K_SpawnBoostTrail(player); @@ -2747,7 +2811,7 @@ static void P_DeathThink(player_t *player) if (player->mo) { player->mo->flags |= (MF_NOGRAVITY|MF_NOCLIP); - player->mo->flags2 |= MF2_DONTDRAW; + player->mo->drawflags |= MFD_DONTDRAW; } } else @@ -2755,14 +2819,9 @@ static void P_DeathThink(player_t *player) K_KartPlayerHUDUpdate(player); - // Force respawn if idle for more than 30 seconds in shooter modes. - if (player->lives > 0 /*&& leveltime >= starttime*/) // *could* you respawn? + if (player->lives > 0 && !(player->pflags & PF_TIMEOVER) && player->deadtimer > TICRATE) { - // SRB2kart - spawn automatically after 1 second - if (player->deadtimer > ((netgame || multiplayer) - ? cv_respawntime.value*TICRATE - : TICRATE)) // don't let them change it in record attack - player->playerstate = PST_REBORN; + player->playerstate = PST_REBORN; } // Keep time rolling @@ -2891,8 +2950,7 @@ fixed_t t_cam_rotate[MAXSPLITSCREENPLAYERS] = -42; // we then throw that ticcmd garbage in the camera and make it move // redefine this -static fixed_t forwardmove[2] = {25<>16, 50<>16}; -static fixed_t sidemove[2] = {2<>16, 4<>16}; +static fixed_t forwardmove = MAXPLMOVE<>16; static fixed_t angleturn[3] = {KART_FULLTURN/2, KART_FULLTURN, KART_FULLTURN/4}; // + slow turn static ticcmd_t cameracmd; @@ -2907,7 +2965,7 @@ void P_InitCameraCmd(void) static ticcmd_t *P_CameraCmd(camera_t *cam) { - INT32 laim, th, tspeed, forward, side, axis; //i + INT32 laim, th, tspeed, forward, axis; //i const INT32 speed = 1; // these ones used for multiple conditions boolean turnleft, turnright, mouseaiming; @@ -2955,7 +3013,7 @@ static ticcmd_t *P_CameraCmd(camera_t *cam) turnright = turnright || (axis > 0); turnleft = turnleft || (axis < 0); } - forward = side = 0; + forward = 0; // use two stage accelerative turning // on the keyboard and joystick @@ -2973,12 +3031,10 @@ static ticcmd_t *P_CameraCmd(camera_t *cam) if (turnright && !(turnleft)) { cmd->angleturn = (INT16)(cmd->angleturn - (angleturn[tspeed])); - side += sidemove[1]; } else if (turnleft && !(turnright)) { cmd->angleturn = (INT16)(cmd->angleturn + (angleturn[tspeed])); - side -= sidemove[1]; } cmd->angleturn = (INT16)(cmd->angleturn - ((mousex*(encoremode ? -1 : 1)*8))); @@ -2991,9 +3047,9 @@ static ticcmd_t *P_CameraCmd(camera_t *cam) cmd->buttons |= BT_BRAKE; axis = PlayerJoyAxis(1, AXISAIM); if (PlayerInputDown(1, gc_aimforward) || (usejoystick && axis < 0)) - forward += forwardmove[1]; + forward += forwardmove; if (PlayerInputDown(1, gc_aimbackward) || (usejoystick && axis > 0)) - forward -= forwardmove[1]; + forward -= forwardmove; // fire with any button/key axis = PlayerJoyAxis(1, AXISFIRE); @@ -3034,21 +3090,12 @@ static ticcmd_t *P_CameraCmd(camera_t *cam) mousex = mousey = mlooky = 0; - if (forward > MAXPLMOVE) - forward = MAXPLMOVE; - else if (forward < -MAXPLMOVE) - forward = -MAXPLMOVE; + cmd->forwardmove += (SINT8)forward; - if (side > MAXPLMOVE) - side = MAXPLMOVE; - else if (side < -MAXPLMOVE) - side = -MAXPLMOVE; - - if (forward || side) - { - cmd->forwardmove = (SINT8)(cmd->forwardmove + forward); - cmd->sidemove = (SINT8)(cmd->sidemove + side); - } + if (cmd->forwardmove > MAXPLMOVE) + cmd->forwardmove = MAXPLMOVE; + else if (cmd->forwardmove < -MAXPLMOVE) + cmd->forwardmove = -MAXPLMOVE; lang += (cmd->angleturn<<16); @@ -3093,11 +3140,10 @@ void P_DemoCameraMovement(camera_t *cam) cam->aiming = R_PointToAngle2(0, cam->z, R_PointToDist2(cam->x, cam->y, lastp->mo->x, lastp->mo->y), lastp->mo->z + lastp->mo->scale*128*P_MobjFlip(lastp->mo)); // This is still unholy. Aim a bit above their heads. } - cam->momx = cam->momy = cam->momz = 0; + if (cmd->forwardmove != 0) { - thrustangle = cam->angle >> ANGLETOFINESHIFT; cam->x += FixedMul(cmd->forwardmove*mapobjectscale, FINECOSINE(thrustangle)); @@ -3113,7 +3159,7 @@ void P_DemoCameraMovement(camera_t *cam) awayviewmobj_hack = P_SpawnMobj(cam->x, cam->y, cam->z, MT_THOK); awayviewmobj_hack->tics = 2; - awayviewmobj_hack->flags2 |= MF2_DONTDRAW; + awayviewmobj_hack->drawflags |= MFD_DONTDRAW; democam.soundmobj = awayviewmobj_hack; @@ -3395,7 +3441,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall if (timeover) angle = mo->angle + FixedAngle(camrotate*FRACUNIT); - else if (leveltime < starttime) + else if (leveltime < introtime) angle = focusangle + FixedAngle(camrotate*FRACUNIT); else if (camstill || resetcalled || player->playerstate == PST_DEAD) angle = thiscam->angle; @@ -3418,7 +3464,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall } } - if (!resetcalled && (leveltime > starttime && timeover != 2) + if (!resetcalled && (leveltime >= introtime && timeover != 2) && ((thiscam == &camera[0] && t_cam_rotate[0] != -42) || (thiscam == &camera[1] && t_cam_rotate[1] != -42) || (thiscam == &camera[2] && t_cam_rotate[2] != -42) @@ -3733,7 +3779,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall } else if (player->exiting || timeover == 2) thiscam->momx = thiscam->momy = thiscam->momz = 0; - else if (leveltime < starttime) + else if (leveltime < introtime) { thiscam->momx = FixedMul(x - thiscam->x, camspeed); thiscam->momy = FixedMul(y - thiscam->y, camspeed); @@ -3897,8 +3943,8 @@ boolean P_SpectatorJoinGame(player_t *player) player->playerstate = PST_REBORN; //Reset away view - if (P_IsLocalPlayer(player) && g_localplayers[0] != consoleplayer) - g_localplayers[0] = consoleplayer; + if (P_IsLocalPlayer(player) && displayplayers[0] != consoleplayer) + displayplayers[0] = consoleplayer; HU_AddChatText(va(M_GetText("\x82*%s entered the game."), player_names[player-players]), false); return true; // no more player->mo, cannot continue. @@ -4007,13 +4053,28 @@ static void P_CalcPostImg(player_t *player) void P_DoTimeOver(player_t *player) { - if (netgame && player->health > 0) + if (player->pflags & PF_TIMEOVER) + { + // NO! Don't do this! + return; + } + + if (P_IsLocalPlayer(player) && !demo.playback) + { + legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p + } + + if (netgame && !player->bot) + { CON_LogMessage(va(M_GetText("%s ran out of time.\n"), player_names[player-players])); + } player->pflags |= PF_TIMEOVER; - if (P_IsLocalPlayer(player) && !demo.playback) - legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p + if (G_GametypeUsesLives()) + { + K_PlayerLoseLife(player); + } if (player->mo) { @@ -4021,8 +4082,6 @@ void P_DoTimeOver(player_t *player) P_DamageMobj(player->mo, NULL, NULL, 10000); } - player->lives = 0; - P_EndingMusic(player); if (!exitcountdown) @@ -4116,6 +4175,287 @@ static void P_ParabolicMove(mobj_t *mo, fixed_t x, fixed_t y, fixed_t z, fixed_t mo->momz = FixedDiv(dh, 2*fixConst) + FixedDiv(dz, FixedDiv(dh, fixConst/2)); } +/* set follower state with our weird hacks + the reason we do this is to avoid followers ever using actions (majormods, yikes!) + without having to touch p_mobj.c. + so we give it 1more tic and change the state when tic == 1 instead of 0 + cool beans? + cool beans. +*/ +static void P_SetFollowerState(mobj_t *f, INT32 state) +{ + + if (!f || P_MobjWasRemoved(f)) + return; // safety net + + // No, do NOT set the follower to S_NULL. Set it to S_INVISIBLE. + if (state == S_NULL) + { + state = S_INVISIBLE; + f->threshold = 1; // Threshold = 1 means stop doing anything related to setting states, so that we don't get out of S_INVISIBLE + } + + // extravalue2 stores the last "first state" we used. + // because states default to idlestates, if we use an animation that uses an "ongoing" state line, don't reset it! + // this prevents it from looking very dumb + if (state == f->extravalue2) + return; + + // we will save the state into extravalue2. + f->extravalue2 = state; + + P_SetMobjStateNF(f, state); + if (f->state->tics > 0) + f->tics++; +} + +// +//P_HandleFollower +// +//Handle the follower's spawning and moving along with the player. Do note that some of the stuff like the removal if a player doesn't exist anymore is handled in MT_FOLLOWER's thinker. +static void P_HandleFollower(player_t *player) +{ + follower_t fl; + angle_t an; + fixed_t zoffs; + fixed_t sx, sy, sz; + UINT8 color; + + fixed_t bubble; // bubble scale (0 if no bubble) + mobj_t *bmobj; // temp bubble mobj + + + if (!player->followerready) + return; // we aren't ready to perform anything follower related yet. + + // How about making sure our follower exists and is added before trying to spawn it n' all? + if (player->followerskin > numfollowers-1 || player->followerskin < -1) + { + //CONS_Printf("Follower skin invlaid. Setting to -1.\n"); + player->followerskin = -1; + return; + } + + // don't do anything if we can't have a follower to begin with. (It gets removed under those conditions) + if (player->spectator) + return; + if (player->followerskin < 0) + return; + // Before we do anything, let's be sure of where we're supposed to be + fl = followers[player->followerskin]; + + an = player->mo->angle + (fl.atangle)*ANG1; // it's aproximative but it really doesn't matter in the grand scheme of things... + zoffs = (fl.zoffs)*FRACUNIT; + bubble = fl.bubblescale; // 0 if no bubble to spawn. + + // do you like angle maths? I certainly don't... + sx = player->mo->x + FixedMul((player->mo->scale*fl.dist), FINECOSINE((an)>>ANGLETOFINESHIFT)); + sy = player->mo->y + FixedMul((player->mo->scale*fl.dist), FINESINE((an)>>ANGLETOFINESHIFT)); + + // for the z coordinate, don't be a doof like Steel and forget that MFE_VERTICALFLIP exists :P + sz = player->mo->z + FixedMul(player->mo->scale, zoffs)*P_MobjFlip(player->mo); + if (player->mo->eflags & MFE_VERTICALFLIP) + sz += fl.height*player->mo->scale; + + // finally, add a cool floating effect to the z height. + // not stolen from k_kart I swear!! + { + const fixed_t pi = (22<>ANGLETOFINESHIFT) & FINEMASK); + sz += FixedMul(player->mo->scale, sine)*P_MobjFlip(player->mo); + } + + // Set follower colour + + switch (player->followercolor) + { + case MAXSKINCOLORS: // "Match" + color = player->skincolor; + break; + case MAXSKINCOLORS+1: // "Opposite" + color = KartColor_Opposite[player->skincolor*2]; + break; + default: + + color = player->followercolor; + if (!color || color > MAXSKINCOLORS+2) // Make sure this isn't garbage + color = player->skincolor; // "Match" as fallback. + + break; + } + + + + if (!player->follower) // follower doesn't exist / isn't valid + { + //CONS_Printf("Spawning follower...\n"); + // so let's spawn one! + P_SetTarget(&player->follower, P_SpawnMobj(sx, sy, sz, MT_FOLLOWER)); + P_SetFollowerState(player->follower, fl.idlestate); + P_SetTarget(&player->follower->target, player->mo); // we need that to know when we need to disappear + player->follower->angle = player->mo->angle; + + // This is safe to only spawn it here, the follower is removed then respawned when switched. + if (bubble) + { + bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_FRONT); + P_SetTarget(&player->follower->hnext, bmobj); + P_SetTarget(&bmobj->target, player->follower); // Used to know if we have to despawn at some point. + + bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_BACK); + P_SetTarget(&player->follower->hnext->hnext, bmobj); // this seems absolutely stupid, I know, but this will make updating the momentums/flags of these a bit easier. + P_SetTarget(&bmobj->target, player->follower); // Ditto + } + + player->follower->extravalue1 = 0; // extravalue1 is used to know what "state set" to use. + /* + 0 = idle + 1 = forwards + 2 = hurt + 3 = win + 4 = lose + 5 = hitconfirm (< this one uses ->movecount as timer to know when to end, and goes back to normal states afterwards, unless hurt) + */ + } + else // follower exists, woo! + { + + // Safety net (2) + + if (P_MobjWasRemoved(player->follower)) + { + P_SetTarget(&player->follower, NULL); // Remove this and respawn one, don't crash the game if Lua decides to P_RemoveMobj this thing. + return; + } + + // first of all, handle states following the same model as above: + if (player->follower->tics == 1) + P_SetFollowerState(player->follower, player->follower->state->nextstate); + + // move the follower next to us (yes, this is really basic maths but it looks pretty damn clean in practice)! + player->follower->momx = (sx - player->follower->x)/fl.horzlag; + player->follower->momy = (sy - player->follower->y)/fl.horzlag; + player->follower->momz = (sz - player->follower->z)/fl.vertlag; + player->follower->angle = player->mo->angle; + + if (player->mo->colorized) + player->follower->color = player->mo->color; + else + player->follower->color = color; + + player->follower->colorized = player->mo->colorized; + + P_SetScale(player->follower, FixedMul(fl.scale, player->mo->scale)); + K_GenericExtraFlagsNoZAdjust(player->follower, player->mo); // Not K_MatchGenericExtraFlag because the Z adjust it has only works properly if master & mo have the same Z height. + + // Match how the player is being drawn + player->follower->drawflags = player->mo->drawflags; + + // Make the follower invisible if we no contest'd rather than removing it. No one will notice the diff seriously. + // Also make the follower invisible if we choose not to have it displayed because it isn't ours. (also quick hacky check for f12) + if (player->pflags & PF_TIMEOVER || (!cv_showfollowers.value && (!P_IsDisplayPlayer(player) || displayplayers[0] != consoleplayer) )) + player->follower->drawflags |= MFD_DONTDRAW; + + if (player->speed && (player->follower->momx || player->follower->momy)) + player->follower->angle = R_PointToAngle2(0, 0, player->follower->momx, player->follower->momy); + // if we're moving let's make the angle the direction we're moving towards. This is to avoid drifting / reverse looking awkward. + // Make sure the follower itself is also moving however, otherwise we'll be facing angle 0 + + // Finally, if the follower has bubbles, move them, set their scale, etc.... + // This is what I meant earlier by it being easier, now we can just use this weird lil loop to get the job done! + + bmobj = player->follower->hnext; // will be NULL if there's no bubble + + while (bmobj && !P_MobjWasRemoved(bmobj)) + { + // match follower's momentums and (e)flags(2). + bmobj->momx = player->follower->momx; + bmobj->momy = player->follower->momy; + bmobj->momz = player->follower->momz; + + P_SetScale(bmobj, FixedMul(bubble, player->mo->scale)); + K_GenericExtraFlagsNoZAdjust(bmobj, player->follower); + bmobj->drawflags = player->mo->drawflags; + + if (player->follower->threshold) // threshold means the follower was "despawned" with S_NULL (is actually just set to S_INVISIBLE) + P_SetMobjState(bmobj, S_INVISIBLE); // sooooo... let's do the same! + + bmobj = bmobj->hnext; // switch to other bubble layer or exit + } + + + if (player->follower->threshold) + return; // Threshold means the follower was "despanwed" with S_NULL. + + // However with how the code is factored, this is just a special case of S_INVISBLE to avoid having to add other player variables. + + + // handle follower animations. Could probably be better... + // hurt or dead + if (player->kartstuff[k_spinouttimer] || player->mo->state == &states[S_KART_SPIN] || player->mo->health <= 0) + { + player->follower->movecount = 0; // cancel hit confirm. + player->follower->angle = player->frameangle; // spin out + if (player->follower->extravalue1 != 2) + { + player->follower->extravalue1 = 2; + P_SetFollowerState(player->follower, fl.hurtstate); + } + if (player->mo->health <= 0) // if dead, follow the player's z momentum exactly so they both look like they die at the same speed. + player->follower->momz = player->mo->momz; + } + else if (player->follower->movecount) + { + if (player->follower->extravalue1 != 5) + { + player->follower->extravalue1 = 5; + P_SetFollowerState(player->follower, fl.hitconfirmstate); + } + player->follower->movecount--; + } + else if (player->speed > 10*player->mo->scale) // animation for moving fast enough + { + + if (player->follower->extravalue1 != 1) + { + player->follower->extravalue1 = 1; + P_SetFollowerState(player->follower, fl.followstate); + } + } + else // animations when nearly still. This includes winning and losing. + { + if (player->follower->extravalue1 != 0) + { + + if (player->exiting) // win/ loss animations + { + if (K_IsPlayerLosing(player)) // L + { + if (player->follower->extravalue1 != 4) + { + player->follower->extravalue1 = 4; + P_SetFollowerState(player->follower, fl.losestate); + } + } + else // W + { + if (player->follower->extravalue1 != 3) + { + player->follower->extravalue1 = 3; + P_SetFollowerState(player->follower, fl.winstate); + } + } + } + else // normal standstill + { + player->follower->extravalue1 = 0; + P_SetFollowerState(player->follower, fl.idlestate); + } + } + } + } +} + // // P_PlayerThink // @@ -4169,27 +4509,42 @@ void P_PlayerThink(player_t *player) player->awayviewtics = 0; // reset to zero } + // Run followes here. We need them to run even when we're dead to follow through what we're doing. + P_HandleFollower(player); + if (player->flashcount) player->flashcount--; if (player->awayviewtics && player->awayviewtics != -1) player->awayviewtics--; + // Track airtime + if (P_IsObjectOnGround(player->mo)) + { + player->airtime = 0; + } + else + { + player->airtime++; + } + cmd = &player->cmd; // SRB2kart // Save the dir the player is holding // to allow items to be thrown forward or backward. if (cmd->buttons & BT_FORWARD) + { player->kartstuff[k_throwdir] = 1; + } else if (cmd->buttons & BT_BACKWARD) + { player->kartstuff[k_throwdir] = -1; + } else + { player->kartstuff[k_throwdir] = 0; - - // Add some extra randomization. - if (cmd->forwardmove) - P_RandomFixed(); + } #ifdef PARANOIA if (player->playerstate == PST_REBORN) @@ -4207,7 +4562,7 @@ void P_PlayerThink(player_t *player) { if (playeringame[i] && !players[i].spectator) { - if (!players[i].exiting && players[i].lives > 0) + if (!players[i].exiting && !(players[i].pflags & PF_TIMEOVER) && players[i].lives > 0) break; } } @@ -4215,11 +4570,10 @@ void P_PlayerThink(player_t *player) if (i == MAXPLAYERS && player->exiting == raceexittime+2) // finished player->exiting = raceexittime+1; +#if 0 // If 10 seconds are left on the timer, // begin the drown music for countdown! - // SRB2Kart: despite how perfect this is, it's disabled FOR A REASON -#if 0 if (racecountdown == 11*TICRATE - 1) { if (P_IsLocalPlayer(player)) @@ -4229,12 +4583,14 @@ void P_PlayerThink(player_t *player) // If you've hit the countdown and you haven't made // it to the exit, you're a goner! - else if (racecountdown == 1 && !player->exiting && !player->spectator && player->lives > 0) + if (racecountdown == 1 && !player->spectator && !player->exiting && !(player->pflags & PF_TIMEOVER) && player->lives > 0) { P_DoTimeOver(player); if (player->playerstate == PST_DEAD) + { return; + } } } @@ -4245,12 +4601,14 @@ void P_PlayerThink(player_t *player) if (player->exiting && exitcountdown) player->exiting = 99; // SRB2kart - } - if (player->exiting == 2 || countdown2 == 2) - { - if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + if (player->exiting == 2 || exitcountdown == 2) + { + if (server) + { + SendNetXCmd(XD_EXITLEVEL, NULL, 0); + } + } } // check water content, set stuff in mobj @@ -4273,9 +4631,9 @@ void P_PlayerThink(player_t *player) if (player->playerstate == PST_DEAD) { if (player->spectator) - player->mo->flags2 |= MF2_SHADOW; + player->mo->drawflags |= MFD_SHADOW; else - player->mo->flags2 &= ~MF2_SHADOW; + player->mo->drawflags &= ~(MFD_TRANSMASK|MFD_BRIGHTMASK); P_DeathThink(player); LUAh_PlayerThink(player); return; @@ -4289,20 +4647,11 @@ void P_PlayerThink(player_t *player) player->health = 1; } -#if 0 - if ((netgame || multiplayer) && player->lives <= 0) - { - // Outside of Co-Op, replenish a user's lives if they are depleted. - // of course, this is just a cheap hack, meh... - player->lives = cv_startinglives.value; - } -#else - player->lives = 1; // SRB2Kart -#endif - // SRB2kart 010217 - if (leveltime < starttime) + if (leveltime < introtime) + { player->powers[pw_nocontrol] = 2; + } // Synchronizes the "real" amount of time spent in the level. if (!player->exiting && !stoppedclock) @@ -4377,17 +4726,34 @@ void P_PlayerThink(player_t *player) player->linkcount = 0; } - // Move around. - // Reactiontime is used to prevent movement - // for a bit after a teleport. - if (player->mo->reactiontime) + if (player->respawn.state != RESPAWNST_NONE) + { + K_RespawnChecker(player); + player->rmomx = player->rmomy = 0; + + if (player->respawn.state == RESPAWNST_DROP) + { + // Allows some turning + P_MovePlayer(player); + } + } + else if (player->mo->reactiontime) + { + // Reactiontime is used to prevent movement + // for a bit after a teleport. player->mo->reactiontime--; - else if (player->powers[pw_carry] == CR_MINECART) + } + else if (player->mo->tracer && player->mo->tracer->type == MT_TUBEWAYPOINT) { P_DoZoomTube(player); - player->rmomx = player->rmomy = 0; // no actual momentum from your controls + player->rmomx = player->rmomy = 0; P_ResetScore(player); } + else + { + // Move around. + P_MovePlayer(player); + } player->mo->movefactor = FRACUNIT; // We're not going to do any more with this, so let's change it back for the next frame. @@ -4437,7 +4803,7 @@ void P_PlayerThink(player_t *player) { if (player == &players[displayplayers[i]] && !camera[i].chase) { - gmobj->flags2 |= MF2_DONTDRAW; + gmobj->drawflags |= MFD_DONTDRAW; break; } } @@ -4485,16 +4851,16 @@ void P_PlayerThink(player_t *player) if (!(//player->pflags & PF_NIGHTSMODE || player->kartstuff[k_hyudorotimer] // SRB2kart - fixes Hyudoro not flashing when it should. || player->kartstuff[k_growshrinktimer] > 0 // Grow doesn't flash either. - || player->kartstuff[k_respawn] // Respawn timer (for drop dash effect) + || (player->respawn.state != RESPAWNST_NONE) // Respawn timer (for drop dash effect) || (player->pflags & PF_TIMEOVER) // NO CONTEST explosion || ((gametyperules & GTR_BUMPERS) && player->kartstuff[k_bumper] <= 0 && player->kartstuff[k_comebacktimer]) || leveltime < starttime)) // Level intro { if (player->powers[pw_flashing] > 0 && player->powers[pw_flashing] < K_GetKartFlashing(player) && (leveltime & 1)) - player->mo->flags2 |= MF2_DONTDRAW; + player->mo->drawflags |= MFD_DONTDRAW; else - player->mo->flags2 &= ~MF2_DONTDRAW; + player->mo->drawflags &= ~MFD_DONTDRAW; } player->pflags &= ~PF_SLIDING; @@ -4693,7 +5059,6 @@ angle_t P_GetLocalAngle(player_t *player) { // this function is from vanilla srb2. can you tell? // (hint: they have separate variables for all of this shit instead of arrays) - UINT8 i; for (i = 0; i <= splitscreen; i++) diff --git a/src/r_data.h b/src/r_data.h index 9a182d496..05fae61cb 100644 --- a/src/r_data.h +++ b/src/r_data.h @@ -93,6 +93,7 @@ extern INT16 color8to16[256]; // remap color index to highcolor extern INT16 *hicolormaps; // remap high colors to high colors.. extern CV_PossibleValue_t Color_cons_t[]; +extern CV_PossibleValue_t Followercolor_cons_t[]; // follower colours table, not a duplicate because of the "Match" option. // Load TEXTURES definitions, create lookup tables void R_LoadTextures(void); diff --git a/src/r_draw.c b/src/r_draw.c index 82040afe3..b020da989 100644 --- a/src/r_draw.c +++ b/src/r_draw.c @@ -148,6 +148,7 @@ static UINT8** translationtablecache[TT_CACHE_SIZE] = {NULL}; UINT8 skincolor_modified[MAXSKINCOLORS]; CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1]; +CV_PossibleValue_t Followercolor_cons_t[MAXSKINCOLORS+3]; // +3 to account for "Match", "Opposite" & NULL /** \brief The R_InitTranslationTables diff --git a/src/r_main.c b/src/r_main.c index 285410e4c..7a216fc33 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -166,10 +166,6 @@ static void ChaseCam_OnChange(void); static void ChaseCam2_OnChange(void); static void ChaseCam3_OnChange(void); static void ChaseCam4_OnChange(void); -static void FlipCam_OnChange(void); -static void FlipCam2_OnChange(void); -static void FlipCam3_OnChange(void); -static void FlipCam4_OnChange(void); void SendWeaponPref(void); void SendWeaponPref2(void); void SendWeaponPref3(void); @@ -180,10 +176,6 @@ consvar_t cv_chasecam = {"chasecam", "On", CV_CALL, CV_OnOff, ChaseCam_OnChange, consvar_t cv_chasecam2 = {"chasecam2", "On", CV_CALL, CV_OnOff, ChaseCam2_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_chasecam3 = {"chasecam3", "On", CV_CALL, CV_OnOff, ChaseCam3_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_chasecam4 = {"chasecam4", "On", CV_CALL, CV_OnOff, ChaseCam4_OnChange, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_flipcam = {"flipcam", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam_OnChange, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_flipcam2 = {"flipcam2", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam2_OnChange, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_flipcam3 = {"flipcam3", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam3_OnChange, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_flipcam4 = {"flipcam4", "No", CV_SAVE|CV_CALL|CV_NOINIT, CV_YesNo, FlipCam4_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_shadow = {"shadow", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_skybox = {"skybox", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -191,8 +183,6 @@ consvar_t cv_allowmlook = {"allowmlook", "Yes", CV_NETVAR, CV_YesNo, NULL, 0, NU consvar_t cv_showhud = {"showhud", "Yes", CV_CALL, CV_YesNo, R_SetViewSize, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_translucenthud = {"translucenthud", "10", CV_SAVE, translucenthud_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_translucency = {"translucency", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; - consvar_t cv_drawdist = {"drawdist", "8192", CV_SAVE, drawdist_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_drawdist_precip = {"drawdist_precip", "1024", CV_SAVE, drawdist_precip_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_fov = {"fov", "90", CV_FLOAT|CV_CALL, fov_cons_t, Fov_OnChange, 0, NULL, NULL, 0, 0, NULL}; @@ -284,26 +274,6 @@ static void ChaseCam4_OnChange(void) ; } -static void FlipCam_OnChange(void) -{ - SendWeaponPref(); -} - -static void FlipCam2_OnChange(void) -{ - SendWeaponPref2(); -} - -static void FlipCam3_OnChange(void) -{ - SendWeaponPref3(); -} - -static void FlipCam4_OnChange(void) -{ - SendWeaponPref4(); -} - // // R_PointOnSide // Traverse BSP (sub) tree, @@ -1675,16 +1645,10 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_tailspickup); CV_RegisterVar(&cv_allowmlook); CV_RegisterVar(&cv_homremoval); - CV_RegisterVar(&cv_flipcam); - CV_RegisterVar(&cv_flipcam2); - CV_RegisterVar(&cv_flipcam3); - CV_RegisterVar(&cv_flipcam4); - // Enough for dedicated server if (dedicated) return; - CV_RegisterVar(&cv_translucency); CV_RegisterVar(&cv_drawdist); CV_RegisterVar(&cv_drawdist_precip); CV_RegisterVar(&cv_fov); diff --git a/src/r_main.h b/src/r_main.h index 0f8781b19..caadf0733 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -101,7 +101,6 @@ extern consvar_t cv_chasecam[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_flipcam[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_shadow; -extern consvar_t cv_translucency; extern consvar_t cv_drawdist, v_drawdist_precip; extern consvar_t cv_fov; extern consvar_t cv_skybox; diff --git a/src/r_plane.c b/src/r_plane.c index 229109e57..01fc43f6e 100644 --- a/src/r_plane.c +++ b/src/r_plane.c @@ -319,7 +319,7 @@ static visplane_t *new_visplane(unsigned hash) visplane_t *check = freetail; if (!check) { - check = calloc(2, sizeof (*check)); + check = calloc(1, sizeof (*check)); if (check == NULL) I_Error("%s: Out of memory", "new_visplane"); // FIXME: ugly } else diff --git a/src/r_segs.c b/src/r_segs.c index d5b36d26d..f9d129900 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -176,14 +176,8 @@ static void R_DrawWallSplats(void) colfunc = colfuncs[BASEDRAWFUNC]; break; case SPLATDRAWMODE_TRANS: - if (!cv_translucency.value) - colfunc = colfuncs[BASEDRAWFUNC]; - else - { - dc_transmap = transtables + ((tr_trans50 - 1)<x, thing->y) : P_GetFFloorTopZAt(rover, thing->x, thing->y); + if CHECKZ { groundz = z; @@ -1227,8 +1228,9 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope) if (shadowslope != NULL) *shadowslope = groundslope; - return groundz; #undef CHECKZ + + return groundz; } static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, fixed_t tx, fixed_t tz) @@ -1865,19 +1867,27 @@ static void R_ProjectSprite(mobj_t *thing) vis->transmap = NULL; // specific translucency - if (!cv_translucency.value) - ; // no translucency - else if (oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW) // actually only the player should use this (temporary invisibility) - vis->transmap = transtables + ((tr_trans80-1)<frame & FF_TRANSMASK) - vis->transmap = transtables + (oldthing->frame & FF_TRANSMASK) - 0x10000; + if (thing->drawflags & MFD_TRANSMASK) // Object is forcing transparency to a specific value + vis->transmap = transtables + ((((thing->drawflags & MFD_TRANSMASK) - MFD_TRANS10) >> MFD_TRANSSHIFT) << FF_TRANSSHIFT); + else if (thing->frame & FF_TRANSMASK) + vis->transmap = transtables + ((thing->frame & FF_TRANSMASK) - FF_TRANS10); - if (oldthing->frame & FF_FULLBRIGHT || oldthing->flags2 & MF2_SHADOW || thing->flags2 & MF2_SHADOW) - vis->cut |= SC_FULLBRIGHT; - else if (oldthing->frame & FF_SEMIBRIGHT) - vis->cut |= SC_SEMIBRIGHT; + if (thing->drawflags & MFD_BRIGHTMASK) + { + if (thing->drawflags & MFD_FULLBRIGHT) + vis->cut |= SC_FULLBRIGHT; + else if (thing->drawflags & MFD_SEMIBRIGHT) + vis->cut |= SC_SEMIBRIGHT; + } + else + { + if (thing->frame & FF_FULLBRIGHT) + vis->cut |= SC_FULLBRIGHT; + else if (thing->frame & FF_SEMIBRIGHT) + vis->cut |= SC_SEMIBRIGHT; + } - if (vis->cut & SC_FULLBRIGHT + if ((vis->cut & SC_FULLBRIGHT) && (!vis->extra_colormap || !(vis->extra_colormap->flags & CMF_FADEFULLBRIGHTSPRITES))) { // full bright: goggles @@ -2047,8 +2057,9 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing) vis->patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE); // specific translucency + // (no draw flags) if (thing->frame & FF_TRANSMASK) - vis->transmap = (thing->frame & FF_TRANSMASK) - 0x10000 + transtables; + vis->transmap = ((thing->frame & FF_TRANSMASK) - FF_TRANS10) + transtables; else vis->transmap = NULL; @@ -2078,7 +2089,7 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel) mobj_t *thing; precipmobj_t *precipthing; // Tails 08-25-2002 INT32 lightnum; - fixed_t limit_dist, hoop_limit_dist; + fixed_t limit_dist; if (rendermode != render_soft) return; @@ -2809,37 +2820,19 @@ void R_ClipSprites(drawseg_t* dsstart, portal_t* portal) /* Check if thing may be drawn from our current view. */ boolean R_ThingVisible (mobj_t *thing) { - boolean split_drawsprite = true; - UINT16 splitflags = 0; - - if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW - || (r_viewmobj && (thing == r_viewmobj || (r_viewmobj->player && r_viewmobj->player->followmobj == thing)))) + if (thing->sprite == SPR_NULL) return false; - splitflags = thing->eflags & (MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4); + if (r_viewmobj && (thing == r_viewmobj || (r_viewmobj->player && r_viewmobj->player->followmobj == thing))) + return false; - if (r_splitscreen && splitflags) - { - split_drawsprite = false; + if ((viewssnum == 0 && (thing->drawflags & MFD_DONTDRAWP1)) + || (viewssnum == 1 && (thing->drawflags & MFD_DONTDRAWP2)) + || (viewssnum == 2 && (thing->drawflags & MFD_DONTDRAWP3)) + || (viewssnum == 3 && (thing->drawflags & MFD_DONTDRAWP4))) + return false; - if (thing->eflags & MFE_DRAWONLYFORP1) - if (viewssnum == 0) - split_drawsprite = true; - - if (thing->eflags & MFE_DRAWONLYFORP2) - if (viewssnum == 1) - split_drawsprite = true; - - if (thing->eflags & MFE_DRAWONLYFORP3 && splitscreen > 1) - if (viewssnum == 2) - split_drawsprite = true; - - if (thing->eflags & MFE_DRAWONLYFORP4 && splitscreen > 2) - if (viewssnum == 3) - split_drawsprite = true; - } - - return split_drawsprite; + return true; } boolean R_ThingVisibleWithinDist (mobj_t *thing, diff --git a/src/r_things.h b/src/r_things.h index af71d2661..7fda89bdd 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -161,7 +161,7 @@ typedef struct vissprite_s lighttable_t *colormap; // for color translation and shadow draw // maxbright frames as well - UINT8 *transmap; // for MF2_SHADOW sprites, which translucency table to use + UINT8 *transmap; // which translucency table to use INT32 mobjflags; diff --git a/src/sdl/Makefile.cfg b/src/sdl/Makefile.cfg index 68e616f01..13ad7033a 100644 --- a/src/sdl/Makefile.cfg +++ b/src/sdl/Makefile.cfg @@ -93,6 +93,11 @@ ifndef NOTHREADS OBJS+=$(OBJDIR)/i_threads.o endif +ifndef NOTHREADS + OPTS+=-DHAVE_THREADS + OBJS+=$(OBJDIR)/i_threads.o +endif + ifdef SDL_TTF OPTS+=-DHAVE_TTF SDL_LDFLAGS+=-lSDL2_ttf -lfreetype -lz diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj index 7bffffb69..02f4de60e 100644 --- a/src/sdl/Srb2SDL-vc10.vcxproj +++ b/src/sdl/Srb2SDL-vc10.vcxproj @@ -234,6 +234,7 @@ + @@ -252,6 +253,7 @@ + @@ -384,6 +386,7 @@ + @@ -407,6 +410,7 @@ + diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters index 8950ece95..8dee62e49 100644 --- a/src/sdl/Srb2SDL-vc10.vcxproj.filters +++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters @@ -297,6 +297,9 @@ I_Interface + + I_Interface + LUA @@ -693,6 +696,9 @@ I_Interface + + I_Interface + LUA diff --git a/src/sdl/Srb2SDL-vc9.vcproj b/src/sdl/Srb2SDL-vc9.vcproj index cfa49ea50..b7094b49c 100644 --- a/src/sdl/Srb2SDL-vc9.vcproj +++ b/src/sdl/Srb2SDL-vc9.vcproj @@ -2090,6 +2090,50 @@ RelativePath="..\console.h" > + + + + + + + + + + + + + + + + @@ -2742,6 +2786,50 @@ RelativePath="..\mserv.h" > + + + + + + + + + + + + + + + + %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) + + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + %(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) @@ -1366,6 +1376,7 @@ + diff --git a/src/sdl12/Srb2SDL-vc9.vcproj b/src/sdl12/Srb2SDL-vc9.vcproj index 9d807be21..16c00454d 100644 --- a/src/sdl12/Srb2SDL-vc9.vcproj +++ b/src/sdl12/Srb2SDL-vc9.vcproj @@ -2090,6 +2090,50 @@ RelativePath="..\console.h" > + + + + + + + + + + + + + + + + diff --git a/src/sounds.c b/src/sounds.c index 47688ae56..755a36dc8 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -877,8 +877,8 @@ sfxinfo_t S_sfx[NUMSFX] = {"bfare", false, 128, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Pleasure Castle fanfare {"merry", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Pleasure Castle merry-go-round ambient {"bowlh", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Pleasure Castle pins - {"tppop", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},// Pleasure Castle bombs - {"hsdoor", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},// Red Barrage Area door + {"tppop", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Pleasure Castle bombs + {"hsdoor", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Red Barrage Area door {"hstrn", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Red Barrage Area train {"aspkb", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Red Barrage Area spikeballs {"wind1", false, 64, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Midnight Channel monsters @@ -891,6 +891,7 @@ sfxinfo_t S_sfx[NUMSFX] = {"sploss", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Down to yellow sparks {"itfree", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // :shitsfree: {"dbgsal", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Debug notification + {"cock", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Hammer cocks, bang bang // SRB2Kart - Engine sounds // Engine class A diff --git a/src/sounds.h b/src/sounds.h index 54839b0e5..a9ced4874 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -954,6 +954,7 @@ typedef enum sfx_sploss, sfx_itfree, sfx_dbgsal, + sfx_cock, // Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy... // Engine class A - Low Speed, Low Weight diff --git a/src/st_stuff.c b/src/st_stuff.c index af63ff87e..ba0e8b030 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -31,7 +31,7 @@ #include "m_misc.h" // moviemode #include "m_anigif.h" // cv_gif_downscale #include "p_setup.h" // NiGHTS grading -#include "k_kart.h" // SRB2kart +#include "k_hud.h" // SRB2kart //random index #include "m_random.h" @@ -884,11 +884,11 @@ static void ST_overlayDrawer(void) INT32 y = (stplyr == &players[displayplayers[0]]) ? 4 : BASEVIDHEIGHT/2-12; sprintf(name, "VIEWPOINT: %s", player_names[stplyr-players]); - V_DrawRightAlignedThinString(BASEVIDWIDTH-40, y, V_HUDTRANSHALF|V_ALLOWLOWERCASE|K_calcSplitFlags(V_SNAPTOTOP|V_SNAPTOBOTTOM|V_SNAPTORIGHT), name); + V_DrawRightAlignedThinString(BASEVIDWIDTH-40, y, V_HUDTRANSHALF|V_ALLOWLOWERCASE|V_SNAPTOTOP|V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN, name); } else if (r_splitscreen) { - V_DrawCenteredThinString((vid.width/vid.dupx)/4, BASEVIDHEIGHT/2 - 12, V_HUDTRANSHALF|V_ALLOWLOWERCASE|K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT), player_names[stplyr-players]); + V_DrawCenteredThinString((vid.width/vid.dupx)/4, BASEVIDHEIGHT/2 - 12, V_HUDTRANSHALF|V_ALLOWLOWERCASE|V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN, player_names[stplyr-players]); } } } @@ -933,16 +933,15 @@ static void ST_overlayDrawer(void) // SRB2kart: changed positions & text if (r_splitscreen) { - INT32 splitflags = K_calcSplitFlags(0); - V_DrawThinString(2, (BASEVIDHEIGHT/2)-20, V_YELLOWMAP|V_HUDTRANSHALF|splitflags, M_GetText("- SPECTATING -")); - V_DrawThinString(2, (BASEVIDHEIGHT/2)-10, V_HUDTRANSHALF|splitflags, itemtxt); + V_DrawThinString(2, (BASEVIDHEIGHT/2)-20, V_YELLOWMAP|V_HUDTRANSHALF|V_SPLITSCREEN, M_GetText("- SPECTATING -")); + V_DrawThinString(2, (BASEVIDHEIGHT/2)-10, V_HUDTRANSHALF|V_SPLITSCREEN, itemtxt); } else { - V_DrawString(2, BASEVIDHEIGHT-40, V_HUDTRANSHALF|V_YELLOWMAP, M_GetText("- SPECTATING -")); - V_DrawString(2, BASEVIDHEIGHT-30, V_HUDTRANSHALF, itemtxt); - V_DrawString(2, BASEVIDHEIGHT-20, V_HUDTRANSHALF, M_GetText("Accelerate - Float")); - V_DrawString(2, BASEVIDHEIGHT-10, V_HUDTRANSHALF, M_GetText("Brake - Sink")); + V_DrawString(2, BASEVIDHEIGHT-40, V_HUDTRANSHALF|V_SPLITSCREEN|V_YELLOWMAP, M_GetText("- SPECTATING -")); + V_DrawString(2, BASEVIDHEIGHT-30, V_HUDTRANSHALF|V_SPLITSCREEN, itemtxt); + V_DrawString(2, BASEVIDHEIGHT-20, V_HUDTRANSHALF|V_SPLITSCREEN, M_GetText("Accelerate - Float")); + V_DrawString(2, BASEVIDHEIGHT-10, V_HUDTRANSHALF|V_SPLITSCREEN, M_GetText("Brake - Sink")); } } } diff --git a/src/st_stuff.h b/src/st_stuff.h index 5570a86fb..e17deb7c3 100644 --- a/src/st_stuff.h +++ b/src/st_stuff.h @@ -74,7 +74,6 @@ extern INT32 st_translucency; extern lumpnum_t st_borderpatchnum; // patches, also used in intermission -extern patch_t *tallnum[10]; extern patch_t *sboscore; extern patch_t *sbotime; extern patch_t *sbocolon; diff --git a/src/v_video.c b/src/v_video.c index 86f241215..a4adb8c57 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -30,6 +30,8 @@ #include "m_random.h" #include "doomstat.h" +#include "k_hud.h" + #ifdef HWRENDER #include "hardware/hw_main.h" #endif @@ -681,76 +683,6 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca y -= offsety; } - if (splitscreen && (scrn & V_PERPLAYER)) - { - fixed_t adjusty = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1); - vdup >>= 1; - rowfrac <<= 1; - y >>= 1; -#ifdef QUADS - if (splitscreen > 1) // 3 or 4 players - { - fixed_t adjustx = ((scrn & V_NOSCALESTART) ? vid.height : BASEVIDHEIGHT)<<(FRACBITS-1)); - fdup >>= 1; - colfrac <<= 1; - x >>= 1; - if (stplyr == &players[displayplayers[0]]) - { - if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle |= 1; - if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT))) - perplayershuffle |= 4; - scrn &= ~V_SNAPTOBOTTOM|V_SNAPTORIGHT; - } - else if (stplyr == &players[displayplayers[1]]) - { - if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle |= 1; - if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT))) - perplayershuffle |= 8; - x += adjustx; - scrn &= ~V_SNAPTOBOTTOM|V_SNAPTOLEFT; - } - else if (stplyr == &players[displayplayers[2]]) - { - if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle |= 2; - if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT))) - perplayershuffle |= 4; - y += adjusty; - scrn &= ~V_SNAPTOTOP|V_SNAPTORIGHT; - } - else //if (stplyr == &players[displayplayers[3]]) - { - if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle |= 2; - if (!(scrn & (V_SNAPTOLEFT|V_SNAPTORIGHT))) - perplayershuffle |= 8; - x += adjustx; - y += adjusty; - scrn &= ~V_SNAPTOTOP|V_SNAPTOLEFT; - } - } - else -#endif - // 2 players - { - if (stplyr == &players[displayplayers[0]]) - { - if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle = 1; - scrn &= ~V_SNAPTOBOTTOM; - } - else //if (stplyr == &players[displayplayers[1]]) - { - if (!(scrn & (V_SNAPTOTOP|V_SNAPTOBOTTOM))) - perplayershuffle = 2; - y += adjusty; - scrn &= ~V_SNAPTOTOP; - } - } - } - desttop = screens[scrn&V_PARAMMASK]; if (!desttop) @@ -774,25 +706,7 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca // Center it if necessary if (!(scrn & V_SCALEPATCHMASK)) { - if (vid.width != BASEVIDWIDTH * dupx) - { - // dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx, - // so center this imaginary screen - if ((scrn & (V_HORZSCREEN|V_SNAPTOLEFT)) == (V_HORZSCREEN|V_SNAPTOLEFT)) - x += (vid.width/2 - (BASEVIDWIDTH/2 * dupx)); - else if (scrn & V_SNAPTORIGHT) - x += (vid.width - (BASEVIDWIDTH * dupx)); - else if (!(scrn & V_SNAPTOLEFT)) - x += (vid.width - (BASEVIDWIDTH * dupx)) / 2; - } - if (vid.height != BASEVIDHEIGHT * dupy) - { - // same thing here - if (scrn & V_SNAPTOBOTTOM) - y += (vid.height - (BASEVIDHEIGHT * dupy)); - else if (!(scrn & V_SNAPTOTOP)) - y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2; - } + K_AdjustXYWithSnap(&x, &y, scrn, dupx, dupy); } desttop += (y*vid.width) + x; @@ -1229,39 +1143,12 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) h *= dupy; // Center it if necessary - if (vid.width != BASEVIDWIDTH * dupx) - { - // dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx, - // so center this imaginary screen - if (c & V_SNAPTORIGHT) - x += (vid.width - (BASEVIDWIDTH * dupx)); - else if (!(c & V_SNAPTOLEFT)) - x += (vid.width - (BASEVIDWIDTH * dupx)) / 2; - if (perplayershuffle & 4) - x -= (vid.width - (BASEVIDWIDTH * dupx)) / 4; - else if (perplayershuffle & 8) - x += (vid.width - (BASEVIDWIDTH * dupx)) / 4; - } - if (vid.height != BASEVIDHEIGHT * dupy) - { - // same thing here - if (c & V_SNAPTOBOTTOM) - y += (vid.height - (BASEVIDHEIGHT * dupy)); - else if (!(c & V_SNAPTOTOP)) - y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2; - if (perplayershuffle & 1) - y -= (vid.height - (BASEVIDHEIGHT * dupy)) / 4; - else if (perplayershuffle & 2) - y += (vid.height - (BASEVIDHEIGHT * dupy)) / 4; - } - if (c & V_SPLITSCREEN) - y += (BASEVIDHEIGHT * dupy)/2; - if (c & V_HORZSCREEN) - x += (BASEVIDWIDTH * dupx)/2; + K_AdjustXYWithSnap(&x, &y, c, dupx, dupy); } if (x >= vid.width || y >= vid.height) return; // off the screen + if (x < 0) { w += x; @@ -1548,27 +1435,7 @@ void V_DrawDiag(INT32 x, INT32 y, INT32 wh, INT32 c) wh *= dupx; // Center it if necessary - if (vid.width != BASEVIDWIDTH * dupx) - { - // dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx, - // so center this imaginary screen - if (c & V_SNAPTORIGHT) - x += (vid.width - (BASEVIDWIDTH * dupx)); - else if (!(c & V_SNAPTOLEFT)) - x += (vid.width - (BASEVIDWIDTH * dupx)) / 2; - } - if (vid.height != BASEVIDHEIGHT * dupy) - { - // same thing here - if (c & V_SNAPTOBOTTOM) - y += (vid.height - (BASEVIDHEIGHT * dupy)); - else if (!(c & V_SNAPTOTOP)) - y += (vid.height - (BASEVIDHEIGHT * dupy)) / 2; - } - if (c & V_SPLITSCREEN) - y += (BASEVIDHEIGHT * dupy)/2; - if (c & V_HORZSCREEN) - x += (BASEVIDWIDTH * dupx)/2; + K_AdjustXYWithSnap(&x, &y, c, dupx, dupy); } if (x >= vid.width || y >= vid.height) @@ -2216,17 +2083,17 @@ void V_DrawCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed) c -= HU_FONTSTART; else c = toupper(c) - HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) return; - w = SHORT(hu_font[c]->width); + w = SHORT(fontv[HU_FONT].font[c]->width); if (x + w > vid.width) return; if (colormap != NULL) - V_DrawMappedPatch(x, y, flags, hu_font[c], colormap); + V_DrawMappedPatch(x, y, flags, fontv[HU_FONT].font[c], colormap); else - V_DrawScaledPatch(x, y, flags, hu_font[c]); + V_DrawScaledPatch(x, y, flags, fontv[HU_FONT].font[c]); } // Writes a single character for the chat (half scaled). (draw WHITE if bit 7 set) @@ -2243,14 +2110,14 @@ void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercaseallowed, UI c -= HU_FONTSTART; else c = toupper(c) - HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) return; - w = SHORT(hu_font[c]->width)/2; + w = SHORT(fontv[HU_FONT].font[c]->width)/2; if (x + w > vid.width) return; - V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/2, flags, hu_font[c], colormap); + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT/2, flags, fontv[HU_FONT].font[c], colormap); } // Precompile a wordwrapped string to any given width. @@ -2301,13 +2168,13 @@ char *V_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) c = toupper(c); c -= HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) { chw = spacewidth; lastusablespace = i; } else - chw = (charwidth ? charwidth : hu_font[c]->width); + chw = (charwidth ? charwidth : fontv[HU_FONT].font[c]->width); x += chw; @@ -2322,214 +2189,297 @@ char *V_WordWrap(INT32 x, INT32 w, INT32 option, const char *string) return newstring; } -// -// Write a string using the hu_font -// NOTE: the text is centered for screens larger than the base width -// -void V_DrawString(INT32 x, INT32 y, INT32 option, const char *string) +static inline fixed_t FixedCharacterDim( + fixed_t scale, + fixed_t chw, + INT32 hchw, + INT32 dupx, + fixed_t * cwp) { - INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth, center = 0, left = 0; - const char *ch = string; - INT32 charflags = (option & V_CHARCOLORMASK); - const UINT8 *colormap = NULL; - INT32 spacewidth = 4, charwidth = 0; - - INT32 lowercase = (option & V_ALLOWLOWERCASE); - option &= ~V_FLIP; // which is also shared with V_ALLOWLOWERCASE... - - if (option & V_NOSCALESTART) - { - dupx = vid.dupx; - dupy = vid.dupy; - scrwidth = vid.width; - } - else - { - dupx = dupy = 1; - scrwidth = vid.width/vid.dupx; - if (!(option & V_SNAPTOLEFT)) - { - left = (scrwidth - BASEVIDWIDTH)/2; - scrwidth -= left; - } - } - - charflags = (option & V_CHARCOLORMASK); - colormap = V_GetStringColormap(charflags); - - switch (option & V_SPACINGMASK) - { - case V_MONOSPACE: - spacewidth = 8; - /* FALLTHRU */ - case V_OLDSPACING: - charwidth = 8; - break; - case V_6WIDTHSPACE: - spacewidth = 6; - default: - break; - } - - for (;;ch++) - { - if (!*ch) - break; - if (*ch & 0x80) //color parsing -x 2.16.09 - { - // manually set flags override color codes - if (!(option & V_CHARCOLORMASK)) - { - charflags = ((*ch & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK; - colormap = V_GetStringColormap(charflags); - } - continue; - } - if (*ch == '\n') - { - cx = x; - - if (option & V_RETURN8) - cy += 8*dupy; - else - cy += 12*dupy; - - continue; - } - - c = *ch; - if (!lowercase) - c = toupper(c); - c -= HU_FONTSTART; - - // character does not exist or is a space - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) - { - cx += spacewidth * dupx; - continue; - } - - if (charwidth) - { - w = charwidth * dupx; - center = w/2 - SHORT(hu_font[c]->width)*dupx/2; - } - else - w = SHORT(hu_font[c]->width) * dupx; - - if (cx > scrwidth) - continue; - if (cx+left + w < 0) //left boundary check - { - cx += w; - continue; - } - - V_DrawFixedPatch((cx + center)<> 1 ); + (*cwp) = chw; + return FixedMul (( cxoff * dupx )<< FRACBITS, scale); +} - if (option & V_NOSCALESTART) +static inline fixed_t BunchedCharacterDim( + fixed_t scale, + fixed_t chw, + INT32 hchw, + INT32 dupx, + fixed_t * cwp) +{ + (void)chw; + (void)hchw; + (void)dupx; + (*cwp) = FixedMul (max (1, (*cwp) - 1) << FRACBITS, scale); + return 0; +} + +void V_DrawStringScaled( + fixed_t x, + fixed_t y, + fixed_t scale, + fixed_t spacescale, + fixed_t lfscale, + INT32 flags, + int fontno, + const char *s) +{ + fixed_t chw; + INT32 hchw;/* half-width for centering */ + fixed_t spacew; + fixed_t lfh; + + INT32 dupx; + + fixed_t right; + fixed_t bot; + + fixed_t (*dim_fn)(fixed_t,fixed_t,INT32,INT32,fixed_t *); + + font_t *font; + + boolean uppercase; + boolean notcolored; + + const UINT8 *colormap; + + fixed_t cx, cy; + + fixed_t cxoff; + fixed_t cw; + + INT32 spacing; + fixed_t left; + + int c; + + uppercase = !( flags & V_ALLOWLOWERCASE ); + flags &= ~(V_FLIP);/* These two (V_ALLOWLOWERCASE) share a bit. */ + + colormap = V_GetStringColormap(( flags & V_CHARCOLORMASK )); + notcolored = !colormap; + + font = &fontv[fontno]; + + chw = 0; + + spacing = ( flags & V_SPACINGMASK ); + + /* + Hardcoded until a better system can be implemented + for determining how fonts space. + */ + switch (fontno) { - dupx = vid.dupx; - dupy = vid.dupy; - scrwidth = vid.width; + default: + case HU_FONT: + spacew = 4; + switch (spacing) + { + case V_MONOSPACE: + spacew = 8; + /* FALLTHRU */ + case V_OLDSPACING: + chw = 8; + break; + case V_6WIDTHSPACE: + spacew = 6; + } + break; + case TINY_FONT: + spacew = 2; + switch (spacing) + { + case V_MONOSPACE: + spacew = 5; + /* FALLTHRU */ + case V_OLDSPACING: + chw = 5; + break; + // Out of video flags, so we're reusing this for alternate charwidth instead + /*case V_6WIDTHSPACE: + spacewidth = 3;*/ + } + break; + case KART_FONT: + spacew = 12; + switch (spacing) + { + case V_MONOSPACE: + spacew = 12; + /* FALLTHRU */ + case V_OLDSPACING: + chw = 12; + break; + case V_6WIDTHSPACE: + spacew = 6; + } + break; + case LT_FONT: + spacew = 12; + break; + case CRED_FONT: + spacew = 16; + break; + } + switch (fontno) + { + default: + case HU_FONT: + case TINY_FONT: + case KART_FONT: + if (( flags & V_RETURN8 )) + lfh = 8; + else + lfh = 12; + break; + case LT_FONT: + case CRED_FONT: + lfh = 12; + break; + } + + hchw = chw >> 1; + + chw <<= FRACBITS; + spacew <<= FRACBITS; + lfh <<= FRACBITS; + +#define Mul( id, scale ) ( id = FixedMul (scale, id) ) + Mul (chw, scale); + Mul (spacew, scale); + Mul (lfh, scale); + + Mul (spacew, spacescale); + Mul (lfh, lfscale); +#undef Mul + + if (( flags & V_NOSCALESTART )) + { + dupx = vid.dupx; + + hchw *= dupx; + + chw *= dupx; + spacew *= dupx; + lfh *= vid.dupy; + + right = vid.width; } else { - dupx = dupy = 1; - scrwidth = vid.width/vid.dupx; - left = (scrwidth - BASEVIDWIDTH)/2; + dupx = 1; + + right = ( vid.width / vid.dupx ); + if (!( flags & V_SNAPTOLEFT )) + { + left = ( right - BASEVIDWIDTH )/ 2;/* left edge of drawable area */ + right -= left; + } } - charflags = (option & V_CHARCOLORMASK); - colormap = V_GetStringColormap(charflags); + right <<= FRACBITS; + bot = vid.height << FRACBITS; - switch (option & V_SPACINGMASK) + if (fontno == TINY_FONT) { - case V_MONOSPACE: - spacewidth = 12; - /* FALLTHRU */ - case V_OLDSPACING: - charwidth = 12; - break; - case V_6WIDTHSPACE: - spacewidth = 6; - default: - break; - } - - for (;;ch++) - { - if (!*ch) - break; - if (*ch & 0x80) //color parsing -x 2.16.09 - { - // manually set flags override color codes - if (!(option & V_CHARCOLORMASK)) - { - charflags = ((*ch & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK; - colormap = V_GetStringColormap(charflags); - } - continue; - } - if (*ch == '\n') - { - cx = x; - - if (option & V_RETURN8) - cy += 8*dupy; - else - cy += 12*dupy; - - continue; - } - - c = *ch; - if (!lowercase) - c = toupper(c); - c -= KART_FONTSTART; - - // character does not exist or is a space - if (c < 0 || c >= KART_FONTSIZE || !kart_font[c]) - { - cx += spacewidth * dupx; - continue; - } - - if (charwidth) - { - w = charwidth * dupx; - center = w/2 - SHORT(kart_font[c]->width)*dupx/2; - } + if (chw) + dim_fn = FixedCharacterDim; else - w = SHORT(kart_font[c]->width) * dupx; - - if (cx > scrwidth) - break; - if (cx+left + w < 0) //left boundary check { - cx += w; - continue; + /* Reuse this flag for the alternate bunched-up spacing. */ + if (( flags & V_6WIDTHSPACE )) + dim_fn = BunchedCharacterDim; + else + dim_fn = VariableCharacterDim; } + } + else + { + if (chw) + dim_fn = CenteredCharacterDim; + else + dim_fn = VariableCharacterDim; + } - V_DrawFixedPatch((cx + center)<= bot) + return; + cx = x; + break; + default: + if (( c & 0x80 )) + { + if (notcolored) + { + colormap = V_GetStringColormap( + ( ( c & 0x7f )<< V_CHARCOLORSHIFT )& + V_CHARCOLORMASK); + } + } + else if (cx < right) + { + if (uppercase) + c = toupper(c); + + c -= font->start; + if (c >= 0 && c < font->size && font->font[c]) + { + cw = SHORT (font->font[c]->width) * dupx; + cxoff = (*dim_fn)(scale, chw, hchw, dupx, &cw); + V_DrawFixedPatch(cx + cxoff, cy, scale, + flags, font->font[c], colormap); + cx += cw; + } + else + cx += spacew; + } + } } } // @@ -2546,228 +2496,18 @@ void V_DrawRightAlignedString(INT32 x, INT32 y, INT32 option, const char *string V_DrawString(x, y, option, string); } -// -// Write a string using the hu_font, 0.5x scale -// NOTE: the text is centered for screens larger than the base width -// -void V_DrawSmallString(INT32 x, INT32 y, INT32 option, const char *string) -{ - INT32 w, c, cx = x, cy = y, dupx, dupy, scrwidth, center = 0, left = 0; - const char *ch = string; - INT32 charflags = 0; - const UINT8 *colormap = NULL; - INT32 spacewidth = 2, charwidth = 0; - - INT32 lowercase = (option & V_ALLOWLOWERCASE); - option &= ~V_FLIP; // which is also shared with V_ALLOWLOWERCASE... - - if (option & V_NOSCALESTART) - { - dupx = vid.dupx; - dupy = vid.dupy; - scrwidth = vid.width; - } - else - { - dupx = dupy = 1; - scrwidth = vid.width/vid.dupx; - left = (scrwidth - BASEVIDWIDTH)/2; - scrwidth -= left; - } - - charflags = (option & V_CHARCOLORMASK); - colormap = V_GetStringColormap(charflags); - - switch (option & V_SPACINGMASK) - { - case V_MONOSPACE: - spacewidth = 4; - /* FALLTHRU */ - case V_OLDSPACING: - charwidth = 4; - break; - case V_6WIDTHSPACE: - spacewidth = 3; - default: - break; - } - - for (;;ch++) - { - if (!*ch) - break; - if (*ch & 0x80) //color parsing -x 2.16.09 - { - // manually set flags override color codes - if (!(option & V_CHARCOLORMASK)) - { - charflags = ((*ch & 0x7f) << V_CHARCOLORSHIFT) & V_CHARCOLORMASK; - colormap = V_GetStringColormap(charflags); - } - continue; - } - if (*ch == '\n') - { - cx = x; - - if (option & V_RETURN8) - cy += 4*dupy; - else - cy += 6*dupy; - - continue; - } - - c = *ch; - if (!lowercase) - c = toupper(c); - c -= HU_FONTSTART; - - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) - { - cx += spacewidth * dupx; - continue; - } - - if (charwidth) - { - w = charwidth * dupx; - center = w/2 - SHORT(hu_font[c]->width)*dupx/4; - } - else - w = SHORT(hu_font[c]->width) * dupx / 2; - - if (cx > scrwidth) - continue; - if (cx+left + w < 0) //left boundary check - { - cx += w; - continue; - } - - V_DrawFixedPatch((cx + center)<= HU_FONTSIZE || !tny_font[c]) - { - cx += spacewidth * dupx; - continue; - } - - if (charwidth) - w = charwidth * dupx; - else - w = ((option & V_6WIDTHSPACE ? max(1, SHORT(tny_font[c]->width)-1) // Reuse this flag for the alternate bunched-up spacing - : SHORT(tny_font[c]->width)) * dupx); - - if (cx > scrwidth) - continue; - if (cx+left + w < 0) //left boundary check - { - cx += w; - continue; - } - - V_DrawFixedPatch(cx<= HU_FONTSIZE || !hu_font[c]) - { - cx += (spacewidth * dupx)<width)*(dupx/2); - } - else - w = SHORT(hu_font[c]->width) * dupx; - - if ((cx>>FRACBITS) > scrwidth) - continue; - if ((cx>>FRACBITS)+left + w < 0) //left boundary check - { - cx += w<width); // this SHOULD always be 5 but I guess custom graphics exist. + INT32 w = SHORT(fontv[PINGNUM_FONT].font[0]->width); // this SHOULD always be 5 but I guess custom graphics exist. if (flags & V_NOSCALESTART) w *= vid.dupx; @@ -2936,353 +2537,11 @@ void V_DrawPingNum(INT32 x, INT32 y, INT32 flags, INT32 num, const UINT8 *colorm do { x -= (w-1); // Oni wanted their outline to intersect. - V_DrawFixedPatch(x<= HU_FONTSIZE || !hu_font[c]) - { - cx += (spacewidth * dupx)<width)*(dupx/4); - } - else - w = SHORT(hu_font[c]->width) * dupx / 2; - - if ((cx>>FRACBITS) > scrwidth) - break; - if ((cx>>FRACBITS)+left + w < 0) //left boundary check - { - cx += w<= HU_FONTSIZE || !tny_font[c]) - { - cx += (spacewidth * dupx)<width)*(dupx/2); - } - else - w = SHORT(tny_font[c]->width) * dupx; - - if ((cx>>FRACBITS) > scrwidth) - break; - if ((cx>>FRACBITS)+left + w < 0) //left boundary check - { - cx += w<= HU_FONTSIZE || !tny_font[c]) - { - cx += FixedMul(spacewidth, dupx); - continue; - } - - if (charwidth) - { - w = FixedMul(charwidth, dupx); - center = w/2 - SHORT(tny_font[c]->width)*(dupx/4); - } - else - w = SHORT(tny_font[c]->width) * dupx / 2; - - if (cx > scrwidth) - break; - if (cx+left + w < 0) //left boundary check - { - cx += w; - continue; - } - - colormap = V_GetStringColormap(charflags); - - V_DrawFixedPatch(cx + center, cy, FRACUNIT/2, option, tny_font[c], colormap); - - cx += w; - } -} - -void V_DrawCenteredSmallThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string) -{ - x -= V_SmallThinStringWidth(string, option)/4; - V_DrawSmallThinStringAtFixed(x, y, option, string); -} - -void V_DrawRightAlignedSmallThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string) -{ - x -= V_SmallThinStringWidth(string, option)/2; - V_DrawSmallThinStringAtFixed(x, y, option, string); -} - // Draws a tallnum. Replaces two functions in y_inter and st_stuff void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num) { @@ -3329,292 +2588,6 @@ void V_DrawPaddedTallNum(INT32 x, INT32 y, INT32 flags, INT32 num, INT32 digits) } while (--digits); } -// Draw an act number for a level title -void V_DrawLevelActNum(INT32 x, INT32 y, INT32 flags, UINT8 num) -{ - if (num > 99) - return; // not supported - - while (num > 0) - { - if (num > 9) // if there are two digits, draw second digit first - V_DrawScaledPatch(x + (V_LevelActNumWidth(num) - V_LevelActNumWidth(num%10)), y, flags, ttlnum[num%10]); - else - V_DrawScaledPatch(x, y, flags, ttlnum[num]); - num = num/10; - } -} - -// Write a string using the credit font -// NOTE: the text is centered for screens larger than the base width -// -void V_DrawCreditString(fixed_t x, fixed_t y, INT32 option, const char *string) -{ - INT32 w, c, dupx, dupy, scrwidth = BASEVIDWIDTH; - fixed_t cx = x, cy = y; - const char *ch = string; - - // It's possible for string to be a null pointer - if (!string) - return; - - if (option & V_NOSCALESTART) - { - dupx = vid.dupx; - dupy = vid.dupy; - scrwidth = vid.width; - } - else - dupx = dupy = 1; - - for (;;) - { - c = *ch++; - if (!c) - break; - if (c == '\n') - { - cx = x; - cy += (12*dupy)<= CRED_FONTSIZE) - { - cx += (16*dupx)<width) * dupx; - if ((cx>>FRACBITS) > scrwidth) - continue; - - V_DrawSciencePatch(cx, cy, option, cred_font[c], FRACUNIT); - cx += w<= NT_FONTSIZE || !ntb_font[c] || !nto_font[c]) - { - cx += FixedMul((4 * dupx)*FRACUNIT, scale); - continue; - } - - w = FixedMul((SHORT(ntb_font[c]->width)+2 * dupx) * FRACUNIT, scale); - - if (FixedInt(cx) > scrwidth) - continue; - if (cx+(left*FRACUNIT) + w < 0) // left boundary check - { - cx += w; - continue; - } - - V_DrawFixedPatch(cx, cy, scale, option, nto_font[c], outlinecolormap); - V_DrawFixedPatch(cx, cy, scale, option, ntb_font[c], basecolormap); - - cx += w; - } -} - -// Looks familiar. -void V_DrawNameTag(INT32 x, INT32 y, INT32 option, fixed_t scale, UINT8 *basecolormap, UINT8 *outlinecolormap, const char *string) -{ - const char *text = string; - const char *first_token = text; - char *last_token = strchr(text, '\n'); - const INT32 lbreakheight = 21; - INT32 ntlines; - - if (option & V_CENTERNAMETAG) - { - ntlines = V_CountNameTagLines(string); - y -= FixedInt(FixedMul(((lbreakheight/2) * (ntlines-1))*FRACUNIT, scale)); - } - - // No line breaks? - // Draw entire string - if (!last_token) - V_DrawNameTagLine(x, y, option, scale, basecolormap, outlinecolormap, string); - // Split string by the line break character - else - { - char *str = NULL; - INT32 len; - while (true) - { - // There are still lines left to draw - if (last_token) - { - size_t shift = 0; - // Free this line - if (str) - Z_Free(str); - // Find string length, do a malloc... - len = (last_token-first_token)+1; - str = ZZ_Alloc(len); - // Copy the line - strncpy(str, first_token, len-1); - str[len-1] = '\0'; - // Don't leave a line break character - // at the start of the string! - if ((strlen(str) >= 2) && (string[0] == '\n') && (string[1] != '\n')) - shift++; - // Then draw it - V_DrawNameTagLine(x, y, option, scale, basecolormap, outlinecolormap, str+shift); - } - // No line break character was found - else - { - // Don't leave a line break character - // at the start of the string! - if ((strlen(first_token) >= 2) && (first_token[0] == '\n') && (first_token[1] != '\n')) - first_token++; - // Then draw it - V_DrawNameTagLine(x, y, option, scale, basecolormap, outlinecolormap, first_token); - break; - } - - // Next line - y += FixedInt(FixedMul(lbreakheight*FRACUNIT, scale)); - if ((last_token-text)+1 >= (signed)strlen(text)) - last_token = NULL; - else - { - first_token = last_token; - last_token = strchr(first_token+1, '\n'); - } - } - // Free this line - if (str) - Z_Free(str); - } -} - -// Count the amount of lines in name tag string -INT32 V_CountNameTagLines(const char *string) -{ - INT32 ntlines = 1; - const char *text = string; - const char *first_token = text; - char *last_token = strchr(text, '\n'); - - // No line breaks? - if (!last_token) - return ntlines; - // Split string by the line break character - else - { - while (true) - { - if (last_token) - ntlines++; - // No line break character was found - else - break; - - // Next line - if ((last_token-text)+1 >= (signed)strlen(text)) - last_token = NULL; - else - { - first_token = last_token; - last_token = strchr(first_token+1, '\n'); - } - } - } - return ntlines; -} - -INT32 V_NameTagWidth(const char *string) -{ - INT32 c, w = 0; - size_t i; - - // It's possible for string to be a null pointer - if (!string) - return 0; - - for (i = 0; i < strlen(string); i++) - { - c = toupper(string[i]) - NT_FONTSTART; - if (c < 0 || c >= NT_FONTSIZE || !ntb_font[c] || !nto_font[c]) - w += 4; - else - w += SHORT(ntb_font[c]->width)+2; - } - - return w; -} - -// Find string width from cred_font chars -// -INT32 V_CreditStringWidth(const char *string) -{ - INT32 c, w = 0; - size_t i; - - // It's possible for string to be a null pointer - if (!string) - return 0; - - for (i = 0; i < strlen(string); i++) - { - c = toupper(string[i]) - CRED_FONTSTART; - if (c < 0 || c >= CRED_FONTSIZE) - w += 16; - else - w += SHORT(cred_font[c]->width); - } - - return w; -} - // Write a string using the level title font // NOTE: the text is centered for screens larger than the base width // @@ -3696,7 +2669,7 @@ INT32 V_LevelNameWidth(const char *string) if (c < 0 || c >= LT_FONTSIZE || !lt_font[c]) w += 12; else - w += SHORT(lt_font[c]->width); + w += SHORT(fontv[LT_FONT].font[c]->width); } return w; @@ -3715,8 +2688,8 @@ INT32 V_LevelNameHeight(const char *string) if (c < 0 || c >= LT_FONTSIZE || !lt_font[c]) continue; - if (SHORT(lt_font[c]->height) > w) - w = SHORT(lt_font[c]->height); + if (SHORT(fontv[LT_FONT].font[c]->height) > w) + w = SHORT(fontv[LT_FONT].font[c]->height); } return w; @@ -3768,11 +2741,12 @@ INT32 V_StringWidth(const char *string, INT32 option) c = string[i]; if ((UINT8)c & 0x80) //color parsing! -Inuyasha 2.16.09 continue; - c = toupper(string[i]) - HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + + c = toupper(c) - HU_FONTSTART; + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) w += spacewidth; else - w += (charwidth ? charwidth : SHORT(hu_font[c]->width)); + w += (charwidth ? charwidth : SHORT(fontv[HU_FONT].font[c]->width)); } if (option & V_NOSCALESTART) @@ -3808,11 +2782,12 @@ INT32 V_SmallStringWidth(const char *string, INT32 option) { if (string[i] & 0x80) continue; - c = toupper(string[i]) - HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !hu_font[c]) + + c = toupper(c) - HU_FONTSTART; + if (c < 0 || c >= HU_FONTSIZE || !fontv[HU_FONT].font[c]) w += spacewidth; else - w += (charwidth ? charwidth : SHORT(hu_font[c]->width)/2); + w += (charwidth ? charwidth : SHORT(fontv[HU_FONT].font[c]->width)/2); } return w; @@ -3847,14 +2822,18 @@ INT32 V_ThinStringWidth(const char *string, INT32 option) { if (string[i] & 0x80) continue; - c = toupper(string[i]) - HU_FONTSTART; - if (c < 0 || c >= HU_FONTSIZE || !tny_font[c]) + + if (!lowercase || !fontv[TINY_FONT].font[c-HU_FONTSTART]) + c = toupper(c); + c -= HU_FONTSTART; + + if (c < 0 || c >= HU_FONTSIZE || !fontv[TINY_FONT].font[c]) w += spacewidth; else { w += (charwidth ? charwidth - : ((option & V_6WIDTHSPACE && i < strlen(string)-1) ? max(1, SHORT(tny_font[c]->width)-1) // Reuse this flag for the alternate bunched-up spacing - : SHORT(tny_font[c]->width))); + : ((option & V_6WIDTHSPACE && i < strlen(string)-1) ? max(1, SHORT(fontv[TINY_FONT].font[c]->width)-1) // Reuse this flag for the alternate bunched-up spacing + : SHORT(fontv[TINY_FONT].font[c]->width))); } } diff --git a/src/v_video.h b/src/v_video.h index a9cf2ce9c..3db520a98 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -135,9 +135,9 @@ void V_CubeApply(UINT8 *red, UINT8 *green, UINT8 *blue); #define V_WRAPX 0x08000000 // Don't clamp texture on X (for HW mode) #define V_WRAPY 0x10000000 // Don't clamp texture on Y (for HW mode) -#define V_NOSCALESTART 0x20000000 // don't scale x, y, start coords -#define V_SPLITSCREEN 0x40000000 -#define V_HORZSCREEN 0x80000000 +#define V_NOSCALESTART 0x20000000 // don't scale x, y, start coords +#define V_SPLITSCREEN 0x40000000 // Add half of screen width or height automatically depending on player number +#define V_SLIDEIN 0x80000000 // Slide in from the sides on level load, depending on snap flags // defines for old functions #define V_DrawPatch(x,y,s,p) V_DrawFixedPatch((x)<>1,option,HU_FONT,string) void V_DrawCenteredSmallString(INT32 x, INT32 y, INT32 option, const char *string); void V_DrawRightAlignedSmallString(INT32 x, INT32 y, INT32 option, const char *string); // draw a string using the tny_font -void V_DrawThinString(INT32 x, INT32 y, INT32 option, const char *string); +#define V_DrawThinString( x,y,option,string ) \ + V__DrawDupxString (x,y,FRACUNIT,option,TINY_FONT,string) void V_DrawCenteredThinString(INT32 x, INT32 y, INT32 option, const char *string); void V_DrawRightAlignedThinString(INT32 x, INT32 y, INT32 option, const char *string); -// draw a string using the tny_font, 0.5x scale -void V_DrawSmallThinString(INT32 x, INT32 y, INT32 option, const char *string); -void V_DrawCenteredSmallThinString(INT32 x, INT32 y, INT32 option, const char *string); -void V_DrawRightAlignedSmallThinString(INT32 x, INT32 y, INT32 option, const char *string); +#define V_DrawStringAtFixed( x,y,option,string ) \ + V__DrawOneScaleString (x,y,FRACUNIT,option,HU_FONT,string) -// draw a string using the hu_font at fixed_t coordinates -void V_DrawStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); -void V_DrawCenteredStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); -void V_DrawRightAlignedStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); - -// draw a string using the hu_font at fixed_t coordinates, 0.5x scale -void V_DrawSmallStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); -void V_DrawCenteredSmallStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); -void V_DrawRightAlignedSmallStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); - -// draw a string using the tny_font at fixed_t coordinates -void V_DrawThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); -void V_DrawCenteredThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); -void V_DrawRightAlignedThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); - -// draw a string using the tny_font at fixed_t coordinates, 0.5x scale -void V_DrawSmallThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); -void V_DrawCenteredSmallThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); -void V_DrawRightAlignedSmallThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, const char *string); +#define V_DrawThinStringAtFixed( x,y,option,string ) \ + V__DrawOneScaleString (x,y,FRACUNIT,option,TINY_FONT,string) // Draw tall nums, used for menu, HUD, intermission void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num); @@ -254,7 +257,8 @@ INT32 V_LevelNameWidth(const char *string); INT32 V_LevelNameHeight(const char *string); INT16 V_LevelActNumWidth(UINT8 num); // act number width -void V_DrawCreditString(fixed_t x, fixed_t y, INT32 option, const char *string); +#define V_DrawCreditString( x,y,option,string ) \ + V__DrawOneScaleString (x,y,FRACUNIT,option,CRED_FONT,string) INT32 V_CreditStringWidth(const char *string); // Draw a string using the nt_font diff --git a/src/w_wad.c b/src/w_wad.c index b663a9548..bf96eb58c 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -1473,7 +1473,6 @@ size_t W_ReadLumpHeaderPwad(UINT16 wad, UINT16 lump, void *dest, size_t size, si size = 0; zerr(zErr); } - (void)inflateEnd(&strm); } else diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj index 6855a4135..c65d867dc 100644 --- a/src/win32/Srb2win-vc10.vcxproj +++ b/src/win32/Srb2win-vc10.vcxproj @@ -235,6 +235,7 @@ + @@ -406,6 +407,7 @@ + diff --git a/src/win32/Srb2win-vc9.vcproj b/src/win32/Srb2win-vc9.vcproj index c1c6b5bc4..710fea317 100644 --- a/src/win32/Srb2win-vc9.vcproj +++ b/src/win32/Srb2win-vc9.vcproj @@ -1831,6 +1831,50 @@ RelativePath="..\console.h" > + + + + + + + + + + + + + + + + diff --git a/src/y_inter.c b/src/y_inter.c index ec70bafb7..93efea725 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -45,6 +45,7 @@ #include "k_pwrlv.h" #include "console.h" // cons_menuhighlight #include "lua_hook.h" // IntermissionThinker hook +#include "k_grandprix.h" #ifdef HWRENDER #include "hardware/hw_main.h" @@ -197,7 +198,12 @@ static void Y_CompareScore(INT32 i) static void Y_CompareRank(INT32 i) { INT16 increase = ((data.match.increase[i] == INT16_MIN) ? 0 : data.match.increase[i]); - UINT32 score = (powertype != -1 ? clientpowerlevels[i][powertype] : players[i].score); + UINT32 score = players[i].score; + + if (powertype != PWRLV_DISABLED) + { + score = clientpowerlevels[i][powertype]; + } if (!(data.match.val[data.match.numplayers] == UINT32_MAX || (score - increase) > data.match.val[data.match.numplayers])) return; @@ -301,18 +307,26 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) data.match.name[data.match.numplayers] = player_names[i]; if (data.match.numplayers && (data.match.val[data.match.numplayers] == data.match.val[data.match.numplayers-1])) - data.match.pos[data.match.numplayers] = data.match.pos[data.match.numplayers-1]; - else - data.match.pos[data.match.numplayers] = data.match.numplayers+1; - - if ((!rankingsmode && powertype == -1) // Single player rankings (grand prix). Online rank is handled below. - && !(players[i].pflags & PF_TIMEOVER) && (data.match.pos[data.match.numplayers] < (numplayersingame + numgriefers))) { - data.match.increase[i] = (numplayersingame + numgriefers) - data.match.pos[data.match.numplayers]; + data.match.pos[data.match.numplayers] = data.match.pos[data.match.numplayers-1]; + } + else + { + data.match.pos[data.match.numplayers] = data.match.numplayers+1; + } + + if ((powertype == PWRLV_DISABLED) + && (!rankingsmode) + && !(players[i].pflags & PF_TIMEOVER) + && (data.match.pos[data.match.numplayers] < (numplayersingame + numgriefers))) + { + // Online rank is handled further below in this file. + data.match.increase[i] = K_CalculateGPRankPoints(data.match.pos[data.match.numplayers], numplayersingame + numgriefers); players[i].score += data.match.increase[i]; } if (demo.recording && !rankingsmode) + { G_WriteStanding( data.match.pos[data.match.numplayers], data.match.name[data.match.numplayers], @@ -320,6 +334,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) *data.match.color[data.match.numplayers], data.match.val[data.match.numplayers] ); + } data.match.numplayers++; } @@ -507,9 +522,36 @@ dontdrawbg: int y2; if (data.match.rankingsmode) - timeheader = (powertype != -1 ? "PWR.LV" : "RANK"); + { + if (powertype == PWRLV_DISABLED) + { + timeheader = "RANK"; + } + else + { + timeheader = "PWR.LV"; + } + } else - timeheader = ((intertype == int_race || (intertype == int_battle && battlecapsules)) ? "TIME" : "SCORE"); + { + switch (intertype) + { + default: + case int_race: + timeheader = "TIME"; + break; + case int_battle: + if (battlecapsules) + { + timeheader = "TIME"; + } + else + { + timeheader = "SCORE"; + } + break; + } + } // draw the level name V_DrawCenteredString(-4 + x + BASEVIDWIDTH/2, 12, 0, data.match.levelstring); @@ -600,8 +642,11 @@ dontdrawbg: if (data.match.rankingsmode) { - if (powertype != -1 && !clientpowerlevels[data.match.num[i]][powertype]) // No power level (splitscreen guests) + if (powertype != PWRLV_DISABLED && !clientpowerlevels[data.match.num[i]][powertype]) + { + // No power level (splitscreen guests) STRBUFCPY(strtime, "----"); + } else { if (data.match.increase[data.match.num[i]] != INT16_MIN) @@ -670,7 +715,7 @@ skiptallydrawer: if (!LUA_HudEnabled(hud_intermissionmessages)) return; - if (timer) + if (timer && grandprixinfo.gp == false) { char *string; INT32 tickdown = (timer+1)/TICRATE; @@ -774,74 +819,73 @@ void Y_Ticker(void) if (intertype == int_race || intertype == int_battle) { - if (netgame || multiplayer) + if (!(multiplayer && demo.playback)) // Don't advance to rankings in replays { - if (sorttic == -1) - sorttic = intertic + max((cv_inttime.value/2)-2, 2)*TICRATE; // 8 second pause after match results - else if (!(multiplayer && demo.playback)) // Don't advance to rankings in replays + if (!data.match.rankingsmode && (intertic >= sorttic + 8)) { - if (!data.match.rankingsmode && (intertic >= sorttic + 8)) - Y_CalculateMatchData(1, Y_CompareRank); + Y_CalculateMatchData(1, Y_CompareRank); + } - if (data.match.rankingsmode && intertic > sorttic+16+(2*TICRATE)) + if (data.match.rankingsmode && intertic > sorttic+16+(2*TICRATE)) + { + INT32 q=0,r=0; + boolean kaching = true; + + for (q = 0; q < data.match.numplayers; q++) { - INT32 q=0,r=0; - boolean kaching = true; - - for (q = 0; q < data.match.numplayers; q++) - { - if (data.match.num[q] == MAXPLAYERS + if (data.match.num[q] == MAXPLAYERS || !data.match.increase[data.match.num[q]] || data.match.increase[data.match.num[q]] == INT16_MIN) - continue; + { + continue; + } - r++; - data.match.jitter[data.match.num[q]] = 1; + r++; + data.match.jitter[data.match.num[q]] = 1; - if (powertype != -1) + if (powertype != PWRLV_DISABLED) + { + // Power Levels + if (abs(data.match.increase[data.match.num[q]]) < 10) { - // Power Levels - if (abs(data.match.increase[data.match.num[q]]) < 10) - { - // Not a lot of point increase left, just set to 0 instantly - data.match.increase[data.match.num[q]] = 0; - } - else - { - SINT8 remove = 0; // default (should not happen) - - if (data.match.increase[data.match.num[q]] < 0) - remove = -10; - else if (data.match.increase[data.match.num[q]] > 0) - remove = 10; - - // Remove 10 points at a time - data.match.increase[data.match.num[q]] -= remove; - - // Still not zero, no kaching yet - if (data.match.increase[data.match.num[q]] != 0) - kaching = false; - } + // Not a lot of point increase left, just set to 0 instantly + data.match.increase[data.match.num[q]] = 0; } else { - // Basic bitch points - if (data.match.increase[data.match.num[q]]) - { - if (--data.match.increase[data.match.num[q]]) - kaching = false; - } + SINT8 remove = 0; // default (should not happen) + + if (data.match.increase[data.match.num[q]] < 0) + remove = -10; + else if (data.match.increase[data.match.num[q]] > 0) + remove = 10; + + // Remove 10 points at a time + data.match.increase[data.match.num[q]] -= remove; + + // Still not zero, no kaching yet + if (data.match.increase[data.match.num[q]] != 0) + kaching = false; } } - - if (r) - { - S_StartSound(NULL, (kaching ? sfx_chchng : sfx_ptally)); - Y_CalculateMatchData(2, Y_CompareRank); - } else - endtic = intertic + 3*TICRATE; // 3 second pause after end of tally + { + // Basic bitch points + if (data.match.increase[data.match.num[q]]) + { + if (--data.match.increase[data.match.num[q]]) + kaching = false; + } + } } + + if (r) + { + S_StartSound(NULL, (kaching ? sfx_chchng : sfx_ptally)); + Y_CalculateMatchData(2, Y_CompareRank); + } + else + endtic = intertic + 3*TICRATE; // 3 second pause after end of tally } } else if (!(intertic & 1)) @@ -1159,19 +1203,11 @@ void Y_StartIntermission(void) #endif // set player Power Level type - powertype = PWRLV_DISABLED; - - if (netgame && cv_kartusepwrlv.value) - { - if ((gametyperules & GTR_CIRCUIT)) - powertype = PWRLV_RACE; - else if ((gametyperules & GTR_BUMPERS)) - powertype = PWRLV_BATTLE; - } + powertype = K_UsingPowerLevels(); if (!multiplayer) { - timer = 0; + timer = 20*TICRATE; if (!majormods && !multiplayer && !demo.playback) // move this once we have a proper time attack screen { @@ -1190,7 +1226,7 @@ void Y_StartIntermission(void) } else { - if (cv_inttime.value == 0 && ((intertype == int_coop) || (intertype == int_spec))) + if (cv_inttime.value == 0) timer = 0; else if (demo.playback) // Override inttime (which is pulled from the replay anyway timer = 10*TICRATE; @@ -1205,10 +1241,8 @@ void Y_StartIntermission(void) if (intermissiontypes[gametype] != int_none) intertype = intermissiontypes[gametype]; - else if (gametype == GT_RACE) - intertype = int_race; - else if (gametype == GT_BATTLE) - intertype = int_battle; + + sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE); // 8 second pause after match results // We couldn't display the intermission even if we wanted to. // But we still need to give the players their score bonuses, dummy. @@ -1250,7 +1284,9 @@ void Y_StartIntermission(void) } if (powertype != PWRLV_DISABLED) + { K_UpdatePowerLevels(); + } //if (intertype == int_race || intertype == int_battle) {