Merge branch 'conditions-cascading' into 'master'

Conditions Cascading

Closes #366

See merge request KartKrew/Kart!1053
This commit is contained in:
Oni 2023-03-23 23:51:30 +00:00
commit b9bbb6cb8a
69 changed files with 3137 additions and 691 deletions

View file

@ -1173,6 +1173,7 @@ bool CallFunc_HaveUnlockableTrigger(ACSVM::Thread *thread, const ACSVM::Word *ar
{
UINT8 id = 0;
bool unlocked = false;
auto info = &static_cast<Thread *>(thread)->info;
(void)argC;
@ -1182,9 +1183,11 @@ bool CallFunc_HaveUnlockableTrigger(ACSVM::Thread *thread, const ACSVM::Word *ar
{
CONS_Printf("Bad unlockable trigger ID %d\n", id);
}
else
else if ((info != NULL)
&& (info->mo != NULL && P_MobjWasRemoved(info->mo) == false)
&& (info->mo->player != NULL))
{
unlocked = (unlocktriggers & (1 << id));
unlocked = (info->mo->player->roundconditions.unlocktriggers & (1 << id));
}
thread->dataStk.push(unlocked);
@ -1354,7 +1357,7 @@ bool CallFunc_BreakTheCapsules(ACSVM::Thread *thread, const ACSVM::Word *argV, A
(void)argV;
(void)argC;
thread->dataStk.push(battlecapsules);
thread->dataStk.push(battleprisons);
return false;
}

View file

@ -2242,6 +2242,11 @@ void CV_AddValue(consvar_t *var, INT32 increment)
if (var->PossibleValue == kartspeed_cons_t)
max++; // Accommodate KARTSPEED_AUTO
}
else if (var->PossibleValue == gpdifficulty_cons_t
&& !M_SecretUnlocked(SECRET_MASTERMODE, false))
{
max = KARTSPEED_HARD+1;
}
}
#ifdef PARANOIA
if (currentindice == -1)

View file

@ -174,11 +174,7 @@ extern CV_PossibleValue_t CV_Unsigned[];
extern CV_PossibleValue_t CV_Natural[];
// SRB2kart
#define KARTSPEED_AUTO -1
#define KARTSPEED_EASY 0
#define KARTSPEED_NORMAL 1
#define KARTSPEED_HARD 2
#define KARTGP_MASTER 3 // Not a speed setting, gives the hardest speed with maxed out bots
// the KARTSPEED and KARTGP were previously defined here, but moved to doomstat to avoid circular dependencies
extern CV_PossibleValue_t kartspeed_cons_t[], dummykartspeed_cons_t[], gpdifficulty_cons_t[];
extern consvar_t cv_execversion;

View file

@ -824,7 +824,7 @@ static boolean CL_SendJoin(void)
for (; i < MAXSPLITSCREENPLAYERS; i++)
strncpy(netbuffer->u.clientcfg.names[i], va("Player %c", 'A' + i), MAXPLAYERNAME);
memcpy(&netbuffer->u.clientcfg.availabilities, R_GetSkinAvailabilities(false), MAXAVAILABILITY*sizeof(UINT8));
memcpy(&netbuffer->u.clientcfg.availabilities, R_GetSkinAvailabilities(false, false), MAXAVAILABILITY*sizeof(UINT8));
return HSendPacket(servernode, false, 0, sizeof (clientconfig_pak));
}
@ -3723,7 +3723,7 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum)
static void Got_AddBot(UINT8 **p, INT32 playernum)
{
INT16 newplayernum;
UINT8 i, skinnum = 0;
UINT8 skinnum = 0;
UINT8 difficulty = DIFFICULTBOT;
if (playernum != serverplayer && !IsPlayerAdmin(playernum))
@ -3753,14 +3753,8 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
playernode[newplayernum] = servernode;
// todo find a way to have all auto unlocked for dedicated
if (playeringame[0])
{
for (i = 0; i < MAXAVAILABILITY; i++)
{
players[newplayernum].availabilities[i] = players[0].availabilities[i];
}
}
// this will permit unlocks
memcpy(&players[newplayernum].availabilities, R_GetSkinAvailabilities(false, true), MAXAVAILABILITY*sizeof(UINT8));
players[newplayernum].splitscreenindex = 0;
players[newplayernum].bot = true;
@ -3933,7 +3927,7 @@ boolean SV_SpawnServer(void)
// strictly speaking, i'm not convinced the following is necessary
// but I'm not confident enough to remove it entirely in case it breaks something
{
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(false);
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(false, false);
SINT8 node = 0;
for (; node < MAXNETNODES; node++)
result |= SV_AddWaitingPlayers(node, availabilitiesbuffer, cv_playername[0].zstring, cv_playername[1].zstring, cv_playername[2].zstring, cv_playername[3].zstring);

View file

@ -1045,6 +1045,9 @@ void D_ClearState(void)
cursongcredit.def = NULL;
S_StopSounds();
if (gamedata && gamedata->deferredsave)
G_SaveGameData();
G_SetGamestate(GS_NULL);
wipegamestate = GS_NULL;
}
@ -1920,23 +1923,32 @@ void D_SRB2Main(void)
newskill = (INT16)j;
}
if (grandprixinfo.gp == true)
// Invalidate if locked.
if ((newskill >= KARTSPEED_HARD && !M_SecretUnlocked(SECRET_HARDSPEED, true))
|| (newskill >= KARTGP_MASTER && !M_SecretUnlocked(SECRET_MASTERMODE, true)))
{
if (newskill == KARTGP_MASTER)
{
grandprixinfo.masterbots = true;
newskill = KARTSPEED_HARD;
}
grandprixinfo.gamespeed = newskill;
}
else if (newskill == KARTGP_MASTER)
{
newskill = KARTSPEED_HARD;
newskill = -1;
}
if (newskill != -1)
{
if (grandprixinfo.gp == true)
{
if (newskill == KARTGP_MASTER)
{
grandprixinfo.masterbots = true;
newskill = KARTSPEED_HARD;
}
grandprixinfo.gamespeed = newskill;
}
else if (newskill == KARTGP_MASTER)
{
newskill = KARTSPEED_HARD;
}
CV_SetValue(&cv_kartspeed, newskill);
}
}
if (server && (dedicated || !M_CheckParm("+map")))
@ -1946,7 +1958,7 @@ void D_SRB2Main(void)
I_Error("Can't get first map of gametype\n");
}
if (M_MapLocked(pstartmap))
if (pstartmap != 1 && M_MapLocked(pstartmap))
{
G_SetUsedCheats();
}

View file

@ -2811,7 +2811,7 @@ static void Command_Map_f(void)
return;
}
if (M_MapLocked(newmapnum))
if (/*newmapnum != 1 &&*/ M_MapLocked(newmapnum))
{
ischeating = true;
}

View file

@ -333,6 +333,29 @@ struct botvars_t
tic_t spindashconfirm; // When high enough, they will try spindashing
};
// player_t struct for round-specific condition tracking
struct roundconditions_t
{
// Reduce the number of checks by only updating when this is true
boolean checkthisframe;
// Trivial Yes/no events across multiple UCRP's
boolean fell_off;
boolean touched_offroad;
boolean touched_sneakerpanel;
boolean debt_rings;
boolean tripwire_hyuu;
boolean spb_neuter;
boolean landmine_dunk;
boolean hit_midair;
mobjeflag_t wet_player;
// 32 triggers, one bit each, for map execution
UINT32 unlocktriggers;
};
// player_t struct for all skybox variables
struct skybox_t {
mobj_t * viewpoint;
@ -695,6 +718,7 @@ struct player_t
#endif
sonicloopvars_t loop;
roundconditions_t roundconditions;
};
#ifdef __cplusplus

View file

