A start on in-level empheral conditions

- M_UpdateUnlockablesAndExtraEmblems: If Playing() and GS_LEVEL, call a bunch of funny conditions for every non-spectator splitscreen player.
    - Called every tick in P_Ticker. May be foolish. May duplicate effort. Worth extra attention later.
- Add a bunch of stacking-together UCRP_REQUIRESPLAYING condition types.
    - A collection of PREFIXes
        - Grand Prix
        - Bonus Round
        - Time Attack
        - Break The Capsules
        - Sealed Star
    - A collection of specific prerequisites
        - Current map is [map value]
        - Character is [character string that gets parsed after skin load]
            - M_UpdateConditionSetsPending(), because SOC is bad and loaded before skins
    - A collection of win conditions (TODO: these still work in FREE PLAY)
        - Finish in good standing
        - Break all the capsules
        - No Contest
        - Finish at least [place]
        - Finish at [place] exactly
        - Finish at [time, tics]
        - Finish at [time, tics] exactly
        - Finish with at least [time, tics] left (GTR_TIMELIMIT only)
    - An AND to concatenate them together (literally just for text adjustment)
This commit is contained in:
toaster 2023-03-02 23:02:42 +00:00
parent e07802d3bf
commit 5b11179213
5 changed files with 354 additions and 46 deletions

View file

@ -2336,9 +2336,10 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
INT32 i;
char *params[4]; // condition, requirement, extra info, extra info
char *spos;
char *pendingstring = NULL;
conditiontype_t ty;
INT32 re;
INT32 re = 0;
INT16 x1 = 0, x2 = 0;
INT32 offset = 0;
@ -2362,8 +2363,8 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
return;
}
if (fastcmp(params[0], "PLAYTIME")
|| (++offset && fastcmp(params[0], "MATCHESPLAYED")))
if ((offset=0) || fastcmp(params[0], "PLAYTIME")
|| (++offset && fastcmp(params[0], "MATCHESPLAYED")))
{
PARAMCHECK(1);
ty = UC_PLAYTIME + offset;
@ -2400,7 +2401,8 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
re = atoi(params[1]);
}
else if ((offset=0) || fastcmp(params[0], "MAPVISITED")
|| (++offset && fastcmp(params[0], "MAPBEATEN")))
|| (++offset && fastcmp(params[0], "MAPBEATEN"))
|| (++offset && fastcmp(params[0], "MAPENCORE")))
{
PARAMCHECK(1);
ty = UC_MAPVISITED + offset;
@ -2480,13 +2482,94 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
return;
}
}
else if (fastcmp(params[0], "AND"))
{
//PARAMCHECK(1);
ty = UC_AND;
}
else if ((offset=0) || fastcmp(params[0], "PREFIX_GRANDPRIX")
|| (++offset && fastcmp(params[0], "PREFIX_BONUSROUND"))
|| (++offset && fastcmp(params[0], "PREFIX_TIMEATTACK"))
|| (++offset && fastcmp(params[0], "PREFIX_BREAKTHECAPSULES"))
|| (++offset && fastcmp(params[0], "PREFIX_SEALEDSTAR")))
{
//PARAMCHECK(1);
ty = UCRP_PREFIX_GRANDPRIX + offset;
}
else if (fastcmp(params[0], "ISMAP"))
{
PARAMCHECK(1);
ty = UCRP_ISMAP;
re = G_MapNumber(params[1]);
if (re >= nummapheaders)
{
deh_warning("Invalid level %s for condition ID %d", params[1], id+1);
return;
}
}
else if (fastcmp(params[0], "ISCHARACTER"))
{
PARAMCHECK(1);
ty = UCRP_ISCHARACTER;
#if 0
{
re = R_SkinAvailable(params[1]);
if (re < 0)
{
deh_warning("Invalid character %s for condition ID %d", params[1], id+1);
return;
}
}
#else
{
pendingstring = Z_StrDup(params[1]);
re = -1;
}
#endif
}
else if ((offset=0) || fastcmp(params[0], "FINISHCOOL")
|| (++offset && fastcmp(params[0], "FINISHALLCAPSULES"))
|| (++offset && fastcmp(params[0], "NOCONTEST")))
{
//PARAMCHECK(1);
ty = UCRP_FINISHCOOL + offset;
}
else if ((offset=0) || fastcmp(params[0], "FINISHPLACE")
|| (++offset && fastcmp(params[0], "FINISHPLACEEXACT")))
{
PARAMCHECK(1);
ty = UCRP_FINISHPLACE + offset;
re = atoi(params[1]);
if (re < 1 || re > MAXPLAYERS)
{
deh_warning("Invalid place %s for condition ID %d", params[1], id+1);
return;
}
}
else if ((offset=0) || fastcmp(params[0], "FINISHTIME")
|| (++offset && fastcmp(params[0], "FINISHTIMEEXACT"))
|| (++offset && fastcmp(params[0], "FINISHTIMELEFT")))
{
PARAMCHECK(1);
ty = UCRP_FINISHTIME + offset;
re = atoi(params[1]);
if (re < 0)
{
deh_warning("Invalid time %s for condition ID %d", params[1], id+1);
return;
}
}
else
{
deh_warning("Invalid condition name %s for condition ID %d", params[0], id+1);
return;
}
M_AddRawCondition(set, (UINT8)id, ty, re, x1, x2);
M_AddRawCondition(set, (UINT8)id, ty, re, x1, x2, pendingstring);
}
void readconditionset(MYFILE *f, UINT8 setnum)

