Merge branch 'gamedata-extension' into 'master'

Gamedata Extensions

See merge request KartKrew/Kart!1262
This commit is contained in:
Oni 2023-06-02 03:40:57 +00:00
commit 9724144e99
37 changed files with 1223 additions and 418 deletions

View file

@ -264,16 +264,12 @@ static bool ACS_GetStateFromString(const char *word, statenum_t *type)
--------------------------------------------------*/
static bool ACS_GetSkinFromString(const char *word, INT32 *type)
{
for (int i = 0; i < numskins; i++)
{
if (fastcmp(word, skins[i].name))
{
*type = i;
return true;
}
}
INT32 skin = R_SkinAvailable(word);
if (skin == -1)
return false;
return false;
*type = skin;
return true;
}
/*--------------------------------------------------
@ -293,7 +289,7 @@ static bool ACS_GetColorFromString(const char *word, skincolornum_t *type)
{
for (int i = 0; i < numskincolors; i++)
{
if (fastcmp(word, skins[i].name))
if (fastcmp(word, skincolors[i].name))
{
*type = static_cast<skincolornum_t>(i);
return true;

View file

@ -2051,7 +2051,7 @@ static void CV_SetValueMaybeStealth(consvar_t *var, INT32 value, boolean stealth
tmpskin = "None";
else
tmpskin = skins[value].name;
strncpy(val, tmpskin, SKINNAMESIZE);
strlcpy(val, tmpskin, SKINNAMESIZE+1);
}
else
sprintf(val, "%d", value);

View file

@ -1851,6 +1851,47 @@ void D_SRB2Main(void)
{
PR_ApplyProfile(cv_ttlprofilen.value, 0);
if (gamedata->importprofilewins == true)
{
profile_t *pr = PR_GetProfile(cv_ttlprofilen.value);
if (pr != NULL)
{
INT32 importskin = R_SkinAvailable(pr->skinname);
if (importskin != -1)
{
skins[importskin].records.wins = pr->wins;
cupheader_t *cup;
for (cup = kartcupheaders; cup; cup = cup->next)
{
for (i = 0; i < KARTGP_MAX; i++)
{
if (cup->windata[i].best_placement == 0)
continue;
cup->windata[i].best_skin.id = importskin;
cup->windata[i].best_skin.unloaded = NULL;
}
}
unloaded_cupheader_t *unloadedcup;
for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next)
{
for (i = 0; i < KARTGP_MAX; i++)
{
if (unloadedcup->windata[i].best_placement == 0)
continue;
unloadedcup->windata[i].best_skin.id = importskin;
unloadedcup->windata[i].best_skin.unloaded = NULL;
}
}
CONS_Printf(" Wins for profile \"%s\" imported onto character \"%s\"\n", pr->profilename, skins[importskin].name);
}
}
gamedata->importprofilewins = false;
}
for (i = 1; i < cv_splitplayers.value; i++)
{
PR_ApplyProfile(cv_lastprofile[i].value, i);

View file

@ -170,7 +170,7 @@ void clear_unlockables(void)
void clear_conditionsets(void)
{
UINT8 i;
UINT16 i;
for (i = 0; i < MAXCONDITIONSETS; ++i)
M_ClearConditionSet(i);
}
@ -192,8 +192,6 @@ void clear_levels(void)
P_DeleteHeaderFollowers(nummapheaders);
Z_Free(mapheaderinfo[nummapheaders]->mainrecord);
Patch_Free(mapheaderinfo[nummapheaders]->thumbnailPic);
Patch_Free(mapheaderinfo[nummapheaders]->minimapPic);
@ -952,7 +950,46 @@ void readlevelheader(MYFILE *f, char * name)
if (mapheaderinfo[num]->lumpname == NULL)
{
mapheaderinfo[num]->lumpname = Z_StrDup(name);
mapheaderinfo[num]->lumpnamehash = quickncasehash(name, MAXMAPLUMPNAME);
// Check to see if we have any custom map record data that we could substitute in.
unloaded_mapheader_t *unloadedmap, *prev = NULL;
for (unloadedmap = unloadedmapheaders; unloadedmap; prev = unloadedmap, unloadedmap = unloadedmap->next)
{
if (unloadedmap->lumpnamehash != mapheaderinfo[num]->lumpnamehash)
continue;
if (strcasecmp(name, unloadedmap->lumpname) != 0)
continue;
// Copy in mapvisited, time, lap, etc.
unloadedmap->records.mapvisited &= MV_MAX;
M_Memcpy(&mapheaderinfo[num]->records, &unloadedmap->records, sizeof(recorddata_t));
// Reuse the zone-allocated lumpname string.
mapheaderinfo[num]->lumpname = unloadedmap->lumpname;
// Remove this entry from the chain.
if (prev)
{
prev->next = unloadedmap->next;
}
else
{
unloadedmapheaders = unloadedmap->next;
}
// Finally, free.
Z_Free(unloadedmap);
break;
}
if (mapheaderinfo[num]->lumpname == NULL)
{
// If there was no string to reuse, dup our own.
mapheaderinfo[num]->lumpname = Z_StrDup(name);
}
}
do
@ -2280,9 +2317,9 @@ void readunlockable(MYFILE *f, INT32 num)
strupr(word2);
if (fastcmp(word, "CONDITIONSET"))
unlockables[num].conditionset = (UINT8)i;
unlockables[num].conditionset = (UINT16)i;
else if (fastcmp(word, "MAJORUNLOCK"))
unlockables[num].majorunlock = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
unlockables[num].majorunlock = (UINT8)(i != 0 || word2[0] == 'T' || word2[0] == 'Y');
else if (fastcmp(word, "TYPE"))
{
if (fastcmp(word2, "NONE"))
@ -2638,9 +2675,10 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
ty = UCRP_PODIUMCUP;
{
cupheader_t *cup = kartcupheaders;
UINT32 hash = quickncasehash(params[1], MAXCUPNAME);
while (cup)
{
if (!strcmp(cup->name, params[1]))
if (hash == cup->namehash && !strcmp(cup->name, params[1]))
break;
cup = cup->next;
}

View file

@ -465,39 +465,81 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
//
else if (fastcmp(word, "CUP"))
{
cupheader_t *cup = kartcupheaders;
cupheader_t *prev = NULL;
while (cup)
size_t len = strlen(word2);
if (len <= MAXCUPNAME-1)
{
if (fastcmp(cup->name, word2))
cupheader_t *cup = kartcupheaders;
cupheader_t *prev = NULL;
UINT32 hash = quickncasehash(word2, MAXCUPNAME);
while (cup)
{
// Only a major mod if editing stuff that isn't your own!
G_SetGameModified(multiplayer, true);
break;
if (hash == cup->namehash && fastcmp(cup->name, word2))
{
// Only a major mod if editing stuff that isn't your own!
G_SetGameModified(multiplayer, true);
break;
}
prev = cup;
cup = cup->next;
}
prev = cup;
cup = cup->next;
}
// Nothing found, add to the end.
if (!cup)
{
cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL);
cup->id = numkartcupheaders;
cup->monitor = 1;
deh_strlcpy(cup->name, word2,
sizeof(cup->name), va("Cup header %s: name", word2));
cup->namehash = hash;
// Nothing found, add to the end.
if (!cup)
// Check to see if we have any custom cup record data that we could substitute in.
unloaded_cupheader_t *unloadedcup, *unloadedprev = NULL;
for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedprev = unloadedcup, unloadedcup = unloadedcup->next)
{
if (unloadedcup->namehash != hash)
continue;
if (strcasecmp(word2, unloadedcup->name) != 0)
continue;
// Copy in standings, etc.
M_Memcpy(&cup->windata, &unloadedcup->windata, sizeof(cup->windata));
// Remove this entry from the chain.
if (unloadedprev)
{
unloadedprev->next = unloadedcup->next;
}
else
{
unloadedcupheaders = unloadedcup->next;
}
// Finally, free.
Z_Free(unloadedcup);
break;
}
if (prev != NULL)
prev->next = cup;
if (kartcupheaders == NULL)
kartcupheaders = cup;
numkartcupheaders++;
CONS_Printf("Added cup %d ('%s')\n", cup->id, cup->name);
}
readcupheader(f, cup);
}
else
{
cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL);
cup->id = numkartcupheaders;
cup->monitor = 1;
deh_strlcpy(cup->name, word2,
sizeof(cup->name), va("Cup header %s: name", word2));
if (prev != NULL)
prev->next = cup;
if (kartcupheaders == NULL)
kartcupheaders = cup;
numkartcupheaders++;
CONS_Printf("Added cup %d ('%s')\n", cup->id, cup->name);
deh_warning("Cup header's name %s is too long (%s characters VS %d max)", word2, sizeu1(len), (MAXCUPNAME-1));
ignorelines(f);
}
readcupheader(f, cup);
}
else if (fastcmp(word, "WEATHER") || fastcmp(word, "PRECIP") || fastcmp(word, "PRECIPITATION"))
{

View file

@ -112,12 +112,47 @@ extern preciptype_t curWeather;
/** Time attack information, currently a very small structure.
*/
struct skinrecord_t
{
UINT32 wins;
// Purely assistive in gamedata save processes
UINT32 _saveid;
};
struct unloaded_skin_t
{
char name[SKINNAMESIZE+1];
UINT32 namehash;
skinrecord_t records;
unloaded_skin_t *next;
};
extern unloaded_skin_t *unloadedskins;
struct skinreference_t
{
unloaded_skin_t *unloaded;
UINT8 id;
};
// mapvisited is now a set of flags that says what we've done in the map.
#define MV_VISITED (1)
#define MV_BEATEN (1<<1)
#define MV_ENCORE (1<<2)
#define MV_SPBATTACK (1<<3)
#define MV_MAX (MV_VISITED|MV_BEATEN|MV_ENCORE|MV_SPBATTACK)
#define MV_FINISHNEEDED (1<<7)
#define MV_PERSISTUNLOADED (MV_SPBATTACK|MV_FINISHNEEDED)
struct recorddata_t
{
UINT8 mapvisited;
tic_t time; ///< Time in which the level was finished.
tic_t lap; ///< Best lap time for this level.
//UINT32 score; ///< Score when the level was finished.
//UINT16 rings; ///< Rings when the level was finished.
};
#define KARTSPEED_AUTO -1
@ -142,16 +177,9 @@ struct cupwindata_t
UINT8 best_placement;
gp_rank_e best_grade;
boolean got_emerald;
skinreference_t best_skin;
};
// mapvisited is now a set of flags that says what we've done in the map.
#define MV_VISITED (1)
#define MV_BEATEN (1<<1)
#define MV_ENCORE (1<<2)
#define MV_SPBATTACK (1<<3)
#define MV_MAX (MV_VISITED|MV_BEATEN|MV_ENCORE|MV_SPBATTACK)
#define MV_MP ((MV_MAX+1)<<1)
// Set if homebrew PWAD stuff has been added.
extern boolean modifiedgame;
extern boolean majormods;
@ -363,11 +391,16 @@ struct customoption_t
#define CUPCACHE_SPECIAL (CUPCACHE_BONUS+MAXBONUSLIST)
#define CUPCACHE_MAX (CUPCACHE_SPECIAL+1)
#define MAXCUPNAME 16 // includes \0, for cleaner savedata
struct cupheader_t
{
UINT16 id; ///< Cup ID
UINT8 monitor; ///< Monitor graphic 1-9 or A-Z
char name[15]; ///< Cup title (14 chars)
char name[MAXCUPNAME]; ///< Cup title
UINT32 namehash; ///< Cup title hash
char icon[9]; ///< Name of the icon patch
char *levellist[CUPCACHE_MAX]; ///< List of levels that belong to this cup
INT16 cachedlevels[CUPCACHE_MAX]; ///< IDs in levellist, bonusgame, and specialstage
@ -381,6 +414,18 @@ struct cupheader_t
extern cupheader_t *kartcupheaders; // Start of cup linked list
extern UINT16 numkartcupheaders;
struct unloaded_cupheader_t
{
char name[MAXCUPNAME];
UINT32 namehash;
cupwindata_t windata[4];
unloaded_cupheader_t *next;
};
extern unloaded_cupheader_t *unloadedcupheaders;
#define MAXMAPLUMPNAME 64 // includes \0, for cleaner savedata
#define MAXSTAFF 3
@ -400,6 +445,7 @@ struct mapheader_t
{
// Core game information, not user-modifiable directly
char *lumpname; ///< Lump name can be really long
UINT32 lumpnamehash; ///< quickncasehash(->lumpname, MAXMAPLUMPNAME)
lumpnum_t lumpnum; ///< Lump number for the map, used by vres_GetMap
void *thumbnailPic; ///< Lump data for the level select thumbnail.
@ -411,8 +457,7 @@ struct mapheader_t
UINT8 ghostCount; ///< Count of valid staff ghosts
staffbrief_t *ghostBrief[MAXSTAFF]; ///< Mallocated array of names for each staff ghost
UINT8 mapvisited; ///< A set of flags that says what we've done in the map.
recorddata_t *mainrecord; ///< Stores best time attack data
recorddata_t records; ///< Stores completion/record attack data
cupheader_t *cup; ///< Cached cup
@ -496,6 +541,18 @@ struct mapheader_t
extern mapheader_t** mapheaderinfo;
extern INT32 nummapheaders, mapallocsize;
struct unloaded_mapheader_t
{
char *lumpname;
UINT32 lumpnamehash;
recorddata_t records;
unloaded_mapheader_t *next;
};
extern unloaded_mapheader_t *unloadedmapheaders;
// Gametypes
#define NUMGAMETYPEFREESLOTS (128)
#define MAXGAMETYPELENGTH (32)

View file

@ -200,10 +200,14 @@ mapheader_t** mapheaderinfo = {NULL};
INT32 nummapheaders = 0;
INT32 mapallocsize = 0;
unloaded_mapheader_t *unloadedmapheaders = NULL;
// Kart cup definitions
cupheader_t *kartcupheaders = NULL;
UINT16 numkartcupheaders = 0;
unloaded_cupheader_t *unloadedcupheaders = NULL;
static boolean exitgame = false;
static boolean retrying = false;
static boolean retryingmodeattack = false;
@ -444,44 +448,60 @@ consvar_t cv_rumble[MAXSPLITSCREENPLAYERS] = {
char player_names[MAXPLAYERS][MAXPLAYERNAME+1];
INT32 player_name_changes[MAXPLAYERS];
// Allocation for time and nights data
void G_AllocMainRecordData(INT16 i)
{
if (i > nummapheaders || !mapheaderinfo[i])
I_Error("G_AllocMainRecordData: Internal map ID %d not found (nummapheaders = %d)\n", i, nummapheaders);
if (!mapheaderinfo[i]->mainrecord)
mapheaderinfo[i]->mainrecord = Z_Malloc(sizeof(recorddata_t), PU_STATIC, NULL);
memset(mapheaderinfo[i]->mainrecord, 0, sizeof(recorddata_t));
}
// MAKE SURE YOU SAVE DATA BEFORE CALLING THIS
void G_ClearRecords(void)
{
INT16 i;
cupheader_t *cup;
UINT16 i;
for (i = 0; i < nummapheaders; ++i)
for (i = 0; i < numskins; i++)
{
if (mapheaderinfo[i]->mainrecord)
{
Z_Free(mapheaderinfo[i]->mainrecord);
mapheaderinfo[i]->mainrecord = NULL;
}
memset(&skins[i].records, 0, sizeof(skins[i].records));
}
for (i = 0; i < nummapheaders; i++)
{
memset(&mapheaderinfo[i]->records, 0, sizeof(recorddata_t));
}
cupheader_t *cup;
for (cup = kartcupheaders; cup; cup = cup->next)
{
memset(&cup->windata, 0, sizeof(cup->windata));
}
unloaded_skin_t *unloadedskin, *nextunloadedskin = NULL;
for (unloadedskin = unloadedskins; unloadedskin; unloadedskin = nextunloadedskin)
{
nextunloadedskin = unloadedskin->next;
Z_Free(unloadedskin);
}
unloadedskins = NULL;
unloaded_mapheader_t *unloadedmap, *nextunloadedmap = NULL;
for (unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = nextunloadedmap)
{
nextunloadedmap = unloadedmap->next;
Z_Free(unloadedmap->lumpname);
Z_Free(unloadedmap);
}
unloadedmapheaders = NULL;
unloaded_cupheader_t *unloadedcup, *nextunloadedcup = NULL;
for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = nextunloadedcup)
{
nextunloadedcup = unloadedcup->next;
Z_Free(unloadedcup);
}
unloadedcupheaders = NULL;
}
// For easy retrieval of records
tic_t G_GetBestTime(INT16 map)
{
if (!mapheaderinfo[map] || !mapheaderinfo[map]->mainrecord || mapheaderinfo[map]->mainrecord->time <= 0)
if (!mapheaderinfo[map] || mapheaderinfo[map]->records.time <= 0)
return (tic_t)UINT32_MAX;
return mapheaderinfo[map]->mainrecord->time;
return mapheaderinfo[map]->records.time;
}
// BE RIGHT BACK
@ -490,10 +510,10 @@ tic_t G_GetBestTime(INT16 map)
/*
tic_t G_GetBestLap(INT16 map)
{
if (!mapheaderinfo[map] || !mapheaderinfo[map]->mainrecord || mapheaderinfo[map]->mainrecord->lap <= 0)
if (!mapheaderinfo[map] || mapheaderinfo[map]->records.lap <= 0)
return (tic_t)UINT32_MAX;
return mapheaderinfo[map]->mainrecord->lap;
return mapheaderinfo[map]->records.lap;
}
*/
@ -621,32 +641,28 @@ void G_UpdateRecords(void)
{
UINT8 earnedEmblems;
// Record new best time
if (!mapheaderinfo[gamemap-1]->mainrecord)
G_AllocMainRecordData(gamemap-1);
if (modeattacking & ATTACKING_TIME)
{
tic_t time = players[consoleplayer].realtime;
if (players[consoleplayer].pflags & PF_NOCONTEST)
time = UINT32_MAX;
if (((mapheaderinfo[gamemap-1]->mainrecord->time == 0) || (time < mapheaderinfo[gamemap-1]->mainrecord->time))
if (((mapheaderinfo[gamemap-1]->records.time == 0) || (time < mapheaderinfo[gamemap-1]->records.time))
&& (time < UINT32_MAX)) // DNF
mapheaderinfo[gamemap-1]->mainrecord->time = time;
mapheaderinfo[gamemap-1]->records.time = time;
}
else
{
mapheaderinfo[gamemap-1]->mainrecord->time = 0;
mapheaderinfo[gamemap-1]->records.time = 0;
}
if (modeattacking & ATTACKING_LAP)
{
if ((mapheaderinfo[gamemap-1]->mainrecord->lap == 0) || (bestlap < mapheaderinfo[gamemap-1]->mainrecord->lap))
mapheaderinfo[gamemap-1]->mainrecord->lap = bestlap;
if ((mapheaderinfo[gamemap-1]->records.lap == 0) || (bestlap < mapheaderinfo[gamemap-1]->records.lap))
mapheaderinfo[gamemap-1]->records.lap = bestlap;
}
else
{
mapheaderinfo[gamemap-1]->mainrecord->lap = 0;
mapheaderinfo[gamemap-1]->records.lap = 0;
}
// Check emblems when level data is updated
@ -807,9 +823,13 @@ INT32 G_MapNumber(const char * name)
#endif
{
INT32 map;
UINT32 hash = quickncasehash(name, MAXMAPLUMPNAME);
for (map = 0; map < nummapheaders; ++map)
{
if (hash != mapheaderinfo[map]->lumpnamehash)
continue;
if (strcasecmp(mapheaderinfo[map]->lumpname, name) != 0)
continue;
@ -3982,16 +4002,16 @@ static void G_UpdateVisited(void)
return;
// Update visitation flags
mapheaderinfo[prevmap]->mapvisited |= MV_BEATEN;
mapheaderinfo[prevmap]->records.mapvisited |= MV_BEATEN;
if (encoremode == true)
{
mapheaderinfo[prevmap]->mapvisited |= MV_ENCORE;
mapheaderinfo[prevmap]->records.mapvisited |= MV_ENCORE;
}
if (modeattacking & ATTACKING_SPB)
{
mapheaderinfo[prevmap]->mapvisited |= MV_SPBATTACK;
mapheaderinfo[prevmap]->records.mapvisited |= MV_SPBATTACK;
}
if (modeattacking)
@ -4446,6 +4466,7 @@ static void G_DoCompleted(void)
grandprixinfo.rank.prisons += numtargets;
grandprixinfo.rank.position = MAXPLAYERS;
grandprixinfo.rank.skin = MAXSKINS;
for (i = 0; i < MAXPLAYERS; i++)
{
@ -4475,7 +4496,12 @@ static void G_DoCompleted(void)
if (players[i].bot == false)
{
grandprixinfo.rank.position = min(grandprixinfo.rank.position, K_GetPodiumPosition(&players[i]));
UINT8 podiumposition = K_GetPodiumPosition(&players[i]);
if (podiumposition <= grandprixinfo.rank.position)
{
grandprixinfo.rank.position = podiumposition;
grandprixinfo.rank.skin = players[i].skin;
}
}
}
}
@ -4746,7 +4772,7 @@ void G_LoadGameSettings(void)
}
#define GD_VERSIONCHECK 0xBA5ED123 // Change every major version, as usual
#define GD_VERSIONMINOR 2 // Change every format update
#define GD_VERSIONMINOR 3 // Change every format update
static const char *G_GameDataFolder(void)
{
@ -4767,7 +4793,13 @@ void G_LoadGameData(void)
boolean gridunusable = false;
savebuffer_t save = {0};
UINT16 emblemreadcount = MAXEMBLEMS;
UINT16 unlockreadcount = MAXUNLOCKABLES;
UINT16 conditionreadcount = MAXCONDITIONSETS;
size_t unlockreadsize = sizeof(UINT16);
//For records
UINT32 numgamedataskins;
UINT32 numgamedatamapheaders;
UINT32 numgamedatacups;
@ -4819,6 +4851,12 @@ void G_LoadGameData(void)
P_SaveBufferFree(&save);
I_Error("Game data is from the future! (expected %d, got %d)\nRename or delete %s (maybe in %s) and try again.", GD_VERSIONMINOR, versionMinor, gamedatafilename, gdfolder);
}
else if (versionMinor < GD_VERSIONMINOR)
{
// We're converting - let'd create a backup.
FIL_WriteFile(va("%s" PATHSEP "%s.bak", srb2home, gamedatafilename), save.buffer, save.size);
}
if ((versionMinor == 0 || versionMinor == 1)
#ifdef DEVELOP
|| M_CheckParm("-resetchallengegrid")
@ -4846,7 +4884,16 @@ void G_LoadGameData(void)
gamedata->pendingkeyrounds = READUINT32(save.p);
gamedata->pendingkeyroundoffset = READUINT8(save.p);
gamedata->keyspending = READUINT8(save.p);
if (versionMinor < 3)
{
gamedata->keyspending = READUINT8(save.p);
}
else
{
gamedata->keyspending = READUINT16(save.p);
}
gamedata->chaokeys = READUINT16(save.p);
gamedata->everloadedaddon = (boolean)READUINT8(save.p);
@ -4870,32 +4917,39 @@ void G_LoadGameData(void)
}
}
if (versionMinor < 3)
{
emblemreadcount = 512;
unlockreadcount = conditionreadcount = UINT8_MAX;
unlockreadsize = sizeof(UINT8);
}
// To save space, use one bit per collected/achieved/unlocked flag
for (i = 0; i < MAXEMBLEMS;)
for (i = 0; i < emblemreadcount;)
{
rtemp = READUINT8(save.p);
for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
for (j = 0; j < 8 && j+i < emblemreadcount; ++j)
gamedata->collected[j+i] = ((rtemp >> j) & 1);
i += j;
}
for (i = 0; i < MAXUNLOCKABLES;)
for (i = 0; i < unlockreadcount;)
{
rtemp = READUINT8(save.p);
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
for (j = 0; j < 8 && j+i < unlockreadcount; ++j)
gamedata->unlocked[j+i] = ((rtemp >> j) & 1);
i += j;
}
for (i = 0; i < MAXUNLOCKABLES;)
for (i = 0; i < unlockreadcount;)
{
rtemp = READUINT8(save.p);
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
for (j = 0; j < 8 && j+i < unlockreadcount; ++j)
gamedata->unlockpending[j+i] = ((rtemp >> j) & 1);
i += j;
}
for (i = 0; i < MAXCONDITIONSETS;)
for (i = 0; i < conditionreadcount;)
{
rtemp = READUINT8(save.p);
for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
for (j = 0; j < 8 && j+i < conditionreadcount; ++j)
gamedata->achieved[j+i] = ((rtemp >> j) & 1);
i += j;
}
@ -4904,7 +4958,7 @@ void G_LoadGameData(void)
{
UINT16 burn = READUINT16(save.p); // Previous challengegridwidth
UINT8 height = (versionMinor > 0) ? CHALLENGEGRIDHEIGHT : 5;
save.p += (burn * height * sizeof(UINT8)); // Step over previous grid data
save.p += (burn * height * unlockreadsize); // Step over previous grid data
gamedata->challengegridwidth = 0;
Z_Free(gamedata->challengegrid);
@ -4917,11 +4971,23 @@ void G_LoadGameData(void)
if (gamedata->challengegridwidth)
{
gamedata->challengegrid = Z_Malloc(
(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)),
(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT16)),
PU_STATIC, NULL);
for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++)
if (unlockreadsize == sizeof(UINT8))
{
gamedata->challengegrid[i] = READUINT8(save.p);
for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++)
{
gamedata->challengegrid[i] = READUINT8(save.p);
if (gamedata->challengegrid[i] == unlockreadcount)
gamedata->challengegrid[i] = MAXUNLOCKABLES;
}
}
else
{
for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++)
{
gamedata->challengegrid[i] = READUINT16(save.p);
}
}
}
else
@ -4933,44 +4999,129 @@ void G_LoadGameData(void)
gamedata->timesBeaten = READUINT32(save.p);
// Main records
skinreference_t *tempskinreferences = NULL;
if (versionMinor < 3)
{
gamedata->importprofilewins = true;
numgamedataskins = 0;
}
else
{
numgamedataskins = READUINT32(save.p);
if (numgamedataskins)
{
tempskinreferences = Z_Malloc(
numgamedataskins * sizeof (skinreference_t),
PU_STATIC,
NULL
);
for (i = 0; i < numgamedataskins; i++)
{
char skinname[SKINNAMESIZE+1];
INT32 skin;
READSTRINGN(save.p, skinname, SKINNAMESIZE);
skin = R_SkinAvailable(skinname);
skinrecord_t dummyrecord;
dummyrecord.wins = READUINT32(save.p);
dummyrecord._saveid = i;
CONS_Printf(" (TEMPORARY DISPLAY) skinname is \"%s\", has %u wins\n", skinname, dummyrecord.wins);
tempskinreferences[i].id = MAXSKINS;
if (skin != -1)
{
// We found a skin, so assign the win.
M_Memcpy(&skins[skin].records, &dummyrecord, sizeof(skinrecord_t));
tempskinreferences[i].id = skin;
tempskinreferences[i].unloaded = NULL;
}
else if (dummyrecord.wins)
{
// Invalid, but we don't want to lose all the juicy statistics.
// Instead, update a FILO linked list of "unloaded skins".
unloaded_skin_t *unloadedskin =
Z_Malloc(
sizeof(unloaded_skin_t),
PU_STATIC, NULL
);
// Establish properties, for later retrieval on file add.
strlcpy(unloadedskin->name, skinname, sizeof(unloadedskin->name));
unloadedskin->namehash = quickncasehash(unloadedskin->name, SKINNAMESIZE);
// Insert at the head, just because it's convenient.
unloadedskin->next = unloadedskins;
unloadedskins = unloadedskin;
// Finally, copy into.
M_Memcpy(&unloadedskin->records, &dummyrecord, sizeof(skinrecord_t));
tempskinreferences[i].unloaded = unloadedskin;
}
}
}
}
numgamedatamapheaders = READUINT32(save.p);
if (numgamedatamapheaders >= NEXTMAP_SPECIAL)
goto datacorrupt;
for (i = 0; i < numgamedatamapheaders; i++)
{
char mapname[MAXMAPLUMPNAME];
INT16 mapnum;
tic_t rectime;
tic_t reclap;
READSTRINGN(save.p, mapname, sizeof(mapname));
READSTRINGL(save.p, mapname, MAXMAPLUMPNAME);
mapnum = G_MapNumber(mapname);
rtemp = READUINT8(save.p);
rectime = (tic_t)READUINT32(save.p);
reclap = (tic_t)READUINT32(save.p);
recorddata_t dummyrecord;
dummyrecord.mapvisited = READUINT8(save.p);
dummyrecord.time = (tic_t)READUINT32(save.p);
dummyrecord.lap = (tic_t)READUINT32(save.p);
if (mapnum < nummapheaders && mapheaderinfo[mapnum])
{
// Valid mapheader, time to populate with record data.
if ((mapheaderinfo[mapnum]->mapvisited = rtemp) & ~MV_MAX)
goto datacorrupt;
if (rectime || reclap)
{
G_AllocMainRecordData((INT16)i);
mapheaderinfo[i]->mainrecord->time = rectime;
mapheaderinfo[i]->mainrecord->lap = reclap;
//CONS_Printf("ID %d, Time = %d, Lap = %d\n", i, rectime/35, reclap/35);
}
dummyrecord.mapvisited &= MV_MAX;
M_Memcpy(&mapheaderinfo[mapnum]->records, &dummyrecord, sizeof(recorddata_t));
}
else
else if (
((dummyrecord.mapvisited & MV_PERSISTUNLOADED) != 0
&& (dummyrecord.mapvisited & MV_BEATEN) != 0)
|| dummyrecord.time != 0
|| dummyrecord.lap != 0
)
{
// Since it's not worth declaring the entire gamedata
// corrupt over extra maps, we report and move on.
CONS_Alert(CONS_WARNING, "Map with lumpname %s does not exist, time record data will be discarded\n", mapname);
// Invalid, but we don't want to lose all the juicy statistics.
// Instead, update a FILO linked list of "unloaded mapheaders".
unloaded_mapheader_t *unloadedmap =
Z_Malloc(
sizeof(unloaded_mapheader_t),
PU_STATIC, NULL
);
// Establish properties, for later retrieval on file add.
unloadedmap->lumpname = Z_StrDup(mapname);
unloadedmap->lumpnamehash = quickncasehash(unloadedmap->lumpname, MAXMAPLUMPNAME);
// Insert at the head, just because it's convenient.
unloadedmap->next = unloadedmapheaders;
unloadedmapheaders = unloadedmap;
// Finally, copy into.
M_Memcpy(&unloadedmap->records, &dummyrecord, sizeof(recorddata_t));
}
}
@ -4980,15 +5131,22 @@ void G_LoadGameData(void)
for (i = 0; i < numgamedatacups; i++)
{
char cupname[16];
char cupname[MAXCUPNAME];
cupheader_t *cup;
cupwindata_t dummywindata[4];
// Find the relevant cup.
READSTRINGN(save.p, cupname, sizeof(cupname));
READSTRINGL(save.p, cupname, sizeof(cupname));
UINT32 hash = quickncasehash(cupname, MAXCUPNAME);
for (cup = kartcupheaders; cup; cup = cup->next)
{
if (cup->namehash != hash)
continue;
if (strcmp(cup->name, cupname))
continue;
break;
}
@ -4997,23 +5155,92 @@ void G_LoadGameData(void)
{
rtemp = READUINT8(save.p);
// ...but only record it if we actually found the associated cup.
if (cup)
{
cup->windata[j].best_placement = (rtemp & 0x0F);
cup->windata[j].best_grade = (rtemp & 0x70)>>4;
if (rtemp & 0x80)
{
if (j == 0)
goto datacorrupt;
dummywindata[j].best_placement = (rtemp & 0x0F);
dummywindata[j].best_grade = (rtemp & 0x70)>>4;
dummywindata[j].got_emerald = !!(rtemp & 0x80);
cup->windata[j].got_emerald = true;
dummywindata[j].best_skin.id = MAXSKINS;
dummywindata[j].best_skin.unloaded = NULL;
if (versionMinor >= 3)
{
UINT32 _saveid = READUINT32(save.p);
if (_saveid < numgamedataskins)
{
M_Memcpy(&dummywindata[j].best_skin, &tempskinreferences[_saveid], sizeof(dummywindata[j].best_skin));
}
}
}
if (versionMinor < 3)
{
// We now require backfilling of placement information.
cupwindata_t bestwindata;
bestwindata.best_placement = 0;
j = KARTGP_MAX;
while (j > 0)
{
j--;
if (bestwindata.best_placement == 0)
{
if (dummywindata[j].best_placement != 0)
{
M_Memcpy(&bestwindata, &dummywindata[j], sizeof(bestwindata));
}
continue;
}
if (dummywindata[j].best_placement != 0)
{
if (dummywindata[j].best_placement < bestwindata.best_placement)
bestwindata.best_placement = dummywindata[j].best_placement;
if (dummywindata[j].best_grade > bestwindata.best_grade)
bestwindata.best_grade = dummywindata[j].best_grade;
bestwindata.got_emerald |= dummywindata[j].got_emerald;
}
M_Memcpy(&dummywindata[j], &bestwindata, sizeof(dummywindata[j]));
}
}
if (cup)
{
// We found a cup, so assign the windata.
M_Memcpy(&cup->windata, &dummywindata, sizeof(cup->windata));
}
else if (dummywindata[0].best_placement != 0)
{
// Invalid, but we don't want to lose all the juicy statistics.
// Instead, update a FILO linked list of "unloaded cupheaders".
unloaded_cupheader_t *unloadedcup =
Z_Malloc(
sizeof(unloaded_cupheader_t),
PU_STATIC, NULL
);
// Establish properties, for later retrieval on file add.
strlcpy(unloadedcup->name, cupname, sizeof(unloadedcup->name));
unloadedcup->namehash = quickncasehash(unloadedcup->name, MAXCUPNAME);
// Insert at the head, just because it's convenient.
unloadedcup->next = unloadedcupheaders;
unloadedcupheaders = unloadedcup;
// Finally, copy into.
M_Memcpy(&unloadedcup->windata, &dummywindata, sizeof(unloadedcup->windata));
}
}
}
if (tempskinreferences)
Z_Free(tempskinreferences);
// done
P_SaveBufferFree(&save);
@ -5074,7 +5301,7 @@ void G_DirtyGameData(void)
void G_SaveGameData(void)
{
size_t length;
INT32 i, j, numcups;
INT32 i, j;
cupheader_t *cup;
UINT8 btemp;
savebuffer_t save = {0};
@ -5095,7 +5322,7 @@ void G_SaveGameData(void)
length = (4+1+1+
4+4+
(4*GDGT_MAX)+
4+1+1+2+
4+1+2+2+
1+1+1+
4+
(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+
@ -5103,16 +5330,105 @@ void G_SaveGameData(void)
if (gamedata->challengegrid)
{
length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT;
length += (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT) * 2;
}
length += 4 + (nummapheaders * (MAXMAPLUMPNAME+1+4+4));
numcups = 0;
UINT32 numgamedataskins = 0;
unloaded_skin_t *unloadedskin;
for (i = 0; i < numskins; i++)
{
// It's safe to assume a skin with no wins will have no other data worth keeping
if (skins[i].records.wins == 0)
{
continue;
}
numgamedataskins++;
}
for (unloadedskin = unloadedskins; unloadedskin; unloadedskin = unloadedskin->next)
{
// Ditto, with the exception that we should warn about it.
if (unloadedskin->records.wins == 0)
{
CONS_Alert(CONS_WARNING, "Unloaded skin \"%s\" has no wins!\n", unloadedskin->name);
continue;
}
numgamedataskins++;
}
length += 4 + (numgamedataskins * (SKINNAMESIZE+4));
UINT32 numgamedatamapheaders = 0;
unloaded_mapheader_t *unloadedmap;
for (i = 0; i < nummapheaders; i++)
{
// It's safe to assume a level with no mapvisited will have no other data worth keeping, since you get MV_VISITED just for opening it.
if (!(mapheaderinfo[i]->records.mapvisited & MV_MAX))
{
continue;
}
numgamedatamapheaders++;
}
for (unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = unloadedmap->next)
{
// Ditto, with the exception that we should warn about it.
if (!(unloadedmap->records.mapvisited & MV_MAX))
{
CONS_Alert(CONS_WARNING, "Unloaded map \"%s\" has no mapvisited!\n", unloadedmap->lumpname);
continue;
}
// It's far off on the horizon, beyond many memory limits, but prevent a potential misery moment of losing ALL your data.
if (++numgamedatamapheaders == UINT32_MAX)
{
CONS_Alert(CONS_WARNING, "Some unloaded map record data has been dropped due to datatype limitations.\n");
break;
}
}
length += 4 + (numgamedatamapheaders * (MAXMAPLUMPNAME+1+4+4));
UINT32 numgamedatacups = 0;
unloaded_cupheader_t *unloadedcup;
for (cup = kartcupheaders; cup; cup = cup->next)
{
numcups++;
// Results are populated downwards, so no Easy win
// means there's no important player data to save.
if (cup->windata[0].best_placement == 0)
continue;
numgamedatacups++;
}
length += 4 + (numcups * (4+16));
for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next)
{
// Ditto, with the exception that we should warn about it.
if (unloadedcup->windata[0].best_placement == 0)
{
CONS_Alert(CONS_WARNING, "Unloaded cup \"%s\" has no windata!\n", unloadedcup->name);
continue;
}
// It's far off on the horizon, beyond many memory limits, but prevent a potential misery moment of losing ALL your data.
if (++numgamedatacups == UINT32_MAX)
{
CONS_Alert(CONS_WARNING, "Some unloaded cup standings data has been dropped due to datatype limitations.\n");
break;
}
}
length += 4 + (numgamedatacups * (MAXCUPNAME + 4*(1+4)));
if (P_SaveBufferAlloc(&save, length) == false)
{
@ -5141,7 +5457,7 @@ void G_SaveGameData(void)
WRITEUINT32(save.p, gamedata->pendingkeyrounds); // 4
WRITEUINT8(save.p, gamedata->pendingkeyroundoffset); // 1
WRITEUINT8(save.p, gamedata->keyspending); // 1
WRITEUINT16(save.p, gamedata->keyspending); // 2
WRITEUINT16(save.p, gamedata->chaokeys); // 2
WRITEUINT8(save.p, gamedata->everloadedaddon); // 1
@ -5187,12 +5503,12 @@ void G_SaveGameData(void)
i += j;
}
if (gamedata->challengegrid) // 2 + gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT
if (gamedata->challengegrid) // 2 + (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT) * 2
{
WRITEUINT16(save.p, gamedata->challengegridwidth);
for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++)
{
WRITEUINT8(save.p, gamedata->challengegrid[i]);
WRITEUINT16(save.p, gamedata->challengegrid[i]);
}
}
else // 2
@ -5203,45 +5519,161 @@ void G_SaveGameData(void)
WRITEUINT32(save.p, gamedata->timesBeaten); // 4
// Main records
WRITEUINT32(save.p, nummapheaders); // 4
for (i = 0; i < nummapheaders; i++) // nummapheaders * (255+1+4+4)
WRITEUINT32(save.p, numgamedataskins); // 4
{
// For figuring out which header to assign it to on load
WRITESTRINGN(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME);
// numgamedataskins * (SKINNAMESIZE+4)
WRITEUINT8(save.p, (mapheaderinfo[i]->mapvisited & MV_MAX));
UINT32 maxid = 0;
if (mapheaderinfo[i]->mainrecord)
for (i = 0; i < numskins; i++)
{
WRITEUINT32(save.p, mapheaderinfo[i]->mainrecord->time);
WRITEUINT32(save.p, mapheaderinfo[i]->mainrecord->lap);
if (skins[i].records.wins == 0)
continue;
WRITESTRINGN(save.p, skins[i].name, SKINNAMESIZE);
WRITEUINT32(save.p, skins[i].records.wins);
skins[i].records._saveid = maxid;
if (++maxid == numgamedataskins)
break;
}
else
if (maxid < numgamedataskins)
{
WRITEUINT32(save.p, 0);
WRITEUINT32(save.p, 0);
for (unloadedskin = unloadedskins; unloadedskin; unloadedskin = unloadedskin->next)
{
if (unloadedskin->records.wins == 0)
continue;
WRITESTRINGN(save.p, unloadedskin->name, SKINNAMESIZE);
WRITEUINT32(save.p, unloadedskin->records.wins);
unloadedskin->records._saveid = maxid;
if (++maxid == numgamedataskins)
break;
}
}
}
WRITEUINT32(save.p, numcups); // 4
#define GETSKINREFSAVEID(ref, var) \
{ \
if (ref.unloaded != NULL) \
var = ref.unloaded->records._saveid;\
else if (ref.id < numskins)\
var = skins[ref.id].records._saveid; \
else \
var = UINT32_MAX; \
}
for (cup = kartcupheaders; cup; cup = cup->next)
WRITEUINT32(save.p, numgamedatamapheaders); // 4
if (numgamedatamapheaders)
{
// For figuring out which header to assign it to on load
WRITESTRINGN(save.p, cup->name, 16);
// numgamedatamapheaders * (MAXMAPLUMPNAME+1+4+4)
for (i = 0; i < KARTGP_MAX; i++)
for (i = 0; i < nummapheaders; i++)
{
btemp = min(cup->windata[i].best_placement, 0x0F);
btemp |= (cup->windata[i].best_grade<<4);
if (i != 0 && cup->windata[i].got_emerald == true)
btemp |= 0x80;
if (!(mapheaderinfo[i]->records.mapvisited & MV_MAX))
continue;
WRITEUINT8(save.p, btemp); // 4 * numcups
WRITESTRINGL(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME);
UINT8 mapvisitedtemp = (mapheaderinfo[i]->records.mapvisited & MV_MAX);
if ((mapheaderinfo[i]->menuflags & LF2_FINISHNEEDED))
{
mapvisitedtemp |= MV_FINISHNEEDED;
}
WRITEUINT8(save.p, mapvisitedtemp);
WRITEUINT32(save.p, mapheaderinfo[i]->records.time);
WRITEUINT32(save.p, mapheaderinfo[i]->records.lap);
if (--numgamedatamapheaders == 0)
break;
}
if (numgamedatamapheaders)
{
for (unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = unloadedmap->next)
{
if (!(unloadedmap->records.mapvisited & MV_MAX))
continue;
WRITESTRINGL(save.p, unloadedmap->lumpname, MAXMAPLUMPNAME);
WRITEUINT8(save.p, unloadedmap->records.mapvisited);
WRITEUINT32(save.p, unloadedmap->records.time);
WRITEUINT32(save.p, unloadedmap->records.lap);
if (--numgamedatamapheaders == 0)
break;
}
}
}
WRITEUINT32(save.p, numgamedatacups); // 4
if (numgamedatacups)
{
// numgamedatacups * (MAXCUPNAME + 4*(1+4))
#define WRITECUPWINDATA(maybeunloadedcup) \
for (i = 0; i < KARTGP_MAX; i++) \
{ \
btemp = min(maybeunloadedcup->windata[i].best_placement, 0x0F); \
btemp |= (maybeunloadedcup->windata[i].best_grade<<4); \
if (maybeunloadedcup->windata[i].got_emerald == true) \
btemp |= 0x80; \
\
WRITEUINT8(save.p, btemp); \
\
GETSKINREFSAVEID(maybeunloadedcup->windata[i].best_skin, j); \
\
WRITEUINT32(save.p, j); \
}
for (cup = kartcupheaders; cup; cup = cup->next)
{
if (cup->windata[0].best_placement == 0)
continue;
WRITESTRINGL(save.p, cup->name, MAXCUPNAME);
WRITECUPWINDATA(cup);
if (--numgamedatacups == 0)
break;
}
if (numgamedatacups)
{
for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next)
{
if (unloadedcup->windata[0].best_placement == 0)
continue;
WRITESTRINGL(save.p, unloadedcup->name, MAXCUPNAME);
WRITECUPWINDATA(unloadedcup);
if (--numgamedatacups == 0)
break;
}
}
#undef WRITECUPWINDATA
}
#undef GETSKINREFSAVEID
length = save.p - save.buffer;
FIL_WriteFile(va(pandf, srb2home, gamedatafilename), save.buffer, length);

View file

@ -264,7 +264,6 @@ void G_SetGameModified(boolean silent, boolean major);
void G_SetUsedCheats(void);
// Gamedata record shit
void G_AllocMainRecordData(INT16 i);
void G_ClearRecords(void);
tic_t G_GetBestTime(INT16 map);

View file

@ -570,17 +570,15 @@ void HWR_InitModels(void)
addskinmodel:
// add player model
for (s = 0; s < MAXSKINS; s++)
s = R_SkinAvailable(skinname);
if (s != -1)
{
if (stricmp(skinname, skins[s].name) == 0)
{
md2_playermodels[s].skin = s;
md2_playermodels[s].scale = scale;
md2_playermodels[s].offset = offset;
md2_playermodels[s].notfound = false;
strcpy(md2_playermodels[s].filename, filename);
goto modelfound;
}
md2_playermodels[s].skin = s;
md2_playermodels[s].scale = scale;
md2_playermodels[s].offset = offset;
md2_playermodels[s].notfound = false;
strcpy(md2_playermodels[s].filename, filename);
goto modelfound;
}
// no sprite/player skin name found?!?D

View file

@ -48,7 +48,7 @@ boolean K_FollowerUsable(INT32 skinnum)
{
// Unlike R_SkinUsable, not netsynced.
// Solely used to prevent an invalid value being sent over the wire.
UINT8 i;
UINT16 i;
INT32 fid;
if (skinnum == -1 || demo.playback)

View file

@ -815,10 +815,13 @@ void K_PlayerFinishGrandPrix(player_t *player)
grandprixinfo.wonround = true;
// Increase your total rings
if (RINGTOTAL(player) > 0)
INT32 ringtotal = RINGTOTAL(player);
if (ringtotal > 0)
{
player->totalring += RINGTOTAL(player);
grandprixinfo.rank.rings += RINGTOTAL(player);
if (ringtotal > 20)
ringtotal = 20;
player->totalring += ringtotal;
grandprixinfo.rank.rings += ringtotal;
}
if (grandprixinfo.eventmode == GPEVENT_NONE)

View file

@ -81,7 +81,7 @@ static patch_t *kp_nocontestminimap;
static patch_t *kp_spbminimap;
static patch_t *kp_wouldyoustillcatchmeifiwereaworm;
static patch_t *kp_catcherminimap;
static patch_t *kp_emeraldminimap;
static patch_t *kp_emeraldminimap[2];
static patch_t *kp_capsuleminimap[3];
static patch_t *kp_ringsticker[2];
@ -358,7 +358,8 @@ void K_LoadKartHUDGraphics(void)
HU_UpdatePatch(&kp_wouldyoustillcatchmeifiwereaworm, "MINIPROG");
HU_UpdatePatch(&kp_catcherminimap, "UFOMAP");
HU_UpdatePatch(&kp_emeraldminimap, "EMEMAP");
HU_UpdatePatch(&kp_emeraldminimap[0], "EMEMAP");
HU_UpdatePatch(&kp_emeraldminimap[1], "SUPMAP");
HU_UpdatePatch(&kp_capsuleminimap[0], "MINICAP1");
HU_UpdatePatch(&kp_capsuleminimap[1], "MINICAP2");
@ -3880,7 +3881,11 @@ static void K_drawKartMinimap(void)
}
else
{
workingPic = kp_emeraldminimap;
UINT8 emid = 0;
if (specialstageinfo.ufo->cvmem > 7)
emid = 1;
workingPic = kp_emeraldminimap[emid];
if (specialstageinfo.ufo->color)
{
colormap = R_GetTranslationColormap(TC_DEFAULT, specialstageinfo.ufo->color, GTC_CACHE);

View file

@ -1189,13 +1189,13 @@ extern struct challengesmenu_s {
tic_t ticker; // How long the menu's been open for
INT16 offset; // To make the icons move smoothly when we transition!
UINT8 currentunlock;
UINT16 currentunlock;
char *unlockcondition;
tic_t unlockanim;
SINT8 row, hilix, focusx;
UINT8 col, hiliy;
INT16 row, hilix, focusx;
UINT16 col, hiliy;
challengegridextradata_t *extradata;
@ -1205,7 +1205,7 @@ extern struct challengesmenu_s {
boolean requestflip;
UINT8 unlockcount[CC_MAX];
UINT16 unlockcount[CC_MAX];
UINT8 fade;
} challengesmenu;

View file

@ -51,6 +51,7 @@
#include "d_player.h" // KITEM_ constants
#include "doomstat.h" // MAXSPLITSCREENPLAYERS
#include "k_grandprix.h" // K_CanChangeRules
#include "k_rank.h" // K_GetGradeColor
#include "y_inter.h" // Y_RoundQueueDrawer
@ -2298,7 +2299,6 @@ void M_DrawCupSelect(void)
INT16 icony = 7;
char status = 'A';
char monitor;
INT32 rankx = 0;
if (!cupgrid.builtgrid[id])
break;
@ -2353,7 +2353,6 @@ void M_DrawCupSelect(void)
if (monitor == '2')
{
icony = 5;
rankx = 2;
}
}
else
@ -2385,43 +2384,94 @@ void M_DrawCupSelect(void)
;
else if (windata->best_placement != 0)
{
char gradeChar = '?';
const INT32 rankw = 14 + 12 + 12 + 2;
INT32 rankx = (x + 19) - (rankw / 2);
const INT32 ranky = 8 + (j*100) - (30*menutransition.tics);
switch (windata->best_grade)
patch_t *gradePat = NULL;
colormap = NULL;
const gp_rank_e grade = windata->best_grade; // (cupgrid.previewanim/TICRATE) % (GRADE_S + 1); -- testing
UINT16 gradecolor = K_GetGradeColor(grade);
if (gradecolor != SKINCOLOR_NONE)
colormap = R_GetTranslationColormap(TC_DEFAULT, gradecolor, GTC_MENUCACHE);
switch (grade)
{
case GRADE_E: { gradeChar = 'E'; break; }
case GRADE_D: { gradeChar = 'D'; break; }
case GRADE_C: { gradeChar = 'C'; break; }
case GRADE_B: { gradeChar = 'B'; break; }
case GRADE_A: { gradeChar = 'A'; break; }
case GRADE_S: { gradeChar = 'S'; break; }
default: { break; }
case GRADE_E:
gradePat = W_CachePatchName("R_CUPRNE", PU_CACHE);
break;
case GRADE_D:
gradePat = W_CachePatchName("R_CUPRND", PU_CACHE);
break;
case GRADE_C:
gradePat = W_CachePatchName("R_CUPRNC", PU_CACHE);
break;
case GRADE_B:
gradePat = W_CachePatchName("R_CUPRNB", PU_CACHE);
break;
case GRADE_A:
gradePat = W_CachePatchName("R_CUPRNA", PU_CACHE);
break;
case GRADE_S:
gradePat = W_CachePatchName("R_CUPRNS", PU_CACHE);
break;
default:
break;
}
V_DrawCharacter(x + 5 + rankx, y + icony + 14, gradeChar, false); // rank
if (gradePat)
V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, gradePat, colormap);
if (windata->got_emerald == true)
rankx += 14 + 1;
patch_t *charPat = NULL;
if ((windata->best_skin.unloaded != NULL)
|| (windata->best_skin.id > numskins))
{
colormap = NULL;
charPat = W_CachePatchName("HUHMAP", PU_CACHE);
}
else
{
UINT8 skin = windata->best_skin.id;
colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE);
charPat = faceprefix[skin][FACE_MINIMAP];
}
if (charPat)
V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, charPat, colormap);
if (cv_dummygpdifficulty.value > 0
&& windata->got_emerald == true)
{
rankx += 12 + 1;
if (templevelsearch.cup->emeraldnum == 0)
V_DrawCharacter(x + 26 - rankx, y + icony + 14, '*', false); // rank
V_DrawCharacter(rankx+2, ranky+2, '+', false);
else
{
UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (templevelsearch.cup->emeraldnum-1) % 7;
patch_t *em;
colormap = NULL;
colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
if (!(cupgrid.previewanim & 1))
{
UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (templevelsearch.cup->emeraldnum-1) % 7;
if (templevelsearch.cup->emeraldnum > 7)
{
em = W_CachePatchName("K_SUPER1", PU_CACHE);
rankx += 2;
}
else
{
em = W_CachePatchName("K_EMERC", PU_CACHE);
colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
}
V_DrawFixedPatch((x + 26 - rankx)*FRACUNIT, (y + icony + 13)*FRACUNIT, FRACUNIT, 0, em, colormap);
const char *emname = va(
"%sMAP%c",
(templevelsearch.cup->emeraldnum > 7) ? "SUP" : "EME",
colormap ? '\0' : 'B'
);
V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, W_CachePatchName(emname, PU_CACHE), colormap);
}
}
}
@ -2640,11 +2690,8 @@ void M_DrawTimeAttack(void)
if ((minimap = mapheaderinfo[map]->minimapPic))
V_DrawScaledPatch(24-t, 82, 0, minimap);
if (mapheaderinfo[map]->mainrecord)
{
timerec = mapheaderinfo[map]->mainrecord->time;
laprec = mapheaderinfo[map]->mainrecord->lap;
}
timerec = mapheaderinfo[map]->records.time;
laprec = mapheaderinfo[map]->records.lap;
if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT)
&& (mapheaderinfo[map]->numlaps != 1))
@ -4613,7 +4660,7 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili
patch_t *pat = missingpat;
UINT8 *colormap = NULL, *bgmap = NULL;
fixed_t siz, accordion;
UINT8 id, num;
UINT16 id, num;
boolean unlockedyet;
boolean categoryside;
@ -5779,13 +5826,13 @@ void M_DrawStatistics(void)
if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)))
continue;
if (!mapheaderinfo[i]->mainrecord || mapheaderinfo[i]->mainrecord->time <= 0)
if (mapheaderinfo[i]->records.time <= 0)
{
mapsunfinished++;
continue;
}
besttime += mapheaderinfo[i]->mainrecord->time;
besttime += mapheaderinfo[i]->records.time;
}
V_DrawThinString(20, 60, V_6WIDTHSPACE|V_ALLOWLOWERCASE, "Combined time records:");