@ -2280,16 +2280,24 @@ void readunlockable(MYFILE *f, INT32 num)
unlockables[num].type = SECRET_FOLLOWER;
else if (fastcmp(word2, "HARDSPEED"))
unlockables[num].type = SECRET_HARDSPEED;
else if (fastcmp(word2, "MASTERMODE"))
unlockables[num].type = SECRET_MASTERMODE;
else if (fastcmp(word2, "ENCORE"))
unlockables[num].type = SECRET_ENCORE;
else if (fastcmp(word2, "LEGACYBOXRUMMAGE"))
unlockables[num].type = SECRET_LEGACYBOXRUMMAGE;
else if (fastcmp(word2, "TIMEATTACK"))
unlockables[num].type = SECRET_TIMEATTACK;
else if (fastcmp(word2, "BREAKTHECAPSULES"))
unlockables[num].type = SECRET_BREAKTHECAPSULES;
else if (fastcmp(word2, "PRISONBREAK"))
unlockables[num].type = SECRET_PRISONBREAK;
else if (fastcmp(word2, "SPECIALATTACK"))
unlockables[num].type = SECRET_SPECIALATTACK;
else if (fastcmp(word2, "SPBATTACK"))
unlockables[num].type = SECRET_SPBATTACK;
else if (fastcmp(word2, "ONLINE"))
unlockables[num].type = SECRET_ONLINE;
else if (fastcmp(word2, "ADDONS"))
unlockables[num].type = SECRET_ADDONS;
else if (fastcmp(word2, "EGGTV"))
unlockables[num].type = SECRET_EGGTV;
else if (fastcmp(word2, "SOUNDTEST"))
unlockables[num].type = SECRET_SOUNDTEST;
else if (fastcmp(word2, "ALTTITLE"))
@ -2328,18 +2336,23 @@ void readunlockable(MYFILE *f, INT32 num)
static void readcondition(UINT8 set, UINT32 id, char *word2)
{
INT32 i;
char *params[4]; // condition, requirement, extra info, extra info
char *params[5]; // condition, requirement, extra info, extra info, stringvar
char *spos;
char *stringvar = NULL;
conditiontype_t ty;
INT32 re;
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 < 4; ++i)
for (i = 0; i < 5; ++i)
{
if (spos != NULL)
{
@ -2356,13 +2369,56 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
return;
}
if (fastcmp(params[0], "PLAYTIME")
|| (++offset && fastcmp(params[0], "MATCHESPLAYED")))
if (fastcmp(params[0], "PLAYTIME"))
{
PARAMCHECK(1);
ty = UC_PLAYTIME + offset;
re = atoi(params[1]);
}
else if (fastcmp(params[0], "ROUNDSPLAYED"))
{
PARAMCHECK(1);
ty = UC_ROUNDSPLAYED;
re = atoi(params[1]);
x1 = GDGT_MAX;
if (re == 0)
{
deh_warning("Rounds played requirement is %d for condition ID %d", re, id+1);
return;
}
if (params[2])
{
if (fastcmp(params[2], "RACE"))
x1 = GDGT_RACE;
else if (fastcmp(params[2], "BATTLE"))
x1 = GDGT_BATTLE;
else if (fastcmp(params[2], "PRISONS"))
x1 = GDGT_PRISONS;
else if (fastcmp(params[2], "SPECIAL"))
x1 = GDGT_SPECIAL;
else if (fastcmp(params[2], "CUSTOM"))
x1 = GDGT_CUSTOM;
else
{
deh_warning("gametype requirement \"%s\" invalid for condition ID %d", params[2], id+1);
return;
}
}
}
else if (fastcmp(params[0], "TOTALRINGS"))
{
PARAMCHECK(1);
ty = UC_TOTALRINGS;
re = atoi(params[1]);
if (re < 2 || re > GDMAX_RINGS)
{
deh_warning("Total Rings requirement %d out of range (%d - %d) for condition ID %d", re, 2, GDMAX_RINGS, id+1);
return;
}
}
else if (fastcmp(params[0], "POWERLEVEL"))
{
PARAMCHECK(2);
@ -2394,7 +2450,9 @@ 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"))
|| (++offset && fastcmp(params[0], "MAPSPBATTACK")))
{
PARAMCHECK(1);
ty = UC_MAPVISITED + offset;
@ -2419,17 +2477,26 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
return;
}
}
else if (fastcmp(params[0], "TRIGGER"))
else if ((offset=0) || fastcmp(params[0], "ALLCHAOS")
|| (++offset && fastcmp(params[0], "ALLSUPER"))
|| (++offset && fastcmp(params[0], "ALLEMERALDS")))
{
PARAMCHECK(1);
ty = UC_TRIGGER;
re = atoi(params[1]);
// constrained by 32 bits
if (re < 0 || re > 31)
//PARAMCHECK(1);
ty = UC_ALLCHAOS + offset;
re = KARTSPEED_NORMAL;
if (params[1])
{
deh_warning("Trigger ID %d out of range (0 - 31) for condition ID %d", re, id+1);
return;
if (fastcmp(params[1], "NORMAL"))
;
else if (fastcmp(params[1], "HARD"))
x1 = KARTSPEED_HARD;
else if (fastcmp(params[1], "MASTER"))
x1 = KARTGP_MASTER;
else
{
deh_warning("gamespeed requirement \"%s\" invalid for condition ID %d", params[1], id+1);
return;
}
}
}
else if (fastcmp(params[0], "TOTALMEDALS"))
@ -2474,13 +2541,273 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
return;
}
}
else if ((offset=0) || fastcmp(params[0], "ADDON")
|| (++offset && fastcmp(params[0], "REPLAY"))
|| (++offset && fastcmp(params[0], "CRASH")))
{
//PARAMCHECK(1);
ty = UC_ADDON + offset;
}
else if ((offset=0) || fastcmp(params[0], "AND")
|| (++offset && fastcmp(params[0], "COMMA")))
{
//PARAMCHECK(1);
ty = UC_AND + offset;
}
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_PRISONBREAK"))
|| (++offset && fastcmp(params[0], "PREFIX_SEALEDSTAR")))
{
//PARAMCHECK(1);
ty = UCRP_PREFIX_GRANDPRIX + offset;
}
else if ((offset=0) || fastcmp(params[0], "PREFIX_ISMAP")
|| (++offset && fastcmp(params[0], "ISMAP")))
{
PARAMCHECK(1);
ty = UCRP_PREFIX_ISMAP + offset;
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
{
stringvar = Z_StrDup(params[1]);
re = -1;
}
#endif
}
else if (fastcmp(params[0], "ISENGINECLASS"))
{
PARAMCHECK(1);
ty = UCRP_ISENGINECLASS;
if (!params[1][1]
&& params[1][0] >= 'A' && params[1][0] <= 'J')
{
re = params[1][0] - 'A';
}
else
{
deh_warning("engine class requirement \"%s\" invalid for condition ID %d", params[1], id+1);
return;
}
}
else if (fastcmp(params[0], "ISDIFFICULTY"))
{
//PARAMCHECK(1);
ty = UCRP_ISDIFFICULTY;
re = KARTSPEED_NORMAL;
if (params[1])
{
if (fastcmp(params[1], "NORMAL"))
;
else if (fastcmp(params[1], "HARD"))
x1 = KARTSPEED_HARD;
else if (fastcmp(params[1], "MASTER"))
x1 = KARTGP_MASTER;
else
{
deh_warning("gamespeed requirement \"%s\" invalid for condition ID %d", params[1], id+1);
return;
}
}
}
else if (fastcmp(params[0], "PODIUMCUP"))
{
PARAMCHECK(1);
ty = UCRP_PODIUMCUP;
{
cupheader_t *cup = kartcupheaders;
while (cup)
{
if (!strcmp(cup->name, params[1]))
break;
cup = cup->next;
}
if (!cup)
{
deh_warning("Invalid cup %s for condition ID %d", params[1], id+1);
return;
}
re = cup->id;
}
if (params[2])
{
if (params[2][0] && !params[2][1])
{
x2 = 1;
switch (params[2][0])
{
case 'E': { x1 = GRADE_E; break; }
case 'D': { x1 = GRADE_D; break; }
case 'C': { x1 = GRADE_C; break; }
case 'B': { x1 = GRADE_B; break; }
case 'A': { x1 = GRADE_A; break; }
case 'S': { x1 = GRADE_S; break; }
default:
deh_warning("Invalid grade %s for condition ID %d", params[2], id+1);
return;
}
}
else if ((offset=0) || fastcmp(params[2], "GOLD")
|| (++offset && fastcmp(params[2], "SILVER"))
|| (++offset && fastcmp(params[2], "BRONZE")))
{
x1 = offset + 1;
}
else
{
deh_warning("Invalid cup result %s for condition ID %d", params[2], id+1);
return;
}
}
}
else if ((offset=0) || fastcmp(params[0], "PODIUMEMERALD")
|| (++offset && fastcmp(params[0], "PODIUMPRIZE")))
{
//PARAMCHECK(1);
ty = UCRP_PODIUMEMERALD + offset;
}
else if ((offset=0) || fastcmp(params[0], "FINISHCOOL")
|| (++offset && fastcmp(params[0], "FINISHALLPRISONS"))
|| (++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 = get_number(params[1]);
if (re < 0)
{
deh_warning("Invalid time %s for condition ID %d", params[1], id+1);
return;
}
}
else if (fastcmp(params[0], "TRIGGER"))
{
PARAMCHECK(2); // strictly speaking at LEAST two
ty = UCRP_TRIGGER;
re = atoi(params[1]);
// constrained by 32 bits
if (re < 0 || re > 31)
{
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];
}
#endif
stringvar = Z_StrDup(params[2]);
}
else if ((offset=0) || fastcmp(params[0], "FALLOFF")
|| (++offset && fastcmp(params[0], "TOUCHOFFROAD"))
|| (++offset && fastcmp(params[0], "TOUCHSNEAKERPANEL"))
|| (++offset && fastcmp(params[0], "RINGDEBT")))
{
PARAMCHECK(1);
ty = UCRP_FALLOFF + offset;
re = 1;
if (params[1][0] == 'F' || params[1][0] == 'N' || params[1][0] == '0')
re = 0;
}
else if ((offset=0) || fastcmp(params[0], "TRIPWIREHYUU")
|| (++offset && fastcmp(params[0], "SPBNEUTER"))
|| (++offset && fastcmp(params[0], "LANDMINEDUNK"))
|| (++offset && fastcmp(params[0], "HITMIDAIR")))
{
//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;
}
M_AddRawCondition(set, (UINT8)id, ty, re, x1, x2);
M_AddRawCondition(set, (UINT8)id, ty, re, x1, x2, stringvar);
}
void readconditionset(MYFILE *f, UINT8 setnum)
@ -2633,6 +2960,10 @@ void readmaincfg(MYFILE *f, boolean mainfile)
clear_emblems();
//clear_levels();
doClearLevels = true;
G_ClearRecords();
M_ClearStats();
M_ClearSecrets();
}
#ifndef DEVELOP
else if (!mainfile && !gamedataadded)
@ -3101,7 +3432,16 @@ void readcupheader(MYFILE *f, cupheader_t *cup)
i = atoi(word2); // used for numerical settings
strupr(word2);
if (fastcmp(word, "ICON"))
if (fastcmp(word, "MONITOR"))
{
if (i > 0 && i < 10)
cup->monitor = i;
else if (!word2[0] || word2[1] != '\0' || word2[0] == '0')
deh_warning("%s Cup: Invalid monitor type \"%s\" (should be 1-9 or A-Z)\n", cup->name, word2);
else
cup->monitor = (word2[0] - 'A') + 10;
}
else if (fastcmp(word, "ICON"))
{
deh_strlcpy(cup->icon, word2,
sizeof(cup->icon), va("%s Cup: icon", cup->name));

View file

@ -5837,7 +5837,7 @@ const char *const GAMETYPERULE_LIST[] = {
"KARMA",
"ITEMARROWS",
"CAPSULES",
"PRISONS",
"CATCHER",
"ROLLINGSTART",
"SPECIALSTART",

View file

@ -486,6 +486,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
{
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)

View file

@ -120,6 +120,30 @@ struct recorddata_t
//UINT16 rings; ///< Rings when the level was finished.
};
#define KARTSPEED_AUTO -1
#define KARTSPEED_EASY 0
#define KARTSPEED_NORMAL 1
#define KARTSPEED_HARD 2
#define KARTGP_MASTER 3 // Not a speed setting, gives the hardest speed with maxed out bots
#define KARTGP_MAX 4
typedef enum
{
GRADE_E,
GRADE_D,
GRADE_C,
GRADE_B,
GRADE_A,
GRADE_S
} gp_rank_e;
struct cupwindata_t
{
UINT8 best_placement;
gp_rank_e best_grade;
boolean got_emerald;
};
// 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)
@ -352,6 +376,7 @@ struct customoption_t
struct cupheader_t
{
UINT16 id; ///< Cup ID
UINT8 monitor; ///< Monitor graphic 1-9 or A-Z
char name[15]; ///< Cup title (14 chars)
char icon[9]; ///< Name of the icon patch
char *levellist[CUPCACHE_MAX]; ///< List of levels that belong to this cup
@ -359,6 +384,7 @@ struct cupheader_t
UINT8 numlevels; ///< Number of levels defined in levellist
UINT8 numbonus; ///< Number of bonus stages defined
UINT8 emeraldnum; ///< ID of Emerald to use for special stage (1-7 for Chaos Emeralds, 8-14 for Super Emeralds, 0 for no emerald)
cupwindata_t windata[4]; ///< Data for cup visitation
cupheader_t *next; ///< Next cup in linked list
};
@ -528,7 +554,7 @@ enum GameTypeRules
GTR_ITEMARROWS = 1<<9, // Show item box arrows above players
// Bonus gametype rules
GTR_CAPSULES = 1<<10, // Can enter Break The Capsules mode
GTR_PRISONS = 1<<10, // Can enter Prison Break mode
GTR_CATCHER = 1<<11, // UFO Catcher (only works with GTR_CIRCUIT)
GTR_ROLLINGSTART = 1<<12, // Rolling start (only works with GTR_CIRCUIT)
GTR_SPECIALSTART = 1<<13, // White fade instant start

View file

@ -310,6 +310,8 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
void F_StartIntro(void)
{
cursongcredit.def = NULL;
if (gamestate)
{
F_WipeStartScreen();
@ -1090,7 +1092,7 @@ void F_GameEvaluationTicker(void)
{
++gamedata->timesBeaten;
M_UpdateUnlockablesAndExtraEmblems(true);
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData();
}
else

View file

@ -26,6 +26,7 @@
#include "g_game.h"
#include "g_demo.h"
#include "m_misc.h"
#include "m_cond.h"
#include "k_menu.h"
#include "m_argv.h"
#include "hu_stuff.h"
@ -2240,7 +2241,7 @@ static void G_SaveDemoSkins(UINT8 **pp)
{
char skin[16];
UINT8 i;
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(true);
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(true, false);
WRITEUINT8((*pp), numskins);
for (i = 0; i < numskins; i++)
@ -4189,7 +4190,15 @@ void G_SaveDemo(void)
if (!modeattacking)
{
if (demo.savemode == DSM_SAVED)
{
CONS_Printf(M_GetText("Demo %s recorded\n"), demoname);
if (gamedata->eversavedreplay == false)
{
gamedata->eversavedreplay = true;
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData();
}
}
else
CONS_Alert(CONS_WARNING, M_GetText("Demo %s not saved\n"), demoname);
}

View file

@ -465,6 +465,8 @@ void G_AllocMainRecordData(INT16 i)
void G_ClearRecords(void)
{
INT16 i;
cupheader_t *cup;
for (i = 0; i < nummapheaders; ++i)
{
if (mapheaderinfo[i]->mainrecord)
@ -473,6 +475,11 @@ void G_ClearRecords(void)
mapheaderinfo[i]->mainrecord = NULL;
}
}
for (cup = kartcupheaders; cup; cup = cup->next)
{
memset(&cup->windata, 0, sizeof(cup->windata));
}
}
// For easy retrieval of records
@ -662,8 +669,8 @@ void G_UpdateRecords(void)
S_StartSound(NULL, sfx_ncitem);
}
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
M_UpdateUnlockablesAndExtraEmblems(true, true);
gamedata->deferredsave = true;
}
//
@ -2367,19 +2374,6 @@ static inline void G_PlayerFinishLevel(INT32 player)
p->starpostnum = 0;
memset(&p->respawn, 0, sizeof (p->respawn));
// SRB2kart: Increment the "matches played" counter.
if (player == consoleplayer)
{
if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified)
{
gamedata->matchesplayed++;
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
}
legitimateexit = false;
}
}
//
@ -2454,6 +2448,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
INT32 kickstartaccel;
boolean enteredGame;
roundconditions_t roundconditions;
boolean saveroundconditions;
score = players[player].score;
lives = players[player].lives;
ctfteam = players[player].ctfteam;
@ -2544,6 +2541,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
khudfinish = 0;
khudcardanimation = 0;
starpostnum = 0;
saveroundconditions = false;
}
else
{
@ -2592,6 +2591,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
starpostnum = players[player].starpostnum;
pflags |= (players[player].pflags & (PF_STASIS|PF_ELIMINATED|PF_NOCONTEST|PF_FAULT|PF_LOSTLIFE));
memcpy(&roundconditions, &players[player].roundconditions, sizeof (roundconditions));
saveroundconditions = true;
}
if (!betweenmaps)
@ -2676,6 +2678,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
memcpy(&p->itemRoulette, &itemRoulette, sizeof (p->itemRoulette));
memcpy(&p->respawn, &respawn, sizeof (p->respawn));
if (saveroundconditions)
memcpy(&p->roundconditions, &roundconditions, sizeof (p->roundconditions));
if (follower)
P_RemoveMobj(follower);
@ -3302,7 +3307,7 @@ static gametype_t defaultgametypes[] =
{
"Battle",
"GT_BATTLE",
GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_POWERSTONES|GTR_KARMA|GTR_ITEMARROWS|GTR_CAPSULES|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_CLOSERPLAYERS,
GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_POWERSTONES|GTR_KARMA|GTR_ITEMARROWS|GTR_PRISONS|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_CLOSERPLAYERS,
TOL_BATTLE,
int_scoreortimeattack,
0,
@ -3866,7 +3871,7 @@ static void G_UpdateVisited(void)
if ((earnedEmblems = M_CompletionEmblems()))
CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
M_UpdateUnlockablesAndExtraEmblems(true);
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData();
}
@ -3946,7 +3951,7 @@ static void G_GetNextMap(void)
{
gp_rank_e grade = K_CalculateGPGrade(&grandprixinfo.rank);
if (grade >= GRADE_A) // On A rank pace? Then you get a chance for S rank!
if (grade >= GRADE_A && grandprixinfo.gamespeed >= KARTSPEED_NORMAL) // On A rank pace? Then you get a chance for S rank!
{
const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_SPECIAL];
if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum])
@ -3954,6 +3959,13 @@ static void G_GetNextMap(void)
grandprixinfo.eventmode = GPEVENT_SPECIAL;
nextmap = cupLevelNum;
newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel);
if (gamedata->everseenspecial == false)
{
gamedata->everseenspecial = true;
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData();
}
}
}
}
@ -4001,7 +4013,7 @@ static void G_GetNextMap(void)
}
else
{
nextmap = prevmap; // Prevent uninitialised use
nextmap = 0; // Prevent uninitialised use -- go to TEST RUN, it's very obvious
}
grandprixinfo.roundnum++;
@ -4164,6 +4176,33 @@ static void G_DoCompleted(void)
if (modeattacking && pausedelay)
pausedelay = 0;
// We do this here so Challenges-related sounds aren't insta-killed
S_StopSounds();
if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified)
{
UINT8 roundtype = GDGT_CUSTOM;
if (gametype == GT_RACE)
roundtype = GDGT_RACE;
else if (gametype == GT_BATTLE)
roundtype = (battleprisons ? GDGT_PRISONS : GDGT_BATTLE);
else if (gametype == GT_SPECIAL || gametype == GT_VERSUS)
roundtype = GDGT_SPECIAL;
gamedata->roundsplayed[roundtype]++;
gamedata->pendingkeyrounds++;
// Done before forced addition of PF_NOCONTEST to make UCRP_NOCONTEST harder to achieve
M_UpdateUnlockablesAndExtraEmblems(true, true);
gamedata->deferredsave = true;
}
if (gamedata->deferredsave)
G_SaveGameData();
legitimateexit = false;
gameaction = ga_nothing;
if (metalplayback)
@ -4174,7 +4213,7 @@ static void G_DoCompleted(void)
G_SetGamestate(GS_NULL);
wipegamestate = GS_NULL;
grandprixinfo.rank.capsules += numtargets;
grandprixinfo.rank.prisons += numtargets;
grandprixinfo.rank.position = MAXPLAYERS;
for (i = 0; i < MAXPLAYERS; i++)
@ -4218,8 +4257,6 @@ static void G_DoCompleted(void)
if (automapactive)
AM_Stop();
S_StopSounds();
prevmap = (INT16)(gamemap-1);
if (!demo.playback)
@ -4486,7 +4523,7 @@ void G_LoadGameSettings(void)
}
#define GD_VERSIONCHECK 0xBA5ED123 // Change every major version, as usual
#define GD_VERSIONMINOR 1 // Change every format update
#define GD_VERSIONMINOR 2 // Change every format update
static const char *G_GameDataFolder(void)
{
@ -4509,36 +4546,36 @@ void G_LoadGameData(void)
//For records
UINT32 numgamedatamapheaders;
UINT32 numgamedatacups;
// Stop saving, until we successfully load it again.
gamedata->loaded = false;
// Clear things so previously read gamedata doesn't transfer
// to new gamedata
// see also M_EraseDataResponse
G_ClearRecords(); // records
M_ClearStats(); // statistics
M_ClearSecrets(); // emblems, unlocks, maps visited, etc
gamedata->totalplaytime = 0; // total play time (separate from all)
gamedata->matchesplayed = 0; // SRB2Kart: matches played & finished
if (M_CheckParm("-nodata"))
{
// Don't load at all.
// The following used to be in M_ClearSecrets, but that was silly.
M_UpdateUnlockablesAndExtraEmblems(false, true);
return;
}
if (M_CheckParm("-resetdata"))
{
// Don't load, but do save. (essentially, reset)
gamedata->loaded = true;
return;
goto finalisegamedata;
}
if (P_SaveBufferFromFile(&save, va(pandf, srb2home, gamedatafilename)) == false)
{
// No gamedata. We can save a new one.
gamedata->loaded = true;
return;
goto finalisegamedata;
}
// Version check
@ -4559,13 +4596,44 @@ 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);
}
if (versionMinor == 0)
if ((versionMinor == 0 || versionMinor == 1)
#ifdef DEVELOP
|| M_CheckParm("-resetchallengegrid")
#endif
)
{
gridunusable = true;
}
if (versionMinor > 1)
{
gamedata->evercrashed = (boolean)READUINT8(save.p);
}
gamedata->totalplaytime = READUINT32(save.p);
gamedata->matchesplayed = READUINT32(save.p);
if (versionMinor > 1)
{
gamedata->totalrings = READUINT32(save.p);
for (i = 0; i < GDGT_MAX; i++)
{
gamedata->roundsplayed[i] = READUINT32(save.p);
}
gamedata->pendingkeyrounds = READUINT32(save.p);
gamedata->pendingkeyroundoffset = READUINT8(save.p);
gamedata->keyspending = READUINT8(save.p);
gamedata->chaokeys = READUINT16(save.p);
gamedata->everloadedaddon = (boolean)READUINT8(save.p);
gamedata->eversavedreplay = (boolean)READUINT8(save.p);
gamedata->everseenspecial = (boolean)READUINT8(save.p);
}
else
{
save.p += 4; // no direct equivalent to matchesplayed
}
{
// Quick & dirty hash for what mod this save file is for.
@ -4672,7 +4740,7 @@ void G_LoadGameData(void)
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);
//CONS_Printf("ID %d, Time = %d, Lap = %d\n", i, rectime/35, reclap/35);
}
}
else
@ -4683,19 +4751,62 @@ void G_LoadGameData(void)
}
}
if (versionMinor > 1)
{
numgamedatacups = READUINT32(save.p);
for (i = 0; i < numgamedatacups; i++)
{
char cupname[16];
cupheader_t *cup;
// Find the relevant cup.
READSTRINGN(save.p, cupname, sizeof(cupname));
for (cup = kartcupheaders; cup; cup = cup->next)
{
if (strcmp(cup->name, cupname))
continue;
break;
}
// Digest its data...
for (j = 0; j < KARTGP_MAX; j++)
{
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;
cup->windata[j].got_emerald = true;
}
}
}
}
}
// done
P_SaveBufferFree(&save);
// Don't consider loaded until it's a success!
// It used to do this much earlier, but this would cause the gamedata to
// save over itself when it I_Errors from the corruption landing point below,
// which can accidentally delete players' legitimate data if the code ever has any tiny mistakes!
gamedata->loaded = true;
finalisegamedata:
{
// Don't consider loaded until it's a success!
// It used to do this much earlier, but this would cause the gamedata to
// save over itself when it I_Errors from the corruption landing point below,
// which can accidentally delete players' legitimate data if the code ever has any tiny mistakes!
gamedata->loaded = true;
// Silent update unlockables in case they're out of sync with conditions
M_UpdateUnlockablesAndExtraEmblems(false);
// Silent update unlockables in case they're out of sync with conditions
M_UpdateUnlockablesAndExtraEmblems(false, true);
return;
return;
}
// Landing point for corrupt gamedata
datacorrupt:
@ -4710,18 +4821,46 @@ void G_LoadGameData(void)
}
}
// G_DirtyGameData
// Modifies the gamedata as little as possible to maintain safety in a crash event, while still recording it.
void G_DirtyGameData(void)
{
FILE *handle = NULL;
const UINT8 writebytesource = true;
if (gamedata)
gamedata->evercrashed = true;
//if (FIL_WriteFileOK(name))
handle = fopen(va(pandf, srb2home, gamedatafilename), "r+");
if (!handle)
return;
// Write a dirty byte immediately after the gamedata check + minor version.
if (fseek(handle, 5, SEEK_SET) != -1)
fwrite(&writebytesource, 1, 1, handle);
fclose(handle);
return;
}
// G_SaveGameData
// Saves the main data file, which stores information such as emblems found, etc.
void G_SaveGameData(void)
{
size_t length;
INT32 i, j;
INT32 i, j, numcups;
cupheader_t *cup;
UINT8 btemp;
savebuffer_t save = {0};
if (gamedata == NULL || !gamedata->loaded)
return; // If never loaded (-nodata), don't save
gamedata->deferredsave = false;
if (usedCheats)
{
#ifdef DEVELOP
@ -4730,12 +4869,27 @@ void G_SaveGameData(void)
return;
}
length = (4+1+4+4+1+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2);
length = (4+1+1+
4+4+
(4*GDGT_MAX)+
4+1+1+2+
1+1+1+
4+
(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+
4+2);
if (gamedata->challengegrid)
{
length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT;
}
length += nummapheaders * (MAXMAPLUMPNAME+1+4+4);
length += 4 + (nummapheaders * (MAXMAPLUMPNAME+1+4+4));
numcups = 0;
for (cup = kartcupheaders; cup; cup = cup->next)
{
numcups++;
}
length += 4 + (numcups * (4+16));
if (P_SaveBufferAlloc(&save, length) == false)
{
@ -4747,8 +4901,30 @@ void G_SaveGameData(void)
WRITEUINT32(save.p, GD_VERSIONCHECK); // 4
WRITEUINT8(save.p, GD_VERSIONMINOR); // 1
// Crash dirtiness
// cannot move, see G_DirtyGameData
WRITEUINT8(save.p, gamedata->evercrashed); // 1
// Statistics
WRITEUINT32(save.p, gamedata->totalplaytime); // 4
WRITEUINT32(save.p, gamedata->matchesplayed); // 4
WRITEUINT32(save.p, gamedata->totalrings); // 4
for (i = 0; i < GDGT_MAX; i++) // 4 * GDGT_MAX
{
WRITEUINT32(save.p, gamedata->roundsplayed[i]);
}
WRITEUINT32(save.p, gamedata->pendingkeyrounds); // 4
WRITEUINT8(save.p, gamedata->pendingkeyroundoffset); // 1
WRITEUINT8(save.p, gamedata->keyspending); // 1
WRITEUINT16(save.p, gamedata->chaokeys); // 2
WRITEUINT8(save.p, gamedata->everloadedaddon); // 1
WRITEUINT8(save.p, gamedata->eversavedreplay); // 1
WRITEUINT8(save.p, gamedata->everseenspecial); // 1
WRITEUINT32(save.p, quickncasehash(timeattackfolder, 64));
// To save space, use one bit per collected/achieved/unlocked flag
@ -4808,7 +4984,7 @@ void G_SaveGameData(void)
for (i = 0; i < nummapheaders; i++) // nummapheaders * (255+1+4+4)
{
// For figuring out which header to assing it to on load
// For figuring out which header to assign it to on load
WRITESTRINGN(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME);
WRITEUINT8(save.p, (mapheaderinfo[i]->mapvisited & MV_MAX));
@ -4825,6 +5001,24 @@ void G_SaveGameData(void)
}
}
WRITEUINT32(save.p, numcups); // 4
for (cup = kartcupheaders; cup; cup = cup->next)
{
// For figuring out which header to assign it to on load
WRITESTRINGN(save.p, cup->name, 16);
for (i = 0; i < KARTGP_MAX; 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;
WRITEUINT8(save.p, btemp); // 4 * numcups
}
}
length = save.p - save.buffer;
FIL_WriteFile(va(pandf, srb2home, gamedatafilename), save.buffer, length);
@ -5175,9 +5369,6 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr
}
}
// Reset unlockable triggers
unlocktriggers = 0;
// clear itemfinder, just in case
if (!dedicated) // except in dedicated servers, where it is not registered and can actually I_Error debug builds
CV_StealthSetValue(&cv_itemfinder, 0);

View file

@ -176,6 +176,7 @@ boolean G_IsTitleCardAvailable(void);
void G_LoadGame(UINT32 slot, INT16 mapoverride);
void G_SaveGameData(void);
void G_DirtyGameData(void);
void G_SaveGame(UINT32 slot, INT16 mapnum);

View file

@ -2474,8 +2474,8 @@ static void HU_DrawRankings(void)
// draw the current gametype in the lower right
if (grandprixinfo.gp == true)
V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Grand Prix");
else if (battlecapsules)
V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Capsules");
else if (battleprisons)
V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Prisons");
else if (gametype >= 0 && gametype < numgametypes)
V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametypes[gametype]->name);
@ -2533,11 +2533,11 @@ static void HU_DrawRankings(void)
}
// Right hand side
if (battlecapsules == true)
if (battleprisons == true)
{
if (numtargets < maptargets)
{
V_DrawCenteredString(256, 8, 0, "CAPSULES");
V_DrawCenteredString(256, 8, 0, "PRISONS");
V_DrawCenteredString(256, 16, hilicol, va("%d", maptargets - numtargets));
}
}

View file

