diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4bb0368a9..a24e66409 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -36,6 +36,7 @@ set(SRB2_CORE_SOURCES m_random.c md5.c mserv.c + http-mserv.c s_sound.c screen.c sounds.c @@ -170,6 +171,7 @@ set(SRB2_CORE_GAME_SOURCES k_botsearch.c k_respawn.c k_grandprix.c + k_hud.c p_local.h p_maputl.h @@ -192,6 +194,7 @@ set(SRB2_CORE_GAME_SOURCES 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 bdbe71f70..6adc8e885 100644 --- a/src/Makefile +++ b/src/Makefile @@ -552,6 +552,7 @@ OBJS:=$(i_main_o) \ $(OBJDIR)/w_wad.o \ $(OBJDIR)/filesrch.o \ $(OBJDIR)/mserv.o \ + $(OBJDIR)/http-mserv.o\ $(OBJDIR)/i_tcp.o \ $(OBJDIR)/lzf.o \ $(OBJDIR)/vid_copy.o \ @@ -564,10 +565,11 @@ OBJS:=$(i_main_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_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 a3baeedda..b418eaf36 100644 --- a/src/Makefile.cfg +++ b/src/Makefile.cfg @@ -1,3 +1,4 @@ +# vim: ft=make # # Makefile.cfg for SRB2 # @@ -7,6 +8,66 @@ # and other things # +# See the following variable don't start with 'GCC'. This is +# to avoid a false positive with the version detection... + +SUPPORTED_GCC_VERSIONS:=\ + 91\ + 81 82 83\ + 71 72\ + 61 62 63 64\ + 51 52 53 54\ + 40 41 42 43 44 45 46 47 48 49 + +LATEST_GCC_VERSION=9.1 + +# gcc or g++ +ifdef PREFIX + CC=$(PREFIX)-gcc + CXX=$(PREFIX)-g++ + OBJCOPY=$(PREFIX)-objcopy + OBJDUMP=$(PREFIX)-objdump + STRIP=$(PREFIX)-strip + WINDRES=$(PREFIX)-windres +else + OBJCOPY=objcopy + OBJDUMP=objdump + STRIP=strip + WINDRES=windres +endif + +# because Apple screws with us on this +# need to get bintools from homebrew +ifdef MACOSX + CC=clang + CXX=clang + OBJCOPY=gobjcopy + OBJDUMP=gobjdump +endif + +# Automatically set version flag, but not if one was manually set +ifeq (,$(filter GCC%,$(.VARIABLES))) + ifneq (,$(findstring gcc,$(shell $(CC) --version))) # if it's GCC + version:=$(shell $(CC) -dumpversion) + + # Turn version into words of major, minor + v:=$(subst ., ,$(version)) + # concat. major minor + v:=$(word 1,$(v))$(word 2,$(v)) + + # If this version is not in the list, default to the latest supported + ifeq (,$(filter $(v),$(SUPPORTED_GCC_VERSIONS))) + $(info\ + Your compiler version, GCC $(version), is not supported by the Makefile.\ + The Makefile will assume GCC $(LATEST_GCC_VERSION).) + GCC$(subst .,,$(LATEST_GCC_VERSION))=1 + else + $(info Detected GCC $(version) (GCC$(v))) + GCC$(v)=1 + endif + endif +endif + ifdef GCC91 GCC83=1 endif @@ -134,6 +195,9 @@ endif ifndef GCC295 WFLAGS+=-Wendif-labels endif +ifdef GCC40 + WFLAGS+=-std=gnu89 +endif ifdef GCC41 WFLAGS+=-Wshadow endif @@ -463,30 +527,6 @@ ifdef ARCHNAME BIN:=$(BIN)/$(ARCHNAME) endif -# gcc or g++ -ifdef PREFIX - CC=$(PREFIX)-gcc - CXX=$(PREFIX)-g++ - OBJCOPY=$(PREFIX)-objcopy - OBJDUMP=$(PREFIX)-objdump - STRIP=$(PREFIX)-strip - WINDRES=$(PREFIX)-windres -else - OBJCOPY=objcopy - OBJDUMP=objdump - STRIP=strip - WINDRES=windres -endif - -# because Apple screws with us on this -# need to get bintools from homebrew -ifdef MACOSX - CC=clang - CXX=clang - OBJCOPY=gobjcopy - OBJDUMP=gobjdump -endif - OBJDUMP_OPTS?=--wide --source --line-numbers LD=$(CC) diff --git a/src/console.c b/src/console.c index ec1e892ed..929b4a9af 100644 --- a/src/console.c +++ b/src/console.c @@ -31,6 +31,7 @@ #include "i_video.h" #include "z_zone.h" #include "i_system.h" +#include "i_threads.h" #include "d_main.h" #include "m_menu.h" #include "filesrch.h" @@ -45,6 +46,16 @@ #define MAXHUDLINES 20 +#ifdef HAVE_THREADS +I_mutex con_mutex; + +# define Lock_state() I_lock_mutex(&con_mutex) +# define Unlock_state() I_unlock_mutex(con_mutex) +#else/*HAVE_THREADS*/ +# define Lock_state() +# define Unlock_state() +#endif/*HAVE_THREADS*/ + static boolean con_started = false; // console has been initialised boolean con_startup = false; // true at game startup, screen need refreshing static boolean con_forcepic = true; // at startup toggle console translucency when first off @@ -170,6 +181,8 @@ static void CONS_hudlines_Change(void) { INT32 i; + Lock_state(); + // Clear the currently displayed lines for (i = 0; i < con_hudlines; i++) con_hudtime[i] = 0; @@ -181,6 +194,8 @@ static void CONS_hudlines_Change(void) con_hudlines = cons_hudlines.value; + Unlock_state(); + CONS_Printf(M_GetText("Number of console HUD lines is now %d\n"), con_hudlines); } @@ -188,12 +203,16 @@ static void CONS_hudlines_Change(void) // static void CONS_Clear_f(void) { + Lock_state(); + memset(con_buffer, 0, CON_BUFFERSIZE); con_cx = 0; con_cy = con_totallines-1; con_line = &con_buffer[con_cy*con_width]; con_scrollup = 0; + + Unlock_state(); } // Choose english keymap @@ -369,20 +388,29 @@ void CON_Init(void) for (i = 0; i < NUMINPUTS; i++) bindtable[i] = NULL; + Lock_state(); + // clear all lines memset(con_buffer, 0, CON_BUFFERSIZE); // make sure it is ready for the loading screen con_width = 0; + + Unlock_state(); + CON_RecalcSize(); CON_SetupColormaps(); + Lock_state(); + //note: CON_Ticker should always execute at least once before D_Display() con_clipviewtop = -1; // -1 does not clip con_hudlines = atoi(cons_hudlines.defaultvalue); + Unlock_state(); + // setup console input filtering CON_InputInit(); @@ -391,15 +419,23 @@ void CON_Init(void) COM_AddCommand("cls", CONS_Clear_f); //COM_AddCommand("english", CONS_English_f); // set console full screen for game startup MAKE SURE VID_Init() done !!! + Lock_state(); + con_destlines = vid.height; con_curlines = vid.height; + Unlock_state(); if (!dedicated) { + Lock_state(); + con_started = true; con_startup = true; // need explicit screen refresh until we are in Doom loop consoletoggle = false; + + Unlock_state(); + CV_RegisterVar(&cons_msgtimeout); CV_RegisterVar(&cons_hudlines); CV_RegisterVar(&cons_speed); @@ -411,19 +447,27 @@ void CON_Init(void) } else { + Lock_state(); + con_started = true; con_startup = false; // need explicit screen refresh until we are in Doom loop consoletoggle = true; + + Unlock_state(); } } // Console input initialization // static void CON_InputInit(void) { + Lock_state(); + // prepare the first prompt line memset(inputlines, 0, sizeof (inputlines)); inputline = 0; input_cur = input_sel = input_len = 0; + + Unlock_state(); } //====================================================================== @@ -439,6 +483,8 @@ static void CON_RecalcSize(void) char *tmp_buffer; char *string; + Lock_state(); + switch (cv_constextsize.value) { case V_NOSCALEPATCH: @@ -476,11 +522,18 @@ static void CON_RecalcSize(void) // check for change of video width if (conw == con_width) + { + Unlock_state(); return; // didn't change + } + + Unlock_state(); tmp_buffer = Z_Malloc(CON_BUFFERSIZE, PU_STATIC, NULL); string = Z_Malloc(CON_BUFFERSIZE, PU_STATIC, NULL); // BP: it is a line but who know + Lock_state(); + oldcon_width = con_width; oldnumlines = con_totallines; oldcon_cy = con_cy; @@ -501,6 +554,8 @@ static void CON_RecalcSize(void) con_line = &con_buffer[con_cy*con_width]; con_scrollup = 0; + Unlock_state(); + // re-arrange console text buffer to keep text if (oldcon_width) // not the first time { @@ -525,7 +580,11 @@ static void CON_RecalcSize(void) static void CON_ChangeHeight(void) { - INT32 minheight = 20 * con_scalefactor; // 20 = 8+8+4 + INT32 minheight; + + Lock_state(); + + minheight = 20 * con_scalefactor; // 20 = 8+8+4 // toggle console in con_destlines = (cons_height.value*vid.height)/100; @@ -535,13 +594,19 @@ static void CON_ChangeHeight(void) con_destlines = vid.height; con_destlines &= ~0x3; // multiple of text row height + + Unlock_state(); } // Handles Console moves in/out of screen (per frame) // static void CON_MoveConsole(void) { - const fixed_t conspeed = FixedDiv(cons_speed.value*vid.fdupy, FRACUNIT); + fixed_t conspeed; + + Lock_state(); + + conspeed = FixedDiv(cons_speed.value*vid.fdupy, FRACUNIT); // instant if (!cons_speed.value) @@ -563,6 +628,8 @@ static void CON_MoveConsole(void) if (con_curlines < con_destlines) con_curlines = con_destlines; } + + Unlock_state(); } INT32 CON_ShiftChar(INT32 ch) @@ -587,27 +654,44 @@ void CON_ClearHUD(void) { INT32 i; + Lock_state(); + for (i = 0; i < con_hudlines; i++) con_hudtime[i] = 0; + + Unlock_state(); } // Force console to move out immediately // note: con_ticker will set consoleready false void CON_ToggleOff(void) { + Lock_state(); + if (!con_destlines) + { + Unlock_state(); return; + } con_destlines = 0; con_curlines = 0; CON_ClearHUD(); con_forcepic = 0; con_clipviewtop = -1; // remove console clipping of view + + Unlock_state(); } boolean CON_Ready(void) { - return consoleready; + boolean ready; + Lock_state(); + { + ready = consoleready; + } + Unlock_state(); + return ready; } // Console ticker: handles console move in/out, cursor blinking @@ -615,7 +699,11 @@ boolean CON_Ready(void) void CON_Ticker(void) { INT32 i; - INT32 minheight = 20 * con_scalefactor; // 20 = 8+8+4 + INT32 minheight; + + Lock_state(); + + minheight = 20 * con_scalefactor; // 20 = 8+8+4 // cursor blinking con_tick++; @@ -673,6 +761,8 @@ void CON_Ticker(void) if (con_hudtime[i] < 0) con_hudtime[i] = 0; } + + Unlock_state(); } // @@ -684,32 +774,51 @@ void CON_Ticker(void) static void CON_InputClear(void) { + Lock_state(); + memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS); input_cur = input_sel = input_len = 0; + + Unlock_state(); } static void CON_InputSetString(const char *c) { + Lock_state(); + memset(inputlines[inputline], 0, CON_MAXPROMPTCHARS); strcpy(inputlines[inputline], c); input_cur = input_sel = input_len = strlen(c); + + Unlock_state(); } static void CON_InputAddString(const char *c) { size_t csize = strlen(c); + + Lock_state(); + if (input_len + csize > CON_MAXPROMPTCHARS-1) + { + Unlock_state(); return; + } if (input_cur != input_len) memmove(&inputlines[inputline][input_cur+csize], &inputlines[inputline][input_cur], input_len-input_cur); memcpy(&inputlines[inputline][input_cur], c, csize); input_len += csize; input_sel = (input_cur += csize); + + Unlock_state(); } static void CON_InputDelSelection(void) { size_t start, end, len; + + Lock_state(); + if (input_cur > input_sel) { start = input_sel; @@ -728,27 +837,39 @@ static void CON_InputDelSelection(void) input_len -= len; input_sel = input_cur = start; + + Unlock_state(); } static void CON_InputAddChar(char c) { if (input_len >= CON_MAXPROMPTCHARS-1) return; + + Lock_state(); + if (input_cur != input_len) memmove(&inputlines[inputline][input_cur+1], &inputlines[inputline][input_cur], input_len-input_cur); inputlines[inputline][input_cur++] = c; inputlines[inputline][++input_len] = 0; input_sel = input_cur; + + Unlock_state(); } static void CON_InputDelChar(void) { if (!input_cur) return; + + Lock_state(); + if (input_cur != input_len) memmove(&inputlines[inputline][input_cur-1], &inputlines[inputline][input_cur], input_len-input_cur); inputlines[inputline][--input_len] = 0; input_sel = --input_cur; + + Unlock_state(); } // @@ -1174,6 +1295,8 @@ static void CON_Print(char *msg) S_StartSound(NULL, sfx_radio); } + Lock_state(); + if (!(*msg & 0x80)) { con_line[con_cx++] = '\x80'; @@ -1234,7 +1357,10 @@ static void CON_Print(char *msg) } if (*msg == '\0') + { + Unlock_state(); return; + } // printable character for (l = 0; l < (con_width-11) && msg[l] > ' '; l++) @@ -1252,6 +1378,8 @@ static void CON_Print(char *msg) for (; l > 0; l--) con_line[con_cx++] = *(msg++); } + + Unlock_state(); } void CON_LogMessage(const char *msg) @@ -1283,6 +1411,7 @@ void CONS_Printf(const char *fmt, ...) { va_list argptr; static char *txt = NULL; + boolean startup; if (txt == NULL) txt = malloc(8192); @@ -1315,11 +1444,16 @@ void CONS_Printf(const char *fmt, ...) CON_LogMessage(txt); #endif + Lock_state(); + // make sure new text is visible con_scrollup = 0; + startup = con_startup; + + Unlock_state(); // if not in display loop, force screen update - if (con_startup) + if (startup) { #if (defined (_WINDOWS)) || (defined (__OS2__) && !defined (HAVE_SDL)) patch_t *con_backpic = W_CachePatchName("KARTKREW", PU_CACHE); @@ -1633,8 +1767,13 @@ static void CON_DrawConsole(void) // void CON_Drawer(void) { + Lock_state(); + if (!con_started || !graphics_started) + { + Unlock_state(); return; + } if (con_recalc) CON_RecalcSize(); @@ -1644,4 +1783,6 @@ void CON_Drawer(void) else if (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_CUTSCENE || gamestate == GS_CREDITS || gamestate == GS_VOTING || gamestate == GS_EVALUATION || gamestate == GS_WAITINGPLAYERS) CON_DrawHudlines(); + + Unlock_state(); } diff --git a/src/console.h b/src/console.h index 7ed585177..da22ee03e 100644 --- a/src/console.h +++ b/src/console.h @@ -12,6 +12,7 @@ #include "d_event.h" #include "command.h" +#include "i_threads.h" #ifdef _WII void CON_InitWii(void); @@ -21,6 +22,10 @@ void CON_Init(void); boolean CON_Responder(event_t *ev); +#ifdef HAVE_THREADS +extern I_mutex con_mutex; +#endif + // set true when screen size has changed, to adapt console extern boolean con_recalc; diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 7542a1b92..1282abb04 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1318,7 +1318,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 @@ -1818,7 +1818,7 @@ static void CL_LoadReceivedSavegame(void) #endif #ifndef NONET -static void SendAskInfo(INT32 node, boolean viams) +static void SendAskInfo(INT32 node) { const tic_t asktime = I_GetTime(); netbuffer->packettype = PT_ASKINFO; @@ -1829,10 +1829,6 @@ static void SendAskInfo(INT32 node, boolean viams) // now allowed traffic from the host to us in, so once the MS relays // our address to the host, it'll be able to speak to us. HSendPacket(node, false, 0, sizeof (askinfo_pak)); - - // Also speak to the MS. - if (viams && node != 0 && node != BROADCASTADDR) - SendAskInfoViaMS(node, asktime); } serverelem_t serverlist[MAXSERVERLIST]; @@ -1898,13 +1894,96 @@ static void SL_InsertServer(serverinfo_pak* info, SINT8 node) M_SortServerList(); } +#ifdef HAVE_THREADS +struct Fetch_servers_ctx +{ + int room; + int id; +}; + +static void +Fetch_servers_thread (struct Fetch_servers_ctx *ctx) +{ + msg_server_t *server_list; + + server_list = GetShortServersList(ctx->room, ctx->id); + + if (server_list) + { + I_lock_mutex(&ms_QueryId_mutex); + { + if (ctx->id != ms_QueryId) + { + free(server_list); + server_list = NULL; + } + } + I_unlock_mutex(ms_QueryId_mutex); + + if (server_list) + { + I_lock_mutex(&m_menu_mutex); + { + if (m_waiting_mode == M_WAITING_SERVERS) + m_waiting_mode = M_NOT_WAITING; + } + I_unlock_mutex(m_menu_mutex); + + I_lock_mutex(&ms_ServerList_mutex); + { + ms_ServerList = server_list; + } + I_unlock_mutex(ms_ServerList_mutex); + } + } + + free(ctx); +} +#endif/*HAVE_THREADS*/ + +void CL_QueryServerList (msg_server_t *server_list) +{ + INT32 i; + + for (i = 0; server_list[i].header.buffer[0]; i++) + { + // Make sure MS version matches our own, to + // thwart nefarious servers who lie to the MS. + + /* lol bruh, that version COMES from the servers */ + //if (strcmp(version, server_list[i].version) == 0) + { + INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port); + if (node == -1) + break; // no more node free + SendAskInfo(node); + // Force close the connection so that servers can't eat + // up nodes forever if we never get a reply back from them + // (usually when they've not forwarded their ports). + // + // Don't worry, we'll get in contact with the working + // servers again when they send SERVERINFO to us later! + // + // (Note: as a side effect this probably means every + // server in the list will probably be using the same node (e.g. node 1), + // not that it matters which nodes they use when + // the connections are closed afterwards anyway) + // -- Monster Iestyn 12/11/18 + Net_CloseConnection(node|FORCECLOSE); + } + } +} + void CL_UpdateServerList(boolean internetsearch, INT32 room) { +#ifdef HAVE_THREADS + struct Fetch_servers_ctx *ctx; +#endif + SL_ClearServerList(0); if (!netgame && I_NetOpenSocket) { - MSCloseUDPSocket(); // Tidy up before wiping the slate. if (I_NetOpenSocket()) { netgame = true; @@ -1914,56 +1993,36 @@ void CL_UpdateServerList(boolean internetsearch, INT32 room) // search for local servers if (netgame) - SendAskInfo(BROADCASTADDR, false); + SendAskInfo(BROADCASTADDR); if (internetsearch) { - const msg_server_t *server_list; - INT32 i = -1; - server_list = GetShortServersList(room); +#ifdef HAVE_THREADS + ctx = malloc(sizeof *ctx); + + /* This called from M_Refresh so I don't use a mutex */ + m_waiting_mode = M_WAITING_SERVERS; + + I_lock_mutex(&ms_QueryId_mutex); + { + ctx->id = ms_QueryId; + } + I_unlock_mutex(ms_QueryId_mutex); + + ctx->room = room; + + I_spawn_thread("fetch-servers", (I_thread_fn)Fetch_servers_thread, ctx); +#else + msg_server_t *server_list; + + server_list = GetShortServersList(room, 0); + if (server_list) { - char version[8] = ""; -#if VERSION > 0 || SUBVERSION > 0 - snprintf(version, sizeof (version), "%d.%d", VERSION, SUBVERSION); -#else - strcpy(version, GetRevisionString()); + CL_QueryServerList(server_list); + free(server_list); + } #endif - version[sizeof (version) - 1] = '\0'; - - for (i = 0; server_list[i].header.buffer[0]; i++) - { - // Make sure MS version matches our own, to - // thwart nefarious servers who lie to the MS. - - if (strcmp(version, server_list[i].version) == 0) - { - INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port); - if (node == -1) - break; // no more node free - SendAskInfo(node, true); - // Force close the connection so that servers can't eat - // up nodes forever if we never get a reply back from them - // (usually when they've not forwarded their ports). - // - // Don't worry, we'll get in contact with the working - // servers again when they send SERVERINFO to us later! - // - // (Note: as a side effect this probably means every - // server in the list will probably be using the same node (e.g. node 1), - // not that it matters which nodes they use when - // the connections are closed afterwards anyway) - // -- Monster Iestyn 12/11/18 - Net_CloseConnection(node|FORCECLOSE); - } - } - } - - //no server list?(-1) or no servers?(0) - if (!i) - { - ; /// TODO: display error or warning? - } } } @@ -1993,11 +2052,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; @@ -2018,11 +2077,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; @@ -2043,14 +2103,13 @@ static boolean CL_FinishedFileList(void) /** Called by CL_ServerConnectionTicker * - * \param viams ??? * \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit. * \return False if the connection was aborted * \sa CL_ServerConnectionTicker * \sa CL_ConnectToServer * */ -static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent) +static boolean CL_ServerConnectionSearchTicker(tic_t *asksent) { #ifndef NONET INT32 i; @@ -2114,7 +2173,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent) // Ask the info to the server (askinfo packet) if (*asksent + NEWTICRATE < I_GetTime()) { - SendAskInfo(servernode, viams); + SendAskInfo(servernode); *asksent = I_GetTime(); } #else @@ -2129,7 +2188,6 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent) /** Called by CL_ConnectToServer * - * \param viams ??? * \param tmpsave The name of the gamestate file??? * \param oldtic Used for knowing when to poll events and redraw * \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit. @@ -2138,7 +2196,7 @@ static boolean CL_ServerConnectionSearchTicker(boolean viams, tic_t *asksent) * \sa CL_ConnectToServer * */ -static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic_t *oldtic, tic_t *asksent) +static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic_t *asksent) { boolean waitmore; INT32 i; @@ -2150,7 +2208,7 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic switch (cl_mode) { case CL_SEARCHING: - if (!CL_ServerConnectionSearchTicker(viams, asksent)) + if (!CL_ServerConnectionSearchTicker(asksent)) return false; break; @@ -2315,11 +2373,10 @@ static boolean CL_ServerConnectionTicker(boolean viams, const char *tmpsave, tic /** Use adaptive send using net_bandwidth and stat.sendbytes * - * \param viams ??? * \todo Better description... * */ -static void CL_ConnectToServer(boolean viams) +static void CL_ConnectToServer(void) { INT32 pnumnodes, nodewaited = doomcom->numnodes, i; tic_t oldtic; @@ -2392,9 +2449,9 @@ static void CL_ConnectToServer(boolean viams) { // If the connection was aborted for some reason, leave #ifndef NONET - if (!CL_ServerConnectionTicker(viams, tmpsave, &oldtic, &asksent)) + if (!CL_ServerConnectionTicker(tmpsave, &oldtic, &asksent)) #else - if (!CL_ServerConnectionTicker(viams, (char*)NULL, &oldtic, (tic_t *)NULL)) + if (!CL_ServerConnectionTicker((char*)NULL, &oldtic, (tic_t *)NULL)) #endif return; @@ -2575,9 +2632,6 @@ static void Command_ReloadBan(void) //recheck ban.txt static void Command_connect(void) { - // Assume we connect directly. - boolean viams = false; - if (COM_Argc() < 2 || *COM_Argv(1) == 0) { CONS_Printf(M_GetText( @@ -2611,9 +2665,6 @@ static void Command_connect(void) if (netgame && !stricmp(COM_Argv(1), "node")) { servernode = (SINT8)atoi(COM_Argv(2)); - - // Use MS to traverse NAT firewalls. - viams = true; } else if (netgame) { @@ -2622,7 +2673,6 @@ static void Command_connect(void) } else if (I_NetOpenSocket) { - MSCloseUDPSocket(); // Tidy up before wiping the slate. I_NetOpenSocket(); netgame = true; multiplayer = true; @@ -2650,7 +2700,7 @@ static void Command_connect(void) SplitScreen_OnChange(); } - CL_ConnectToServer(viams); + CL_ConnectToServer(); } #endif @@ -3824,7 +3874,6 @@ boolean SV_SpawnServer(void) SV_GenContext(); if (netgame && I_NetOpenSocket) { - MSCloseUDPSocket(); // Tidy up before wiping the slate. I_NetOpenSocket(); if (ms_RoomId > 0) RegisterServer(); @@ -3832,7 +3881,7 @@ boolean SV_SpawnServer(void) // non dedicated server just connect to itself if (!dedicated) - CL_ConnectToServer(false); + CL_ConnectToServer(); else doomcom->numslots = 1; } @@ -4319,7 +4368,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) { XBOXSTATIC char buf[2]; @@ -5768,7 +5816,13 @@ FILESTAMP if (nowtime > resptime) { resptime = nowtime; +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif M_Ticker(); +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif CON_Ticker(); } SV_FileSendTicker(); diff --git a/src/d_clisrv.h b/src/d_clisrv.h index c3cd4622b..1f21779d4 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -18,6 +18,7 @@ #include "d_netcmd.h" #include "tables.h" #include "d_player.h" +#include "mserv.h" #include "k_pwrlv.h" // PWRLV_NUMTYPES /* @@ -566,6 +567,7 @@ typedef enum } kickreason_t; extern boolean server; +extern boolean serverrunning; #define client (!server) extern boolean dedicated; // For dedicated server extern UINT16 software_MAXPACKETLENGTH; @@ -614,6 +616,7 @@ void CL_RemoveSplitscreenPlayer(UINT8 p); void CL_Reset(void); void CL_ClearPlayer(INT32 playernum); void CL_RemovePlayer(INT32 playernum, INT32 reason); +void CL_QueryServerList(msg_server_t *list); void CL_UpdateServerList(boolean internetsearch, INT32 room); // Is there a game running boolean Playing(void); diff --git a/src/d_main.c b/src/d_main.c index fe7e312ee..76f9f95eb 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -50,6 +50,7 @@ int snprintf(char *str, size_t n, const char *fmt, ...); #include "hu_stuff.h" #include "i_sound.h" #include "i_system.h" +#include "i_threads.h" #include "i_video.h" #include "m_argv.h" #include "m_menu.h" @@ -198,6 +199,8 @@ void D_ProcessEvents(void) { event_t *ev; + boolean eaten; + for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1)) { ev = &events[eventtail]; @@ -219,7 +222,17 @@ void D_ProcessEvents(void) } // Menu input - if (M_Responder(ev)) +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif + { + eaten = M_Responder(ev); + } +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif + + if (eaten) continue; // menu ate the event // Demo input: @@ -228,7 +241,17 @@ void D_ProcessEvents(void) continue; // demo ate the event // console input - if (CON_Responder(ev)) +#ifdef HAVE_THREADS + I_lock_mutex(&con_mutex); +#endif + { + eaten = CON_Responder(ev); + } +#ifdef HAVE_THREADS + I_unlock_mutex(con_mutex); +#endif + + if (eaten) continue; // ate the event G_Responder(ev); @@ -517,7 +540,13 @@ static void D_Display(void) if (gamestate != GS_TIMEATTACK) CON_Drawer(); +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif M_Drawer(); // menu is drawn even on top of everything +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif // focus lost moved to M_Drawer // diff --git a/src/d_net.h b/src/d_net.h index 809c4ebcd..607f3e901 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 3ce214aab..a106530cf 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -116,6 +116,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); @@ -292,6 +302,23 @@ consvar_t cv_skin2 = {"skin2", DEFAULTSKIN2, CV_SAVE|CV_CALL|CV_NOINIT, NULL, Sk consvar_t cv_skin3 = {"skin3", DEFAULTSKIN3, CV_SAVE|CV_CALL|CV_NOINIT, NULL, Skin3_OnChange, 0, NULL, NULL, 0, 0, NULL}; consvar_t cv_skin4 = {"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; @@ -368,6 +395,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}; @@ -804,11 +832,17 @@ void D_RegisterClientCommands(void) for (i = 0; i < MAXSKINCOLORS; i++) { - Color_cons_t[i].value = i; - Color_cons_t[i].strvalue = KartColor_Names[i]; // SRB2kart + Color_cons_t[i].value = Followercolor_cons_t[i].value = i; + Color_cons_t[i].strvalue = Followercolor_cons_t[i].strvalue = KartColor_Names[i]; // SRB2kart } - Color_cons_t[MAXSKINCOLORS].value = 0; - Color_cons_t[MAXSKINCOLORS].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. if (dedicated) return; @@ -880,18 +914,27 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_playername); CV_RegisterVar(&cv_playercolor); CV_RegisterVar(&cv_skin); // r_things.c (skin NAME) + CV_RegisterVar(&cv_follower); + CV_RegisterVar(&cv_followercolor); + CV_RegisterVar(&cv_showfollowers); // secondary player (splitscreen) CV_RegisterVar(&cv_playername2); CV_RegisterVar(&cv_playercolor2); CV_RegisterVar(&cv_skin2); + CV_RegisterVar(&cv_follower2); + CV_RegisterVar(&cv_followercolor2); // third player CV_RegisterVar(&cv_playername3); CV_RegisterVar(&cv_playercolor3); CV_RegisterVar(&cv_skin3); + CV_RegisterVar(&cv_follower3); + CV_RegisterVar(&cv_followercolor3); // fourth player CV_RegisterVar(&cv_playername4); CV_RegisterVar(&cv_playercolor4); CV_RegisterVar(&cv_skin4); + CV_RegisterVar(&cv_follower4); + CV_RegisterVar(&cv_followercolor4); // preferred number of players CV_RegisterVar(&cv_splitplayers); @@ -1449,7 +1492,7 @@ static INT32 snacpending = 0, snac2pending = 0, snac3pending = 0, snac4pending = // static void SendNameAndColor(void) { - XBOXSTATIC char buf[MAXPLAYERNAME+2]; + XBOXSTATIC char buf[MAXPLAYERNAME+3]; char *p; p = buf; @@ -1474,10 +1517,13 @@ static void SendNameAndColor(void) CV_StealthSet(&cv_playercolor, cv_playercolor.defaultvalue); } - if (!strcmp(cv_playername.string, player_names[consoleplayer]) - && cv_playercolor.value == players[consoleplayer].skincolor - && !strcmp(cv_skin.string, skins[players[consoleplayer].skin].name)) - return; + // ditto for follower colour: + if (!cv_followercolor.value) + CV_StealthSet(&cv_followercolor, "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.value > numfollowers-1 || cv_follower.value < -1) + CV_StealthSet(&cv_follower, "-1"); // We'll handle it later if we're not playing. if (!Playing()) @@ -1496,6 +1542,10 @@ static void SendNameAndColor(void) if (players[consoleplayer].mo) players[consoleplayer].mo->color = players[consoleplayer].skincolor; + // Update follower for local games: + if (cv_follower.value >= -1 && cv_follower.value != players[consoleplayer].followerskin) + SetFollower(consoleplayer, cv_follower.value); + if (metalrecording) { // Metal Sonic is Sonic, obviously. SetPlayerSkinByNum(consoleplayer, 0); @@ -1559,6 +1609,8 @@ static void SendNameAndColor(void) WRITESTRINGN(p, cv_playername.zstring, MAXPLAYERNAME); WRITEUINT8(p, (UINT8)cv_playercolor.value); WRITEUINT8(p, (UINT8)cv_skin.value); + WRITESINT8(p, (UINT8)cv_follower.value); + WRITESINT8(p, (UINT8)cv_followercolor.value); SendNetXCmd(XD_NAMEANDCOLOR, buf, p - buf); } @@ -1566,7 +1618,7 @@ static void SendNameAndColor(void) static void SendNameAndColor2(void) { INT32 secondplaya = -1; - XBOXSTATIC char buf[MAXPLAYERNAME+2]; + XBOXSTATIC char buf[MAXPLAYERNAME+3]; char *p; if (splitscreen < 1) @@ -1602,6 +1654,14 @@ static void SendNameAndColor2(void) CV_StealthSet(&cv_playercolor2, cv_playercolor2.defaultvalue); } + // ditto for follower colour: + if (!cv_followercolor2.value) + CV_StealthSet(&cv_followercolor2, "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_follower2.value > numfollowers-1 || cv_follower2.value < -1) + CV_StealthSet(&cv_follower2, "-1"); + // We'll handle it later if we're not playing. if (!Playing()) return; @@ -1619,6 +1679,10 @@ static void SendNameAndColor2(void) if (players[secondplaya].mo) players[secondplaya].mo->color = players[secondplaya].skincolor; + // Update follower for local games: + if (cv_follower2.value >= -1 && cv_follower2.value != players[secondplaya].followerskin) + SetFollower(secondplaya, cv_follower2.value); + if ((foundskin = R_SkinAvailable(cv_skin2.string)) != -1) { //boolean notsame; @@ -1675,13 +1739,15 @@ static void SendNameAndColor2(void) WRITESTRINGN(p, cv_playername2.zstring, MAXPLAYERNAME); WRITEUINT8(p, (UINT8)cv_playercolor2.value); WRITEUINT8(p, (UINT8)cv_skin2.value); + WRITESINT8(p, (UINT8)cv_follower2.value); + WRITESINT8(p, (UINT8)cv_followercolor2.value); SendNetXCmd2(XD_NAMEANDCOLOR, buf, p - buf); } static void SendNameAndColor3(void) { INT32 thirdplaya = -1; - XBOXSTATIC char buf[MAXPLAYERNAME+2]; + XBOXSTATIC char buf[MAXPLAYERNAME+3]; char *p; if (splitscreen < 2) @@ -1706,6 +1772,10 @@ static void SendNameAndColor3(void) CV_StealthSetValue(&cv_playercolor3, skincolor_blueteam); } + // ditto for follower colour: + if (!cv_followercolor3.value) + CV_StealthSet(&cv_followercolor3, "Match"); // set it to "Match". I don't care about your stupidity! + // never allow the color "none" if (!cv_playercolor3.value) { @@ -1717,6 +1787,10 @@ static void SendNameAndColor3(void) CV_StealthSet(&cv_playercolor3, cv_playercolor3.defaultvalue); } + // 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_follower3.value > numfollowers-1 || cv_follower3.value < -1) + CV_StealthSet(&cv_follower3, "-1"); + // We'll handle it later if we're not playing. if (!Playing()) return; @@ -1734,6 +1808,10 @@ static void SendNameAndColor3(void) if (players[thirdplaya].mo) players[thirdplaya].mo->color = players[thirdplaya].skincolor; + // Update follower for local games: + if (cv_follower3.value >= -1 && cv_follower3.value != players[thirdplaya].followerskin) + SetFollower(thirdplaya, cv_follower3.value); + if ((foundskin = R_SkinAvailable(cv_skin3.string)) != -1) { //boolean notsame; @@ -1790,13 +1868,15 @@ static void SendNameAndColor3(void) WRITESTRINGN(p, cv_playername3.zstring, MAXPLAYERNAME); WRITEUINT8(p, (UINT8)cv_playercolor3.value); WRITEUINT8(p, (UINT8)cv_skin3.value); + WRITESINT8(p, (UINT8)cv_follower3.value); + WRITESINT8(p, (UINT8)cv_followercolor3.value); SendNetXCmd3(XD_NAMEANDCOLOR, buf, p - buf); } static void SendNameAndColor4(void) { INT32 fourthplaya = -1; - XBOXSTATIC char buf[MAXPLAYERNAME+2]; + XBOXSTATIC char buf[MAXPLAYERNAME+3]; char *p; if (splitscreen < 3) @@ -1821,6 +1901,10 @@ static void SendNameAndColor4(void) CV_StealthSetValue(&cv_playercolor4, skincolor_blueteam); } + // ditto for follower colour: + if (!cv_followercolor4.value) + CV_StealthSet(&cv_followercolor4, "Match"); // set it to "Match". I don't care about your stupidity! + // never allow the color "none" if (!cv_playercolor4.value) { @@ -1832,6 +1916,10 @@ static void SendNameAndColor4(void) CV_StealthSet(&cv_playercolor4, cv_playercolor4.defaultvalue); } + // 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_follower4.value > numfollowers-1 || cv_follower4.value < -1) + CV_StealthSet(&cv_follower4, "-1"); + // We'll handle it later if we're not playing. if (!Playing()) return; @@ -1849,6 +1937,10 @@ static void SendNameAndColor4(void) if (players[fourthplaya].mo) players[fourthplaya].mo->color = players[fourthplaya].skincolor; + // Update follower for local games: + if (cv_follower4.value >= -1 && cv_follower4.value != players[fourthplaya].followerskin) + SetFollower(fourthplaya, cv_follower4.value); + if ((foundskin = R_SkinAvailable(cv_skin4.string)) != -1) { //boolean notsame; @@ -1905,6 +1997,8 @@ static void SendNameAndColor4(void) WRITESTRINGN(p, cv_playername4.zstring, MAXPLAYERNAME); WRITEUINT8(p, (UINT8)cv_playercolor4.value); WRITEUINT8(p, (UINT8)cv_skin4.value); + WRITESINT8(p, (UINT8)cv_follower4.value); + WRITESINT8(p, (UINT8)cv_followercolor4.value); SendNetXCmd4(XD_NAMEANDCOLOR, buf, p - buf); } @@ -1912,7 +2006,8 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) { player_t *p = &players[playernum]; char name[MAXPLAYERNAME+1]; - UINT8 color, skin; + UINT8 color, skin, followercolor; + SINT8 follower; #ifdef PARANOIA if (playernum < 0 || playernum > MAXPLAYERS) @@ -1936,6 +2031,8 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) READSTRINGN(*cp, name, MAXPLAYERNAME); color = READUINT8(*cp); skin = READUINT8(*cp); + follower = READSINT8(*cp); + followercolor = READSINT8(*cp); // set name if (strcasecmp(player_names[playernum], name) != 0) @@ -1995,6 +2092,13 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) } else SetPlayerSkinByNum(playernum, skin); + + // set follower colour: + // Don't bother doing garbage and kicking if we receive None, this is both silly and a waste of time, this will be handled properly in P_HandleFollower. + p->followercolor = followercolor; + + // set follower + SetFollower(playernum, follower); } void SendWeaponPref(void) @@ -2002,8 +2106,6 @@ void SendWeaponPref(void) XBOXSTATIC UINT8 buf[1]; buf[0] = 0; - if (cv_flipcam.value) - buf[0] |= 1; SendNetXCmd(XD_WEAPONPREF, buf, 1); } @@ -2012,8 +2114,6 @@ void SendWeaponPref2(void) XBOXSTATIC UINT8 buf[1]; buf[0] = 0; - if (cv_flipcam2.value) - buf[0] |= 1; SendNetXCmd2(XD_WEAPONPREF, buf, 1); } @@ -2022,8 +2122,6 @@ void SendWeaponPref3(void) XBOXSTATIC UINT8 buf[1]; buf[0] = 0; - if (cv_flipcam3.value) - buf[0] |= 1; SendNetXCmd3(XD_WEAPONPREF, buf, 1); } @@ -2032,18 +2130,13 @@ void SendWeaponPref4(void) XBOXSTATIC UINT8 buf[1]; buf[0] = 0; - if (cv_flipcam4.value) - buf[0] |= 1; SendNetXCmd4(XD_WEAPONPREF, buf, 1); } static void Got_WeaponPref(UINT8 **cp,INT32 playernum) { - UINT8 prefs = READUINT8(*cp); - - players[playernum].pflags &= ~(PF_FLIPCAM); - if (prefs & 1) - players[playernum].pflags |= PF_FLIPCAM; + (void)playernum; + /*UINT8 prefs = */READUINT8(*cp); // Read it still to avoid instant desyncs in netgames. } static void Got_PowerLevel(UINT8 **cp,INT32 playernum) @@ -2815,6 +2908,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); @@ -6034,6 +6128,198 @@ static void Name4_OnChange(void) SendNameAndColor4(); } +// 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 diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 5e13f56da..6acaf7b97 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -21,18 +21,23 @@ extern consvar_t cv_playername; extern consvar_t cv_playercolor; extern consvar_t cv_skin; +extern consvar_t cv_follower; +extern consvar_t cv_showfollowers; // secondary splitscreen player extern consvar_t cv_playername2; extern consvar_t cv_playercolor2; extern consvar_t cv_skin2; +extern consvar_t cv_follower2; // third splitscreen player extern consvar_t cv_playername3; extern consvar_t cv_playercolor3; extern consvar_t cv_skin3; +extern consvar_t cv_follower3; // fourth splitscreen player extern consvar_t cv_playername4; extern consvar_t cv_playercolor4; extern consvar_t cv_skin4; +extern consvar_t cv_follower4; // preferred number of players extern consvar_t cv_splitplayers; @@ -103,7 +108,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 45c711fba..8e885f977 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -1084,7 +1084,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 a9907588c..581b6fb83 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -237,7 +237,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, @@ -287,12 +288,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_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 @@ -373,6 +378,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 @@ -514,6 +522,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) diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index c73c161b5..6c14a0f4b 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 ccb0d3260..151f96dcd 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -34,6 +34,7 @@ #include "lua_script.h" #include "lua_hook.h" #include "d_clisrv.h" +#include "r_things.h" // for followers #include "m_cond.h" @@ -674,6 +675,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); @@ -3418,6 +3684,13 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad) // This is not a major mod. 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 (fastcmp(word, "CHARACTER")) { @@ -6312,6 +6585,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", @@ -7207,6 +7513,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", @@ -7795,6 +8126,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", @@ -8074,6 +8407,10 @@ 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", @@ -8122,7 +8459,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 @@ -8139,7 +8476,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. @@ -8162,10 +8499,6 @@ static const char *const MOBJEFLAG_LIST[] = { "JUSTBOUNCEDWALL", // SRB2Kart: Mobj already bounced off a wall this tic "SPRUNG", // Mobj was already sprung this tic "APPLYPMOMZ", // Platform movement - "DRAWONLYFORP1", // SRB2Kart: Splitscreen sprite draw flags - "DRAWONLYFORP2", - "DRAWONLYFORP3", - "DRAWONLYFORP4", NULL }; @@ -8504,12 +8837,16 @@ static const char *const KARTSTUFF_LIST[] = { "JMP", "OFFROAD", "BRAKESTOP", + "SPINDASH", + "SPINDASHSPEED", + "SPINDASHBOOST", "WATERSKIP", "DASHPADCOOLDOWN", "NUMBOOSTS", "BOOSTPOWER", "SPEEDBOOST", "ACCELBOOST", + "HANDLEBOOST", "DRAFTPOWER", "DRAFTLEEWAY", "LASTDRAFT", @@ -9051,7 +9388,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}, @@ -9076,7 +9413,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}, @@ -9098,6 +9436,29 @@ struct { {"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}, #endif {NULL,0} diff --git a/src/doomstat.h b/src/doomstat.h index ffac81726..e678886a8 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -482,8 +482,13 @@ extern UINT16 extralifetics; // 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; @@ -534,6 +539,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_finale.c b/src/f_finale.c index a4de49788..7a8cf0681 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -24,6 +24,7 @@ #include "w_wad.h" #include "z_zone.h" #include "i_system.h" +#include "i_threads.h" #include "m_menu.h" #include "dehacked.h" #include "g_input.h" @@ -317,7 +318,13 @@ void F_IntroDrawer(void) { I_OsPolling(); I_UpdateNoBlit(); +#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(); // Update the screen with the image Tails 06-19-2001 } } 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/g_game.c b/src/g_game.c index 47b2c6808..207c8ca5f 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -224,10 +224,15 @@ UINT16 spacetimetics = 11*TICRATE + (TICRATE/2); UINT16 extralifetics = 4*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); @@ -289,6 +294,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. @@ -1255,13 +1261,12 @@ INT32 JoyAxis(axis_input_e axissel, UINT8 p) INT32 localaiming[MAXSPLITSCREENPLAYERS]; angle_t localangle[MAXSPLITSCREENPLAYERS]; -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 void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) { - 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, analogjoystickmove, gamepadjoystickmove; @@ -1372,7 +1377,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) turnright = turnright || (axis > 0); turnleft = turnleft || (axis < 0); } - forward = side = 0; + forward = 0; // use two stage accelerative turning // on the keyboard and joystick @@ -1393,13 +1398,11 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) { cmd->angleturn = (INT16)(cmd->angleturn - (angleturn[tspeed])); cmd->driftturn = (INT16)(cmd->driftturn - (angleturn[tspeed])); - side += sidemove[1]; } else if (turnleft && !(turnright)) { cmd->angleturn = (INT16)(cmd->angleturn + (angleturn[tspeed])); cmd->driftturn = (INT16)(cmd->driftturn + (angleturn[tspeed])); - side -= sidemove[1]; } if (analogjoystickmove && axis != 0) @@ -1407,7 +1410,6 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) // JOYAXISRANGE should be 1023 (divide by 1024) cmd->angleturn = (INT16)(cmd->angleturn - (((axis * angleturn[1]) >> 10))); // ANALOG! cmd->driftturn = (INT16)(cmd->driftturn - (((axis * angleturn[1]) >> 10))); - side += ((axis * sidemove[0]) >> 10); } // Specator mouse turning @@ -1427,9 +1429,9 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) cmd->buttons |= BT_BRAKE; axis = JoyAxis(AXISAIM, ssplayer); if (InputDown(gc_aimforward, ssplayer) || (usejoystick && axis < 0)) - forward += forwardmove[1]; + forward += forwardmove; if (InputDown(gc_aimbackward, ssplayer) || (usejoystick && axis > 0)) - forward -= forwardmove[1]; + forward -= forwardmove; } else { @@ -1438,13 +1440,13 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) if (InputDown(gc_accelerate, ssplayer) || (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 = JoyAxis(AXISBRAKE, ssplayer); @@ -1452,14 +1454,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. @@ -1508,7 +1510,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 // mouse look stuff (mouse look is not the same as mouse aim) @@ -1554,21 +1556,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 (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; //{ 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 @@ -1588,12 +1581,7 @@ 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->respawn.state != RESPAWNST_NONE) // Respawning - || (player->spectator || objectplacing)) // Not a physical player - lang += (cmd->angleturn<<16); + 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 @@ -1623,7 +1611,7 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer) #endif //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) displayplayers[0] = consoleplayer; } @@ -2337,10 +2325,6 @@ void G_Ticker(boolean run) UINT32 i; INT32 buf; ticcmd_t *cmd; - UINT32 ra_timeskip = (modeattacking && !demo.playback && leveltime < starttime - TICRATE*4) ? 0 : (starttime - TICRATE*4 - 1); - // starttime - TICRATE*4 is where we want RA to start when we PLAY IT, so we will loop the main thinker on RA start to get it to this point, - // the reason this is done is to ensure that ghosts won't look out of synch with other map elements (objects, moving platforms...) - // when we REPLAY, don't skip, let the camera spin, do its thing etc~ // also the -1 is to ensure that the thinker runs in the loop below. @@ -2413,16 +2397,12 @@ void G_Ticker(boolean run) switch (gamestate) { case GS_LEVEL: - - for (; ra_timeskip < starttime - TICRATE*4; ra_timeskip++) // this looks weird but this is done to not break compability with older demos for now. - { - if (demo.title) - F_TitleDemoTicker(); - P_Ticker(run); // tic the game - ST_Ticker(); - AM_Ticker(); - HU_Ticker(); - } + if (demo.title) + F_TitleDemoTicker(); + P_Ticker(run); // tic the game + ST_Ticker(); + AM_Ticker(); + HU_Ticker(); break; case GS_INTERMISSION: @@ -2535,7 +2515,7 @@ 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->starpostnum = 0; @@ -2570,6 +2550,10 @@ void G_PlayerReborn(INT32 player) // SRB2kart 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; INT32 pflags; @@ -2615,7 +2599,7 @@ void G_PlayerReborn(INT32 player) jointime = players[player].jointime; splitscreenindex = players[player].splitscreenindex; spectator = players[player].spectator; - pflags = (players[player].pflags & (PF_TIMEOVER|PF_FLIPCAM|PF_TAGIT|PF_TAGGED|PF_WANTSTOJOIN)); + pflags = (players[player].pflags & (PF_TIMEOVER|PF_TAGIT|PF_TAGGED|PF_WANTSTOJOIN)); // As long as we're not in multiplayer, carry over cheatcodes from map to map if (!(netgame || multiplayer)) @@ -2630,6 +2614,10 @@ void G_PlayerReborn(INT32 player) // SRB2kart kartspeed = players[player].kartspeed; kartweight = players[player].kartweight; + follower = players[player].follower; + followerready = players[player].followerready; + followercolor = players[player].followercolor; + followerskin = players[player].followerskin; // charflags = players[player].charflags; @@ -2684,6 +2672,9 @@ void G_PlayerReborn(INT32 player) 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]; @@ -2740,6 +2731,16 @@ void G_PlayerReborn(INT32 player) 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; p->pflags |= PF_ATTACKDOWN; @@ -3207,7 +3208,7 @@ void G_AddPlayer(INT32 playernum) p->jointime = 0; p->playerstate = PST_REBORN; - demo_extradata[playernum] |= DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN; // Set everything + demo_extradata[playernum] |= DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything } void G_ExitLevel(void) @@ -4762,13 +4763,12 @@ char *G_BuildMapTitle(INT32 mapnum) // For demos #define ZT_FWD 0x01 -#define ZT_SIDE 0x02 -#define ZT_ANGLE 0x04 -#define ZT_BUTTONS 0x08 -#define ZT_AIMING 0x10 -#define ZT_DRIFT 0x20 -#define ZT_LATENCY 0x40 -#define DEMOMARKER 0x80 // demoend +#define ZT_ANGLE 0x02 +#define ZT_BUTTONS 0x04 +#define ZT_AIMING 0x08 +#define ZT_DRIFT 0x10 +#define ZT_LATENCY 0x20 +#define DEMOMARKER 0x40 // demoend UINT8 demo_extradata[MAXPLAYERS]; UINT8 demo_writerng; // 0=no, 1=yes, 2=yes but on a timeout @@ -4831,7 +4831,6 @@ ticcmd_t *G_MoveTiccmd(ticcmd_t* dest, const ticcmd_t* src, const size_t n) for (i = 0; i < n; i++) { dest[i].forwardmove = src[i].forwardmove; - dest[i].sidemove = src[i].sidemove; dest[i].angleturn = SHORT(src[i].angleturn); dest[i].aiming = (INT16)SHORT(src[i].aiming); dest[i].buttons = (UINT16)SHORT(src[i].buttons); @@ -4936,6 +4935,25 @@ void G_ReadDemoExtraData(void) // Name M_Memcpy(player_names[p],demo_p,16); demo_p += 16; + } + if (extradata & DXD_FOLLOWER) + { + // Set our follower + M_Memcpy(name, demo_p, 16); + demo_p += 16; + SetPlayerFollower(p, name); + + // Follower's color + M_Memcpy(name, demo_p, 16); + demo_p += 16; + for (i = 0; i < MAXSKINCOLORS; i++) + if (!stricmp(KartColor_Names[i], name)) // SRB2kart + { + players[p].followercolor = i; + break; + } + + } if (extradata & DXD_PLAYSTATE) { @@ -5039,6 +5057,7 @@ void G_WriteDemoExtraData(void) WRITEUINT8(demo_p, skins[players[i].skin].kartspeed); WRITEUINT8(demo_p, skins[players[i].skin].kartweight); + } if (demo_extradata[i] & DXD_COLOR) { @@ -5056,6 +5075,21 @@ void G_WriteDemoExtraData(void) M_Memcpy(demo_p,name,16); demo_p += 16; } + if (demo_extradata[i] & DXD_FOLLOWER) + { + // write follower + memset(name, 0, 16); + strncpy(name, followers[players[i].followerskin].skinname, 16); + M_Memcpy(demo_p, name, 16); + demo_p += 16; + + // write follower color + memset(name, 0, 16); + strncpy(name, Followercolor_cons_t[players[i].followercolor].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" + M_Memcpy(demo_p,name,16); + demo_p += 16; + + } if (demo_extradata[i] & DXD_PLAYSTATE) { demo_writerng = 1; @@ -5105,8 +5139,6 @@ void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum) if (ziptic & ZT_FWD) oldcmd[playernum].forwardmove = READSINT8(demo_p); - if (ziptic & ZT_SIDE) - oldcmd[playernum].sidemove = READSINT8(demo_p); if (ziptic & ZT_ANGLE) oldcmd[playernum].angleturn = READINT16(demo_p); if (ziptic & ZT_BUTTONS) @@ -5120,14 +5152,7 @@ void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum) G_CopyTiccmd(cmd, &oldcmd[playernum], 1); - // SRB2kart: Copy-pasted from ticcmd building, removes that crappy demo cam - if (((players[displayplayers[0]].mo && players[displayplayers[0]].speed > 0) // Moving - || (leveltime > starttime && (cmd->buttons & BT_ACCELERATE && cmd->buttons & BT_BRAKE)) // Rubber-burn turn - || (players[displayplayers[0]].respawn.state != RESPAWNST_NONE) // Respawning - || (players[displayplayers[0]].spectator || objectplacing)) // Not a physical player - && !(players[displayplayers[0]].kartstuff[k_spinouttimer] - && players[displayplayers[0]].kartstuff[k_sneakertimer])) // Spinning and boosting cancels out spinout - localangle[0] += (cmd->angleturn<<16); + localangle[0] += (cmd->angleturn<<16); if (!(demoflags & DF_GHOST) && *demo_p == DEMOMARKER) { @@ -5153,13 +5178,6 @@ void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum) ziptic |= ZT_FWD; } - if (cmd->sidemove != oldcmd[playernum].sidemove) - { - WRITEUINT8(demo_p,cmd->sidemove); - oldcmd[playernum].sidemove = cmd->sidemove; - ziptic |= ZT_SIDE; - } - if (cmd->angleturn != oldcmd[playernum].angleturn) { WRITEINT16(demo_p,cmd->angleturn); @@ -5656,6 +5674,8 @@ void G_GhostTicker(void) g->p += 16; // Same tbh if (ziptic & DXD_NAME) g->p += 16; // yea + if (ziptic & DXD_FOLLOWER) + g->p += 32; // ok (32 because there's both the skin and the colour) if (ziptic & DXD_PLAYSTATE && READUINT8(g->p) != DXD_PST_PLAYING) I_Error("Ghost is not a record attack ghost"); //@TODO lmao don't blow up like this } @@ -5671,8 +5691,6 @@ void G_GhostTicker(void) if (ziptic & ZT_FWD) g->p++; - if (ziptic & ZT_SIDE) - g->p++; if (ziptic & ZT_ANGLE) g->p += 2; if (ziptic & ZT_BUTTONS) @@ -5961,7 +5979,7 @@ void G_PreviewRewind(tic_t previewtime) if (!info->playerinfo[i].ingame || !info->playerinfo[i].player.mo) { if (players[i].mo) - players[i].mo->flags2 |= MF2_DONTDRAW; + players[i].mo->drawflags |= MFD_DONTDRAW; continue; } @@ -5969,7 +5987,7 @@ void G_PreviewRewind(tic_t previewtime) if (!players[i].mo) continue; //@TODO spawn temp object to act as a player display - players[i].mo->flags2 &= ~MF2_DONTDRAW; + players[i].mo->drawflags &= ~MFD_DONTDRAW; P_UnsetThingPosition(players[i].mo); #define TWEEN(pr) info->playerinfo[i].mobj.pr + FixedMul((INT32) (next_info->playerinfo[i].mobj.pr - info->playerinfo[i].mobj.pr), tweenvalue) @@ -6326,7 +6344,9 @@ void G_BeginRecording(void) demoflags |= DF_ENCORE; #ifdef HAVE_BLUA - demoflags |= DF_LUAVARS; + if (!modeattacking) // Ghosts don't read luavars, and you shouldn't ever need to save Lua in replays, you doof! + // SERIOUSLY THOUGH WHY WOULD YOU LOAD HOSTMOD AND RECORD A GHOST WITH IT !???? + demoflags |= DF_LUAVARS; #endif // Setup header. @@ -6394,6 +6414,12 @@ void G_BeginRecording(void) CV_SaveNetVars(&demo_p, true); // Now store some info for each in-game player + + // Lat' 12/05/19: Do note that for the first game you load, everything that gets saved here is total garbage; + // The name will always be Player , the skin sonic, the color None and the follower 0. This is only correct on subsequent games. + // In the case of said first game, the skin and the likes are updated with Got_NameAndColor, which are then saved in extradata for the demo with DXD_SKIN in r_things.c for instance. + + for (p = 0; p < MAXPLAYERS; p++) { if (playeringame[p]) { player = &players[p]; @@ -6418,6 +6444,25 @@ void G_BeginRecording(void) M_Memcpy(demo_p,name,16); demo_p += 16; + // Save follower's skin name + // PS: We must check for 'follower' to determine if the followerskin is valid. It's going to be 0 if we don't have a follower, but 0 is also absolutely a valid follower! + // Doesn't really matter if the follower mobj is valid so long as it exists in a way or another. + + memset(name, 0, 16); + if (player->follower) + strncpy(name, followers[player->followerskin].skinname, 16); + else + strncpy(name, "None", 16); // Say we don't have one, then. + + M_Memcpy(demo_p,name,16); + demo_p += 16; + + // Save follower's colour + memset(name, 0, 16); + strncpy(name, Followercolor_cons_t[player->followercolor].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" + M_Memcpy(demo_p, name, 16); + demo_p += 16; + // Score, since Kart uses this to determine where you start on the map WRITEUINT32(demo_p, player->score); @@ -6434,8 +6479,9 @@ void G_BeginRecording(void) WRITEUINT8(demo_p, 0xFF); // Denote the end of the player listing #ifdef HAVE_BLUA - // player lua vars, always saved even if empty - LUA_ArchiveDemo(); + // player lua vars, always saved even if empty... Unless it's record attack. + if (!modeattacking) + LUA_ArchiveDemo(); #endif memset(&oldcmd,0,sizeof(oldcmd)); @@ -7017,7 +7063,7 @@ void G_DoPlayDemo(char *defdemoname) { UINT8 i, p; lumpnum_t l; - char skin[17],color[17],*n,*pdemoname; + char skin[17],color[17],follower[17],*n,*pdemoname; UINT8 version,subversion; UINT32 randseed; char msg[1024]; @@ -7031,6 +7077,7 @@ void G_DoPlayDemo(char *defdemoname) skin[16] = '\0'; color[16] = '\0'; + follower[16] = '\0'; // No demo name means we're restarting the current demo if (defdemoname == NULL) @@ -7345,6 +7392,23 @@ void G_DoPlayDemo(char *defdemoname) break; } + // Follower + M_Memcpy(follower, demo_p, 16); + demo_p += 16; + SetPlayerFollower(p, follower); + + // Follower colour + M_Memcpy(color, demo_p, 16); + demo_p += 16; + for (i = 0; i < MAXSKINCOLORS +2; i++) // +2 because of Match and Opposite + { + if (!stricmp(Followercolor_cons_t[i].strvalue, color)) + { + players[p].followercolor = i; + break; + } + } + // Score, since Kart uses this to determine where you start on the map players[p].score = READUINT32(demo_p); @@ -7370,6 +7434,7 @@ void G_DoPlayDemo(char *defdemoname) if (!gL) // No Lua state! ...I guess we'll just start one... LUA_ClearState(); + // No modeattacking check, DF_LUAVARS won't be present here. LUA_UnArchiveDemo(); } #endif @@ -7574,6 +7639,9 @@ void G_AddGhost(char *defdemoname) M_Memcpy(color, p, 16); p += 16; + // Follower data was here, skip it, we don't care about it for ghosts. + p += 32; // followerskin (16) + followercolor (16) + p += 4; // score p += 2; // powerlevel diff --git a/src/g_game.h b/src/g_game.h index 21d32b302..e0f25aa92 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -226,6 +226,7 @@ extern UINT8 demo_writerng; #define DXD_NAME 0x04 // name changed #define DXD_COLOR 0x08 // color changed #define DXD_PLAYSTATE 0x10 // state changed between playing, spectating, or not in-game +#define DXD_FOLLOWER 0x20 // follower was changed #define DXD_PST_PLAYING 0x01 #define DXD_PST_SPECTATING 0x02 diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c index ba069420e..c86b965a8 100644 --- a/src/hardware/hw_draw.c +++ b/src/hardware/hw_draw.c @@ -29,6 +29,7 @@ #include "../z_zone.h" #include "../v_video.h" #include "../st_stuff.h" +#include "../k_hud.h" #include #include "../i_video.h" // for rendermode != render_glide @@ -194,12 +195,6 @@ void HWR_DrawFixedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscale, cy -= offsety; } - if (option & V_SPLITSCREEN) - cy += FIXED_TO_FLOAT((BASEVIDHEIGHT/2)<= -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_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC); - const column_t *column = (const column_t *)((const UINT8 *)(realpatch) + LONG((realpatch)->columnofs[0])); - 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 (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f) - { - if ((option & (V_SPLITSCREEN|V_SNAPTOBOTTOM)) == (V_SPLITSCREEN|V_SNAPTOBOTTOM)) - cy += ((float)vid.height/2 - ((float)BASEVIDHEIGHT/2 * dupy)); - else if (option & V_SNAPTOBOTTOM) - cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)); - else if (!(option & V_SNAPTOTOP)) - cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy))/2; - } + INT32 intx, inty; + intx = (INT32)cx; + inty = (INT32)cy; + K_AdjustXYWithSnap(&intx, &inty, option, dupx, dupy); + cx = (float)intx; + cy = (float)inty; } } @@ -377,7 +349,7 @@ void HWR_DrawCroppedPatch(GLPatch_t *gpatch, fixed_t x, fixed_t y, fixed_t pscal } if (fabsf((float)vid.height - (float)BASEVIDHEIGHT * dupy) > 1.0E-36f) { - if ((option & (V_SPLITSCREEN|V_SNAPTOBOTTOM)) == (V_SPLITSCREEN|V_SNAPTOBOTTOM)) + if ((option & V_SNAPTOBOTTOM) == V_SNAPTOBOTTOM) cy += ((float)vid.height/2 - ((float)BASEVIDHEIGHT/2 * dupy)); else if (option & V_SNAPTOBOTTOM) cy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)); @@ -872,6 +844,7 @@ void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, UINT32 color, INT32 if (!(options & V_NOSCALESTART)) { float dupx = (float)vid.dupx, dupy = (float)vid.dupy; + INT32 intx, inty; if (x == 0 && y == 0 && w == BASEVIDWIDTH && h == BASEVIDHEIGHT) { @@ -890,26 +863,11 @@ void HWR_DrawConsoleFill(INT32 x, INT32 y, INT32 w, INT32 h, UINT32 color, INT32 fw *= dupx; fh *= dupy; - if (fabsf((float)vid.width - ((float)BASEVIDWIDTH * dupx)) > 1.0E-36f) - { - if (options & V_SNAPTORIGHT) - fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)); - else if (!(options & V_SNAPTOLEFT)) - fx += ((float)vid.width - ((float)BASEVIDWIDTH * dupx)) / 2; - } - if (fabsf((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) > 1.0E-36f) - { - // same thing here - if (options & V_SNAPTOBOTTOM) - fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)); - else if (!(options & V_SNAPTOTOP)) - fy += ((float)vid.height - ((float)BASEVIDHEIGHT * dupy)) / 2; - } - 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) @@ -980,6 +938,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) { @@ -998,26 +957,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 (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 (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 9c8098877..47f1b4e49 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -2829,17 +2829,18 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) UINT8 lightlevel = 0; extracolormap_t *colormap = NULL; UINT8 i; + SINT8 flip = P_MobjFlip(thing); INT32 light; fixed_t scalemul; UINT16 alpha; fixed_t floordiff; - fixed_t floorz; + fixed_t groundz; fixed_t slopez; - pslope_t *floorslope; + pslope_t *groundslope; - floorz = R_GetShadowZ(thing, &floorslope); - floordiff = abs(thingzpos - floorz); + groundz = R_GetShadowZ(thing, &groundslope); + floordiff = abs((flip < 0 ? thing->height : 0) + thingzpos - groundz); alpha = floordiff / (4*FRACUNIT) + 75; if (alpha >= 255) return; @@ -2860,7 +2861,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) HWR_GetPatch(gpatch); scalemul = FixedMul(FRACUNIT - floordiff/640, scale); - scalemul = FixedMul(scalemul, (thing->radius*2) / gpatch->height); + scalemul = FixedMul(scalemul, (thing->radius*2) / SHORT(gpatch->height)); fscale = FIXED_TO_FLOAT(scalemul); fx = FIXED_TO_FLOAT(thingxpos); @@ -2872,9 +2873,9 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) // 0--1 if (thing && fabsf(fscale - 1.0f) > 1.0E-36f) - offset = (gpatch->height/2) * fscale; + offset = (SHORT(gpatch->height)/2) * fscale; else - offset = (float)(gpatch->height/2); + offset = (float)(SHORT(gpatch->height)/2); shadowVerts[2].x = shadowVerts[3].x = fx + offset; shadowVerts[1].x = shadowVerts[0].x = fx - offset; @@ -2889,18 +2890,18 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) shadowVerts[i].z = fy + ((oldx - fx) * gr_viewsin) + ((oldy - fy) * gr_viewcos); } - if (floorslope) + if (groundslope) { for (i = 0; i < 4; i++) { - slopez = P_GetZAt(floorslope, FLOAT_TO_FIXED(shadowVerts[i].x), FLOAT_TO_FIXED(shadowVerts[i].z)); - shadowVerts[i].y = FIXED_TO_FLOAT(slopez) + 0.05f; + slopez = P_GetZAt(groundslope, FLOAT_TO_FIXED(shadowVerts[i].x), FLOAT_TO_FIXED(shadowVerts[i].z)); + shadowVerts[i].y = FIXED_TO_FLOAT(slopez) + flip * 0.05f; } } else { for (i = 0; i < 4; i++) - shadowVerts[i].y = FIXED_TO_FLOAT(floorz) + 0.05f; + shadowVerts[i].y = FIXED_TO_FLOAT(groundz) + flip * 0.05f; } shadowVerts[0].s = shadowVerts[3].s = 0; @@ -2911,7 +2912,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) if (thing->subsector->sector->numlights) { - light = R_GetPlaneLight(thing->subsector->sector, floorz, false); // Always use the light at the top instead of whatever I was doing before + light = R_GetPlaneLight(thing->subsector->sector, groundz, false); // Always use the light at the top instead of whatever I was doing before if (thing->subsector->sector->lightlist[light].extra_colormap) colormap = thing->subsector->sector->lightlist[light].extra_colormap; @@ -2973,6 +2974,7 @@ static void HWR_SplitSprite(gr_vissprite_t *spr) FUINT lightlevel; FBITFIELD blend = 0; UINT8 alpha; + UINT8 brightmode = 0; INT32 i; float realtop, realbot, top, bot; @@ -3071,16 +3073,8 @@ static void HWR_SplitSprite(gr_vissprite_t *spr) // co-ordinates memcpy(wallVerts, baseWallVerts, sizeof(baseWallVerts)); - if (!cv_translucency.value) // translucency disabled - { - Surf.PolyColor.s.alpha = 0xFF; - blend = PF_Translucent|PF_Occlude; - } - 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 @@ -3097,15 +3091,30 @@ static void HWR_SplitSprite(gr_vissprite_t *spr) temp = FLOAT_TO_FIXED(realtop); + 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; + } + #ifdef ESLOPE // Start with the lightlevel and colormap from the top of the sprite lightlevel = 255; colormap = list[sector->numlights - 1].extra_colormap; - if (!(spr->mobj->frame & FF_FULLBRIGHT)) + if (brightmode != 1) { lightlevel = *list[sector->numlights - 1].lightlevel; - if (spr->mobj->frame & FF_SEMIBRIGHT) + if (brightmode == 2) lightlevel = 128 + (lightlevel>>1); } @@ -3115,10 +3124,10 @@ static void HWR_SplitSprite(gr_vissprite_t *spr) : sector->lightlist[i].height; if (h <= temp) { - if (!(spr->mobj->frame & FF_FULLBRIGHT)) + if (brightmode != 1) { lightlevel = *list[i-1].lightlevel; - if (spr->mobj->frame & FF_SEMIBRIGHT) + if (brightmode == 2) lightlevel = 128 + (lightlevel>>1); } colormap = list[i-1].extra_colormap; @@ -3127,10 +3136,10 @@ static void HWR_SplitSprite(gr_vissprite_t *spr) } #else i = R_GetPlaneLight(sector, temp, false); - if (!(spr->mobj->frame & FF_FULLBRIGHT)) + if (brightmode != 1) { lightlevel = *list[i].lightlevel; - if (spr->mobj->frame & FF_SEMIBRIGHT) + if (brightmode == 2) lightlevel = 128 + (lightlevel>>1); } colormap = list[i].extra_colormap; @@ -3147,10 +3156,10 @@ static void HWR_SplitSprite(gr_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)) + if (brightmode != 1) { lightlevel = *list[i].lightlevel; - if (spr->mobj->frame & FF_SEMIBRIGHT) + if (brightmode == 2) lightlevel = 128 + (lightlevel>>1); } colormap = list[i].extra_colormap; @@ -3427,12 +3436,28 @@ static void HWR_DrawSprite(gr_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)) + 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 (brightmode != 1) { lightlevel = sector->lightlevel; - if (spr->mobj->frame & FF_SEMIBRIGHT) + if (brightmode == 2) lightlevel = 128 + (lightlevel>>1); } @@ -3441,16 +3466,9 @@ static void HWR_DrawSprite(gr_vissprite_t *spr) { FBITFIELD blend = 0; - if (!cv_translucency.value) // translucency disabled - { - Surf.PolyColor.s.alpha = 0xFF; - blend = PF_Translucent|PF_Occlude; - } - 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 @@ -3519,15 +3537,31 @@ static inline void HWR_DrawPrecipitationSprite(gr_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)) + if (brightmode != 1) lightlevel = *sector->lightlist[light].lightlevel; if (sector->lightlist[light].extra_colormap) @@ -3535,24 +3569,21 @@ static inline void HWR_DrawPrecipitationSprite(gr_vissprite_t *spr) } else { - if (!(spr->mobj->frame & FF_FULLBRIGHT)) + if (brightmode != 1) lightlevel = sector->lightlevel; if (sector->extra_colormap) colormap = sector->extra_colormap; } - if (spr->mobj->frame & FF_SEMIBRIGHT) + 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 @@ -3587,8 +3618,14 @@ static int CompareVisSprites(const void *p1, const void *p2) // make transparent sprites last // "boolean to int" - int transparency1 = (spr1->mobj->flags2 & MF2_SHADOW) || (spr1->mobj->frame & FF_TRANSMASK); - int transparency2 = (spr2->mobj->flags2 & MF2_SHADOW) || (spr2->mobj->frame & FF_TRANSMASK); + int transparency1 = (spr1->mobj->drawflags & FF_TRANSMASK) ? + ((spr1->mobj->drawflags & FF_TRANSMASK)>>MFD_TRANSSHIFT) : + ((spr1->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT); + + int transparency2 = (spr2->mobj->drawflags & FF_TRANSMASK) ? + ((spr2->mobj->drawflags & FF_TRANSMASK)>>MFD_TRANSSHIFT) : + ((spr2->mobj->frame & FF_TRANSMASK)>>FF_TRANSSHIFT); + idiff = transparency1 - transparency2; if (idiff != 0) return idiff; @@ -3962,9 +3999,6 @@ void HWR_AddSprites(sector_t *sec) precipmobj_t *precipthing; fixed_t approx_dist, limit_dist; - INT32 splitflags; - boolean split_drawsprite; // drawing with splitscreen flags - // BSP is traversed by subsector. // A sector might have been split into several // subsectors during BSP building. @@ -3981,36 +4015,13 @@ void HWR_AddSprites(sector_t *sec) { for (thing = sec->thinglist; thing; thing = thing->snext) { - - split_drawsprite = false; - - if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW) + if (thing->sprite == SPR_NULL) continue; - splitflags = thing->eflags & (MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4); - - if (r_splitscreen && splitflags) - { - 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; - } - else - split_drawsprite = true; - - if (!split_drawsprite) + 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))) continue; approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y); @@ -4026,36 +4037,13 @@ void HWR_AddSprites(sector_t *sec) // Draw everything in sector, no checks for (thing = sec->thinglist; thing; thing = thing->snext) { - - split_drawsprite = false; - - if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW) + if (thing->sprite == SPR_NULL) continue; - splitflags = thing->eflags & (MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4); - - if (r_splitscreen && splitflags) - { - 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; - } - else - split_drawsprite = true; - - if (!split_drawsprite) + 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))) continue; HWR_ProjectSprite(thing); diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 27ad45d9e..cb4fab7b3 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -1083,6 +1083,22 @@ void HWR_DrawMD2(gr_vissprite_t *spr) sector_t *sector = spr->mobj->subsector->sector; extracolormap_t *colormap = sector->extra_colormap; UINT8 lightlevel = 255; + 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) { @@ -1090,7 +1106,7 @@ void HWR_DrawMD2(gr_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)) + if (brightmode != 1) lightlevel = *sector->lightlist[light].lightlevel; if (sector->lightlist[light].extra_colormap) @@ -1098,13 +1114,16 @@ void HWR_DrawMD2(gr_vissprite_t *spr) } else { - if (!(spr->mobj->frame & FF_FULLBRIGHT)) + 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 @@ -1131,8 +1150,8 @@ void HWR_DrawMD2(gr_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/http-mserv.c b/src/http-mserv.c new file mode 100644 index 000000000..9d05b80e5 --- /dev/null +++ b/src/http-mserv.c @@ -0,0 +1,675 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2020 by James R. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +// \brief HTTP based master server + +/* +Documentation available here. + + +*/ + +#include + +#include "doomdef.h" +#include "d_clisrv.h" +#include "command.h" +#include "m_argv.h" +#include "m_menu.h" +#include "mserv.h" +#include "i_tcp.h"/* for current_port */ +#include "i_threads.h" + +/* reasonable default I guess?? */ +#define DEFAULT_BUFFER_SIZE (4096) + +/* I just stop myself from making macros anymore. */ +#define Blame( ... ) \ + CONS_Printf("\x85" __VA_ARGS__) + +static void MasterServer_Debug_OnChange (void); + +consvar_t cv_masterserver_timeout = { + "masterserver_timeout", "5", CV_SAVE, CV_Unsigned, + NULL, 0, NULL, NULL, 0, 0, NULL/* C90 moment */ +}; + +consvar_t cv_masterserver_debug = { + "masterserver_debug", "Off", CV_SAVE|CV_CALL, CV_OnOff, + MasterServer_Debug_OnChange, 0, NULL, NULL, 0, 0, NULL/* C90 moment */ +}; + +consvar_t cv_masterserver_token = { + "masterserver_token", "", CV_SAVE, NULL, + NULL, 0, NULL, NULL, 0, 0, NULL/* C90 moment */ +}; + +static int hms_started; + +static char *hms_api; +#ifdef HAVE_THREADS +static I_mutex hms_api_mutex; +#endif + +static char *hms_server_token; + +struct HMS_buffer +{ + CURL *curl; + char *buffer; + int needle; + int end; +}; + +static void +Contact_error (void) +{ + CONS_Alert(CONS_ERROR, + "There was a problem contacting the master server...\n" + ); +} + +static size_t +HMS_on_read (char *s, size_t _1, size_t n, void *userdata) +{ + struct HMS_buffer *buffer; + size_t blocks; + + (void)_1; + + buffer = userdata; + + if (n >= (size_t)( buffer->end - buffer->needle )) + { + /* resize to next multiple of buffer size */ + blocks = ( n / DEFAULT_BUFFER_SIZE + 1 ); + buffer->end += ( blocks * DEFAULT_BUFFER_SIZE ); + + buffer->buffer = realloc(buffer->buffer, buffer->end); + } + + memcpy(&buffer->buffer[buffer->needle], s, n); + buffer->needle += n; + + return n; +} + +static struct HMS_buffer * +HMS_connect (const char *format, ...) +{ + va_list ap; + CURL *curl; + char *url; + char *quack_token; + size_t seek; + size_t token_length; + struct HMS_buffer *buffer; + + if (! hms_started) + { + if (curl_global_init(CURL_GLOBAL_ALL) != 0) + { + Contact_error(); + Blame("From curl_global_init.\n"); + return NULL; + } + else + { + atexit(curl_global_cleanup); + hms_started = 1; + } + } + + curl = curl_easy_init(); + + if (! curl) + { + Contact_error(); + Blame("From curl_easy_init.\n"); + return NULL; + } + + if (cv_masterserver_token.string[0]) + { + quack_token = curl_easy_escape(curl, cv_masterserver_token.string, 0); + token_length = ( sizeof "?token="-1 )+ strlen(quack_token); + } + else + { + quack_token = NULL; + token_length = 0; + } + +#ifdef HAVE_THREADS + I_lock_mutex(&hms_api_mutex); +#endif + + seek = strlen(hms_api) + 1;/* + '/' */ + + va_start (ap, format); + url = malloc(seek + vsnprintf(0, 0, format, ap) + token_length + 1); + va_end (ap); + + sprintf(url, "%s/", hms_api); + +#ifdef HAVE_THREADS + I_unlock_mutex(hms_api_mutex); +#endif + + va_start (ap, format); + seek += vsprintf(&url[seek], format, ap); + va_end (ap); + + if (quack_token) + sprintf(&url[seek], "?token=%s", quack_token); + + CONS_Printf("HMS: connecting '%s'...\n", url); + + buffer = malloc(sizeof *buffer); + buffer->curl = curl; + buffer->end = DEFAULT_BUFFER_SIZE; + buffer->buffer = malloc(buffer->end); + buffer->needle = 0; + + if (cv_masterserver_debug.value) + { + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_STDERR, logstream); + } + + if (M_CheckParm("-bindaddr") && M_IsNextParm()) + { + curl_easy_setopt(curl, CURLOPT_INTERFACE, M_GetNextParm()); + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + + curl_easy_setopt(curl, CURLOPT_TIMEOUT, cv_masterserver_timeout.value); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HMS_on_read); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, buffer); + + curl_free(quack_token); + free(url); + + return buffer; +} + +static int +HMS_do (struct HMS_buffer *buffer) +{ + CURLcode cc; + long status; + + char *p; + + cc = curl_easy_perform(buffer->curl); + + if (cc != CURLE_OK) + { + Contact_error(); + Blame( + "From curl_easy_perform: %s\n", + curl_easy_strerror(cc) + ); + return 0; + } + + buffer->buffer[buffer->needle] = '\0'; + + curl_easy_getinfo(buffer->curl, CURLINFO_RESPONSE_CODE, &status); + + if (status != 200) + { + p = strchr(buffer->buffer, '\n'); + + if (p) + *p = '\0'; + + Contact_error(); + Blame( + "Master server error %ld: %s%s\n", + status, + buffer->buffer, + ( (p) ? "" : " (malformed)" ) + ); + + return 0; + } + else + return 1; +} + +static void +HMS_end (struct HMS_buffer *buffer) +{ + curl_easy_cleanup(buffer->curl); + free(buffer->buffer); + free(buffer); +} + +int +HMS_fetch_rooms (int joining, int query_id) +{ + struct HMS_buffer *hms; + int ok; + + int doing_shit; + + char *id; + char *title; + char *room_motd; + + int id_no; + + char *p; + char *end; + + int i; + + (void)query_id; + + hms = HMS_connect("rooms"); + + if (! hms) + return 0; + + if (HMS_do(hms)) + { + doing_shit = 1; + + p = hms->buffer; + + for (i = 0; i < NUM_LIST_ROOMS && ( end = strstr(p, "\n\n\n") );) + { + *end = '\0'; + + id = strtok(p, "\n"); + title = strtok(0, "\n"); + room_motd = strtok(0, ""); + + if (id && title && room_motd) + { + id_no = atoi(id); + + /* + Don't show the 'All' room if hosting. And it's a hack like this + because I'm way too lazy to add another feature to the MS. + */ + if (joining || id_no != 0) + { +#ifdef HAVE_THREADS + I_lock_mutex(&ms_QueryId_mutex); + { + if (query_id != ms_QueryId) + doing_shit = 0; + } + I_unlock_mutex(ms_QueryId_mutex); + + if (! doing_shit) + break; +#endif + + room_list[i].header.buffer[0] = 1; + + room_list[i].id = id_no; + strlcpy(room_list[i].name, title, sizeof room_list[i].name); + strlcpy(room_list[i].motd, room_motd, sizeof room_list[i].motd); + + i++; + } + + p = ( end + 3 );/* skip the three linefeeds */ + } + else + break; + } + + if (doing_shit) + room_list[i].header.buffer[0] = 0; + + ok = 1; + + if (doing_shit) + { +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); +#endif + { + 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; + } + } + } +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); +#endif + } + } + else + ok = 0; + + HMS_end(hms); + + return ok; +} + +int +HMS_register (void) +{ + struct HMS_buffer *hms; + int ok; + + char post[256]; + + char *title; + + hms = HMS_connect("rooms/%d/register", ms_RoomId); + + if (! hms) + return 0; + + title = curl_easy_escape(hms->curl, cv_servername.string, 0); + + snprintf(post, sizeof post, + "port=%d&" + "title=%s&" + "version=%d.%d", + + current_port, + + title, + + VERSION, + SUBVERSION + ); + + curl_free(title); + + curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post); + + ok = HMS_do(hms); + + if (ok) + { + hms_server_token = strdup(strtok(hms->buffer, "\n")); + } + + HMS_end(hms); + + return ok; +} + +int +HMS_unlist (void) +{ + struct HMS_buffer *hms; + int ok; + + hms = HMS_connect("servers/%s/unlist", hms_server_token); + + if (! hms) + return 0; + + curl_easy_setopt(hms->curl, CURLOPT_CUSTOMREQUEST, "POST"); + + ok = HMS_do(hms); + HMS_end(hms); + + free(hms_server_token); + + return ok; +} + +int +HMS_update (void) +{ + struct HMS_buffer *hms; + int ok; + + char post[256]; + + char *title; + + hms = HMS_connect("servers/%s/update", hms_server_token); + + if (! hms) + return 0; + + title = curl_easy_escape(hms->curl, cv_servername.string, 0); + + snprintf(post, sizeof post, + "title=%s", + title + ); + + curl_free(title); + + curl_easy_setopt(hms->curl, CURLOPT_POSTFIELDS, post); + + ok = HMS_do(hms); + HMS_end(hms); + + return ok; +} + +void +HMS_list_servers (void) +{ + struct HMS_buffer *hms; + + char *p; + + hms = HMS_connect("servers"); + + if (! hms) + return; + + if (HMS_do(hms)) + { + p = &hms->buffer[strlen(hms->buffer)]; + while (*--p == '\n') + ; + + CONS_Printf("%s\n", hms->buffer); + } + + HMS_end(hms); +} + +msg_server_t * +HMS_fetch_servers (msg_server_t *list, int room_number, int query_id) +{ + struct HMS_buffer *hms; + + int doing_shit; + + char local_version[9]; + + char *room; + + char *address; + char *port; + char *title; + char *version; + + char *end; + char *section_end; + char *p; + + int i; + + (void)query_id; + + if (room_number > 0) + { + hms = HMS_connect("rooms/%d/servers", room_number); + } + else + hms = HMS_connect("servers"); + + if (! hms) + return NULL; + + if (HMS_do(hms)) + { + doing_shit = 1; + + snprintf(local_version, sizeof local_version, + "%d.%d", + VERSION, + SUBVERSION + ); + + p = hms->buffer; + i = 0; + + do + { + section_end = strstr(p, "\n\n"); + + room = strtok(p, "\n"); + + p = strtok(0, ""); + + if (! p) + break; + + while (i < MAXSERVERLIST && ( end = strchr(p, '\n') )) + { + *end = '\0'; + + address = strtok(p, " "); + port = strtok(0, " "); + title = strtok(0, " "); + version = strtok(0, ""); + + if (address && port && title && version) + { +#ifdef HAVE_THREADS + I_lock_mutex(&ms_QueryId_mutex); + { + if (query_id != ms_QueryId) + doing_shit = 0; + } + I_unlock_mutex(ms_QueryId_mutex); + + if (! doing_shit) + break; +#endif + + if (strcmp(version, local_version) == 0) + { + strlcpy(list[i].ip, address, sizeof list[i].ip); + strlcpy(list[i].port, port, sizeof list[i].port); + strlcpy(list[i].name, title, sizeof list[i].name); + strlcpy(list[i].version, version, sizeof list[i].version); + + list[i].room = atoi(room); + + list[i].header.buffer[0] = 1; + + i++; + } + + if (end == section_end)/* end of list for this room */ + break; + else + p = ( end + 1 );/* skip server delimiter */ + } + else + { + section_end = 0;/* malformed so quit the parsing */ + break; + } + } + + if (! doing_shit) + break; + + p = ( section_end + 2 ); + } + while (section_end) ; + + if (doing_shit) + list[i].header.buffer[0] = 0; + } + else + list = NULL; + + HMS_end(hms); + + return list; +} + +int +HMS_compare_mod_version (char *buffer, size_t buffer_size) +{ + struct HMS_buffer *hms; + int ok; + + char *version; + char *version_name; + + hms = HMS_connect("versions/%d", MODID); + + if (! hms) + return 0; + + ok = 0; + + if (HMS_do(hms)) + { + version = strtok(hms->buffer, " "); + version_name = strtok(0, "\n"); + + if (version && version_name) + { + if (atoi(version) != MODVERSION) + { + strlcpy(buffer, version_name, buffer_size); + ok = 1; + } + else + ok = -1; + } + } + + HMS_end(hms); + + return ok; +} + +void +HMS_set_api (char *api) +{ +#ifdef HAVE_THREADS + I_lock_mutex(&hms_api_mutex); +#endif + { + free(hms_api); + hms_api = api; + } +#ifdef HAVE_THREADS + I_unlock_mutex(hms_api_mutex); +#endif +} + +static void +MasterServer_Debug_OnChange (void) +{ + /* TODO: change to 'latest-log.txt' for log files revision. */ + if (cv_masterserver_debug.value) + CONS_Printf("Master server debug messages will appear in log.txt\n"); +} diff --git a/src/hu_stuff.c b/src/hu_stuff.c index cf217ed1a..19a049b12 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2366,11 +2366,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/i_threads.h b/src/i_threads.h new file mode 100644 index 000000000..45a3dcc3e --- /dev/null +++ b/src/i_threads.h @@ -0,0 +1,39 @@ +// SONIC ROBO BLAST 2 KART +//----------------------------------------------------------------------------- +// Copyright (C) 2020 by James R. +// +// 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 i_threads.h +/// \brief Multithreading abstraction + +#ifdef HAVE_THREADS + +#ifndef I_THREADS_H +#define I_THREADS_H + +typedef void (*I_thread_fn)(void *userdata); + +typedef void * I_mutex; +typedef void * I_cond; + +void I_start_threads (void); +void I_stop_threads (void); + +void I_spawn_thread (const char *name, I_thread_fn, void *userdata); + +/* check in your thread whether to return early */ +int I_thread_is_stopped (void); + +void I_lock_mutex (I_mutex *); +void I_unlock_mutex (I_mutex); + +void I_hold_cond (I_cond *, I_mutex); + +void I_wake_one_cond (I_cond *); +void I_wake_all_cond (I_cond *); + +#endif/*I_THREADS_H*/ +#endif/*HAVE_THREADS*/ diff --git a/src/info.c b/src/info.c index 795885f92..e67a7be69 100644 --- a/src/info.c +++ b/src/info.c @@ -53,6 +53,7 @@ char sprnames[NUMSPRITES + 1][5] = "SRBB","SRBC","SRBD","SRBE","SRBF","SRBG","SRBH","SRBI","SRBJ","SRBK", "SRBL","SRBM","SRBN","SRBO", //SRB2kart Sprites + "RNDM","RPOP","SGNS","FAST","DSHR","BOST","BOSM","KFRE","KINV","KINF", "WIPD","DRIF","BDRF","DUST","DRWS","RSHE","FITM","BANA","ORBN","JAWZ","SSMN", "KRBM","BHOG","BHBM","SPBM","THNS","BUBS","BWVE", @@ -69,8 +70,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","EGOO","WTRL","XMS4", - "XMS5","VIEW" + "XMS5","FBUB","GCHA","CHEZ","VIEW","DBCL","DBNC","DBST", }; // Doesn't work with g++, needs actionf_p1 (don't modify this comment) @@ -2558,10 +2560,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 @@ -3461,6 +3495,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 @@ -15313,6 +15378,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 @@ -20553,6 +20672,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_RINGSPARKS -1, // doomednum S_RINGSPARKS1, // spawnstate @@ -20796,6 +20916,90 @@ 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 933f6d37e..1cfef7fb9 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -474,6 +474,7 @@ fixed_t K_BotTopSpeedRubberband(player_t *player) fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict) { fixed_t rubberband = K_BotRubberband(player) - FRACUNIT; + fixed_t newfrict; if (rubberband <= 0) { @@ -481,8 +482,14 @@ fixed_t K_BotFrictionRubberband(player_t *player, fixed_t frict) return frict; } - // 128 is a magic number that felt good in-game - return FixedDiv(frict, FRACUNIT + (rubberband / 2)); + newfrict = FixedDiv(frict, FRACUNIT + (rubberband / 2)); + + if (newfrict < 0) + newfrict = 0; + if (newfrict > FRACUNIT) + newfrict = FRACUNIT; + + return newfrict; } /*-------------------------------------------------- @@ -698,13 +705,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; @@ -741,7 +751,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 @@ -775,7 +785,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_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 6809129f9..589e986b0 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -27,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) @@ -40,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; @@ -144,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); @@ -243,6 +262,7 @@ consvar_t *KartItemCVars[NUMKARTRESULTS-1] = &cv_pogospring, &cv_superring, &cv_kitchensink, + &cv_dualsneaker, &cv_triplesneaker, &cv_triplebanana, &cv_decabanana, @@ -259,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 @@ -306,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 @@ -348,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; @@ -393,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, boolean rival) +INT32 K_KartGetItemOdds(UINT8 pos, SINT8 item, fixed_t mashed, boolean spbrush, boolean bot, boolean rival) { INT32 newodds; INT32 i; @@ -507,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: @@ -589,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; @@ -1117,10 +1144,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); @@ -1196,6 +1226,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]) { @@ -1221,6 +1252,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]) { @@ -1330,15 +1362,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; @@ -1409,6 +1445,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); @@ -1566,13 +1606,21 @@ 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); } +// 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; @@ -1588,7 +1636,7 @@ 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; @@ -1629,7 +1677,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; +} + +static fixed_t K_RandomFlip(fixed_t f) +{ + return ( ( leveltime & 1 ) ? f : -f ); +} + +void K_SpawnDriftBoostClip(player_t *player) +{ + mobj_t *clip; + fixed_t scale = 115*FRACUNIT/100; + fixed_t z; + + if (( player->mo->eflags & MFE_VERTICALFLIP )) + z = player->mo->z; + else + z = player->mo->z + player->mo->height; + + clip = P_SpawnMobj(player->mo->x, player->mo->y, z, MT_DRIFTCLIP); + + P_SetTarget(&clip->target, player->mo); + P_SetScale(clip, ( clip->destscale = FixedMul(scale, player->mo->scale) )); + K_MatchGenericExtraFlags(clip, player->mo); + + clip->fuse = 105; + clip->momz = 7 * P_MobjFlip(clip) * clip->scale; + + if (player->mo->momz > 0) + clip->momz += player->mo->momz; + + P_InstaThrust(clip, player->mo->angle + + K_RandomFlip(P_RandomRange(FRACUNIT/2, FRACUNIT)), + FixedMul(scale, player->speed)); +} + +void K_SpawnDriftBoostClipSpark(mobj_t *clip) +{ + mobj_t *spark; + + spark = P_SpawnMobj(clip->x, clip->y, clip->z, MT_DRIFTCLIPSPARK); + + P_SetTarget(&spark->target, clip); + P_SetScale(spark, ( spark->destscale = clip->scale )); + K_MatchGenericExtraFlags(spark, clip); + + spark->momx = clip->momx/2; + spark->momy = clip->momx/2; } /** \brief Handles the state changing for moving players, moved here to eliminate duplicate code @@ -1899,6 +1994,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 @@ -1946,11 +2048,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 @@ -1966,49 +2095,85 @@ 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); \ } +#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 { UINT8 i; for (i = 0; i < player->kartstuff[k_numsneakers]; i++) { - ADDBOOST(FRACUNIT/2, 8*FRACUNIT); // + 50% top speed, + 800% acceleration + 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(3*FRACUNIT/20, FRACUNIT); // + 15% top speed, + 100% acceleration + ADDBOOST(3*FRACUNIT/20, FRACUNIT, 0); // + 15% top speed, + 100% acceleration, +0% handling } if (player->kartstuff[k_draftpower] > 0) // Drafting @@ -2031,6 +2196,8 @@ static void K_GetKartBoostPower(player_t *player) } player->kartstuff[k_accelboost] = accelboost; + player->kartstuff[k_handleboost] = handleboost; + player->kartstuff[k_numboosts] = numboosts; } @@ -2098,7 +2265,7 @@ 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)) { // Rubberbanding acceleration is waekened since it makes hits feel more meaningful @@ -2116,21 +2283,45 @@ UINT16 K_GetKartFlashing(player_t *player) if (!player) return tics; + tics += (tics/8) * (player->kartspeed); + if (G_BattleGametype()) tics *= 2; - tics += (flashingtics/8) * (player->kartspeed); - return tics; } -fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove) +SINT8 K_GetForwardMove(player_t *player) +{ + 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 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 @@ -2147,26 +2338,20 @@ fixed_t K_3dKartMovement(player_t *player, boolean onground, fixed_t forwardmove newspeed = FixedDiv(FixedDiv(FixedMul(oldspeed, accelmax - p_accel) + FixedMul(p_speed, p_accel), accelmax), orig); 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 (player->kartstuff[k_sneakertimer]) - 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; } @@ -2874,13 +3059,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; @@ -3075,6 +3257,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 { @@ -3165,6 +3348,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); @@ -3316,7 +3502,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) @@ -3425,7 +3611,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))); @@ -3619,8 +3805,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; @@ -3650,8 +3836,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 @@ -3746,7 +3932,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); @@ -3754,7 +3941,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; @@ -3992,7 +4191,7 @@ void K_DoSneaker(player_t *player, INT32 type) { player->pflags |= PF_ATTACKDOWN; K_PlayBoostTaunt(player->mo); - + } player->kartstuff[k_sneakertimer] = sneakertime; @@ -4253,6 +4452,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; @@ -4359,12 +4559,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->drawflags &= ~MFD_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) { @@ -4704,6 +4962,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; @@ -4784,9 +5045,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)); @@ -4997,10 +5258,12 @@ static void K_UpdateEngineSounds(player_t *player, ticcmd_t *cmd) #endif return; - if ((leveltime >= starttime-(2*TICRATE) && leveltime <= starttime) || (player->respawn.state == RESPAWNST_DROP)) // 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; @@ -5093,9 +5356,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]) @@ -5110,6 +5370,11 @@ void K_KartPlayerHUDUpdate(player_t *player) if (player->karthud[khud_tauntvoices]) player->karthud[khud_tauntvoices]--; + 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! @@ -5335,7 +5600,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)) @@ -5362,16 +5627,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)) @@ -5575,6 +5844,16 @@ 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]--; @@ -5631,7 +5910,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]) @@ -6216,7 +6495,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); @@ -6224,32 +6503,52 @@ 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; + } + if (player->trickpanel != 0) { 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 @@ -6258,32 +6557,23 @@ INT16 K_GetKartTurnValue(player_t *player, INT16 turnvalue) countersteer = FRACUNIT; } - turnvalue = K_GetKartDriftValue(player, countersteer); - - return turnvalue; + return K_GetKartDriftValue(player, countersteer); } - if (player->kartstuff[k_sneakertimer] || 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) @@ -6294,6 +6584,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); @@ -6310,10 +6639,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); @@ -6326,9 +6651,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) { @@ -6339,8 +6661,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) { @@ -6351,8 +6672,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) { @@ -6363,8 +6683,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); } } @@ -6502,7 +6821,7 @@ static void K_KartDrift(player_t *player, boolean onground) player->kartstuff[k_driftend] = 0; } - if ((!player->kartstuff[k_sneakertimer]) + if ((player->kartstuff[k_handleboost] == 0) || (!player->cmd.driftturn) || (!player->kartstuff[k_aizdriftstrat]) || (player->cmd.driftturn > 0) != (player->kartstuff[k_aizdriftstrat] > 0)) @@ -6601,12 +6920,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; @@ -6666,10 +6985,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 // @@ -6718,7 +7122,7 @@ 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 + 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. @@ -6920,6 +7324,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; @@ -7319,61 +7724,35 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (G_RaceGametype()) 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 (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) // dead in match? you da bomb { K_DropItems(player); //K_StripItems(player); K_StripOther(player); - player->mo->flags2 |= MF2_SHADOW; + player->mo->drawflags |= MFD_SHADOW; player->powers[pw_flashing] = player->kartstuff[k_comebacktimer]; } else if (G_RaceGametype() || player->kartstuff[k_bumper] > 0) { - player->mo->flags2 &= ~MF2_SHADOW; + player->mo->drawflags &= ~(MFD_TRANSMASK|MFD_BRIGHTMASK); } if (player->trickpanel == 1 && player->trickdelay <= 0) @@ -7431,6 +7810,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } } + 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; @@ -7439,16 +7820,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; - } - - // change friction while braking no matter what, otherwise it's not any more effective than just letting go off accel - if (player->speed > 0 && cmd->forwardmove < 0) - 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 (G_BattleGametype() && player->kartstuff[k_bumper] <= 0) @@ -7466,14 +7843,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) @@ -7493,16 +7871,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } } - 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. @@ -7525,76 +7894,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) @@ -7692,3617 +7991,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_rival[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); - - // 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); - } - - // 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); - } - - // 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_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); - 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 = 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; - -#ifdef HAVE_BLUA - if (!LUA_HudEnabled(hud_minirankings)) - return false; // Don't proceed but still return true for free play above if HUD is disabled. -#endif - - for (j = 0; j < numplayersingame; j++) - { - UINT8 lowestposition = MAXPLAYERS+1; - for (i = 0; i < MAXPLAYERS; i++) - { - if (completed[i] || !playeringame[i] || players[i].spectator || !players[i].mo) - continue; - - if (players[i].kartstuff[k_position] >= lowestposition) - continue; - - rankplayer[ranklines] = i; - lowestposition = players[i].kartstuff[k_position]; - } - - i = rankplayer[ranklines]; - - completed[i] = true; - - if (players+i == stplyr) - strank = ranklines; - - //if (ranklines == 5) - //break; // Only draw the top 5 players -- we do this a different way now... - - ranklines++; - } - - if (ranklines < 5) - Y -= (9*ranklines); - else - Y -= (9*5); - - if (G_BattleGametype() || strank <= 2) // too close to the top, or playing battle, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2) - { - i = 0; - if (ranklines > 5) // could be both... - ranklines = 5; - } - else if (strank+3 > ranklines) // too close to the bottom? - { - i = ranklines - 5; - if (i < 0) - i = 0; - } - else - { - i = strank-2; - ranklines = strank+3; - } - - for (; i < ranklines; i++) - { - if (!playeringame[rankplayer[i]]) continue; - if (players[rankplayer[i]].spectator) continue; - if (!players[rankplayer[i]].mo) continue; - - bumperx = FACE_X+19; - - if (players[rankplayer[i]].mo->color) - { - colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE); - if (players[rankplayer[i]].mo->colorized) - colormap = R_GetTranslationColormap(TC_RAINBOW, players[rankplayer[i]].mo->color, GTC_CACHE); - else - colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE); - - V_DrawMappedPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, facerankprefix[players[rankplayer[i]].skin], colormap); - -#ifdef HAVE_BLUA - if (LUA_HudEnabled(hud_battlebumpers)) - { -#endif - if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] > 0) - { - V_DrawMappedPatch(bumperx-2, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[0], colormap); - for (j = 1; j < players[rankplayer[i]].kartstuff[k_bumper]; j++) - { - bumperx += 5; - V_DrawMappedPatch(bumperx, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_tinybumper[1], colormap); - } - } -#ifdef HAVE_BLUA - } // A new level of stupidity: checking if lua is enabled to close a bracket. :Fascinating: -#endif - } - - if (i == strank) - V_DrawScaledPatch(FACE_X, Y, V_HUDTRANS|V_SNAPTOLEFT, kp_facehighlight[(leveltime / 4) % 8]); - - if (G_BattleGametype() && players[rankplayer[i]].kartstuff[k_bumper] <= 0) - V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SNAPTOLEFT, kp_ranknobumpers); - else - { - INT32 pos = players[rankplayer[i]].kartstuff[k_position]; - if (pos < 0 || pos > MAXPLAYERS) - pos = 0; - // Draws the little number over the face - V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SNAPTOLEFT, kp_facenum[pos]); - } - - Y += 18; - } - - return false; -} - -// -// HU_DrawTabRankings -- moved here to take advantage of kart stuff! -// -void HU_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol) -{ - static tic_t alagles_timer = 9; - INT32 i, rightoffset = 240; - const UINT8 *colormap; - INT32 dupadjust = (vid.width/vid.dupx), duptweak = (dupadjust - BASEVIDWIDTH)/2; - int y2; - - //this function is designed for 9 or less score lines only - //I_Assert(scorelines <= 9); -- not today bitch, kart fixed it up - - V_DrawFill(1-duptweak, 26, dupadjust-2, 1, 0); // Draw a horizontal line because it looks nice! - if (scorelines > 8) - { - V_DrawFill(160, 26, 1, 147, 0); // Draw a vertical line to separate the two sides. - V_DrawFill(1-duptweak, 173, dupadjust-2, 1, 0); // And a horizontal line near the bottom. - rightoffset = (BASEVIDWIDTH/2) - 4 - x; - } - - for (i = 0; i < scorelines; i++) - { - char strtime[MAXPLAYERNAME+1]; - - if (players[tab[i].num].spectator || !players[tab[i].num].mo) - continue; //ignore them. - - if (netgame) // don't draw 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 = 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, fontv[PINGNUM_FONT].font[ln[0]]); - V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|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|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); - V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[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 (!uselives) - { - 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, fontv[PINGNUM_FONT].font[rn[0]], ringmap); - V_DrawMappedPatch(fr+15, fy-10, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[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 (uselives) - { - 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, fontv[PINGNUM_FONT].font[(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 (!uselives) - 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 (uselives) - { - UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); - V_DrawMappedPatch(LAPS_X+46, LAPS_Y-16, V_HUDTRANS|splitflags, facerankprefix[stplyr->skin], colormap); - V_DrawScaledPatch(LAPS_X+63, LAPS_Y-11, V_HUDTRANS|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow - } - } -} - -#undef RINGANIM_NUMFRAMES -#undef RINGANIM_FLIPFRAME - -static void K_drawKartSpeedometer(void) -{ - static fixed_t convSpeed; - UINT8 labeln = 0; - UINT8 numbers[3]; - INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT); - UINT8 battleoffset = 0; - - if (!stplyr->exiting) // Keep the same speed value as when you crossed the finish line! - { - switch (cv_kartspeedometer.value) - { - case 1: // Sonic Drift 2 style percentage - default: - convSpeed = (((25*stplyr->speed)/24) * 100) / K_GetKartSpeed(stplyr, false); // Based on top speed! (cheats with the numbers due to some weird discrepancy) - labeln = 0; - break; - case 2: // Kilometers - convSpeed = FixedDiv(FixedMul(stplyr->speed, 142371), mapobjectscale)/FRACUNIT; // 2.172409058 - labeln = 1; - break; - case 3: // Miles - convSpeed = FixedDiv(FixedMul(stplyr->speed, 88465), mapobjectscale)/FRACUNIT; // 1.349868774 - labeln = 2; - break; - case 4: // Fracunits - convSpeed = FixedDiv(stplyr->speed, mapobjectscale)/FRACUNIT; // 1.0. duh. - labeln = 3; - break; - } - } - - // Don't overflow - if (convSpeed > 999) - convSpeed = 999; - - numbers[0] = ((convSpeed / 100) % 10); - numbers[1] = ((convSpeed / 10) % 10); - numbers[2] = (convSpeed % 10); - - if (G_BattleGametype()) - battleoffset = 8; - - V_DrawScaledPatch(LAPS_X, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_speedometersticker); - V_DrawScaledPatch(LAPS_X+7, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[0]]); - V_DrawScaledPatch(LAPS_X+13, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[1]]); - V_DrawScaledPatch(LAPS_X+19, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_facenum[numbers[2]]); - V_DrawScaledPatch(LAPS_X+29, LAPS_Y-25 + battleoffset, V_HUDTRANS|splitflags, kp_speedometerlabel[labeln]); -} - -static void K_drawKartBumpersOrKarma(void) -{ - UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); - INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT); - - if (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, fontv[PINGNUM_FONT].font[ln[0]]); - V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[1]]); - - ln[0] = ((maptargets / 10) % 10); - ln[1] = (maptargets % 10); - - V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); - V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[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, fontv[PINGNUM_FONT].font[ln[0]]); - V_DrawScaledPatch(fx+17, fy, V_HUDTRANS|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|splitflags, fontv[PINGNUM_FONT].font[ln[0]]); - V_DrawScaledPatch(fx+31, fy, V_HUDTRANS|splitflags, fontv[PINGNUM_FONT].font[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 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, UINT8 camnum, 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; - - INT32 anglediff = (signed)(camang - R_PointToAngle2(campos->x, campos->y, point->x, point->y)); - fixed_t distance = R_PointToDist2(campos->x, campos->y, point->x, point->y); - fixed_t factor = INT32_MAX; - - if (abs(anglediff) > ANGLE_90) - { - if (hud_x != NULL) - { - *hud_x = -BASEVIDWIDTH * FRACUNIT; - } - - if (hud_y != NULL) - { - *hud_y = -BASEVIDWIDTH * FRACUNIT; - } - - //*hud_scale = FRACUNIT; - return; - } - - 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 (encoremode) - { - *hud_x = (BASEVIDWIDTH * FRACUNIT) - *hud_x; - } - - if (r_splitscreen >= 2) - { - *hud_x /= 2; - - if (camnum & 1) - { - *hud_x += swhalffixed; - } - } - } - - 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 (r_splitscreen >= 1) - { - *hud_y /= 2; - - if (camnum > 1) - { - *hud_y += shhalffixed; - } - } - } - - //*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 = K_calcSplitFlags(V_SNAPTOBOTTOM); - - 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, cnum, &v); - - colormap = R_GetTranslationColormap(TC_DEFAULT, checkplayer->mo->color, GTC_CACHE); - V_DrawFixedPatch(x, CHEK_Y * FRACUNIT, FRACUNIT, V_HUDTRANS|splitflags, kp_check[pnum], colormap); - } -} - -static void K_drawKartNameTags(void) -{ - const fixed_t maxdistance = 8192*mapobjectscale; - camera_t *thiscam; - vertex_t c; - UINT8 cnum = 0; - UINT8 i; - - 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; - - fixed_t x = -BASEVIDWIDTH * FRACUNIT; - fixed_t y = -BASEVIDWIDTH * FRACUNIT; - - vertex_t v; - UINT8 j; - - 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; - } - - for (j = 0; j <= r_splitscreen; j++) - { - if (ntplayer == &players[displayplayers[j]]) - { - break; - } - } - - if (j <= r_splitscreen) - { - // Is a player that's being shown on this computer - 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; - } - - K_ObjectTracking(&x, &y, &c, thiscam->angle, thiscam->aiming, cnum, &v); - - if (x == -BASEVIDWIDTH * FRACUNIT) - { - // Off-screen - continue; - } - - if (ntplayer->bot) - { - if (ntplayer->botvars.rival == true) - { - UINT8 blink = ((leveltime / 7) & 1); - V_DrawFixedPatch(x, y, FRACUNIT, V_HUDTRANS, kp_rival[blink], NULL); - } - } - else if (netgame) - { - if ((ntplayer->kartstuff[k_position] >= stplyr->kartstuff[k_position]-2) - && (ntplayer->kartstuff[k_position] <= stplyr->kartstuff[k_position]+2)) - { - INT32 namelen = V_ThinStringWidth(player_names[i], V_6WIDTHSPACE|V_ALLOWLOWERCASE); - INT32 clr = K_SkincolorToTextColor(ntplayer->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 - 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[i]); - } - } - } -} - -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; - - // 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] < 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 ((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_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. -#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 = 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|colortranslations[stplyr->skincolor][5]; - const INT32 accent2 = splitflags|colortranslations[stplyr->skincolor][9]; - ticcmd_t *cmd = &stplyr->cmd; - - if (timeinmap <= 105) - return; - - if (timeinmap < 113) - { - INT32 count = ((INT32)(timeinmap) - 105); - offs = 64; - while (count-- > 0) - offs >>= 1; - x += offs; - } - -#define BUTTW 8 -#define BUTTH 11 - -#define drawbutt(xoffs, butt, symb)\ - if (stplyr->cmd.buttons & butt)\ - {\ - offs = 2;\ - col = accent1;\ - }\ - else\ - {\ - offs = 0;\ - col = accent2;\ - V_DrawFill(x+(xoffs), y+BUTTH, BUTTW-1, 2, splitflags|31);\ - }\ - V_DrawFill(x+(xoffs), y+offs, BUTTW-1, BUTTH, col);\ - V_DrawFixedPatch((x+1+(xoffs))<driftturn) // no turn - target = 0; - else // turning of multiple strengths! - { - target = ((abs(cmd->driftturn) - 1)/125)+1; - if (target > 4) - target = 4; - if (cmd->driftturn < 0) - target = -target; - } - - if (pn != target) - { - if (abs(pn - target) == 1) - pn = target; - else if (pn < target) - pn += 2; - else //if (pn > target) - pn -= 2; - } - - if (pn < 0) - { - splitflags |= V_FLIP; // right turn - x--; - } - - target = abs(pn); - if (target > 4) - target = 4; - - if (!stplyr->skincolor) - V_DrawFixedPatch(x<skincolor, GTC_CACHE); - V_DrawFixedPatch(x<karthud[khud_lapanimation]; - UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, stplyr->skincolor, GTC_CACHE); - - V_DrawFixedPatch((BASEVIDWIDTH/2 + (32*max(0, stplyr->karthud[khud_lapanimation]-76)))*FRACUNIT, - (48 - (32*max(0, progress-76)))*FRACUNIT, - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - (modeattacking ? kp_lapanim_emblem[1] : kp_lapanim_emblem[0]), colormap); - - if (stplyr->karthud[khud_laphand] >= 1 && stplyr->karthud[khud_laphand] <= 3) - { - V_DrawFixedPatch((BASEVIDWIDTH/2 + (32*max(0, stplyr->karthud[khud_lapanimation]-76)))*FRACUNIT, - (48 - (32*max(0, progress-76)) - + 4 - abs((signed)((leveltime % 8) - 4)))*FRACUNIT, - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_hand[stplyr->karthud[khud_laphand]-1], NULL); - } - - if (stplyr->laps == (UINT8)(cv_numlaps.value)) - { - V_DrawFixedPatch((62 - (32*max(0, progress-76)))*FRACUNIT, // 27 - 30*FRACUNIT, // 24 - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_final[min(progress/2, 10)], NULL); - - if (progress/2-12 >= 0) - { - V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194 - 30*FRACUNIT, // 24 - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_lap[min(progress/2-12, 6)], NULL); - } - } - else - { - V_DrawFixedPatch((82 - (32*max(0, progress-76)))*FRACUNIT, // 61 - 30*FRACUNIT, // 24 - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_lap[min(progress/2, 6)], NULL); - - if (progress/2-8 >= 0) - { - V_DrawFixedPatch((188 + (32*max(0, progress-76)))*FRACUNIT, // 194 - 30*FRACUNIT, // 24 - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_number[(((UINT32)stplyr->laps) / 10)][min(progress/2-8, 2)], NULL); - - if (progress/2-10 >= 0) - { - V_DrawFixedPatch((208 + (32*max(0, progress-76)))*FRACUNIT, // 221 - 30*FRACUNIT, // 24 - FRACUNIT, V_SNAPTOTOP|V_HUDTRANS, - kp_lapanim_number[(((UINT32)stplyr->laps) % 10)][min(progress/2-10, 2)], NULL); - } - } - } -} - -void K_drawKartFreePlay(UINT32 flashtime) -{ - // no splitscreen support because it's not FREE PLAY if you have more than one player in-game - // (you fool, you can take splitscreen online. :V) - - if ((flashtime % TICRATE) < TICRATE/2) - return; - - V_DrawKartString((BASEVIDWIDTH - (LAPS_X+1)) - (12*9), // mirror the laps thingy - LAPS_Y+3, V_HUDTRANS|V_SNAPTOBOTTOM|V_SNAPTORIGHT, "FREE PLAY"); -} - -static void -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; - } - - 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_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 = ((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 >= 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 (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_kart.h b/src/k_kart.h index af247029d..c5710affc 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -17,18 +17,24 @@ angle_t K_ReflectAngle(angle_t angle, angle_t against, fixed_t maxspeed, fixed_t 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_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,24 @@ 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_DropKitchenSink(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 +94,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_respawn.c b/src/k_respawn.c index f49ca3cf8..d1d47638e 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -34,9 +34,15 @@ fixed_t K_RespawnOffset(player_t *player, boolean flip) if (flip == true) { - player->mo->flags2 |= MF2_OBJECTFLIP; + // 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); + z -= ((128 * mapobjectscale) + (player->mo->height)); } else { @@ -75,7 +81,7 @@ static void K_RespawnAtWaypoint(player_t *player, waypoint_t *waypoint) 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); + player->respawn.flip = (waypoint->mobj->flags2 & MF2_OBJECTFLIP) ? true : false; // K_RespawnOffset wants a boolean! player->respawn.pointz += K_RespawnOffset(player, player->respawn.flip); } @@ -96,11 +102,19 @@ void K_DoIngameRespawn(player_t *player) return; } - if (leveltime <= starttime) + 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; @@ -108,7 +122,7 @@ void K_DoIngameRespawn(player_t *player) player->trickpanel = 0; // Set up respawn position if invalid - if (player->respawn.wp != NULL) + if (player->respawn.wp != NULL && leveltime >= starttime) { const UINT32 dist = RESPAWN_DIST + (player->airtime * 48); player->respawn.distanceleft = (dist * mapobjectscale) / FRACUNIT; @@ -279,7 +293,7 @@ static void K_MovePlayerToRespawnPoint(player_t *player) player->mo->momx = player->mo->momy = player->mo->momz = 0; player->powers[pw_flashing] = 2; - player->powers[pw_nocontrol] = 2; + player->powers[pw_nocontrol] = max(2, player->powers[pw_nocontrol]); if (leveltime % 8 == 0 && !mapreset) { @@ -301,7 +315,7 @@ static void K_MovePlayerToRespawnPoint(player_t *player) // Reduce by the amount we needed to get to this waypoint stepamt -= dist; - // We've reached the destination point, + // We've reached the destination point, P_UnsetThingPosition(player->mo); player->mo->x = dest.x; player->mo->y = dest.y; @@ -325,8 +339,9 @@ static void K_MovePlayerToRespawnPoint(player_t *player) dest.x, dest.y ); - if ((player->respawn.distanceleft == 0) - && (K_GetWaypointIsSpawnpoint(player->respawn.wp) == true)) + 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; @@ -531,7 +546,8 @@ static void K_MovePlayerToRespawnPoint(player_t *player) --------------------------------------------------*/ static void K_DropDashWait(player_t *player) { - player->respawn.timer--; + if (player->powers[pw_nocontrol] == 0) + player->respawn.timer--; if (leveltime % 8 == 0) { diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 15eba4136..9916ceab8 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -28,6 +28,7 @@ #include "k_kart.h" // SRB2Kart #include "k_battle.h" #include "k_color.h" +#include "k_hud.h" #include "d_netcmd.h" // IsPlayerAdmin #include "lua_script.h" diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index cd5f4d267..3c85c358b 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -51,6 +51,7 @@ enum mobj_e { mobj_flags, mobj_flags2, mobj_eflags, + mobj_drawflags, mobj_skin, mobj_color, mobj_bnext, @@ -119,6 +120,7 @@ static const char *const mobj_opt[] = { "flags", "flags2", "eflags", + "drawflags", "skin", "color", "bnext", @@ -255,6 +257,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; @@ -541,7 +546,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 68947ee5b..0d5a30cab 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -119,7 +119,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) @@ -140,6 +140,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) @@ -243,6 +245,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); @@ -372,6 +382,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")) @@ -491,6 +502,14 @@ static int player_set(lua_State *L) 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); @@ -758,8 +777,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")) @@ -788,8 +805,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_menu.c b/src/m_menu.c index 9fdd70b12..432889e4c 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" @@ -56,7 +57,8 @@ #include "byteptr.h" #include "st_stuff.h" #include "i_sound.h" -#include "k_kart.h" // SRB2kart +#include "k_hud.h" // SRB2kart +#include "k_kart.h" // KartItemCVars #include "k_pwrlv.h" #include "d_player.h" // KITEM_ constants #include "k_color.h" @@ -125,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 @@ -158,6 +166,7 @@ INT16 startmap; // Mario, NiGHTS, or just a plain old normal game? static INT16 itemOn = 1; // menu item skull is on, Hack by Tails 09-18-2002 static INT16 skullAnimCounter = 10; // skull animation counter +static tic_t followertimer = 0; // Used for smooth follower floating static UINT8 setupcontrolplayer; static INT32 (*setupcontrols)[2]; // pointer to the gamecontrols of the player being edited @@ -1032,6 +1041,7 @@ static menuitem_t MP_PlayerSetupMenu[] = { {IT_KEYHANDLER | IT_STRING, NULL, "Name", M_HandleSetupMultiPlayer, 0}, {IT_KEYHANDLER | IT_STRING, NULL, "Character", M_HandleSetupMultiPlayer, 16}, // Tails 01-18-2001 + {IT_KEYHANDLER | IT_STRING, NULL, "Follower", M_HandleSetupMultiPlayer, 26}, {IT_KEYHANDLER | IT_STRING, NULL, "Color", M_HandleSetupMultiPlayer, 152}, }; @@ -1065,7 +1075,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}, @@ -1430,24 +1440,27 @@ enum static menuitem_t OP_HUDOptionsMenu[] = { - {IT_STRING | IT_CVAR, NULL, "Show HUD (F3)", &cv_showhud, 10}, - {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "HUD Visibility", &cv_translucenthud, 20}, - {IT_STRING | IT_SUBMENU, NULL, "Online HUD options...",&OP_ChatOptionsDef, 35}, - {IT_STRING | IT_CVAR, NULL, "Background Glass", &cons_backcolor, 45}, + {IT_STRING | IT_CVAR, NULL, "Show Followers", &cv_showfollowers, 10}, + + {IT_STRING | IT_CVAR, NULL, "Show HUD (F3)", &cv_showhud, 20}, + {IT_STRING | IT_CVAR | IT_CV_SLIDER, + NULL, "HUD Visibility", &cv_translucenthud, 30}, + + {IT_STRING | IT_SUBMENU, NULL, "Online HUD options...",&OP_ChatOptionsDef, 45}, + {IT_STRING | IT_CVAR, NULL, "Background Glass", &cons_backcolor, 55}, {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "Minimap Visibility", &cv_kartminimap, 60}, - {IT_STRING | IT_CVAR, NULL, "Speedometer Display", &cv_kartspeedometer, 70}, - {IT_STRING | IT_CVAR, NULL, "Show \"CHECK\"", &cv_kartcheck, 80}, + NULL, "Minimap Visibility", &cv_kartminimap, 70}, + {IT_STRING | IT_CVAR, NULL, "Speedometer Display", &cv_kartspeedometer, 80}, + {IT_STRING | IT_CVAR, NULL, "Show \"CHECK\"", &cv_kartcheck, 90}, - {IT_STRING | IT_CVAR, NULL, "Menu Highlights", &cons_menuhighlight, 95}, + {IT_STRING | IT_CVAR, NULL, "Menu Highlights", &cons_menuhighlight, 105}, // highlight info - (GOOD HIGHLIGHT, WARNING HIGHLIGHT) - 105 (see M_DrawHUDOptions) - {IT_STRING | IT_CVAR, NULL, "Console Text Size", &cv_constextsize, 120}, + {IT_STRING | IT_CVAR, NULL, "Console Text Size", &cv_constextsize, 130}, - {IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"", &cv_showfocuslost, 135}, + {IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"", &cv_showfocuslost, 145}, }; // Ok it's still called chatoptions but we'll put ping display in here to be clean @@ -2882,7 +2895,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; } @@ -3294,6 +3306,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 @@ -3336,6 +3372,8 @@ void M_Ticker(void) if (--skullAnimCounter <= 0) skullAnimCounter = 8; + followertimer++; + if (currentMenu == &PlaybackMenuDef) { if (playback_enterheld > 0) @@ -3351,6 +3389,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 } // @@ -4849,7 +4900,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; } @@ -5693,7 +5744,7 @@ static void M_DrawReplayStartMenu(void) // Lat: 08/06/2020: For some reason missing skins have their value set to 255 (don't even ask me why I didn't write this) // and for an even STRANGER reason this passes the first check below, so we're going to make sure that the skin here ISN'T 255 before we do anything stupid. - if (demolist[dir_on[menudepthleft]].standings[0].skin != 0xFF && W_CheckNumForName(skins[demolist[dir_on[menudepthleft]].standings[i].skin].facerank) != LUMPERROR) + if (demolist[dir_on[menudepthleft]].standings[i].skin != 0xFF && W_CheckNumForName(skins[demolist[dir_on[menudepthleft]].standings[i].skin].facerank) != LUMPERROR) { patch = facerankprefix[demolist[dir_on[menudepthleft]].standings[i].skin]; colormap = R_GetTranslationColormap( @@ -8460,22 +8511,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) @@ -8555,6 +8649,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) @@ -8636,10 +8738,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); @@ -8648,7 +8750,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) { @@ -8677,18 +8834,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; @@ -8701,34 +8861,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 @@ -9312,9 +9485,15 @@ static void M_HandleConnectIP(INT32 choice) // ======================== // Tails 03-02-2002 +// used for skin display on player setup menu static INT32 multi_tics; static state_t *multi_state; +// used for follower display on player setup menu +static INT32 follower_tics; +static UINT32 follower_frame; // used for FF_ANIMATE garbo +static state_t *follower_state; + // this is set before entering the MultiPlayer setup menu, // for either player 1 or 2 static char setupm_name[MAXPLAYERNAME+1]; @@ -9322,8 +9501,10 @@ static player_t *setupm_player; static consvar_t *setupm_cvskin; static consvar_t *setupm_cvcolor; static consvar_t *setupm_cvname; +static consvar_t *setupm_cvfollower; static INT32 setupm_fakeskin; static INT32 setupm_fakecolor; +static INT32 setupm_fakefollower; // -1 is for none, our followers start at 0 static void M_DrawSetupMultiPlayerMenu(void) { @@ -9341,6 +9522,7 @@ static void M_DrawSetupMultiPlayerMenu(void) UINT8 i; const UINT8 *flashcol = V_GetStringColormap(highlightflags); INT32 statx, staty; + char *fname; mx = MP_PlayerSetupDef.x; my = MP_PlayerSetupDef.y; @@ -9372,11 +9554,31 @@ static void M_DrawSetupMultiPlayerMenu(void) '\x1D' | highlightflags, false); // right arrow } + // draw follower string + fname = malloc(SKINNAMESIZE+1); + + if (setupm_fakefollower == -1) + strcpy(fname, "None"); + else + strcpy(fname, followers[setupm_fakefollower].name); + + st = V_StringWidth(fname, 0); + V_DrawString(BASEVIDWIDTH - mx - st, my + 26, + ((MP_PlayerSetupMenu[2].status & IT_TYPE) == IT_SPACE ? V_TRANSLUCENT : 0)|highlightflags|V_ALLOWLOWERCASE, + fname); + if (itemOn == 2) + { + V_DrawCharacter(BASEVIDWIDTH - mx - 10 - st - (skullAnimCounter/5), my + 26, + '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(BASEVIDWIDTH - mx + 2 + (skullAnimCounter/5), my + 26, + '\x1D' | highlightflags, false); // right arrow + } + // draw the name of the color you have chosen // Just so people don't go thinking that "Default" is Green. st = V_StringWidth(KartColor_Names[setupm_fakecolor], 0); V_DrawString(BASEVIDWIDTH - mx - st, my + 152, highlightflags|V_ALLOWLOWERCASE, KartColor_Names[setupm_fakecolor]); // SRB2kart - if (itemOn == 2) + if (itemOn == 3) { V_DrawCharacter(BASEVIDWIDTH - mx - 10 - st - (skullAnimCounter/5), my + 152, '\x1C' | highlightflags, false); // left arrow @@ -9524,7 +9726,7 @@ static void M_DrawSetupMultiPlayerMenu(void) sprframe = &sprdef->spriteframes[frame]; patch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE); - if (sprframe->flip & 1) // Only for first sprite + if (sprframe->flip & 2) // Only for first sprite flags |= V_FLIP; // This sprite is left/right flipped! // draw box around guy @@ -9545,9 +9747,88 @@ static void M_DrawSetupMultiPlayerMenu(void) else V_DrawMappedPatch(mx+43, my+131, flags, patch, colormap); } + + // draw their follower if there is one + if (setupm_fakefollower > -1 && setupm_fakefollower < numfollowers) + { + // animate the follower + + if (--follower_tics <= 0) + { + + // FF_ANIMATE; cycle through FRAMES and get back afterwards. This will be prominent amongst followers hence why it's being supported here. + if (follower_state->frame & FF_ANIMATE) + { + follower_frame++; + follower_tics = follower_state->var2; + if (follower_frame > (follower_state->frame & FF_FRAMEMASK) + follower_state->var1) // that's how it works, right? + follower_frame = follower_state->frame & FF_FRAMEMASK; + } + else + { + st = follower_state->nextstate; + if (st != S_NULL) + follower_state = &states[st]; + follower_tics = follower_state->tics; + if (follower_tics == -1) + follower_tics = 15; // er, what? + // get spritedef: + follower_frame = follower_state->frame & FF_FRAMEMASK; + } + } + sprdef = &sprites[follower_state->sprite]; + + // draw the follower + + if (follower_frame >= sprdef->numframes) + follower_frame = 0; // frame doesn't exist, we went beyond it... what? + sprframe = &sprdef->spriteframes[follower_frame]; + patch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE); + if (sprframe->flip & 2) // Only for first sprite + flags |= V_FLIP; // This sprite is left/right flipped! + + // @TODO: Reminder that followers on the menu right now do NOT support the 'followercolor' command, considering this whole menu is getting remade anyway, I see no point in incorporating it in right now. + + // draw follower sprite + if (setupm_fakecolor) // inverse should never happen + { + + // Fake the follower's in game appearance by now also applying some of its variables! coolio, eh? + follower_t fl = followers[setupm_fakefollower]; // shortcut for our sanity + // smooth floating, totally not stolen from rocket sneakers. + const fixed_t pi = (22<>ANGLETOFINESHIFT) & FINEMASK); + + UINT8 *colormap = R_GetTranslationColormap(-1, setupm_fakecolor, 0); + V_DrawFixedPatch((mx+65)*FRACUNIT, (my+131-fl.zoffs)*FRACUNIT+sine, fl.scale, flags, patch, colormap); + Z_Free(colormap); + } + } + #undef charw } +// follower state update. This is its own function so that it's at least somewhat clean +static void M_GetFollowerState(void) +{ + + if (setupm_fakefollower <= -1 || setupm_fakefollower > numfollowers-1) // yikes, there's none! + return; + // ^ we don't actually need to set anything since it won't be displayed anyway. + + //followertimer = 0; // reset timer. not like it'll overflow anytime soon but whatever. + + // set follower state + follower_state = &states[followers[setupm_fakefollower].followstate]; + + if (follower_state->frame & FF_ANIMATE) + follower_tics = follower_state->var2; // support for FF_ANIMATE + else + follower_tics = follower_state->tics; + + follower_frame = follower_state->frame & FF_FRAMEMASK; +} + // Handle 1P/2P MP Setup static void M_HandleSetupMultiPlayer(INT32 choice) { @@ -9575,7 +9856,13 @@ static void M_HandleSetupMultiPlayer(INT32 choice) S_StartSound(NULL,sfx_menu1); // Tails setupm_fakeskin--; } - else if (itemOn == 2) // player color + else if (itemOn == 2) // follower + { + S_StartSound(NULL,sfx_menu1); + setupm_fakefollower--; + M_GetFollowerState(); // update follower state + } + else if (itemOn == 3) // player color { S_StartSound(NULL,sfx_menu1); // Tails setupm_fakecolor--; @@ -9587,8 +9874,15 @@ static void M_HandleSetupMultiPlayer(INT32 choice) { S_StartSound(NULL,sfx_menu1); // Tails setupm_fakeskin++; + M_GetFollowerState(); // update follower state } - else if (itemOn == 2) // player color + else if (itemOn == 2) // follower + { + S_StartSound(NULL,sfx_menu1); + setupm_fakefollower++; + M_GetFollowerState(); + } + else if (itemOn == 3) // player color { S_StartSound(NULL,sfx_menu1); // Tails setupm_fakecolor++; @@ -9608,7 +9902,12 @@ static void M_HandleSetupMultiPlayer(INT32 choice) setupm_name[l-1] =0; } } - else if (itemOn == 2) + else if (itemOn == 2) // follower + { + S_StartSound(NULL,sfx_menu1); + setupm_fakefollower = -1; + } + else if (itemOn == 3) { UINT8 col = skins[setupm_fakeskin].prefcolor; if (setupm_fakecolor != col) @@ -9646,6 +9945,18 @@ static void M_HandleSetupMultiPlayer(INT32 choice) if (setupm_fakeskin > numskins-1) setupm_fakeskin = 0; + // check followers: + if (setupm_fakefollower < -1) + { + setupm_fakefollower = numfollowers-1; + M_GetFollowerState(); // update follower state + } + if (setupm_fakefollower > numfollowers-1) + { + setupm_fakefollower = -1; + M_GetFollowerState(); // update follower state + } + // check color if (setupm_fakecolor < 1) setupm_fakecolor = MAXSKINCOLORS-1; @@ -9668,6 +9979,7 @@ static void M_SetupMultiPlayer(INT32 choice) multi_state = &states[mobjinfo[MT_PLAYER].seestate]; multi_tics = multi_state->tics; + strcpy(setupm_name, cv_playername.string); // set for player 1 @@ -9675,6 +9987,15 @@ static void M_SetupMultiPlayer(INT32 choice) setupm_cvskin = &cv_skin; setupm_cvcolor = &cv_playercolor; setupm_cvname = &cv_playername; + setupm_cvfollower = &cv_follower; + + setupm_fakefollower = atoi(setupm_cvfollower->string); // update fake follower value + + // yikes, we don't want none of that... + if (setupm_fakefollower > numfollowers-1) + setupm_fakefollower = -1; + + M_GetFollowerState(); // update follower state // For whatever reason this doesn't work right if you just use ->value setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); @@ -9706,6 +10027,15 @@ static void M_SetupMultiPlayer2(INT32 choice) setupm_cvskin = &cv_skin2; setupm_cvcolor = &cv_playercolor2; setupm_cvname = &cv_playername2; + setupm_cvfollower = &cv_follower2; + + setupm_fakefollower = atoi(setupm_cvfollower->string); // update fake follower value + + // yikes, we don't want none of that... + if (setupm_fakefollower > numfollowers-1) + setupm_fakefollower = -1; + + M_GetFollowerState(); // update follower state // For whatever reason this doesn't work right if you just use ->value setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); @@ -9737,6 +10067,15 @@ static void M_SetupMultiPlayer3(INT32 choice) setupm_cvskin = &cv_skin3; setupm_cvcolor = &cv_playercolor3; setupm_cvname = &cv_playername3; + setupm_cvfollower = &cv_follower3; + + setupm_fakefollower = atoi(setupm_cvfollower->string); // update fake follower value + + // yikes, we don't want none of that... + if (setupm_fakefollower > numfollowers-1) + setupm_fakefollower = -1; + + M_GetFollowerState(); // update follower state // For whatever reason this doesn't work right if you just use ->value setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); @@ -9768,6 +10107,15 @@ static void M_SetupMultiPlayer4(INT32 choice) setupm_cvskin = &cv_skin4; setupm_cvcolor = &cv_playercolor4; setupm_cvname = &cv_playername4; + setupm_cvfollower = &cv_follower4; + + setupm_fakefollower = atoi(setupm_cvfollower->string); // update fake follower value + + // yikes, we don't want none of that... + if (setupm_fakefollower > numfollowers-1) + setupm_fakefollower = -1; + + M_GetFollowerState(); // update follower state // For whatever reason this doesn't work right if you just use ->value setupm_fakeskin = R_SkinAvailable(setupm_cvskin->string); @@ -9800,6 +10148,7 @@ static boolean M_QuitMultiPlayerMenu(void) // you know what? always putting these in the buffer won't hurt anything. COM_BufAddText (va("%s \"%s\"\n",setupm_cvskin->name,skins[setupm_fakeskin].name)); COM_BufAddText (va("%s %d\n",setupm_cvcolor->name,setupm_fakecolor)); + COM_BufAddText (va("%s %d\n",setupm_cvfollower->name,setupm_fakefollower)); return true; } @@ -10683,7 +11032,7 @@ static void M_DrawHUDOptions(void) const char *str1 = " Warning highlight"; const char *str2 = ","; const char *str3 = "Good highlight"; - INT32 x = BASEVIDWIDTH - currentMenu->x + 2, y = currentMenu->y + 105; + INT32 x = BASEVIDWIDTH - currentMenu->x + 2, y = currentMenu->y + 115; INT32 w0 = V_StringWidth(str0, 0), w1 = V_StringWidth(str1, 0), w2 = V_StringWidth(str2, 0), w3 = V_StringWidth(str3, 0); M_DrawGenericMenu(); @@ -10920,6 +11269,7 @@ static void M_DrawMonitorToggles(void) switch (currentMenu->menuitems[thisitem].alphaKey) { + case KRITEM_DUALSNEAKER: case KRITEM_DUALJAWZ: drawnum = 2; break; @@ -10989,6 +11339,7 @@ static void M_DrawMonitorToggles(void) switch (currentMenu->menuitems[itemOn].alphaKey) { + case KRITEM_DUALSNEAKER: case KRITEM_DUALJAWZ: drawnum = 2; break; diff --git a/src/m_menu.h b/src/m_menu.h index 86a840894..1ad20c777 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -17,6 +17,8 @@ #include "d_event.h" #include "command.h" +#include "i_threads.h" +#include "mserv.h" #include "r_things.h" // for SKINNAMESIZE // @@ -72,6 +74,18 @@ typedef enum } menumessagetype_t; void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtype); +typedef enum +{ + M_NOT_WAITING, + + M_WAITING_VERSION, + M_WAITING_ROOMS, + M_WAITING_SERVERS, +} +M_waiting_mode_t; + +extern M_waiting_mode_t m_waiting_mode; + // Called by linux_x/i_video_xshm.c void M_QuitResponse(INT32 ch); @@ -161,6 +175,9 @@ typedef struct menuitem_s extern menuitem_t PlayerMenu[MAXSKINS]; +extern menuitem_t MP_RoomMenu[]; +extern UINT32 roomIds[NUM_LIST_ROOMS]; + typedef struct menu_s { const char *menutitlepic; @@ -176,6 +193,10 @@ typedef struct menu_s void M_SetupNextMenu(menu_t *menudef); void M_ClearMenus(boolean callexitmenufunc); +#ifdef HAVE_THREADS +extern I_mutex m_menu_mutex; +#endif + extern menu_t *currentMenu; extern menu_t MainDef; diff --git a/src/mserv.c b/src/mserv.c index 12469928d..f3d414c92 100644 --- a/src/mserv.c +++ b/src/mserv.c @@ -2,242 +2,77 @@ //----------------------------------------------------------------------------- // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2018 by Sonic Team Junior. +// Copyright (C) 2020 by James R. // // 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 mserv.c -/// \brief Commands used for communicate with the master server - -#ifdef __GNUC__ -#include -#include -#include -#endif +/// \brief Commands used to communicate with the master server #if !defined (UNDER_CE) #include #endif -#if (defined (NOMD5) || defined (NOMSERV)) && !defined (NONET) -#define NONET -#endif - -#ifndef NONET - -#ifndef NO_IPV6 -#define HAVE_IPV6 -#endif - -#if (defined (_WIN32) || defined (_WIN32_WCE)) && !defined (_XBOX) -#define RPC_NO_WINDOWS_H -#ifdef HAVE_IPV6 -#include -#else -#include // socket(),... -#endif //!HAVE_IPV6 -#else -#ifdef __OS2__ -#include -#endif // __OS2__ - -#ifdef HAVE_LWIP -#include -#include -#include -#define ioctl lwip_ioctl -#else -#include -#ifdef __APPLE_CC__ -#ifndef _BSD_SOCKLEN_T_ -#define _BSD_SOCKLEN_T_ -#endif -#endif -#include // socket(),... -#include // sockaddr_in -#ifdef _PS3 -#include -#elif !defined(_arch_dreamcast) -#include // getaddrinfo(),... -#include -#endif -#endif - -#ifdef _arch_dreamcast -#include "sdl12/SRB2DC/dchelp.h" -#endif - -#include // timeval,... (TIMEOUT) -#include -#endif // _WIN32/_WIN32_WCE - -#ifdef __OS2__ -#include -#endif // __OS2__ -#endif // !NONET - #include "doomstat.h" #include "doomdef.h" #include "command.h" -#include "i_net.h" -#include "console.h" +#include "i_threads.h" #include "mserv.h" -#include "d_net.h" -#include "i_tcp.h" -#include "i_system.h" -#include "byteptr.h" #include "m_menu.h" -#include "m_argv.h" // Alam is going to kill me <3 -#include "m_misc.h" // GetRevisionString() +#include "z_zone.h" -#ifdef _WIN32_WCE -#include "sdl12/SRB2CE/cehelp.h" -#endif +static int MSId; +static int MSRegisteredId = -1; -#include "i_addrinfo.h" +static boolean MSRegistered; +static boolean MSInProgress; +static boolean MSUpdateAgain; -// ================================ DEFINITIONS =============================== +static time_t MSLastPing; -#define PACKET_SIZE 1024 +#ifdef HAVE_THREADS +static I_mutex MSMutex; +static I_cond MSCond; +# define Lock_state() I_lock_mutex (&MSMutex) +# define Unlock_state() I_unlock_mutex (MSMutex) +#else/*HAVE_THREADS*/ +# define Lock_state() +# define Unlock_state() +#endif/*HAVE_THREADS*/ -#define MS_NO_ERROR 0 -#define MS_SOCKET_ERROR -201 -#define MS_CONNECT_ERROR -203 -#define MS_WRITE_ERROR -210 -#define MS_READ_ERROR -211 -#define MS_CLOSE_ERROR -212 -#define MS_GETHOSTBYNAME_ERROR -220 -#define MS_GETHOSTNAME_ERROR -221 -#define MS_TIMEOUT_ERROR -231 - -// see master server code for the values -#define ADD_SERVER_MSG 101 -#define REMOVE_SERVER_MSG 103 -#define ADD_SERVERv2_MSG 104 -#define GET_SERVER_MSG 200 -#define GET_SHORT_SERVER_MSG 205 -#define ASK_SERVER_MSG 206 -#define ANSWER_ASK_SERVER_MSG 207 -#define ASK_SERVER_MSG 206 -#define ANSWER_ASK_SERVER_MSG 207 -#define GET_MOTD_MSG 208 -#define SEND_MOTD_MSG 209 -#define GET_ROOMS_MSG 210 -#define SEND_ROOMS_MSG 211 -#define GET_ROOMS_HOST_MSG 212 -#define GET_VERSION_MSG 213 -#define SEND_VERSION_MSG 214 -#define GET_BANNED_MSG 215 // Someone's been baaaaaad! -#define PING_SERVER_MSG 216 - -#define HEADER_SIZE (sizeof (INT32)*4) - -#define HEADER_MSG_POS 0 -#define IP_MSG_POS 16 -#define PORT_MSG_POS 32 -#define HOSTNAME_MSG_POS 40 - - -#if defined(_MSC_VER) -#pragma pack(1) -#endif - -/** A message to be exchanged with the master server. - */ -typedef struct -{ - INT32 id; ///< Unused? - INT32 type; ///< Type of message. - INT32 room; ///< Because everyone needs a roomie. - UINT32 length; ///< Length of the message. - char buffer[PACKET_SIZE]; ///< Actual contents of the message. -} ATTRPACK msg_t; - -#if defined(_MSC_VER) -#pragma pack() -#endif - -typedef struct Copy_CVarMS_t -{ - char ip[64]; - char port[8]; - char name[64]; -} Copy_CVarMS_s; -static Copy_CVarMS_s registered_server; -static time_t MSLastPing; - -#if defined(_MSC_VER) -#pragma pack(1) -#endif -typedef struct -{ - char ip[16]; // Big enough to hold a full address. - UINT16 port; - UINT8 padding1[2]; - tic_t time; -} ATTRPACK ms_holepunch_packet_t; -#if defined(_MSC_VER) -#pragma pack() -#endif - -// win32 or djgpp -#if defined (_WIN32) || defined (_WIN32_WCE) || defined (__DJGPP__) -#define ioctl ioctlsocket -#define close closesocket -#ifdef WATTCP -#define strerror strerror_s -#endif -#if defined (_WIN32) || defined (_WIN32_WCE) -#undef errno -#define errno h_errno // some very strange things happen when not using h_error -#endif -#ifndef AI_ADDRCONFIG -#define AI_ADDRCONFIG 0x00000400 -#endif -#endif +static void Update_parameters (void); #ifndef NONET static void Command_Listserv_f(void); #endif static void MasterServer_OnChange(void); -static void ServerName_OnChange(void); -#define DEF_PORT "28900" -consvar_t cv_masterserver = {"masterserver", "ms.srb2.org:"DEF_PORT, CV_SAVE, NULL, MasterServer_OnChange, 0, NULL, NULL, 0, 0, NULL}; -consvar_t cv_servername = {"servername", "SRB2Kart server", CV_SAVE|CV_CALL|CV_NOINIT, NULL, ServerName_OnChange, 0, NULL, NULL, 0, 0, NULL}; +static CV_PossibleValue_t masterserver_update_rate_cons_t[] = { + {2, "MIN"}, + {60, "MAX"}, + {0} +}; + +consvar_t cv_masterserver = {"masterserver", "https://mb.srb2.org/MS/0", CV_SAVE|CV_CALL, NULL, MasterServer_OnChange, 0, NULL, NULL, 0, 0, NULL}; +consvar_t cv_servername = {"servername", "SRB2Kart server", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Update_parameters, 0, NULL, NULL, 0, 0, NULL}; + +consvar_t cv_masterserver_update_rate = {"masterserver_update_rate", "15", CV_SAVE|CV_CALL|CV_NOINIT, masterserver_update_rate_cons_t, Update_parameters, 0, NULL, NULL, 0, 0, NULL}; INT16 ms_RoomId = -1; -static enum { MSCS_NONE, MSCS_WAITING, MSCS_REGISTERED, MSCS_FAILED } con_state = MSCS_NONE; +#ifdef HAVE_THREADS +int ms_QueryId; +I_mutex ms_QueryId_mutex; + +msg_server_t *ms_ServerList; +I_mutex ms_ServerList_mutex; +#endif -static INT32 msnode = -1; UINT16 current_port = 0; -#if (defined (_WIN32) || defined (_WIN32_WCE) || defined (_WIN32)) && !defined (NONET) -typedef SOCKET SOCKET_TYPE; -#define ERRSOCKET (SOCKET_ERROR) -#else -#if (defined (__unix__) && !defined (MSDOS)) || defined (__APPLE__) || defined (__HAIKU__) || defined (_PS3) -typedef int SOCKET_TYPE; -#else -typedef unsigned long SOCKET_TYPE; -#endif -#define ERRSOCKET (-1) -#endif - -#if (defined (WATTCP) && !defined (__libsocket_socklen_t)) || defined (_WIN32) -typedef int socklen_t; -#endif - -#ifndef NONET -static SOCKET_TYPE socket_fd = ERRSOCKET; // WINSOCK socket -static struct timeval select_timeout; -static fd_set wset; -static size_t recvfull(SOCKET_TYPE s, char *buf, size_t len, int flags); -#endif - // Room list is an external variable now. // Avoiding having to get info ten thousand times... msg_rooms_t room_list[NUM_LIST_ROOMS+1]; // +1 for easy test @@ -251,371 +86,96 @@ void AddMServCommands(void) { #ifndef NONET CV_RegisterVar(&cv_masterserver); + CV_RegisterVar(&cv_masterserver_update_rate); + CV_RegisterVar(&cv_masterserver_timeout); + CV_RegisterVar(&cv_masterserver_debug); + CV_RegisterVar(&cv_masterserver_token); CV_RegisterVar(&cv_servername); COM_AddCommand("listserv", Command_Listserv_f); #endif } -/** Closes the connection to the master server. - * - * \todo Fix for Windows? - */ -static void CloseConnection(void) +static void WarnGUI (void) { -#ifndef NONET - if (socket_fd != (SOCKET_TYPE)ERRSOCKET) - close(socket_fd); - socket_fd = ERRSOCKET; +#ifdef HAVE_THREADS + I_lock_mutex(&m_menu_mutex); #endif -} - -// -// MS_Write(): -// -static INT32 MS_Write(msg_t *msg) -{ -#ifdef NONET - (void)msg; - return MS_WRITE_ERROR; -#else - size_t len; - - if (msg->length == 0) - msg->length = (INT32)strlen(msg->buffer); - len = msg->length + HEADER_SIZE; - - msg->type = htonl(msg->type); - msg->length = htonl(msg->length); - msg->room = htonl(msg->room); - - if ((size_t)send(socket_fd, (char *)msg, (int)len, 0) != len) - return MS_WRITE_ERROR; - return 0; + M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n\nCheck the console for details.\n"), NULL, MM_NOTHING); +#ifdef HAVE_THREADS + I_unlock_mutex(m_menu_mutex); #endif } -// -// MS_Read(): -// -static INT32 MS_Read(msg_t *msg) -{ -#ifdef NONET - (void)msg; - return MS_READ_ERROR; -#else - if (recvfull(socket_fd, (char *)msg, HEADER_SIZE, 0) != HEADER_SIZE) - return MS_READ_ERROR; - - msg->type = ntohl(msg->type); - msg->length = ntohl(msg->length); - msg->room = ntohl(msg->room); - - if (!msg->length) // fix a bug in Windows 2000 - return 0; - - if (recvfull(socket_fd, (char *)msg->buffer, msg->length, 0) != msg->length) - return MS_READ_ERROR; - return 0; -#endif -} - -#ifndef NONET -/** Gets a list of game servers from the master server. - */ -static INT32 GetServersList(void) -{ - msg_t msg; - INT32 count = 0; - - msg.type = GET_SERVER_MSG; - msg.length = 0; - msg.room = 0; - if (MS_Write(&msg) < 0) - return MS_WRITE_ERROR; - - while (MS_Read(&msg) >= 0) - { - if (!msg.length) - { - if (!count) - CONS_Alert(CONS_NOTICE, M_GetText("No servers currently running.\n")); - return MS_NO_ERROR; - } - count++; - CONS_Printf("%s",msg.buffer); - } - - return MS_READ_ERROR; -} -#endif - -// -// MS_Connect() -// -static INT32 MS_Connect(const char *ip_addr, const char *str_port, INT32 async) -{ -#ifdef NONET - (void)ip_addr; - (void)str_port; - (void)async; -#else - struct my_addrinfo *ai, *runp, hints; - int gaie; - - memset (&hints, 0x00, sizeof(hints)); -#ifdef AI_ADDRCONFIG - hints.ai_flags = AI_ADDRCONFIG; -#endif - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; - - //I_InitTcpNetwork(); this is already done on startup in D_SRB2Main() - if (!I_InitTcpDriver()) // this is done only if not already done - return MS_SOCKET_ERROR; - - gaie = I_getaddrinfo(ip_addr, str_port, &hints, &ai); - if (gaie != 0) - return MS_GETHOSTBYNAME_ERROR; - else - runp = ai; - - while (runp != NULL) - { - socket_fd = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol); - if (socket_fd != (SOCKET_TYPE)ERRSOCKET) - { - if (async) // do asynchronous connection - { -#ifdef FIONBIO -#ifdef WATTCP - char res = 1; -#else - unsigned long res = 1; -#endif - - ioctl(socket_fd, FIONBIO, &res); -#endif - - if (connect(socket_fd, runp->ai_addr, (socklen_t)runp->ai_addrlen) == ERRSOCKET) - { -#ifdef _WIN32 // humm, on win32/win64 it doesn't work with EINPROGRESS (stupid windows) - if (WSAGetLastError() != WSAEWOULDBLOCK) -#else - if (errno != EINPROGRESS) -#endif - { - con_state = MSCS_FAILED; - CloseConnection(); - I_freeaddrinfo(ai); - return MS_CONNECT_ERROR; - } - } - con_state = MSCS_WAITING; - FD_ZERO(&wset); - FD_SET(socket_fd, &wset); - select_timeout.tv_sec = 0, select_timeout.tv_usec = 0; - I_freeaddrinfo(ai); - return 0; - } - else if (connect(socket_fd, runp->ai_addr, (socklen_t)runp->ai_addrlen) != ERRSOCKET) - { - I_freeaddrinfo(ai); - return 0; - } - } - runp = runp->ai_next; - } - I_freeaddrinfo(ai); -#endif - return MS_CONNECT_ERROR; -} - #define NUM_LIST_SERVER MAXSERVERLIST -const msg_server_t *GetShortServersList(INT32 room) +msg_server_t *GetShortServersList(INT32 room, int id) { - static msg_server_t server_list[NUM_LIST_SERVER+1]; // +1 for easy test - msg_t msg; - INT32 i; + msg_server_t *server_list; - // we must be connected to the master server before writing to it - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) - { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING); - return NULL; - } + // +1 for easy test + server_list = malloc(( NUM_LIST_SERVER + 1 ) * sizeof *server_list); - msg.type = GET_SHORT_SERVER_MSG; - msg.length = 0; - msg.room = room; - if (MS_Write(&msg) < 0) - return NULL; - - for (i = 0; i < NUM_LIST_SERVER && MS_Read(&msg) >= 0; i++) - { - if (!msg.length) - { - server_list[i].header.buffer[0] = 0; - CloseConnection(); - return server_list; - } - M_Memcpy(&server_list[i], msg.buffer, sizeof (msg_server_t)); - server_list[i].header.buffer[0] = 1; - } - CloseConnection(); - if (i == NUM_LIST_SERVER) - { - server_list[i].header.buffer[0] = 0; + if (HMS_fetch_servers(server_list, room, id)) return server_list; - } else + { + free(server_list); + WarnGUI(); return NULL; + } } -INT32 GetRoomsList(boolean hosting) +INT32 GetRoomsList(boolean hosting, int id) { - static msg_ban_t banned_info[1]; - msg_t msg; - INT32 i; - - // we must be connected to the master server before writing to it - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) - { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING); - return -1; - } - - if (hosting) - msg.type = GET_ROOMS_HOST_MSG; - else - msg.type = GET_ROOMS_MSG; - msg.length = 0; - msg.room = 0; - if (MS_Write(&msg) < 0) - { - room_list[0].id = 1; - strcpy(room_list[0].motd,"Master Server Offline."); - strcpy(room_list[0].name,"Offline"); - return -1; - } - - for (i = 0; i < NUM_LIST_ROOMS && MS_Read(&msg) >= 0; i++) - { - if(msg.type == GET_BANNED_MSG) - { - char banmsg[1000]; - M_Memcpy(&banned_info[0], msg.buffer, sizeof (msg_ban_t)); - if (hosting) - sprintf(banmsg, M_GetText("You have been banned from\nhosting netgames.\n\nUnder the following IP Range:\n%s - %s\n\nFor the following reason:\n%s\n\nYour ban will expire on:\n%s"),banned_info[0].ipstart,banned_info[0].ipend,banned_info[0].reason,banned_info[0].endstamp); - else - sprintf(banmsg, M_GetText("You have been banned from\njoining netgames.\n\nUnder the following IP Range:\n%s - %s\n\nFor the following reason:\n%s\n\nYour ban will expire on:\n%s"),banned_info[0].ipstart,banned_info[0].ipend,banned_info[0].reason,banned_info[0].endstamp); - M_StartMessage(banmsg, NULL, MM_NOTHING); - ms_RoomId = -1; - return -2; - } - if (!msg.length) - { - room_list[i].header.buffer[0] = 0; - CloseConnection(); - return 1; - } - M_Memcpy(&room_list[i], msg.buffer, sizeof (msg_rooms_t)); - room_list[i].header.buffer[0] = 1; - } - CloseConnection(); - if (i == NUM_LIST_ROOMS) - { - room_list[i].header.buffer[0] = 0; + if (HMS_fetch_rooms( ! hosting, id)) return 1; - } else { - room_list[0].id = 1; - strcpy(room_list[0].motd,M_GetText("Master Server Offline.")); - strcpy(room_list[0].name,M_GetText("Offline")); + WarnGUI(); return -1; } } #ifdef UPDATE_ALERT -const char *GetMODVersion(void) +char *GetMODVersion(int id) { - static msg_t msg; + char *buffer; + int c; - // we must be connected to the master server before writing to it - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) + (void)id; + + buffer = malloc(16); + + c = HMS_compare_mod_version(buffer, 16); + +#ifdef HAVE_THREADS + I_lock_mutex(&ms_QueryId_mutex); { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - M_StartMessage(M_GetText("There was a problem connecting to\nthe Master Server\n"), NULL, MM_NOTHING); - return NULL; + if (id != ms_QueryId) + c = -1; } + I_unlock_mutex(ms_QueryId_mutex); +#endif - msg.type = GET_VERSION_MSG; - msg.length = sizeof MODVERSION; - msg.room = MODID; // Might as well use it for something. - sprintf(msg.buffer,"%d",MODVERSION); - if (MS_Write(&msg) < 0) - { - CONS_Alert(CONS_ERROR, M_GetText("Could not send to the Master Server\n")); - M_StartMessage(M_GetText("Could not send to the Master Server\n"), NULL, MM_NOTHING); - CloseConnection(); - return NULL; - } - - if (MS_Read(&msg) < 0) - { - CONS_Alert(CONS_ERROR, M_GetText("No reply from the Master Server\n")); - M_StartMessage(M_GetText("No reply from the Master Server\n"), NULL, MM_NOTHING); - CloseConnection(); - return NULL; - } - - CloseConnection(); - - if(strcmp(msg.buffer,"NULL") != 0) - { - return msg.buffer; - } + if (c > 0) + return buffer; else + { + free(buffer); + + if (! c) + WarnGUI(); + return NULL; + } } // Console only version of the above (used before game init) void GetMODVersion_Console(void) { - static msg_t msg; + char buffer[16]; - // we must be connected to the master server before writing to it - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) - { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - return; - } - - msg.type = GET_VERSION_MSG; - msg.length = sizeof MODVERSION; - msg.room = MODID; // Might as well use it for something. - sprintf(msg.buffer,"%d",MODVERSION); - if (MS_Write(&msg) < 0) - { - CONS_Alert(CONS_ERROR, M_GetText("Could not send to the Master Server\n")); - CloseConnection(); - return; - } - - if (MS_Read(&msg) < 0) - { - CONS_Alert(CONS_ERROR, M_GetText("No reply from the Master Server\n")); - CloseConnection(); - return; - } - - CloseConnection(); - - if(strcmp(msg.buffer,"NULL") != 0) - I_Error(UPDATE_ALERT_STRING_CONSOLE, VERSIONSTRING, msg.buffer); + if (HMS_compare_mod_version(buffer, sizeof buffer) > 0) + I_Error(UPDATE_ALERT_STRING_CONSOLE, VERSIONSTRING, buffer); } #endif @@ -624,401 +184,347 @@ void GetMODVersion_Console(void) */ static void Command_Listserv_f(void) { - if (con_state == MSCS_WAITING) - { - CONS_Alert(CONS_NOTICE, M_GetText("Not yet connected to the Master Server.\n")); - return; - } - CONS_Printf(M_GetText("Retrieving server list...\n")); - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - return; + HMS_list_servers(); } - - if (GetServersList()) - CONS_Alert(CONS_ERROR, M_GetText("Cannot get server list\n")); - - CloseConnection(); } #endif -FUNCMATH static const char *int2str(INT32 n) +static void +Finish_registration (void) { - INT32 i; - static char res[16]; + int registered; - res[15] = '\0'; - res[14] = (char)((char)(n%10)+'0'); - for (i = 13; (n /= 10); i--) - res[i] = (char)((char)(n%10)+'0'); + CONS_Printf("Registering this server on the master server...\n"); - return &res[i+1]; -} + registered = HMS_register(); -#ifndef NONET -static INT32 ConnectionFailed(void) -{ - con_state = MSCS_FAILED; - CONS_Alert(CONS_ERROR, M_GetText("Connection to Master Server failed\n")); - CloseConnection(); - return MS_CONNECT_ERROR; -} -#endif - -/** Tries to register the local game server on the master server. - */ -static INT32 AddToMasterServer(boolean firstadd) -{ -#ifdef NONET - (void)firstadd; -#else - static INT32 retry = 0; - int i, res; - socklen_t j; - msg_t msg; - msg_server_t *info = (msg_server_t *)msg.buffer; - INT32 room = -1; - fd_set tset; - time_t timestamp = time(NULL); - UINT32 signature, tmp; - const char *insname; - - M_Memcpy(&tset, &wset, sizeof (tset)); - res = select(255, NULL, &tset, NULL, &select_timeout); - if (res != ERRSOCKET && !res) + Lock_state(); { - if (retry++ > 30) // an about 30 second timeout + MSRegistered = registered; + MSRegisteredId = MSId; + + time(&MSLastPing); + } + Unlock_state(); + + if (registered) + CONS_Printf("Master server registration successful.\n"); +} + +static void +Finish_update (void) +{ + int registered; + int done; + + Lock_state(); + { + registered = MSRegistered; + MSUpdateAgain = false;/* this will happen anyway */ + } + Unlock_state(); + + if (registered) + { + if (HMS_update()) { - retry = 0; - CONS_Alert(CONS_ERROR, M_GetText("Master Server timed out\n")); - MSLastPing = timestamp; - return ConnectionFailed(); + Lock_state(); + { + time(&MSLastPing); + MSRegistered = true; + } + Unlock_state(); + + CONS_Printf("Updated master server listing.\n"); } - return MS_CONNECT_ERROR; + else + Finish_registration(); } - retry = 0; - /* - Somehow we can still select our old socket despite it being closed(?). - Atleast, that's what I THINK is happening. Anyway, we have to check that we - haven't open a socket, and actually open it! - */ - /*if (res == ERRSOCKET)*//* wtf? no! */ - if (socket_fd == (SOCKET_TYPE)ERRSOCKET) + else + Finish_registration(); + + Lock_state(); { - if (MS_Connect(GetMasterServerIP(), GetMasterServerPort(), 0)) + done = ! MSUpdateAgain; + + if (done) + MSInProgress = false; + } + Unlock_state(); + + if (! done) + Finish_update(); +} + +static void +Finish_unlist (void) +{ + int registered; + + Lock_state(); + { + registered = MSRegistered; + } + Unlock_state(); + + if (registered) + { + CONS_Printf("Removing this server from the master server...\n"); + + if (HMS_unlist()) + CONS_Printf("Server deregistration request successfully sent.\n"); + + Lock_state(); { - CONS_Alert(CONS_ERROR, M_GetText("Master Server socket error #%u: %s\n"), errno, strerror(errno)); - MSLastPing = timestamp; - return ConnectionFailed(); + MSRegistered = false; } - } + Unlock_state(); - // so, the socket is writable, but what does that mean, that the connection is - // ok, or bad... let see that! - j = (socklen_t)sizeof (i); - getsockopt(socket_fd, SOL_SOCKET, SO_ERROR, (char *)&i, &j); - /* - This is also wrong. If getsockopt fails, i doesn't have to be set. Plus, if - it is set (which it appearantly is on linux), we check errno anyway. And in - the case that i is returned as normal, we don't even report the correct - value! So we accomplish NOTHING, except returning due to dumb luck. - If you care, fix this--I don't. -James (R.) - */ - if (i) // it was bad - { - CONS_Alert(CONS_ERROR, M_GetText("Master Server socket error #%u: %s\n"), errno, strerror(errno)); - MSLastPing = timestamp; - return ConnectionFailed(); - } - -#ifdef PARANOIA - if (ms_RoomId <= 0) - I_Error("Attmepted to host in room \"All\"!\n"); +#ifdef HAVE_THREADS + I_wake_all_cond(&MSCond); #endif - room = ms_RoomId; - - for(signature = 0, insname = cv_servername.string; *insname; signature += *insname++); - tmp = (UINT32)(signature * (size_t)&MSLastPing); - signature *= tmp; - signature &= 0xAAAAAAAA; - M_Memcpy(&info->header.signature, &signature, sizeof (UINT32)); - - strcpy(info->ip, ""); - strcpy(info->port, int2str(current_port)); - strcpy(info->name, cv_servername.string); - M_Memcpy(&info->room, & room, sizeof (INT32)); -#if VERSION > 0 || SUBVERSION > 0 - sprintf(info->version, "%d.%d", VERSION, SUBVERSION); -#else // Trunk build, send revision info - strcpy(info->version, GetRevisionString()); -#endif - strcpy(registered_server.name, cv_servername.string); - - if(firstadd) - msg.type = ADD_SERVER_MSG; - else - msg.type = PING_SERVER_MSG; - - msg.length = (UINT32)sizeof (msg_server_t); - msg.room = 0; - if (MS_Write(&msg) < 0) - { - MSLastPing = timestamp; - return ConnectionFailed(); } - if(con_state != MSCS_REGISTERED) - CONS_Printf(M_GetText("Master Server update successful.\n")); - - MSLastPing = timestamp; - con_state = MSCS_REGISTERED; - CloseConnection(); -#endif - return MS_NO_ERROR; -} - -static INT32 RemoveFromMasterSever(void) -{ - msg_t msg; - msg_server_t *info = (msg_server_t *)msg.buffer; - - strcpy(info->header.buffer, ""); - strcpy(info->ip, ""); - strcpy(info->port, int2str(current_port)); - strcpy(info->name, registered_server.name); - sprintf(info->version, "%d.%d", VERSION, SUBVERSION); - - msg.type = REMOVE_SERVER_MSG; - msg.length = (UINT32)sizeof (msg_server_t); - msg.room = 0; - if (MS_Write(&msg) < 0) - return MS_WRITE_ERROR; - - return MS_NO_ERROR; -} - -const char *GetMasterServerPort(void) -{ - const char *t = cv_masterserver.string; - - while ((*t != ':') && (*t != '\0')) - t++; - - if (*t) - return ++t; - else - return DEF_PORT; -} - -/** Gets the IP address of the master server. Actually, it seems to just - * return the hostname, instead; the lookup is done elsewhere. - * - * \return Hostname of the master server, without port number on the end. - * \todo Rename function? - */ -const char *GetMasterServerIP(void) -{ - static char str_ip[64]; - char *t = str_ip; - - if (strstr(cv_masterserver.string, "srb2.ssntails.org:28910") - || strstr(cv_masterserver.string, "srb2.servegame.org:28910") - || strstr(cv_masterserver.string, "srb2.servegame.org:28900") - ) + Lock_state(); { - // replace it with the current default one - CV_Set(&cv_masterserver, cv_masterserver.defaultvalue); + if (MSId == MSRegisteredId) + MSId++; } - - strcpy(t, cv_masterserver.string); - - while ((*t != ':') && (*t != '\0')) - t++; - *t = '\0'; - - return str_ip; + Unlock_state(); } -void MSOpenUDPSocket(void) +#ifdef HAVE_THREADS +static int * +Server_id (void) { -#ifndef NONET - if (I_NetMakeNodewPort) + int *id; + id = malloc(sizeof *id); + Lock_state(); { - // If it's already open, there's nothing to do. - if (msnode < 0) - msnode = I_NetMakeNodewPort(GetMasterServerIP(), GetMasterServerPort()); + *id = MSId; } - else -#endif - msnode = -1; + Unlock_state(); + return id; } -void MSCloseUDPSocket(void) +static int * +New_server_id (void) { - if (msnode != INT16_MAX) I_NetFreeNodenum(msnode); - msnode = -1; + int *id; + id = malloc(sizeof *id); + Lock_state(); + { + *id = ++MSId; + I_wake_all_cond(&MSCond); + } + Unlock_state(); + return id; } +static void +Register_server_thread (int *id) +{ + int same; + + Lock_state(); + { + /* wait for previous unlist to finish */ + while (*id == MSId && MSRegistered) + I_hold_cond(&MSCond, MSMutex); + + same = ( *id == MSId );/* it could have been a while */ + } + Unlock_state(); + + if (same)/* it could have been a while */ + Finish_registration(); + + free(id); +} + +static void +Update_server_thread (int *id) +{ + int same; + + Lock_state(); + { + same = ( *id == MSRegisteredId ); + } + Unlock_state(); + + if (same) + Finish_update(); + + free(id); +} + +static void +Unlist_server_thread (int *id) +{ + int same; + + Lock_state(); + { + same = ( *id == MSRegisteredId ); + } + Unlock_state(); + + if (same) + Finish_unlist(); + + free(id); +} + +static void +Change_masterserver_thread (char *api) +{ + Lock_state(); + { + while (MSRegistered) + I_hold_cond(&MSCond, MSMutex); + } + Unlock_state(); + + HMS_set_api(api); +} +#endif/*HAVE_THREADS*/ + void RegisterServer(void) { - if (con_state == MSCS_REGISTERED || con_state == MSCS_WAITING) - return; - - CONS_Printf(M_GetText("Registering this server on the Master Server...\n")); - - strcpy(registered_server.ip, GetMasterServerIP()); - strcpy(registered_server.port, GetMasterServerPort()); - - if (MS_Connect(registered_server.ip, registered_server.port, 1)) - { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - return; - } - MSOpenUDPSocket(); - - // keep the TCP connection open until AddToMasterServer() is completed; +#ifdef HAVE_THREADS + I_spawn_thread( + "register-server", + (I_thread_fn)Register_server_thread, + New_server_id() + ); +#else + Finish_registration(); +#endif } -static inline void SendPingToMasterServer(void) +static void UpdateServer(void) { -/* static tic_t next_time = 0; - tic_t cur_time; - char *inbuffer = (char*)netbuffer; - - cur_time = I_GetTime(); - if (!netgame) - UnregisterServer(); - else if (cur_time > next_time) // ping every 2 second if possible - { - next_time = cur_time+2*TICRATE; - - if (con_state == MSCS_WAITING) - AddToMasterServer(); - - if (con_state != MSCS_REGISTERED) - return; - - // cur_time is just a dummy data to send - WRITEUINT32(inbuffer, cur_time); - doomcom->datalength = sizeof (cur_time); - doomcom->remotenode = (INT16)msnode; - I_NetSend(); - } -*/ - -// Here, have a simpler MS Ping... - Cue - if(time(NULL) > (MSLastPing+(60*2)) && con_state != MSCS_NONE) - { - //CONS_Debug(DBG_NETPLAY, "%ld (current time) is greater than %d (Last Ping Time)\n", time(NULL), MSLastPing); - if(MSLastPing < 1) - AddToMasterServer(true); - else - AddToMasterServer(false); - } -} - -void SendAskInfoViaMS(INT32 node, tic_t asktime) -{ - const char *address; - UINT16 port; - char *inip; - ms_holepunch_packet_t mshpp; - - MSOpenUDPSocket(); - - // This must be called after calling MSOpenUDPSocket, due to the - // static buffer. - address = I_GetNodeAddress(node); - - // no address? - if (!address) - return; - - // Copy the IP address into the buffer. - inip = mshpp.ip; - while(*address && *address != ':') *inip++ = *address++; - *inip = '\0'; - - // Get the port. - port = (UINT16)(*address++ ? atoi(address) : 0); - mshpp.port = SHORT(port); - - // Set the time for ping calculation. - mshpp.time = LONG(asktime); - - // Send to the MS. - M_Memcpy(netbuffer, &mshpp, sizeof(mshpp)); - doomcom->datalength = sizeof(ms_holepunch_packet_t); - doomcom->remotenode = (INT16)msnode; - I_NetSend(); +#ifdef HAVE_THREADS + I_spawn_thread( + "update-server", + (I_thread_fn)Update_server_thread, + Server_id() + ); +#else + Finish_update(); +#endif } void UnregisterServer(void) { - if (con_state != MSCS_REGISTERED) +#ifdef HAVE_THREADS + I_spawn_thread( + "unlist-server", + (I_thread_fn)Unlist_server_thread, + Server_id() + ); +#else + Finish_unlist(); +#endif +} + +static boolean +Online (void) +{ + return ( serverrunning && ms_RoomId > 0 ); +} + +static inline void SendPingToMasterServer(void) +{ + int ready; + time_t now; + + if (Online()) { - con_state = MSCS_NONE; - CloseConnection(); - return; + time(&now); + + Lock_state(); + { + ready = ( + MSRegisteredId == MSId && + ! MSInProgress && + now >= ( MSLastPing + 60 * cv_masterserver_update_rate.value ) + ); + + if (ready) + MSInProgress = true; + } + Unlock_state(); + + if (ready) + UpdateServer(); } +} - con_state = MSCS_NONE; +static void +Update_parameters (void) +{ + int registered; + int delayed; - CONS_Printf(M_GetText("Removing this server from the Master Server...\n")); - - if (MS_Connect(registered_server.ip, registered_server.port, 0)) + if (Online()) { - CONS_Alert(CONS_ERROR, M_GetText("Cannot connect to the Master Server\n")); - return; + Lock_state(); + { + delayed = MSInProgress; + + if (delayed)/* do another update after the current one */ + MSUpdateAgain = true; + else + registered = MSRegistered; + } + Unlock_state(); + + if (! delayed && registered) + UpdateServer(); } - - if (RemoveFromMasterSever() < 0) - CONS_Alert(CONS_ERROR, M_GetText("Cannot remove this server from the Master Server\n")); - - CloseConnection(); - MSCloseUDPSocket(); - MSLastPing = 0; } void MasterClient_Ticker(void) { - if (server && ms_RoomId > 0) - SendPingToMasterServer(); + SendPingToMasterServer(); } -static void ServerName_OnChange(void) +static void +Set_api (const char *api) { - if (con_state == MSCS_REGISTERED) - AddToMasterServer(false); +#ifdef HAVE_THREADS + I_spawn_thread( + "change-masterserver", + (I_thread_fn)Change_masterserver_thread, + strdup(api) + ); +#else + HMS_set_api(strdup(api)); +#endif } static void MasterServer_OnChange(void) { UnregisterServer(); - RegisterServer(); -} -#ifndef NONET -// Like recv, but waits until we've got enough data to fill the buffer. -static size_t recvfull(SOCKET_TYPE s, char *buf, size_t len, int flags) -{ - /* Total received. */ - size_t totallen = 0; - - while(totallen < len) - { - ssize_t ret = (ssize_t)recv(s, buf + totallen, (int)(len - totallen), flags); - - /* Error. */ - if(ret == -1) - return (size_t)-1; - - totallen += ret; + /* + TODO: remove this for v2, it's just a hack + for those coming in with an old config. + */ + if ( + ! cv_masterserver.changed && + strcmp(cv_masterserver.string, "ms.srb2.org:28900") == 0 + ){ + CV_StealthSet(&cv_masterserver, cv_masterserver.defaultvalue); } - return totallen; + Set_api(cv_masterserver.string); + + if (Online()) + RegisterServer(); } -#endif diff --git a/src/mserv.h b/src/mserv.h index 8f8152a7c..9269c408a 100644 --- a/src/mserv.h +++ b/src/mserv.h @@ -2,6 +2,7 @@ //----------------------------------------------------------------------------- // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2018 by Sonic Team Junior. +// Copyright (C) 2020 by James R. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -13,7 +14,7 @@ #ifndef _MSERV_H_ #define _MSERV_H_ -#define MASTERSERVERS21 // MasterServer v2.1 +#include "i_threads.h" // lowered from 32 due to menu changes #define NUM_LIST_ROOMS 16 @@ -64,33 +65,47 @@ typedef struct // ================================ GLOBALS =============================== extern consvar_t cv_masterserver, cv_servername; +extern consvar_t cv_masterserver_update_rate; +extern consvar_t cv_masterserver_timeout; +extern consvar_t cv_masterserver_debug; +extern consvar_t cv_masterserver_token; // < 0 to not connect (usually -1) (offline mode) // == 0 to show all rooms, not a valid hosting room // anything else is whatever room the MS assigns to that number (online mode) extern INT16 ms_RoomId; -const char *GetMasterServerPort(void); -const char *GetMasterServerIP(void); +#ifdef HAVE_THREADS +extern int ms_QueryId; +extern I_mutex ms_QueryId_mutex; -void MSOpenUDPSocket(void); -void MSCloseUDPSocket(void); - -void SendAskInfoViaMS(INT32 node, tic_t asktime); +extern msg_server_t *ms_ServerList; +extern I_mutex ms_ServerList_mutex; +#endif void RegisterServer(void); void UnregisterServer(void); void MasterClient_Ticker(void); -const msg_server_t *GetShortServersList(INT32 room); -INT32 GetRoomsList(boolean hosting); +msg_server_t *GetShortServersList(INT32 room, int id); +INT32 GetRoomsList(boolean hosting, int id); #ifdef UPDATE_ALERT -const char *GetMODVersion(void); +char *GetMODVersion(int id); void GetMODVersion_Console(void); #endif extern msg_rooms_t room_list[NUM_LIST_ROOMS+1]; void AddMServCommands(void); +/* HTTP */ +void HMS_set_api (char *api); +int HMS_fetch_rooms (int joining, int id); +int HMS_register (void); +int HMS_unlist (void); +int HMS_update (void); +void HMS_list_servers (void); +msg_server_t * HMS_fetch_servers (msg_server_t *list, int room, int id); +int HMS_compare_mod_version (char *buffer, size_t size_of_buffer); + #endif diff --git a/src/p_enemy.c b/src/p_enemy.c index d148a09a0..611e45c40 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -808,10 +808,10 @@ void A_Look(mobj_t *actor) return; #endif - 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 @@ -3412,10 +3412,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)) { @@ -3547,9 +3547,9 @@ void A_BubbleCheck(mobj_t *actor) return; #endif 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 @@ -3654,9 +3654,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) @@ -3877,9 +3877,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); @@ -5342,9 +5342,9 @@ void A_CrawlaCommanderThink(mobj_t *actor) thefloor = actor->floorz; if (actor->fuse & 1) - actor->flags2 |= MF2_DONTDRAW; + actor->drawflags |= MFD_DONTDRAW; else - actor->flags2 &= ~MF2_DONTDRAW; + actor->drawflags &= ~MFD_DONTDRAW; if (actor->reactiontime > 0) actor->reactiontime--; @@ -9067,7 +9067,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 edfa2ec3c..5ad39ea6b 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2218,7 +2218,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source) target->player->score = 0; }*/ - target->flags2 &= ~MF2_DONTDRAW; + target->drawflags &= ~MFD_DONTDRAW; } // if killed by a player @@ -2959,7 +2959,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; diff --git a/src/p_mobj.c b/src/p_mobj.c index 8c7e84c8a..94ed691fd 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1168,26 +1168,6 @@ static void P_PlayerFlip(mobj_t *mo) if (mo->tracer) mo->tracer->eflags ^= MFE_VERTICALFLIP; } - else if (mo->player->pflags & PF_FLIPCAM) - { - UINT8 i; - - mo->player->aiming = InvAngle(mo->player->aiming); - - for (i = 0; i <= r_splitscreen; i++) - { - if (mo->player-players == displayplayers[i]) - { - localaiming[i] = mo->player->aiming; - if (camera[i].chase) { - camera[i].aiming = InvAngle(camera[i].aiming); - camera[i].z = mo->z - camera[i].z + mo->z; - if (mo->eflags & MFE_VERTICALFLIP) - camera[i].z += FixedMul(20*FRACUNIT, mo->scale); - } - } - } - } } // @@ -1461,10 +1441,10 @@ static void P_XYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy) mo->momy = FixedMul(mo->momy, ns); } else if (abs(player->rmomx) < FixedMul(STOPSPEED, mo->scale) - && abs(player->rmomy) < FixedMul(STOPSPEED, mo->scale) - && (!(player->cmd.forwardmove && !(twodlevel || mo->flags2 & MF2_TWOD)) && !player->cmd.sidemove && !(player->pflags & PF_SPINNING)) + && abs(player->rmomy) < FixedMul(STOPSPEED, mo->scale) + && (!(K_GetForwardMove(player) && !(twodlevel || mo->flags2 & MF2_TWOD)) && !(player->pflags & PF_SPINNING)) #ifdef ESLOPE - && !(player->mo->standingslope && (!(player->mo->standingslope->flags & SL_NOPHYSICS)) && (abs(player->mo->standingslope->zdelta) >= FRACUNIT/2)) + && !(player->mo->standingslope && (!(player->mo->standingslope->flags & SL_NOPHYSICS)))// && (abs(player->mo->standingslope->zdelta) >= FRACUNIT/2)) #endif ) { @@ -1476,16 +1456,8 @@ static void P_XYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy) } else { - 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; } @@ -1961,7 +1933,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; //} @@ -2457,6 +2429,17 @@ static 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)) @@ -2796,7 +2779,6 @@ static void P_PlayerZMovement(mobj_t *mo) // Check if we're on a polyobject // that triggers a linedef executor. msecnode_t *node; - boolean stopmovecut = false; for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next) { @@ -2828,8 +2810,8 @@ static void P_PlayerZMovement(mobj_t *mo) polysec = po->lines[0]->backsector; // Moving polyobjects should act like conveyors if the player lands on one. (I.E. none of the momentum cut thing below) -Red - if ((mo->z == polysec->ceilingheight || mo->z+mo->height == polysec->floorheight) && po->thinker) - stopmovecut = true; + /*if ((mo->z == polysec->ceilingheight || mo->z+mo->height == polysec->floorheight) && po->thinker) + stopmovecut = true;*/ if (!(po->flags & POF_LDEXEC)) { @@ -2850,24 +2832,7 @@ static void P_PlayerZMovement(mobj_t *mo) } } } - - if (!stopmovecut) #endif - - // Cut momentum in half when you hit the ground and - // aren't pressing any controls. - if (!(mo->player->cmd.forwardmove || mo->player->cmd.sidemove) && !mo->player->cmomx && !mo->player->cmomy - && !(mo->player->kartstuff[k_spinouttimer])) - { - mo->momx = mo->momx/2; - mo->momy = mo->momy/2; - - if (mo->player->cmd.buttons & BT_BRAKE && !(mo->player->cmd.forwardmove)) // FURTHER slowdown if you're braking. - { - mo->momx = mo->momx/2; - mo->momy = mo->momy/2; - } - } } if (mo->health) @@ -3599,8 +3564,6 @@ boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled if (encoremode) postimg = postimg_mirror; - else if (player->pflags & PF_FLIPCAM && !(player->pflags & PF_NIGHTSMODE) && player->mo->eflags & MFE_VERTICALFLIP) - postimg = postimg_flip; else if (player->awayviewtics && player->awayviewmobj && !P_MobjWasRemoved(player->awayviewmobj)) // Camera must obviously exist { camera_t dummycam; @@ -4048,9 +4011,9 @@ 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) @@ -4059,7 +4022,7 @@ static void P_RingThinker(mobj_t *mobj) if (!LUAh_MobjFuse(mobj)) #endif { - 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; @@ -6086,14 +6049,14 @@ static void P_NightsItemChase(mobj_t *thing) if (thing->info->painstate) P_SetMobjState(thing,thing->info->painstate); else - thing->flags2 |= MF2_SHADOW; + thing->drawflags |= MFD_SHADOW; } else { if (thing->info->painstate) P_SetMobjState(thing,thing->info->spawnstate); else - thing->flags2 &= ~MF2_SHADOW; + thing->drawflags &= ~(MFD_TRANSMASK|MFD_BRIGHTMASK); } } @@ -6482,7 +6445,7 @@ void P_MobjThinker(mobj_t *mobj) if (mobj->type == MT_GHOST && mobj->fuse > 0 // Not guaranteed to be MF_SCENERY or not MF_SCENERY! && (signed)(mobj->frame >> FF_TRANSSHIFT) < (NUMTRANSMAPS-1) - mobj->fuse / 2) // fade out when nearing the end of fuse... - mobj->frame = (mobj->frame & ~FF_TRANSMASK) | (((NUMTRANSMAPS-1) - mobj->fuse / 2) << FF_TRANSSHIFT); + mobj->drawflags = (mobj->drawflags & ~MFD_TRANSMASK) | (((NUMTRANSMAPS-1) - mobj->fuse / 2) << MFD_TRANSSHIFT); // Special thinker for scenery objects if (mobj->flags & MF_SCENERY) @@ -6495,6 +6458,27 @@ void P_MobjThinker(mobj_t *mobj) #endif switch (mobj->type) { + 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_HOOP: if (mobj->fuse > 1) P_MoveHoop(mobj); @@ -6648,30 +6632,7 @@ void P_MobjThinker(mobj_t *mobj) offz = mobj->target->height / 5; } - if (mobj->target->eflags & MFE_DRAWONLYFORP1) // groooooaann... - mobj->eflags |= MFE_DRAWONLYFORP1; - else - mobj->eflags &= ~MFE_DRAWONLYFORP1; - - if (mobj->target->eflags & MFE_DRAWONLYFORP2) - mobj->eflags |= MFE_DRAWONLYFORP2; - else - mobj->eflags &= ~MFE_DRAWONLYFORP2; - - if (mobj->target->eflags & MFE_DRAWONLYFORP3) - mobj->eflags |= MFE_DRAWONLYFORP3; - else - mobj->eflags &= ~MFE_DRAWONLYFORP3; - - if (mobj->target->eflags & MFE_DRAWONLYFORP4) - mobj->eflags |= MFE_DRAWONLYFORP4; - else - mobj->eflags &= ~MFE_DRAWONLYFORP4; - - if (mobj->target->flags2 & MF2_DONTDRAW) - mobj->flags2 |= MF2_DONTDRAW; - else - mobj->flags2 &= ~MF2_DONTDRAW; + mobj->drawflags = (mobj->target->drawflags & MFD_DONTDRAW); if (mobj->target->eflags & MFE_VERTICALFLIP) offz += 4*FRACUNIT; @@ -6734,7 +6695,7 @@ void P_MobjThinker(mobj_t *mobj) || (P_IsDisplayPlayer(mobj->target->player)) #endif ) - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; P_UnsetThingPosition(mobj); mobj->x = mobj->target->x; @@ -6784,7 +6745,7 @@ void P_MobjThinker(mobj_t *mobj) mobj->tracer->colorized = false; } - if (!(mobj->flags2 & MF2_DONTDRAW)) + if (!(mobj->drawflags & MFD_DONTDRAW)) { const INT32 numberdisplaymin = ((mobj->target->player->kartstuff[k_itemtype] == KITEM_ORBINAUT) ? 5 : 2); @@ -6794,7 +6755,7 @@ void P_MobjThinker(mobj_t *mobj) P_SetMobjState(mobj, S_PLAYERARROW_BOX); mobj->tracer->sprite = SPR_ITEM; mobj->tracer->frame = FF_FULLBRIGHT|(((mobj->target->player->kartstuff[k_itemroulette] % (13*3)) / 3) + 1); - mobj->tracer->flags2 &= ~MF2_DONTDRAW; + mobj->tracer->drawflags &= ~MFD_DONTDRAW; } else if (mobj->target->player->kartstuff[k_stolentimer] > 0) { @@ -6802,16 +6763,16 @@ void P_MobjThinker(mobj_t *mobj) mobj->tracer->sprite = SPR_ITEM; mobj->tracer->frame = FF_FULLBRIGHT|KITEM_HYUDORO; if (leveltime & 2) - mobj->tracer->flags2 &= ~MF2_DONTDRAW; + mobj->tracer->drawflags &= ~MFD_DONTDRAW; else - mobj->tracer->flags2 |= MF2_DONTDRAW; + mobj->tracer->drawflags |= MFD_DONTDRAW; } else if ((mobj->target->player->kartstuff[k_stealingtimer] > 0) && (leveltime & 2)) { P_SetMobjState(mobj, S_PLAYERARROW_BOX); mobj->tracer->sprite = SPR_ITEM; mobj->tracer->frame = FF_FULLBRIGHT|KITEM_HYUDORO; - mobj->tracer->flags2 &= ~MF2_DONTDRAW; + mobj->tracer->drawflags &= ~MFD_DONTDRAW; } else if (mobj->target->player->kartstuff[k_eggmanexplode] > 1) { @@ -6819,9 +6780,9 @@ void P_MobjThinker(mobj_t *mobj) mobj->tracer->sprite = SPR_ITEM; mobj->tracer->frame = FF_FULLBRIGHT|KITEM_EGGMAN; if (leveltime & 1) - mobj->tracer->flags2 &= ~MF2_DONTDRAW; + mobj->tracer->drawflags &= ~MFD_DONTDRAW; else - mobj->tracer->flags2 |= MF2_DONTDRAW; + mobj->tracer->drawflags |= MFD_DONTDRAW; } else if (mobj->target->player->kartstuff[k_rocketsneakertimer] > 1) { @@ -6830,9 +6791,9 @@ void P_MobjThinker(mobj_t *mobj) mobj->tracer->sprite = SPR_ITEM; mobj->tracer->frame = FF_FULLBRIGHT|KITEM_ROCKETSNEAKER; if (leveltime & 1) - mobj->tracer->flags2 &= ~MF2_DONTDRAW; + mobj->tracer->drawflags &= ~MFD_DONTDRAW; else - mobj->tracer->flags2 |= MF2_DONTDRAW; + mobj->tracer->drawflags |= MFD_DONTDRAW; } else if (mobj->target->player->kartstuff[k_itemtype] && mobj->target->player->kartstuff[k_itemamount] > 0) { @@ -6861,12 +6822,12 @@ void P_MobjThinker(mobj_t *mobj) if (mobj->target->player->kartstuff[k_itemheld]) { if (leveltime & 1) - mobj->tracer->flags2 &= ~MF2_DONTDRAW; + mobj->tracer->drawflags &= ~MFD_DONTDRAW; else - mobj->tracer->flags2 |= MF2_DONTDRAW; + mobj->tracer->drawflags |= MFD_DONTDRAW; } else - mobj->tracer->flags2 &= ~MF2_DONTDRAW; + mobj->tracer->drawflags &= ~MFD_DONTDRAW; } else { @@ -6907,7 +6868,7 @@ void P_MobjThinker(mobj_t *mobj) mobj->movecount = 0; } else - mobj->tracer->flags2 |= MF2_DONTDRAW; + mobj->tracer->drawflags |= MFD_DONTDRAW; } else if (mobj->health > 0) { @@ -6930,10 +6891,10 @@ void P_MobjThinker(mobj_t *mobj) return; } - if (mobj->tracer->flags2 & MF2_DONTDRAW) - mobj->flags2 |= MF2_DONTDRAW; + if (mobj->tracer->drawflags & MFD_DONTDRAW) + mobj->drawflags |= MFD_DONTDRAW; else - mobj->flags2 &= ~MF2_DONTDRAW; + mobj->drawflags &= ~MFD_DONTDRAW; P_UnsetThingPosition(mobj); mobj->x = mobj->target->x; @@ -6984,7 +6945,7 @@ void P_MobjThinker(mobj_t *mobj) mobj->eflags |= MFE_VERTICALFLIP; if (mobj->tics > 0) - mobj->flags2 ^= MF2_DONTDRAW; + mobj->drawflags ^= MFD_DONTDRAW; break; //} case MT_WATERDROP: @@ -7038,16 +6999,15 @@ void P_MobjThinker(mobj_t *mobj) if (mobj->target->eflags & MFE_VERTICALFLIP) { mobj->z = mobj->target->z - FixedMul(16*FRACUNIT, mobj->target->scale) - mobj->height; - if (mobj->target->player->pflags & PF_FLIPCAM) - mobj->eflags |= MFE_VERTICALFLIP; } else mobj->z = mobj->target->z + (mobj->target->height) + FixedMul(8*FRACUNIT, mobj->target->scale); // Adjust height for height changes if (mobj->threshold <= 35) - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; else - mobj->flags2 &= ~MF2_DONTDRAW; + mobj->drawflags &= ~MFD_DONTDRAW; + if (mobj->threshold <= 30) mobj->threshold = 40; mobj->threshold--; @@ -7367,14 +7327,15 @@ void P_MobjThinker(mobj_t *mobj) break; case MT_PLAYER: /// \todo Have the player's dead body completely finish its animation even if they've already respawned. - if (!(mobj->flags2 & MF2_DONTDRAW)) + // This feels like it has some serious potential for breakage. Is there anything else we can base this off of instead of a drawing flag? + if (!(mobj->drawflags & MFD_DONTDRAW)) { if (!mobj->fuse) { // Go away. /// \todo Actually go ahead and remove mobj completely, and fix any bugs and crashes doing this creates. Chasecam should stop moving, and F12 should never return to it. mobj->momz = 0; if (mobj->player) - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; else // safe to remove, nobody's going to complain! { P_RemoveMobj(mobj); @@ -7403,7 +7364,7 @@ void P_MobjThinker(mobj_t *mobj) case MT_ORBINAUT_SHIELD: case MT_BANANA_SHIELD: case MT_EGGMANITEM_SHIELD: - mobj->flags2 ^= MF2_DONTDRAW; + mobj->drawflags ^= MFD_DONTDRAW; break; case MT_JAWZ: case MT_JAWZ_DUD: @@ -7411,7 +7372,7 @@ void P_MobjThinker(mobj_t *mobj) P_SetMobjState(mobj, mobj->info->xdeathstate); // fallthru case MT_JAWZ_SHIELD: - mobj->flags2 ^= MF2_DONTDRAW; + mobj->drawflags ^= MFD_DONTDRAW; break; case MT_SSMINE: case MT_SPBEXPLOSION: @@ -7431,7 +7392,7 @@ void P_MobjThinker(mobj_t *mobj) return; case MT_CDUFO: if (mobj->fuse > TICRATE) - mobj->flags2 ^= MF2_DONTDRAW; // only by good fortune does this end with it having MF2_DONTDRAW... don't touch! + mobj->drawflags ^= MFD_DONTDRAW; // only by good fortune does this end with it having MFD_DONTDRAW... don't touch! break; case MT_SMK_PIPE: if (mobj->flags2 & MF2_AMBUSH) @@ -7440,7 +7401,7 @@ void P_MobjThinker(mobj_t *mobj) P_SetMobjStateNF(mobj, mobj->info->spawnstate); /* FALLTHRU */ case MT_SMK_MOLE: - mobj->flags2 ^= MF2_DONTDRAW; + mobj->drawflags ^= MFD_DONTDRAW; if (P_IsObjectOnGround(mobj)) { P_RemoveMobj(mobj); @@ -7461,7 +7422,7 @@ void P_MobjThinker(mobj_t *mobj) mobj->frame &= (~FF_FULLBRIGHT); } - mobj->flags2 ^= MF2_DONTDRAW; + mobj->drawflags ^= MFD_DONTDRAW; if (P_IsObjectOnGround(mobj)) { P_RemoveMobj(mobj); @@ -7761,7 +7722,7 @@ void P_MobjThinker(mobj_t *mobj) case MT_NIGHTSDRONE: if (mobj->state >= &states[S_NIGHTSDRONE_SPARKLING1] && mobj->state <= &states[S_NIGHTSDRONE_SPARKLING16]) { - mobj->flags2 &= ~MF2_DONTDRAW; + mobj->drawflags &= ~MFD_DONTDRAW; mobj->z = mobj->floorz + mobj->height + (mobj->spawnpoint->options >> ZSHIFT) * FRACUNIT; mobj->angle = 0; @@ -7787,7 +7748,7 @@ void P_MobjThinker(mobj_t *mobj) { mobj->flags &= ~MF_NOGRAVITY; P_SetMobjState(mobj, S_NIGHTSDRONE1); - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; } } else if (mobj->tracer && mobj->tracer->player) @@ -7795,7 +7756,7 @@ void P_MobjThinker(mobj_t *mobj) if (!(mobj->tracer->player->pflags & PF_NIGHTSMODE)) { mobj->flags &= ~MF_NOGRAVITY; - mobj->flags2 &= ~MF2_DONTDRAW; + mobj->drawflags &= ~MFD_DONTDRAW; P_SetMobjState(mobj, S_NIGHTSDRONE1); } else if (!mobj->tracer->player->bonustime) @@ -7832,7 +7793,7 @@ void P_MobjThinker(mobj_t *mobj) P_RemoveMobj(mobj->target); P_SetTarget(&mobj->target, NULL); } - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; } } else if (mobj->tracer && mobj->tracer->player) @@ -7852,10 +7813,10 @@ void P_MobjThinker(mobj_t *mobj) mobj->flags |= MF_NOGRAVITY; } else - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; } else // Not NiGHTS - mobj->flags2 &= ~MF2_DONTDRAW; + mobj->drawflags &= ~MFD_DONTDRAW; } mobj->angle += ANG10; if (mobj->z <= mobj->floorz) @@ -8297,15 +8258,62 @@ void P_MobjThinker(mobj_t *mobj) return; } - mobj->angle = mobj->target->angle; - P_TeleportMove(mobj, mobj->target->x + P_ReturnThrustX(mobj, mobj->angle+ANGLE_180, mobj->target->radius), - mobj->target->y + P_ReturnThrustY(mobj, mobj->angle+ANGLE_180, mobj->target->radius), mobj->target->z); + //mobj->angle = mobj->target->angle; + { + angle_t angle = R_PointToAngle2(0, 0, mobj->target->momx, mobj->target->momy); + fixed_t nudge; + + mobj->angle = angle; + + if (( mobj->fuse & 1 )) + { + nudge = 4*mobj->target->radius; + } + else + { + nudge = 2*mobj->target->radius; + /* rotate the papersprite frames to see the flat angle */ + mobj->angle += ANGLE_90; + } + + P_TeleportMove(mobj, + mobj->target->x + P_ReturnThrustX(mobj, angle + ANGLE_180, nudge), + mobj->target->y + P_ReturnThrustY(mobj, angle + ANGLE_180, nudge), + mobj->target->z); + } P_SetScale(mobj, mobj->target->scale); - mobj->flags2 ^= MF2_DONTDRAW; #ifdef HWRENDER mobj->modeltilt = mobj->target->modeltilt; #endif + if (mobj->fuse <= 16) + { + mobj->color = SKINCOLOR_KETCHUP; + /* don't draw papersprite frames after blue boost */ + mobj->drawflags ^= MFD_DONTDRAW; + } + else if (mobj->fuse <= 32) + mobj->color = SKINCOLOR_SAPPHIRE; + else if (mobj->fuse > 32) + mobj->color = (UINT8)(1 + (leveltime % (MAXSKINCOLORS-1))); + + switch (mobj->extravalue1) + { + case 3:/* rainbow boost */ + /* every 20 tics, bang! */ + if (( 120 - mobj->fuse ) % 10 == 0) + { + K_SpawnDriftBoostClip(mobj->target->player); + S_StartSound(mobj->target, sfx_s3k77); + } + break; + + case 2:/* blue boost */ + if (mobj->fuse == 16)/* to red*/ + K_SpawnDriftBoostClip(mobj->target->player); + break; + } + { player_t *p = NULL; if (mobj->target->target && mobj->target->target->player) @@ -8420,7 +8428,7 @@ void P_MobjThinker(mobj_t *mobj) K_MatchGenericExtraFlags(mobj, mobj->target); if (leveltime & 1) - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; } break; case MT_PLAYERRETICULE: @@ -8432,7 +8440,7 @@ void P_MobjThinker(mobj_t *mobj) P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z); break; case MT_INSTASHIELDB: - mobj->flags2 ^= MF2_DONTDRAW; + mobj->drawflags ^= MFD_DONTDRAW; K_MatchGenericExtraFlags(mobj, mobj->target); /* FALLTHRU */ case MT_INSTASHIELDA: @@ -8545,14 +8553,14 @@ void P_MobjThinker(mobj_t *mobj) mobj->angle = ang; if (leveltime & 1) - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; if (trans >= NUMTRANSMAPS) - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; else if (trans == 0) - mobj->frame = (mobj->frame & ~FF_TRANSMASK); + mobj->drawflags = (mobj->drawflags & ~MFD_TRANSMASK); else - mobj->frame = (mobj->frame & ~FF_TRANSMASK)|(trans << FF_TRANSSHIFT); + mobj->drawflags = (mobj->drawflags & ~MFD_TRANSMASK)|(trans << MFD_TRANSSHIFT); } break; case MT_THUNDERSHIELD: @@ -8619,7 +8627,7 @@ void P_MobjThinker(mobj_t *mobj) P_SetMobjState(mobj, S_BUBBLESHIELDBLOWUP); mobj->angle += ANGLE_22h; - mobj->flags2 &= ~MF2_SHADOW; + mobj->drawflags &= ~MFD_SHADOW; scale += (blow * (3*scale)) / bubbletime; mobj->frame = (states[S_BUBBLESHIELDBLOWUP].frame + mobj->extravalue1); @@ -8689,9 +8697,9 @@ void P_MobjThinker(mobj_t *mobj) else { if (mobj->target->player->kartstuff[k_bubblecool] && ((curstate-S_BUBBLESHIELD1) & 1)) - mobj->flags2 |= MF2_SHADOW; + mobj->drawflags |= MFD_SHADOW; else - mobj->flags2 &= ~MF2_SHADOW; + mobj->drawflags &= ~MFD_SHADOW; } } @@ -8853,18 +8861,7 @@ void P_MobjThinker(mobj_t *mobj) if (!mobj->extravalue2) { - if (mobj->eflags & MFE_VERTICALFLIP) - mobj->z -= mobj->height; - else - mobj->z += mobj->height; - - S_StartSound(mobj, mobj->info->deathsound); - P_SetObjectMomZ(mobj, 8*FRACUNIT, false); - P_InstaThrust(mobj, R_PointToAngle2(mobj->target->x, mobj->target->y, mobj->x, mobj->y)+ANGLE_90, 16*FRACUNIT); - mobj->momx += mobj->target->momx; - mobj->momy += mobj->target->momy; - mobj->momz += mobj->target->momz; - mobj->extravalue2 = 1; + K_DropRocketSneaker(mobj->target->player); } else if (P_IsObjectOnGround(mobj)) { @@ -8904,9 +8901,9 @@ void P_MobjThinker(mobj_t *mobj) if (state < S_PLAYERBOMB1 || state > S_PLAYERBOMB20) P_SetMobjState(mobj, S_PLAYERBOMB1); if (mobj->target->player->kartstuff[k_comebacktimer] < TICRATE && (leveltime & 1)) - mobj->flags2 &= ~MF2_DONTDRAW; + mobj->drawflags &= ~MFD_DONTDRAW; else - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; } else { @@ -8921,9 +8918,9 @@ void P_MobjThinker(mobj_t *mobj) P_SetMobjState(mobj, S_PLAYERFAKE1); if (mobj->target->player->powers[pw_flashing] && (leveltime & 1)) - mobj->flags2 |= MF2_DONTDRAW; + mobj->drawflags |= MFD_DONTDRAW; else - mobj->flags2 &= ~MF2_DONTDRAW; + mobj->drawflags &= ~MFD_DONTDRAW; } // Update mobj antigravity status: @@ -8953,10 +8950,10 @@ void P_MobjThinker(mobj_t *mobj) cur->colorized = true; K_FlipFromObject(cur, mobj->target); - if (mobj->flags2 & MF2_DONTDRAW) - cur->flags2 |= MF2_DONTDRAW; + if (mobj->drawflags & MFD_DONTDRAW) + cur->drawflags |= MFD_DONTDRAW; else - cur->flags2 &= ~MF2_DONTDRAW; + cur->drawflags &= ~MFD_DONTDRAW; cur = cur->hnext; } @@ -10314,7 +10311,7 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s P_SpawnMobj(newmobj->x, newmobj->y, newmobj->z, MT_EXPLODE); // poof into existance // Transfer flags2 (strongbox, objectflip) - newmobj->flags2 = mobj->flags2 & ~MF2_DONTDRAW; + newmobj->flags2 = mobj->flags2; if (mobj->threshold == 70) newmobj->threshold = 70; } @@ -10366,7 +10363,7 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s return; } else if (((mobj->type == MT_RANDOMITEM && mobj->threshold == 69) || mobj->type == MT_EGGMANITEM || mobj->type == MT_FALLINGROCK) && mobj->fuse <= TICRATE) - mobj->flags2 ^= MF2_DONTDRAW; + mobj->drawflags ^= MFD_DONTDRAW; } I_Assert(mobj != NULL); @@ -10596,13 +10593,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; } } @@ -10733,6 +10730,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; @@ -11667,13 +11667,19 @@ 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); + + if (time < 10*TICRATE) + { + // Ensure it doesn't go into absurdly low values + time = 10*TICRATE; + } } // nothing left to respawn? @@ -11729,8 +11735,8 @@ void P_RespawnSpecials(void) ss->sector->ceilingheight) - (mthing->options >> ZSHIFT) * FRACUNIT; if (mthing->options & MTF_AMBUSH && (i == MT_RING || i == MT_REDTEAMRING || i == MT_BLUETEAMRING || i == MT_COIN || P_WeaponOrPanel(i))) - z -= 24*FRACUNIT; - z -= mobjinfo[i].height; // Don't forget the height! + z -= 24 * mapobjectscale; + z -= FixedMul(mobjinfo[i].height, mapobjectscale); // Don't forget the height! } else { @@ -11741,7 +11747,7 @@ void P_RespawnSpecials(void) ss->sector->floorheight) + (mthing->options >> ZSHIFT) * FRACUNIT; if (mthing->options & MTF_AMBUSH && (i == MT_RING || i == MT_REDTEAMRING || i == MT_BLUETEAMRING || i == MT_COIN || P_WeaponOrPanel(i))) - z += 24*FRACUNIT; + z += 24 * mapobjectscale; } mo = P_SpawnMobj(x, y, z, i); @@ -11870,6 +11876,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; @@ -11883,7 +11891,7 @@ void P_SpawnPlayer(INT32 playernum) { mobj_t *overheadarrow = P_SpawnMobj(mobj->x, mobj->y, mobj->z + P_GetPlayerHeight(p)+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... @@ -11909,10 +11917,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; } } } diff --git a/src/p_mobj.h b/src/p_mobj.h index 746529360..dbb3f4d58 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. @@ -160,7 +160,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; @@ -170,7 +170,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 @@ -187,7 +187,7 @@ typedef enum MF2_JUSTATTACKED = 1<<17, // can be pushed by other moving mobjs MF2_FIRING = 1<<18, // turret fire MF2_SUPERFIRE = 1<<19, // Firing something with Super Sonic-stopping properties. Or, if mobj has MF_MISSILE, this is the actual fire from it. - MF2_SHADOW = 1<<20, // Fuzzy draw, makes targeting harder. + // free: 1<<20 MF2_STRONGBOX = 1<<21, // Flag used for "strong" random monitors. MF2_OBJECTFLIP = 1<<22, // Flag for objects that always have flipped gravity. MF2_SKULLFLY = 1<<23, // Special handling: skull in flight. @@ -241,14 +241,43 @@ typedef enum MFE_SPRUNG = 1<<8, // Platform movement MFE_APPLYPMOMZ = 1<<9, - // SRB2Kart: Splitscreen sprite display; very wasteful but I couldn't think of another way to do it... - MFE_DRAWONLYFORP1 = 1<<10, - MFE_DRAWONLYFORP2 = 1<<11, - MFE_DRAWONLYFORP3 = 1<<12, - MFE_DRAWONLYFORP4 = 1<<13, // 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 ?! ?! ?! // @@ -304,6 +333,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 57b6e20c5..516c47e6f 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -57,10 +57,11 @@ typedef enum { // RFLAGPOINT = 0x01, // BFLAGPOINT = 0x02, - CAPSULE = 0x04, - AWAYVIEW = 0x08, - FIRSTAXIS = 0x10, - SECONDAXIS = 0x20, + CAPSULE = 4, + AWAYVIEW = 8, + FIRSTAXIS = 16, + SECONDAXIS = 32, + FOLLOWER = 64, } player_saveflags; // @@ -218,6 +219,9 @@ static void P_NetArchivePlayers(void) if (players[i].axis2) flags |= SECONDAXIS; + if (players[i].follower) + flags |= FOLLOWER; + WRITEINT16(save_p, players[i].lastsidehit); WRITEINT16(save_p, players[i].lastlinehit); @@ -249,6 +253,14 @@ 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++) @@ -437,6 +449,13 @@ 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++) @@ -984,6 +1003,7 @@ typedef enum MD2_SLOPE = 1<<13, #endif MD2_SHADOWSCALE = 1<<14, + MD2_DRAWFLAGS = 1<<15, } mobj_diff2_t; typedef enum @@ -1182,6 +1202,8 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) #endif if (mobj->shadowscale) diff2 |= MD2_SHADOWSCALE; + if (mobj->drawflags) + diff2 |= MD2_DRAWFLAGS; if (mobj->colorized) diff2 |= MD2_COLORIZED; if (mobj == waypointcap) @@ -1313,6 +1335,17 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) WRITEUINT8(save_p, mobj->colorized); if (diff2 & MD2_SHADOWSCALE) WRITEFIXED(save_p, mobj->shadowscale); + 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); } @@ -2242,6 +2275,8 @@ static void LoadMobjThinker(actionf_p1 thinker) mobj->colorized = READUINT8(save_p); if (diff2 & MD2_SHADOWSCALE) mobj->shadowscale = READFIXED(save_p); + if (diff2 & MD2_DRAWFLAGS) + mobj->drawflags = READUINT16(save_p); if (diff & MD_REDFLAG) { @@ -3134,6 +3169,7 @@ 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) { if (mobj->player->capsule) @@ -3164,6 +3200,13 @@ static void P_RelinkPointers(void) if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "awayviewmobj 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; @@ -3433,6 +3476,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) @@ -3558,6 +3605,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 655b667fd..73a25fe51 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -1159,11 +1159,9 @@ static inline void P_SpawnEmblems(void) emblemmobj->flags |= MF_NOCLIP; emblemmobj->flags &= ~MF_SPECIAL; emblemmobj->flags |= MF_NOBLOCKMAP; - emblemmobj->frame |= (tr_trans50<drawflags |= (tr_trans50 << MFD_TRANSSHIFT); P_SetThingPosition(emblemmobj); } - else - emblemmobj->frame &= ~FF_TRANSMASK; } } @@ -1452,7 +1450,7 @@ static void P_LoadRawSideDefs2(void *data) UINT16 i; INT32 num; size_t j; - UINT32 cr, cg, cb; + RGBA_t color; for (i = 0; i < numsides; i++) { @@ -1534,23 +1532,21 @@ static void P_LoadRawSideDefs2(void *data) // encore mode colormaps! // do it like software by aproximating a color to a palette index, and then convert it to its encore variant and then back to a color code. // do this for both the start and fade colormaps. - - cr = (HEX2INT(col[1]) << 4) + (HEX2INT(col[2]) << 0); - cg = (HEX2INT(col[3]) << 12) + (HEX2INT(col[4]) << 8); - cb = (HEX2INT(col[5]) << 20) + (HEX2INT(col[6]) << 16); + + color.s.red = (HEX2INT(col[1]) << 4) + HEX2INT(col[2]); + color.s.green = (HEX2INT(col[3]) << 4) + HEX2INT(col[4]); + color.s.blue = (HEX2INT(col[5]) << 4) + HEX2INT(col[6]); #ifdef GLENCORE if (encoremap) { - j = encoremap[NearestColor((UINT8)cr, (UINT8)cg, (UINT8)cb)]; + j = encoremap[NearestColor(color.s.red, color.s.green, color.s.blue)]; //CONS_Printf("R_CreateColormap: encoremap[%d] = %d\n", j, encoremap[j]); -- moved encoremap upwards for optimisation - cr = pLocalPalette[j].s.red; - cg = pLocalPalette[j].s.green; - cb = pLocalPalette[j].s.blue; + color = pLocalPalette[j]; // note: this sets alpha to 255, we will reset it below } #endif - - sec->extra_colormap->rgba = cr + cg + cb; + color.s.alpha = 0; // reset/init the alpha, so the addition below will work correctly + sec->extra_colormap->rgba = color.rgba; // alpha if (msd->toptexture[7]) @@ -1577,23 +1573,21 @@ static void P_LoadRawSideDefs2(void *data) col = msd->bottomtexture; // do the exact same thing as above here. - - cr = (HEX2INT(col[1]) << 4) + (HEX2INT(col[2]) << 0); - cg = (HEX2INT(col[3]) << 12) + (HEX2INT(col[4]) << 8); - cb = (HEX2INT(col[5]) << 20) + (HEX2INT(col[6]) << 16); + + color.s.red = (HEX2INT(col[1]) << 4) + HEX2INT(col[2]); + color.s.green = (HEX2INT(col[3]) << 4) + HEX2INT(col[4]); + color.s.blue = (HEX2INT(col[5]) << 4) + HEX2INT(col[6]); #ifdef GLENCORE if (encoremap) { - j = encoremap[NearestColor((UINT8)cr, (UINT8)cg, (UINT8)cb)]; + j = encoremap[NearestColor(color.s.red, color.s.green, color.s.blue)]; //CONS_Printf("R_CreateColormap: encoremap[%d] = %d\n", j, encoremap[j]); -- moved encoremap upwards for optimisation - cr = pLocalPalette[j].s.red; - cg = pLocalPalette[j].s.green; - cb = pLocalPalette[j].s.blue; + color = pLocalPalette[j]; // note: this sets alpha to 255, we will reset it below } #endif - - sec->extra_colormap->fadergba = cr + cg + cb; + color.s.alpha = 0; // reset/init the alpha, so the addition below will work correctly + sec->extra_colormap->fadergba = color.rgba; // alpha if (msd->bottomtexture[7]) @@ -2370,6 +2364,7 @@ lumpnum_t lastloadedmaplumpnum; // for comparative savegame static void P_LevelInitStuff(void) { INT32 i; + UINT8 p = 0; leveltime = 0; @@ -2416,6 +2411,9 @@ static void P_LevelInitStuff(void) for (i = 0; i < MAXPLAYERS; i++) { + if (playeringame[i] && !players[i].spectator) + p++; + if (grandprixinfo.gp == false) { players[i].lives = 3; @@ -2460,8 +2458,34 @@ static void P_LevelInitStuff(void) // and this stupid flag as a result players[i].pflags &= ~PF_TRANSFERTOCLOSEST; + + // Wipe follower from existence to avoid crashes + players[i].follower = NULL; } + 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) { diff --git a/src/p_slopes.c b/src/p_slopes.c index 0825ebca6..6c44d2d9e 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 #ifdef ESLOPE @@ -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 927fd71f0..d54737bc1 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2066,7 +2066,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; @@ -2101,6 +2106,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])); diff --git a/src/p_tick.c b/src/p_tick.c index d57a6dc43..0c7f5b1dc 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -711,9 +711,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 (G_BattleGametype()) diff --git a/src/p_user.c b/src/p_user.c index 0f9987425..de50aaa0f 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -45,6 +45,7 @@ // 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" @@ -653,7 +654,7 @@ static void P_DeNightserizePlayer(player_t *player) player->mo->flags &= ~MF_NOGRAVITY; - player->mo->flags2 &= ~MF2_DONTDRAW; + player->mo->drawflags &= ~MFD_DONTDRAW; // Restore aiming angle if (player == &players[consoleplayer]) @@ -728,7 +729,7 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime) player->mo->flags |= MF_NOGRAVITY; - player->mo->flags2 |= MF2_DONTDRAW; + player->mo->drawflags |= MFD_DONTDRAW; player->nightstime = player->startedtime = nighttime*TICRATE; player->bonustime = false; @@ -1622,7 +1623,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; } } } @@ -1658,8 +1659,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj) ghost->sprite = mobj->sprite; 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; @@ -3410,7 +3410,7 @@ static void P_DoFiring(player_t *player, ticcmd_t *cmd) // SRB2kart - unused. return; P_SetWeaponDelay(player, (3*TICRATE)/2); - mo = P_SpawnPlayerMissile(player->mo, MT_REDRING, MF2_RAILRING|MF2_DONTDRAW); + mo = P_SpawnPlayerMissile(player->mo, MT_REDRING, MF2_RAILRING); // Rail has no unique thrown object, therefore its sound plays here. S_StartSound(player->mo, sfx_rail1); @@ -4020,9 +4020,9 @@ static void P_2dMovement(player_t *player) static void P_3dMovement(player_t *player) { ticcmd_t *cmd; - angle_t movepushangle, movepushsideangle; // Analog + angle_t movepushangle; // Analog //INT32 topspeed, acceleration, thrustfactor; - fixed_t movepushforward = 0, movepushside = 0; + 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); @@ -4039,13 +4039,6 @@ static void P_3dMovement(player_t *player) cmd = &player->cmd; - if (player->pflags & PF_STASIS || player->kartstuff[k_spinouttimer]) // pw_introcam? - { - cmd->forwardmove = cmd->sidemove = 0; - } - - cmd->sidemove = 0; // TODO: Remove sidemove entirely - 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 @@ -4053,8 +4046,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 { @@ -4107,10 +4098,6 @@ 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. onground = P_IsObjectOnGround(player->mo); @@ -4119,23 +4106,11 @@ static void P_3dMovement(player_t *player) // Forward movement if (!(P_PlayerInPain(player) && !onground)) { - movepushforward = K_3dKartMovement(player, onground, cmd->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; - } - totalthrust.x += P_ReturnThrustX(player->mo, movepushangle, movepushforward); totalthrust.y += P_ReturnThrustY(player->mo, movepushangle, movepushforward); } @@ -4144,18 +4119,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! @@ -4323,7 +4286,7 @@ static void P_SpectatorMovement(player_t *player) if (mo) { mo->flags2 |= MF2_RAILRING; - mo->flags2 |= MF2_DONTDRAW; + mo->drawflags |= MFD_DONTDRAW; mo->flags |= MF_NOCLIPHEIGHT; mo->flags |= MF_NOCLIP; mo->flags &= ~MF_MISSILE; @@ -5066,7 +5029,7 @@ static void P_NiGHTSMovement(player_t *player) radius = player->mo->target->radius; player->mo->flags |= MF_NOGRAVITY; - player->mo->flags2 |= MF2_DONTDRAW; + player->mo->drawflags |= MFD_DONTDRAW; P_SetScale(player->mo->tracer, player->mo->scale); if (player->mo->eflags & MFE_VERTICALFLIP) @@ -5794,18 +5757,10 @@ static 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->respawn.state != RESPAWNST_NONE) // 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. @@ -7110,7 +7065,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 @@ -7245,8 +7200,7 @@ fixed_t t_cam4_rotate = -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; @@ -7261,7 +7215,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; @@ -7310,7 +7264,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 @@ -7328,12 +7282,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))); @@ -7346,9 +7298,9 @@ static ticcmd_t *P_CameraCmd(camera_t *cam) cmd->buttons |= BT_BRAKE; axis = JoyAxis(AXISAIM, 1); if (InputDown(gc_aimforward, 1) || (usejoystick && axis < 0)) - forward += forwardmove[1]; + forward += forwardmove; if (InputDown(gc_aimbackward, 1) || (usejoystick && axis > 0)) - forward -= forwardmove[1]; + forward -= forwardmove; // fire with any button/key axis = JoyAxis(AXISFIRE, 1); @@ -7389,21 +7341,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); @@ -7448,11 +7391,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)); @@ -7468,7 +7410,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; @@ -7741,7 +7683,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; @@ -7764,7 +7706,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 != -42) || (thiscam == &camera[1] && t_cam2_rotate != -42) || (thiscam == &camera[2] && t_cam3_rotate != -42) @@ -8081,7 +8023,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); @@ -8409,6 +8351,287 @@ void P_DoTimeOver(player_t *player) exitcountdown = 5*TICRATE; } +/* 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 // @@ -8463,6 +8686,9 @@ 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--; @@ -8584,9 +8810,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); return; @@ -8601,7 +8827,7 @@ void P_PlayerThink(player_t *player) } // SRB2kart 010217 - if (leveltime < starttime) + if (leveltime < introtime) { player->powers[pw_nocontrol] = 2; } @@ -8733,8 +8959,7 @@ void P_PlayerThink(player_t *player) gmobj->fuse = 2; if (leveltime & 1) { - gmobj->frame &= ~FF_TRANSMASK; - gmobj->frame |= tr_trans70<drawflags |= tr_trans70 << MFD_TRANSSHIFT; } // Hide the mobj from our sights if we're the displayplayer and chasecam is off. @@ -8744,7 +8969,7 @@ void P_PlayerThink(player_t *player) { if (player == &players[displayplayers[i]] && !camera[i].chase) { - gmobj->flags2 |= MF2_DONTDRAW; + gmobj->drawflags |= MFD_DONTDRAW; break; } } @@ -8884,16 +9109,16 @@ void P_PlayerThink(player_t *player) { 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; } /*else if (player->mo->tracer) { if (player->powers[pw_flashing] & 1) - player->mo->tracer->flags2 |= MF2_DONTDRAW; + player->mo->tracer->drawflags |= MFD_DONTDRAW; else - player->mo->tracer->flags2 &= ~MF2_DONTDRAW; + player->mo->tracer->drawflags &= ~MFD_DONTDRAW; }*/ player->pflags &= ~PF_SLIDING; @@ -9232,8 +9457,8 @@ void P_PlayerAfterThink(player_t *player) if (!(player->mo->tracer->target->flags & MF_SLIDEME) // Noclimb on chain parameters gives this && !(twodlevel || player->mo->flags2 & MF2_TWOD)) // why on earth would you want to turn them in 2D mode? { - player->mo->tracer->target->health += cmd->sidemove; - player->mo->angle += cmd->sidemove< ANGLE_MAX + //player->mo->tracer->target->health += cmd->sidemove; + //player->mo->angle += cmd->sidemove< ANGLE_MAX if (player == &players[consoleplayer]) localangle[0] = player->mo->angle; // Adjust the local control angle. @@ -9271,7 +9496,7 @@ void P_PlayerAfterThink(player_t *player) // spectator invisibility and nogravity. if ((netgame || multiplayer) && player->spectator) { - player->mo->flags2 |= MF2_DONTDRAW; + player->mo->drawflags |= MFD_DONTDRAW; player->mo->flags |= MF_NOGRAVITY; } diff --git a/src/r_data.h b/src/r_data.h index 610a9c399..f9812d4e4 100644 --- a/src/r_data.h +++ b/src/r_data.h @@ -60,6 +60,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 TEXTURE1/TEXTURE2/PNAMES definitions, create lookup tables void R_LoadTextures(void); diff --git a/src/r_draw.c b/src/r_draw.c index 7746693bf..c993810d5 100644 --- a/src/r_draw.c +++ b/src/r_draw.c @@ -147,6 +147,7 @@ static UINT8** translationtablecache[TT_CACHE_SIZE] = {NULL}; // SKINCOLOR DEFINITIONS HAVE BEEN MOVED TO K_KART.C 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 5f7b0cadd..4cdfc470e 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -151,10 +151,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); @@ -165,10 +161,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}; @@ -177,8 +169,8 @@ 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_nights = {"drawdist_nights", "2048", 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_precipdensity = {"precipdensity", "Moderate", CV_SAVE, precipdensity_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -283,26 +275,6 @@ static void ChaseCam4_OnChange(void) CV_SetValue(&cv_analog4, 1);*/ } -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, @@ -1488,17 +1460,11 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_soniccd); 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_precipdensity); - CV_RegisterVar(&cv_translucency); CV_RegisterVar(&cv_drawdist); //CV_RegisterVar(&cv_drawdist_nights); CV_RegisterVar(&cv_drawdist_precip); diff --git a/src/r_main.h b/src/r_main.h index 879d4c6eb..641fdb3e4 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -75,9 +75,7 @@ boolean R_DoCulling(line_t *cullheight, line_t *viewcullheight, fixed_t vz, fixe extern consvar_t cv_showhud, cv_translucenthud; extern consvar_t cv_homremoval; extern consvar_t cv_chasecam, cv_chasecam2, cv_chasecam3, cv_chasecam4; -extern consvar_t cv_flipcam, cv_flipcam2, cv_flipcam3, cv_flipcam4; extern consvar_t cv_shadow; -extern consvar_t cv_translucency; extern consvar_t /*cv_precipdensity,*/ cv_drawdist, /*cv_drawdist_nights,*/ cv_drawdist_precip; extern consvar_t cv_fov; extern consvar_t cv_skybox; diff --git a/src/r_segs.c b/src/r_segs.c index efb1f4887..9078d5f98 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -179,14 +179,8 @@ static void R_DrawWallSplats(void) colfunc = basecolfunc; break; case SPLATDRAWMODE_TRANS: - if (!cv_translucency.value) - colfunc = basecolfunc; - else - { - dc_transmap = transtables + ((tr_trans50 - 1)<extra_colormap = sector->lightlist[i].extra_colormap; -/* - if (thing->frame & FF_TRANSMASK) - ; - else if (thing->flags2 & MF2_SHADOW) - ; - else -*/ if (!((newsprite->cut & SC_FULLBRIGHT) && (!newsprite->extra_colormap || !(newsprite->extra_colormap->fog & 1)))) { @@ -1164,25 +1157,34 @@ static void R_SplitSprite(vissprite_t *sprite) // fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope) { - fixed_t z, floorz = INT32_MIN; - pslope_t *slope, *floorslope = NULL; + boolean isflipped = thing->eflags & MFE_VERTICALFLIP; + fixed_t z, groundz = isflipped ? INT32_MAX : INT32_MIN; + pslope_t *slope, *groundslope = NULL; msecnode_t *node; sector_t *sector; ffloor_t *rover; +#define CHECKZ (isflipped ? z > thing->z+thing->height/2 && z < groundz : z < thing->z+thing->height/2 && z > groundz) for (node = thing->touching_sectorlist; node; node = node->m_sectorlist_next) { sector = node->m_sector; - slope = (sector->heightsec != -1) ? NULL : sector->f_slope; - z = slope ? P_GetZAt(slope, thing->x, thing->y) : ( - (sector->heightsec != -1) ? sectors[sector->heightsec].floorheight : sector->floorheight - ); + slope = sector->heightsec != -1 ? NULL : (isflipped ? sector->c_slope : sector->f_slope); - if (z < thing->z+thing->height/2 && z > floorz) + if (sector->heightsec != -1) + z = isflipped ? sectors[sector->heightsec].ceilingheight : sectors[sector->heightsec].floorheight; + else { - floorz = z; - floorslope = slope; + if (isflipped) + z = sector->c_slope ? P_GetZAt(sector->c_slope, thing->x, thing->y) : sector->ceilingheight; // P_GetSectorCeilingZAt + else + z = sector->f_slope ? P_GetZAt(sector->f_slope, thing->x, thing->y) : sector->floorheight; // P_GetSectorFloorZAt + } + + if CHECKZ + { + groundz = z; + groundslope = slope; } if (sector->ffloors) @@ -1191,24 +1193,30 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope) if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_RENDERPLANES) || (rover->alpha < 90 && !(rover->flags & FF_SWIMMABLE))) continue; - z = *rover->t_slope ? P_GetZAt(*rover->t_slope, thing->x, thing->y) : *rover->topheight; - if (z < thing->z+thing->height/2 && z > floorz) + if (isflipped) + z = *rover->b_slope ? P_GetZAt(*rover->b_slope, thing->x, thing->y) : *rover->bottomheight; // P_GetFFloorBottomZAt + else + z = *rover->t_slope ? P_GetZAt(*rover->t_slope, thing->x, thing->y) : *rover->topheight; // P_GetFFloorTopZAt + + if CHECKZ { - floorz = z; - floorslope = *rover->t_slope; + groundz = z; + groundslope = isflipped ? *rover->b_slope : *rover->t_slope; } } } - if (thing->floorz > floorz + (!floorslope ? 0 : FixedMul(abs(floorslope->zdelta), thing->radius*3/2))) + if (isflipped ? (thing->ceilingz < groundz - (!groundslope ? 0 : FixedMul(abs(groundslope->zdelta), thing->radius*3/2))) + : (thing->floorz > groundz + (!groundslope ? 0 : FixedMul(abs(groundslope->zdelta), thing->radius*3/2)))) { - floorz = thing->floorz; - floorslope = NULL; + groundz = isflipped ? thing->ceilingz : thing->floorz; + groundslope = NULL; } #if 0 // Unfortunately, this drops CEZ2 down to sub-17 FPS on my i7. //#ifdef POLYOBJECTS - // Check polyobjects and see if floorz needs to be altered, for rings only because they don't update floorz + // NOTE: this section was not updated to reflect reverse gravity support + // Check polyobjects and see if groundz needs to be altered, for rings only because they don't update floorz if (thing->type == MT_RING) { INT32 xl, xh, yl, yh, bx, by; @@ -1253,10 +1261,10 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope) // We're inside it! Yess... z = po->lines[0]->backsector->ceilingheight; - if (z < thing->z+thing->height/2 && z > floorz) + if (z < thing->z+thing->height/2 && z > groundz) { - floorz = z; - floorslope = NULL; + groundz = z; + groundslope = NULL; } } plink = (polymaplink_t *)(plink->link.next); @@ -1266,9 +1274,9 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope) #endif if (shadowslope != NULL) - *shadowslope = floorslope; + *shadowslope = groundslope; - return floorz; + return groundz; } static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, fixed_t tx, fixed_t tz) @@ -1279,14 +1287,15 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, INT32 light = 0; fixed_t scalemul; UINT8 trans; fixed_t floordiff; - fixed_t floorz; - pslope_t *floorslope; + fixed_t groundz; + pslope_t *groundslope; + boolean isflipped = thing->eflags & MFE_VERTICALFLIP; - floorz = R_GetShadowZ(thing, &floorslope); + groundz = R_GetShadowZ(thing, &groundslope); - if (abs(floorz-viewz)/tz > 4) return; // Prevent stretchy shadows and possible crashes + if (abs(groundz-viewz)/tz > 4) return; // Prevent stretchy shadows and possible crashes - floordiff = abs(thing->z - floorz); + floordiff = abs((isflipped ? thing->height : 0) + thing->z - groundz); trans = floordiff / (100*FRACUNIT) + 3; if (trans >= 9) return; @@ -1301,41 +1310,41 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, xscale = FixedDiv(projection, tz); yscale = FixedDiv(projectiony, tz); shadowxscale = FixedMul(thing->radius*2, scalemul); - shadowyscale = FixedMul(FixedMul(thing->radius*2, scalemul), FixedDiv(abs(floorz - viewz), tz)); - shadowyscale = min(shadowyscale, shadowxscale) / patch->height; - shadowxscale /= patch->width; + shadowyscale = FixedMul(FixedMul(thing->radius*2, scalemul), FixedDiv(abs(groundz - viewz), tz)); + shadowyscale = min(shadowyscale, shadowxscale) / SHORT(patch->height); + shadowxscale /= SHORT(patch->width); shadowskew = 0; - if (floorslope) + if (groundslope) { // haha let's try some dumb stuff fixed_t xslope, zslope; - angle_t sloperelang = (R_PointToAngle(thing->x, thing->y) - floorslope->xydirection) >> ANGLETOFINESHIFT; + angle_t sloperelang = (R_PointToAngle(thing->x, thing->y) - groundslope->xydirection) >> ANGLETOFINESHIFT; - xslope = FixedMul(FINESINE(sloperelang), floorslope->zdelta); - zslope = FixedMul(FINECOSINE(sloperelang), floorslope->zdelta); + xslope = FixedMul(FINESINE(sloperelang), groundslope->zdelta); + zslope = FixedMul(FINECOSINE(sloperelang), groundslope->zdelta); //CONS_Printf("Shadow is sloped by %d %d\n", xslope, zslope); - if (viewz < floorz) - shadowyscale += FixedMul(FixedMul(thing->radius*2 / patch->height, scalemul), zslope); + if (viewz < groundz) + shadowyscale += FixedMul(FixedMul(thing->radius*2 / SHORT(patch->height), scalemul), zslope); else - shadowyscale -= FixedMul(FixedMul(thing->radius*2 / patch->height, scalemul), zslope); + shadowyscale -= FixedMul(FixedMul(thing->radius*2 / SHORT(patch->height), scalemul), zslope); shadowyscale = abs(shadowyscale); shadowskew = xslope; } - tx -= patch->width * shadowxscale/2; + tx -= SHORT(patch->width) * shadowxscale/2; x1 = (centerxfrac + FixedMul(tx,xscale))>>FRACBITS; if (x1 >= viewwidth) return; - tx += patch->width * shadowxscale; + tx += SHORT(patch->width) * shadowxscale; x2 = ((centerxfrac + FixedMul(tx,xscale))>>FRACBITS); x2--; if (x2 < 0 || x2 <= x1) return; - if (shadowyscale < FRACUNIT/patch->height) return; // fix some crashes? + if (shadowyscale < FRACUNIT/SHORT(patch->height)) return; // fix some crashes? shadow = R_NewVisSprite(); @@ -1347,7 +1356,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, shadow->heightsec = vis->heightsec; shadow->thingheight = FRACUNIT; - shadow->pz = floorz; + shadow->pz = groundz + (isflipped ? -shadow->thingheight : 0); shadow->pzt = shadow->pz + shadow->thingheight; shadow->mobjflags = 0; @@ -1355,8 +1364,8 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, shadow->dispoffset = vis->dispoffset - 5; shadow->gx = thing->x; shadow->gy = thing->y; - shadow->gzt = shadow->pz + patch->height * shadowyscale / 2; - shadow->gz = shadow->gzt - patch->height * shadowyscale; + shadow->gzt = (isflipped ? shadow->pzt : shadow->pz) + SHORT(patch->height) * shadowyscale / 2; + shadow->gz = shadow->gzt - SHORT(patch->height) * shadowyscale; shadow->texturemid = FixedMul(thing->scale, FixedDiv(shadow->gzt - viewz, shadowyscale)); if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES) shadow->texturemid = FixedMul(shadow->texturemid, ((skin_t *)thing->skin)->highresscale); @@ -1386,7 +1395,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, shadow->startfrac = 0; //shadow->xiscale = 0x7ffffff0 / (shadow->xscale/2); - shadow->xiscale = (patch->width<xiscale = (SHORT(patch->width)<x1 > x1) shadow->startfrac += shadow->xiscale*(shadow->x1-x1); @@ -1850,19 +1859,27 @@ static void R_ProjectSprite(mobj_t *thing) vis->transmap = NULL; // specific translucency - if (!cv_translucency.value) - ; // no translucency - else if (thing->flags2 & MF2_SHADOW) // actually only the player should use this (temporary invisibility) - vis->transmap = transtables + ((tr_trans80-1)<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) - 0x10000; + vis->transmap = transtables + ((thing->frame & FF_TRANSMASK) - FF_TRANS10); - if (thing->frame & FF_FULLBRIGHT || thing->flags2 & MF2_SHADOW) - vis->cut |= SC_FULLBRIGHT; - else if (thing->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->fog & 1))) { // full bright: goggles @@ -2054,8 +2071,9 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing) vis->patch = sprframe->lumppat[0]; // 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,9 +2096,6 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel) INT32 lightnum; fixed_t approx_dist, limit_dist; - INT32 splitflags; // check if a mobj has spliscreen flags - boolean split_drawsprite; // used for splitscreen flags - if (rendermode != render_soft) return; @@ -2114,35 +2129,13 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel) { for (thing = sec->thinglist; thing; thing = thing->snext) { - split_drawsprite = false; - - if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW) + if (thing->sprite == SPR_NULL) continue; - splitflags = thing->eflags & (MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4); - - if (r_splitscreen && splitflags) - { - 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; - } - else - split_drawsprite = true; - - if (!split_drawsprite) + 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))) continue; approx_dist = P_AproxDistance(viewx-thing->x, viewy-thing->y); @@ -2158,36 +2151,13 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel) // Draw everything in sector, no checks for (thing = sec->thinglist; thing; thing = thing->snext) { - - split_drawsprite = false; - - if (thing->sprite == SPR_NULL || thing->flags2 & MF2_DONTDRAW) + if (thing->sprite == SPR_NULL) continue; - splitflags = thing->eflags & (MFE_DRAWONLYFORP1|MFE_DRAWONLYFORP2|MFE_DRAWONLYFORP3|MFE_DRAWONLYFORP4); - - if (r_splitscreen && splitflags) - { - 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; - } - else - split_drawsprite = true; - - if (!split_drawsprite) + 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))) continue; R_ProjectSprite(thing); @@ -2891,7 +2861,12 @@ void R_DrawMasked(void) // // ========================================================================== +// We can assume those are tied to skins somewhat, hence why they're defined here. INT32 numskins = 0; +follower_t followers[MAXSKINS]; +// default followers are defined in SOC_FLWR in followers.kart / gfx.kart (depending on what exe this is, at this point) + + skin_t skins[MAXSKINS]; // FIXTHIS: don't work because it must be inistilised before the config load //#define SKINVALUES @@ -2954,6 +2929,19 @@ INT32 R_SkinAvailable(const char *name) return -1; } +// same thing but for followers: +INT32 R_FollowerAvailable(const char *name) +{ + INT32 i; + + for (i = 0; i < numfollowers; i++) + { + if (stricmp(followers[i].skinname,name)==0) + return i; + } + return -1; +} + // network code calls this when a 'skin change' is received boolean SetPlayerSkin(INT32 playernum, const char *skinname) { @@ -2979,13 +2967,37 @@ boolean SetPlayerSkin(INT32 playernum, const char *skinname) return false; } +// Again, same thing but for followers; +boolean SetPlayerFollower(INT32 playernum, const char *skinname) +{ + INT32 i; + player_t *player = &players[playernum]; + + for (i = 0; i < numfollowers; i++) + { + // search in the skin list + if (stricmp(followers[i].skinname, skinname) == 0) + { + SetFollower(playernum, i); + return true; + } + } + + if (P_IsLocalPlayer(player)) + CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found.\n"), skinname); + else if(server || IsPlayerAdmin(consoleplayer)) + CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) follower '%s' not found\n"), playernum, player_names[playernum], skinname); + + SetFollower(playernum, -1); // reminder that -1 is nothing + return false; +} + // Same as SetPlayerSkin, but uses the skin #. // network code calls this when a 'skin change' is received void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) { player_t *player = &players[playernum]; skin_t *skin = &skins[skinnum]; - if (skinnum >= 0 && skinnum < numskins) // Make sure it exists! { player->skin = skinnum; @@ -3016,6 +3028,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) if (player->mo) P_SetScale(player->mo, player->mo->scale); + // for replays: We have changed our skin mid-game; let the game know so it can do the same in the replay! demo_extradata[playernum] |= DXD_SKIN; return; @@ -3028,6 +3041,53 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) SetPlayerSkinByNum(playernum, 0); // not found put the sonic skin } +// you get the drill, now we do the same for followers: +void SetFollower(INT32 playernum, INT32 skinnum) +{ + player_t *player = &players[playernum]; + mobj_t *bub; + mobj_t *tmp; + + player->followerready = true; // we are ready to perform follower related actions in the player thinker, now. + if (skinnum >= -1 && skinnum <= numfollowers) // Make sure it exists! + { + /* + We don't spawn the follower here since it'll be easier to handle all of it in the Player thinker itself. + However, we will despawn it right here if there's any to make it easy for the player thinker to replace it or delete it. + */ + if (player->follower && skinnum != player->followerskin) // this is also called when we change colour so don't respawn the follower unless we changed skins + { + + // Remove follower's possible hnext list (bubble) + bub = player->follower->hnext; + + while (bub && !P_MobjWasRemoved(bub)) + { + tmp = bub->hnext; + P_RemoveMobj(bub); + bub = tmp; + } + + P_RemoveMobj(player->follower); + P_SetTarget(&player->follower, NULL); + } + + player->followerskin = skinnum; + //CONS_Printf("Updated player follower num\n"); + + // for replays: We have changed our follower mid-game; let the game know so it can do the same in the replay! + demo_extradata[playernum] |= DXD_FOLLOWER; + + return; + } + + if (P_IsLocalPlayer(player)) + CONS_Alert(CONS_WARNING, M_GetText("Follower %d not found\n"), skinnum); + else if(server || IsPlayerAdmin(consoleplayer)) + CONS_Alert(CONS_WARNING, "Player %d (%s) follower %d not found\n", playernum, player_names[playernum], skinnum); + SetFollower(playernum, -1); // Not found, then set -1 (nothing) as our follower. +} + // // Add skins from a pwad, each skin preceded by 'S_SKIN' marker // diff --git a/src/r_things.h b/src/r_things.h index 6d0e86425..94bd37656 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -103,6 +103,45 @@ typedef struct } skin_t; extern CV_PossibleValue_t Forceskin_cons_t[]; +// +// for followers. +// +// We'll define these here because they're really just a mobj that'll follow some rules behind a player +// +typedef struct follower_s +{ + char skinname[SKINNAMESIZE+1]; // Skin Name. This is what to refer to when asking the commands anything. + char name[SKINNAMESIZE+1]; // Name. This is used for the menus. We'll just follow the same rules as skins for this. + + UINT8 defaultcolor; // default color for menus. + + fixed_t scale; // Scale relative to the player's. + fixed_t bubblescale; // Bubble scale relative to the player scale. If not set, no bubble will spawn (default) + + // some position shenanigans: + INT32 atangle; // angle the object will be at around the player. The object itself will always face the same direction as the player. + INT32 dist; // distance relative to the player. (In a circle) + INT32 height; // height of the follower, this is mostly important for Z flipping. + INT32 zoffs; // Z offset relative to the player's height. Cannot be negative. + + // movement options + + INT32 horzlag; // Lag for X/Y displacement. Default is 2. Must be > 0 because we divide by this number. + INT32 vertlag; // not Vert from Neptunia lagging, this is for Z displacement lag Default is 6. Must be > 0 because we divide by this number. + INT32 bobamp; // Bob amplitude. Default is 4. + INT32 bobspeed; // Arbitrary modifier for bobbing speed, default is TICRATE*2 (70). + + // from there on out, everything is STATES to allow customization + // these are only set once when the action is performed and are then free to animate however they want. + + INT32 idlestate; // state when the player is at a standstill + INT32 followstate; // state when the player is moving + INT32 hurtstate; // state when the player is being hurt + INT32 winstate; // state when the player has won + INT32 losestate; // state when the player has lost + INT32 hitconfirmstate; // state for hit confirm + INT32 hitconfirmtime; // time to keep the above playing for +} follower_t; // ----------- // NOT SKINS STUFF ! @@ -161,7 +200,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; @@ -199,12 +238,18 @@ typedef struct drawnode_s extern INT32 numskins; extern skin_t skins[MAXSKINS]; +extern INT32 numfollowers; +extern follower_t followers[MAXSKINS]; // again, use the same rules as skins, no reason not to. boolean SetPlayerSkin(INT32 playernum,const char *skinname); void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002 INT32 R_SkinAvailable(const char *name); void R_AddSkins(UINT16 wadnum); +INT32 R_FollowerAvailable(const char *name); +boolean SetPlayerFollower(INT32 playernum,const char *skinname); +void SetFollower(INT32 playernum,INT32 skinnum); + #ifdef DELFILE void R_DelSkins(UINT16 wadnum); #endif diff --git a/src/sdl/Makefile.cfg b/src/sdl/Makefile.cfg index 58c4d0861..b0c591ce2 100644 --- a/src/sdl/Makefile.cfg +++ b/src/sdl/Makefile.cfg @@ -83,6 +83,11 @@ else SDL_LDFLAGS+=-lSDL2_mixer 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 895a21df4..e9377b1c0 100644 --- a/src/sdl/Srb2SDL-vc10.vcxproj +++ b/src/sdl/Srb2SDL-vc10.vcxproj @@ -250,6 +250,7 @@ + @@ -401,6 +402,7 @@ + diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters index 9e5b98375..f197a394b 100644 --- a/src/sdl/Srb2SDL-vc10.vcxproj.filters +++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters @@ -291,6 +291,9 @@ I_Interface + + I_Interface + LUA @@ -663,6 +666,9 @@ I_Interface + + I_Interface + LUA diff --git a/src/sdl/Srb2SDL-vc9.vcproj b/src/sdl/Srb2SDL-vc9.vcproj index 85c2533ea..7c5c523b8 100644 --- a/src/sdl/Srb2SDL-vc9.vcproj +++ b/src/sdl/Srb2SDL-vc9.vcproj @@ -2826,6 +2826,50 @@ RelativePath="..\mserv.h" > + + + + + + + + + + + + + + + + + +typedef void * (*Create_fn)(void); + +struct Link; +struct Thread; + +typedef struct Link * Link; +typedef struct Thread * Thread; + +struct Link +{ + void * data; + Link next; + Link prev; +}; + +struct Thread +{ + I_thread_fn entry; + void * userdata; + + SDL_Thread * thread; +}; + +static Link i_thread_pool; +static Link i_mutex_pool; +static Link i_cond_pool; + +static I_mutex i_thread_pool_mutex; +static I_mutex i_mutex_pool_mutex; +static I_mutex i_cond_pool_mutex; + +static SDL_atomic_t i_threads_running = {1}; + +static Link +Insert_link ( + Link * head, + Link link +){ + link->prev = NULL; + link->next = (*head); + if ((*head)) + (*head)->prev = link; + (*head) = link; + return link; +} + +static void +Free_link ( + Link * head, + Link link +){ + if (link->prev) + link->prev->next = link->next; + else + (*head) = link->next; + + if (link->next) + link->next->prev = link->prev; + + free(link->data); + free(link); +} + +static Link +New_link (void *data) +{ + Link link; + + link = malloc(sizeof *link); + + if (! link) + abort(); + + link->data = data; + + return link; +} + +static void * +Identity ( + Link * pool_anchor, + I_mutex pool_mutex, + + void ** anchor, + + Create_fn create_fn +){ + void * id; + + id = SDL_AtomicGetPtr(anchor); + + if (! id) + { + I_lock_mutex(&pool_mutex); + { + id = SDL_AtomicGetPtr(anchor); + + if (! id) + { + id = (*create_fn)(); + + if (! id) + abort(); + + Insert_link(pool_anchor, New_link(id)); + + SDL_AtomicSetPtr(anchor, id); + } + } + I_unlock_mutex(pool_mutex); + } + + return id; +} + +static int +Worker ( + Link link +){ + Thread th; + + th = link->data; + + (*th->entry)(th->userdata); + + if (SDL_AtomicGet(&i_threads_running)) + { + I_lock_mutex(&i_thread_pool_mutex); + { + if (SDL_AtomicGet(&i_threads_running)) + { + SDL_DetachThread(th->thread); + Free_link(&i_thread_pool, link); + } + } + I_unlock_mutex(i_thread_pool_mutex); + } + + return 0; +} + +void +I_spawn_thread ( + const char * name, + I_thread_fn entry, + void * userdata +){ + Link link; + Thread th; + + th = malloc(sizeof *th); + + if (! th) + abort();/* this is pretty GNU of me */ + + th->entry = entry; + th->userdata = userdata; + + I_lock_mutex(&i_thread_pool_mutex); + { + link = Insert_link(&i_thread_pool, New_link(th)); + + if (SDL_AtomicGet(&i_threads_running)) + { + th->thread = SDL_CreateThread( + (SDL_ThreadFunction)Worker, + name, + link + ); + + if (! th->thread) + abort(); + } + } + I_unlock_mutex(i_thread_pool_mutex); +} + +int +I_thread_is_stopped (void) +{ + return ( ! SDL_AtomicGet(&i_threads_running) ); +} + +void +I_start_threads (void) +{ + i_thread_pool_mutex = SDL_CreateMutex(); + i_mutex_pool_mutex = SDL_CreateMutex(); + i_cond_pool_mutex = SDL_CreateMutex(); + + if (!( + i_thread_pool_mutex && + i_mutex_pool_mutex && + i_cond_pool_mutex + )){ + abort(); + } +} + +void +I_stop_threads (void) +{ + Link link; + Link next; + + Thread th; + SDL_mutex * mutex; + SDL_cond * cond; + + if (i_threads_running.value) + { + /* rely on the good will of thread-san */ + SDL_AtomicSet(&i_threads_running, 0); + + I_lock_mutex(&i_thread_pool_mutex); + { + for ( + link = i_thread_pool; + link; + link = next + ){ + next = link->next; + th = link->data; + + SDL_WaitThread(th->thread, NULL); + + free(th); + free(link); + } + } + I_unlock_mutex(i_thread_pool_mutex); + + for ( + link = i_mutex_pool; + link; + link = next + ){ + next = link->next; + mutex = link->data; + + SDL_DestroyMutex(mutex); + + free(link); + } + + for ( + link = i_cond_pool; + link; + link = next + ){ + next = link->next; + cond = link->data; + + SDL_DestroyCond(cond); + + free(link); + } + + SDL_DestroyMutex(i_thread_pool_mutex); + SDL_DestroyMutex(i_mutex_pool_mutex); + SDL_DestroyMutex(i_cond_pool_mutex); + } +} + +void +I_lock_mutex ( + I_mutex * anchor +){ + SDL_mutex * mutex; + + mutex = Identity( + &i_mutex_pool, + i_mutex_pool_mutex, + anchor, + (Create_fn)SDL_CreateMutex + ); + + if (SDL_LockMutex(mutex) == -1) + abort(); +} + +void +I_unlock_mutex ( + I_mutex id +){ + if (SDL_UnlockMutex(id) == -1) + abort(); +} + +void +I_hold_cond ( + I_cond * cond_anchor, + I_mutex mutex_id +){ + SDL_cond * cond; + + cond = Identity( + &i_cond_pool, + i_cond_pool_mutex, + cond_anchor, + (Create_fn)SDL_CreateCond + ); + + if (SDL_CondWait(cond, mutex_id) == -1) + abort(); +} + +void +I_wake_one_cond ( + I_cond * anchor +){ + SDL_cond * cond; + + cond = Identity( + &i_cond_pool, + i_cond_pool_mutex, + anchor, + (Create_fn)SDL_CreateCond + ); + + if (SDL_CondSignal(cond) == -1) + abort(); +} + +void +I_wake_all_cond ( + I_cond * anchor +){ + SDL_cond * cond; + + cond = Identity( + &i_cond_pool, + i_cond_pool_mutex, + anchor, + (Create_fn)SDL_CreateCond + ); + + if (SDL_CondBroadcast(cond) == -1) + abort(); +} diff --git a/src/sounds.c b/src/sounds.c index b64dda5cb..5581ade45 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -820,6 +820,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 3d6e4f007..d9b303044 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -897,6 +897,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 4b5bc7d09..5105172d2 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -28,7 +28,7 @@ #include "m_menu.h" #include "m_cheat.h" #include "p_setup.h" // NiGHTS grading -#include "k_kart.h" // SRB2kart +#include "k_hud.h" // SRB2kart //random index #include "m_random.h" @@ -699,7 +699,7 @@ static inline void ST_drawRings(void) // SRB2kart - unused. /* static void ST_drawLives(void) // SRB2kart - unused. { - const INT32 v_splitflag = (splitscreen && stplyr == &players[displayplayers[0]] ? V_SPLITSCREEN : 0); + const INT32 v_splitflag = V_SPLITSCREEN; if (!stplyr->skincolor) return; // Just joined a server, skin isn't loaded yet! @@ -1920,11 +1920,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]); } } } @@ -2000,16 +2000,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/v_video.c b/src/v_video.c index 0f6479205..3771419d5 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -26,6 +26,8 @@ #include "m_random.h" #include "doomstat.h" +#include "k_hud.h" + #ifdef HWRENDER #include "hardware/hw_main.h" #endif @@ -608,12 +610,6 @@ void V_DrawFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_t y -= offsety; } - if (scrn & V_SPLITSCREEN) - y += (BASEVIDHEIGHT/2)<width)<>FRACBITS == BASEVIDWIDTH - && y == 0 && FixedMul(SHORT(patch->height)<>FRACBITS == BASEVIDHEIGHT) - { - column = (const column_t *)((const UINT8 *)(patch) + LONG(patch->columnofs[0])); - source = (const UINT8 *)(column) + 3; - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (column->topdelta == 0xff ? 31 : source[0])); - }*/ - - 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_SPLITSCREEN|V_SNAPTOTOP)) == (V_SPLITSCREEN|V_SNAPTOTOP)) - y += (vid.height/2 - (BASEVIDHEIGHT/2 * dupy)); - else 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; @@ -923,31 +888,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 (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) return; // off the screen + if (x < 0) { w += x; @@ -1151,27 +1097,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) @@ -1879,6 +1805,7 @@ void V_DrawStringScaled( chw <<= FRACBITS; spacew <<= FRACBITS; + lfh <<= FRACBITS; #define Mul( id, scale ) ( id = FixedMul (scale, id) ) Mul (chw, scale); diff --git a/src/v_video.h b/src/v_video.h index 2066d1399..95b3f16fb 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -129,9 +129,9 @@ extern UINT8 hudtrans; #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)<