Merge branch 'auto-medal-times' into 'master'

Add automedal time configs and calculation

Closes #790

See merge request KartKrew/Kart!1777
This commit is contained in:
Oni 2024-01-05 23:01:24 +00:00
commit 0f468c4634
11 changed files with 202 additions and 37 deletions

View file

@ -4920,6 +4920,72 @@ static void Command_cxdiag_f(void)
}
}
{
CONS_Printf("\x82""Evaluating Time Medals...\n");
for (i = 0; i < numemblems; i++)
{
if (emblemlocations[i].type != ET_TIME)
continue;
INT32 checkLevel = M_EmblemMapNum(&emblemlocations[i]);
if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel])
continue;
if (emblemlocations[i].tag > 0)
{
if (emblemlocations[i].tag > mapheaderinfo[checkLevel]->ghostCount)
{
CONS_Printf("\x87"" Time Medal %u (level %s) has tag %d, which is greater than the number of associated Staff Ghosts (%u)\n", i+1, mapheaderinfo[checkLevel]->lumpname, emblemlocations[i].tag, mapheaderinfo[checkLevel]->ghostCount);
errors++;
}
}
else switch (emblemlocations[i].tag)
{
case 0:
{
if (emblemlocations[i].var < TICRATE)
{
CONS_Printf("\x87"" Time Medal %u (level %s) is set to %d (less than a second??)\n", i+1, mapheaderinfo[checkLevel]->lumpname, emblemlocations[i].var);
errors++;
}
break;
}
case AUTOMEDAL_PLATINUM:
{
if (mapheaderinfo[checkLevel]->ghostCount == 0)
{
CONS_Printf("\x87"" Time Medal %u (level %s) is AUTOMEDAL_PLATINUM, but there are no associated Staff Ghosts\n", i+1, mapheaderinfo[checkLevel]->lumpname);
errors++;
}
break;
}
case AUTOMEDAL_GOLD:
case AUTOMEDAL_SILVER:
case AUTOMEDAL_BRONZE:
{
if (mapheaderinfo[checkLevel]->ghostCount < 2)
{
CONS_Printf("\x87"" Time Medal %u (level %s) is an Auto Medal, but there are %u associated Staff Ghosts, which is less than the 2 recommended minimum\n", i+1, mapheaderinfo[checkLevel]->lumpname, mapheaderinfo[checkLevel]->ghostCount);
errors++;
}
break;
}
default:
{
CONS_Printf("\x87"" Time Medal %u (level %s) has invalid tag (%d)\n", i+1, mapheaderinfo[checkLevel]->lumpname, emblemlocations[i].tag);
errors++;
break;
}
}
}
}
if (errors)
CONS_Printf("\x85""%u errors detected.\n", errors);
else

View file