View file

@ -303,7 +303,7 @@ void K_FinishCeremony(void)
--------------------------------------------------*/
void K_ResetCeremony(void)
{
UINT8 i;
SINT8 i;
memset(&podiumData, 0, sizeof(struct podiumData_s));
@ -324,22 +324,43 @@ void K_ResetCeremony(void)
// Write grade, position, and emerald-having-ness for later sessions!
i = (grandprixinfo.masterbots) ? KARTGP_MASTER : grandprixinfo.gamespeed;
if ((grandprixinfo.cup->windata[i].best_placement == 0) // First run
|| (podiumData.rank.position < grandprixinfo.cup->windata[i].best_placement)) // Later, better run
// All results populate downwards in difficulty. This prevents someone
// who's just won on Normal from feeling obligated to complete Easy too.
for (; i >= 0; i--)
{
grandprixinfo.cup->windata[i].best_placement = podiumData.rank.position;
boolean anymerit = false;
// The following will not occour in unmodified builds, but pre-emptively sanitise gamedata if someone just changes MAXPLAYERS and calls it a day
if (grandprixinfo.cup->windata[i].best_placement > 0x0F)
grandprixinfo.cup->windata[i].best_placement = 0x0F;
if ((grandprixinfo.cup->windata[i].best_placement == 0) // First run
|| (podiumData.rank.position <= grandprixinfo.cup->windata[i].best_placement)) // Later, better run
{
grandprixinfo.cup->windata[i].best_placement = podiumData.rank.position;
// The following will not occur in unmodified builds, but pre-emptively sanitise gamedata if someone just changes MAXPLAYERS and calls it a day
if (grandprixinfo.cup->windata[i].best_placement > 0x0F)
grandprixinfo.cup->windata[i].best_placement = 0x0F;
anymerit = true;
}
if (podiumData.grade >= grandprixinfo.cup->windata[i].best_grade)
{
grandprixinfo.cup->windata[i].best_grade = podiumData.grade;
anymerit = true;
}
if (podiumData.rank.specialWon == true)
{
grandprixinfo.cup->windata[i].got_emerald = true;
anymerit = true;
}
if (anymerit == true)
{
grandprixinfo.cup->windata[i].best_skin.id = podiumData.rank.skin;
grandprixinfo.cup->windata[i].best_skin.unloaded = NULL;
}
}
if (podiumData.grade > grandprixinfo.cup->windata[i].best_grade)
grandprixinfo.cup->windata[i].best_grade = podiumData.grade;
if (i != KARTSPEED_EASY && podiumData.rank.specialWon == true)
grandprixinfo.cup->windata[i].got_emerald = true;
// Save before playing the noise
G_SaveGameData();
}