@ -26,7 +26,7 @@
struct battleovertime battleovertime;
// Capsules mode enabled for this map?
boolean battlecapsules = false;
boolean battleprisons = false;
// box respawning in battle mode
INT32 nummapboxes = 0;
@ -38,8 +38,8 @@ UINT8 numtargets = 0; // Capsules busted
INT32 K_StartingBumperCount(void)
{
if (battlecapsules)
return 0; // always 1 hit in Break the Capsules
if (battleprisons)
return 0; // always 1 hit in Prison Break
return cv_kartbumpers.value;
}
@ -143,7 +143,7 @@ void K_CheckBumpers(void)
if (numingame <= 1)
{
if ((gametyperules & GTR_CAPSULES) && (K_CanChangeRules(true) == true))
if ((gametyperules & GTR_PRISONS) && (K_CanChangeRules(true) == true))
{
// Reset map to turn on battle capsules
if (server)
@ -343,7 +343,7 @@ void K_RunPaperItemSpawners(void)
UINT8 pcount = 0;
INT16 i;
if (battlecapsules)
if (battleprisons)
{
// Gametype uses paper items, but this specific expression doesn't
return;
@ -771,7 +771,7 @@ void K_BattleInit(boolean singleplayercontext)
{
size_t i;
if ((gametyperules & GTR_CAPSULES) && singleplayercontext && !battlecapsules)
if ((gametyperules & GTR_PRISONS) && singleplayercontext && !battleprisons)
{
mapthing_t *mt = mapthings;
for (i = 0; i < nummapthings; i++, mt++)
@ -782,7 +782,7 @@ void K_BattleInit(boolean singleplayercontext)
maptargets++;
}
battlecapsules = true;
battleprisons = true;
}
if (gametyperules & GTR_BUMPERS)

View file

@ -18,7 +18,7 @@ extern struct battleovertime
fixed_t x, y, z; ///< Position to center on
} battleovertime;
extern boolean battlecapsules;
extern boolean battleprisons;
extern INT32 nummapboxes, numgotboxes; // keep track of spawned battle mode items
extern UINT8 maptargets, numtargets;

View file

@ -1,7 +1,7 @@
// SONIC ROBO BLAST 2 KART
//-----------------------------------------------------------------------------
// Copyright (C) 2018-2022 by Viv "toaster" Grannell
// Copyright (C) 2018-2022 by Kart Krew
// Copyright (C) 2018-2023 by Vivian "toastergrl" Grannell
// Copyright (C) 2018-2023 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.

View file

@ -1,7 +1,7 @@
// SONIC ROBO BLAST 2 KART
//-----------------------------------------------------------------------------
// Copyright (C) 2018-2022 by Viv "toaster" Grannell
// Copyright (C) 2018-2022 by Kart Krew
// Copyright (C) 2018-2023 by Vivian "toastergrl" Grannell
// Copyright (C) 2018-2023 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.

View file

@ -108,13 +108,15 @@ boolean K_AddBot(UINT8 skin, UINT8 difficulty, UINT8 *p)
--------------------------------------------------*/
void K_UpdateMatchRaceBots(void)
{
const UINT8 defaultbotskin = R_BotDefaultSkin();
const UINT8 difficulty = cv_kartbot.value;
UINT8 pmax = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxconnections.value);
UINT8 numplayers = 0;
UINT8 numbots = 0;
UINT8 numwaiting = 0;
SINT8 wantedbots = 0;
boolean skinusable[MAXSKINS];
UINT8 usableskins = 0;
UINT8 grabskins[MAXSKINS+1];
UINT8 i;
if (!server)
@ -122,18 +124,12 @@ void K_UpdateMatchRaceBots(void)
return;
}
// init usable bot skins list
for (i = 0; i < MAXSKINS; i++)
// Init usable bot skins list
for (i = 0; i < numskins; i++)
{
if (i < numskins)
{
skinusable[i] = true;
}
else
{
skinusable[i] = false;
}
grabskins[usableskins++] = i;
}
grabskins[usableskins] = MAXSKINS;
if (cv_maxplayers.value > 0)
{
@ -146,7 +142,7 @@ void K_UpdateMatchRaceBots(void)
{
if (!players[i].spectator)
{
skinusable[players[i].skin] = false;
grabskins[players[i].skin] = MAXSKINS;
if (players[i].bot)
{
@ -185,48 +181,42 @@ void K_UpdateMatchRaceBots(void)
{
// We require MORE bots!
UINT8 newplayernum = 0;
boolean usedallskins = false;
if (dedicated)
{
newplayernum = 1;
}
// Rearrange usable bot skins list to prevent gaps for randomised selection
for (i = 0; i < usableskins; i++)
{
if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true)))
continue;
while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true)))
{
usableskins--;
}
grabskins[i] = grabskins[usableskins];
grabskins[usableskins] = MAXSKINS;
}
while (numbots < wantedbots)
{
UINT8 skin = M_RandomKey(numskins);
UINT8 skinnum = defaultbotskin;
if (usedallskins == false)
if (usableskins > 0)
{
UINT8 loops = 0;
while (!skinusable[skin])
{
if (loops >= numskins)
{
// no more skins, stick to our first choice
usedallskins = true;
break;
}
skin++;
if (skin >= numskins)
{
skin = 0;
}
loops++;
}
UINT8 index = M_RandomKey(usableskins);
skinnum = grabskins[index];
grabskins[index] = grabskins[--usableskins];
}
if (!K_AddBot(skin, difficulty, &newplayernum))
if (!K_AddBot(skinnum, difficulty, &newplayernum))
{
// Not enough player slots to add the bot, break the loop.
break;
}
skinusable[skin] = false;
numbots++;
}
}

View file

