diff --git a/src/k_menu.h b/src/k_menu.h index 137e8c562..0c59bfb19 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -183,6 +183,12 @@ extern menu_t PLAY_TimeAttackDef; extern menuitem_t PLAY_MP_OptSelect[]; extern menu_t PLAY_MP_OptSelectDef; +extern menuitem_t PLAY_MP_Host[]; +extern menu_t PLAY_MP_HostDef; + +extern menuitem_t PLAY_MP_JoinIP[]; +extern menu_t PLAY_MP_JoinIPDef; + extern menuitem_t PLAY_MP_RoomSelect[]; extern menu_t PLAY_MP_RoomSelectDef; @@ -331,7 +337,8 @@ extern struct cupgrid_s { SINT8 pageno; UINT8 numpages; tic_t previewanim; - boolean grandprix; // Setup grand prix server after picking + boolean grandprix; // Setup grand prix server after picking + boolean netgame; // Start the game in an actual server } cupgrid; extern struct levellist_s { @@ -342,6 +349,7 @@ extern struct levellist_s { INT16 choosemap; UINT8 newgametype; boolean timeattack; // Setup time attack menu after picking + boolean netgame; // Start the game in an actual server } levellist; boolean M_CanShowLevelInList(INT16 mapnum, UINT8 gt); @@ -361,6 +369,9 @@ void M_LevelSelectTick(void); extern struct mpmenu_s { UINT8 modechoice; + INT16 modewinextend[3][3]; // Used to "extend" the options in the mode select screen. + // format for each option: {extended?, max extension, # lines extended} + // See M_OptSelectTick, it'll make more sense there. Sorry if this is a bit of a mess! UINT8 room; @@ -371,6 +382,18 @@ extern struct mpmenu_s { void M_MPOptSelect(INT32 choice); void M_MPOptSelectInit(INT32 choice); void M_MPOptSelectTick(void); +boolean M_MPResetOpts(void); +consvar_t cv_dummygametype; // lazy hack to allow us to select the GT on the server host submenu +consvar_t cv_dummyip; // I HAVE + // HAVE YOUR IP ADDRESS (This just the hack Cvar we'll type into and then it apends itself to "connect" in the console for IP join) + +// MP Host +void M_MPHostInit(INT32 choice); +void M_MPSetupNetgameMapSelect(INT32 choice); + +// MP join by IP +void M_MPJoinIPInit(INT32 choice); +void M_JoinIP(void); // Server browser room selection void M_MPRoomSelect(INT32 choice); @@ -414,6 +437,8 @@ void M_DrawTimeAttack(void); // Multiplayer menu stuff void M_DrawMPOptSelect(void); +void M_DrawMPHost(void); +void M_DrawMPJoinIP(void); void M_DrawMPRoomSelect(void); // Replay Playback diff --git a/src/k_menudef.c b/src/k_menudef.c index 0f4de555b..987bd6e26 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -176,10 +176,10 @@ menuitem_t PLAY_MP_OptSelect[] = { //{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, M_MPOptSelect, 0, 0}, {IT_STRING | IT_CALL, "Host Game", "Start your own online game!", - NULL, NULL, 0, 0}, + NULL, M_MPHostInit, 0, 0}, {IT_STRING | IT_CALL, "Join by IP", "Join an online game by its IP address.", - NULL, NULL, 0, 0}, + NULL, M_MPJoinIPInit, 0, 0}, {IT_STRING | IT_CALL, "Server Browser", "Search for game servers to play in.", NULL, M_MPRoomSelectInit, 0, 0}, @@ -191,12 +191,69 @@ menu_t PLAY_MP_OptSelectDef = { 0, PLAY_MP_OptSelect, 0, 0, - 0, 0, + -1, 1, M_DrawMPOptSelect, M_MPOptSelectTick, NULL }; +// MULTIPLAYER HOST SCREEN +menuitem_t PLAY_MP_Host[] = +{ + //{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, M_MPOptSelect, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_STRING, "Server Name", "Display name for your game online. Other players will see this.", + NULL, &cv_servername, 0, 0}, + + {IT_STRING | IT_CVAR, "Public Server", "Display or not your game in the Server Browser for other players.", + NULL, &cv_advertise, 0, 0}, + + {IT_STRING | IT_CVAR, "Max. Players", "Set how many players can play at once. Others will spectate.", + NULL, &cv_ingamecap, 0, 0}, + + {IT_STRING | IT_CVAR, "Gamemode", "Are we racing? Or perhaps battling?", + NULL, &cv_dummygametype, 0, 0}, + + {IT_STRING | IT_CALL, "GO", "Select a map with the currently selected gamemode", + NULL, M_MPSetupNetgameMapSelect, 0, 0}, + +}; + +menu_t PLAY_MP_HostDef = { + sizeof (PLAY_MP_Host) / sizeof (menuitem_t), + &PLAY_MP_OptSelectDef, + 0, + PLAY_MP_Host, + 0, 0, + -1, 1, // 1 frame transition.... This is really just because I don't want the black fade when we press esc, hehe + M_DrawMPHost, + M_MPOptSelectTick, // This handles the unfolding options + M_MPResetOpts +}; + +// MULTIPLAYER JOIN BY IP +menuitem_t PLAY_MP_JoinIP[] = +{ + //{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, M_MPOptSelect, 0, 0}, + + {IT_STRING | IT_CVAR | IT_CV_STRING, "Address: ", "Type the IPv4 address of the server you wish to connect to.", + NULL, &cv_dummyip, 0, 0}, + + {IT_STRING | IT_CALL, "GO", "Select a map with the currently selected gamemode", + NULL, M_JoinIP, 0, 0}, +}; + +menu_t PLAY_MP_JoinIPDef = { + sizeof (PLAY_MP_JoinIP) / sizeof (menuitem_t), + &PLAY_MP_OptSelectDef, + 0, + PLAY_MP_JoinIP, + 0, 0, + -1, 1, // 1 frame transition.... This is really just because I don't want the black fade when we press esc, hehe + M_DrawMPJoinIP, + M_MPOptSelectTick, // This handles the unfolding options + M_MPResetOpts +}; // MULTIPLAYER ROOM SELECT (CORE / MODDED) menuitem_t PLAY_MP_RoomSelect[] = diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 8eb55b040..3c178b654 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1306,23 +1306,19 @@ void M_DrawTimeAttack(void) } // This draws the options of a given menu in a fashion specific to the multiplayer option select screen (host game / server browser etc) -// TODO: Add 2nd argument that lets us "expand" a given option via an array +// First argument is the menu to draw the options from, the 2nd one is a table that contains which option is to be "extendded" +// Format for each option: {extended? (only used by the ticker), max extension (likewise), # of lines to extend} -static void M_MPOptDrawer(menu_t *m) +// NOTE: This is pretty rigid and only intended for use with the multiplayer options menu which has *3* choices. + +static void M_MPOptDrawer(menu_t *m, INT16 extend[3][3]) { // This is a copypaste of the generic gamemode menu code with a few changes. // TODO: Allow specific options to "expand" into smaller ones. patch_t *buttback = W_CachePatchName("M_PLAT2", PU_CACHE); - - UINT8 n = m->numitems-1; INT32 i, x = 142, y = 32; // Dirty magic numbers for now but they work out. - if (menutransition.tics) - { - x += 24 * menutransition.tics; - } - for (i = 0; i < m->numitems; i++) { @@ -1331,8 +1327,9 @@ static void M_MPOptDrawer(menu_t *m) case IT_STRING: { UINT8 *colormap = NULL; + INT16 j = 0; - if (i == itemOn) + if ((currentMenu == m && i == itemOn) || extend[i][0]) // Selected / unfolded { colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); } @@ -1341,28 +1338,184 @@ static void M_MPOptDrawer(menu_t *m) colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE); } - V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, buttback, colormap); + if (extend[i][2]) + { + for (j=0; j < extend[i][2]/2; j++) + { + // Draw rectangles that look like the current selected item starting from the top of the actual selection graphic and going up to where it's supposed to go. + // With colour 169 (that's the index of the shade of black the plague colourization gives us. ...No I don't like using a magic number either. + V_DrawFill(x + (extend[i][2]/2) - j - (buttback->width/2), (y + extend[i][2]) - (2*j), 225, 2, 169); + } + } + V_DrawFixedPatch((x + (extend[i][2]/2)) *FRACUNIT, (y + extend[i][2])*FRACUNIT, FRACUNIT, 0, buttback, colormap); V_DrawCenteredGamemodeString(x, y - 3, V_ALLOWLOWERCASE, colormap, m->menuitems[i].text); } break; } x += GM_XOFFSET; - y += GM_YOFFSET; + y += GM_YOFFSET + extend[i][2]; } } +// Draws the EGGA CHANNEL background. +static void M_DrawEggaChannel(void) +{ + patch_t *background = W_CachePatchName("M_EGGACH", PU_CACHE); + + V_DrawFill(0, 0, 999, 999, 25); + V_DrawFixedPatch(160<numitems; i++) + { + + if (i == currentMenu->numitems-1) + { + + UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE); + if (i == itemOn) + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); + + // Ideally we'd calculate this but it's not worth it for a 1-off menu probably..... + V_DrawFixedPatch(212<width/2), 100 -3, V_ALLOWLOWERCASE, colormap, currentMenu->menuitems[i].text); + } + else + { + switch (currentMenu->menuitems[i].status & IT_DISPLAY) + { + case IT_STRING: + { + V_DrawString(xp, yp, V_ALLOWLOWERCASE | (i == itemOn ? highlightflags : 0), currentMenu->menuitems[i].text); + + // Cvar specific handling + switch (currentMenu->menuitems[i].status & IT_TYPE) + { + case IT_CVAR: + { + consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction; + switch (currentMenu->menuitems[i].status & IT_CVARTYPE) + { + case IT_CV_STRING: + V_DrawThinString(xp + 96, yp, V_ALLOWLOWERCASE, cv->string); + if (skullAnimCounter < 4 && i == itemOn) + V_DrawCharacter(xp + 93 + V_ThinStringWidth(cv->string, 0), yp +1, '_' | 0x80, false); + + break; + + default: + w = V_StringWidth(cv->string, 0); + V_DrawString(xp + 138 - w, yp, ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string); + if (i == itemOn) + { + V_DrawCharacter(xp + 138 - 10 - w - (skullAnimCounter/5), yp, '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(xp + 138 + 2 + (skullAnimCounter/5), yp, '\x1D' | highlightflags, false); // right arrow + } + break; + } + break; + } + } + + xp += 5; + yp += 11; + + break; + } + break; + } + + } + + } +} + +// Multiplayer mode option select: JOIN BY IP +// Once again we'll copypaste 1 bit of the generic menu handler we used for hosting but we only need it for IT_CV_STRING since that's all we got :) +// (I don't like duplicating code but I rather this than some horrible all-in-one function with too many options...) +void M_DrawMPJoinIP(void) +{ + + patch_t *gobutt = W_CachePatchName("M_BUTTGO", PU_CACHE); // I'm very mature + INT32 xp = 70, yp = 100, i = 0; // Starting position for the text drawing. + M_DrawMPOptSelect(); // Draw the Multiplayer option select menu first + + // Now draw our host options... + for (i = 0; i < currentMenu->numitems; i++) + { + + if (i == currentMenu->numitems-1) + { + + UINT8 *colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_MOSS, GTC_CACHE); + if (i == itemOn) + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_CACHE); + + // Ideally we'd calculate this but it's not worth it for a 1-off menu probably..... + V_DrawFixedPatch(219<width/2), 114 -3, V_ALLOWLOWERCASE, colormap, currentMenu->menuitems[i].text); + } + else + { + switch (currentMenu->menuitems[i].status & IT_DISPLAY) + { + case IT_STRING: + { + V_DrawString(xp, yp, V_ALLOWLOWERCASE | (i == itemOn ? highlightflags : 0), currentMenu->menuitems[i].text); + + // Cvar specific handling + switch (currentMenu->menuitems[i].status & IT_TYPE) + { + case IT_CVAR: + { + consvar_t *cv = (consvar_t *)currentMenu->menuitems[i].itemaction; + switch (currentMenu->menuitems[i].status & IT_CVARTYPE) + { + case IT_CV_STRING: + V_DrawThinString(xp + 65, yp-1, V_ALLOWLOWERCASE, cv->string); + if (skullAnimCounter < 4 && i == itemOn) + V_DrawCharacter(xp + 65 + V_ThinStringWidth(cv->string, 0), yp, '_' | 0x80, false); + + break; + + default: + break; + } + break; + } + } + + xp += 5; + yp += 11; + + break; + } + break; + } + + } + + } } // Multiplayer room select diff --git a/src/k_menufunc.c b/src/k_menufunc.c index bd1890cf5..ae756c669 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -164,12 +164,15 @@ static CV_PossibleValue_t dummyteam_cons_t[] = {{0, "Spectator"}, {1, "Red"}, {2 static CV_PossibleValue_t dummyspectate_cons_t[] = {{0, "Spectator"}, {1, "Playing"}, {0, NULL}}; static CV_PossibleValue_t dummyscramble_cons_t[] = {{0, "Random"}, {1, "Points"}, {0, NULL}}; static CV_PossibleValue_t dummystaff_cons_t[] = {{0, "MIN"}, {100, "MAX"}, {0, NULL}}; +static CV_PossibleValue_t dummygametype_cons_t[] = {{0, "Race"}, {1, "Battle"}, {0, NULL}}; static consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); static consvar_t cv_dummyteam = CVAR_INIT ("dummyteam", "Spectator", CV_HIDDEN, dummyteam_cons_t, NULL); static consvar_t cv_dummyspectate = CVAR_INIT ("dummyspectate", "Spectator", CV_HIDDEN, dummyspectate_cons_t, NULL); static consvar_t cv_dummyscramble = CVAR_INIT ("dummyscramble", "Random", CV_HIDDEN, dummyscramble_cons_t, NULL); static consvar_t cv_dummystaff = CVAR_INIT ("dummystaff", "0", CV_HIDDEN|CV_CALL, dummystaff_cons_t, Dummystaff_OnChange); +consvar_t cv_dummygametype = CVAR_INIT ("dummygametype", "Race", CV_HIDDEN, dummygametype_cons_t, NULL); +consvar_t cv_dummyip = CVAR_INIT ("dummyip", "", CV_HIDDEN, NULL, NULL); // ========================================================================== // CVAR ONCHANGE EVENTS GO HERE @@ -1559,6 +1562,8 @@ void M_Init(void) CV_RegisterVar(&cv_dummyspectate); CV_RegisterVar(&cv_dummyscramble); CV_RegisterVar(&cv_dummystaff); + CV_RegisterVar(&cv_dummygametype); + CV_RegisterVar(&cv_dummyip); M_UpdateMenuBGImage(true); @@ -2286,30 +2291,10 @@ static void M_LevelSelectScrollDest(void) levellist.dest = (6*m)-3; } -void M_LevelSelectInit(INT32 choice) +// Builds the level list we'll be using from the gametype we're choosing and send us to the apropriate menu. +static void M_LevelListFromGametype(INT16 gt) { - (void)choice; - - switch (currentMenu->menuitems[itemOn].mvar1) - { - case 0: - cupgrid.grandprix = false; - levellist.timeattack = false; - break; - case 1: - cupgrid.grandprix = false; - levellist.timeattack = true; - break; - case 2: - cupgrid.grandprix = true; - levellist.timeattack = false; - break; - default: - CONS_Alert(CONS_WARNING, "Bad level select init\n"); - return; - } - - levellist.newgametype = currentMenu->menuitems[itemOn].mvar2; + levellist.newgametype = gt; PLAY_CupSelectDef.prevMenu = currentMenu; // Obviously go to Cup Select in gametypes that have cups. @@ -2351,6 +2336,41 @@ void M_LevelSelectInit(INT32 choice) PLAY_LevelSelectDef.prevMenu = currentMenu; M_SetupNextMenu(&PLAY_LevelSelectDef, false); + +} + +// Init level select for use in local play using the last choice we made. +// For the online MP version, see M_MPSetupNetgameMapSelect() + +void M_LevelSelectInit(INT32 choice) +{ + (void)choice; + + levellist.netgame = false; // Make sure this is reset as we'll only be using this function for offline games! + cupgrid.netgame = false; // Ditto + + switch (currentMenu->menuitems[itemOn].mvar1) + { + case 0: + cupgrid.grandprix = false; + levellist.timeattack = false; + break; + case 1: + cupgrid.grandprix = false; + levellist.timeattack = true; + break; + case 2: + cupgrid.grandprix = true; + levellist.timeattack = false; + break; + default: + CONS_Alert(CONS_WARNING, "Bad level select init\n"); + return; + } + + levellist.newgametype = currentMenu->menuitems[itemOn].mvar2; + + M_LevelListFromGametype(levellist.newgametype); } void M_CupSelectHandler(INT32 choice) @@ -2433,6 +2453,7 @@ void M_CupSelectHandler(INT32 choice) paused = false; SV_StartSinglePlayerServer(); multiplayer = true; // yeah, SV_StartSinglePlayerServer clobbers this... + netgame = levellist.netgame; // ^ ditto. D_MapChange( grandprixinfo.cup->levellist[0] + 1, GT_RACE, @@ -2567,6 +2588,8 @@ void M_LevelSelectHandler(INT32 choice) paused = false; SV_StartSinglePlayerServer(); multiplayer = true; // yeah, SV_StartSinglePlayerServer clobbers this... + netgame = levellist.netgame; // ^ ditto. + D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); M_ClearMenus(true); @@ -2609,22 +2632,121 @@ void M_LevelSelectTick(void) struct mpmenu_s mpmenu; // MULTIPLAYER OPTION SELECT -void M_MPOptSelect(INT32 choice) -{ +// Use this as a quit routine within the HOST GAME and JOIN BY IP "sub" menus +boolean M_MPResetOpts(void) +{ + UINT8 i = 0; + + for (; i < 3; i++) + mpmenu.modewinextend[i][0] = 0; // Undo this + + return true; } -void M_MPOptSelectInit(INT32 chocie) +void M_MPOptSelectInit(INT32 choice) { + INT16 arrcpy[3][3] = {{0,68,0}, {0,48,0}, {0,12,0}}; + UINT8 i = 0, j = 0; // To copy the array into the struct + + (void)choice; + mpmenu.modechoice = 0; mpmenu.ticker = 0; + for (; i < 3; i++) + for (j = 0; j < 3; j++) + mpmenu.modewinextend[i][j] = arrcpy[i][j]; // I miss Lua already + M_SetupNextMenu(&PLAY_MP_OptSelectDef, false); } void M_MPOptSelectTick(void) { + UINT8 i = 0; + // 3 Because we have 3 options in the menu + for (; i < 3; i++) + { + if (mpmenu.modewinextend[i][0]) + mpmenu.modewinextend[i][2] += 8; + else + mpmenu.modewinextend[i][2] -= 8; + + mpmenu.modewinextend[i][2] = min(mpmenu.modewinextend[i][1], max(0, mpmenu.modewinextend[i][2])); + //CONS_Printf("%d - %d,%d,%d\n", i, mpmenu.modewinextend[i][0], mpmenu.modewinextend[i][1], mpmenu.modewinextend[i][2]); + } +} + + +// MULTIPLAYER HOST +void M_MPHostInit(INT32 choice) +{ + + (void)choice; + mpmenu.modewinextend[0][0] = 1; + M_SetupNextMenu(&PLAY_MP_HostDef, true); +} + +void M_MPSetupNetgameMapSelect(INT32 choice) +{ + + INT16 gt = GT_RACE; + (void)choice; + + levellist.netgame = true; // Yep, we'll be starting a netgame. + cupgrid.netgame = true; // Ditto + levellist.timeattack = false; // Make sure we reset those + cupgrid.grandprix = false; // Ditto + + // In case we ever want to add new gamemodes there somehow, have at it! + switch (cv_dummygametype.value) + { + case 1: // Battle + { + gt = GT_BATTLE; + break; + } + + default: + { + gt = GT_RACE; + break; + } + } + + M_LevelListFromGametype(gt); // Setup the level select. + // (This will also automatically send us to the apropriate menu) +} + +// MULTIPLAYER JOIN BY IP +void M_MPJoinIPInit(INT32 choice) +{ + + (void)choice; + mpmenu.modewinextend[1][0] = 1; + M_SetupNextMenu(&PLAY_MP_JoinIPDef, true); +} + +// Attempts to join a given IP from the menu. +void M_JoinIP(void) +{ + if (*(cv_dummyip.string) == '\0') // Jack shit + { + M_StartMessage("Please specify an address.\n", NULL, MM_NOTHING); + return; + } + + COM_BufAddText(va("connect \"%s\"\n", cv_dummyip.string)); + M_ClearMenus(true); + + // A little "please wait" message. + M_DrawTextBox(56, BASEVIDHEIGHT/2-12, 24, 2); + V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Connecting to server..."); + I_OsPolling(); + I_UpdateNoBlit(); + if (rendermode == render_soft) + I_FinishUpdate(); // page flip or blit buffer } // MULTIPLAYER ROOM SELECT MENU @@ -2664,6 +2786,7 @@ void M_MPRoomSelectTick(void) void M_MPRoomSelectInit(INT32 choice) { + (void)choice; mpmenu.room = 0; mpmenu.ticker = 0;