Major changes to follower selection

- Followers now have categories, definable in SOC
- New character select step: Follower category
    - "None" is a category, just skips straight to Follower None
    - Select a category to go to the previous regular follower selection step
- Press the C/Extra button to reset a character select step to its "default"
    - Character: Center the character in the engine class (goes from [0,1] to [1,1], etc)
    - Character alts: Centers the "primary" alt (prefers Eggman over Eggrobo)
    - Skincolor: Centers the character's prefcolour
    - Follower category: Centers on the "None" option
    - Follower: [CURRENTLY NO BEHAVIOUR]
    - Followercolor: Cycles between follower's defaultcolour, "Match", and "Opposite"
This commit is contained in:
toaster 2022-11-06 22:53:12 +00:00
parent 368a45c674
commit 8aeaf9738d
8 changed files with 354 additions and 35 deletions

View file

@ -3139,10 +3139,9 @@ void readfollower(MYFILE *f)
INT32 res;
INT32 i;
if (numfollowers > MAXSKINS)
if (numfollowers >= MAXSKINS)
{
deh_warning("Error: Too many followers, cannot add anymore.\n");
return;
I_Error("Out of Followers\nLoad less addons to fix this.");
}
s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
@ -3162,7 +3161,8 @@ void readfollower(MYFILE *f)
followers[numfollowers].bobamp = 4*FRACUNIT;
followers[numfollowers].hitconfirmtime = TICRATE;
followers[numfollowers].defaultcolor = FOLLOWERCOLOR_MATCH;
strcpy(followers[numfollowers].icon, "M_NORANK");
followers[numfollowers].category = UINT8_MAX;
strcpy(followers[numfollowers].icon, "MISSING");
do
{
@ -3201,6 +3201,23 @@ void readfollower(MYFILE *f)
strcpy(followers[numfollowers].icon, word2);
nameset = true;
}
else if (fastcmp(word, "CATEGORY"))
{
INT32 j;
for (j = 0; j < numfollowercategories; j++)
{
if (!stricmp(followercategories[j].name, word2))
{
followers[numfollowers].category = j;
break;
}
}
if (j == numfollowercategories)
{
deh_warning("Follower %d: unknown follower category '%s'", numfollowers, word2);
}
}
else if (fastcmp(word, "MODE"))
{
if (word2)
@ -3406,10 +3423,82 @@ if (!followers[numfollowers].field) \
#undef NOSTATE
CONS_Printf("Added follower '%s'\n", dname);
if (followers[numfollowers].category < numfollowercategories)
followercategories[followers[numfollowers].category].numincategory++;
numfollowers++; // add 1 follower
Z_Free(s);
}
void readfollowercategory(MYFILE *f)
{
char *s;
char *word, *word2;
char *tmp;
boolean nameset;
if (numfollowercategories == MAXFOLLOWERCATEGORIES)
{
I_Error("Out of Follower categories\nLoad less addons to fix this.");
}
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.
strcpy(followercategories[numfollowercategories].icon, "MISSING");
followercategories[numfollowercategories].numincategory = 0;
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"))
{
strcpy(followercategories[numfollowercategories].name, word2);
nameset = true;
}
else if (fastcmp(word, "ICON"))
{
strcpy(followercategories[numfollowercategories].icon, word2);
nameset = true;
}
}
} while (!myfeof(f)); // finish when the line is empty
if (!nameset)
{
// well this is problematic.
strcpy(followercategories[numfollowercategories].name, va("Followercategory%d", numfollowercategories)); // this is lazy, so what
}
CONS_Printf("Added follower category '%s'\n", followercategories[numfollowercategories].name);
numfollowercategories++; // add 1 follower
Z_Free(s);
}
void readweather(MYFILE *f, INT32 num)
{
char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);

View file

@ -82,6 +82,7 @@ void clear_conditionsets(void);
void readcupheader(MYFILE *f, cupheader_t *cup);
void readfollower(MYFILE *f);
void readfollowercategory(MYFILE *f);
preciptype_t get_precip(const char *word);
void readweather(MYFILE *f, INT32 num);

View file