@ -406,7 +406,15 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2)
// Banana snipe!
if (t1->health > 1)
{
if (t1->target && t1->target->player)
{
t1->target->player->roundconditions.landmine_dunk = true;
t1->target->player->roundconditions.checkthisframe = true;
}
S_StartSound(t2, sfx_bsnipe);
}
if (t2->player->flamedash && t2->player->itemtype == KITEM_FLAMESHIELD)
{

View file

@ -95,25 +95,6 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers)
return points;
}
/*--------------------------------------------------
UINT8 K_BotDefaultSkin(void)
See header file for description.
--------------------------------------------------*/
UINT8 K_BotDefaultSkin(void)
{
const char *defaultbotskinname = "eggrobo";
INT32 defaultbotskin = R_SkinAvailable(defaultbotskinname);
if (defaultbotskin == -1)
{
// This shouldn't happen, but just in case
defaultbotskin = 0;
}
return (UINT8)defaultbotskin;
}
/*--------------------------------------------------
UINT8 K_GetGPPlayerCount(UINT8 humans)
@ -139,7 +120,7 @@ UINT8 K_GetGPPlayerCount(UINT8 humans)
--------------------------------------------------*/
void K_InitGrandPrixBots(void)
{
const UINT8 defaultbotskin = K_BotDefaultSkin();
const UINT8 defaultbotskin = R_BotDefaultSkin();
const UINT8 startingdifficulty = K_BotStartingDifficulty(grandprixinfo.gamespeed);
UINT8 difficultylevels[MAXPLAYERS];
@ -532,7 +513,7 @@ void K_IncreaseBotDifficulty(player_t *bot)
--------------------------------------------------*/
void K_RetireBots(void)
{
const UINT8 defaultbotskin = K_BotDefaultSkin();
const UINT8 defaultbotskin = R_BotDefaultSkin();
SINT8 newDifficulty;
UINT8 usableskins;

View file

@ -75,16 +75,6 @@ UINT8 K_BotStartingDifficulty(SINT8 value);
INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers);
/*--------------------------------------------------
UINT8 K_BotDefaultSkin(void);
Returns the skin number of the skin the game
uses as a fallback option.
--------------------------------------------------*/
UINT8 K_BotDefaultSkin(void);
/*--------------------------------------------------
UINT8 K_GetGPPlayerCount(UINT8 humans)

View file

@ -2855,7 +2855,7 @@ static void K_drawKartBumpersOrKarma(void)
V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]);
V_DrawScaledPatch(fx+22, fy, V_HUDTRANS|V_SLIDEIN|splitflags, frameslash);
if (battlecapsules)
if (battleprisons)
{
V_DrawMappedPatch(fx+1, fy-2, V_HUDTRANS|V_SLIDEIN|splitflags, kp_rankcapsule, NULL);
@ -2911,7 +2911,7 @@ static void K_drawKartBumpersOrKarma(void)
}
else
{
if (battlecapsules)
if (battleprisons)
{
if (numtargets > 9 && maptargets > 9)
V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_capsulestickerwide, NULL);
@ -3801,7 +3801,7 @@ static void K_drawKartMinimap(void)
workingPic = kp_capsuleminimap[(mobj->extravalue1 != 0 ? 1 : 0)];
break;
case MT_CDUFO:
if (battlecapsules) //!battleprisons
if (battleprisons)
workingPic = kp_capsuleminimap[2];
break;
default:
@ -4244,7 +4244,7 @@ static void K_drawBattleFullscreen(void)
if (K_IsPlayerLosing(stplyr))
p = kp_battlelose;
else if (stplyr->position == 1 && (!battlecapsules || numtargets >= maptargets))
else if (stplyr->position == 1 && (!battleprisons || numtargets >= maptargets))
p = kp_battlewin;
V_DrawFixedPatch(x<<FRACBITS, y<<FRACBITS, scale, splitflags, p, NULL);
@ -4940,7 +4940,7 @@ static void K_DrawGPRankDebugger(void)
V_DrawThinString(0, 30, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
va("CONTINUES: %d", grandprixinfo.rank.continuesUsed));
V_DrawThinString(0, 40, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
va("CAPSULES: %d / %d", grandprixinfo.rank.capsules, grandprixinfo.rank.totalCapsules));
va("PRISONS: %d / %d", grandprixinfo.rank.prisons, grandprixinfo.rank.totalPrisons));
V_DrawThinString(0, 50, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
va("RINGS: %d / %d", grandprixinfo.rank.rings, grandprixinfo.rank.totalRings));
V_DrawThinString(0, 60, V_SNAPTOTOP|V_SNAPTOLEFT|V_6WIDTHSPACE|V_ALLOWLOWERCASE,
@ -5090,7 +5090,7 @@ void K_drawKartHUD(void)
;
else if ((gametyperules & GTR_POWERSTONES))
{
if (!battlecapsules)
if (!battleprisons)
K_drawKartEmeralds();
}
else if (!islonesome)

View file

@ -365,7 +365,7 @@ bool is_object_tracking_target(const mobj_t* mobj)
{
case MT_BATTLECAPSULE:
case MT_CDUFO:
return battlecapsules; // battleprisons
return battleprisons;
case MT_SPECIAL_UFO:
return true;

View file

@ -371,7 +371,7 @@ boolean K_IsPlayerLosing(player_t *player)
if (player->pflags & PF_NOCONTEST)
return true;
if (battlecapsules && numtargets == 0)
if (battleprisons && numtargets == 0)
return true; // Didn't even TRY?
if (player->position == 1)
@ -558,7 +558,7 @@ boolean K_TimeAttackRules(void)
return true;
}
if (battlecapsules == true)
if (battleprisons == true)
{
// Break the Capsules always uses Time Attack
// rules, since you can bring 2-4 players in
@ -1129,6 +1129,13 @@ static void K_UpdateOffroad(player_t *player)
if (player->offroad > offroadstrength)
player->offroad = offroadstrength;
if (player->roundconditions.touched_offroad == false
&& player->offroad > (2*offroadstrength) / TICRATE)
{
player->roundconditions.touched_offroad = true;
player->roundconditions.checkthisframe = true;
}
}
else
player->offroad = 0;
@ -4223,7 +4230,15 @@ void K_ApplyTripWire(player_t *player, tripwirestate_t state)
K_TumblePlayer(player, NULL, NULL);
if (state == TRIPSTATE_PASSED)
{
S_StartSound(player->mo, sfx_ssa015);
if (player->roundconditions.tripwire_hyuu == false
&& player->hyudorotimer > 0)
{
player->roundconditions.tripwire_hyuu = true;
player->roundconditions.checkthisframe = true;
}
}
else if (state == TRIPSTATE_BLOCKED)
{
S_StartSound(player->mo, sfx_kc40);
@ -5823,6 +5838,13 @@ void K_DoSneaker(player_t *player, INT32 type)
{
const fixed_t intendedboost = FRACUNIT/2;
if (player->roundconditions.touched_sneakerpanel == false
&& player->floorboost != 0)
{
player->roundconditions.touched_sneakerpanel = true;
player->roundconditions.checkthisframe = true;
}
if (player->floorboost == 0 || player->floorboost == 3)
{
const sfxenum_t normalsfx = sfx_cdfm01;
@ -7067,7 +7089,7 @@ static void K_UpdateEngineSounds(player_t *player)
const UINT16 buttons = K_GetKartButtons(player);
INT32 class, s, w; // engine class number
INT32 class; // engine class number
UINT8 volume = 255;
fixed_t volumedampen = FRACUNIT;
@ -7082,17 +7104,7 @@ static void K_UpdateEngineSounds(player_t *player)
return;
}
s = (player->kartspeed - 1) / 3;
w = (player->kartweight - 1) / 3;
#define LOCKSTAT(stat) \
if (stat < 0) { stat = 0; } \
if (stat > 2) { stat = 2; }
LOCKSTAT(s);
LOCKSTAT(w);
#undef LOCKSTAT
class = s + (3*w);
class = R_GetEngineClass(player->kartspeed, player->kartweight, 0); // there are no unique sounds for ENGINECLASS_J
#if 0
if ((leveltime % 8) != ((player-players) % 8)) // Per-player offset, to make engines sound distinct!
@ -11603,7 +11615,7 @@ tic_t K_TimeLimitForGametype(void)
// Grand Prix
if (!K_CanChangeRules(true))
{
if (battlecapsules)
if (battleprisons)
{
return 20*TICRATE;
}
@ -11617,7 +11629,7 @@ tic_t K_TimeLimitForGametype(void)
}
// No time limit for Break the Capsules FREE PLAY
if (battlecapsules)
if (battleprisons)
{
return 0;
}
@ -11668,7 +11680,7 @@ UINT32 K_PointLimitForGametype(void)
boolean K_Cooperative(void)
{
if (battlecapsules)
if (battleprisons)
{
return true;
}

View file

@ -698,6 +698,7 @@ boolean M_CharacterSelectHandler(INT32 choice);
void M_CharacterSelectTick(void);
boolean M_CharacterSelectQuit(void);
void M_SetupPlayMenu(INT32 choice);
void M_SetupGametypeMenu(INT32 choice);
void M_SetupRaceMenu(INT32 choice);
@ -727,13 +728,16 @@ extern struct levellist_s {
SINT8 cursor;
UINT16 y;
UINT16 dest;
INT16 choosemap;
UINT16 choosemap;
UINT16 mapcount;
UINT8 newgametype;
UINT8 guessgt;
levelsearch_t levelsearch;
boolean netgame; // Start the game in an actual server
} levellist;
extern cupheader_t dummy_lostandfound;
boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch);
UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch);
UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch);
@ -776,6 +780,7 @@ extern struct mpmenu_s {
// See M_OptSelectTick, it'll make more sense there. Sorry if this is a bit of a mess!
UINT8 room;
boolean roomforced;
tic_t ticker;
UINT8 servernum;
@ -1133,7 +1138,9 @@ void M_DrawAddons(void);
#define CC_UNLOCKED 1
#define CC_TALLY 2
#define CC_ANIM 3
#define CC_MAX 4
#define CC_CHAOANIM 4
#define CC_CHAONOPE 5
#define CC_MAX 6
#define TILEFLIP_MAX 16
@ -1144,7 +1151,6 @@ extern struct timeattackmenu_s {
} timeattackmenu;
// Keep track of some pause menu data for visual goodness.
extern struct challengesmenu_s {
@ -1163,6 +1169,7 @@ extern struct challengesmenu_s {
boolean pending;
boolean requestnew;
boolean chaokeyadd;
boolean requestflip;
@ -1180,6 +1187,7 @@ boolean M_ChallengesInputs(INT32 ch);
extern struct statisticsmenu_s {
INT32 location;
INT32 nummaps;
INT32 numextramedals;
INT32 maxscroll;
UINT16 *maplist;
} statisticsmenu;

View file

@ -791,6 +791,8 @@ void M_DrawKartGamemodeMenu(void)
for (i = 0; i < currentMenu->numitems; i++)
{
INT32 type;
if (currentMenu->menuitems[i].status == IT_DISABLED)
{
continue;
@ -807,9 +809,12 @@ void M_DrawKartGamemodeMenu(void)
}
}
switch (currentMenu->menuitems[i].status & IT_DISPLAY)
type = (currentMenu->menuitems[i].status & IT_DISPLAY);
switch (type)
{
case IT_STRING:
case IT_TRANSTEXT2:
{
UINT8 *colormap = NULL;
@ -823,7 +828,13 @@ void M_DrawKartGamemodeMenu(void)
}
V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, 0, W_CachePatchName("MENUPLTR", PU_CACHE), colormap);
V_DrawGamemodeString(x + 16, y - 3, V_ALLOWLOWERCASE, colormap, currentMenu->menuitems[i].text);
V_DrawGamemodeString(x + 16, y - 3,
(type == IT_TRANSTEXT2
? V_TRANSLUCENT
: 0
)|V_ALLOWLOWERCASE,
colormap,
currentMenu->menuitems[i].text);
}
break;
}
@ -1470,23 +1481,31 @@ static void M_DrawCharSelectPreview(UINT8 num)
if (p->showextra == true)
{
INT32 randomskin = 0;
switch (p->mdepth)
{
case CSSTEP_CHARS: // Character Select grid
V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, va("Speed %u - Weight %u", p->gridx+1, p->gridy+1));
break;
case CSSTEP_ALTS: // Select clone
case CSSTEP_READY:
if (p->clonenum < setup_chargrid[p->gridx][p->gridy].numskins
&& setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum] < numskins)
{
V_DrawThinString(x-3, y+2, V_6WIDTHSPACE,
V_DrawThinString(x-3, y+12, V_6WIDTHSPACE,
skins[setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]].name);
randomskin = (skins[setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]].flags & SF_IRONMAN);
}
else
{
V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, va("BAD CLONENUM %u", p->clonenum));
V_DrawThinString(x-3, y+12, V_6WIDTHSPACE, va("BAD CLONENUM %u", p->clonenum));
}
/* FALLTHRU */
case CSSTEP_CHARS: // Character Select grid
V_DrawThinString(x-3, y+2, V_6WIDTHSPACE, va("Class %c (s %c - w %c)",
('A' + R_GetEngineClass(p->gridx+1, p->gridy+1, randomskin)),
(randomskin
? '?' : ('1'+p->gridx)),
(randomskin
? '?' : ('1'+p->gridy))
));
break;
case CSSTEP_COLORS: // Select color
if (p->color < numskincolors)
@ -2066,7 +2085,11 @@ static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch)
V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE));
if (levelsearch->cup)
if (levelsearch->cup == &dummy_lostandfound)
{
V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, "Lost and Found");
}
else if (levelsearch->cup)
{
boolean unlocked = (M_GetFirstLevelInList(&temp, levelsearch) != NEXTMAP_INVALID);
UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE);
@ -2095,6 +2118,8 @@ static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch)
void M_DrawCupSelect(void)
{
UINT8 i, j, temp = 0;
UINT8 *colormap = NULL;
cupwindata_t *windata = NULL;
levelsearch_t templevelsearch = levellist.levelsearch; // full copy
for (i = 0; i < CUPMENU_COLUMNS; i++)
@ -2105,28 +2130,82 @@ void M_DrawCupSelect(void)
patch_t *patch = NULL;
INT16 x, y;
INT16 icony = 7;
char status = 'A';
char monitor;
INT32 rankx = 0;
if (!cupgrid.builtgrid[id])
break;
templevelsearch.cup = cupgrid.builtgrid[id];
/*if (templevelsearch.cup->emeraldnum == 0)
patch = W_CachePatchName("CUPMON3A", PU_CACHE);
else*/ if (templevelsearch.cup->emeraldnum > 7)
if (cupgrid.grandprix
&& (cv_dummygpdifficulty.value >= 0 && cv_dummygpdifficulty.value < KARTGP_MAX))
{
patch = W_CachePatchName("CUPMON2A", PU_CACHE);
icony = 5;
UINT16 col = SKINCOLOR_NONE;
windata = &templevelsearch.cup->windata[cv_dummygpdifficulty.value];
switch (windata->best_placement)
{
case 0:
break;
case 1:
col = SKINCOLOR_GOLD;
status = 'B';
break;
case 2:
col = SKINCOLOR_SILVER;
status = 'B';
break;
case 3:
col = SKINCOLOR_BRONZE;
status = 'B';
break;
default:
col = SKINCOLOR_BEIGE;
break;
}
if (col != SKINCOLOR_NONE)
colormap = R_GetTranslationColormap(TC_RAINBOW, col, GTC_MENUCACHE);
else
colormap = NULL;
}
if (templevelsearch.cup == &dummy_lostandfound)
{
// No cup? Lost and found!
monitor = '0';
}
else
patch = W_CachePatchName("CUPMON1A", PU_CACHE);
{
if (templevelsearch.cup->monitor < 10)
{
monitor = '0' + templevelsearch.cup->monitor;
if (monitor == '2')
{
icony = 5;
rankx = 2;
}
}
else
{
monitor = 'A' + (templevelsearch.cup->monitor - 10);
}
}
patch = W_CachePatchName(va("CUPMON%c%c", monitor, status), PU_CACHE);
x = 14 + (i*42);
y = 20 + (j*44) - (30*menutransition.tics);
V_DrawScaledPatch(x, y, 0, patch);
V_DrawFixedPatch((x)*FRACUNIT, (y)<<FRACBITS, FRACUNIT, 0, patch, colormap);
if (M_GetFirstLevelInList(&temp, &templevelsearch) == NEXTMAP_INVALID)
if (templevelsearch.cup == &dummy_lostandfound)
; // Only ever placed on the list if valid
else if (M_GetFirstLevelInList(&temp, &templevelsearch) == NEXTMAP_INVALID)
{
patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE);
V_DrawScaledPatch(x + 8, y + icony, 0, st);
@ -2135,6 +2214,39 @@ void M_DrawCupSelect(void)
{
V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName(templevelsearch.cup->icon, PU_CACHE));
V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName("CUPBOX", PU_CACHE));
if (!windata)
;
else if (windata->best_placement != 0)
{
char gradeChar = '?';
switch (windata->best_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; }
}
V_DrawCharacter(x + 5 + rankx, y + icony + 14, gradeChar, false); // rank
if (windata->got_emerald == true)
{
if (templevelsearch.cup->emeraldnum == 0)
V_DrawCharacter(x + 26 - rankx, y + icony + 14, '*', false); // rank
else
{
UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (templevelsearch.cup->emeraldnum-1) % 7;
colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
V_DrawFixedPatch((x + 26 - rankx)*FRACUNIT, (y + icony + 13)*FRACUNIT, FRACUNIT, 0, W_CachePatchName("K_EMERC", PU_CACHE), colormap);
}
}
}
}
}
}
@ -2320,7 +2432,7 @@ void M_DrawLevelSelect(void)
void M_DrawTimeAttack(void)
{
INT16 map = levellist.choosemap;
UINT16 map = levellist.choosemap;
INT16 t = (48*menutransition.tics);
INT16 leftedge = 149+t+16;
INT16 rightedge = 149+t+155;
@ -2366,7 +2478,7 @@ void M_DrawTimeAttack(void)
K_drawKartTimestamp(timerec, 162+t, timeheight+6, 0, 1);
// SPB Attack control hint + menu overlay
if (levellist.newgametype == GT_RACE && levellist.levelsearch.timeattack == true)
if (levellist.newgametype == GT_RACE && levellist.levelsearch.timeattack == true && M_SecretUnlocked(SECRET_SPBATTACK, true))
{
const UINT8 anim_duration = 16;
const UINT8 anim = (timeattackmenu.ticker % (anim_duration * 2)) < anim_duration;
@ -2379,10 +2491,9 @@ void M_DrawTimeAttack(void)
else
V_DrawScaledPatch(buttonx + 35, buttony - 3, V_SNAPTOLEFT, W_CachePatchName("TLB_IB", PU_CACHE));
if (timeattackmenu.ticker > (timeattackmenu.spbflicker + TICRATE/6) || timeattackmenu.ticker % 2)
if ((timeattackmenu.spbflicker == 0 || timeattackmenu.ticker % 2) == (cv_dummyspbattack.value == 1))
{
if (cv_dummyspbattack.value)
V_DrawMappedPatch(buttonx + 7, buttony - 1, 0, W_CachePatchName("K_SPBATK", PU_CACHE), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_MENUCACHE));
V_DrawMappedPatch(buttonx + 7, buttony - 1, 0, W_CachePatchName("K_SPBATK", PU_CACHE), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_MENUCACHE));
}
}
@ -2776,8 +2887,12 @@ void M_DrawMPRoomSelect(void)
// Draw buttons:
V_DrawFixedPatch(160<<FRACBITS, 100<<FRACBITS, FRACUNIT, mpmenu.room ? (5<<V_ALPHASHIFT) : 0, butt1[(mpmenu.room) ? 1 : 0], NULL);
V_DrawFixedPatch(160<<FRACBITS, 100<<FRACBITS, FRACUNIT, (!mpmenu.room) ? (5<<V_ALPHASHIFT) : 0, butt2[(!mpmenu.room) ? 1 : 0], NULL);
if (!mpmenu.roomforced || mpmenu.room == 0)
V_DrawFixedPatch(160<<FRACBITS, 100<<FRACBITS, FRACUNIT, mpmenu.room ? (5<<V_ALPHASHIFT) : 0, butt1[(mpmenu.room) ? 1 : 0], NULL);
if (!mpmenu.roomforced || mpmenu.room == 1)
V_DrawFixedPatch(160<<FRACBITS, 100<<FRACBITS, FRACUNIT, (!mpmenu.room) ? (5<<V_ALPHASHIFT) : 0, butt2[(!mpmenu.room) ? 1 : 0], NULL);
}
// SERVER BROWSER
@ -4731,18 +4846,22 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili
case SECRET_CUP:
categoryid = '4';
break;
//case SECRET_MASTERBOTS:
case SECRET_HARDSPEED:
case SECRET_MASTERMODE:
case SECRET_ENCORE:
categoryid = '5';
break;
case SECRET_ONLINE:
case SECRET_ADDONS:
case SECRET_EGGTV:
case SECRET_ALTTITLE:
case SECRET_SOUNDTEST:
categoryid = '6';
break;
case SECRET_TIMEATTACK:
case SECRET_BREAKTHECAPSULES:
case SECRET_PRISONBREAK:
case SECRET_SPECIALATTACK:
case SECRET_SPBATTACK:
categoryid = '7';
break;
}
@ -4793,16 +4912,25 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili
break;
}
/*case SECRET_MASTERBOTS:
iconid = 4;
break;*/
case SECRET_HARDSPEED:
iconid = 3;
break;
case SECRET_MASTERMODE:
iconid = 4;
break;
case SECRET_ENCORE:
iconid = 5;
break;
case SECRET_ONLINE:
iconid = 10;
break;
case SECRET_ADDONS:
iconid = 12;
break;
case SECRET_EGGTV:
iconid = 11;
break;
case SECRET_ALTTITLE:
iconid = 6;
break;
@ -4813,12 +4941,15 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili
case SECRET_TIMEATTACK:
iconid = 7;
break;
case SECRET_BREAKTHECAPSULES:
case SECRET_PRISONBREAK:
iconid = 8;
break;
case SECRET_SPECIALATTACK:
iconid = 9;
break;
case SECRET_SPBATTACK:
iconid = 0; // TEMPORARY
break;
default:
{
@ -4896,6 +5027,8 @@ drawborder:
}
}
#define challengetransparentstrength 8
static void M_DrawChallengePreview(INT32 x, INT32 y)
{
unlockable_t *ref = NULL;
@ -4942,12 +5075,57 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
{
case SECRET_SKIN:
{
INT32 skin = M_UnlockableSkinNum(ref);
INT32 skin = M_UnlockableSkinNum(ref), i;
// Draw our character!
if (skin != -1)
{
colormap = R_GetTranslationColormap(skin, skins[skin].prefcolor, GTC_MENUCACHE);
M_DrawCharacterSprite(x, y, skin, false, false, 0, colormap);
for (i = 0; i < skin; i++)
{
if (!R_SkinUsable(-1, i, false))
continue;
if (skins[i].kartspeed != skins[skin].kartspeed)
continue;
if (skins[i].kartweight != skins[skin].kartweight)
continue;
colormap = R_GetTranslationColormap(i, skins[i].prefcolor, GTC_MENUCACHE);
break;
}
V_DrawFixedPatch(4*FRACUNIT, (BASEVIDHEIGHT-(4+16))*FRACUNIT,
FRACUNIT,
0, faceprefix[i][FACE_RANK],
colormap);
if (i != skin)
{
V_DrawScaledPatch(4, (11 + BASEVIDHEIGHT-(4+16)), 0, W_CachePatchName("ALTSDOT", PU_CACHE));
}
V_DrawFadeFill(4+16, (BASEVIDHEIGHT-(4+16)), 16, 16, 0, 31, challengetransparentstrength);
V_DrawFill(4+16+5, (BASEVIDHEIGHT-(4+16))+1, 1, 14, 0);
V_DrawFill(4+16+5+5, (BASEVIDHEIGHT-(4+16))+1, 1, 14, 0);
V_DrawFill(4+16+1, (BASEVIDHEIGHT-(4+16))+5, 14, 1, 0);
V_DrawFill(4+16+1, (BASEVIDHEIGHT-(4+16))+5+5, 14, 1, 0);
// The following is a partial duplication of R_GetEngineClass
{
INT32 s = (skins[skin].kartspeed - 1)/3;
INT32 w = (skins[skin].kartweight - 1)/3;
#define LOCKSTAT(stat) \
if (stat < 0) { stat = 0; } \
if (stat > 2) { stat = 2; }
LOCKSTAT(s);
LOCKSTAT(w);
#undef LOCKSTAT
V_DrawFill(4+16 + (s*5), (BASEVIDHEIGHT-(4+16)) + (w*5), 6, 6, 0);
}
}
break;
}
@ -4968,14 +5146,27 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
UINT16 col = K_GetEffectiveFollowerColor(followers[fskin].defaultcolor, cv_playercolor[0].value);
colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
M_DrawFollowerSprite(x - 16, y, fskin, false, 0, colormap, NULL);
if (followers[fskin].category < numfollowercategories)
{
V_DrawFixedPatch(4*FRACUNIT, (BASEVIDHEIGHT-(4+16))*FRACUNIT,
FRACUNIT,
0, W_CachePatchName(followercategories[followers[fskin].category].icon, PU_CACHE),
NULL);
}
}
break;
}
case SECRET_CUP:
{
levelsearch_t templevelsearch;
UINT32 i, id, maxid, offset;
cupheader_t *temp = M_UnlockableCup(ref);
templevelsearch.cup = M_UnlockableCup(ref);
if (!temp)
break;
templevelsearch.cup = temp;
templevelsearch.typeoflevel = G_TOLFlag(GT_RACE)|G_TOLFlag(GT_BATTLE);
templevelsearch.cupmode = true;
templevelsearch.timeattack = false;
@ -4983,17 +5174,87 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
M_DrawCupPreview(146, &templevelsearch);
maxid = id = (temp->id % 14);
offset = (temp->id - id) * 2;
while (temp && maxid < 14)
{
maxid++;
temp = temp->next;
}
V_DrawFadeFill(4, (BASEVIDHEIGHT-(4+16)), 28 + offset, 16, 0, 31, challengetransparentstrength);
for (i = 0; i < offset; i += 4)
{
V_DrawFill(4+1 + i, (BASEVIDHEIGHT-(4+16))+3, 2, 2, 15);
V_DrawFill(4+1 + i, (BASEVIDHEIGHT-(4+16))+8+3, 2, 2, 15);
}
for (i = 0; i < 7; i++)
{
if (templevelsearch.cup && id == i)
{
V_DrawFill(offset + 4 + (i*4), (BASEVIDHEIGHT-(4+16)), 4, 8, 0);
}
else if (i < maxid)
{
V_DrawFill(offset + 4+1 + (i*4), (BASEVIDHEIGHT-(4+16))+3, 2, 2, 0);
}
if (templevelsearch.cup && (templevelsearch.cup->id % 14) == i+7)
{
V_DrawFill(offset + 4 + (i*4), (BASEVIDHEIGHT-(4+16))+8, 4, 8, 0);
}
else if (i+7 < maxid)
{
V_DrawFill(offset + 4+1 + (i*4), (BASEVIDHEIGHT-(4+16))+8+3, 2, 2, 0);
}
}
break;
}
case SECRET_MAP:
{
const char *gtname = "INVALID HEADER";
UINT16 mapnum = M_UnlockableMapNum(ref);
K_DrawMapThumbnail(
(x-30)<<FRACBITS, (146+2)<<FRACBITS,
60<<FRACBITS,
(x-50)<<FRACBITS, (146+2)<<FRACBITS,
80<<FRACBITS,
0,
mapnum,
NULL);
if (mapnum < nummapheaders && mapheaderinfo[mapnum] != NULL)
{
INT32 guessgt = G_GuessGametypeByTOL(mapheaderinfo[mapnum]->typeoflevel);
if (guessgt == -1)
{
// No Time Attack support, so specify...
gtname = "Match Race/Online";
}
else
{
if (guessgt == GT_VERSUS)
{
// Fudge since there's no Versus-specific menu right now...
guessgt = GT_SPECIAL;
}
if (guessgt == GT_SPECIAL && !M_SecretUnlocked(SECRET_SPECIALATTACK, true))
{
gtname = "???";
}
else
{
gtname = gametypes[guessgt]->name;
}
}
}
V_DrawThinString(1, BASEVIDHEIGHT-(9+3), V_ALLOWLOWERCASE|V_6WIDTHSPACE, gtname);
break;
}
case SECRET_ENCORE:
@ -5016,7 +5277,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
specialmap = tamapcache;
break;
}
case SECRET_BREAKTHECAPSULES:
case SECRET_PRISONBREAK:
{
static UINT16 btcmapcache = NEXTMAP_INVALID;
if (btcmapcache > nummapheaders)
@ -5036,6 +5297,16 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
specialmap = sscmapcache;
break;
}
case SECRET_SPBATTACK:
{
static UINT16 spbmapcache = NEXTMAP_INVALID;
if (spbmapcache > nummapheaders)
{
spbmapcache = G_RandMap(G_TOLFlag(GT_RACE), -1, 2, 0, false, NULL);
}
specialmap = spbmapcache;
break;
}
case SECRET_HARDSPEED:
{
static UINT16 hardmapcache = NEXTMAP_INVALID;
@ -5046,12 +5317,31 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
specialmap = hardmapcache;
break;
}
case SECRET_MASTERMODE:
{
static UINT16 mastermapcache = NEXTMAP_INVALID;
if (mastermapcache > nummapheaders)
{
mastermapcache = G_RandMap(G_TOLFlag(GT_RACE), -1, 2, 0, false, NULL);
}
specialmap = mastermapcache;
break;
}
case SECRET_ONLINE:
{
V_DrawFixedPatch(-3*FRACUNIT, (y-40)*FRACUNIT,
FRACUNIT,
0, W_CachePatchName("EGGASTLA", PU_CACHE),
NULL);
break;
}
case SECRET_ALTTITLE:
{
x = 8;
y = BASEVIDHEIGHT-16;
V_DrawGamemodeString(x, y - 32, V_ALLOWLOWERCASE, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_MENUCACHE), cv_alttitle.string);
V_DrawThinString(x, y, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Press (A)");
break;
}
default:
{
@ -5079,6 +5369,13 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
V_DrawFixedPatch((x+40)<<FRACBITS, ((y+25)<<FRACBITS) - (rubyheight<<1), FRACUNIT, 0, W_CachePatchName("RUBYICON", PU_CACHE), NULL);
rubyfloattime += FixedMul(ANGLE_MAX/NEWTICRATE, renderdeltatics);
}
else if (ref->type == SECRET_SPBATTACK)
{
V_DrawFixedPatch((x+40-25)<<FRACBITS, ((y+25-25)<<FRACBITS),
FRACUNIT, 0,
W_CachePatchName(K_GetItemPatch(KITEM_SPB, false), PU_CACHE),
NULL);
}
else if (ref->type == SECRET_HARDSPEED)
{
V_DrawFixedPatch((x+40-25)<<FRACBITS, ((y+25-25)<<FRACBITS),
@ -5086,6 +5383,13 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
W_CachePatchName(K_GetItemPatch(KITEM_ROCKETSNEAKER, false), PU_CACHE),
NULL);
}
else if (ref->type == SECRET_MASTERMODE)
{
V_DrawFixedPatch((x+40-25)<<FRACBITS, ((y+25-25)<<FRACBITS),
FRACUNIT, 0,
W_CachePatchName(K_GetItemPatch(KITEM_JAWZ, false), PU_CACHE),
NULL);
}
else
{
colormap = R_GetTranslationColormap(TC_DEFAULT, cv_playercolor[0].value, GTC_MENUCACHE);
@ -5096,8 +5400,8 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
}
}
#define challengetransparentstrength 8
#define challengesgridstep 22
#define challengekeybarwidth 50
void M_DrawChallenges(void)
{
@ -5221,6 +5525,34 @@ void M_DrawChallenges(void)
challengedesc:
// Chao Keys
{
patch_t *key = W_CachePatchName("UN_CHA00", PU_CACHE);
INT32 offs = challengesmenu.unlockcount[CC_CHAONOPE];
if (offs & 1)
offs = -offs;
offs /= 2;
if (gamedata->chaokeys > 9)
{
offs -= 6;
if (gamedata->chaokeys > 99)
offs -= 2; // as far as we can go
}
V_DrawFixedPatch((8+offs)*FRACUNIT, 5*FRACUNIT, FRACUNIT, 0, key, NULL);
V_DrawKartString((27+offs), 9-challengesmenu.unlockcount[CC_CHAOANIM], 0, va("%u", gamedata->chaokeys));
offs = challengekeybarwidth;
if (gamedata->chaokeys < GDMAX_CHAOKEYS)
offs = ((gamedata->pendingkeyroundoffset * challengekeybarwidth)/GDCONVERT_ROUNDSTOKEY);
if (offs > 0)
V_DrawFill(1, 25, offs, 2, 0);
if (offs < challengekeybarwidth)
V_DrawFadeFill(1+offs, 25, challengekeybarwidth-offs, 2, 0, 31, challengetransparentstrength);
}
// Tally
{
str = va("%d/%d",
@ -5275,6 +5607,7 @@ challengedesc:
#undef challengetransparentstrength
#undef challengesgridstep
#undef challengekeybarwidth
// Statistics menu
@ -5302,6 +5635,17 @@ static void M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y)
curtype = 2;
break;
}
case ET_MAP:
{
if (((emblem->flags & ME_ENCORE) && !M_SecretUnlocked(SECRET_ENCORE, true))
|| ((emblem->flags & ME_SPBATTACK) && !M_SecretUnlocked(SECRET_SPBATTACK, true)))
{
emblem = M_GetLevelEmblems(-1);
continue;
}
curtype = 0;
break;
}
default:
curtype = 0;
break;
@ -5334,27 +5678,53 @@ static void M_DrawStatsMaps(void)
V_DrawCharacter(10, y-(skullAnimCounter/5),
'\x1A' | highlightflags, false); // up arrow
while (statisticsmenu.maplist[++i] != NEXTMAP_INVALID)
while ((mnum = statisticsmenu.maplist[++i]) != NEXTMAP_INVALID)
{
if (location)
{
--location;
continue;
}
else if (dotopname)
if (dotopname || mnum >= nummapheaders)
{
V_DrawThinString(20, y, V_6WIDTHSPACE|highlightflags, "LEVEL NAME");
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, y, V_6WIDTHSPACE|highlightflags, "MEDALS");
if (mnum >= nummapheaders)
{
mnum = statisticsmenu.maplist[1+i];
if (mnum >= nummapheaders)
mnum = statisticsmenu.maplist[i-1];
}
if (mnum < nummapheaders)
{
const char *str;
if (mapheaderinfo[mnum]->cup)
str = va("%s CUP", mapheaderinfo[mnum]->cup->name);
else
str = "LOST AND FOUND";
V_DrawThinString(20, y, V_6WIDTHSPACE|highlightflags, str);
}
if (dotopname)
{
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, y, V_6WIDTHSPACE|highlightflags, "MEDALS");
dotopname = false;
}
y += STATSSTEP;
dotopname = false;
if (y >= BASEVIDHEIGHT-STATSSTEP)
goto bottomarrow;
continue;
}
mnum = statisticsmenu.maplist[i]+1;
M_DrawMapMedals(mnum, 291, y);
M_DrawMapMedals(mnum+1, 291, y);
{
char *title = G_BuildMapTitle(mnum);
V_DrawThinString(20, y, V_6WIDTHSPACE, title);
char *title = G_BuildMapTitle(mnum+1);
V_DrawThinString(24, y, V_6WIDTHSPACE, title);
Z_Free(title);
}
@ -5363,15 +5733,12 @@ static void M_DrawStatsMaps(void)
if (y >= BASEVIDHEIGHT-STATSSTEP)
goto bottomarrow;
}
if (dotopname && !location)
{
V_DrawString(20, y, V_6WIDTHSPACE|highlightflags, "LEVEL NAME");
V_DrawString(256, y, V_6WIDTHSPACE|highlightflags, "MEDALS");
y += STATSSTEP;
}
else if (location)
if (location)
--location;
if (statisticsmenu.numextramedals == 0)
goto bottomarrow;
// Extra Emblem headers
for (i = 0; i < 2; ++i)
{
@ -5410,7 +5777,6 @@ static void M_DrawStatsMaps(void)
continue;
}
if (i >= 0)
{
if (gamedata->unlocked[i])
{
@ -5425,7 +5791,7 @@ static void M_DrawStatsMaps(void)
V_DrawSmallScaledPatch(291, y+1, V_6WIDTHSPACE, W_CachePatchName("NEEDIT", PU_CACHE));
}
V_DrawThinString(20, y, V_6WIDTHSPACE, va("%s", unlockables[i].name));
V_DrawThinString(24, y, V_6WIDTHSPACE, va("%s", unlockables[i].name));
}
y += STATSSTEP;
@ -5441,7 +5807,7 @@ bottomarrow:
void M_DrawStatistics(void)
{
char beststr[40];
char beststr[256];
tic_t besttime = 0;
@ -5453,14 +5819,71 @@ void M_DrawStatistics(void)
V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL);
}
beststr[0] = 0;
V_DrawThinString(20, 22, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Total Play Time:");
V_DrawCenteredThinString(BASEVIDWIDTH/2, 32, V_6WIDTHSPACE,
va("%i hours, %i minutes, %i seconds",
G_TicsToHours(gamedata->totalplaytime),
G_TicsToMinutes(gamedata->totalplaytime, false),
G_TicsToSeconds(gamedata->totalplaytime)));
V_DrawThinString(20, 42, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Total Matches:");
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 42, V_6WIDTHSPACE, va("%i played", gamedata->matchesplayed));
besttime = G_TicsToHours(gamedata->totalplaytime);
if (besttime)
{
if (besttime >= 24)
{
strcat(beststr, va("%u day%s, ", besttime/24, (besttime < 48 ? "" : "s")));
besttime %= 24;
}
strcat(beststr, va("%u hour%s, ", besttime, (besttime == 1 ? "" : "s")));
}
besttime = G_TicsToMinutes(gamedata->totalplaytime, false);
if (besttime)
{
strcat(beststr, va("%u minute%s, ", besttime, (besttime == 1 ? "" : "s")));
}
besttime = G_TicsToSeconds(gamedata->totalplaytime);
strcat(beststr, va("%i second%s", besttime, (besttime == 1 ? "" : "s")));
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 22, V_6WIDTHSPACE, beststr);
beststr[0] = 0;
V_DrawThinString(20, 32, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Total Rings:");
if (gamedata->totalrings > GDMAX_RINGS)
{
sprintf(beststr, "%c999,999,999+", '\x82');
}
else if (gamedata->totalrings >= 1000000)
{
sprintf(beststr, "%u,%03u,%03u", (gamedata->totalrings/1000000), (gamedata->totalrings/1000)%1000, (gamedata->totalrings%1000));
}
else if (gamedata->totalrings >= 1000)
{
sprintf(beststr, "%u,%03u", (gamedata->totalrings/1000), (gamedata->totalrings%1000));
}
else
{
sprintf(beststr, "%u", gamedata->totalrings);
}
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 32, V_6WIDTHSPACE, va("%s collected", beststr));
beststr[0] = 0;
V_DrawThinString(20, 42, V_6WIDTHSPACE|V_ALLOWLOWERCASE|highlightflags, "Total Rounds:");
strcat(beststr, va("%u Race", gamedata->roundsplayed[GDGT_RACE]));
if (gamedata->roundsplayed[GDGT_PRISONS] > 0)
{
strcat(beststr, va(", %u Prisons", gamedata->roundsplayed[GDGT_PRISONS]));
}
strcat(beststr, va(", %u Battle", gamedata->roundsplayed[GDGT_BATTLE]));
if (gamedata->roundsplayed[GDGT_SPECIAL] > 0)
{
strcat(beststr, va(", %u Special", gamedata->roundsplayed[GDGT_SPECIAL]));
}
if (gamedata->roundsplayed[GDGT_CUSTOM] > 0)
{
strcat(beststr, va(", %u Custom", gamedata->roundsplayed[GDGT_CUSTOM]));
}
V_DrawRightAlignedThinString(BASEVIDWIDTH-20, 42, V_6WIDTHSPACE, beststr);
if (!statisticsmenu.maplist)
{
@ -5468,6 +5891,8 @@ void M_DrawStatistics(void)
return;
}
besttime = 0;
for (i = 0; i < nummapheaders; i++)
{
if (!mapheaderinfo[i] || (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)))

