Repair character colors and followercolors via the menu, both visually and mechanically.

* Fully reimplement the MenuColor system from 2.2's codebase, so super and emerald colours are now inaccessible again.
* Add FOLLOWERCOLOR_ constants and internal loop support to M_GetColorBefore and M_GetColorAfter.
* Fix improper initialisation of certain menu colour data.
* Repair previously created (or manually-edited) profiles with invalid colours.
* Add an actual function to turn followercolor constants to effective values.
This commit is contained in:
toaster 2022-05-21 19:35:40 +01:00
parent aa0c6d12eb
commit 5b13d4f75d
9 changed files with 207 additions and 173 deletions

View file

@ -226,6 +226,10 @@ typedef struct skincolor_s
boolean accessible; // Accessible by the color command + setup menu
} skincolor_t;
#define FOLLOWERCOLOR_MATCH UINT16_MAX
#define FOLLOWERCOLOR_OPPOSITE (UINT16_MAX-1)
UINT16 K_GetEffectiveFollowerColor(UINT16 followercolor, UINT16 playercolor);
typedef enum
{
SKINCOLOR_NONE = 0,

View file

@ -488,11 +488,12 @@ void M_StopMessage(INT32 choice);
void M_QuitResponse(INT32 ch);
void M_QuitSRB2(INT32 choice);
extern UINT16 nummenucolors;
void M_AddMenuColor(UINT16 color);
void M_MoveColorBefore(UINT16 color, UINT16 targ);
void M_MoveColorAfter(UINT16 color, UINT16 targ);
UINT16 M_GetColorBefore(UINT16 color);
UINT16 M_GetColorAfter(UINT16 color);
UINT16 M_GetColorBefore(UINT16 color, UINT16 amount, boolean follower);
UINT16 M_GetColorAfter(UINT16 color, UINT16 amount, boolean follower);
void M_InitPlayerSetupColors(void);
void M_FreePlayerSetupColors(void);
@ -525,7 +526,7 @@ typedef struct setup_player_s
SINT8 clonenum;
SINT8 rotate;
UINT8 delay;
UINT8 color;
UINT16 color;
UINT8 mdepth;
// Hack, save player 1's original device even if they init charsel with keyboard.
@ -534,7 +535,7 @@ typedef struct setup_player_s
UINT8 ponedevice;
INT32 followern;
INT16 followercolor;
UINT16 followercolor;
tic_t follower_tics;
tic_t follower_timer;
UINT8 follower_frame;

View file

@ -818,15 +818,15 @@ void M_DrawImageDef(void)
static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
{
angle_t angamt = ANGLE_MAX;
UINT8 numoptions;
UINT8 i;
UINT16 i, numoptions;
UINT16 l = 0, r = 0;
if (p->mdepth == CSSTEP_ALTS)
numoptions = setup_chargrid[p->gridx][p->gridy].numskins;
else if (p->mdepth == CSSTEP_FOLLOWERCOLORS)
numoptions = numskincolors-1 +2;
numoptions = nummenucolors+2;
else
numoptions = numskincolors-1;
numoptions = nummenucolors;
angamt /= numoptions;
@ -836,9 +836,9 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
boolean subtract = (i & 1);
angle_t ang = ((i+1)/2) * angamt;
patch_t *patch = NULL;
UINT8 *colormap;
fixed_t radius;
INT16 n;
UINT8 *colormap = NULL;
fixed_t radius = 28<<FRACBITS;
UINT16 n = 0;
if (p->mdepth == CSSTEP_ALTS)
{
@ -865,36 +865,24 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
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)
if (i == 0)
{
case FOLLOWERCOLOR_MATCH: // "Match"
col = p->color;
break;
case FOLLOWERCOLOR_OPPOSITE: // "Opposite"
col = skincolors[p->color].invcolor;
break;
n = l = r = M_GetColorBefore(p->followercolor, numoptions/2, true);
}
else if (subtract)
{
n = l = M_GetColorBefore(l, 1, true);
}
else
{
n = r = M_GetColorAfter(r, 1, true);
}
col = K_GetEffectiveFollowerColor(n, p->color);
colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
if (n > p->followercolor)
diff = n - p->followercolor;
else
diff = p->followercolor - n;
diff = (numoptions - i)/2; // only 0 when i == numoptions-1
if (diff == 0)
patch = W_CachePatchName("COLORSP2", PU_CACHE);
@ -903,29 +891,28 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
else
patch = W_CachePatchName("COLORSP0", PU_CACHE);
radius = 28<<FRACBITS;
//radius -= SHORT(patch->width) << FRACBITS;
cx -= (SHORT(patch->width) << FRACBITS) >> 1;
}
else
{
INT16 diff;
n = (p->color-1) + numoptions/2;
if (subtract)
n -= ((i+1)/2);
if (i == 0)
{
n = l = r = M_GetColorBefore(p->color, numoptions/2, false);
}
else if (subtract)
{
n = l = M_GetColorBefore(l, 1, false);
}
else
n += ((i+1)/2);
n %= numoptions;
n++;
{
n = r = M_GetColorAfter(r, 1, false);
}
colormap = R_GetTranslationColormap(TC_DEFAULT, n, GTC_MENUCACHE);
if (n > p->color)
diff = n - p->color;
else
diff = p->color - n;
diff = (numoptions - i)/2; // only 0 when i == numoptions-1
if (diff == 0)
patch = W_CachePatchName("COLORSP2", PU_CACHE);
@ -934,9 +921,6 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
else
patch = W_CachePatchName("COLORSP0", PU_CACHE);
radius = 28<<FRACBITS;
//radius -= SHORT(patch->width) << FRACBITS;
cx -= (SHORT(patch->width) << FRACBITS) >> 1;
}
@ -945,7 +929,7 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
else
ang = ANGLE_90 + ang;
if (numoptions % 2)
if (numoptions & 1)
ang = (signed)(ang - (angamt/2));
if (p->rotate)
@ -1034,19 +1018,9 @@ static void M_DrawFollowerList(setup_player_t *p, UINT8 num)
if (W_LumpExists(fl.icon) && cf >= 0)
{
UINT16 col = (unsigned)p->followercolor;
UINT16 col = K_GetEffectiveFollowerColor(p->followercolor, p->color);
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)
@ -1137,19 +1111,7 @@ static boolean M_DrawFollowerSprite(INT16 x, INT16 y, INT32 num, INT32 addflags,
{
const fixed_t pi = (22<<FRACBITS) / 7; // loose approximation, this doesn't need to be incredibly precise
sine = fl.bobamp * FINESINE((((8*pi*(fl.bobspeed)) * p->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;
}
color = K_GetEffectiveFollowerColor(p->followercolor, p->color);
}
colormap = R_GetTranslationColormap(TC_DEFAULT, color, GTC_MENUCACHE);
@ -1405,20 +1367,10 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p)
{
if (M_DrawFollowerSprite(x-44 +12, y+119, 0, V_FLIP, 0, sp))
{
UINT16 col = (unsigned)p->followercolor;
UINT16 col = K_GetEffectiveFollowerColor(sp->followercolor, sp->color);;
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);
}
@ -1433,23 +1385,12 @@ static void M_DrawProfileCard(INT32 x, INT32 y, boolean greyedout, profile_t *p)
else if (skinnum > -1) // otherwise, read from profile.
{
UINT16 col = (unsigned)p->followercolor;
UINT16 col = K_GetEffectiveFollowerColor(p->followercolor, p->color);;
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);

View file

@ -501,16 +501,18 @@ void M_EraseData(INT32 choice)
// BASIC MENU HANDLING
// =========================================================================
UINT16 nummenucolors = 0;
void M_AddMenuColor(UINT16 color) {
menucolor_t *c;
// SRB2Kart: I do not understand vanilla doesn't need this but WE do???!?!??!
if (!skincolors[color].accessible) {
if (color >= numskincolors) {
CONS_Printf("M_AddMenuColor: color %d does not exist.",color);
return;
}
if (color >= numskincolors) {
CONS_Printf("M_AddMenuColor: color %d does not exist.",color);
// SRB2Kart: I do not understand vanilla doesn't need this but WE do???!?!??!
if (!skincolors[color].accessible) {
return;
}
@ -528,6 +530,8 @@ void M_AddMenuColor(UINT16 color) {
menucolorhead->prev = c;
menucolortail = c;
}
nummenucolors++;
}
void M_MoveColorBefore(UINT16 color, UINT16 targ) {
@ -614,36 +618,118 @@ void M_MoveColorAfter(UINT16 color, UINT16 targ) {
t->next = c;
}
UINT16 M_GetColorBefore(UINT16 color) {
menucolor_t *look;
UINT16 M_GetColorBefore(UINT16 color, UINT16 amount, boolean follower)
{
menucolor_t *look = NULL;
if (color >= numskincolors) {
CONS_Printf("M_GetColorBefore: color %d does not exist.\n",color);
return 0;
}
for (; amount > 0; amount--)
{
if (follower == true)
{
if (color == FOLLOWERCOLOR_OPPOSITE)
{
look = menucolortail;
color = menucolortail->color;
continue;
}
for (look=menucolorhead;;look=look->next) {
if (look->color == color)
return look->prev->color;
if (look==menucolortail)
if (color == FOLLOWERCOLOR_MATCH)
{
look = NULL;
color = FOLLOWERCOLOR_OPPOSITE;
continue;
}
if (color == menucolorhead->color)
{
look = NULL;
color = FOLLOWERCOLOR_MATCH;
continue;
}
}
if (color == 0 || color >= numskincolors)
{
CONS_Printf("M_GetColorBefore: color %d does not exist.\n",color);
return 0;
}
if (look == NULL)
{
for (look = menucolorhead;; look = look->next)
{
if (look->color == color)
{
break;
}
if (look == menucolortail)
{
return 0;
}
}
}
look = look->prev;
color = look->color;
}
return color;
}
UINT16 M_GetColorAfter(UINT16 color) {
menucolor_t *look;
UINT16 M_GetColorAfter(UINT16 color, UINT16 amount, boolean follower)
{
menucolor_t *look = NULL;
if (color >= numskincolors) {
CONS_Printf("M_GetColorAfter: color %d does not exist.\n",color);
return 0;
}
for (; amount > 0; amount--)
{
if (follower == true)
{
if (color == menucolortail->color)
{
look = NULL;
color = FOLLOWERCOLOR_OPPOSITE;
continue;
}
for (look=menucolorhead;;look=look->next) {
if (look->color == color)
return look->next->color;
if (look==menucolortail)
if (color == FOLLOWERCOLOR_OPPOSITE)
{
look = NULL;
color = FOLLOWERCOLOR_MATCH;
continue;
}
if (color == FOLLOWERCOLOR_MATCH)
{
look = menucolorhead;
color = menucolorhead->color;
continue;
}
}
if (color == 0 || color >= numskincolors)
{
CONS_Printf("M_GetColorAfter: color %d does not exist.\n",color);
return 0;
}
if (look == NULL)
{
for (look = menucolorhead;; look = look->next)
{
if (look->color == color)
{
break;
}
if (look == menucolortail)
{
return 0;
}
}
}
look = look->next;
color = look->color;
}
return color;
}
void M_InitPlayerSetupColors(void) {
@ -2126,7 +2212,7 @@ void M_CharacterSelectInit(void)
{
// Default to no follower / match colour.
setup_player[i].followern = -1;
setup_player[i].followercolor = -1;
setup_player[i].followercolor = FOLLOWERCOLOR_MATCH;
// Set default selected profile to the last used profile for each player:
// (Make sure we don't overshoot it somehow if we deleted profiles or whatnot)
@ -2576,18 +2662,14 @@ static void M_HandleColorRotate(setup_player_t *p, UINT8 num)
if (menucmd[num].dpad_lr > 0)
{
p->color++;
if (p->color >= numskincolors)
p->color = 1;
p->color = M_GetColorAfter(p->color, 1, false);
p->rotate = CSROTATETICS;
M_SetMenuDelay(num); //CSROTATETICS
S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s
}
else if (menucmd[num].dpad_lr < 0)
{
p->color--;
if (p->color < 1)
p->color = numskincolors-1;
p->color = M_GetColorBefore(p->color, 1, false);
p->rotate = -CSROTATETICS;
M_SetMenuDelay(num); //CSROTATETICS
S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s
@ -2696,7 +2778,6 @@ static void M_HandleChooseFollower(setup_player_t *p, UINT8 num)
static void M_HandleFollowerColorRotate(setup_player_t *p, UINT8 num)
{
if (cv_splitdevice.value)
num = 0;
@ -2704,29 +2785,14 @@ static void M_HandleFollowerColorRotate(setup_player_t *p, UINT8 num)
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->followercolor = M_GetColorAfter(p->followercolor, 1, true);
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->followercolor = M_GetColorBefore(p->followercolor, 1, true);
p->rotate = -CSROTATETICS;
M_SetMenuDelay(num); //CSROTATETICS
S_StartSound(NULL, sfx_s3k5b); //sfx_s3kc3s

View file

@ -199,6 +199,25 @@ void PR_LoadProfiles(void)
{
profilesList[i] = Z_Malloc(sizeof(profile_t), PU_STATIC, NULL);
fread(profilesList[i], sizeof(profile_t), 1, f);
// Attempt to correct numerical footguns
if (profilesList[i]->color >= numskincolors
|| profilesList[i]->color == 0
|| skincolors[profilesList[i]->color].accessible == false)
{
profilesList[i]->color = PROFILEDEFAULTCOLOR;
}
if (profilesList[i]->followercolor == FOLLOWERCOLOR_MATCH
|| profilesList[i]->followercolor == FOLLOWERCOLOR_OPPOSITE)
{
; // Valid, even outside the bounds
}
else if (profilesList[i]->followercolor >= numskincolors
|| profilesList[i]->followercolor == 0
|| skincolors[profilesList[i]->followercolor].accessible == false)
{
profilesList[i]->followercolor = PROFILEDEFAULTFOLLOWERCOLOR;
}
}
fclose(f);

View file

@ -35,7 +35,7 @@
#define PROFILEDEFAULTSKIN "sonic"
#define PROFILEDEFAULTCOLOR SKINCOLOR_SAPPHIRE
#define PROFILEDEFAULTFOLLOWER "none"
#define PROFILEDEFAULTFOLLOWERCOLOR 255
#define PROFILEDEFAULTFOLLOWERCOLOR FOLLOWERCOLOR_MATCH
// Man I wish I had more than 16 friends!!

View file

@ -354,14 +354,26 @@ static int lib_pMoveColorAfter(lua_State *L)
static int lib_pGetColorBefore(lua_State *L)
{
UINT16 color = (UINT16)luaL_checkinteger(L, 1);
lua_pushinteger(L, M_GetColorBefore(color));
UINT16 amount = (UINT16)luaL_checkinteger(L, 2);
boolean follower = lua_optboolean(L, 3);
lua_pushinteger(L, M_GetColorBefore(color, amount, follower));
return 1;
}
static int lib_pGetColorAfter(lua_State *L)
{
UINT16 color = (UINT16)luaL_checkinteger(L, 1);
lua_pushinteger(L, M_GetColorAfter(color));
UINT16 amount = (UINT16)luaL_checkinteger(L, 2);
boolean follower = lua_optboolean(L, 3);
lua_pushinteger(L, M_GetColorAfter(color, amount, follower));
return 1;
}
static int lib_pGetEffectiveFollowerColor(lua_State *L)
{
UINT16 followercolor = (UINT16)luaL_checkinteger(L, 1);
UINT16 playercolor = (UINT16)luaL_checkinteger(L, 2);
lua_pushinteger(L, K_GetEffectiveFollowerColor(followercolor, playercolor));
return 1;
}
@ -3926,6 +3938,7 @@ static luaL_Reg lib[] = {
{"P_ReturnThrustX",lib_pReturnThrustX},
{"P_ReturnThrustY",lib_pReturnThrustY},
{"P_NukeEnemies",lib_pNukeEnemies},
{"K_GetEffectiveFollowerColor",lib_pGetEffectiveFollowerColor},
// p_map
{"P_CheckPosition",lib_pCheckPosition},

View file

@ -3948,6 +3948,16 @@ static void P_SetFollowerState(mobj_t *f, INT32 state)
f->tics++;
}
UINT16 K_GetEffectiveFollowerColor(UINT16 followercolor, UINT16 playercolor)
{
if (followercolor < numskincolors) // bog standard
return followercolor;
if (followercolor == FOLLOWERCOLOR_OPPOSITE) // "Opposite"
return skincolors[playercolor].invcolor;
//if (followercolor == FOLLOWERCOLOR_MATCH) -- "Match"
return playercolor;
}
//
//P_HandleFollower
//
@ -4007,25 +4017,7 @@ static void P_HandleFollower(player_t *player)
sz += FixedMul(player->mo->scale, sine)*P_MobjFlip(player->mo);
}
// Set follower colour
switch (player->followercolor)
{
case FOLLOWERCOLOR_MATCH: // "Match"
color = player->skincolor;
break;
case FOLLOWERCOLOR_OPPOSITE: // "Opposite"
color = skincolors[player->skincolor].invcolor;
break;
default:
color = player->followercolor;
if (!color || color > MAXSKINCOLORS+2) // Make sure this isn't garbage
color = player->skincolor; // "Match" as fallback.
break;
}
color = K_GetEffectiveFollowerColor(player->followercolor, player->skincolor);
if (!player->follower) // follower doesn't exist / isn't valid
{

View file

@ -41,8 +41,6 @@ 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.
#define FOLLOWERCOLOR_MATCH UINT16_MAX
#define FOLLOWERCOLOR_OPPOSITE (UINT16_MAX-1)
// I/O, setting up the stuff.
void R_InitTextureData(void);