From 5b1117921393027469a1b96961bc7c7a877b0446 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 2 Mar 2023 23:02:42 +0000 Subject: [PATCH] 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) --- src/deh_soc.c | 93 +++++++++++++++++- src/m_cond.c | 264 +++++++++++++++++++++++++++++++++++++++++++------- src/m_cond.h | 38 ++++++-- src/p_tick.c | 4 + src/r_skins.c | 1 + 5 files changed, 354 insertions(+), 46 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index f79cf63d7..076713297 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -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) diff --git a/src/m_cond.c b/src/m_cond.c index 070c5b9ea..76fe45199 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -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) diff --git a/src/m_cond.h b/src/m_cond.h index 6918e4771..6a0949f58 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -33,15 +33,39 @@ typedef enum UC_POWERLEVEL, // SRB2Kart: POWERLEVEL [power level to reach] [gametype, "0" for race, "1" for battle] UC_GAMECLEAR, // GAMECLEAR 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); diff --git a/src/p_tick.c b/src/p_tick.c index 70c4a14f3..9a1324f2f 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -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! diff --git a/src/r_skins.c b/src/r_skins.c index d6294fa0d..1f4daba5f 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -156,6 +156,7 @@ void R_InitSkins(void) R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps); } ST_ReloadSkinFaceGraphics(); + M_UpdateConditionSetsPending(); } UINT8 *R_GetSkinAvailabilities(boolean demolock)