View file

@ -15,6 +15,7 @@
#include "v_video.h"
#include "f_finale.h"
#include "m_misc.h"
#include "m_cond.h"
#ifdef PC_DOS
#include <stdio.h> // for snprintf
@ -369,9 +370,17 @@ boolean M_Responder(event_t *ev)
void M_PlayMenuJam(void)
{
menu_t *refMenu = (menuactive ? currentMenu : restoreMenu);
static boolean loserclubpermitted = false;
boolean loserclub = (loserclubpermitted && (gamedata->musicflags & GDMUSIC_LOSERCLUB));
if (challengesmenu.pending)
{
S_StopMusic();
cursongcredit.def = NULL;
loserclubpermitted = true;
return;
}
if (Playing())
return;
@ -382,15 +391,27 @@ void M_PlayMenuJam(void)
{
S_StopMusic();
cursongcredit.def = NULL;
return;
}
else
else if (!loserclub)
{
if (NotCurrentlyPlaying(refMenu->music))
{
S_ChangeMusicInternal(refMenu->music, true);
S_ShowMusicCredit();
}
return;
}
}
if (loserclub)
{
if (refMenu != NULL && NotCurrentlyPlaying("LOSERC"))
{
S_ChangeMusicInternal("LOSERC", true);
S_ShowMusicCredit();
}
return;
}
@ -478,6 +499,7 @@ menu_t *M_SpecificMenuRestore(menu_t *torestore)
}
// One last catch.
M_SetupPlayMenu(-1);
PLAY_CharSelectDef.prevMenu = &MainDef;
return torestore;
@ -945,7 +967,7 @@ static void M_HandleMenuInput(void)
{
if (((currentMenu->menuitems[itemOn].status & IT_CALLTYPE) & IT_CALL_NOTMODIFIED) && majormods)
{
M_StartMessage(M_GetText("This cannot be done with complex addons\nor in a cheated game.\n\nPress (B)\n"), NULL, MM_NOTHING);
M_StartMessage(M_GetText("This cannot be done with complex addons\nor in a cheated game.\n\nPress (B)"), NULL, MM_NOTHING);
return;
}
}

View file