@ -2279,7 +2279,7 @@ void reademblemdata(MYFILE *f, INT32 num)
emblemlocations[num-1].type = (UINT8)value;
}
else if (fastcmp(word, "TAG"))
emblemlocations[num-1].tag = (INT16)value;
emblemlocations[num-1].tag = get_number(word2);
else if (fastcmp(word, "MAPNAME"))
{
emblemlocations[num-1].level = Z_StrDup(word2);

View file

@ -6916,6 +6916,12 @@ struct int_const_s const INT_CONST[] = {
{"ME_ENCORE",ME_ENCORE},
{"ME_SPBATTACK",ME_SPBATTACK},
// Automedal SOC tags
{"AUTOMEDAL_BRONZE",AUTOMEDAL_BRONZE},
{"AUTOMEDAL_SILVER",AUTOMEDAL_SILVER},
{"AUTOMEDAL_GOLD",AUTOMEDAL_GOLD},
{"AUTOMEDAL_PLATINUM",AUTOMEDAL_PLATINUM},
// p_local.h constants
{"FLOATSPEED",FLOATSPEED},
{"MAXSTEPMOVE",MAXSTEPMOVE},

View file

@ -252,32 +252,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
else
i = 0;
if (fastcmp(word, "EMBLEM"))
{
if (!mainfile && !gamedataadded)
{
deh_warning("You must define a custom gamedata to use \"%s\"", word);
ignorelines(f);
}
else
{
if (!word2)
i = numemblems + 1;
if (i > 0 && i <= MAXEMBLEMS)
{
if (numemblems < i)
numemblems = i;
reademblemdata(f, i);
}
else
{
deh_warning("Emblem number %d out of range (1 - %d)", i, MAXEMBLEMS);
ignorelines(f);
}
}
continue;
}
if (word2)
{
if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
@ -430,6 +404,28 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
ignorelines(f);
}
}
else if (fastcmp(word, "EMBLEM"))
{
if (!mainfile && !gamedataadded)
{
deh_warning("You must define a custom gamedata to use \"%s\"", word);
ignorelines(f);
}
else if (i > 0 && i <= MAXEMBLEMS)
{
if (numemblems < i)
{
// This is no longer strictly necessary... but I've left it in as an optimisation, because the datatype is now immensohuge, heh.
numemblems = i;
}
reademblemdata(f, i);
}
else
{
deh_warning("Emblem number %d out of range (1 - %d)", i, MAXEMBLEMS);
ignorelines(f);
}
}
else if (fastcmp(word, "UNLOCKABLE"))
{
if (!mainfile && !gamedataadded)

View file

@ -483,6 +483,7 @@ struct mapheader_t
UINT8 ghostCount; ///< Count of valid staff ghosts
UINT32 ghostBriefSize; ///< Size of ghostBrief vector allocation
staffbrief_t **ghostBrief; ///< Valid staff ghosts, pointers are owned
tic_t automedaltime[4]; ///< Auto Medal times derived from ghost times, best to worst
recorddata_t records; ///< Stores completion/record attack data

View file

@ -482,8 +482,16 @@ bademblem:
else
stickermedalinfo.timetoreach = mapheaderinfo[map]->ghostBrief[emblem->tag-1]->time;
}
else if (emblem->tag < 0 && emblem->tag > AUTOMEDAL_MAX)
{
// Use auto medal times for emblem tags, see AUTOMEDAL_ in m_cond.h
int index = -emblem->tag - 1; // 0 is Platinum, 3 is Bronze
stickermedalinfo.timetoreach = mapheaderinfo[map]->automedaltime[index];
}
else
{
stickermedalinfo.timetoreach = emblem->var;
}
}
}
@ -4559,7 +4567,7 @@ void G_LoadGameSettings(void)
}
#define GD_VERSIONCHECK 0xBA5ED123 // Change every major version, as usual
#define GD_VERSIONMINOR 9 // Change every format update
#define GD_VERSIONMINOR 10 // Change every format update
// You can't rearrange these without a special format update
typedef enum
@ -4765,6 +4773,10 @@ void G_LoadGameData(void)
unlockreadcount = conditionreadcount = UINT8_MAX;
unlockreadsize = sizeof(UINT8);
}
else if (versionMinor < 10)
{
emblemreadcount = 1024*2;
}
// To save space, use one bit per collected/achieved/unlocked flag
for (i = 0; i < emblemreadcount;)

View file

