RingRacers/src/k_profiles.c
toaster 5b13d4f75d 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.
2022-05-22 16:57:26 +01:00

311 lines
No EOL
8.2 KiB
C

// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2020 by Sonic Team Junior.
//
// 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_profiles.c
/// \brief implements methods for profiles etc.
#include "d_main.h" // pandf
#include "k_profiles.h"
#include "z_zone.h"
// List of all the profiles.
static profile_t *profilesList[MAXPROFILES+1]; // +1 because we're gonna add a default "GUEST' profile.
static UINT8 numprofiles = 0; // # of loaded profiles
INT32 PR_GetNumProfiles(void)
{
return numprofiles;
}
profile_t* PR_MakeProfile(const char *prname, const char *pname, const char *sname, const UINT16 col, const char *fname, UINT16 fcol, INT32 controlarray[num_gamecontrols][MAXINPUTMAPPING])
{
profile_t *new = Z_Malloc(sizeof(profile_t), PU_STATIC, NULL);
UINT8 i;
new->version = PROFILEVER;
strcpy(new->profilename, prname);
new->profilename[sizeof new->profilename - 1] = '\0';
strcpy(new->skinname, sname);
strcpy(new->playername, pname);
new->color = col;
strcpy(new->follower, fname);
new->followercolor = fcol;
new->kickstartaccel = false;
// Copy from gamecontrol directly as we'll be setting controls up directly in the profile.
memcpy(new->controls, controlarray, sizeof(new->controls));
// Init both power levels
for (i = 0; i < PWRLV_NUMTYPES; i++)
new->powerlevels[i] = PWRLVRECORD_START;
return new;
}
profile_t* PR_MakeProfileFromPlayer(const char *prname, const char *pname, const char *sname, const UINT16 col, const char *fname, UINT16 fcol, UINT8 pnum)
{
// Generate profile using the player's gamecontrol, as we set them directly when making profiles from menus.
profile_t *new = PR_MakeProfile(prname, pname, sname, col, fname, fcol, gamecontrol[pnum]);
// Player bound cvars:
new->kickstartaccel = cv_kickstartaccel[pnum].value;
return new;
}
boolean PR_AddProfile(profile_t *p)
{
if (numprofiles < MAXPROFILES+1)
{
profilesList[numprofiles] = p;
numprofiles++;
CONS_Printf("Profile '%s' added\n", p->profilename);
return true;
}
else
return false;
}
profile_t* PR_GetProfile(INT32 num)
{
if (num < numprofiles)
return profilesList[num];
else
return NULL;
}
boolean PR_DeleteProfile(INT32 num)
{
UINT8 i, j;
if (num <= 0 || num > numprofiles)
return false;
// If we're deleting inbetween profiles, move everything.
if (num < numprofiles)
{
for (i = num; i < numprofiles-1; i++)
{
profilesList[i] = profilesList[i+1];
// Make sure to move cv_lastprofile values as well
for (j = 0; j < MAXSPLITSCREENPLAYERS; j++)
{
if (cv_lastprofile[j].value == num)
CV_StealthSetValue(&cv_lastprofile[j], 0); // If we were on the deleted profile, default back to guest.
else if (cv_lastprofile[j].value == i+1) // Otherwise, shift our lastprofile number down to match the new order.
CV_StealthSetValue(&cv_lastprofile[j], cv_lastprofile[j].value-1);
}
}
}
// In any case, delete the last profile as well.
profilesList[numprofiles] = NULL;
numprofiles--;
PR_SaveProfiles();
return true;
}
void PR_InitNewProfile(void)
{
char pname[PROFILENAMELEN+1] = "PRF";
profile_t *dprofile;
UINT8 usenum = numprofiles-1;
UINT8 i;
boolean nameok = false;
// When deleting profile, it's possible to do some pretty wacko stuff that would lead a new fresh profile to share the same name as another profile we have never changed the name of.
while (!nameok)
{
strcpy(pname, va("PRF%c", 'A'+usenum-1));
for (i = 0; i < numprofiles; i++)
{
profile_t *pr = PR_GetProfile(i);
if (!strcmp(pr->profilename, pname))
{
usenum++;
if (usenum > 'Z' -1)
usenum = 'A';
break;
}
// if we got here, then it means the name is okay!
if (i == numprofiles-1)
nameok = true;
}
}
dprofile = PR_MakeProfile(pname, PROFILEDEFAULTPNAME, PROFILEDEFAULTSKIN, PROFILEDEFAULTCOLOR, PROFILEDEFAULTFOLLOWER, PROFILEDEFAULTFOLLOWERCOLOR, gamecontroldefault);
PR_AddProfile(dprofile);
}
void PR_SaveProfiles(void)
{
FILE *f = NULL;
// save powerlevel in the current profile.
// granted we're using a profile that isn't guest, that is.
if (cv_currprofile.value > 0)
{
profile_t *pr = PR_GetProfile(cv_currprofile.value);
memcpy(&pr->powerlevels, vspowerlevel, sizeof(vspowerlevel));
}
f = fopen(va(pandf, srb2home, PROFILESFILE), "w");
if (f != NULL)
{
UINT8 i;
fwrite(&numprofiles, sizeof numprofiles, 1, f);
for (i = 1; i < numprofiles; ++i)
{
fwrite(profilesList[i], sizeof(profile_t), 1, f);
}
fclose(f);
}
else
I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?");
}
void PR_LoadProfiles(void)
{
FILE *f = NULL;
profile_t *dprofile = PR_MakeProfile(PROFILEDEFAULTNAME, PROFILEDEFAULTPNAME, PROFILEDEFAULTSKIN, PROFILEDEFAULTCOLOR, PROFILEDEFAULTFOLLOWER, PROFILEDEFAULTFOLLOWERCOLOR, gamecontroldefault);
f = fopen(va(pandf, srb2home, PROFILESFILE), "r");
if (f != NULL)
{
INT32 i;
fread(&numprofiles, sizeof numprofiles, 1, f);
for (i = 1; i < numprofiles; ++i)
{
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);
// Overwrite the first profile for the default profile to avoid letting anyone tamper with it.
profilesList[0] = dprofile;
}
else
{
// No profiles. Add the default one.
PR_AddProfile(dprofile);
}
}
void PR_ApplyProfile(UINT8 profilenum, UINT8 playernum)
{
profile_t *p = PR_GetProfile(profilenum);
// this CAN happen!!
if (p == NULL)
{
CONS_Printf("Profile '%d' could not be loaded as it does not exist. Guest Profile will be loaded instead.\n", profilenum);
profilenum = 0; // make sure to set this so that the cvar is set properly.
p = PR_GetProfile(0); // Use guest profile instead if things went south somehow.
}
CV_StealthSet(&cv_skin[playernum], p->skinname);
CV_StealthSetValue(&cv_playercolor[playernum], p->color);
CV_StealthSet(&cv_playername[playernum], p->playername);
// Followers
CV_StealthSet(&cv_follower[playernum], p->follower);
CV_StealthSetValue(&cv_followercolor[playernum], p->followercolor);
// toggles
CV_StealthSetValue(&cv_kickstartaccel[playernum], p->kickstartaccel);
// set controls...
memcpy(&gamecontrol[playernum], p->controls, sizeof(gamecontroldefault));
// set memory cvar
CV_StealthSetValue(&cv_lastprofile[playernum], profilenum);
// If we're doing this on P1, also change current profile.
// and update the powerlevel local array.
if (!playernum)
{
CV_StealthSetValue(&cv_currprofile, profilenum);
memcpy(&vspowerlevel, p->powerlevels, sizeof(p->powerlevels));
}
}
void PR_ApplyProfileLight(UINT8 profilenum, UINT8 playernum)
{
profile_t *p = PR_GetProfile(profilenum);
CV_StealthSet(&cv_skin[playernum], p->skinname);
CV_StealthSetValue(&cv_playercolor[playernum], p->color);
CV_StealthSet(&cv_playername[playernum], p->playername);
// Followers
CV_StealthSet(&cv_follower[playernum], p->follower);
CV_StealthSetValue(&cv_followercolor[playernum], p->followercolor);
}
UINT8 PR_GetProfileNum(profile_t *p)
{
UINT8 i;
for (i = 0; i < MAXPROFILES+1; i++)
{
profile_t *comp = PR_GetProfile(i);
if (comp == p)
return i;
}
return 0;
}
SINT8 PR_ProfileUsedBy(profile_t *p)
{
UINT8 i;
UINT8 prn = PR_GetProfileNum(p);
for (i=0; i < MAXSPLITSCREENPLAYERS; i++)
{
if (prn == cv_lastprofile[i].value)
return i;
}
return -1;
}