View file

@ -329,6 +329,11 @@ void PR_LoadProfiles(void)
P_SaveBufferFree(&save);
I_Error("Existing %s is from the future! (expected %d, got %d)", PROFILESFILE, PROFILEVER, version);
}
else if (version < PROFILEVER)
{
// We're converting - let'd create a backup.
FIL_WriteFile(va("%s" PATHSEP "%s.bak", srb2home, PROFILESFILE), save.buffer, save.size);
}
numprofiles = READUINT8(save.p);
if (numprofiles > MAXPROFILES)

View file

@ -417,3 +417,31 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData)
return retGrade;
}
/*--------------------------------------------------
UINT16 K_GetGradeColor(gp_rank_e grade)
See header file for description.
--------------------------------------------------*/
UINT16 K_GetGradeColor(gp_rank_e grade)
{
switch (grade)
{
case GRADE_E:
return SKINCOLOR_BLUE;
case GRADE_D:
return SKINCOLOR_TURTLE;
case GRADE_C:
return SKINCOLOR_ORANGE;
case GRADE_B:
return SKINCOLOR_RED;
case GRADE_A:
return SKINCOLOR_MAGENTA;
case GRADE_S:
return SKINCOLOR_PIGEON;
default:
break;
}
return SKINCOLOR_NONE;
}