@ -212,7 +212,7 @@ void G_UseContinue(void);
void G_AfterIntermission(void);
void G_EndGame(void); // moved from y_inter.c/h and renamed
#define MAXMEDALVISIBLECOUNT 3
#define MAXMEDALVISIBLECOUNT 4
extern struct stickermedalinfo
{
UINT8 visiblecount;

View file

@ -45,7 +45,7 @@ emblem_t emblemlocations[MAXEMBLEMS];
// Unlockables
unlockable_t unlockables[MAXUNLOCKABLES];
// Number of emblems
// Highest used emblem ID
INT32 numemblems = 0;
// The challenge that will truly let the games begin.
@ -2271,7 +2271,7 @@ static const char *M_GetConditionString(condition_t *cn)
i = cn->requirement-1;
checkLevel = M_EmblemMapNum(&emblemlocations[i]);
if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel])
if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel] || emblemlocations[i].type == ET_NONE)
return va("INVALID MEDAL MAP \"%d:%d\"", cn->requirement, checkLevel);
title = M_BuildConditionTitle(checkLevel);
@ -3160,6 +3160,14 @@ UINT16 M_CheckLevelEmblems(void)
res = (G_GetBestTime(levelnum) <= mapheaderinfo[checkLevel]->ghostBrief[tag-1]->time);
}
else if (tag < 0 && tag > AUTOMEDAL_MAX)
{
// Use auto medal times for emblem tags, see AUTOMEDAL_ in m_cond.h
int index = -tag - 1; // 0 is Platinum, 3 is Bronze
tic_t time = mapheaderinfo[checkLevel]->automedaltime[index];
res = (G_GetBestTime(levelnum) <= time);
}
else
{
res = (G_GetBestTime(levelnum) <= (unsigned)valToReach);
@ -3394,6 +3402,8 @@ INT32 M_CountMedals(boolean all, boolean extraonly)
{
for (i = 0; i < numemblems; ++i)
{
if (emblemlocations[i].type == ET_NONE)
continue;
if ((emblemlocations[i].type == ET_GLOBAL)
&& (emblemlocations[i].flags & GE_NOTMEDAL))
continue;
@ -3424,6 +3434,8 @@ boolean M_GotEnoughMedals(INT32 number)
INT32 i, gottenmedals = 0;
for (i = 0; i < numemblems; ++i)
{
if (emblemlocations[i].type == ET_NONE)
continue;
if (!gamedata->collected[i])
continue;
if (++gottenmedals < number)
@ -3693,6 +3705,9 @@ emblem_t *M_GetLevelEmblems(INT32 mapnum)
while (--i >= 0)
{
if (emblemlocations[i].type == ET_NONE)
continue;
INT32 checkLevel = M_EmblemMapNum(&emblemlocations[i]);
if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel])

View file

@ -171,10 +171,10 @@ struct conditionset_t
};
// Emblem information
#define ET_GLOBAL 0 // Emblem with a position in space
#define ET_MAP 1 // Beat the map
#define ET_TIME 2 // Get the time
//#define ET_DEVTIME 3 // Time, but the value is tied to a Time Trial demo, not pre-defined
#define ET_NONE 0 // Empty slot
#define ET_GLOBAL 1 // Emblem with a position in space
#define ET_MAP 2 // Beat the map
#define ET_TIME 3 // Get the time
// Global emblem flags
#define GE_NOTMEDAL 1 // Doesn't count towards number of medals
@ -185,6 +185,13 @@ struct conditionset_t
#define ME_ENCORE 1 // Achieve in Encore
#define ME_SPBATTACK 2 // Achieve in SPB Attack
// Automedal SOC tags
#define AUTOMEDAL_MAX -5 // just in case any more are ever added
#define AUTOMEDAL_BRONZE -4
#define AUTOMEDAL_SILVER -3
#define AUTOMEDAL_GOLD -2
#define AUTOMEDAL_PLATINUM -1
struct emblem_t
{
UINT8 type; ///< Emblem type
@ -260,7 +267,7 @@ typedef enum
// If you have more secrets than these variables allow in your game,
// you seriously need to get a life.
#define MAXCONDITIONSETS 1024
#define MAXEMBLEMS (MAXCONDITIONSETS*2)
#define MAXEMBLEMS (MAXCONDITIONSETS*4)
#define MAXUNLOCKABLES MAXCONDITIONSETS
#define CHALLENGEGRIDHEIGHT 5

View file

@ -229,7 +229,8 @@ boolean P_CanPickupEmblem(player_t *player, INT32 emblemID)
boolean P_EmblemWasCollected(INT32 emblemID)
{
if (emblemID < 0 || emblemID >= numemblems)
if (emblemID < 0 || emblemID >= numemblems
|| emblemlocations[emblemID].type == ET_NONE)
{
// Invalid emblem ID, can't pickup.
return true;

View file

@ -13,6 +13,7 @@
#include <algorithm>
#include <string>
#include <vector>
#include <fmt/format.h>
@ -492,6 +493,10 @@ static void P_ClearSingleMapHeaderInfo(INT16 num)
mapheaderinfo[num]->ghostBrief = NULL;
mapheaderinfo[num]->ghostCount = 0;
mapheaderinfo[num]->ghostBriefSize = 0;
mapheaderinfo[num]->automedaltime[0] = 1;
mapheaderinfo[num]->automedaltime[1] = 2;
mapheaderinfo[num]->automedaltime[2] = 3;
mapheaderinfo[num]->automedaltime[3] = 4;
}
/** Allocates a new map-header structure.
@ -8848,6 +8853,60 @@ static lumpinfo_t* FindFolder(const char *folName, UINT16 *start, UINT16 *end, l
return lumpinfo;
}
static void P_DeriveAutoMedalTimes(mapheader_t& map)
{
// Gather staff ghost times
std::vector<tic_t> stafftimes;
for (int i = 0; i < map.ghostCount; i++)
{
tic_t time = map.ghostBrief[i]->time;
if (time <= 0)
{
continue;
}
stafftimes.push_back(map.ghostBrief[i]->time);
}
if (stafftimes.empty())
{
// Use fallback times
map.automedaltime[0] = 1;
map.automedaltime[1] = 2;
map.automedaltime[2] = 3;
map.automedaltime[3] = 4;
return;
}
std::sort(stafftimes.begin(), stafftimes.end());
// Auto Platinum is the best staff ghost time
// Auto Gold is the median staff ghost time
// Silver and Bronze are 10% longer successively
tic_t best = stafftimes.at(0);
tic_t gold = stafftimes.at(stafftimes.size() / 2);
if (gold == best)
{
gold += 1;
}
tic_t silver = static_cast<tic_t>(std::ceil(gold * 1.1f));
if (silver == gold)
{
silver += 1;
}
tic_t bronze = static_cast<tic_t>(std::ceil(silver * 1.1f));
if (bronze == silver)
{
bronze += 1;
}
map.automedaltime[0] = best;
map.automedaltime[1] = gold;
map.automedaltime[2] = silver;
map.automedaltime[3] = bronze;
}
lumpnum_t wadnamelump = LUMPERROR;
INT16 wadnamemap = 0; // gamemap based
@ -9024,6 +9083,8 @@ UINT8 P_InitMapData(void)
}
}
P_DeriveAutoMedalTimes(*mapheaderinfo[i]);
vres_Free(virtmap);
}
}