diff --git a/src/deh_soc.c b/src/deh_soc.c index 72c76d406..a1586cf2a 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3505,6 +3505,7 @@ void readfollower(MYFILE *f) followers[numfollowers].bobamp = 4; followers[numfollowers].hitconfirmtime = TICRATE; followers[numfollowers].defaultcolor = SKINCOLOR_GREEN; + strcpy(followers[numfollowers].icon, "M_NORANK"); do { @@ -3538,6 +3539,11 @@ void readfollower(MYFILE *f) strcpy(followers[numfollowers].name, word2); nameset = true; } + else if (fastcmp(word, "ICON")) + { + strcpy(followers[numfollowers].icon, word2); + nameset = true; + } else if (fastcmp(word, "DEFAULTCOLOR")) { followers[numfollowers].defaultcolor = (UINT16)get_number(word2); diff --git a/src/k_menu.h b/src/k_menu.h index 4d35962e5..64162ae87 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -493,6 +493,8 @@ typedef enum CSSTEP_CHARS, CSSTEP_ALTS, CSSTEP_COLORS, + CSSTEP_FOLLOWER, + CSSTEP_FOLLOWERCOLORS, CSSTEP_READY } setup_mdepth_t; @@ -506,6 +508,13 @@ typedef struct setup_player_s UINT8 delay; UINT8 color; UINT8 mdepth; + + INT32 followern; + INT16 followercolor; + tic_t follower_tics; + tic_t follower_timer; + UINT8 follower_frame; + state_t *follower_state; } setup_player_t; extern setup_player_t setup_player[MAXSPLITSCREENPLAYERS]; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index e1b2731b0..bbff97bba 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -823,6 +823,8 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) if (p->mdepth == CSSTEP_ALTS) numoptions = setup_chargrid[p->gridx][p->gridy].numskins; + else if (p->mdepth == CSSTEP_FOLLOWERCOLORS) + numoptions = numskincolors-1 +2; else numoptions = numskincolors-1; @@ -857,6 +859,55 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y) cx -= (SHORT(patch->width) << FRACBITS) >> 1; cy -= (SHORT(patch->height) << FRACBITS) >> 1; } + + else if (p->mdepth == CSSTEP_FOLLOWERCOLORS) + { + INT16 diff; + UINT16 col; + + n = (p->followercolor-1) + numoptions/2; + + if (subtract) + n -= ((i+1)/2); + else + n += ((i+1)/2); + + n %= numoptions; + n++; + + if (!n) + continue; + + col = (unsigned)(n); + switch (col) + { + case FOLLOWERCOLOR_MATCH: // "Match" + col = p->color; + break; + case FOLLOWERCOLOR_OPPOSITE: // "Opposite" + col = skincolors[p->color].invcolor; + break; + } + + colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + + if (n > p->followercolor) + diff = n - p->followercolor; + else + diff = p->followercolor - n; + + if (diff == 0) + patch = W_CachePatchName("COLORSP2", PU_CACHE); + else if (abs(diff) < 25) + patch = W_CachePatchName("COLORSP1", PU_CACHE); + else + patch = W_CachePatchName("COLORSP0", PU_CACHE); + + radius = 28<width) << FRACBITS; + + cx -= (SHORT(patch->width) << FRACBITS) >> 1; + } else { INT16 diff; @@ -949,6 +1000,164 @@ static boolean M_DrawCharacterSprite(INT16 x, INT16 y, SINT8 skin, INT32 addflag return true; } +// Draws the follower list. +static void M_DrawFollowerList(setup_player_t *p, UINT8 num) +{ + INT16 x = 82; + INT16 y = 3; + INT32 cf = p->followern; + UINT8 i; + UINT8 *colormap = NULL; + + if (num & 1) + x = 172; + + if (num >= 2) + y = 176; + + // this places it at the bottom of the card! + if (optionsmenu.profile) + { + x = 35; + y = 143; + } + + // Start 1 follower below. + cf--; + if (cf < -1) + cf = numfollowers-1; + + for (i = 0; i < 3; i++) + { + patch_t *pp = NULL; + follower_t fl = followers[cf]; + + if (W_LumpExists(fl.icon) && cf >= 0) + { + UINT16 col = (unsigned)p->followercolor; + pp = W_CachePatchName(fl.icon, PU_CACHE); + + switch (col) + { + case FOLLOWERCOLOR_MATCH: // "Match" + col = p->color; + break; + case FOLLOWERCOLOR_OPPOSITE: // "Opposite" + col = skincolors[p->color].invcolor; + break; + } + + colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + V_DrawMappedPatch(x, y, 0, pp, colormap); + if (i == 1) + V_DrawMappedPatch(x, y, 0, W_CachePatchName(va("K_CHILI%d", p->follower_timer%16 /2 +1), PU_CACHE), NULL); + + } + // No patch.... + else + { + V_DrawFill(x, y, 16, 16, 0); + + if (i == 1) + V_DrawMappedPatch(x, y, 0, W_CachePatchName(va("K_CHILI%d", p->follower_timer%16 /2 +1), PU_CACHE), NULL); + + if (cf >= 0) + V_DrawString(x, y, 0, va("%d\n", cf)); + else + V_DrawMappedPatch(x-4, y-3, 0, W_CachePatchName("K_NOBLNS", PU_CACHE), NULL); + + } + + cf++; + if (cf > numfollowers-1) + cf = -1; + + x += 23; + } +} + +// Returns false is the follower shouldn't be rendered. +// 'num' can be used to directly specify the follower number, but doing this will not animate it. +// if a setup_player_t is specified instead, its data will be used to animate the follower sprite. +static boolean M_DrawFollowerSprite(INT16 x, INT16 y, INT32 num, INT32 addflags, UINT16 color, setup_player_t *p) +{ + + spritedef_t *sprdef; + spriteframe_t *sprframe; + patch_t *patch; + INT32 followernum; + state_t *usestate; + UINT32 useframe; + follower_t fl; + UINT8 *colormap = NULL; + + if (p != NULL) + followernum = p->followern; + else + followernum = num; + + // Don't draw if we're outta bounds. + if (followernum < 0 || followernum > numfollowers-1) + return false; + + fl = followers[followernum]; + + if (p != NULL) + { + usestate = p->follower_state; + useframe = p->follower_frame; + } + else + { + usestate = &states[followers[followernum].followstate]; + useframe = usestate->frame & FF_FRAMEMASK; + } + + sprdef = &sprites[usestate->sprite]; + + // draw the follower + + if (useframe >= sprdef->numframes) + useframe = 0; // frame doesn't exist, we went beyond it... what? + + sprframe = &sprdef->spriteframes[useframe]; + patch = W_CachePatchNum(sprframe->lumppat[1], PU_CACHE); + + if (sprframe->flip & 2) // Only for first sprite + { + if (addflags & V_FLIP) + addflags &= ~V_FLIP; + else + addflags |= V_FLIP; // This sprite is left/right flipped! + } + + fixed_t sine = 0; + + if (p != NULL) + { + const fixed_t pi = (22<follower_timer)>>ANGLETOFINESHIFT) & FINEMASK); + color = p->followercolor; + + switch (color) + { + case FOLLOWERCOLOR_MATCH: // "Match" + color = p->color; + break; + case FOLLOWERCOLOR_OPPOSITE: // "Opposite" + color = skincolors[p->color].invcolor; + break; + default: + break; + } + } + + colormap = R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE); + V_DrawFixedPatch((x)*FRACUNIT, (y-12)*FRACUNIT+sine, fl.scale, addflags, patch, colormap); + + return true; +} + static void M_DrawCharSelectSprite(UINT8 num, INT16 x, INT16 y) { setup_player_t *p = &setup_player[num]; @@ -983,7 +1192,15 @@ static void M_DrawCharSelectPreview(UINT8 num) { M_DrawCharSelectSprite(num, x+32, y+75); - if (p->mdepth == CSSTEP_ALTS || p->mdepth == CSSTEP_COLORS) + if (p->mdepth >= CSSTEP_FOLLOWER) + { + M_DrawFollowerSprite(x+16, y+75, -1, !(num & 1) ? V_FLIP : 0, 0, p); + + if (p->mdepth == CSSTEP_FOLLOWER) + M_DrawFollowerList(p, num); + } + + if (p->mdepth == CSSTEP_ALTS || p->mdepth == CSSTEP_COLORS || p->mdepth == CSSTEP_FOLLOWERCOLORS) { M_DrawCharSelectCircle(p, x+32, y+64); } @@ -1175,21 +1392,63 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p) // check what setup_player is doing in priority. if (sp->mdepth >= CSSTEP_CHARS) { - skinnum = setup_chargrid[sp->gridx][sp->gridy].skinlist[sp->clonenum]; + skinnum = setup_chargrid[sp->gridx][sp->gridy].skinlist[sp->clonenum]; if (M_DrawCharacterSprite(x-22, y+119, skinnum, V_FLIP, colormap)) V_DrawMappedPatch(x+14, y+66, 0, faceprefix[skinnum][FACE_RANK], colormap); - if (sp->mdepth == CSSTEP_ALTS || sp->mdepth == CSSTEP_COLORS) + if (M_DrawFollowerSprite(x-44 +12, y+119, 0, V_FLIP, 0, sp)) + { + UINT16 col = (unsigned)p->followercolor; + patch_t *ico = W_CachePatchName(followers[sp->followern].icon, PU_CACHE); + UINT8 *fcolormap; + + switch (col) + { + case FOLLOWERCOLOR_MATCH: // "Match" + col = sp->color; + break; + case FOLLOWERCOLOR_OPPOSITE: // "Opposite" + col = skincolors[sp->color].invcolor; + break; + } + + fcolormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + V_DrawMappedPatch(x+14+18, y+66, 0, ico, fcolormap); + } + + if (sp->mdepth == CSSTEP_ALTS || sp->mdepth == CSSTEP_COLORS || sp->mdepth == CSSTEP_FOLLOWERCOLORS) { M_DrawCharSelectCircle(sp, x-22, y+104); } } else if (skinnum > -1) // otherwise, read from profile. { + + UINT16 col = (unsigned)p->followercolor; + UINT8 fln = R_FollowerAvailable(p->follower); + if (M_DrawCharacterSprite(x-22, y+119, skinnum, V_FLIP, colormap)) V_DrawMappedPatch(x+14, y+66, 0, faceprefix[skinnum][FACE_RANK], colormap); + + switch (col) + { + case FOLLOWERCOLOR_MATCH: // "Match" + col = p->color; + break; + case FOLLOWERCOLOR_OPPOSITE: // "Opposite" + col = skincolors[p->color].invcolor; + break; + } + + + if (M_DrawFollowerSprite(x-44 +12, y+119, fln, V_FLIP, col, NULL)) + { + patch_t *ico = W_CachePatchName(followers[fln].icon, PU_CACHE); + UINT8 *fcolormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE); + V_DrawMappedPatch(x+14+18, y+66, 0, ico, fcolormap); + } } V_DrawCenteredGamemodeString(x, y+24, 0, 0, pname); @@ -1287,8 +1546,13 @@ void M_DrawCharacterSelect(void) if (optionsmenu.profile == NULL) M_DrawCharSelectPreview(i); else if (i == 0) + { M_DrawProfileCard(optionsmenu.optx, optionsmenu.opty, false, optionsmenu.profile); + if (setup_player[0].mdepth == CSSTEP_FOLLOWER) + M_DrawFollowerList(&setup_player[0], 0); + } + if (i >= setup_numplayers) continue; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index c90abed0f..65b5260f1 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -2027,6 +2027,11 @@ static void M_SetupProfileGridPos(setup_player_t *p) profile_t *pr = PR_GetProfile(p->profilen); INT32 i; + // While we're here, read follower values. + p->followern = R_FollowerAvailable(pr->follower); + p->followercolor = pr->followercolor; + + // Now position the grid for skin for (i = 0; i < numskins; i++) { if (!(strcmp(pr->skinname, skins[i].name))) @@ -2058,9 +2063,9 @@ void M_CharacterSelectInit(void) if (i != 0 || optionsmenu.profile) CV_SetValue(&cv_usejoystick[i], -1); - CONS_Printf("Device for %d set to %d\n", i, -1); + //CONS_Printf("Device for %d set to %d\n", i, -1); } - CONS_Printf("========\n"); + //CONS_Printf("========\n"); memset(setup_chargrid, -1, sizeof(setup_chargrid)); for (i = 0; i < 9; i++) @@ -2075,6 +2080,13 @@ void M_CharacterSelectInit(void) memset(setup_explosions, 0, sizeof(setup_explosions)); setup_animcounter = 0; + // Default to no follower / Match + for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + setup_player[i].followern = -1; + setup_player[i].followercolor = -1; + } + for (i = 0; i < numskins; i++) { UINT8 x = skins[i].kartspeed-1; @@ -2229,16 +2241,16 @@ static boolean M_HandlePressStart(setup_player_t *p, UINT8 num) { // Available!! Let's use this one!! CV_SetValue(&cv_usejoystick[num], i); - CONS_Printf("Device for %d set to %d\n", num, i); - CONS_Printf("========\n"); + //CONS_Printf("Device for %d set to %d\n", num, i); + //CONS_Printf("========\n"); for (j = num+1; j < MAXSPLITSCREENPLAYERS; j++) { // Un-set devices for other players. CV_SetValue(&cv_usejoystick[j], -1); - CONS_Printf("Device for %d set to %d\n", j, -1); + //CONS_Printf("Device for %d set to %d\n", j, -1); } - CONS_Printf("========\n"); + //CONS_Printf("========\n"); //setup_numplayers++; p->mdepth = CSSTEP_PROFILE; @@ -2428,6 +2440,21 @@ static boolean M_HandleCharacterGrid(setup_player_t *p, UINT8 num) return false; } +// Gets the selected follower's state for a given setup player. +static void M_GetFollowerState(setup_player_t *p) +{ + + p->follower_state = &states[followers[p->followern].followstate]; + + if (p->follower_state->frame & FF_ANIMATE) + p->follower_tics = p->follower_state->var2; // support for FF_ANIMATE + else + p->follower_tics = p->follower_state->tics; + + p->follower_frame = p->follower_state->frame & FF_FRAMEMASK; +} + + static void M_HandleCharRotate(setup_player_t *p, UINT8 num) { UINT8 numclones = setup_chargrid[p->gridx][p->gridy].numskins; @@ -2488,10 +2515,12 @@ static void M_HandleColorRotate(setup_player_t *p, UINT8 num) if (M_MenuButtonPressed(num, MBT_A) || M_MenuButtonPressed(num, MBT_X) /*|| M_MenuButtonPressed(num, MBT_START)*/) { - p->mdepth = CSSTEP_READY; - p->delay = TICRATE; - M_SetupReadyExplosions(p); - S_StartSound(NULL, sfx_s3k4e); + p->mdepth = CSSTEP_FOLLOWER; + M_GetFollowerState(p); + + //p->delay = TICRATE; + //M_SetupReadyExplosions(p); + //S_StartSound(NULL, sfx_s3k4e); M_SetMenuDelay(num); } else if (M_MenuButtonPressed(num, MBT_B) || M_MenuButtonPressed(num, MBT_Y)) @@ -2505,6 +2534,133 @@ static void M_HandleColorRotate(setup_player_t *p, UINT8 num) } } +static void M_AnimateFollower(setup_player_t *p) +{ + if (--p->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 (p->follower_state->frame & FF_ANIMATE) + { + p->follower_frame++; + p->follower_tics = p->follower_state->var2; + if (p->follower_frame > (p->follower_state->frame & FF_FRAMEMASK) + p->follower_state->var1) // that's how it works, right? + p->follower_frame = p->follower_state->frame & FF_FRAMEMASK; + } + else + { + if (p->follower_state->nextstate != S_NULL) + p->follower_state = &states[p->follower_state->nextstate]; + p->follower_tics = p->follower_state->tics; + /*if (p->follower_tics == -1) + p->follower_tics = 15; // er, what?*/ + // get spritedef: + p->follower_frame = p->follower_state->frame & FF_FRAMEMASK; + } + } + + p->follower_timer++; +} + +static void M_HandleChooseFollower(setup_player_t *p, UINT8 num) +{ + + M_AnimateFollower(p); + + if (menucmd[num].dpad_lr > 0 && numfollowers) + { + p->followern++; + if (p->followern > numfollowers-1) + p->followern = -1; + + M_SetMenuDelay(num); + S_StartSound(NULL, sfx_menu1); + M_GetFollowerState(p); + } + else if (menucmd[num].dpad_lr < 0 && numfollowers) + { + p->followern--; + if (p->followern < -1) + p->followern = numfollowers-1; + + M_SetMenuDelay(num); + S_StartSound(NULL, sfx_menu1); + M_GetFollowerState(p); + } + else if (M_MenuButtonPressed(num, MBT_A) || M_MenuButtonPressed(num, MBT_X)) + { + if (p->followern > -1) + p->mdepth = CSSTEP_FOLLOWERCOLORS; + else + { + p->mdepth = CSSTEP_READY; + p->delay = TICRATE; + M_SetupReadyExplosions(p); + S_StartSound(NULL, sfx_s3k4e); + M_SetMenuDelay(num); + } + + S_StartSound(NULL, sfx_s3k63); + M_SetMenuDelay(num); + } + else if (M_MenuButtonPressed(num, MBT_B) || M_MenuButtonPressed(num, MBT_Y)) + { + p->mdepth = CSSTEP_COLORS; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } +} + +static void M_HandleFollowerColorRotate(setup_player_t *p, UINT8 num) +{ + M_AnimateFollower(p); + + if (menucmd[num].dpad_lr > 0) + { + p->followercolor++; + + // Go back to -2 (Opposite) + if (p->followercolor >= numskincolors) + p->followercolor = -2; + + // Make sure we skip 0. + if (p->followercolor == 0) + p->followercolor++; + + p->rotate = CSROTATETICS; + M_SetMenuDelay(num); //CSROTATETICS + S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s + } + else if (menucmd[num].dpad_lr < 0) + { + p->followercolor--; + if (p->followercolor < -2) + p->followercolor = numskincolors-1; + + if (p->followercolor == 0) + p->followercolor--; + + p->rotate = -CSROTATETICS; + M_SetMenuDelay(num); //CSROTATETICS + S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s + } + + if (M_MenuButtonPressed(num, MBT_A) || M_MenuButtonPressed(num, MBT_X) /*|| M_MenuButtonPressed(num, MBT_START)*/) + { + p->mdepth = CSSTEP_READY; + p->delay = TICRATE; + M_SetupReadyExplosions(p); + S_StartSound(NULL, sfx_s3k4e); + M_SetMenuDelay(num); + } + else if (M_MenuButtonPressed(num, MBT_B) || M_MenuButtonPressed(num, MBT_Y)) + { + p->mdepth = CSSTEP_FOLLOWER; + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(num); + } +} + boolean M_CharacterSelectHandler(INT32 choice) { INT32 i; @@ -2535,6 +2691,12 @@ boolean M_CharacterSelectHandler(INT32 choice) case CSSTEP_COLORS: // Select color M_HandleColorRotate(p, i); break; + case CSSTEP_FOLLOWER: + M_HandleChooseFollower(p, i); + break; + case CSSTEP_FOLLOWERCOLORS: + M_HandleFollowerColorRotate(p, i); + break; case CSSTEP_READY: default: // Unready if (M_MenuButtonPressed(i, MBT_B) || M_MenuButtonPressed(i, MBT_Y)) @@ -2584,7 +2746,7 @@ static void M_MPConfirmCharacterSelection(void) INT16 col; char colstr[8]; - char commandnames[][2][MAXSTRINGLENGTH] = { {"skin ", "color "}, {"skin2 ", "color2 "}, {"skin3 ", "color3 "}, {"skin4 ", "color4 "}}; + char commandnames[][4][MAXSTRINGLENGTH] = { {"skin ", "color ", "follower ", "followercolor "}, {"skin2 ", "color2 ", "follower2 ", "followercolor2 "}, {"skin3 ", "color3 " "follower3 ", "followercolor3 "}, {"skin4 ", "color4 ", "follower4 ", "followercolor4 "}}; // ^ laziness 100 (we append a space directly so that we don't have to do it later too!!!!) for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) @@ -2594,7 +2756,6 @@ static void M_MPConfirmCharacterSelection(void) // skin strcpy(cmd, commandnames[i][0]); strcat(cmd, skins[setup_player[i].skin].name); - COM_ImmedExecute(cmd); // colour @@ -2603,10 +2764,18 @@ static void M_MPConfirmCharacterSelection(void) sprintf(colstr, "%d", col); strcpy(cmd, commandnames[i][1]); strcat(cmd, colstr); + COM_ImmedExecute(cmd); + // follower + strcpy(cmd, commandnames[i][2]); + strcat(cmd, va("%d", setup_player[i].followern)); + COM_ImmedExecute(cmd); + + // follower color + strcpy(cmd, commandnames[i][3]); + strcat(cmd, va("%d", setup_player[i].followercolor)); COM_ImmedExecute(cmd); } - M_ClearMenus(true); } @@ -2659,9 +2828,14 @@ void M_CharacterSelectTick(void) // in a profile; update the selected profile and then go back to the profile menu. if (optionsmenu.profile) { + // save player strcpy(optionsmenu.profile->skinname, skins[setup_player[0].skin].name); optionsmenu.profile->color = setup_player[0].color; + // save follower + strcpy(optionsmenu.profile->follower, followers[setup_player[0].followern].skinname); + optionsmenu.profile->followercolor = setup_player[0].followercolor; + // reset setup_player memset(setup_player, 0, sizeof(setup_player)); @@ -2674,6 +2848,9 @@ void M_CharacterSelectTick(void) { CV_StealthSet(&cv_skin[i], skins[setup_player[i].skin].name); CV_StealthSetValue(&cv_playercolor[i], setup_player[i].color); + + CV_StealthSetValue(&cv_follower[i], setup_player[i].followern); + CV_StealthSetValue(&cv_followercolor[i], setup_player[i].followercolor); } CV_StealthSetValue(&cv_splitplayers, setup_numplayers); diff --git a/src/r_skins.h b/src/r_skins.h index 2d29af188..95de83788 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -128,6 +128,9 @@ typedef struct follower_s INT32 losestate; // state when the player has lost INT32 hitconfirmstate; // state for hit confirm UINT32 hitconfirmtime; // time to keep the above playing for + + // visual + char icon[8+1]; // Lump names are only 8 characters. (+1 for \0) } follower_t; extern INT32 numfollowers;