@ -13,7 +13,6 @@
#include "k_podium.h"
#include "doomdef.h"
#include "doomstat.h"
#include "d_main.h"
#include "d_netcmd.h"
#include "f_finale.h"
@ -69,6 +68,31 @@ boolean K_PodiumSequence(void)
return (gamestate == GS_CEREMONY);
}
/*--------------------------------------------------
boolean K_PodiumRanking(void)
See header file for description.
--------------------------------------------------*/
boolean K_PodiumRanking(void)
{
return (gamestate == GS_CEREMONY && podiumData.ranking == true);
}
/*--------------------------------------------------
boolean K_PodiumGrade(void)
See header file for description.
--------------------------------------------------*/
gp_rank_e K_PodiumGrade(void)
{
if (K_PodiumSequence() == false)
{
return 0;
}
return podiumData.grade;
}
/*--------------------------------------------------
UINT8 K_GetPodiumPosition(player_t *player)
@ -264,6 +288,10 @@ void K_FinishCeremony(void)
}
podiumData.ranking = true;
// Play the noise now
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData();
}
/*--------------------------------------------------
@ -273,6 +301,8 @@ void K_FinishCeremony(void)
--------------------------------------------------*/
void K_ResetCeremony(void)
{
UINT8 i;
memset(&podiumData, 0, sizeof(struct podiumData_s));
if (K_PodiumSequence() == false)
@ -280,8 +310,36 @@ void K_ResetCeremony(void)
return;
}
// Establish rank and grade for this play session.
podiumData.rank = grandprixinfo.rank;
podiumData.grade = K_CalculateGPGrade(&podiumData.rank);
if (!grandprixinfo.cup)
{
return;
}
// 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
{
grandprixinfo.cup->windata[i].best_placement = podiumData.rank.position;
// 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 (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();
}
/*--------------------------------------------------
@ -458,7 +516,7 @@ void K_CeremonyDrawer(void)
case 5:
{
V_DrawString(x, y, V_ALLOWLOWERCASE,
va("CAPSULES: %d / %d", podiumData.rank.capsules, podiumData.rank.totalCapsules)
va("PRISONS: %d / %d", podiumData.rank.prisons, podiumData.rank.totalPrisons)
);
break;
}

View file

@ -14,6 +14,7 @@
#define __K_PODIUM__
#include "doomtype.h"
#include "doomstat.h" // gp_rank_e
#include "d_event.h"
#include "p_mobj.h"
@ -37,6 +38,37 @@ extern "C" {
boolean K_PodiumSequence(void);
/*--------------------------------------------------
boolean K_PodiumRanking(void);
Returns whenver or not we are in the podium
final state.
Input Arguments:-
N/A
Return:-
true if we're in GS_CEREMONY, otherwise false.
--------------------------------------------------*/
boolean K_PodiumRanking(void);
/*--------------------------------------------------
boolean K_PodiumGrade(void)
Returns the podium grade.
Input Arguments:-
N/A
Return:-
gp_rank_e constant if we're in GS_CEREMONY, otherwise 0.
--------------------------------------------------*/
gp_rank_e K_PodiumGrade(void);
/*--------------------------------------------------
UINT8 K_GetPodiumPosition(player_t *player);

View file

@ -397,6 +397,7 @@ void K_CashInPowerLevels(void)
{
SINT8 powerType = K_UsingPowerLevels();
UINT8 i;
boolean gamedataupdate;
//CONS_Printf("\n========\n");
//CONS_Printf("Cashing in power level changes...\n");
@ -417,14 +418,19 @@ void K_CashInPowerLevels(void)
{
pr->powerlevels[powerType] = clientpowerlevels[i][powerType];
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
gamedataupdate = true;
}
}
clientPowerAdd[i] = 0;
}
if (gamedataupdate)
{
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData();
}
//CONS_Printf("========\n");
}
@ -637,7 +643,7 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss)
{
pr->powerlevels[powerType] = yourPower + inc;
M_UpdateUnlockablesAndExtraEmblems(true);
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData();
}
}

View file

@ -304,7 +304,12 @@ void K_InitGrandPrixRank(gpRank_t *rankData)
const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[i];
if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] != NULL)
{
laps += mapheaderinfo[cupLevelNum]->numlaps;
if (!cv_gptest.value)
{
laps += mapheaderinfo[cupLevelNum]->numlaps;
continue;
}
laps++;
}
}
@ -335,7 +340,7 @@ void K_InitGrandPrixRank(gpRank_t *rankData)
continue;
}
rankData->totalCapsules += RankCapsules_CountFromMap(virt);
rankData->totalPrisons += RankCapsules_CountFromMap(virt);
vres_Free(virt);
}
}
@ -360,9 +365,9 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData)
const INT32 positionWeight = 150;
const INT32 pointsWeight = 100;
const INT32 lapsWeight = 100;
const INT32 capsulesWeight = 100;
const INT32 prisonsWeight = 100;
const INT32 ringsWeight = 50;
const INT32 total = positionWeight + pointsWeight + lapsWeight + capsulesWeight + ringsWeight;
const INT32 total = positionWeight + pointsWeight + lapsWeight + prisonsWeight + ringsWeight;
const INT32 continuesPenalty = 20;
INT32 ours = 0;
@ -385,9 +390,9 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData)
ours += (rankData->laps * lapsWeight) / rankData->totalLaps;
}
if (rankData->totalCapsules > 0)
if (rankData->totalPrisons > 0)
{
ours += (rankData->capsules * capsulesWeight) / rankData->totalCapsules;
ours += (rankData->prisons * prisonsWeight) / rankData->totalPrisons;
}
if (rankData->totalRings > 0)

View file

@ -35,8 +35,8 @@ struct gpRank_t
UINT32 continuesUsed;
UINT32 capsules;
UINT32 totalCapsules;
UINT32 prisons;
UINT32 totalPrisons;
UINT32 rings;
UINT32 totalRings;
@ -44,15 +44,7 @@ struct gpRank_t
boolean specialWon;
};
typedef enum
{
GRADE_E,
GRADE_D,
GRADE_C,
GRADE_B,
GRADE_A,
GRADE_S
} gp_rank_e;
// gp_rank_e was once defined here, but moved to doomstat.h to prevent circular dependency
// 3rd place is neutral, anything below is a penalty
#define RANK_NEUTRAL_POSITION (3)

View file

@ -1230,7 +1230,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
kartitems_t *presetlist = K_KartItemReelTimeAttack;
// If the objective is not to go fast, it's to cause serious damage.
if (gametyperules & GTR_CAPSULES)
if (gametyperules & GTR_PRISONS)
{
presetlist = K_KartItemReelBreakTheCapsules;
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2022-2023 by Vivian "toaster" Grannell.
// Copyright (C) 2022-2023 by Vivian "toastergrl" Grannell.
// Copyright (C) 2012-2016 by Matthew "Kaito Sinclaire" Walsh.
// Copyright (C) 2012-2020 by Sonic Team Junior.
//
@ -29,19 +29,81 @@ extern "C" {
typedef enum
{
UC_PLAYTIME, // PLAYTIME [tics]
UC_MATCHESPLAYED, // SRB2Kart: MATCHESPLAYED [x played]
UC_ROUNDSPLAYED, // ROUNDSPLAYED [x played]
UC_TOTALRINGS, // TOTALRINGS [x collected]
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_TRIGGER, // TRIGGER [trigger number]
UC_MAPVISITED, // MAPVISITED [map]
UC_MAPBEATEN, // MAPBEATEN [map]
UC_MAPENCORE, // MAPENCORE [map]
UC_MAPSPBATTACK, // MAPSPBATTACK [map]
UC_MAPTIME, // MAPTIME [map] [time to beat, tics]
UC_ALLCHAOS, // ALLCHAOS [minimum difficulty]
UC_ALLSUPER, // ALLSUPER [minimum difficulty]
UC_ALLEMERALDS, // ALLEMERALDS [minimum difficulty]
UC_TOTALMEDALS, // TOTALMEDALS [number of emblems]
UC_EMBLEM, // EMBLEM [emblem number]
UC_UNLOCKABLE, // UNLOCKABLE [unlockable number]
UC_CONDITIONSET, // CONDITIONSET [condition set number]
UC_ADDON, // Ever loaded a custom file?
UC_REPLAY, // Save a replay
UC_CRASH, // Hee ho !
// Just for string building
UC_AND,
UC_COMMA,
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_PRISONBREAK, // PRISON BREAK:
UCRP_PREFIX_SEALEDSTAR, // SEALED STAR:
UCRP_PREFIX_ISMAP, // name of [map]:
UCRP_ISMAP, // gamemap == [map]
UCRP_ISCHARACTER, // character == [skin]
UCRP_ISENGINECLASS, // engine class [class]
UCRP_ISDIFFICULTY, // difficulty >= [difficulty]
UCRP_PODIUMCUP, // cup == [cup] [optional: >= grade OR place]
UCRP_PODIUMEMERALD, // Get to podium sequence with that cup's emerald
UCRP_PODIUMPRIZE, // Get to podium sequence with that cup's bonus (alternate string version of UCRP_PODIUMEMERALD
UCRP_FINISHCOOL, // Finish in good standing
UCRP_FINISHALLPRISONS, // Break all prisons
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
UCRP_TRIGGER, // Map execution trigger [id]
UCRP_FALLOFF, // Fall off (or don't)
UCRP_TOUCHOFFROAD, // Touch offroad (or don't)
UCRP_TOUCHSNEAKERPANEL, // Either touch sneaker panel (or don't)
UCRP_RINGDEBT, // Go into debt (or don't)
UCRP_TRIPWIREHYUU, // Go through tripwire with Hyudoro
UCRP_SPBNEUTER, // Kill an SPB with Lightning
UCRP_LANDMINEDUNK, // huh? you died? that's weird. all i did was try to hug you...
UCRP_HITMIDAIR, // Hit another player mid-air with a kartfielditem
UCRP_WETPLAYER, // Don't touch [fluid]
} conditiontype_t;
// Condition Set information
@ -55,6 +117,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 *stringvar; /// <- Extra z-allocated string for the condition when needed
};
struct conditionset_t
{
@ -120,13 +183,19 @@ typedef enum
// Difficulty restrictions
SECRET_HARDSPEED, // Permit Hard gamespeed
SECRET_MASTERMODE, // Permit Master Mode bots in GP
SECRET_ENCORE, // Permit Encore option
SECRET_LEGACYBOXRUMMAGE, // Permit the Legacy Box for record attack, etc
// Menu restrictions
SECRET_TIMEATTACK, // Permit Time attack
SECRET_BREAKTHECAPSULES, // Permit SP Capsule attack
SECRET_PRISONBREAK, // Permit SP Prison attack
SECRET_SPECIALATTACK, // Permit Special attack (You're blue now!)
SECRET_SPBATTACK, // Permit SPB mode of Time attack
// Option restrictions
SECRET_ONLINE, // Permit netplay (ankle-high barrier to jumping in the deep end)
SECRET_ADDONS, // Permit menu addfile
SECRET_EGGTV, // Permit replay playback menu
SECRET_SOUNDTEST, // Permit Sound Test
SECRET_ALTTITLE, // Permit alternate titlescreen
@ -148,12 +217,35 @@ typedef enum
#endif
#define challengegridloops (gamedata->challengegridwidth >= CHALLENGEGRIDLOOPWIDTH)
#define GDMUSIC_LOSERCLUB 0x01
// This is the largest number of 9s that will fit in UINT32 and UINT16 respectively.
#define GDMAX_RINGS 999999999
#define GDMAX_CHAOKEYS 9999
#ifdef DEVELOP
#define GDCONVERT_ROUNDSTOKEY 20
#else
#define GDCONVERT_ROUNDSTOKEY 50
#endif
typedef enum {
GDGT_RACE,
GDGT_BATTLE,
GDGT_PRISONS,
GDGT_SPECIAL,
GDGT_CUSTOM,
GDGT_MAX
} roundsplayed_t;
// GAMEDATA STRUCTURE
// Everything that would get saved in gamedata.dat
struct gamedata_t
{
// WHENEVER OR NOT WE'RE READY TO SAVE
boolean loaded;
boolean deferredsave;
boolean deferredconditioncheck;
// CONDITION SETS ACHIEVED
boolean achieved[MAXCONDITIONSETS];
@ -174,7 +266,21 @@ struct gamedata_t
// PLAY TIME
UINT32 totalplaytime;
UINT32 matchesplayed;
UINT32 roundsplayed[GDGT_MAX];
UINT32 totalrings;
// Chao Key condition bypass
UINT32 pendingkeyrounds;
UINT8 pendingkeyroundoffset;
UINT8 keyspending;
UINT16 chaokeys;
// SPECIFIC SPECIAL EVENTS
boolean everloadedaddon;
boolean eversavedreplay;
boolean everseenspecial;
boolean evercrashed;
UINT8 musicflags;
};
extern gamedata_t *gamedata;
@ -188,8 +294,6 @@ extern unlockable_t unlockables[MAXUNLOCKABLES];
extern INT32 numemblems;
extern UINT32 unlocktriggers;
void M_NewGameDataStruct(void);
// Challenges menu stuff
@ -213,16 +317,21 @@ 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 *stringvar);
void M_UpdateConditionSetsPending(void);
// Clearing secrets
void M_ClearConditionSet(UINT8 set);
void M_ClearSecrets(void);
void M_ClearStats(void);
// Updating conditions and unlockables
UINT8 M_CheckCondition(condition_t *cn);
boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud);
UINT8 M_GetNextAchievedUnlock(void);
boolean M_CheckCondition(condition_t *cn, player_t *player);
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);
@ -230,7 +339,7 @@ UINT8 M_CompletionEmblems(void);
boolean M_CheckNetUnlockByID(UINT8 unlockid);
boolean M_SecretUnlocked(INT32 type, boolean local);
boolean M_CupLocked(cupheader_t *cup);
boolean M_MapLocked(INT32 mapnum);
boolean M_MapLocked(UINT16 mapnum);
INT32 M_CountMedals(boolean all, boolean extraonly);
// Emblem shit

View file

@ -2,22 +2,26 @@
/// \brief Extras Menu
#include "../k_menu.h"
#include "../m_cond.h"
#include "../s_sound.h"
menuitem_t EXTRAS_Main[] =
{
// The following has NULL strings for text and tooltip.
// These are populated in M_InitExtras depending on unlock state.
// (This is legal - they're (const char)*'s, not const (char*)'s.
{IT_STRING | IT_CALL, "Addons", "Add files to customize your experience.",
{IT_STRING | IT_CALL, NULL, NULL,
NULL, {.routine = M_Addons}, 0, 0},
{IT_STRING | IT_CALL, "Challenges", "View the requirements for some of the secret content you can unlock!",
NULL, {.routine = M_Challenges}, 0, 0},
{IT_STRING | IT_CALL, "Replay Hut", "Play the replays you've saved throughout your many races & battles!",
NULL, {.routine = M_ReplayHut}, 0, 0},
{IT_STRING | IT_CALL, "Statistics", "Look back on some of your greatest achievements such as your playtime and wins!",
NULL, {.routine = M_Statistics}, 0, 0},
{IT_STRING | IT_CALL, NULL, NULL,
NULL, {.routine = M_ReplayHut}, 0, 0},
};
// the extras menu essentially reuses the options menu stuff
@ -54,6 +58,40 @@ void M_InitExtras(INT32 choice)
extrasmenu.textx = 0;
extrasmenu.texty = 0;
// Addons
if (M_SecretUnlocked(SECRET_ADDONS, true))
{
EXTRAS_Main[0].status = IT_STRING | IT_CALL;
EXTRAS_Main[0].text = "Addons";
EXTRAS_Main[0].tooltip = "Add files to customize your experience.";
}
else
{
EXTRAS_Main[0].status = IT_STRING | IT_TRANSTEXT;
EXTRAS_Main[0].text = EXTRAS_Main[0].tooltip = "???";
if (EXTRAS_MainDef.lastOn == 0)
{
EXTRAS_MainDef.lastOn = 1;
}
}
// Egg TV
if (M_SecretUnlocked(SECRET_EGGTV, true))
{
EXTRAS_Main[3].status = IT_STRING | IT_CALL;
EXTRAS_Main[3].text = "Egg TV";
EXTRAS_Main[3].tooltip = "Watch the replays you've saved throughout your many races & battles!";
}
else
{
EXTRAS_Main[3].status = IT_STRING | IT_TRANSTEXT;
EXTRAS_Main[3].text = EXTRAS_Main[3].tooltip = "???";
if (EXTRAS_MainDef.lastOn == 3)
{
EXTRAS_MainDef.lastOn = 2;
}
}
M_SetupNextMenu(&EXTRAS_MainDef, false);
}

View file

@ -341,7 +341,7 @@ void M_HandleAddons(INT32 choice)
//MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
if (currentMenu->prevMenu)
M_SetupNextMenu(currentMenu->prevMenu, false);
M_SetupNextMenu(M_InterruptMenuWithChallenges(currentMenu->prevMenu), false);
else
M_ClearMenus(true);

View file

@ -49,17 +49,59 @@ menu_t MISC_StatisticsDef = {
struct challengesmenu_s challengesmenu;
static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh)
static void M_ChallengesAutoFocus(UINT16 unlockid, boolean fresh)
{
UINT8 i;
SINT8 work;
if (unlockid >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0
&& (gamedata->chaokeys < GDMAX_CHAOKEYS))
challengesmenu.chaokeyadd = true;
if (fresh && unlockid >= MAXUNLOCKABLES)
{
UINT8 selection[MAXUNLOCKABLES];
UINT8 numunlocks = 0;
// Get a random available unlockable.
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (!unlockables[i].conditionset)
{
continue;
}
if (!gamedata->unlocked[i])
{
continue;
}
selection[numunlocks++] = i;
}
if (!numunlocks)
{
// ...OK, get a random unlockable.
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (!unlockables[i].conditionset)
{
continue;
}
selection[numunlocks++] = i;
}
}
unlockid = selection[M_RandomKey(numunlocks)];
}
if (unlockid >= MAXUNLOCKABLES)
return;
challengesmenu.currentunlock = unlockid;
challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock);
challengesmenu.unlockanim = 0;
challengesmenu.unlockanim = (challengesmenu.pending && !challengesmenu.chaokeyadd ? 0 : MAXUNLOCKTIME);
if (gamedata->challengegrid == NULL || challengesmenu.extradata == NULL)
return;
@ -161,12 +203,16 @@ static void M_ChallengesAutoFocus(UINT8 unlockid, boolean fresh)
menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
{
UINT8 i;
UINT16 newunlock = M_GetNextAchievedUnlock();
UINT16 i, newunlock;
M_UpdateUnlockablesAndExtraEmblems(false);
if (Playing())
return desiredmenu;
if ((challengesmenu.pending = (newunlock < MAXUNLOCKABLES)))
M_UpdateUnlockablesAndExtraEmblems(false, true);
newunlock = M_GetNextAchievedUnlock();
if ((challengesmenu.pending = (newunlock != MAXUNLOCKABLES)))
{
S_StopMusic();
MISC_ChallengesDef.prevMenu = desiredmenu;
@ -177,6 +223,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
challengesmenu.ticker = 0;
challengesmenu.requestflip = false;
challengesmenu.requestnew = false;
challengesmenu.chaokeyadd = false;
challengesmenu.currentunlock = MAXUNLOCKABLES;
challengesmenu.unlockcondition = NULL;
@ -210,6 +257,9 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
if (challengesmenu.pending)
M_ChallengesAutoFocus(newunlock, true);
else if (newunlock >= MAXUNLOCKABLES && gamedata->pendingkeyrounds > 0
&& (gamedata->chaokeys < GDMAX_CHAOKEYS))
challengesmenu.chaokeyadd = true;
return &MISC_ChallengesDef;
}
@ -219,7 +269,6 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
void M_Challenges(INT32 choice)
{
UINT8 i;
(void)choice;
M_InterruptMenuWithChallenges(NULL);
@ -227,40 +276,7 @@ void M_Challenges(INT32 choice)
if (gamedata->challengegrid != NULL && !challengesmenu.pending)
{
UINT8 selection[MAXUNLOCKABLES];
UINT8 numunlocks = 0;
// Get a random available unlockable.
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (!unlockables[i].conditionset)
{
continue;
}
if (!gamedata->unlocked[i])
{
continue;
}
selection[numunlocks++] = i;
}
if (!numunlocks)
{
// ...OK, get a random unlockable.
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (!unlockables[i].conditionset)
{
continue;
}
selection[numunlocks++] = i;
}
}
M_ChallengesAutoFocus(selection[M_RandomKey(numunlocks)], true);
M_ChallengesAutoFocus(UINT16_MAX, true);
}
M_SetupNextMenu(&MISC_ChallengesDef, false);
@ -270,7 +286,7 @@ void M_ChallengesTick(void)
{
const UINT8 pid = 0;
UINT16 i;
UINT8 newunlock = MAXUNLOCKABLES;
UINT16 newunlock = MAXUNLOCKABLES;
// Ticking
challengesmenu.ticker++;
@ -280,8 +296,11 @@ void M_ChallengesTick(void)
if (setup_explosions[i].tics > 0)
setup_explosions[i].tics--;
}
if (challengesmenu.unlockcount[CC_ANIM] > 0)
challengesmenu.unlockcount[CC_ANIM]--;
for (i = CC_ANIM; i < CC_MAX; i++)
{
if (challengesmenu.unlockcount[i] > 0)
challengesmenu.unlockcount[i]--;
}
M_CupSelectTick();
// Update tile flip state.
@ -307,109 +326,158 @@ void M_ChallengesTick(void)
}
}
if (challengesmenu.pending)
if (challengesmenu.pending && challengesmenu.fade < 5)
{
// Pending mode.
// Fade increase.
challengesmenu.fade++;
}
else if (challengesmenu.chaokeyadd == true)
{
if (challengesmenu.ticker <= 5)
; // recreate the slight delay the unlock fades provide
else if (gamedata->pendingkeyrounds == 0)
{
gamedata->keyspending = 0;
gamedata->pendingkeyroundoffset %= GDCONVERT_ROUNDSTOKEY;
if (challengesmenu.requestnew)
{
// The menu apparatus is requesting a new unlock.
challengesmenu.requestnew = false;
if ((newunlock = M_GetNextAchievedUnlock()) < MAXUNLOCKABLES)
{
// We got one!
M_ChallengesAutoFocus(newunlock, false);
}
else
{
// All done! Let's save the unlocks we've busted open.
challengesmenu.pending = false;
G_SaveGameData();
}
challengesmenu.chaokeyadd = false;
challengesmenu.requestnew = true;
}
else if (challengesmenu.fade < 5)
else if (gamedata->chaokeys >= GDMAX_CHAOKEYS)
{
// Fade increase.
challengesmenu.fade++;
// The above condition will run on the next tic because of this set
gamedata->pendingkeyrounds = 0;
gamedata->pendingkeyroundoffset = 0;
}
else
{
// Unlock sequence.
tic_t nexttime = M_MenuExtraHeld(pid) ? (UNLOCKTIME*2) : MAXUNLOCKTIME;
UINT32 keyexchange = gamedata->keyspending;
if (++challengesmenu.unlockanim >= nexttime)
if (keyexchange > gamedata->pendingkeyrounds)
{
challengesmenu.requestnew = true;
keyexchange = 1;
}
else if (keyexchange >= GDCONVERT_ROUNDSTOKEY/2)
{
keyexchange = GDCONVERT_ROUNDSTOKEY/2;
}
if (challengesmenu.currentunlock < MAXUNLOCKABLES
&& challengesmenu.unlockanim == UNLOCKTIME)
keyexchange |= 1; // guarantee an odd delta for the sake of the sound
gamedata->pendingkeyrounds -= keyexchange;
gamedata->pendingkeyroundoffset += keyexchange;
if (!(gamedata->pendingkeyrounds & 1))
{
// Unlock animation... also tied directly to the actual unlock!
gamedata->unlocked[challengesmenu.currentunlock] = true;
M_UpdateUnlockablesAndExtraEmblems(true);
S_StartSound(NULL, sfx_ptally);
}
// Update shown description just in case..?
challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock);
if (gamedata->pendingkeyroundoffset >= GDCONVERT_ROUNDSTOKEY)
{
gamedata->pendingkeyroundoffset %= GDCONVERT_ROUNDSTOKEY;
challengesmenu.unlockcount[CC_TALLY]++;
challengesmenu.unlockcount[CC_ANIM]++;
if (challengesmenu.extradata)
if (gamedata->keyspending > 0)
{
unlockable_t *ref;
UINT16 bombcolor;
M_UpdateChallengeGridExtraData(challengesmenu.extradata);
ref = &unlockables[challengesmenu.currentunlock];
bombcolor = SKINCOLOR_NONE;
if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors)
{
bombcolor = ref->color;
}
else switch (ref->type)
{
case SECRET_SKIN:
{
INT32 skin = M_UnlockableSkinNum(ref);
if (skin != -1)
{
bombcolor = skins[skin].prefcolor;
}
break;
}
case SECRET_FOLLOWER:
{
INT32 skin = M_UnlockableFollowerNum(ref);
if (skin != -1)
{
bombcolor = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value);
}
break;
}
default:
break;
}
if (bombcolor == SKINCOLOR_NONE)
{
bombcolor = cv_playercolor[0].value;
}
i = (ref->majorunlock && M_RandomChance(FRACUNIT/2)) ? 1 : 0;
M_SetupReadyExplosions(false, challengesmenu.hilix, challengesmenu.hiliy+i, bombcolor);
if (ref->majorunlock)
{
M_SetupReadyExplosions(false, challengesmenu.hilix+1, challengesmenu.hiliy+(1-i), bombcolor);
}
S_StartSound(NULL, sfx_s3k4e);
S_StartSound(NULL, sfx_achiev);
gamedata->keyspending--;
gamedata->chaokeys++;
challengesmenu.unlockcount[CC_CHAOANIM]++;
}
}
}
}
else if (challengesmenu.requestnew)
{
// The menu apparatus is requesting a new unlock.
challengesmenu.requestnew = false;
if ((newunlock = M_GetNextAchievedUnlock()) != MAXUNLOCKABLES)
{
// We got one!
M_ChallengesAutoFocus(newunlock, false);
}
else
{
// All done! Let's save the unlocks we've busted open.
challengesmenu.pending = challengesmenu.chaokeyadd = false;
G_SaveGameData();
}
}
else if (challengesmenu.pending)
{
tic_t nexttime = M_MenuExtraHeld(pid) ? (UNLOCKTIME*2) : MAXUNLOCKTIME;
if (++challengesmenu.unlockanim >= nexttime)
{
challengesmenu.requestnew = true;
}
if (challengesmenu.currentunlock < MAXUNLOCKABLES
&& challengesmenu.unlockanim == UNLOCKTIME)
{
// Unlock animation... also tied directly to the actual unlock!
gamedata->unlocked[challengesmenu.currentunlock] = true;
M_UpdateUnlockablesAndExtraEmblems(true, true);
// Update shown description just in case..?
challengesmenu.unlockcondition = M_BuildConditionSetString(challengesmenu.currentunlock);
challengesmenu.unlockcount[CC_TALLY]++;
challengesmenu.unlockcount[CC_ANIM]++;
if (challengesmenu.extradata)
{
unlockable_t *ref;
UINT16 bombcolor;
M_UpdateChallengeGridExtraData(challengesmenu.extradata);
ref = &unlockables[challengesmenu.currentunlock];
bombcolor = SKINCOLOR_NONE;
if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors)
{
bombcolor = ref->color;
}
else switch (ref->type)
{
case SECRET_SKIN:
{
INT32 skin = M_UnlockableSkinNum(ref);
if (skin != -1)
{
bombcolor = skins[skin].prefcolor;
}
break;
}
case SECRET_FOLLOWER:
{
INT32 skin = M_UnlockableFollowerNum(ref);
if (skin != -1)
{
bombcolor = K_GetEffectiveFollowerColor(followers[skin].defaultcolor, cv_playercolor[0].value);
}
break;
}
default:
break;
}
if (bombcolor == SKINCOLOR_NONE)
{
bombcolor = cv_playercolor[0].value;
}
i = (ref->majorunlock && M_RandomChance(FRACUNIT/2)) ? 1 : 0;
M_SetupReadyExplosions(false, challengesmenu.hilix, challengesmenu.hiliy+i, bombcolor);
if (ref->majorunlock)
{
M_SetupReadyExplosions(false, challengesmenu.hilix+1, challengesmenu.hiliy+(1-i), bombcolor);
}
S_StartSound(NULL, sfx_s3k4e);
}
}
}
else
{
@ -441,28 +509,57 @@ boolean M_ChallengesInputs(INT32 ch)
const boolean move = (menucmd[pid].dpad_ud != 0 || menucmd[pid].dpad_lr != 0);
(void) ch;
if (challengesmenu.fade)
if (challengesmenu.fade || challengesmenu.chaokeyadd)
{
;
}
#ifdef DEVELOP
else if (M_MenuExtraPressed(pid) && challengesmenu.extradata) // debugging
else if (M_MenuExtraPressed(pid)
&& challengesmenu.extradata)
{
if (challengesmenu.currentunlock < MAXUNLOCKABLES)
{
Z_Free(gamedata->challengegrid);
gamedata->challengegrid = NULL;
gamedata->challengegridwidth = 0;
M_PopulateChallengeGrid();
M_UpdateChallengeGridExtraData(challengesmenu.extradata);
i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy;
M_ChallengesAutoFocus(challengesmenu.currentunlock, true);
if (challengesmenu.currentunlock < MAXUNLOCKABLES
&& !gamedata->unlocked[challengesmenu.currentunlock]
&& !unlockables[challengesmenu.currentunlock].majorunlock
&& ((challengesmenu.extradata[i].flags & CHE_HINT)
|| (challengesmenu.unlockcount[CC_UNLOCKED] + challengesmenu.unlockcount[CC_TALLY] == 0))
&& gamedata->chaokeys > 0)
{
gamedata->chaokeys--;
challengesmenu.unlockcount[CC_CHAOANIM]++;
S_StartSound(NULL, sfx_chchng);
challengesmenu.pending = true;
M_ChallengesAutoFocus(challengesmenu.currentunlock, false);
}
else
{
challengesmenu.unlockcount[CC_CHAONOPE] = 6;
S_StartSound(NULL, sfx_s3k7b); //sfx_s3kb2
#if 0 // debugging
if (challengesmenu.currentunlock < MAXUNLOCKABLES)
{
if (gamedata->unlocked[challengesmenu.currentunlock] && challengesmenu.unlockanim >= UNLOCKTIME)
{
if (challengesmenu.unlockcount[CC_TALLY] > 0)
challengesmenu.unlockcount[CC_TALLY]--;
else
challengesmenu.unlockcount[CC_UNLOCKED]--;
}
Z_Free(gamedata->challengegrid);
gamedata->challengegrid = NULL;
gamedata->challengegridwidth = 0;
M_PopulateChallengeGrid();
M_UpdateChallengeGridExtraData(challengesmenu.extradata);
challengesmenu.pending = true;
M_ChallengesAutoFocus(challengesmenu.currentunlock, true);
}
#endif
}
return true;
}
#endif
else
{
if (M_MenuBackPressed(pid) || start)

View file

@ -8,37 +8,80 @@
struct statisticsmenu_s statisticsmenu;
static boolean M_StatisticsAddMap(UINT16 map, cupheader_t *cup, boolean *headerexists)
{
if (!mapheaderinfo[map])
return false;
if (mapheaderinfo[map]->cup != cup)
return false;
// Check for no visibility
if (mapheaderinfo[map]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU))
return false;
// No TEST RUN, as that's another exception to Time Attack too
if (!mapheaderinfo[map]->typeoflevel)
return false;
// Check for completion
if ((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED)
&& !(mapheaderinfo[map]->mapvisited & MV_BEATEN))
return false;
// Check for unlock
if (M_MapLocked(map+1))
return false;
if (*headerexists == false)
{
statisticsmenu.maplist[statisticsmenu.nummaps++] = NEXTMAP_TITLE; // cheeky hack
*headerexists = true;
}
statisticsmenu.maplist[statisticsmenu.nummaps++] = map;
return true;
}
void M_Statistics(INT32 choice)
{
UINT16 i = 0;
cupheader_t *cup;
UINT16 i;
boolean headerexists;
(void)choice;
statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * nummapheaders, PU_STATIC, NULL);
statisticsmenu.maplist = Z_Malloc(sizeof(UINT16) * (nummapheaders+1 + numkartcupheaders), PU_STATIC, NULL);
statisticsmenu.nummaps = 0;
for (cup = kartcupheaders; cup; cup = cup->next)
{
headerexists = false;
if (M_CupLocked(cup))
continue;
for (i = 0; i < CUPCACHE_MAX; i++)
{
if (cup->cachedlevels[i] >= nummapheaders)
continue;
M_StatisticsAddMap(cup->cachedlevels[i], cup, &headerexists);
}
}
headerexists = false;
for (i = 0; i < nummapheaders; i++)
{
if (!mapheaderinfo[i])
continue;
// Check for no visibility + legacy box
if (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU))
continue;
// Check for completion
if ((mapheaderinfo[i]->menuflags & LF2_FINISHNEEDED)
&& !(mapheaderinfo[i]->mapvisited & MV_BEATEN))
continue;
// Check for unlock
if (M_MapLocked(i+1))
continue;
statisticsmenu.maplist[statisticsmenu.nummaps++] = i;
M_StatisticsAddMap(i, NULL, &headerexists);
}
if ((i = statisticsmenu.numextramedals = M_CountMedals(true, true)) != 0)
i += 2;
statisticsmenu.maplist[statisticsmenu.nummaps] = NEXTMAP_INVALID;
statisticsmenu.maxscroll = (statisticsmenu.nummaps + M_CountMedals(true, true) + 2) - 10;
statisticsmenu.maxscroll = (statisticsmenu.nummaps + i) - 11;
statisticsmenu.location = 0;
if (statisticsmenu.maxscroll < 0)

View file

@ -91,6 +91,9 @@ void M_InitOptions(INT32 choice)
(M_SecretUnlocked(SECRET_ENCORE, false) ? (IT_STRING | IT_CVAR) : IT_DISABLED);
}
OPTIONS_DataDef.menuitems[dopt_addon].status = (M_SecretUnlocked(SECRET_ADDONS, true)
? (IT_STRING | IT_SUBMENU)
: (IT_TRANSTEXT2 | IT_SPACE));
OPTIONS_DataDef.menuitems[dopt_erase].status = (gamestate == GS_MENU
? (IT_STRING | IT_SUBMENU)
: (IT_TRANSTEXT2 | IT_SPACE));

View file

@ -6,27 +6,31 @@
#include "../m_cond.h" // Condition Sets
#include "../f_finale.h"
#define EC_CHALLENGES 0x01
#define EC_STATISTICS 0x02
#define EC_TIMEATTACK 0x04
#define EC_ALLGAME (EC_CHALLENGES|EC_STATISTICS|EC_TIMEATTACK)
menuitem_t OPTIONS_DataErase[] =
{
{IT_STRING | IT_CALL, "Erase Challenges Data", "Be careful! What's deleted is gone forever!",
NULL, {.routine = M_EraseData}, EC_CHALLENGES, 0},
{IT_STRING | IT_CALL, "Erase Time Attack Data", "Be careful! What's deleted is gone forever!",
NULL, {.routine = M_EraseData}, 0, 0},
{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 Unlockable Data", "Be careful! What's deleted is gone forever!",
NULL, {.routine = M_EraseData}, 0, 0},
{IT_STRING | IT_CALL, "Erase GP & Time Attack 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!",
NULL, {.routine = M_EraseData}, EC_ALLGAME, 0},
{IT_SPACE | IT_NOTHING, NULL, NULL,
NULL, {NULL}, 0, 0},
{IT_STRING | IT_CALL, "Erase Profile Data...", "Select a Profile to erase.",
{IT_STRING | IT_CALL, "Erase a Profile...", "Select a Profile to erase.",
NULL, {.routine = M_CheckProfileData}, 0, 0},
{IT_SPACE | IT_NOTHING, NULL, NULL,
NULL, {NULL}, 0, 0},
{IT_STRING | IT_CALL, "\x85\x45rase all Data", "Be careful! What's deleted is gone forever!",
NULL, {.routine = M_EraseData}, 0, 0},
};
menu_t OPTIONS_DataEraseDef = {
@ -53,17 +57,17 @@ static void M_EraseDataResponse(INT32 ch)
S_StartSound(NULL, sfx_itrole); // bweh heh heh
// Delete the data
if (optionsmenu.erasecontext == 2)
{
// SRB2Kart: This actually needs to be done FIRST, so that you don't immediately regain playtime/matches secrets
gamedata->totalplaytime = 0;
gamedata->matchesplayed = 0;
}
if (optionsmenu.erasecontext != 1)
// see also G_LoadGameData
// We do these in backwards order to prevent things from being immediately re-unlocked.
if (optionsmenu.erasecontext & EC_TIMEATTACK)
G_ClearRecords();
if (optionsmenu.erasecontext != 0)
if (optionsmenu.erasecontext & EC_STATISTICS)
M_ClearStats();
if (optionsmenu.erasecontext & EC_CHALLENGES)
M_ClearSecrets();
M_UpdateUnlockablesAndExtraEmblems(false, true);
F_StartIntro();
M_ClearMenus(true);
}
@ -71,15 +75,21 @@ static void M_EraseDataResponse(INT32 ch)
void M_EraseData(INT32 choice)
{
const char *eschoice, *esstr = M_GetText("Are you sure you want to erase\n%s?\n\nPress (A) to confirm or (B) to cancel\n");
(void)choice;
optionsmenu.erasecontext = (UINT8)choice;
optionsmenu.erasecontext = (UINT8)currentMenu->menuitems[itemOn].mvar1;
if (choice == 0)
eschoice = M_GetText("Time Attack data");
else if (choice == 1)
eschoice = M_GetText("Secrets data");
else
if (optionsmenu.erasecontext == EC_CHALLENGES)
eschoice = M_GetText("Challenges data");
else if (optionsmenu.erasecontext == EC_STATISTICS)
eschoice = M_GetText("Statistics data");
else if (optionsmenu.erasecontext == EC_TIMEATTACK)
eschoice = M_GetText("GP & Time Attack data");
else if (optionsmenu.erasecontext == EC_ALLGAME)
eschoice = M_GetText("ALL game data");
else
eschoice = "[misconfigured erasecontext]";
M_StartMessage(va(esstr, eschoice), FUNCPTRCAST(M_EraseDataResponse), MM_YESNO);
}

View file

@ -2,16 +2,42 @@
/// \brief Play Menu
#include "../k_menu.h"
#include "../m_cond.h"
menuitem_t PLAY_MainMenu[] =
{
{IT_STRING | IT_CALL, "Local Play", "Play only on this computer.",
NULL, {.routine = M_SetupGametypeMenu}, 0, 0},
{IT_STRING | IT_CALL, "Online", "Connect to other computers.",
{IT_STRING | IT_CALL, "Online", NULL,
NULL, {.routine = M_MPOptSelectInit}, /*M_MPRoomSelectInit,*/ 0, 0},
{IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0},
};
menu_t PLAY_MainDef = KARTGAMEMODEMENU(PLAY_MainMenu, &PLAY_CharSelectDef);
void M_SetupPlayMenu(INT32 choice)
{
#ifdef TESTERS
(void)choice;
#else
if (choice != -1)
PLAY_MainDef.prevMenu = currentMenu;
// Enable/disable online play.
if (!M_SecretUnlocked(SECRET_ONLINE, true))
{
PLAY_MainMenu[1].status = IT_TRANSTEXT2 | IT_CALL;
PLAY_MainMenu[1].tooltip = "You'll need experience to play over the internet!";
}
else
{
PLAY_MainMenu[1].status = IT_STRING | IT_CALL;
PLAY_MainMenu[1].tooltip = "Connect to other computers over the internet.";
}
if (choice != -1)
M_SetupNextMenu(&PLAY_MainDef, false);
#endif
}

