readcondition: Support non-fragmented parameters that can be used for the Challenge Description directly

- "DescriptionOverride" (new!)
    - Provide a full description in place of the params and this will be used
    - Any conditions that are before it will not be wiped, so you can prefix it if you need to
    - Can be used with any other set of Conditions
        - `Condition1 = DescriptionOverride Complete the sentence: "ring racers (???)"`
- "Password"
    - Now supports passwords that contain spaces
        - `Condition1 = Password race as a ring!`
- "WetPlayer"
    - Now supports liquids that contain spaces
    - HOWEVER, it comes with the following caveats as part of the change:
        - The strictness level must be provided first.
        - You can't leave the strictness out. The previous default behaviour now requires STANDARD to be explicitly written.
        - `Condition1 = WetPlayer Standard Mega Mack` can be used, now.
This commit is contained in:
toaster 2023-10-13 16:21:18 +01:00
parent 7d5f0ea3ba
commit 1c4750a0a2
3 changed files with 114 additions and 69 deletions

View file

@ -72,6 +72,8 @@ fixed_t get_number(const char *word)
#define PARAMCHECK(n) do { if (!params[n]) { deh_warning("Too few parameters, need %d", n); return; }} while (0)
#define EXTENDEDPARAMCHECK(spos, n) do { if (!spos || !(*spos)) { deh_warning("Missing extended parameter, need at least %d", n); return; }} while (0)
/* ======================================================================== */
// Load a dehacked file format
/* ======================================================================== */
@ -2421,43 +2423,121 @@ void readunlockable(MYFILE *f, INT32 num)
Z_Free(s);
}
// This is a home-grown strtok(" ") equivalent so we can isolate the first chunk without destroying the rest of the line.
static void conditiongetparam(char **params, UINT8 paramid, char **spos)
{
if (*spos == NULL || *(*spos) == '\0')
{
params[paramid] = NULL;
return;
}
params[paramid] = *spos;
while (*(*spos) != '\0' && *(*spos) != ' ')
{
*(*spos) = toupper(*(*spos));
(*spos)++;
}
if (*(*spos) == ' ')
{
*(*spos) = '\0';
(*spos)++;
while (*(*spos) == ' ')
(*spos)++;
}
}
static void readcondition(UINT16 set, UINT32 id, char *word2)
{
INT32 i;
char *params[5]; // condition, requirement, extra info, extra info, stringvar
char *spos;
const UINT8 MAXCONDITIONPARAMS = 5;
char *params[MAXCONDITIONPARAMS]; // condition, requirement, extra info, extra info, stringvar
char *spos = NULL;
char *stringvar = NULL;
conditiontype_t ty;
conditiontype_t ty = UC_NONE;
INT32 re = 0;
INT16 x1 = 0, x2 = 0;
INT32 offset = 0;
#if 0
char *endpos = word2 + strlen(word2);
#endif
spos = strtok(word2, " ");
for (i = 0; i < 5; ++i)
// Lop the leading spaces off
if (word2 && *word2)
{
if (spos != NULL)
{
params[i] = spos;
spos = strtok(NULL, " ");
}
else
params[i] = NULL;
spos = word2;
while (*spos == ' ')
spos++;
}
conditiongetparam(params, 0, &spos);
if (!params[0])
{
deh_warning("condition line is empty for condition ID %d", id+1);
return;
}
if (fastcmp(params[0], "PLAYTIME"))
// We do free descriptions first.
if (fastcmp(params[0], "DESCRIPTIONOVERRIDE"))
{
EXTENDEDPARAMCHECK(spos, 1);
ty = UC_DESCRIPTIONOVERRIDE;
stringvar = Z_StrDup(spos);
}
else if (fastcmp(params[0], "PASSWORD"))
{
EXTENDEDPARAMCHECK(spos, 1);
ty = UC_PASSWORD;
stringvar = Z_StrDup(spos);
re = -1;
}
if (ty != UC_NONE)
goto setcondition;
// Now conditions that take one standard param and one free description.
conditiongetparam(params, 1, &spos);
if (fastcmp(params[0], "WETPLAYER"))
{
PARAMCHECK(1);
//EXTENDEDPARAMCHECK(spos, 2);
ty = UCRP_WETPLAYER;
re = MFE_UNDERWATER;
x1 = 1;
if (fastcmp(params[1], "STRICT"))
re |= MFE_TOUCHWATER;
else if (fastcmp(params[1], "STANDARD"))
;
else
{
deh_warning("liquid strictness requirement \"%s\" invalid for condition ID %d", params[1], id+1);
return;
}
if (spos && *spos)
stringvar = Z_StrDup(spos);
}
if (ty != UC_NONE)
goto setcondition;
// Now for all other conditions.
for (i = 2; i < MAXCONDITIONPARAMS; i++)
{
conditiongetparam(params, i, &spos);
}
if (ty != UC_NONE)
;
else if (fastcmp(params[0], "PLAYTIME"))
{
PARAMCHECK(1);
ty = UC_PLAYTIME + offset;
@ -2627,13 +2707,6 @@ static void readcondition(UINT16 set, UINT32 id, char *word2)
//PARAMCHECK(1);
ty = UC_ADDON + offset;
}
else if (fastcmp(params[0], "PASSWORD"))
{
PARAMCHECK(1);
ty = UC_PASSWORD;
stringvar = Z_StrDup(params[1]);
re = -1;
}
else if (fastcmp(params[0], "SPRAYCAN"))
{
PARAMCHECK(1);
@ -2844,27 +2917,6 @@ static void readcondition(UINT16 set, UINT32 id, char *word2)
deh_warning("Trigger ID %d out of range (0 - 31) for condition ID %d", re, id+1);
return;
}
// The following undid the effects of strtok.
// Unfortunately, there is no way it can reasonably undo the effects of strupr.
// If we want custom descriptions for map execution triggers, we're gonna need a different method.
#if 0
// undo affect of strtok
i = 5;
// so spos will still be the strtok from earlier
while (i >= 2)
{
if (!spos)
continue;
while (*spos != '\0')
spos++;
if (spos < endpos)
*spos = ' ';
spos = params[--i];
}
stringvar = Z_StrDup(params[2]);
#endif
}
else if ((offset=0) || fastcmp(params[0], "FALLOFF")
|| (++offset && fastcmp(params[0], "TOUCHOFFROAD"))
@ -2886,32 +2938,13 @@ static void readcondition(UINT16 set, UINT32 id, char *word2)
//PARAMCHECK(1);
ty = UCRP_TRIPWIREHYUU + offset;
}
else if (fastcmp(params[0], "WETPLAYER"))
{
PARAMCHECK(1);
ty = UCRP_WETPLAYER;
re = MFE_UNDERWATER;
x1 = 1;
if (params[2])
{
if (fastcmp(params[2], "STRICT"))
re |= MFE_TOUCHWATER;
else
{
deh_warning("liquid strictness requirement \"%s\" invalid for condition ID %d", params[2], id+1);
return;
}
}
stringvar = Z_StrDup(params[1]);
}
else
{
deh_warning("Invalid condition name %s for condition ID %d", params[0], id+1);
return;
}
setcondition:
M_AddRawCondition(set, (UINT8)id, ty, re, x1, x2, stringvar);
}
@ -2954,7 +2987,7 @@ void readconditionset(MYFILE *f, UINT16 setnum)
// Now get the part after
word2 = tmp += 2;
strupr(word2);
//strupr(word2);
if (fastncmp(word, "CONDITION", 9))
{

View file

@ -1241,6 +1241,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
{
switch (cn->type)
{
case UC_NONE:
return false;
case UC_PLAYTIME: // Requires total playing time >= x
return (gamedata->totalplaytime >= (unsigned)cn->requirement);
case UC_ROUNDSPLAYED: // Requires any level completed >= x times
@ -1359,6 +1361,7 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
// Just for string building
case UC_AND:
case UC_COMMA:
case UC_DESCRIPTIONOVERRIDE:
return true;
case UCRP_PREFIX_GRANDPRIX:
@ -1550,7 +1553,7 @@ static boolean M_CheckConditionSet(conditionset_t *c, player_t *player)
continue;
// Skip entries that are JUST for string building
if (cn->type == UC_AND || cn->type == UC_COMMA)
if (cn->type == UC_AND || cn->type == UC_COMMA || cn->type == UC_DESCRIPTIONOVERRIDE)
continue;
lastID = cn->id;
@ -1962,6 +1965,8 @@ static const char *M_GetConditionString(condition_t *cn)
return "&";
case UC_COMMA:
return ",";
case UC_DESCRIPTIONOVERRIDE:
return cn->stringvar;
case UCRP_PREFIX_GRANDPRIX:
return "GRAND PRIX:";
@ -2140,7 +2145,7 @@ static const char *M_GetConditionString(condition_t *cn)
case UCRP_WETPLAYER:
return va("without %s %s",
(cn->requirement & MFE_TOUCHWATER) ? "touching any" : "going into",
cn->stringvar);
(cn->stringvar) ? cn->stringvar : "water");
default:
break;
@ -2208,6 +2213,10 @@ char *M_BuildConditionSetString(UINT16 unlockid)
stopasap = true;
work = "???";
}
else if (cn->type == UC_DESCRIPTIONOVERRIDE)
{
stopasap = true;
}
worklen = strlen(work);
strncat(message, work, len);

View file

@ -28,6 +28,8 @@ extern "C" {
// [required] <optional>
typedef enum
{
UC_NONE,
UC_PLAYTIME, // PLAYTIME [tics]
UC_ROUNDSPLAYED, // ROUNDSPLAYED [x played]
UC_TOTALRINGS, // TOTALRINGS [x collected]
@ -68,6 +70,7 @@ typedef enum
// Just for string building
UC_AND,
UC_COMMA,
UC_DESCRIPTIONOVERRIDE,
UCRP_REQUIRESPLAYING, // All conditions below this can only be checked if (Playing() && gamestate == GS_LEVEL).