View file

@ -26,6 +26,7 @@ struct gpRank_t
UINT8 totalPlayers;
UINT8 position;
UINT8 skin;
UINT32 winPoints;
UINT32 totalPoints;
@ -81,6 +82,20 @@ void K_InitGrandPrixRank(gpRank_t *rankData);
gp_rank_e K_CalculateGPGrade(gpRank_t *rankData);
/*--------------------------------------------------
UINT16 K_GetGradeColor(gp_rank_e grade)
Maps grades to skincolors for HUD purposes.
Input Arguments:-
grade - gp_rank_e representing an achieved ranking.
Return:-
skincolor ID representing the achieved grade.
--------------------------------------------------*/
UINT16 K_GetGradeColor(gp_rank_e grade);
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -98,7 +98,16 @@ void SV_LoadStats(void)
save.p += headerlen;
UINT8 version = READUINT8(save.p);
(void)version; // for now
if (version > SERVERSTATSVER)
{
P_SaveBufferFree(&save);
I_Error("Existing %s is from the future! (expected %d, got %d)", SERVERSTATSFILE, SERVERSTATSVER, version);
}
else if (version < SERVERSTATSVER)
{
// We're converting - let'd create a backup.
FIL_WriteFile(va("%s" PATHSEP "%s.bak", srb2home, SERVERSTATSFILE), save.buffer, save.size);
}
numtracked = READUINT32(save.p);