View file

@ -5,6 +5,7 @@
#include "../r_skins.h"
#include "../s_sound.h"
#include "../k_grandprix.h" // K_CanChangeRules
#include "../m_cond.h" // Condition Sets
menuitem_t PLAY_CharSelect[] =
{
@ -1503,7 +1504,7 @@ void M_CharacterSelectTick(void)
#if defined (TESTERS)
M_MPOptSelectInit(0);
#else
M_SetupNextMenu(&PLAY_MainDef, false);
M_SetupPlayMenu(0);
#endif
}

View file

@ -12,7 +12,7 @@ menuitem_t PLAY_GamemodesMenu[] =
{IT_STRING | IT_CALL, "Battle", "It's last kart standing in this free-for-all!",
"MENIMG00", {.routine = M_LevelSelectInit}, 0, GT_BATTLE},
{IT_STRING | IT_CALL, "Capsules", "Bust up all of the capsules in record time!",
{IT_STRING | IT_CALL, "Prisons", "Bust up all of the Prison Eggs in record time!",
NULL, {.routine = M_LevelSelectInit}, 1, GT_BATTLE},
{IT_STRING | IT_CALL, "Special", "Strike your target and secure the prize!",
@ -42,7 +42,7 @@ void M_SetupGametypeMenu(INT32 choice)
{
boolean anyunlocked = false;
if (M_SecretUnlocked(SECRET_BREAKTHECAPSULES, true))
if (M_SecretUnlocked(SECRET_PRISONBREAK, true))
{
// Re-add Capsules
PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL;
@ -59,7 +59,7 @@ void M_SetupGametypeMenu(INT32 choice)
if (!anyunlocked)
{
// Only one non-Back entry, let's skip straight to Race.
M_SetupRaceMenu(0);
M_SetupRaceMenu(choice);
return;
}
}

View file

@ -89,7 +89,7 @@ void M_SetupDifficultyOptions(INT32 choice)
PLAY_RaceDifficulty[drace_mapselect].status = IT_STRING|IT_CALL; // Level Select (Match Race)
PLAY_RaceDifficultyDef.lastOn = drace_mapselect; // Select map select by default.
if (M_SecretUnlocked(SECRET_ENCORE, false))
if (M_SecretUnlocked(SECRET_ENCORE, true))
{
PLAY_RaceDifficulty[drace_encore].status = IT_STRING2|IT_CVAR; // Encore on/off
}

View file

@ -9,6 +9,7 @@
#include "../d_main.h" // srb2home
#include "../m_misc.h" // M_MkdirEach
#include "../z_zone.h" // Z_StrDup/Z_Free
#include "../m_cond.h"
static void CV_SPBAttackChanged(void)
{
@ -21,7 +22,11 @@ struct timeattackmenu_s timeattackmenu;
void M_TimeAttackTick(void)
{
timeattackmenu.ticker++;
timeattackmenu.ticker++;
if (timeattackmenu.spbflicker > 0)
{
timeattackmenu.spbflicker--;
}
}
boolean M_TimeAttackInputs(INT32 ch)
@ -30,10 +35,10 @@ boolean M_TimeAttackInputs(INT32 ch)
const boolean buttonR = M_MenuButtonPressed(pid, MBT_R);
(void) ch;
if (buttonR && levellist.newgametype == GT_RACE)
if (buttonR && levellist.newgametype == GT_RACE && M_SecretUnlocked(SECRET_SPBATTACK, true))
{
CV_AddValue(&cv_dummyspbattack, 1);
timeattackmenu.spbflicker = timeattackmenu.ticker;
timeattackmenu.spbflicker = TICRATE/6;
if (cv_dummyspbattack.value)
{
S_StartSound(NULL, sfx_s3k9f);
@ -231,6 +236,9 @@ void M_PrepareTimeAttack(INT32 choice)
}
}
if (levellist.levelsearch.timeattack == false || levellist.newgametype != GT_RACE || !M_SecretUnlocked(SECRET_SPBATTACK, true))
CV_SetValue(&cv_dummyspbattack, 0);
// Time-sticker Medals
G_UpdateTimeStickerMedals(levellist.choosemap, false);

View file

@ -2,6 +2,8 @@
/// \brief MULTIPLAYER OPTION SELECT
#include "../k_menu.h"
#include "../m_cond.h"
#include "../s_sound.h"
#if defined (TESTERS)
#define IT_STRING_CALL_NOTESTERS IT_DISABLED
@ -62,6 +64,15 @@ void M_MPOptSelectInit(INT32 choice)
INT16 arrcpy[3][3] = {{0,68,0}, {0,12,0}, {0,74,0}};
const UINT32 forbidden = GTR_FORBIDMP;
#ifndef TESTERS
if (choice != -1 && !M_SecretUnlocked(SECRET_ONLINE, true))
{
M_StartMessage("Online play is ""\x8B""not yet unlocked""\x80"".\n\nYou'll want experience in ""\x8B""Grand Prix""\x80""\nbefore even thinking about facing\nopponents from across the world.\n\nPress (B)", NULL, MM_NOTHING);
S_StartSound(NULL, sfx_s3k36);
return;
}
#endif
mpmenu.modechoice = 0;
mpmenu.ticker = 0;

View file

@ -3,6 +3,7 @@
#include "../k_menu.h"
#include "../s_sound.h"
#include "../m_cond.h"
menuitem_t PLAY_MP_RoomSelect[] =
{
@ -30,23 +31,23 @@ void M_MPRoomSelect(INT32 choice)
const UINT8 pid = 0;
(void) choice;
if (menucmd[pid].dpad_lr)
{
mpmenu.room = (!mpmenu.room) ? 1 : 0;
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
}
else if (M_MenuBackPressed(pid))
if (M_MenuBackPressed(pid))
{
M_GoBack(0);
M_SetMenuDelay(pid);
}
else if (M_MenuConfirmPressed(pid))
{
M_ServersMenu(0);
M_SetMenuDelay(pid);
}
else if (mpmenu.roomforced == false
&& menucmd[pid].dpad_lr != 0)
{
mpmenu.room ^= 1;
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
}
}
void M_MPRoomSelectTick(void)
@ -57,7 +58,8 @@ void M_MPRoomSelectTick(void)
void M_MPRoomSelectInit(INT32 choice)
{
(void)choice;
mpmenu.room = 0;
mpmenu.room = (modifiedgame == true) ? 1 : 0;
mpmenu.roomforced = ((modifiedgame == true) || (!M_SecretUnlocked(SECRET_ADDONS, true)));
mpmenu.ticker = 0;
mpmenu.servernum = 0;
mpmenu.scrolln = 0;

View file

@ -84,7 +84,7 @@ void M_CupSelectHandler(INT32 choice)
if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/)
{
INT16 count;
UINT16 count;
cupheader_t *newcup = cupgrid.builtgrid[CUPMENU_CURSORID];
cupheader_t *oldcup = levellist.levelsearch.cup;
@ -94,7 +94,7 @@ void M_CupSelectHandler(INT32 choice)
count = M_CountLevelsToShowInList(&levellist.levelsearch);
if ((!newcup)
|| (count <= 0)
|| (count == 0)
|| (cupgrid.grandprix == true && newcup->cachedlevels[0] == NEXTMAP_INVALID))
{
S_StartSound(NULL, sfx_s3kb2);
@ -161,7 +161,7 @@ void M_CupSelectHandler(INT32 choice)
restoreMenu = &PLAY_CupSelectDef;
}
else if (count == 1)
else if (count == 1 && levellist.levelsearch.timeattack == true)
{
PLAY_TimeAttackDef.transitionID = currentMenu->transitionID+1;
M_LevelSelected(0);
@ -174,6 +174,7 @@ void M_CupSelectHandler(INT32 choice)
levellist.cursor = 0;
}
levellist.mapcount = count;
M_LevelSelectScrollDest();
levellist.y = levellist.dest;

View file

@ -9,6 +9,8 @@
#include "../../f_finale.h" // F_WipeStartScreen
#include "../../v_video.h"
cupheader_t dummy_lostandfound;
menuitem_t PLAY_LevelSelect[] =
{
{IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_LevelSelectHandler}, 0, 0},
@ -57,8 +59,9 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch)
if (mapheaderinfo[mapnum]->lumpnum == LUMPERROR)
return false;
// Check for TOL
if (!(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel))
// Check for TOL (permits TEST RUN outside of time attack)
if ((levelsearch->timeattack || mapheaderinfo[mapnum]->typeoflevel)
&& !(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel))
return false;
// Should the map be hidden?
@ -70,10 +73,14 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch)
return false;
// Don't permit cup when no cup requested (also no dupes in time attack)
if (levelsearch->cupmode
&& (levelsearch->timeattack || !levelsearch->cup)
&& mapheaderinfo[mapnum]->cup != levelsearch->cup)
return false;
if (levelsearch->cupmode)
{
cupheader_t *cup = (levelsearch->cup == &dummy_lostandfound) ? NULL : levelsearch->cup;
if ((!cup || levelsearch->timeattack)
&& mapheaderinfo[mapnum]->cup != cup)
return false;
}
// Finally, the most complex check: does the map have lock conditions?
if (levelsearch->checklocked)
@ -97,9 +104,9 @@ UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch)
INT16 i, count = 0;
if (!levelsearch)
return false;
return 0;
if (levelsearch->cup)
if (levelsearch->cup && levelsearch->cup != &dummy_lostandfound)
{
if (levelsearch->checklocked && M_CupLocked(levelsearch->cup))
return 0;
@ -126,9 +133,9 @@ UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch)
INT16 mapnum = NEXTMAP_INVALID;
if (!levelsearch)
return false;
return NEXTMAP_INVALID;
if (levelsearch->cup)
if (levelsearch->cup && levelsearch->cup != &dummy_lostandfound)
{
if (levelsearch->checklocked && M_CupLocked(levelsearch->cup))
{
@ -151,6 +158,9 @@ UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch)
for (mapnum = 0; mapnum < nummapheaders; mapnum++)
if (M_CanShowLevelInList(mapnum, levelsearch))
break;
if (mapnum >= nummapheaders)
mapnum = NEXTMAP_INVALID;
}
return mapnum;
@ -159,9 +169,9 @@ UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch)
UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch)
{
if (!levelsearch)
return false;
return NEXTMAP_INVALID;
if (levelsearch->cup)
if (levelsearch->cup && levelsearch->cup != &dummy_lostandfound)
{
mapnum = NEXTMAP_INVALID;
(*i)++;
@ -185,14 +195,14 @@ UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch)
void M_LevelSelectScrollDest(void)
{
UINT16 m = M_CountLevelsToShowInList(&levellist.levelsearch)-1;
UINT16 m = levellist.mapcount-1;
levellist.dest = (6*levellist.cursor);
if (levellist.dest < 3)
levellist.dest = 3;
if (levellist.dest > (6*m)-3)
if (m && levellist.dest > (6*m)-3)
levellist.dest = (6*m)-3;
}
@ -209,6 +219,9 @@ boolean M_LevelListFromGametype(INT16 gt)
{
cupgrid.cappages = 0;
cupgrid.builtgrid = NULL;
dummy_lostandfound.cachedlevels[0] = NEXTMAP_INVALID;
first = false;
}
levellist.newgametype = gt;
@ -227,11 +240,8 @@ boolean M_LevelListFromGametype(INT16 gt)
levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT));
first = false;
}
if (levellist.levelsearch.timeattack == false || levellist.newgametype != GT_RACE)
CV_SetValue(&cv_dummyspbattack, 0);
}
// Obviously go to Cup Select in gametypes that have cups.
// Use a really long level select in gametypes that don't use cups.
@ -266,6 +276,31 @@ boolean M_LevelListFromGametype(INT16 gt)
}
memset(cupgrid.builtgrid, 0, cupgrid.cappages * pagelen);
// The following doubles the size of the buffer if necessary.
#define GRID_INSERTCUP \
if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * pagelen) \
{ \
const size_t firstlen = cupgrid.cappages * pagelen; \
cupgrid.builtgrid = Z_Realloc(cupgrid.builtgrid, \
firstlen * 2, \
PU_STATIC, NULL); \
\
if (!cupgrid.builtgrid) \
{ \
I_Error("M_LevelListFromGametype: Not enough memory to reallocate builtgrid"); \
} \
\
cupgrid.cappages *= 2; \
} \
\
cupgrid.builtgrid[currentid] = templevelsearch.cup;
#define GRID_FOCUSCUP \
cupgrid.x = currentid % CUPMENU_COLUMNS; \
cupgrid.y = (currentid / CUPMENU_COLUMNS) % CUPMENU_ROWS; \
cupgrid.pageno = currentid / (CUPMENU_COLUMNS * CUPMENU_ROWS); \
currentvalid = true;
while (templevelsearch.cup)
{
templevelsearch.checklocked = false;
@ -278,23 +313,7 @@ boolean M_LevelListFromGametype(INT16 gt)
foundany = true;
if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * pagelen)
{
// Double the size of the buffer, and clear the other stuff.
const size_t firstlen = cupgrid.cappages * pagelen;
cupgrid.builtgrid = Z_Realloc(cupgrid.builtgrid,
firstlen * 2,
PU_STATIC, NULL);
if (!cupgrid.builtgrid)
{
I_Error("M_LevelListFromGametype: Not enough memory to reallocate builtgrid");
}
cupgrid.cappages *= 2;
}
cupgrid.builtgrid[currentid] = templevelsearch.cup;
GRID_INSERTCUP;
templevelsearch.checklocked = true;
if (M_GetFirstLevelInList(&temp, &templevelsearch) != NEXTMAP_INVALID)
@ -305,10 +324,7 @@ boolean M_LevelListFromGametype(INT16 gt)
? (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == templevelsearch.cup)
: (gt == -1 && levellist.levelsearch.cup == templevelsearch.cup))
{
cupgrid.x = currentid % CUPMENU_COLUMNS;
cupgrid.y = (currentid / CUPMENU_COLUMNS) % CUPMENU_ROWS;
cupgrid.pageno = currentid / (CUPMENU_COLUMNS * CUPMENU_ROWS);
currentvalid = true;
GRID_FOCUSCUP;
}
}
@ -316,6 +332,34 @@ boolean M_LevelListFromGametype(INT16 gt)
templevelsearch.cup = templevelsearch.cup->next;
}
// Lost and found, a simplified version of the above loop.
if (cupgrid.grandprix == false)
{
templevelsearch.cup = &dummy_lostandfound;
templevelsearch.checklocked = true;
if (M_GetFirstLevelInList(&temp, &levellist.levelsearch) != NEXTMAP_INVALID)
{
foundany = true;
GRID_INSERTCUP;
highestunlockedid = currentid;
if (Playing()
? (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == NULL)
: (gt == -1 && levellist.levelsearch.cup == templevelsearch.cup))
{
GRID_FOCUSCUP;
}
currentid++;
}
templevelsearch.cup = NULL;
}
#undef GRID_INSERTCUP
#undef GRID_FOCUSCUP
if (foundany == false)
{
return false;
@ -356,6 +400,7 @@ boolean M_LevelListFromGametype(INT16 gt)
levellist.levelsearch.cup = NULL;
}
levellist.mapcount = M_CountLevelsToShowInList(&levellist.levelsearch);
M_LevelSelectScrollDest();
levellist.y = levellist.dest;
@ -518,7 +563,6 @@ void M_LevelSelected(INT16 add)
void M_LevelSelectHandler(INT32 choice)
{
INT16 maxlevels = M_CountLevelsToShowInList(&levellist.levelsearch);
const UINT8 pid = 0;
(void)choice;
@ -531,7 +575,7 @@ void M_LevelSelectHandler(INT32 choice)
if (menucmd[pid].dpad_ud > 0)
{
levellist.cursor++;
if (levellist.cursor >= maxlevels)
if (levellist.cursor >= levellist.mapcount)
levellist.cursor = 0;
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
@ -540,7 +584,7 @@ void M_LevelSelectHandler(INT32 choice)
{
levellist.cursor--;
if (levellist.cursor < 0)
levellist.cursor = maxlevels-1;
levellist.cursor = levellist.mapcount-1;
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
}

View file

@ -27,7 +27,7 @@ static inline size_t M_StringHeight(const char *string)
void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtype)
{
const UINT8 pid = 0;
size_t max = 0, start = 0, strlines = 0, i;
size_t max = 0, maxatstart = 0, start = 0, strlines, i;
static char *message = NULL;
Z_Free(message);
message = Z_StrDup(string);
@ -41,12 +41,13 @@ void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtyp
{
start = i;
max += 4;
maxatstart = max;
}
else if (message[i] == '\n')
{
strlines = i;
start = 0;
max = 0;
maxatstart = 0;
continue;
}
else if (message[i] & 0x80)
@ -58,8 +59,7 @@ void M_StartMessage(const char *string, void *routine, menumessagetype_t itemtyp
if (max >= BASEVIDWIDTH && start > 0)
{
message[start] = '\n';
max -= (start-strlines)*8;
strlines = start;
max -= maxatstart;
start = 0;
}
}

View file

@ -3,6 +3,7 @@
#include "../../k_menu.h"
#include "../../k_grandprix.h" // K_CanChangeRules
#include "../../m_cond.h"
#include "../../s_sound.h"
// ESC pause menu
@ -136,7 +137,11 @@ void M_OpenPauseMenu(void)
PAUSE_Main[mpause_switchmap].status = IT_STRING | IT_CALL;
PAUSE_Main[mpause_restartmap].status = IT_STRING | IT_CALL;
PAUSE_Main[mpause_addons].status = IT_STRING | IT_CALL;
if (M_SecretUnlocked(SECRET_ADDONS, true))
{
PAUSE_Main[mpause_addons].status = IT_STRING | IT_CALL;
}
}
}
else if (!netgame && !demo.playback)