View file

@ -25,6 +25,10 @@
#include "r_draw.h" // R_GetColorByName
#include "s_sound.h" // S_StartSound
#include "k_kart.h" // K_IsPLayerLosing
#include "k_grandprix.h" // grandprixinfo
#include "k_battle.h" // battlecapsules
#include "k_specialstage.h" // specialstageinfo
#include "k_pwrlv.h"
#include "k_profiles.h"
@ -427,7 +431,7 @@ void M_UpdateChallengeGridExtraData(challengegridextradata_t *extradata)
}
}
void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2)
void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *pendingstring)
{
condition_t *cond;
UINT32 num, wnum;
@ -446,6 +450,7 @@ void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1
cond[wnum].requirement = r;
cond[wnum].extrainfo1 = x1;
cond[wnum].extrainfo2 = x2;
cond[wnum].pendingstring = pendingstring;
}
void M_ClearConditionSet(UINT8 set)
@ -490,8 +495,51 @@ void M_ClearSecrets(void)
// Condition set checking
// ----------------------
void M_UpdateConditionSetsPending(void)
{
UINT32 i, j;
conditionset_t *c;
condition_t *cn;
for (i = 0; i < MAXCONDITIONSETS; ++i)
{
c = &conditionSets[i];
if (!c->numconditions)
continue;
for (j = 0; j < c->numconditions; ++j)
{
cn = &c->condition[j];
if (cn->pendingstring == NULL)
continue;
switch (cn->type)
{
case UCRP_ISCHARACTER:
{
cn->requirement = R_SkinAvailable(cn->pendingstring);
if (cn->requirement < 0)
{
CONS_Alert(CONS_WARNING, "UCRP_ISCHARACTER: Invalid character %s for condition ID %d", cn->pendingstring, cn->id+1);
return;
}
break;
}
default:
break;
}
Z_Free(cn->pendingstring);
cn->pendingstring = NULL;
}
}
}
// See also M_GetConditionString
UINT8 M_CheckCondition(condition_t *cn)
boolean M_CheckCondition(condition_t *cn, player_t *player)
{
switch (cn->type)
{
@ -544,16 +592,68 @@ UINT8 M_CheckCondition(condition_t *cn)
return gamedata->unlocked[cn->requirement-1];
case UC_CONDITIONSET: // requires condition set x to already be achieved
return M_Achieved(cn->requirement-1);
case UC_AND: // Just for string building
return true;
case UCRP_PREFIX_GRANDPRIX:
return (grandprixinfo.gp == true);
case UCRP_PREFIX_BONUSROUND:
return ((grandprixinfo.gp == true) && (grandprixinfo.eventmode == GPEVENT_BONUS));
case UCRP_PREFIX_TIMEATTACK:
return (modeattacking != ATTACKING_NONE);
case UCRP_PREFIX_BREAKTHECAPSULES:
return ((gametyperules & GTR_CAPSULES) && battlecapsules);
case UCRP_PREFIX_SEALEDSTAR:
return (specialstageinfo.valid == true);
case UCRP_ISMAP:
return (gamemap == cn->requirement+1);
case UCRP_ISCHARACTER:
return (player->skin == cn->requirement);
case UCRP_FINISHCOOL:
return (player->exiting
&& !(player->pflags & PF_NOCONTEST)
&& !K_IsPlayerLosing(player));
case UCRP_FINISHALLCAPSULES:
return (battlecapsules
&& !(player->pflags & PF_NOCONTEST)
&& numtargets >= maptargets);
case UCRP_NOCONTEST:
return (player->pflags & PF_NOCONTEST);
case UCRP_FINISHPLACE:
return (player->exiting
&& !(player->pflags & PF_NOCONTEST)
&& player->position <= cn->requirement);
case UCRP_FINISHPLACEEXACT:
return (player->exiting
&& !(player->pflags & PF_NOCONTEST)
&& player->position == cn->requirement);
case UCRP_FINISHTIME:
return (player->exiting
&& !(player->pflags & PF_NOCONTEST)
&& player->realtime <= (unsigned)cn->requirement);
case UCRP_FINISHTIMEEXACT:
return (player->exiting
&& !(player->pflags & PF_NOCONTEST)
&& player->realtime == (unsigned)cn->requirement);
case UCRP_FINISHTIMELEFT:
return (timelimitintics
&& player->exiting
&& !(player->pflags & PF_NOCONTEST)
&& player->realtime < timelimitintics
&& (timelimitintics + extratimeintics + secretextratime - player->realtime) <= (unsigned)cn->requirement);
}
return false;
}
static UINT8 M_CheckConditionSet(conditionset_t *c)
static boolean M_CheckConditionSet(conditionset_t *c, player_t *player)
{
UINT32 i;
UINT32 lastID = 0;
condition_t *cn;
UINT8 achievedSoFar = true;
boolean achievedSoFar = true;
for (i = 0; i < c->numconditions; ++i)
{
@ -569,7 +669,16 @@ static UINT8 M_CheckConditionSet(conditionset_t *c)
continue;
lastID = cn->id;
achievedSoFar = M_CheckCondition(cn);
if ((player != NULL) != (cn->type >= UCRP_REQUIRESPLAYING))
{
//CONS_Printf("skipping %s:%u:%u (%s)\n", sizeu1(c-conditionSets), cn->id, i, player ? "player exists" : "player does not exist");
achievedSoFar = false;
continue;
}
achievedSoFar = M_CheckCondition(cn, player);
//CONS_Printf("%s:%u:%u - %u is %s\n", sizeu1(c-conditionSets), cn->id, i, cn->type, achievedSoFar ? "true" : "false");
}
return achievedSoFar;
@ -599,6 +708,17 @@ static char *M_BuildConditionTitle(UINT16 map)
return title;
}
static const char *M_GetNthType(UINT8 position)
{
if (position == 1)
return "st";
if (position == 2)
return "nd";
if (position == 3)
return "rd";
return "th";
}
// See also M_CheckCondition
static const char *M_GetConditionString(condition_t *cn)
{
@ -606,6 +726,8 @@ static const char *M_GetConditionString(condition_t *cn)
char *title = NULL;
const char *work = NULL;
// If this function returns NULL, it stops building the condition and just does ???'s.
#define BUILDCONDITIONTITLE(i) (M_BuildConditionTitle(i))
switch (cn->type)
@ -737,6 +859,60 @@ static const char *M_GetConditionString(condition_t *cn)
gamedata->unlocked[cn->requirement-1]
? unlockables[cn->requirement-1].name
: "???");
case UC_AND:
return "&";
case UCRP_PREFIX_GRANDPRIX:
return "GRAND PRIX:";
case UCRP_PREFIX_BONUSROUND:
return "BONUS ROUND:";
case UCRP_PREFIX_TIMEATTACK:
if (!M_SecretUnlocked(SECRET_TIMEATTACK, true))
return NULL;
return "TIME ATTACK:";
case UCRP_PREFIX_BREAKTHECAPSULES:
return "BREAK THE CAPSULES:";
case UCRP_PREFIX_SEALEDSTAR:
return "SEALED STAR:";
case UCRP_ISMAP:
if (cn->requirement >= nummapheaders || !mapheaderinfo[cn->requirement])
return va("INVALID MAP CONDITION \"%d:%d\"", cn->type, cn->requirement);
title = BUILDCONDITIONTITLE(cn->requirement);
work = va("On %s,", title);
Z_Free(title);
return work;
case UCRP_ISCHARACTER:
if (cn->requirement < 0 || !skins[cn->requirement].realname[0])
return va("INVALID CHAR CONDITION \"%d:%d\"", cn->type, cn->requirement);
return va("as %s", skins[cn->requirement].realname);
case UCRP_FINISHCOOL:
return "finish in good standing";
case UCRP_FINISHALLCAPSULES:
return "break every capsule";
case UCRP_NOCONTEST:
return "NO CONTEST";
case UCRP_FINISHPLACE:
case UCRP_FINISHPLACEEXACT:
return va("finish in %d%s%s", cn->requirement, M_GetNthType(cn->requirement),
((cn->type == UCRP_FINISHPLACE && cn->requirement > 1)
? " or better" : ""));
case UCRP_FINISHTIME:
case UCRP_FINISHTIMEEXACT:
return va("finish in %s%i:%02i.%02i",
(cn->type == UCRP_FINISHTIMEEXACT ? "exactly " : ""),
G_TicsToMinutes(cn->requirement, true),
G_TicsToSeconds(cn->requirement),
G_TicsToCentiseconds(cn->requirement));
case UCRP_FINISHTIMELEFT:
return va("finish with %i:%02i.%02i remaining",
G_TicsToMinutes(cn->requirement, true),
G_TicsToSeconds(cn->requirement),
G_TicsToCentiseconds(cn->requirement));
default:
break;
}
@ -746,20 +922,16 @@ static const char *M_GetConditionString(condition_t *cn)
#undef BUILDCONDITIONTITLE
}
//#define ACHIEVEDBRITE
char *M_BuildConditionSetString(UINT8 unlockid)
{
conditionset_t *c = NULL;
UINT32 lastID = 0;
condition_t *cn;
#ifdef ACHIEVEDBRITE
boolean achieved = false;
#endif
size_t len = 1024, worklen;
static char message[1024] = "";
const char *work = NULL;
size_t max = 0, start = 0, strlines = 0, i;
boolean stopasap = false;
message[0] = '\0';
@ -781,43 +953,40 @@ char *M_BuildConditionSetString(UINT8 unlockid)
if (i > 0)
{
worklen = 3;
if (lastID == cn->id)
{
strncat(message, "\n& ", len);
}
else
if (lastID != cn->id)
{
worklen = 4;
strncat(message, "\nOR ", len);
worklen++;
}
else //if (cn->type >= UCRP_REQUIRESPLAYING)
{
worklen = 1;
strncat(message, " ", len);
}
/*else
{
worklen = 3;
strncat(message, "\n& ", len);
}*/
len -= worklen;
}
lastID = cn->id;
#ifdef ACHIEVEDBRITE
achieved = M_CheckCondition(cn);
if (achieved)
{
strncat(message, "\0x82", len);
len--;
}
#endif
work = M_GetConditionString(cn);
if (work == NULL)
{
stopasap = true;
work = "???";
}
worklen = strlen(work);
strncat(message, work, len);
len -= worklen;
#ifdef ACHIEVEDBRITE
if (achieved)
if (stopasap)
{
strncat(message, "\0x80", len);
len--;
break;
}
#endif
}
// Rudementary word wrapping.
@ -854,10 +1023,11 @@ char *M_BuildConditionSetString(UINT8 unlockid)
return message;
}
static void M_CheckUnlockConditions(void)
static boolean M_CheckUnlockConditions(player_t *player)
{
INT32 i;
conditionset_t *c;
boolean ret;
for (i = 0; i < MAXCONDITIONSETS; ++i)
{
@ -865,8 +1035,13 @@ static void M_CheckUnlockConditions(void)
if (!c->numconditions || gamedata->achieved[i])
continue;
gamedata->achieved[i] = (M_CheckConditionSet(c));
if ((gamedata->achieved[i] = (M_CheckConditionSet(c, player))) != true)
continue;
ret = true;
}
return ret;
}
boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud)
@ -882,7 +1057,26 @@ boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud)
M_CompletionEmblems();
}
M_CheckUnlockConditions();
response = M_CheckUnlockConditions(NULL);
if (Playing() && (gamestate == GS_LEVEL))
{
for (i = 0; i <= splitscreen; i++)
{
if (!playeringame[g_localplayers[i]])
continue;
if (players[g_localplayers[i]].spectator)
continue;
response |= M_CheckUnlockConditions(&players[g_localplayers[i]]);
}
}
if (!response && loud)
{
return false;
}
response = 0;
// Go through unlockables
for (i = 0; i < MAXUNLOCKABLES; ++i)