@ -235,6 +235,12 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
readfollower(f);
continue;
}
else if (fastcmp(word, "FOLLOWERCATEGORY"))
{
// This is not a major mod.
readfollowercategory(f);
continue;
}
word2 = strtok(NULL, " ");
if (word2) {

View file

@ -15,6 +15,9 @@
INT32 numfollowers = 0;
follower_t followers[MAXSKINS];
INT32 numfollowercategories;
followercategory_t followercategories[MAXFOLLOWERCATEGORIES];
CV_PossibleValue_t Followercolor_cons_t[MAXSKINCOLORS+3]; // +3 to account for "Match", "Opposite" & NULL
/*--------------------------------------------------

View file

@ -49,6 +49,8 @@ typedef struct follower_s
char name[SKINNAMESIZE+1]; // Name. This is used for the menus. We'll just follow the same rules as skins for this.
char icon[8+1]; // Lump names are only 8 characters. (+1 for \0)
UINT8 category; // Category
skincolornum_t defaultcolor; // default color for menus.
followermode_t mode; // Follower behavior modifier.
@ -85,6 +87,18 @@ typedef struct follower_s
extern INT32 numfollowers;
extern follower_t followers[MAXSKINS];
#define MAXFOLLOWERCATEGORIES 32
typedef struct followercategory_s
{
char name[SKINNAMESIZE+1]; // Name. This is used for the menus. We'll just follow the same rules as skins for this.
char icon[8+1]; // Lump names are only 8 characters. (+1 for \0)
UINT8 numincategory;
} followercategory_t;
extern INT32 numfollowercategories;
extern followercategory_t followercategories[MAXFOLLOWERCATEGORIES];
/*--------------------------------------------------
INT32 K_FollowerAvailable(const char *name)

View file

@ -599,6 +599,7 @@ typedef enum
CSSTEP_CHARS,
CSSTEP_ALTS,
CSSTEP_COLORS,
CSSTEP_FOLLOWERCATEGORY,
CSSTEP_FOLLOWER,
CSSTEP_FOLLOWERCOLORS,
CSSTEP_READY
@ -614,6 +615,7 @@ typedef struct setup_player_s
UINT8 delay;
UINT16 color;
UINT8 mdepth;
boolean hitlag;
// Hack, save player 1's original device even if they init charsel with keyboard.
// If they play ALONE, allow them to retain that original device, otherwise, ignore this.
@ -622,7 +624,8 @@ typedef struct setup_player_s
UINT8 changeselect;
INT32 followern;
INT16 followercategory;
INT16 followern;
UINT16 followercolor;
tic_t follower_tics;
tic_t follower_timer;

View file

@ -969,7 +969,8 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
{
angle_t angamt = ANGLE_MAX;
UINT16 i, numoptions = 0;
UINT16 l = 0, r = 0;
INT16 l = 0, r = 0;
INT16 subtractcheck;
switch (p->mdepth)
{
@ -979,8 +980,11 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
case CSSTEP_COLORS:
numoptions = nummenucolors;
break;
case CSSTEP_FOLLOWERCATEGORY:
numoptions = numfollowercategories+1;
break;
case CSSTEP_FOLLOWER:
numoptions = numfollowers+1;
numoptions = followercategories[p->followercategory].numincategory;
break;
case CSSTEP_FOLLOWERCOLORS:
numoptions = nummenucolors+2;
@ -994,17 +998,19 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
return;
}
subtractcheck = 1 ^ (numoptions & 1);
angamt /= numoptions;
for (i = 0; i < numoptions; i++)
{
fixed_t cx = x << FRACBITS, cy = y << FRACBITS;
boolean subtract = (i & 1);
boolean subtract = (i & 1) == subtractcheck;
angle_t ang = ((i+1)/2) * angamt;
patch_t *patch = NULL;
UINT8 *colormap = NULL;
fixed_t radius = 28<<FRACBITS;
UINT16 n = 0;
INT16 n = 0;
switch (p->mdepth)
{
@ -1017,7 +1023,7 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
n -= ((i+1)/2);
else
n += ((i+1)/2);
n %= numoptions;
n = (n + numoptions) % numoptions;
skin = setup_chargrid[p->gridx][p->gridy].skinlist[n];
patch = faceprefix[skin][FACE_RANK];
@ -1061,16 +1067,16 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
break;
}
case CSSTEP_FOLLOWER:
case CSSTEP_FOLLOWERCATEGORY:
{
follower_t *fl = NULL;
followercategory_t *fc = NULL;
n = (p->followern + 1) + numoptions/2;
n = (p->followercategory + 1) + numoptions/2;
if (subtract)
n -= ((i+1)/2);
else
n += ((i+1)/2);
n %= numoptions;
n = (n + numoptions) % numoptions;
if (n == 0)
{
@ -1078,7 +1084,67 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
}
else
{
fl = &followers[n - 1];
fc = &followercategories[n - 1];
patch = W_CachePatchName(fc->icon, PU_CACHE);
}
radius = 24<<FRACBITS;
cx -= (SHORT(patch->width) << FRACBITS) >> 1;
cy -= (SHORT(patch->height) << FRACBITS) >> 1;
break;
}
case CSSTEP_FOLLOWER:
{
follower_t *fl = NULL;
INT16 startfollowern = p->followern;
if (i == 0)
{
n = p->followern;
r = (numoptions+1)/2;
while (r)
{
n--;
if (n < 0)
n = numfollowers-1;
if (n == startfollowern)
break;
if (followers[n].category == p->followercategory)
r--;
}
l = r = n;
}
else if (subtract)
{
do
{
l--;
if (l < 0)
l = numfollowers-1;
if (l == startfollowern)
break;
}
while (followers[l].category != p->followercategory);
n = l;
}
else
{
do
{
r++;
if (r >= numfollowers)
r = 0;
if (r == startfollowern)
break;
}
while (followers[r].category != p->followercategory);
n = r;
}
{
fl = &followers[n];
patch = W_CachePatchName(fl->icon, PU_CACHE);
colormap = R_GetTranslationColormap(TC_DEFAULT,
@ -1142,7 +1208,12 @@ static void M_DrawCharSelectCircle(setup_player_t *p, INT16 x, INT16 y)
ang = (signed)(ang - (angamt/2));
if (p->rotate)
ang = (signed)(ang + ((angamt / CSROTATETICS) * p->rotate));
{
SINT8 rotate = p->rotate;
if ((p->hitlag == true) && (setup_animcounter & 1))
rotate = -rotate;
ang = (signed)(ang + ((angamt / CSROTATETICS) * rotate));
}
cx += FixedMul(radius, FINECOSINE(ang >> ANGLETOFINESHIFT));
cy -= FixedMul(radius, FINESINE(ang >> ANGLETOFINESHIFT)) / 3;
@ -1264,24 +1335,19 @@ static boolean M_DrawFollowerSprite(INT16 x, INT16 y, INT32 num, boolean charfli
static void M_DrawCharSelectSprite(UINT8 num, INT16 x, INT16 y, boolean charflip)
{
setup_player_t *p = &setup_player[num];
UINT8 cnum = p->clonenum;
INT16 skin;
UINT8 color;
UINT8 *colormap;
// for p1 alone don't try to preview things on pages that don't exist lol.
if (p->mdepth == CSSTEP_CHARS && setup_numplayers == 1)
cnum = setup_page;
if (p->skin < 0)
return;
skin = setup_chargrid[p->gridx][p->gridy].skinlist[cnum];
if (p->mdepth < CSSTEP_COLORS)
color = skins[skin].prefcolor;
color = skins[p->skin].prefcolor;
else
color = p->color;
colormap = R_GetTranslationColormap(skin, color, GTC_MENUCACHE);
colormap = R_GetTranslationColormap(p->skin, color, GTC_MENUCACHE);
if (skin >= 0)
M_DrawCharacterSprite(x, y, skin, charflip, (p->mdepth == CSSTEP_READY), 0, colormap);
M_DrawCharacterSprite(x, y, p->skin, charflip, (p->mdepth == CSSTEP_READY), 0, colormap);
}
static void M_DrawCharSelectPreview(UINT8 num)

View file

@ -2150,6 +2150,7 @@ void M_CharacterSelectInit(void)
{
// Default to no follower / match colour.
setup_player[i].followern = -1;
setup_player[i].followercategory = -1;
setup_player[i].followercolor = FOLLOWERCOLOR_MATCH;
// Set default selected profile to the last used profile for each player:
@ -2578,6 +2579,15 @@ static boolean M_HandleCharacterGrid(setup_player_t *p, UINT8 num)
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(num);
}
else if (M_MenuExtraPressed(num))
{
p->gridx /= 3;
p->gridx = (3*p->gridx) + 1;
p->gridy /= 3;
p->gridy = (3*p->gridy) + 1;
S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s
M_SetMenuDelay(num);
}
// try to set the clone num to the page # if possible.
p->clonenum = setup_page;
@ -2692,6 +2702,14 @@ static void M_HandleCharRotate(setup_player_t *p, UINT8 num)
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(num);
}
else if (M_MenuExtraPressed(num))
{
p->clonenum = 0;
p->rotate = CSROTATETICS;
p->hitlag = true;
S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s
M_SetMenuDelay(num);
}
}
static void M_HandleColorRotate(setup_player_t *p, UINT8 num)
@ -2716,8 +2734,7 @@ static void M_HandleColorRotate(setup_player_t *p, UINT8 num)
if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/)
{
p->mdepth = CSSTEP_FOLLOWER;
M_GetFollowerState(p);
p->mdepth = CSSTEP_FOLLOWERCATEGORY;
S_StartSound(NULL, sfx_s3k63);
M_SetMenuDelay(num);
}
@ -2734,6 +2751,17 @@ static void M_HandleColorRotate(setup_player_t *p, UINT8 num)
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(num);
}
else if (M_MenuExtraPressed(num))
{
if (p->skin >= 0)
{
p->color = skins[p->skin].prefcolor;
p->rotate = CSROTATETICS;
p->hitlag = true;
S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s
M_SetMenuDelay(num);
}
}
}
static void M_AnimateFollower(setup_player_t *p)
@ -2764,16 +2792,100 @@ static void M_AnimateFollower(setup_player_t *p)
p->follower_timer++;
}
static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num)
static void M_HandleFollowerCategoryRotate(setup_player_t *p, UINT8 num)
{
if (cv_splitdevice.value)
num = 0;
if (menucmd[num].dpad_lr > 0)
{
p->followern++;
if (p->followern >= numfollowers)
p->followercategory++;
if (p->followercategory >= numfollowercategories)
p->followercategory = -1;
p->rotate = CSROTATETICS;
p->delay = CSROTATETICS;
S_StartSound(NULL, sfx_s3kc3s);
}
else if (menucmd[num].dpad_lr < 0)
{
p->followercategory--;
if (p->followercategory < -1)
p->followercategory = numfollowercategories-1;
p->rotate = -CSROTATETICS;
p->delay = CSROTATETICS;
S_StartSound(NULL, sfx_s3kc3s);
}
if (M_MenuConfirmPressed(num) /*|| M_MenuButtonPressed(num, MBT_START)*/)
{
if (p->followercategory < 0)
{
p->followern = -1;
p->mdepth = CSSTEP_READY;
p->delay = TICRATE;
M_SetupReadyExplosions(p);
S_StartSound(NULL, sfx_s3k4e);
}
else
{
if (p->followern < 0 || followers[p->followern].category != p->followercategory)
{
p->followern = 0;
while (p->followern < numfollowers && followers[p->followern].category != p->followercategory)
p->followern++;
}
if (p->followern >= numfollowers)
{
p->followern = -1;
S_StartSound(NULL, sfx_s3kb2);
}
else
{
M_GetFollowerState(p);
p->mdepth = CSSTEP_FOLLOWER;
S_StartSound(NULL, sfx_s3k63);
}
}
M_SetMenuDelay(num);
}
else if (M_MenuBackPressed(num))
{
p->mdepth = CSSTEP_COLORS;
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(num);
}
else if (M_MenuExtraPressed(num))
{
p->followercategory = -1;
p->rotate = CSROTATETICS;
p->hitlag = true;
S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s
M_SetMenuDelay(num);
}
}
static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num)
{
INT16 startfollowern = p->followern;
if (cv_splitdevice.value)
num = 0;
if (menucmd[num].dpad_lr > 0)
{
do
{
p->followern++;
if (p->followern >= numfollowers)
p->followern = 0;
if (p->followern == startfollowern)
break;
}
while (followers[p->followern].category != p->followercategory);
M_GetFollowerState(p);
@ -2783,9 +2895,15 @@ static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num)
}
else if (menucmd[num].dpad_lr < 0)
{
p->followern--;
if (p->followern < -1)
p->followern = numfollowers-1;
do
{
p->followern--;
if (p->followern < 0)
p->followern = numfollowers-1;
if (p->followern == startfollowern)
break;
}
while (followers[p->followern].category != p->followercategory);
M_GetFollowerState(p);
@ -2813,7 +2931,7 @@ static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num)
}
else if (M_MenuBackPressed(num))
{
p->mdepth = CSSTEP_COLORS;
p->mdepth = CSSTEP_FOLLOWERCATEGORY;
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(num);
}
@ -2851,10 +2969,24 @@ static void M_HandleFollowerColorRotate(setup_player_t *p, UINT8 num)
}
else if (M_MenuBackPressed(num))
{
M_GetFollowerState(p);
p->mdepth = CSSTEP_FOLLOWER;
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(num);
}
else if (M_MenuExtraPressed(num))
{
if (p->followercolor == FOLLOWERCOLOR_MATCH)
p->followercolor = FOLLOWERCOLOR_OPPOSITE;
else if (p->followercolor == followers[p->followern].defaultcolor)
p->followercolor = FOLLOWERCOLOR_MATCH;
else
p->followercolor = followers[p->followern].defaultcolor;
p->rotate = CSROTATETICS;
p->hitlag = true;
S_StartSound(NULL, sfx_s3k7b); //sfx_s3kc3s
M_SetMenuDelay(num);
}
}
boolean M_CharacterSelectHandler(INT32 choice)
@ -2903,6 +3035,9 @@ boolean M_CharacterSelectHandler(INT32 choice)
case CSSTEP_COLORS: // Select color
M_HandleColorRotate(p, i);
break;
case CSSTEP_FOLLOWERCATEGORY:
M_HandleFollowerCategoryRotate(p, i);
break;
case CSSTEP_FOLLOWER:
M_HandleFollowerRotate(p, i);
break;
@ -3006,6 +3141,8 @@ void M_CharacterSelectTick(void)
setup_player[i].rotate--;
else if (setup_player[i].rotate < 0)
setup_player[i].rotate++;
else
setup_player[i].hitlag = false;
if (i >= setup_numplayers)
continue;