View file

@ -386,10 +386,8 @@ static int libd_getSprite2Patch(lua_State *L)
else // find skin by name
{
const char *name = luaL_checkstring(L, 1);
for (i = 0; i < numskins; i++)
if (fastcmp(skins[i].name, name))
break;
if (i >= numskins)
i = R_SkinAvailable(name);
if (i == -1)
return 0;
}

View file

@ -662,17 +662,16 @@ static int mobj_set(lua_State *L)
break;
case mobj_skin: // set skin by name
{
INT32 i;
char skin[SKINNAMESIZE+1]; // all skin names are limited to this length
strlcpy(skin, luaL_checkstring(L, 3), sizeof skin);
strlwr(skin); // all skin names are lowercase
for (i = 0; i < numskins; i++)
if (fastcmp(skins[i].name, skin))
{
if (!mo->player || R_SkinUsable(mo->player-players, i, false))
mo->skin = &skins[i];
return 0;
}
const char *name = luaL_checkstring(L, 3);
INT32 skin = R_SkinAvailable(name);
if (skin != -1)
{
if (!mo->player || R_SkinUsable(mo->player-players, skin, false))
mo->skin = &skins[skin];
return 0;
}
return luaL_error(L, "mobj.skin '%s' not found!", skin);
}
case mobj_color:

View file

@ -196,12 +196,12 @@ static int lib_getSkin(lua_State *L)
}
// find skin by name
for (i = 0; i < numskins; i++)
if (fastcmp(skins[i].name, field))
{
LUA_PushUserdata(L, &skins[i], META_SKIN);
return 1;
}
i = R_SkinAvailable(field);
if (i != -1)
{
LUA_PushUserdata(L, &skins[i], META_SKIN);
return 1;
}
return 0;
}

View file

@ -62,7 +62,7 @@ typedef struct
// Cheat responders
static UINT8 cheatf_warp(void)
{
UINT8 i;
UINT16 i;
boolean success = false;
/*if (modifiedgame)
@ -98,7 +98,7 @@ static UINT8 cheatf_warp(void)
#ifdef DEVELOP
static UINT8 cheatf_devmode(void)
{
UINT8 i;
UINT16 i;
if (modifiedgame)
return 0;

View file

@ -60,7 +60,7 @@ void M_PopulateChallengeGrid(void)
{
UINT16 i, j;
UINT16 numunlocks = 0, nummajorunlocks = 0, numempty = 0;
UINT8 selection[2][MAXUNLOCKABLES + (CHALLENGEGRIDHEIGHT-1)];
UINT16 selection[2][MAXUNLOCKABLES + (CHALLENGEGRIDHEIGHT-1)];
UINT16 majorcompact = 2;
if (gamedata->challengegrid != NULL)
@ -98,7 +98,7 @@ void M_PopulateChallengeGrid(void)
if (nummajorunlocks)
{
// Getting the number of 2-highs you can fit into two adjacent columns.
UINT8 majorpad = (CHALLENGEGRIDHEIGHT/2);
UINT16 majorpad = (CHALLENGEGRIDHEIGHT/2);
numempty = nummajorunlocks%majorpad;
majorpad = (nummajorunlocks+(majorpad-1))/majorpad;
@ -128,7 +128,7 @@ void M_PopulateChallengeGrid(void)
}
gamedata->challengegrid = Z_Malloc(
(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)),
(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT16)),
PU_STATIC, NULL);
if (!gamedata->challengegrid)
@ -136,9 +136,10 @@ void M_PopulateChallengeGrid(void)
I_Error("M_PopulateChallengeGrid: was not able to allocate grid");
}
memset(gamedata->challengegrid,
MAXUNLOCKABLES,
(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)));
for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); ++i)
{
gamedata->challengegrid[i] = MAXUNLOCKABLES;
}
// Attempt to place all large tiles first.
if (nummajorunlocks)
@ -212,7 +213,7 @@ quickcheckagain:
#if (CHALLENGEGRIDHEIGHT == 4)
while (nummajorunlocks > 0)
{
UINT8 unlocktomoveup = MAXUNLOCKABLES;
UINT16 unlocktomoveup = MAXUNLOCKABLES;
j = gamedata->challengegridwidth-1;
@ -313,7 +314,7 @@ quickcheckagain:
void M_UpdateChallengeGridExtraData(challengegridextradata_t *extradata)
{
UINT8 i, j, num, id, tempid, work;
UINT16 i, j, num, id, tempid, work;
boolean idchange;
if (gamedata->challengegrid == NULL)
@ -478,7 +479,7 @@ void M_UpdateChallengeGridExtraData(challengegridextradata_t *extradata)
}
}
void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *stringvar)
void M_AddRawCondition(UINT16 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *stringvar)
{
condition_t *cond;
UINT32 num, wnum;
@ -500,7 +501,7 @@ void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1
cond[wnum].stringvar = stringvar;
}
void M_ClearConditionSet(UINT8 set)
void M_ClearConditionSet(UINT16 set)
{
if (conditionSets[set].numconditions)
{
@ -531,17 +532,14 @@ void M_ClearStats(void)
gamedata->everseenspecial = false;
gamedata->evercrashed = false;
gamedata->musicflags = 0;
gamedata->importprofilewins = false;
}
void M_ClearSecrets(void)
{
INT32 i;
for (i = 0; i < nummapheaders; ++i)
{
mapheaderinfo[i]->mapvisited = 0;
}
for (i = 0; i < MAXEMBLEMS; ++i)
gamedata->collected[i] = false;
for (i = 0; i < MAXUNLOCKABLES; ++i)
@ -702,7 +700,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
return ((cn->requirement < nummapheaders)
&& (mapheaderinfo[cn->requirement])
&& ((mapheaderinfo[cn->requirement]->mapvisited & mvtype) == mvtype));
&& ((mapheaderinfo[cn->requirement]->records.mapvisited & mvtype) == mvtype));
}
case UC_MAPTIME: // Requires time on map <= x
return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement);
@ -932,7 +930,7 @@ static char *M_BuildConditionTitle(UINT16 map)
if (((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED)
// the following is intentionally not MV_BEATEN, just in case the title is for "Finish a round on X"
&& !(mapheaderinfo[map]->mapvisited & MV_VISITED))
&& !(mapheaderinfo[map]->records.mapvisited & MV_VISITED))
|| M_MapLocked(map+1))
return Z_StrDup("???");
@ -1398,7 +1396,7 @@ static const char *M_GetConditionString(condition_t *cn)
#undef BUILDCONDITIONTITLE
}
char *M_BuildConditionSetString(UINT8 unlockid)
char *M_BuildConditionSetString(UINT16 unlockid)
{
conditionset_t *c = NULL;
UINT32 lastID = 0;
@ -1654,7 +1652,7 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall)
UINT16 M_GetNextAchievedUnlock(void)
{
UINT8 i;
UINT16 i;
// Go through unlockables
for (i = 0; i < MAXUNLOCKABLES; ++i)
@ -1686,14 +1684,14 @@ UINT16 M_GetNextAchievedUnlock(void)
}
// Emblem unlocking shit
UINT8 M_CheckLevelEmblems(void)
UINT16 M_CheckLevelEmblems(void)
{
INT32 i;
INT32 valToReach;
INT16 tag;
INT16 levelnum;
UINT8 res;
UINT8 somethingUnlocked = 0;
boolean res;
UINT16 somethingUnlocked = 0;
// Update Score, Time, Rings emblems
for (i = 0; i < numemblems; ++i)
@ -1739,13 +1737,13 @@ UINT8 M_CheckLevelEmblems(void)
return somethingUnlocked;
}
UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough.
UINT16 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough.
{
INT32 i;
INT32 embtype;
INT16 levelnum;
UINT8 res;
UINT8 somethingUnlocked = 0;
boolean res;
UINT16 somethingUnlocked = 0;
UINT8 flags;
for (i = 0; i < numemblems; ++i)
@ -1770,7 +1768,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
if (embtype & ME_SPBATTACK)
flags |= MV_SPBATTACK;
res = ((mapheaderinfo[levelnum]->mapvisited & flags) == flags);
res = ((mapheaderinfo[levelnum]->records.mapvisited & flags) == flags);
gamedata->collected[i] = res;
if (res)
@ -1784,7 +1782,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa
// Quick unlock checks
// -------------------
boolean M_CheckNetUnlockByID(UINT8 unlockid)
boolean M_CheckNetUnlockByID(UINT16 unlockid)
{
if (unlockid >= MAXUNLOCKABLES
|| !unlockables[unlockid].conditionset)
@ -1827,7 +1825,7 @@ boolean M_SecretUnlocked(INT32 type, boolean local)
boolean M_CupLocked(cupheader_t *cup)
{
UINT8 i;
UINT16 i;
// Don't lock maps in dedicated servers.
// That just makes hosts' lives hell.
@ -1855,7 +1853,7 @@ boolean M_CupLocked(cupheader_t *cup)
boolean M_MapLocked(UINT16 mapnum)
{
UINT8 i;
UINT16 i;
// Don't lock maps in dedicated servers.
// That just makes hosts' lives hell.
@ -1921,7 +1919,7 @@ INT32 M_CountMedals(boolean all, boolean extraonly)
// Theoretically faster than using M_CountMedals()
// Stops when it reaches the target number of medals.
UINT8 M_GotEnoughMedals(INT32 number)
boolean M_GotEnoughMedals(INT32 number)
{
INT32 i, gottenmedals = 0;
for (i = 0; i < numemblems; ++i)
@ -1945,7 +1943,7 @@ UINT8 M_GotEnoughMedals(INT32 number)
return false;
}
UINT8 M_GotLowEnoughTime(INT32 tictime)
boolean M_GotLowEnoughTime(INT32 tictime)
{
INT32 curtics = 0;
INT32 i;
@ -1955,9 +1953,9 @@ UINT8 M_GotLowEnoughTime(INT32 tictime)
if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & LF2_NOTIMEATTACK))
continue;
if (!mapheaderinfo[i]->mainrecord || !mapheaderinfo[i]->mainrecord->time)
if (!mapheaderinfo[i]->records.time)
return false;
else if ((curtics += mapheaderinfo[i]->mainrecord->time) > tictime)
if ((curtics += mapheaderinfo[i]->records.time) > tictime)
return false;
}
return true;
@ -2064,9 +2062,10 @@ cupheader_t *M_UnlockableCup(unlockable_t *unlock)
if (unlock->stringVarCache == -1)
{
// Get the cup from the string.
UINT32 hash = quickncasehash(unlock->stringVar, MAXCUPNAME);
while (cup)
{
if (!strcmp(cup->name, unlock->stringVar))
if (hash == cup->namehash && !strcmp(cup->name, unlock->stringVar))
break;
cup = cup->next;
}

View file

@ -159,7 +159,7 @@ struct unlockable_t
char name[64];
char *icon;
UINT16 color;
UINT8 conditionset;
UINT16 conditionset;
INT16 type;
INT16 variable;
char *stringVar;
@ -206,8 +206,8 @@ typedef enum
// If you have more secrets than these variables allow in your game,
// you seriously need to get a life.
#define MAXCONDITIONSETS UINT8_MAX
#define MAXEMBLEMS 512
#define MAXCONDITIONSETS 1024
#define MAXEMBLEMS (MAXCONDITIONSETS*2)
#define MAXUNLOCKABLES MAXCONDITIONSETS
#define CHALLENGEGRIDHEIGHT 4
@ -260,7 +260,7 @@ struct gamedata_t
// CHALLENGE GRID
UINT16 challengegridwidth;
UINT8 *challengegrid;
UINT16 *challengegrid;
// # OF TIMES THE GAME HAS BEEN BEATEN
UINT32 timesBeaten;
@ -273,7 +273,7 @@ struct gamedata_t
// Chao Key condition bypass
UINT32 pendingkeyrounds;
UINT8 pendingkeyroundoffset;
UINT8 keyspending;
UINT16 keyspending;
UINT16 chaokeys;
// SPECIFIC SPECIAL EVENTS
@ -282,6 +282,9 @@ struct gamedata_t
boolean everseenspecial;
boolean evercrashed;
UINT8 musicflags;
// BACKWARDS COMPAT ASSIST
boolean importprofilewins;
};
extern gamedata_t *gamedata;
@ -314,15 +317,15 @@ void M_UpdateChallengeGridExtraData(challengegridextradata_t *extradata);
#define CHE_CONNECTEDUP (1<<2)
#define CHE_DONTDRAW (CHE_CONNECTEDLEFT|CHE_CONNECTEDUP)
char *M_BuildConditionSetString(UINT8 unlockid);
char *M_BuildConditionSetString(UINT16 unlockid);
#define DESCRIPTIONWIDTH 170
// Condition set setup
void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *stringvar);
void M_AddRawCondition(UINT16 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *stringvar);
void M_UpdateConditionSetsPending(void);
// Clearing secrets
void M_ClearConditionSet(UINT8 set);
void M_ClearConditionSet(UINT16 set);
void M_ClearSecrets(void);
void M_ClearStats(void);
@ -335,11 +338,11 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall);
#define PENDING_CHAOKEYS (UINT16_MAX-1)
UINT16 M_GetNextAchievedUnlock(void);
UINT8 M_CheckLevelEmblems(void);
UINT8 M_CompletionEmblems(void);
UINT16 M_CheckLevelEmblems(void);
UINT16 M_CompletionEmblems(void);
// Checking unlockable status
boolean M_CheckNetUnlockByID(UINT8 unlockid);
boolean M_CheckNetUnlockByID(UINT16 unlockid);
boolean M_SecretUnlocked(INT32 type, boolean local);
boolean M_CupLocked(cupheader_t *cup);
boolean M_MapLocked(UINT16 mapnum);
@ -353,8 +356,8 @@ const char *M_GetEmblemPatch(emblem_t *em, boolean big);
// If you're looking to compare stats for unlocks or what not, use these
// They stop checking upon reaching the target number so they
// should be (theoretically?) slightly faster.
UINT8 M_GotEnoughMedals(INT32 number);
UINT8 M_GotLowEnoughTime(INT32 tictime);
boolean M_GotEnoughMedals(INT32 number);
boolean M_GotLowEnoughTime(INT32 tictime);
INT32 M_UnlockableSkinNum(unlockable_t *unlock);
INT32 M_UnlockableFollowerNum(unlockable_t *unlock);

View file

@ -53,8 +53,8 @@ struct challengesmenu_s challengesmenu;
static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
{
UINT8 i;
SINT8 work;
UINT16 i;
INT16 work;
if (unlockid >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0
&& (gamedata->chaokeys < GDMAX_CHAOKEYS))
@ -62,8 +62,8 @@ static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
if (fresh && unlockid >= MAXUNLOCKABLES)
{
UINT8 selection[MAXUNLOCKABLES];
UINT8 numunlocks = 0;
UINT16 selection[MAXUNLOCKABLES];
UINT16 numunlocks = 0;
// Get a random available unlockable.
for (i = 0; i < MAXUNLOCKABLES; i++)
@ -512,7 +512,7 @@ void M_ChallengesTick(void)
boolean M_ChallengesInputs(INT32 ch)
{
const UINT8 pid = 0;
UINT8 i;
UINT16 i;
const boolean start = M_MenuButtonPressed(pid, MBT_START);
const boolean move = (menucmd[pid].dpad_ud != 0 || menucmd[pid].dpad_lr != 0);
(void) ch;

View file

@ -26,7 +26,7 @@ static boolean M_StatisticsAddMap(UINT16 map, cupheader_t *cup, boolean *headere
// Check for completion
if ((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED)
&& !(mapheaderinfo[map]->mapvisited & MV_BEATEN))
&& !(mapheaderinfo[map]->records.mapvisited & MV_BEATEN))
return false;
// Check for unlock

View file

@ -19,7 +19,7 @@ menuitem_t OPTIONS_DataErase[] =
{IT_STRING | IT_CALL, "Erase Statistics Data", "Be careful! What's deleted is gone forever!",
NULL, {.routine = M_EraseData}, EC_STATISTICS, 0},
{IT_STRING | IT_CALL, "Erase GP & Time Attack Data", "Be careful! What's deleted is gone forever!",
{IT_STRING | IT_CALL, "Erase GP & Record Data", "Be careful! What's deleted is gone forever!",
NULL, {.routine = M_EraseData}, EC_TIMEATTACK, 0},
{IT_STRING | IT_CALL, "\x85\x45rase all Game Data", "Be careful! What's deleted is gone forever!",
@ -89,7 +89,7 @@ void M_EraseData(INT32 choice)
else if (optionsmenu.erasecontext == EC_STATISTICS)
eschoice = M_GetText("Statistics data");
else if (optionsmenu.erasecontext == EC_TIMEATTACK)
eschoice = M_GetText("GP & Time Attack data");
eschoice = M_GetText("GP & Record data");
else if (optionsmenu.erasecontext == EC_ALLGAME)
eschoice = M_GetText("ALL game data");
else

View file

@ -404,6 +404,8 @@ void M_CharacterSelectInit(void)
memset(setup_explosions, 0, sizeof(setup_explosions));
setup_animcounter = 0;
UINT32 localskinhash[MAXSPLITSCREENPLAYERS];
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
// Default to no follower / match colour.
@ -414,6 +416,9 @@ void M_CharacterSelectInit(void)
// 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)
setup_player[i].profilen = min(cv_lastprofile[i].value, PR_GetNumProfiles());
// Assistant for comparisons.
localskinhash[i] = quickncasehash(cv_skin[i].string, SKINNAMESIZE);
}
for (i = 0; i < numskins; i++)
@ -436,7 +441,14 @@ void M_CharacterSelectInit(void)
for (j = 0; j < MAXSPLITSCREENPLAYERS; j++)
{
if (!strcmp(cv_skin[j].string, skins[i].name))
// See also R_SkinAvailable
if (localskinhash[j] != skins[i].namehash)
continue;
if (!stricmp(cv_skin[j].string, skins[i].name))
continue;
{
setup_player[j].gridx = x;
setup_player[j].gridy = y;

View file

@ -88,7 +88,7 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch)
{
// Check for completion
if ((mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED)
&& !(mapheaderinfo[mapnum]->mapvisited & MV_BEATEN))
&& !(mapheaderinfo[mapnum]->records.mapvisited & MV_BEATEN))
return false;
// Check for unlock

View file

@ -38,6 +38,7 @@
#define UFO_NUMARMS (3)
#define UFO_ARMDELTA (ANGLE_MAX / UFO_NUMARMS)
#define ufo_emeraldnum(o) ((o)->cvmem)
#define ufo_waypoint(o) ((o)->extravalue1)
#define ufo_distancetofinish(o) ((o)->extravalue2)
#define ufo_speed(o) ((o)->watertop)
@ -1005,7 +1006,7 @@ static mobj_t *InitSpecialUFO(waypoint_t *start)
overlay = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_OVERLAY);
ufo->color = SKINCOLOR_CHAOSEMERALD1;
i = P_GetNextEmerald();
i = ufo_emeraldnum(ufo) = P_GetNextEmerald();
if (i > 0)
{
ufo->color += (i - 1) % 7;

View file

@ -446,9 +446,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 num)
P_DeleteHeaderFollowers(num);
#endif
mapheaderinfo[num]->mapvisited = 0;
Z_Free(mapheaderinfo[num]->mainrecord);
mapheaderinfo[num]->mainrecord = NULL;
memset(&mapheaderinfo[num]->records, 0, sizeof(recorddata_t));
mapheaderinfo[num]->justPlayed = 0;
mapheaderinfo[num]->anger = 0;
@ -506,7 +504,6 @@ void P_AllocMapHeader(INT16 i)
mapheaderinfo[i]->minimapPic = NULL;
mapheaderinfo[i]->ghostCount = 0;
mapheaderinfo[i]->cup = NULL;
mapheaderinfo[i]->mainrecord = NULL;
mapheaderinfo[i]->followers = NULL;
nummapheaders++;
}
@ -8309,7 +8306,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
if (!demo.playback)
{
mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED;
mapheaderinfo[gamemap-1]->records.mapvisited |= MV_VISITED;
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData();

View file

@ -1355,11 +1355,19 @@ void P_DoPlayerExit(player_t *player, pflags_t flags)
G_UpdateRecords();
}
profile_t *pr = PR_GetPlayerProfile(player);
if (pr != NULL && !losing)
if (!losing)
{
pr->wins++;
PR_SaveProfiles();
profile_t *pr = PR_GetPlayerProfile(player);
if (pr != NULL)
{
pr->wins++;
PR_SaveProfiles();
}
if (P_IsLocalPlayer(player) && player->skin < numskins)
{
skins[player->skin].records.wins++;
}
}
player->karthud[khud_cardanimation] = 0; // srb2kart: reset battle animation

View file

@ -41,6 +41,8 @@
INT32 numskins = 0;
skin_t skins[MAXSKINS];
unloaded_skin_t *unloadedskins = NULL;
// FIXTHIS: don't work because it must be inistilised before the config load
//#define SKINVALUES
#ifdef SKINVALUES
@ -125,6 +127,8 @@ static void Sk_SetDefaultValue(skin_t *skin)
skin->highresscale = FRACUNIT;
// no specific memset for skinrecord_t as it's already nuked by the full skin_t wipe
for (i = 0; i < sfx_skinsoundslot0; i++)
if (S_sfx[i].skinsound != -1)
skin->soundsid[S_sfx[i].skinsound] = i;
@ -183,7 +187,8 @@ void R_InitSkins(void)
UINT8 *R_GetSkinAvailabilities(boolean demolock, boolean forbots)
{
UINT8 i, shif, byte;
UINT16 i;
UINT8 shif, byte;
INT32 skinid;
static UINT8 responsebuffer[MAXAVAILABILITY];
UINT8 defaultbotskin = R_BotDefaultSkin();
@ -221,7 +226,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins)
{
boolean needsunlocked = false;
boolean useplayerstruct = (Playing() && playernum != -1);
UINT8 i;
UINT16 i;
INT32 skinid;
if (skinnum == -1)
@ -314,12 +319,17 @@ UINT32 R_GetLocalRandomSkin(void)
INT32 R_SkinAvailable(const char *name)
{
INT32 i;
UINT32 hash = quickncasehash(name, SKINNAMESIZE);
for (i = 0; i < numskins; i++)
{
// search in the skin list
if (stricmp(skins[i].name,name)==0)
return i;
if (skins[i].namehash != hash)
continue;
if (stricmp(skins[i].name,name)!=0)
continue;
return i;
}
return -1;
}
@ -727,9 +737,59 @@ static void R_LoadSkinSprites(UINT16 wadnum, UINT16 *lump, UINT16 *lastlump, ski
// returns whether found appropriate property
static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
{
if (!stricmp(stoken, "rivals"))
{
size_t len = strlen(value);
size_t i;
char rivalname[SKINNAMESIZE+1] = "";
UINT8 pos = 0;
UINT8 numrivals = 0;
// Can't use strtok, because the above function's already using it.
// Using it causes it to upset the saved pointer,
// corrupting the reading for the rest of the file.
// So instead we get to crawl through the value, character by character,
// and write it down as we go, until we hit a comma or the end of the string.
// Yaaay.
for (i = 0; i <= len; i++)
{
if (numrivals >= SKINRIVALS)
{
break;
}
if (value[i] == ',' || i == len)
{
if (pos == 0)
continue;
STRBUFCPY(skin->rivals[numrivals], rivalname);
strlwr(skin->rivals[numrivals]);
numrivals++;
if (i == len)
break;
for (; pos > 0; pos--)
{
rivalname[pos] = '\0';
}
continue;
}
rivalname[pos] = value[i];
pos++;
}
}
// custom translation table
if (!stricmp(stoken, "startcolor"))
else if (!stricmp(stoken, "startcolor"))
{
skin->starttranscolor = atoi(value);
}
#define FULLPROCESS(field) else if (!stricmp(stoken, #field)) skin->field = get_number(value);
// character type identification
@ -917,45 +977,6 @@ void R_AddSkins(UINT16 wadnum, boolean mainfile)
STRBUFCPY(skin->realname, value);
SYMBOLCONVERT(skin->realname)
}
else if (!stricmp(stoken, "rivals"))
{
size_t len = strlen(value);
size_t i;
char rivalname[SKINNAMESIZE] = "";
UINT8 pos = 0;
UINT8 numrivals = 0;
// Can't use strtok, because this function's already using it.
// Using it causes it to upset the saved pointer,
// corrupting the reading for the rest of the file.
// So instead we get to crawl through the value, character by character,
// and write it down as we go, until we hit a comma or the end of the string.
// Yaaay.
for (i = 0; i <= len; i++)
{
if (numrivals >= SKINRIVALS)
{
break;
}
if (value[i] == ',' || i == len)
{
STRBUFCPY(skin->rivals[numrivals], rivalname);
strlwr(skin->rivals[numrivals]);
numrivals++;
memset(rivalname, 0, sizeof (rivalname));
pos = 0;
continue;
}
rivalname[pos] = value[i];
pos++;
}
}
else if (!R_ProcessPatchableFields(skin, stoken, value))
CONS_Debug(DBG_SETUP, "R_AddSkins: Unknown keyword '%s' in S_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename);
@ -987,6 +1008,68 @@ next_token:
HWR_AddPlayerModel(numskins);
#endif
// Finally, conclude by setting up final properties.
skin->namehash = quickncasehash(skin->name, SKINNAMESIZE);
{
// Check to see if we have any custom skin wins data that we could substitute in.
unloaded_skin_t *unloadedskin, *unloadedprev = NULL;
for (unloadedskin = unloadedskins; unloadedskin; unloadedprev = unloadedskin, unloadedskin = unloadedskin->next)
{
if (unloadedskin->namehash != skin->namehash)
continue;
if (strcasecmp(skin->name, unloadedskin->name) != 0)
continue;
// Copy in wins, etc.
M_Memcpy(&skin->records, &unloadedskin->records, sizeof(skin->records));
// Remove this entry from the chain.
if (unloadedprev)
{
unloadedprev->next = unloadedskin->next;
}
else
{
unloadedskins = unloadedskin->next;
}
// Now... we assign everything which used this pointer the new skin id.
UINT8 i;
cupheader_t *cup;
for (cup = kartcupheaders; cup; cup = cup->next)
{
for (i = 0; i < KARTGP_MAX; i++)
{
if (cup->windata[i].best_skin.unloaded != unloadedskin)
continue;
cup->windata[i].best_skin.id = numskins;
cup->windata[i].best_skin.unloaded = NULL;
}
}
unloaded_cupheader_t *unloadedcup;
for (unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next)
{
for (i = 0; i < KARTGP_MAX; i++)
{
if (unloadedcup->windata[i].best_skin.unloaded != unloadedskin)
continue;
unloadedcup->windata[i].best_skin.id = numskins;
unloadedcup->windata[i].best_skin.unloaded = NULL;
}
}
// Finally, free.
Z_Free(unloadedskin);
break;
}
}
numskins++;
}
return;
@ -1073,45 +1156,6 @@ void R_PatchSkins(UINT16 wadnum, boolean mainfile)
STRBUFCPY(skin->realname, value);
SYMBOLCONVERT(skin->realname)
}
else if (!stricmp(stoken, "rivals"))
{
size_t len = strlen(value);
size_t i;
char rivalname[SKINNAMESIZE] = "";
UINT8 pos = 0;
UINT8 numrivals = 0;
// Can't use strtok, because this function's already using it.
// Using it causes it to upset the saved pointer,
// corrupting the reading for the rest of the file.
// So instead we get to crawl through the value, character by character,
// and write it down as we go, until we hit a comma or the end of the string.
// Yaaay.
for (i = 0; i <= len; i++)
{
if (numrivals >= SKINRIVALS)
{
break;
}
if (value[i] == ',' || i == len)
{
STRBUFCPY(skin->rivals[numrivals], rivalname);
strlwr(skin->rivals[numrivals]);
numrivals++;
memset(rivalname, 0, sizeof (rivalname));
pos = 0;
continue;
}
rivalname[pos] = value[i];
pos++;
}
}
else if (!R_ProcessPatchableFields(skin, stoken, value))
CONS_Debug(DBG_SETUP, "R_PatchSkins: Unknown keyword '%s' in P_SKIN lump #%d (WAD %s)\n", stoken, lump, wadfiles[wadnum]->filename);
}

View file

@ -37,7 +37,8 @@ extern "C" {
/// The skin_t struct
struct skin_t
{
char name[SKINNAMESIZE+1]; // INT16 descriptive name of the skin
char name[SKINNAMESIZE+1]; // descriptive name of the skin
UINT32 namehash; // quickncasehash(->name, SKINNAMESIZE)
UINT16 wadnum;
skinflags_t flags;
@ -58,6 +59,8 @@ struct skin_t
fixed_t highresscale; // scale of highres, default is 0.5
skinrecord_t records;
char rivals[SKINRIVALS][SKINNAMESIZE+1]; // Your top 3 rivals for GP mode. Uses names so that you can reference skins that aren't added
// specific sounds per skin

View file

@ -1534,7 +1534,7 @@ static boolean S_SoundTestDefLocked(musicdef_t *def)
// Is the level tied to SP progression?
if ((mapheaderinfo[def->sequence.map]->menuflags & LF2_FINISHNEEDED)
&& !(mapheaderinfo[def->sequence.map]->mapvisited & MV_BEATEN))
&& !(mapheaderinfo[def->sequence.map]->records.mapvisited & MV_BEATEN))
return true;
// Finally, do a full-fat map check.

View file

@ -113,6 +113,9 @@ TYPEDEF (skincolor_t);
// doomstat.h
TYPEDEF (precipprops_t);
TYPEDEF (skinrecord_t);
TYPEDEF (unloaded_skin_t);
TYPEDEF (skinreference_t);
TYPEDEF (recorddata_t);
TYPEDEF (cupwindata_t);
TYPEDEF (scene_t);
@ -125,8 +128,10 @@ TYPEDEF (customoption_t);
TYPEDEF (gametype_t);
TYPEDEF (staffbrief_t);
TYPEDEF (mapheader_t);
TYPEDEF (unloaded_mapheader_t);
TYPEDEF (tolinfo_t);
TYPEDEF (cupheader_t);
TYPEDEF (unloaded_cupheader_t);
// font.h
TYPEDEF (font_t);