View file

@ -33,15 +33,39 @@ typedef enum
UC_POWERLEVEL, // SRB2Kart: POWERLEVEL [power level to reach] [gametype, "0" for race, "1" for battle]
UC_GAMECLEAR, // GAMECLEAR <x times>
UC_OVERALLTIME, // OVERALLTIME [time to beat, tics]
UC_MAPVISITED, // MAPVISITED [map number]
UC_MAPBEATEN, // MAPBEATEN [map number]
UC_MAPENCORE, // MAPENCORE [map number]
UC_MAPTIME, // MAPTIME [map number] [time to beat, tics]
UC_MAPVISITED, // MAPVISITED [map]
UC_MAPBEATEN, // MAPBEATEN [map]
UC_MAPENCORE, // MAPENCORE [map]
UC_MAPTIME, // MAPTIME [map] [time to beat, tics]
UC_TRIGGER, // TRIGGER [trigger number]
UC_TOTALMEDALS, // TOTALMEDALS [number of emblems]
UC_EMBLEM, // EMBLEM [emblem number]
UC_UNLOCKABLE, // UNLOCKABLE [unlockable number]
UC_CONDITIONSET, // CONDITIONSET [condition set number]
UC_AND, // Just for string building
UCRP_REQUIRESPLAYING, // All conditions below this can only be checked if (Playing() && gamestate == GS_LEVEL).
UCRP_PREFIX_GRANDPRIX = UCRP_REQUIRESPLAYING, // GRAND PRIX:
UCRP_PREFIX_BONUSROUND, // BONUS ROUND:
UCRP_PREFIX_TIMEATTACK, // TIME ATTACK:
UCRP_PREFIX_BREAKTHECAPSULES, // BREAK THE CAPSULES:
UCRP_PREFIX_SEALEDSTAR, // SEALED STAR:
UCRP_ISMAP, // gamemap == [map]
UCRP_ISCHARACTER, // character == [skin]
UCRP_FINISHCOOL, // Finish in good standing
UCRP_FINISHALLCAPSULES, // Break all capsules
UCRP_NOCONTEST, // No Contest
UCRP_FINISHPLACE, // Finish at least [place]
UCRP_FINISHPLACEEXACT, // Finish at [place] exactly
UCRP_FINISHTIME, // Finish <= [time, tics]
UCRP_FINISHTIMEEXACT, // Finish == [time, tics]
UCRP_FINISHTIMELEFT, // Finish with at least [time, tics] to spare
} conditiontype_t;
// Condition Set information
@ -55,6 +79,7 @@ struct condition_t
INT32 requirement; /// <- The requirement for this variable.
INT16 extrainfo1; /// <- Extra information for the condition when needed.
INT16 extrainfo2; /// <- Extra information for the condition when needed.
char *pendingstring; /// oooohhh my god i hate loading order for SOC VS skins
};
struct conditionset_t
{
@ -217,14 +242,15 @@ char *M_BuildConditionSetString(UINT8 unlockid);
#define DESCRIPTIONWIDTH 170
// Condition set setup
void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2);
void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2, char *pendingstring);
void M_UpdateConditionSetsPending(void);
// Clearing secrets
void M_ClearConditionSet(UINT8 set);
void M_ClearSecrets(void);
// Updating conditions and unlockables
UINT8 M_CheckCondition(condition_t *cn);
boolean M_CheckCondition(condition_t *cn, player_t *player);
boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud);
UINT8 M_GetNextAchievedUnlock(void);
UINT8 M_CheckLevelEmblems(void);

View file

@ -630,6 +630,10 @@ void P_Ticker(boolean run)
#undef PLAYERCONDITION
ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time;
// TODO would this be laggy with more conditions in play...
if (M_UpdateUnlockablesAndExtraEmblems(true))
G_SaveGameData();
}
// Keep track of how long they've been playing!

View file

@ -156,6 +156,7 @@ void R_InitSkins(void)
R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps);
}
ST_ReloadSkinFaceGraphics();
M_UpdateConditionSetsPending();
}
UINT8 *R_GetSkinAvailabilities(boolean demolock)