View file

@ -534,9 +534,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
if (P_IsLocalPlayer(player) && !gamedata->collected[special->health-1])
{
gamedata->collected[special->health-1] = gotcollected = true;
if (!M_UpdateUnlockablesAndExtraEmblems(true))
if (!M_UpdateUnlockablesAndExtraEmblems(true, true))
S_StartSound(NULL, sfx_ncitem);
G_SaveGameData();
gamedata->deferredsave = true;
}
if (netgame)
@ -636,7 +636,7 @@ static void P_AddBrokenPrison(mobj_t *target, mobj_t *source)
{
(void)target;
if (!battlecapsules) // !battleprisons
if (!battleprisons)
return;
if ((gametyperules & GTR_POINTLIMIT) && (source && source->player))
@ -845,7 +845,7 @@ void P_CheckPointLimit(void)
if (!(gametyperules & GTR_POINTLIMIT))
return;
if (battlecapsules)
if (battleprisons)
return;
// pointlimit is nonzero, check if it's been reached by this player
@ -1953,6 +1953,13 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source,
switch (type)
{
case DMG_DEATHPIT:
// Fell off the stage
if (player->roundconditions.fell_off == false)
{
player->roundconditions.fell_off = true;
player->roundconditions.checkthisframe = true;
}
if (gametyperules & GTR_BUMPERS)
{
player->mo->health--;
@ -2087,6 +2094,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
player_t *player;
player_t *playerInflictor;
boolean force = false;
boolean spbpop = false;
INT32 laglength = 6;
@ -2125,6 +2133,16 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
return false;
break;
case MT_SPB:
spbpop = (damagetype & DMG_TYPEMASK) == DMG_VOLTAGE;
if (spbpop && source && source->player
&& source->player->roundconditions.spb_neuter == false)
{
source->player->roundconditions.spb_neuter = true;
source->player->roundconditions.checkthisframe = true;
}
break;
default:
break;
}
@ -2143,7 +2161,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
if (!force)
{
if (!(target->type == MT_SPB && (damagetype & DMG_TYPEMASK) == DMG_VOLTAGE))
if (!spbpop)
{
if (!(target->flags & MF_SHOOTABLE))
return false; // shouldn't happen...
@ -2190,6 +2208,18 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
}
}
if (inflictor && source && source->player)
{
if (source->player->roundconditions.hit_midair == false
&& K_IsMissileOrKartItem(source)
&& target->player->airtime > TICRATE/2
&& source->player->airtime > TICRATE/2)
{
source->player->roundconditions.hit_midair = true;
source->player->roundconditions.checkthisframe = true;
}
}
// Instant-Death
if ((damagetype & DMG_DEATHMASK))
{

View file

@ -3427,6 +3427,20 @@ void P_MobjCheckWater(mobj_t *mobj)
{
return;
}
if (!(p->roundconditions.wet_player & MFE_TOUCHWATER)
&& (mobj->eflags & MFE_TOUCHWATER))
{
p->roundconditions.wet_player |= MFE_TOUCHWATER;
p->roundconditions.checkthisframe = true;
}
if (!(p->roundconditions.wet_player & MFE_UNDERWATER)
&& (mobj->eflags & MFE_UNDERWATER))
{
p->roundconditions.wet_player |= MFE_UNDERWATER;
p->roundconditions.checkthisframe = true;
}
}
if (mobj->flags & MF_APPLYTERRAIN)
@ -9381,7 +9395,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
{
if (gametyperules & GTR_PAPERITEMS)
{
if (battlecapsules == true)
if (battleprisons == true)
{
;
}

View file

@ -488,6 +488,10 @@ static void P_NetArchivePlayers(savebuffer_t *save)
WRITEFIXED(save->p, players[i].loop.shift.x);
WRITEFIXED(save->p, players[i].loop.shift.y);
WRITEUINT8(save->p, players[i].loop.flip);
// ACS has read access to this, so it has to be net-communicated.
// It is the ONLY roundcondition that is sent over the wire and I'd like it to stay that way.
WRITEUINT32(save->p, players[i].roundconditions.unlocktriggers);
}
}
@ -876,6 +880,10 @@ static void P_NetUnArchivePlayers(savebuffer_t *save)
players[i].loop.shift.y = READFIXED(save->p);
players[i].loop.flip = READUINT8(save->p);
// ACS has read access to this, so it has to be net-communicated.
// It is the ONLY roundcondition that is sent over the wire and I'd like it to stay that way.
players[i].roundconditions.unlocktriggers = READUINT32(save->p);
//players[i].viewheight = P_GetPlayerViewHeight(players[i]); // scale cannot be factored in at this point
}
}
@ -4997,7 +5005,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending)
// SRB2kart
WRITEINT32(save->p, numgotboxes);
WRITEUINT8(save->p, numtargets);
WRITEUINT8(save->p, battlecapsules);
WRITEUINT8(save->p, battleprisons);
WRITEUINT8(save->p, gamespeed);
WRITEUINT8(save->p, numlaps);
@ -5166,7 +5174,7 @@ static inline boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading)
// SRB2kart
numgotboxes = READINT32(save->p);
numtargets = READUINT8(save->p);
battlecapsules = (boolean)READUINT8(save->p);
battleprisons = (boolean)READUINT8(save->p);
gamespeed = READUINT8(save->p);
numlaps = READUINT8(save->p);

View file

@ -7071,7 +7071,7 @@ static void P_InitLevelSettings(void)
nummaprings = 0;
nummapboxes = numgotboxes = 0;
maptargets = numtargets = 0;
battlecapsules = false;
battleprisons = false;
// emerald hunt
hunt1 = hunt2 = hunt3 = NULL;
@ -7501,6 +7501,7 @@ static void P_InitGametype(void)
// Started a game? Move on to the next jam when you go back to the title screen
CV_SetValue(&cv_menujam_update, 1);
gamedata->musicflags = 0;
}
struct minimapinfo minimapinfo;
@ -7993,7 +7994,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
{
mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED;
M_UpdateUnlockablesAndExtraEmblems(true);
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData();
}

View file

@ -1541,17 +1541,18 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
return false;
break;
case 317:
if (actor && actor->player)
{ // Unlockable triggers required
INT32 trigid = triggerline->args[1];
if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
return false;
else if (trigid < 0 || trigid > 31) // limited by 32 bit variable
if (trigid < 0 || trigid > 31) // limited by 32 bit variable
{
CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger (sidedef %hu): bad trigger ID %d\n", triggerline->sidenum[0], trigid);
return false;
}
else if (!(unlocktriggers & (1 << trigid)))
else if (!(actor && actor->player))
return false;
else if (!(actor->player->roundconditions.unlocktriggers & (1 << trigid)))
return false;
}
break;
@ -2111,6 +2112,12 @@ static void K_HandleLapIncrement(player_t *player)
}
lastLowestLap = lowestLap;
if (P_IsLocalPlayer(player))
{
player->roundconditions.checkthisframe = true;
gamedata->deferredconditioncheck = true;
}
}
else if (player->starpostnum)
{
@ -3420,29 +3427,23 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha
break;
case 441: // Trigger unlockable
if (!(demo.playback || netgame || multiplayer))
{
INT32 trigid = args[0];
if (trigid < 0 || trigid > 31) // limited by 32 bit variable
CONS_Debug(DBG_GAMELOGIC, "Unlockable trigger: bad trigger ID %d\n", trigid);
else
else if (mo && mo->player)
{
UINT32 flag = 1 << trigid;
if (unlocktriggers & flag)
if (mo->player->roundconditions.unlocktriggers & flag)
{
// Execute one time only
break;
}
unlocktriggers |= flag;
// Unlocked something?
if (M_UpdateUnlockablesAndExtraEmblems(true))
{
G_SaveGameData(); // only save if unlocked something
}
mo->player->roundconditions.unlocktriggers |= flag;
mo->player->roundconditions.checkthisframe = true;
}
}
break;

View file

@ -708,6 +708,11 @@ void P_Ticker(boolean run)
}
ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time;
// TODO would this be laggy with more conditions in play...
if (((!demo.playback && leveltime > introtime && M_UpdateUnlockablesAndExtraEmblems(true, false))
|| (gamedata && gamedata->deferredsave)))
G_SaveGameData();
}
// Keep track of how long they've been playing!

View file

@ -509,6 +509,11 @@ INT32 P_GivePlayerRings(player_t *player, INT32 num_rings)
if ((gametyperules & GTR_SPHERES)) // No rings in Battle Mode
return 0;
if (gamedata && num_rings > 0 && P_IsLocalPlayer(player) && gamedata->totalrings <= GDMAX_RINGS)
{
gamedata->totalrings += num_rings;
}
test = player->rings + num_rings;
if (test > 20) // Caps at 20 rings, sorry!
num_rings -= (test-20);
@ -517,6 +522,12 @@ INT32 P_GivePlayerRings(player_t *player, INT32 num_rings)
player->rings += num_rings;
if (player->roundconditions.debt_rings == false && player->rings < 0)
{
player->roundconditions.debt_rings = true;
player->roundconditions.checkthisframe = true;
}
return num_rings;
}
@ -1268,7 +1279,11 @@ void P_DoPlayerExit(player_t *player)
return;
if (P_IsLocalPlayer(player) && (!player->spectator && !demo.playback))
{
legitimateexit = true;
player->roundconditions.checkthisframe = true;
gamedata->deferredconditioncheck = true;
}
if (G_GametypeUsesLives() && losing)
{
@ -3807,6 +3822,8 @@ void P_DoTimeOver(player_t *player)
if (P_IsLocalPlayer(player) && !demo.playback)
{
legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p
player->roundconditions.checkthisframe = true;
gamedata->deferredconditioncheck = true;
}
if (netgame && !player->bot && !(gametyperules & GTR_BOSS))

View file

@ -1,5 +1,6 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2016-2023 by Vivian "toastergrl" Grannell
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2020 by Sonic Team Junior.
@ -129,6 +130,27 @@ static void Sk_SetDefaultValue(skin_t *skin)
skin->soundsid[S_sfx[i].skinsound] = i;
}
// Grab the default skin
UINT8 R_BotDefaultSkin(void)
{
static INT32 defaultbotskin = -1;
if (defaultbotskin == -1)
{
const char *defaultbotskinname = "eggrobo";
defaultbotskin = R_SkinAvailable(defaultbotskinname);
if (defaultbotskin == -1)
{
// This shouldn't happen, but just in case
defaultbotskin = 0;
}
}
return (UINT8)defaultbotskin;
}
//
// Initialize the basic skins
//
@ -156,13 +178,15 @@ void R_InitSkins(void)
R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps);
}
ST_ReloadSkinFaceGraphics();
M_UpdateConditionSetsPending();
}
UINT8 *R_GetSkinAvailabilities(boolean demolock)
UINT8 *R_GetSkinAvailabilities(boolean demolock, boolean forbots)
{
UINT8 i, shif, byte;
INT32 skinid;
static UINT8 responsebuffer[MAXAVAILABILITY];
UINT8 defaultbotskin = R_BotDefaultSkin();
memset(&responsebuffer, 0, sizeof(responsebuffer));
@ -171,15 +195,17 @@ UINT8 *R_GetSkinAvailabilities(boolean demolock)
if (unlockables[i].type != SECRET_SKIN)
continue;
// NEVER EVER EVER M_CheckNetUnlockByID
if (gamedata->unlocked[i] != true && !demolock)
continue;
skinid = M_UnlockableSkinNum(&unlockables[i]);
if (skinid < 0 || skinid >= MAXSKINS)
continue;
if ((forbots
? (M_CheckNetUnlockByID(i) || skinid == defaultbotskin) // Assert the host's lock.
: gamedata->unlocked[i]) // Assert the local lock.
!= true && !demolock)
continue;
shif = (skinid % 8);
byte = (skinid / 8);
@ -250,8 +276,11 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins)
return !!(players[playernum].availabilities[byte] & (1 << shif));
}
// Use the host's if it's checking general state
if (playernum == -1)
return M_CheckNetUnlockByID(i);
// Use the unlockables table directly
// NOTE: M_CheckNetUnlockByID would be correct in many circumstances... but not all. TODO figure out how to discern.
return (boolean)(gamedata->unlocked[i]);
}
@ -270,6 +299,25 @@ INT32 R_SkinAvailable(const char *name)
return -1;
}
// Returns engine class dependent on skin properties
engineclass_t R_GetEngineClass(SINT8 speed, SINT8 weight, skinflags_t flags)
{
if (flags & SF_IRONMAN)
return ENGINECLASS_J;
speed = (speed - 1) / 3;
weight = (weight - 1) / 3;
#define LOCKSTAT(stat) \
if (stat < 0) { stat = 0; } \
if (stat > 2) { stat = 2; }
LOCKSTAT(speed);
LOCKSTAT(weight);
#undef LOCKSTAT
return (speed + (3*weight));
}
// Auxillary function that actually sets the skin
static void SetSkin(player_t *player, INT32 skinnum)
{

View file

@ -1,5 +1,6 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2016-2023 by Vivian "toastergrl" Grannell
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2020 by Sonic Team Junior.
@ -74,6 +75,25 @@ enum facepatches {
NUMFACES
};
typedef enum {
ENGINECLASS_A,
ENGINECLASS_B,
ENGINECLASS_C,
ENGINECLASS_D,
ENGINECLASS_E,
ENGINECLASS_F,
ENGINECLASS_G,
ENGINECLASS_H,
ENGINECLASS_I,
ENGINECLASS_J
} engineclass_t;
engineclass_t R_GetEngineClass(SINT8 speed, SINT8 weight, skinflags_t flags);
/// Externs
extern INT32 numskins;
extern skin_t skins[MAXSKINS];
@ -91,8 +111,9 @@ void ClearFakePlayerSkin(player_t* player);
boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins);
INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight, UINT32 flags, boolean unlock);
UINT8 *R_GetSkinAvailabilities(boolean demolock);
UINT8 *R_GetSkinAvailabilities(boolean demolock, boolean forbots);
INT32 R_SkinAvailable(const char *name);
UINT8 R_BotDefaultSkin(void);
void R_PatchSkins(UINT16 wadnum, boolean mainfile);
void R_AddSkins(UINT16 wadnum, boolean mainfile);

View file

@ -328,6 +328,7 @@ FUNCNORETURN static ATTRNORETURN void signal_handler(INT32 num)
{
D_QuitNetGame(); // Fix server freezes
CL_AbortDownloadResume();
G_DirtyGameData();
#ifdef UNIXBACKTRACE
write_backtrace(num);
#endif
@ -724,6 +725,8 @@ static void I_RegisterSignals (void)
#ifdef NEWSIGNALHANDLER
static void signal_handler_child(INT32 num)
{
G_DirtyGameData();
#ifdef UNIXBACKTRACE
write_backtrace(num);
#endif
@ -1542,6 +1545,7 @@ void I_Error(const char *error, ...)
if (errorcount == 8)
{
M_SaveConfig(NULL);
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData();
}
if (errorcount > 20)
@ -1573,6 +1577,7 @@ void I_Error(const char *error, ...)
M_SaveConfig(NULL); // save game config, cvars..
D_SaveBan(); // save the ban list
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData(); // Tails 12-08-2002
// Shutdown. Here might be other errors.

View file

@ -357,6 +357,7 @@ static void signal_handler(INT32 num)
sigmsg = sigdef;
}
G_DirtyGameData();
I_OutputMsg("signal_handler() error: %s\n", sigmsg);
signal(num, SIG_DFL); //default signal action
raise(num);
@ -3083,6 +3084,7 @@ void I_Error(const char *error, ...)
if (errorcount == 9)
{
M_SaveConfig(NULL);
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData();
}
if (errorcount > 20)
@ -3147,6 +3149,7 @@ void I_Error(const char *error, ...)
#ifndef NONET
D_SaveBan(); // save the ban list
#endif
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData(); // Tails 12-08-2002
// Shutdown. Here might be other errors.

View file

@ -44,6 +44,7 @@ TYPEDEF (discordRequest_t);
// d_player.h
TYPEDEF (respawnvars_t);
TYPEDEF (botvars_t);
TYPEDEF (roundconditions_t);
TYPEDEF (skybox_t);
TYPEDEF (itemroulette_t);
TYPEDEF (altview_t);
@ -107,6 +108,7 @@ TYPEDEF (skincolor_t);
// doomstat.h
TYPEDEF (precipprops_t);
TYPEDEF (recorddata_t);
TYPEDEF (cupwindata_t);
TYPEDEF (scene_t);
TYPEDEF (cutscene_t);
TYPEDEF (textpage_t);

View file

@ -49,6 +49,7 @@
#include "fastcmp.h"
#include "g_game.h" // G_LoadGameData
#include "m_cond.h" // gamedata itself
#include "filesrch.h"
#include "i_video.h" // rendermode
@ -813,6 +814,14 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
}
#endif
// Do this immediately before anything of consequence that invalidates gamedata can happen.
if ((mainfile == false) && (gamedata != NULL) && (gamedata->everloadedaddon == false))
{
gamedata->everloadedaddon = true;
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData();
}
switch(type = ResourceFileDetect(filename))
{
case RET_SOC:

View file

@ -469,6 +469,7 @@ static void signal_handler(int num)
char sigdef[64];
D_QuitNetGame(); // Fix server freezes
G_DirtyGameData();
I_ShutdownSystem();
switch (num)
@ -607,6 +608,7 @@ void I_Error(const char *error, ...)
if (errorcount == 7)
{
M_SaveConfig(NULL);
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData();
}
if (errorcount > 20)
@ -636,6 +638,7 @@ void I_Error(const char *error, ...)
if (!errorcount)
{
M_SaveConfig(NULL); // save game config, cvars..
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData();
}
@ -726,7 +729,7 @@ void I_Quit(void)
G_CheckDemoStatus();
M_SaveConfig(NULL); // save game config, cvars..
G_SaveGameData();
G_SaveGameData(); // undirty your save
// maybe it needs that the ticcount continues,
// or something else that will be finished by I_ShutdownSystem(),