Merge branch 'unlockables-undefeatable' into 'master'

Unlockables Undefeatable

Closes #350 and #324

See merge request KartKrew/Kart!789
This commit is contained in:
Oni 2022-12-19 22:21:30 +00:00
commit 62906ebd4e
51 changed files with 3894 additions and 1295 deletions

View file

@ -84,7 +84,7 @@ CV_PossibleValue_t CV_Natural[] = {{1, "MIN"}, {999999999, "MAX"}, {0, NULL}};
#else
#define VALUE "Off"
#endif
consvar_t cv_cheats = CVAR_INIT ("cheats", VALUE, CV_NETVAR|CV_CALL, CV_OnOff, CV_CheatsChanged);
consvar_t cv_cheats = CVAR_INIT ("cheats", VALUE, CV_NETVAR|CV_CALL|CV_NOINIT, CV_OnOff, CV_CheatsChanged);
#undef VALUE
// SRB2kart
@ -1481,7 +1481,7 @@ boolean CV_CompleteValue(consvar_t *var, const char **valstrp, INT32 *intval)
{
v = R_SkinAvailable(valstr);
if (!R_SkinUsable(-1, v))
if (!R_SkinUsable(-1, v, false))
v = -1;
goto finish;
@ -1966,13 +1966,13 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
return;
}
if (var == &cv_kartencore && !M_SecretUnlocked(SECRET_ENCORE))
if (var == &cv_kartencore && !M_SecretUnlocked(SECRET_ENCORE, false))
{
CONS_Printf(M_GetText("You haven't unlocked Encore Mode yet!\n"));
return;
}
if (var == &cv_kartspeed && !M_SecretUnlocked(SECRET_HARDSPEED))
if (var == &cv_kartspeed && !M_SecretUnlocked(SECRET_HARDSPEED, false))
{
if (!stricmp(value, "Hard") || atoi(value) >= KARTSPEED_HARD)
{
@ -1984,7 +1984,7 @@ static void CV_SetCVar(consvar_t *var, const char *value, boolean stealth)
if (var == &cv_forceskin)
{
INT32 skin = R_SkinAvailable(value);
if ((stricmp(value, "None")) && ((skin == -1) || !R_SkinUsable(-1, skin)))
if ((stricmp(value, "None")) && ((skin == -1) || !R_SkinUsable(-1, skin, false)))
{
CONS_Printf("Please provide a valid skin name (\"None\" disables).\n");
return;
@ -2109,7 +2109,7 @@ void CV_AddValue(consvar_t *var, INT32 increment)
else if (newvalue >= numskins)
newvalue = -1;
} while ((oldvalue != newvalue)
&& !(R_SkinUsable(-1, newvalue)));
&& !(R_SkinUsable(-1, newvalue, false)));
}
else
newvalue = var->value + increment;
@ -2227,7 +2227,7 @@ void CV_AddValue(consvar_t *var, INT32 increment)
|| var->PossibleValue == dummykartspeed_cons_t
|| var->PossibleValue == gpdifficulty_cons_t)
{
if (!M_SecretUnlocked(SECRET_HARDSPEED))
if (!M_SecretUnlocked(SECRET_HARDSPEED, false))
{
max = KARTSPEED_NORMAL+1;
if (var->PossibleValue == kartspeed_cons_t)

View file

@ -57,6 +57,7 @@
#include "k_boss.h"
#include "doomstat.h"
#include "s_sound.h" // sfx_syfail
#include "m_cond.h" // netUnlocked
// cl loading screen
#include "v_video.h"
@ -824,6 +825,8 @@ 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));
return HSendPacket(servernode, false, 0, sizeof (clientconfig_pak));
}
@ -3461,6 +3464,11 @@ void SV_ResetServer(void)
CV_RevertNetVars();
// Copy our unlocks to a place where net material can grab at/overwrite them safely.
// (permits all unlocks in dedicated)
for (i = 0; i < MAXUNLOCKABLES; i++)
netUnlocked[i] = (dedicated || gamedata->unlocked[i]);
DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n");
}
@ -3565,8 +3573,8 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
return;
}
node = (UINT8)READUINT8(*p);
newplayernum = (UINT8)READUINT8(*p);
node = READUINT8(*p);
newplayernum = READUINT8(*p);
CONS_Debug(DBG_NETPLAY, "addplayer: %d %d\n", node, newplayernum);
@ -3587,8 +3595,13 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
console = (UINT8)READUINT8(*p);
splitscreenplayer = (UINT8)READUINT8(*p);
console = READUINT8(*p);
splitscreenplayer = READUINT8(*p);
for (i = 0; i < MAXAVAILABILITY; i++)
{
newplayer->availabilities[i] = READUINT8(*p);
}
// the server is creating my player
if (node == mynode)
@ -3690,7 +3703,7 @@ static void Got_RemovePlayer(UINT8 **p, INT32 playernum)
static void Got_AddBot(UINT8 **p, INT32 playernum)
{
INT16 newplayernum;
UINT8 skinnum = 0;
UINT8 i, skinnum = 0;
UINT8 difficulty = DIFFICULTBOT;
if (playernum != serverplayer && !IsPlayerAdmin(playernum))
@ -3704,9 +3717,9 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
return;
}
newplayernum = (UINT8)READUINT8(*p);
skinnum = (UINT8)READUINT8(*p);
difficulty = (UINT8)READUINT8(*p);
newplayernum = READUINT8(*p);
skinnum = READUINT8(*p);
difficulty = READUINT8(*p);
CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum);
@ -3720,6 +3733,15 @@ 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];
}
}
players[newplayernum].splitscreenindex = 0;
players[newplayernum].bot = true;
players[newplayernum].botvars.difficulty = difficulty;
@ -3737,10 +3759,10 @@ static void Got_AddBot(UINT8 **p, INT32 playernum)
LUA_HookInt(newplayernum, HOOK(PlayerJoin));
}
static boolean SV_AddWaitingPlayers(SINT8 node, const char *name, const char *name2, const char *name3, const char *name4)
static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities, const char *name, const char *name2, const char *name3, const char *name4)
{
INT32 n, newplayernum;
UINT8 buf[4 + MAXPLAYERNAME];
INT32 n, newplayernum, i;
UINT8 buf[4 + MAXPLAYERNAME + MAXAVAILABILITY];
UINT8 *buf_p = buf;
boolean newplayer = false;
@ -3823,6 +3845,11 @@ static boolean SV_AddWaitingPlayers(SINT8 node, const char *name, const char *na
WRITEUINT8(buf_p, nodetoplayer[node]); // consoleplayer
WRITEUINT8(buf_p, playerpernode[node]); // splitscreen num
for (i = 0; i < MAXAVAILABILITY; i++)
{
WRITEUINT8(buf_p, availabilities[i]);
}
playerpernode[node]++;
SendNetXCmd(XD_ADDPLAYER, buf, buf_p - buf);
@ -3886,9 +3913,10 @@ 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);
SINT8 node = 0;
for (; node < MAXNETNODES; node++)
result |= SV_AddWaitingPlayers(node, cv_playername[0].zstring, cv_playername[1].zstring, cv_playername[2].zstring, cv_playername[3].zstring);
result |= SV_AddWaitingPlayers(node, availabilitiesbuffer, cv_playername[0].zstring, cv_playername[1].zstring, cv_playername[2].zstring, cv_playername[3].zstring);
}
return result;
#endif
@ -3971,6 +3999,7 @@ static void HandleConnect(SINT8 node)
{
char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME + 1];
INT32 i;
UINT8 availabilitiesbuffer[MAXAVAILABILITY];
// Sal: Dedicated mode is INCREDIBLY hacked together.
// If a server filled out, then it'd overwrite the host and turn everyone into weird husks.....
@ -4077,6 +4106,8 @@ static void HandleConnect(SINT8 node)
}
}
memcpy(availabilitiesbuffer, netbuffer->u.clientcfg.availabilities, sizeof(availabilitiesbuffer));
// client authorised to join
nodewaiting[node] = (UINT8)(netbuffer->u.clientcfg.localplayers - playerpernode[node]);
if (!nodeingame[node])
@ -4111,7 +4142,8 @@ static void HandleConnect(SINT8 node)
SV_SendSaveGame(node, false); // send a complete game state
DEBFILE("send savegame\n");
}
SV_AddWaitingPlayers(node, names[0], names[1], names[2], names[3]);
SV_AddWaitingPlayers(node, availabilitiesbuffer, names[0], names[1], names[2], names[3]);
joindelay += cv_joindelay.value * TICRATE;
player_joining = true;
}

View file

@ -246,6 +246,7 @@ struct clientconfig_pak
UINT8 localplayers; // number of splitscreen players
UINT8 mode;
char names[MAXSPLITSCREENPLAYERS][MAXPLAYERNAME];
UINT8 availabilities[MAXAVAILABILITY];
} ATTRPACK;
#define SV_SPEEDMASK 0x03 // used to send kartspeed

View file

@ -953,11 +953,6 @@ void D_StartTitle(void)
for (i = 0; i < MAXPLAYERS; i++)
CL_ClearPlayer(i);
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
players[g_localplayers[i]].availabilities = R_GetSkinAvailabilities();
}
splitscreen = 0;
SplitScreen_OnChange();
@ -1151,6 +1146,10 @@ static void IdentifyVersion(void)
#ifdef USE_PATCH_FILE
D_AddFile(startupiwads, va(pandf,srb2waddir,PATCHNAME));
#endif
#define UNLOCKTESTING
#if defined(DEVELOP) && defined(UNLOCKTESTING)
D_AddFile(startupiwads, va(pandf,srb2waddir,"unlocks.pk3"));
#endif
////
#undef TEXTURESNAME
#undef MAPSNAME
@ -1385,6 +1384,8 @@ void D_SRB2Main(void)
Z_Init();
CON_SetLoadingProgress(LOADED_ZINIT);
M_NewGameDataStruct();
// Do this up here so that WADs loaded through the command line can use ExecCfg
COM_Init();
@ -1456,6 +1457,9 @@ void D_SRB2Main(void)
#ifdef USE_PATCH_FILE
mainwads++; // scripts.pk3
#endif
#ifdef UNLOCKTESTING
mainwads++; // unlocks.pk3
#endif
#endif //ifndef DEVELOP

View file

@ -467,6 +467,7 @@ consvar_t cv_autobalance = CVAR_INIT ("autobalance", "Off", CV_SAVE|CV_NETVAR|CV
consvar_t cv_teamscramble = CVAR_INIT ("teamscramble", "Off", CV_SAVE|CV_NETVAR|CV_CALL|CV_NOINIT, teamscramble_cons_t, TeamScramble_OnChange);
consvar_t cv_scrambleonchange = CVAR_INIT ("scrambleonchange", "Off", CV_SAVE|CV_NETVAR, teamscramble_cons_t, NULL);
consvar_t cv_alttitle = CVAR_INIT ("alttitle", "Off", CV_CALL|CV_NOSHOWHELP|CV_NOINIT|CV_SAVE, CV_OnOff, AltTitle_OnChange);
consvar_t cv_itemfinder = CVAR_INIT ("itemfinder", "Off", CV_CALL|CV_NOSHOWHELP, CV_OnOff, ItemFinder_OnChange);
// Scoring type options
@ -939,6 +940,7 @@ void D_RegisterClientCommands(void)
CV_RegisterVar(&cv_mindelay);
// HUD
CV_RegisterVar(&cv_alttitle);
CV_RegisterVar(&cv_itemfinder);
CV_RegisterVar(&cv_showinputjoy);
@ -1408,26 +1410,29 @@ boolean CanChangeSkinWhilePlaying(INT32 playernum)
static void ForceAllSkins(INT32 forcedskin)
{
INT32 i, j;
INT32 i;
if (demo.playback)
return; // DXD_SKIN should handle all changes for us
for (i = 0; i < MAXPLAYERS; ++i)
{
if (!playeringame[i])
continue;
SetPlayerSkinByNum(i, forcedskin);
}
// If it's me (or my brother (or my sister (or my trusty pet dog))), set appropriate skin value in cv_skin
if (dedicated) // But don't do this for dedicated servers, of course.
if (dedicated)
return;
// If it's me (or my brother (or my sister (or my trusty pet dog))), set appropriate skin value in cv_skin
for (i = 0; i <= splitscreen; i++)
{
if (!playeringame[g_localplayers[i]])
continue;
for (j = 0; j <= splitscreen; j++)
{
if (i == g_localplayers[j])
{
CV_StealthSet(&cv_skin[j], skins[forcedskin].name);
break;
}
}
CV_StealthSet(&cv_skin[i], skins[forcedskin].name);
}
}
@ -1478,6 +1483,8 @@ static void SendNameAndColor(UINT8 n)
char buf[MAXPLAYERNAME+12];
char *p;
UINT16 i = 0;
if (splitscreen < n)
return; // can happen if skin4/color4/name4 changed
@ -1495,9 +1502,10 @@ static void SendNameAndColor(UINT8 n)
CV_StealthSetValue(&cv_playercolor[n], skins[player->skin].prefcolor);
else if (skincolors[atoi(cv_playercolor[n].defaultvalue)].accessible)
CV_StealthSet(&cv_playercolor[n], cv_playercolor[n].defaultvalue);
else {
UINT16 i = 0;
while (i<numskincolors && !skincolors[i].accessible) i++;
else
{
while (i < numskincolors && !skincolors[i].accessible)
i++;
CV_StealthSetValue(&cv_playercolor[n], (i != numskincolors) ? i : SKINCOLOR_BLUE);
}
}
@ -1514,8 +1522,6 @@ static void SendNameAndColor(UINT8 n)
&& cv_followercolor[n].value == player->followercolor)
return;
player->availabilities = R_GetSkinAvailabilities();
// We'll handle it later if we're not playing.
if (!Playing())
return;
@ -1532,7 +1538,8 @@ static void SendNameAndColor(UINT8 n)
K_KartResetPlayerColor(player);
if ((foundskin = R_SkinAvailable(cv_skin[n].string)) != -1 && R_SkinUsable(playernum, foundskin))
foundskin = R_SkinAvailable(cv_skin[n].string);
if (foundskin != -1 && R_SkinUsable(playernum, foundskin, false))
{
SetPlayerSkin(playernum, cv_skin[n].string);
CV_StealthSet(&cv_skin[n], skins[foundskin].name);
@ -1550,6 +1557,8 @@ static void SendNameAndColor(UINT8 n)
// Update follower for local games:
foundskin = K_FollowerAvailable(cv_follower[n].string);
if (!K_FollowerUsable(foundskin))
foundskin = -1;
CV_StealthSet(&cv_follower[n], (foundskin == -1) ? "None" : followers[foundskin].name);
cv_follower[n].value = foundskin;
K_SetFollowerByNum(playernum, foundskin);
@ -1577,14 +1586,14 @@ static void SendNameAndColor(UINT8 n)
// check if player has the skin loaded (cv_skin may have
// the name of a skin that was available in the previous game)
cv_skin[n].value = R_SkinAvailable(cv_skin[n].string);
if ((cv_skin[n].value < 0) || !R_SkinUsable(playernum, cv_skin[n].value))
if ((cv_skin[n].value < 0) || !R_SkinUsable(playernum, cv_skin[n].value, false))
{
CV_StealthSet(&cv_skin[n], DEFAULTSKIN);
cv_skin[n].value = 0;
}
cv_follower[n].value = K_FollowerAvailable(cv_follower[n].string);
if (cv_follower[n].value < 0)
if (cv_follower[n].value < 0 || !K_FollowerUsable(cv_follower[n].value))
{
CV_StealthSet(&cv_follower[n], "None");
cv_follower[n].value = -1;
@ -1592,7 +1601,6 @@ static void SendNameAndColor(UINT8 n)
// Finally write out the complete packet and send it off.
WRITESTRINGN(p, cv_playername[n].zstring, MAXPLAYERNAME);
WRITEUINT32(p, (UINT32)player->availabilities);
WRITEUINT16(p, (UINT16)cv_playercolor[n].value);
WRITEUINT8(p, (UINT8)cv_skin[n].value);
WRITEINT16(p, (INT16)cv_follower[n].value);
@ -1634,11 +1642,9 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
}
READSTRINGN(*cp, name, MAXPLAYERNAME);
p->availabilities = READUINT32(*cp);
color = READUINT16(*cp);
skin = READUINT8(*cp);
follower = READINT16(*cp);
//CONS_Printf("Recieved follower id %d\n", follower);
followercolor = READUINT16(*cp);
// set name
@ -1659,56 +1665,20 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
{
boolean kick = false;
// team colors
if (G_GametypeHasTeams())
{
if (p->ctfteam == 1 && p->skincolor != skincolor_redteam)
kick = true;
else if (p->ctfteam == 2 && p->skincolor != skincolor_blueteam)
kick = true;
}
// don't allow inaccessible colors
if (skincolors[p->skincolor].accessible == false)
kick = true;
// availabilities
for (i = 0; i < MAXSKINS; i++)
{
UINT32 playerhasunlocked = (p->availabilities & (1 << i));
boolean islocked = false;
UINT8 j;
for (j = 0; j < MAXUNLOCKABLES; j++)
{
if (unlockables[j].type != SECRET_SKIN)
continue;
if (unlockables[j].variable == i)
{
islocked = true;
break;
}
}
if (islocked == false && playerhasunlocked == true)
{
// hacked client that enabled every bit
kick = true;
break;
}
}
if (kick)
{
CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s (team: %d), color: %d)\n"), player_names[playernum], p->ctfteam, p->skincolor);
CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s, color: %d)\n"), player_names[playernum], p->skincolor);
SendKick(playernum, KICK_MSG_CON_FAIL);
return;
}
}
// set skin
if (cv_forceskin.value >= 0 && (netgame || multiplayer)) // Server wants everyone to use the same player
if (cv_forceskin.value >= 0 && K_CanChangeRules(true)) // Server wants everyone to use the same player
{
const INT32 forcedskin = cv_forceskin.value;
SetPlayerSkinByNum(playernum, forcedskin);
@ -2890,7 +2860,7 @@ static void Command_Map_f(void)
{
newencoremode = !newencoremode;
if (!M_SecretUnlocked(SECRET_ENCORE) && newencoremode == true && !usingcheats)
if (!M_SecretUnlocked(SECRET_ENCORE, false) && newencoremode == true && !usingcheats)
{
CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n"));
return;
@ -4876,12 +4846,25 @@ FUNCNORETURN static ATTRNORETURN void Command_Quit_f(void)
I_Quit();
}
void AltTitle_OnChange(void)
{
if (!cv_alttitle.value)
return; // it's fine.
if (!M_SecretUnlocked(SECRET_ALTTITLE, true))
{
CONS_Printf(M_GetText("You haven't earned this yet.\n"));
CV_StealthSetValue(&cv_itemfinder, 0);
return;
}
}
void ItemFinder_OnChange(void)
{
if (!cv_itemfinder.value)
return; // it's fine.
if (!M_SecretUnlocked(SECRET_ITEMFINDER))
if (!M_SecretUnlocked(SECRET_ITEMFINDER, true))
{
CONS_Printf(M_GetText("You haven't earned this yet.\n"));
CV_StealthSetValue(&cv_itemfinder, 0);
@ -5731,11 +5714,6 @@ void Command_ExitGame_f(void)
for (i = 0; i < MAXPLAYERS; i++)
CL_ClearPlayer(i);
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
players[g_localplayers[i]].availabilities = R_GetSkinAvailabilities();
}
splitscreen = 0;
SplitScreen_OnChange();

View file

@ -94,7 +94,7 @@ extern consvar_t cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector;
extern consvar_t cv_spbtest, cv_gptest, cv_reducevfx;
extern consvar_t cv_kartdebugwaypoints, cv_kartdebugbotpredict;
extern consvar_t cv_itemfinder;
extern consvar_t cv_alttitle, cv_itemfinder;
extern consvar_t cv_inttime, cv_advancemap;
extern consvar_t cv_overtime;
@ -238,6 +238,7 @@ boolean IsPlayerAdmin(INT32 playernum);
void SetAdminPlayer(INT32 playernum);
void ClearAdminPlayers(void);
void RemoveAdminPlayer(INT32 playernum);
void AltTitle_OnChange(void);
void ItemFinder_OnChange(void);
void D_SetPassword(const char *pw);

View file

@ -421,7 +421,7 @@ struct player_t
UINT16 skincolor;
INT32 skin;
UINT32 availabilities;
UINT8 availabilities[MAXAVAILABILITY];
UINT8 fakeskin; // ironman
UINT8 lastfakeskin;

View file

@ -131,11 +131,44 @@ static float searchfvalue(const char *s)
#endif
// These are for clearing all of various things
void clear_emblems(void)
{
INT32 i;
for (i = 0; i < MAXEMBLEMS; ++i)
{
Z_Free(emblemlocations[i].level);
emblemlocations[i].level = NULL;
Z_Free(emblemlocations[i].stringVar);
emblemlocations[i].stringVar = NULL;
}
memset(&emblemlocations, 0, sizeof(emblemlocations));
numemblems = 0;
}
void clear_unlockables(void)
{
INT32 i;
for (i = 0; i < MAXUNLOCKABLES; ++i)
{
Z_Free(unlockables[i].stringVar);
unlockables[i].stringVar = NULL;
Z_Free(unlockables[i].icon);
unlockables[i].icon = NULL;
}
memset(&unlockables, 0, sizeof(unlockables));
}
void clear_conditionsets(void)
{
UINT8 i;
for (i = 0; i < MAXCONDITIONSETS; ++i)
M_ClearConditionSet(i+1);
M_ClearConditionSet(i);
}
void clear_levels(void)
@ -1148,13 +1181,6 @@ void readlevelheader(MYFILE *f, char * name)
mapheaderinfo[num]->encorepal = (UINT16)i;
else if (fastcmp(word, "NUMLAPS"))
mapheaderinfo[num]->numlaps = (UINT8)i;
else if (fastcmp(word, "UNLOCKABLE"))
{
if (i >= 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something
mapheaderinfo[num]->unlockrequired = (SINT8)i - 1;
else
deh_warning("Level header %d: invalid unlockable number %d", num, i);
}
else if (fastcmp(word, "SKYBOXSCALE"))
mapheaderinfo[num]->skybox_scalex = mapheaderinfo[num]->skybox_scaley = mapheaderinfo[num]->skybox_scalez = (INT16)i;
else if (fastcmp(word, "SKYBOXSCALEX"))
@ -2098,14 +2124,6 @@ void reademblemdata(MYFILE *f, INT32 num)
word2 = tmp += 2;
value = atoi(word2); // used for numerical settings
// Up here to allow lowercase in hints
if (fastcmp(word, "HINT"))
{
while ((tmp = strchr(word2, '\\')))
*tmp = '\n';
deh_strlcpy(emblemlocations[num-1].hint, word2, sizeof (emblemlocations[num-1].hint), va("Emblem %d: hint", num));
continue;
}
strupr(word2);
if (fastcmp(word, "TYPE"))
@ -2124,6 +2142,7 @@ void reademblemdata(MYFILE *f, INT32 num)
else if (fastcmp(word, "MAPNAME"))
{
emblemlocations[num-1].level = Z_StrDup(word2);
emblemlocations[num-1].levelCache = NEXTMAP_INVALID;
}
else if (fastcmp(word, "SPRITE"))
{
@ -2140,7 +2159,14 @@ void reademblemdata(MYFILE *f, INT32 num)
else if (fastcmp(word, "COLOR"))
emblemlocations[num-1].color = get_number(word2);
else if (fastcmp(word, "VAR"))
{
Z_Free(emblemlocations[num-1].stringVar);
emblemlocations[num-1].stringVar = Z_StrDup(word2);
emblemlocations[num-1].var = get_number(word2);
}
else if (fastcmp(word, "FLAGS"))
emblemlocations[num-1].flags = get_number(word2);
else
deh_warning("Emblem %d: unknown word '%s'", num, word);
}
@ -2165,88 +2191,6 @@ void reademblemdata(MYFILE *f, INT32 num)
Z_Free(s);
}
void readextraemblemdata(MYFILE *f, INT32 num)
{
char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
char *word = s;
char *word2;
char *tmp;
INT32 value;
memset(&extraemblems[num-1], 0, sizeof(extraemblem_t));
do
{
if (myfgets(s, MAXLINELEN, f))
{
if (s[0] == '\n')
break;
// First remove trailing newline, if there is one
tmp = strchr(s, '\n');
if (tmp)
*tmp = '\0';
tmp = strchr(s, '#');
if (tmp)
*tmp = '\0';
if (s == tmp)
continue; // Skip comment lines, but don't break.
// Get the part before the " = "
tmp = strchr(s, '=');
if (tmp)
*(tmp-1) = '\0';
else
break;
strupr(word);
// Now get the part after
word2 = tmp += 2;
value = atoi(word2); // used for numerical settings
if (fastcmp(word, "NAME"))
deh_strlcpy(extraemblems[num-1].name, word2,
sizeof (extraemblems[num-1].name), va("Extra emblem %d: name", num));
else if (fastcmp(word, "OBJECTIVE"))
deh_strlcpy(extraemblems[num-1].description, word2,
sizeof (extraemblems[num-1].description), va("Extra emblem %d: objective", num));
else if (fastcmp(word, "CONDITIONSET"))
extraemblems[num-1].conditionset = (UINT8)value;
else if (fastcmp(word, "SHOWCONDITIONSET"))
extraemblems[num-1].showconditionset = (UINT8)value;
else
{
strupr(word2);
if (fastcmp(word, "SPRITE"))
{
if (word2[0] >= 'A' && word2[0] <= 'Z')
value = word2[0];
else
value += 'A'-1;
if (value < 'A' || value > 'Z')
deh_warning("Emblem %d: sprite must be from A - Z (1 - 26)", num);
else
extraemblems[num-1].sprite = (UINT8)value;
}
else if (fastcmp(word, "COLOR"))
extraemblems[num-1].color = get_number(word2);
else
deh_warning("Extra emblem %d: unknown word '%s'", num, word);
}
}
} while (!myfeof(f));
if (!extraemblems[num-1].sprite)
extraemblems[num-1].sprite = 'C';
if (!extraemblems[num-1].color)
extraemblems[num-1].color = SKINCOLOR_RED;
Z_Free(s);
}
void readunlockable(MYFILE *f, INT32 num)
{
char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
@ -2256,7 +2200,6 @@ void readunlockable(MYFILE *f, INT32 num)
INT32 i;
memset(&unlockables[num], 0, sizeof(unlockable_t));
unlockables[num].objective[0] = '/';
do
{
@ -2291,62 +2234,65 @@ void readunlockable(MYFILE *f, INT32 num)
if (fastcmp(word, "NAME"))
deh_strlcpy(unlockables[num].name, word2,
sizeof (unlockables[num].name), va("Unlockable %d: name", num));
else if (fastcmp(word, "OBJECTIVE"))
deh_strlcpy(unlockables[num].objective, word2,
sizeof (unlockables[num].objective), va("Unlockable %d: objective", num));
sizeof (unlockables[num].name), va("Unlockable %d: name", num+1));
else
{
strupr(word2);
if (fastcmp(word, "CONDITIONSET"))
unlockables[num].conditionset = (UINT8)i;
else if (fastcmp(word, "SHOWCONDITIONSET"))
unlockables[num].showconditionset = (UINT8)i;
else if (fastcmp(word, "NOCECHO"))
unlockables[num].nocecho = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
else if (fastcmp(word, "NOCHECKLIST"))
unlockables[num].nochecklist = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
else if (fastcmp(word, "MAJORUNLOCK"))
unlockables[num].majorunlock = (UINT8)(i || word2[0] == 'T' || word2[0] == 'Y');
else if (fastcmp(word, "TYPE"))
{
if (fastcmp(word2, "NONE"))
unlockables[num].type = SECRET_NONE;
else if (fastcmp(word2, "HEADER"))
unlockables[num].type = SECRET_HEADER;
else if (fastcmp(word2, "EXTRAMEDAL"))
unlockables[num].type = SECRET_EXTRAMEDAL;
else if (fastcmp(word2, "CUP"))
unlockables[num].type = SECRET_CUP;
else if (fastcmp(word2, "MAP"))
unlockables[num].type = SECRET_MAP;
else if (fastcmp(word2, "SKIN"))
unlockables[num].type = SECRET_SKIN;
else if (fastcmp(word2, "WARP"))
unlockables[num].type = SECRET_WARP;
else if (fastcmp(word2, "LEVELSELECT"))
unlockables[num].type = SECRET_LEVELSELECT;
else if (fastcmp(word2, "FOLLOWER"))
unlockables[num].type = SECRET_FOLLOWER;
else if (fastcmp(word2, "HARDSPEED"))
unlockables[num].type = SECRET_HARDSPEED;
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, "SOUNDTEST"))
unlockables[num].type = SECRET_SOUNDTEST;
else if (fastcmp(word2, "CREDITS"))
unlockables[num].type = SECRET_CREDITS;
else if (fastcmp(word2, "ALTTITLE"))
unlockables[num].type = SECRET_ALTTITLE;
else if (fastcmp(word2, "ITEMFINDER"))
unlockables[num].type = SECRET_ITEMFINDER;
else if (fastcmp(word2, "EMBLEMHINTS"))
unlockables[num].type = SECRET_EMBLEMHINTS;
else if (fastcmp(word2, "ENCORE"))
unlockables[num].type = SECRET_ENCORE;
else if (fastcmp(word2, "HARDSPEED"))
unlockables[num].type = SECRET_HARDSPEED;
else if (fastcmp(word2, "HELLATTACK"))
unlockables[num].type = SECRET_HELLATTACK;
else if (fastcmp(word2, "PANDORA"))
unlockables[num].type = SECRET_PANDORA;
else
unlockables[num].type = (INT16)i;
unlockables[num].stringVarCache = -1;
}
else if (fastcmp(word, "VAR"))
{
// TODO: different field for level name string
Z_Free(unlockables[num].stringVar);
unlockables[num].stringVar = Z_StrDup(word2);
unlockables[num].stringVarCache = -1;
unlockables[num].variable = (INT16)i;
}
else if (fastcmp(word, "ICON"))
{
Z_Free(unlockables[num].icon);
unlockables[num].icon = Z_StrDup(word2);
}
else if (fastcmp(word, "COLOR"))
{
unlockables[num].color = get_number(word2);
}
else
deh_warning("Unlockable %d: unknown word '%s'", num+1, word);
}
@ -2383,7 +2329,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
if (!params[0])
{
deh_warning("condition line is empty for condition ID %d", id);
deh_warning("condition line is empty for condition ID %d", id+1);
return;
}
@ -2401,9 +2347,15 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
re = atoi(params[1]);
x1 = atoi(params[2]);
if (re < PWRLVRECORD_MIN || re > PWRLVRECORD_MAX)
{
deh_warning("Power level requirement %d out of range (%d - %d) for condition ID %d", re, PWRLVRECORD_MIN, PWRLVRECORD_MAX, id+1);
return;
}
if (x1 < 0 || x1 >= PWRLV_NUMTYPES)
{
deh_warning("Power level type %d out of range (0 - %d) for condition ID %d", x1, PWRLV_NUMTYPES-1, id);
deh_warning("Power level type %d out of range (0 - %d) for condition ID %d", x1, PWRLV_NUMTYPES-1, id+1);
return;
}
}
@ -2427,7 +2379,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
if (re >= nummapheaders)
{
deh_warning("Invalid level %s for condition ID %d", params[1], id);
deh_warning("Invalid level %s for condition ID %d", params[1], id+1);
return;
}
}
@ -2440,7 +2392,7 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
if (x1 >= nummapheaders)
{
deh_warning("Invalid level %s for condition ID %d", params[1], id);
deh_warning("Invalid level %s for condition ID %d", params[1], id+1);
return;
}
}
@ -2453,14 +2405,14 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
// 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);
deh_warning("Trigger ID %d out of range (0 - 31) for condition ID %d", re, id+1);
return;
}
}
else if (fastcmp(params[0], "TOTALEMBLEMS"))
else if (fastcmp(params[0], "TOTALMEDALS"))
{
PARAMCHECK(1);
ty = UC_TOTALEMBLEMS;
ty = UC_TOTALMEDALS;
re = atoi(params[1]);
}
else if (fastcmp(params[0], "EMBLEM"))
@ -2471,19 +2423,19 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
if (re <= 0 || re > MAXEMBLEMS)
{
deh_warning("Emblem %d out of range (1 - %d) for condition ID %d", re, MAXEMBLEMS, id);
deh_warning("Emblem %d out of range (1 - %d) for condition ID %d", re, MAXEMBLEMS, id+1);
return;
}
}
else if (fastcmp(params[0], "EXTRAEMBLEM"))
else if (fastcmp(params[0], "UNLOCKABLE"))
{
PARAMCHECK(1);
ty = UC_EXTRAEMBLEM;
ty = UC_UNLOCKABLE;
re = atoi(params[1]);
if (re <= 0 || re > MAXEXTRAEMBLEMS)
if (re <= 0 || re > MAXUNLOCKABLES)
{
deh_warning("Extra emblem %d out of range (1 - %d) for condition ID %d", re, MAXEXTRAEMBLEMS, id);
deh_warning("Unlockable %d out of range (1 - %d) for condition ID %d", re, MAXUNLOCKABLES, id+1);
return;
}
}
@ -2495,13 +2447,13 @@ static void readcondition(UINT8 set, UINT32 id, char *word2)
if (re <= 0 || re > MAXCONDITIONSETS)
{
deh_warning("Condition set %d out of range (1 - %d) for condition ID %d", re, MAXCONDITIONSETS, id);
deh_warning("Condition set %d out of range (1 - %d) for condition ID %d", re, MAXCONDITIONSETS, id+1);
return;
}
}
else
{
deh_warning("Invalid condition name %s for condition ID %d", params[0], id);
deh_warning("Invalid condition name %s for condition ID %d", params[0], id+1);
return;
}
@ -2554,13 +2506,13 @@ void readconditionset(MYFILE *f, UINT8 setnum)
id = (UINT8)atoi(word + 9);
if (id == 0)
{
deh_warning("Condition set %d: unknown word '%s'", setnum, word);
deh_warning("Condition set %d: unknown word '%s'", setnum+1, word);
continue;
}
else if (previd > id)
{
// out of order conditions can cause problems, so enforce proper order
deh_warning("Condition set %d: conditions are out of order, ignoring this line", setnum);
deh_warning("Condition set %d: conditions are out of order, ignoring this line", setnum+1);
continue;
}
previd = id;
@ -2575,13 +2527,14 @@ void readconditionset(MYFILE *f, UINT8 setnum)
Z_Free(s);
}
void readmaincfg(MYFILE *f)
void readmaincfg(MYFILE *f, boolean mainfile)
{
char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
char *word = s;
char *word2;
char *tmp;
INT32 value;
boolean doClearLevels = false;
do
{
@ -2615,7 +2568,54 @@ void readmaincfg(MYFILE *f)
value = atoi(word2); // used for numerical settings
if (fastcmp(word, "EXECCFG"))
if (fastcmp(word, "GAMEDATA"))
{
size_t filenamelen;
// Check the data filename so that mods
// can't write arbitrary files.
if (!GoodDataFileName(word2))
I_Error("Maincfg: bad data file name '%s'\n", word2);
G_SaveGameData();
strlcpy(gamedatafilename, word2, sizeof (gamedatafilename));
strlwr(gamedatafilename);
savemoddata = true;
majormods = false;
// Also save a time attack folder
filenamelen = strlen(gamedatafilename)-4; // Strip off the extension
filenamelen = min(filenamelen, sizeof (timeattackfolder));
strncpy(timeattackfolder, gamedatafilename, filenamelen);
timeattackfolder[min(filenamelen, sizeof (timeattackfolder) - 1)] = '\0';
strcpy(savegamename, timeattackfolder);
strlcat(savegamename, "%u.ssg", sizeof(savegamename));
// can't use sprintf since there is %u in savegamename
strcatbf(savegamename, srb2home, PATHSEP);
strcpy(liveeventbackup, va("live%s.bkp", timeattackfolder));
strcatbf(liveeventbackup, srb2home, PATHSEP);
refreshdirmenu |= REFRESHDIR_GAMEDATA;
gamedataadded = true;
titlechanged = true;
clear_unlockables();
clear_conditionsets();
clear_emblems();
//clear_levels();
doClearLevels = true;
}
else if (!mainfile && !gamedataadded)
{
deh_warning("You must define a custom gamedata to use \"%s\"", word);
}
else if (fastcmp(word, "CLEARLEVELS"))
{
doClearLevels = (UINT8)(value == 0 || word2[0] == 'F' || word2[0] == 'N');
}
else if (fastcmp(word, "EXECCFG"))
{
if (strchr(word2, '.'))
COM_BufAddText(va("exec %s\n", word2));
@ -2800,40 +2800,6 @@ void readmaincfg(MYFILE *f)
{
maxXtraLife = (UINT8)get_number(word2);
}
else if (fastcmp(word, "GAMEDATA"))
{
size_t filenamelen;
// Check the data filename so that mods
// can't write arbitrary files.
if (!GoodDataFileName(word2))
I_Error("Maincfg: bad data file name '%s'\n", word2);
G_SaveGameData();
strlcpy(gamedatafilename, word2, sizeof (gamedatafilename));
strlwr(gamedatafilename);
savemoddata = true;
majormods = false;
// Also save a time attack folder
filenamelen = strlen(gamedatafilename)-4; // Strip off the extension
filenamelen = min(filenamelen, sizeof (timeattackfolder));
strncpy(timeattackfolder, gamedatafilename, filenamelen);
timeattackfolder[min(filenamelen, sizeof (timeattackfolder) - 1)] = '\0';
strcpy(savegamename, timeattackfolder);
strlcat(savegamename, "%u.ssg", sizeof(savegamename));
// can't use sprintf since there is %u in savegamename
strcatbf(savegamename, srb2home, PATHSEP);
strcpy(liveeventbackup, va("live%s.bkp", timeattackfolder));
strcatbf(liveeventbackup, srb2home, PATHSEP);
refreshdirmenu |= REFRESHDIR_GAMEDATA;
gamedataadded = true;
titlechanged = true;
}
else if (fastcmp(word, "RESETDATA"))
{
P_ResetData(value);
@ -2858,6 +2824,11 @@ void readmaincfg(MYFILE *f)
}
} while (!myfeof(f));
if (doClearLevels)
{
clear_levels();
}
Z_Free(s);
}
@ -3154,13 +3125,6 @@ void readcupheader(MYFILE *f, cupheader_t *cup)
else
deh_warning("%s Cup: invalid emerald number %d", cup->name, i);
}
else if (fastcmp(word, "UNLOCKABLE"))
{
if (i >= 0 && i <= MAXUNLOCKABLES) // 0 for no unlock required, anything else requires something
cup->unlockrequired = (SINT8)i - 1;
else
deh_warning("%s Cup: invalid unlockable number %d", cup->name, i);
}
else
deh_warning("%s Cup: unknown word '%s'", cup->name, word);
}

View file

@ -57,10 +57,9 @@ sfxenum_t get_sfx(const char *word);
skincolornum_t get_skincolor(const char *word);
void readwipes(MYFILE *f);
void readmaincfg(MYFILE *f);
void readmaincfg(MYFILE *f, boolean mainfile);
void readconditionset(MYFILE *f, UINT8 setnum);
void readunlockable(MYFILE *f, INT32 num);
void readextraemblemdata(MYFILE *f, INT32 num);
void reademblemdata(MYFILE *f, INT32 num);
void readsound(MYFILE *f, INT32 num);
void readframe(MYFILE *f, INT32 num);
@ -77,6 +76,8 @@ void readlight(MYFILE *f, INT32 num);
void readskincolor(MYFILE *f, INT32 num);
void readthing(MYFILE *f, INT32 num);
void readfreeslots(MYFILE *f);
void clear_emblems(void);
void clear_unlockables(void);
void clear_levels(void);
void clear_conditionsets(void);

View file

@ -6348,7 +6348,8 @@ struct int_const_s const INT_CONST[] = {
{"SF_X2AWAYSOUND",SF_X2AWAYSOUND},
// Global emblem var flags
// none in kart yet
{"GE_NOTMEDAL", GE_NOTMEDAL},
{"GE_TIMED", GE_TIMED},
// Map emblem var flags
{"ME_ENCORE",ME_ENCORE},

View file

@ -217,7 +217,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
else if (fastcmp(word, "MAINCFG"))
{
G_SetGameModified(multiplayer, true);
readmaincfg(f);
readmaincfg(f, mainfile);
continue;
}
else if (fastcmp(word, "WIPES"))
@ -278,32 +278,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
}
continue;
}
else if (fastcmp(word, "EXTRAEMBLEM"))
{
if (!mainfile && !gamedataadded)
{
deh_warning("You must define a custom gamedata to use \"%s\"", word);
ignorelines(f);
}
else
{
if (!word2)
i = numextraemblems + 1;
if (i > 0 && i <= MAXEXTRAEMBLEMS)
{
if (numextraemblems < i)
numextraemblems = i;
readextraemblemdata(f, i);
}
else
{
deh_warning("Extra emblem number %d out of range (1 - %d)", i, MAXEXTRAEMBLEMS);
ignorelines(f);
}
}
continue;
}
if (word2)
{
if (fastcmp(word, "THING") || fastcmp(word, "MOBJ") || fastcmp(word, "OBJECT"))
@ -479,7 +453,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
ignorelines(f);
}
else if (i > 0 && i <= MAXCONDITIONSETS)
readconditionset(f, (UINT8)i);
readconditionset(f, (UINT8)(i-1));
else
{
deh_warning("Condition set number %d out of range (1 - %d)", i, MAXCONDITIONSETS);
@ -512,7 +486,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
{
cup = Z_Calloc(sizeof (cupheader_t), PU_STATIC, NULL);
cup->id = numkartcupheaders;
cup->unlockrequired = -1;
deh_strlcpy(cup->name, word2,
sizeof(cup->name), va("Cup header %s: name", word2));
if (prev != NULL)
@ -569,41 +542,6 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
{
deh_warning("Patch is only compatible with base SRB2.");
}
// Clear all data in certain locations (mostly for unlocks)
// Unless you REALLY want to piss people off,
// define a custom gamedata /before/ doing this!!
// (then again, modifiedgame will prevent game data saving anyway)
else if (fastcmp(word, "CLEAR"))
{
boolean clearall = (fastcmp(word2, "ALL"));
if (!mainfile && !gamedataadded)
{
deh_warning("You must define a custom gamedata to use \"%s\"", word);
continue;
}
if (clearall || fastcmp(word2, "UNLOCKABLES"))
memset(&unlockables, 0, sizeof(unlockables));
if (clearall || fastcmp(word2, "EMBLEMS"))
{
memset(&emblemlocations, 0, sizeof(emblemlocations));
numemblems = 0;
}
if (clearall || fastcmp(word2, "EXTRAEMBLEMS"))
{
memset(&extraemblems, 0, sizeof(extraemblems));
numextraemblems = 0;
}
if (clearall || fastcmp(word2, "CONDITIONSETS"))
clear_conditionsets();
if (clearall || fastcmp(word2, "LEVELS"))
clear_levels();
}
else
deh_warning("Unknown word: %s", word);
}

View file

@ -540,7 +540,9 @@ void DRPC_UpdatePresence(void)
else
{
// Map name on tool tip
snprintf(mapname, 48, "Map: %s", G_BuildMapTitle(gamemap));
char *title = G_BuildMapTitle(gamemap);
snprintf(mapname, 48, "Map: %s", title);
Z_Free(title);
discordPresence.largeImageText = mapname;
}

View file

@ -201,7 +201,8 @@ extern char logfilename[1024];
#define MAXGAMEPADS (MAXSPLITSCREENPLAYERS * 2) // Number of gamepads we'll be allowing
#define MAXSKINS UINT8_MAX
#define SKINNAMESIZE 16 // Moved from r_skins.h as including that particular header causes issues later down the line.
#define SKINNAMESIZE 16
#define MAXAVAILABILITY ((MAXSKINS + 7)/8)
#define COLORRAMPSIZE 16
#define MAXCOLORNAME 32

View file

@ -186,8 +186,6 @@ extern INT32 postimgparam[MAXSPLITSCREENPLAYERS];
extern INT32 viewwindowx, viewwindowy;
extern INT32 viewwidth, scaledviewwidth;
extern boolean gamedataloaded;
// Player taking events, and displaying.
extern INT32 consoleplayer;
extern INT32 displayplayers[MAXSPLITSCREENPLAYERS];
@ -357,7 +355,6 @@ 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)
SINT8 unlockrequired; ///< An unlockable is required to select this cup. -1 for no unlocking required.
cupheader_t *next; ///< Next cup in linked list
};
@ -393,7 +390,6 @@ struct mapheader_t
// Selection metadata
char keywords[33]; ///< Keywords separated by space to search for. 32 characters.
SINT8 unlockrequired; ///< Is an unlockable required to play this level? -1 if no.
UINT8 levelselect; ///< Is this map available in the level select? If so, which map list is it available in?
UINT16 menuflags; ///< LF2_flags: options that affect record attack menus
@ -540,9 +536,6 @@ struct tolinfo_t
extern tolinfo_t TYPEOFLEVEL[NUMTOLNAMES];
extern UINT32 lastcustomtol;
extern tic_t totalplaytime;
extern UINT32 matchesplayed;
extern UINT8 stagefailed;
// Emeralds stored as bits to throw savegame hackers off.
@ -580,10 +573,6 @@ extern INT32 luabanks[NUM_LUABANKS];
extern INT32 nummaprings; //keep track of spawned rings/coins
extern UINT32 token; ///< Number of tokens collected in a level
extern UINT32 tokenlist; ///< List of tokens collected
extern boolean gottoken; ///< Did you get a token? Used for end of act
extern INT32 tokenbits; ///< Used for setting token bits
extern UINT32 bluescore; ///< Blue Team Scores
extern UINT32 redscore; ///< Red Team Scores
@ -684,8 +673,6 @@ extern INT16 votelevels[4][2];
extern SINT8 votes[MAXPLAYERS];
extern SINT8 pickedvote;
extern UINT32 timesBeaten; // # of times the game has been beaten.
// ===========================
// Internal parameters, fixed.
// ===========================

View file

@ -866,7 +866,7 @@ boolean F_CreditResponder(event_t *event)
return false;
}
/*if (!(timesBeaten) && !(netgame || multiplayer) && !cht_debug)
/*if (!(gamedata->timesBeaten) && !(netgame || multiplayer) && !cht_debug)
return false;*/
if (key != KEY_ESCAPE && key != KEY_ENTER && key != KEY_BACKSPACE)
@ -1024,31 +1024,6 @@ void F_GameEvaluationDrawer(void)
V_DrawCreditString((BASEVIDWIDTH - V_CreditStringWidth(endingtext))<<(FRACBITS-1), (BASEVIDHEIGHT-100)<<(FRACBITS-1), 0, endingtext);
#if 0 // the following looks like hot garbage the more unlockables we add, and we now have a lot of unlockables
if (finalecount >= 5*TICRATE)
{
V_DrawString(8, 16, V_YELLOWMAP, "Unlocked:");
if (!usedCheats)
{
INT32 startcoord = 32;
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (unlockables[i].conditionset && unlockables[i].conditionset < MAXCONDITIONSETS
&& unlockables[i].type && !unlockables[i].nocecho)
{
if (unlockables[i].unlocked)
V_DrawString(8, startcoord, 0, unlockables[i].name);
startcoord += 8;
}
}
}
else
V_DrawString(8, 96, V_YELLOWMAP, "Cheated games\ncan't unlock\nextras!");
}
#endif
if (marathonmode)
{
const char *rtatext, *cuttext;
@ -1101,11 +1076,9 @@ void F_GameEvaluationTicker(void)
{
if (!usedCheats)
{
++timesBeaten;
if (M_UpdateUnlockablesAndExtraEmblems())
S_StartSound(NULL, sfx_s3k68);
++gamedata->timesBeaten;
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
}
else
@ -1611,7 +1584,7 @@ void F_EndingDrawer(void)
//colset(linkmap, 164, 165, 169); -- the ideal purple colour to represent a clicked in-game link, but not worth it just for a soundtest-controlled secret
V_DrawCenteredString(BASEVIDWIDTH/2, 8, V_ALLOWLOWERCASE|(trans<<V_ALPHASHIFT), str);
V_DrawCharacter(32, BASEVIDHEIGHT-16, '>'|(trans<<V_ALPHASHIFT), false);
V_DrawString(40, ((finalecount == STOPPINGPOINT-(20+TICRATE)) ? 1 : 0)+BASEVIDHEIGHT-16, ((timesBeaten || finalecount >= STOPPINGPOINT-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<<V_ALPHASHIFT), " [S] ===>");
V_DrawString(40, ((finalecount == STOPPINGPOINT-(20+TICRATE)) ? 1 : 0)+BASEVIDHEIGHT-16, ((gamedata->timesBeaten || finalecount >= STOPPINGPOINT-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<<V_ALPHASHIFT), " [S] ===>");
}
if (finalecount > STOPPINGPOINT-(20+(2*TICRATE)))
@ -1804,7 +1777,14 @@ static void F_CacheTitleScreen(void)
break; // idk do we still want this?
case TTMODE_RINGRACERS:
kts_bumper = W_CachePatchName("KTSBUMPR1", PU_PATCH_LOWPRIORITY);
if (!M_SecretUnlocked(SECRET_ALTTITLE, true))
{
CV_StealthSetValue(&cv_alttitle, 0);
}
kts_bumper = W_CachePatchName(
(cv_alttitle.value ? "KTSJUMPR1" : "KTSBUMPR1"),
PU_PATCH_LOWPRIORITY);
kts_eggman = W_CachePatchName("KTSEGG01", PU_PATCH_LOWPRIORITY);
kts_tails = W_CachePatchName("KTSTAL01", PU_PATCH_LOWPRIORITY);
kts_tails_tails = W_CachePatchName("KTSTAL02", PU_PATCH_LOWPRIORITY);

View file

@ -212,47 +212,6 @@ void G_LoadMetal(UINT8 **buffer)
metal_p = metalbuffer + READUINT32(*buffer);
}
// Finds a skin with the closest stats if the expected skin doesn't exist.
static INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight, UINT32 flags)
{
INT32 i, closest_skin = 0;
UINT8 closest_stats, stat_diff;
boolean doflagcheck = true;
UINT32 flagcheck = flags;
flaglessretry:
closest_stats = UINT8_MAX;
for (i = 0; i < numskins; i++)
{
stat_diff = abs(skins[i].kartspeed - kartspeed) + abs(skins[i].kartweight - kartweight);
if (doflagcheck && (skins[i].flags & flagcheck) != flagcheck)
{
continue;
}
if (stat_diff < closest_stats)
{
closest_stats = stat_diff;
closest_skin = i;
}
}
if (stat_diff && (doflagcheck || closest_stats == UINT8_MAX))
{
// Just grab *any* SF_IRONMAN if we don't get it on the first pass.
if ((flagcheck & SF_IRONMAN) && (flagcheck != SF_IRONMAN))
{
flagcheck = SF_IRONMAN;
}
doflagcheck = false;
goto flaglessretry;
}
return closest_skin;
}
void G_ReadDemoExtraData(void)
{
INT32 p, extradata, i;
@ -276,10 +235,8 @@ void G_ReadDemoExtraData(void)
{
extradata = READUINT8(demo_p);
if (extradata & DXD_PLAYSTATE)
if (extradata & DXD_JOINDATA)
{
i = READUINT8(demo_p);
if (!playeringame[p])
{
CL_ClearPlayer(p);
@ -288,14 +245,22 @@ void G_ReadDemoExtraData(void)
players[p].spectator = true;
}
if ((players[p].bot = !!(i & DXD_PST_ISBOT)))
for (i = 0; i < MAXAVAILABILITY; i++)
{
players[p].availabilities[i] = READUINT8(demo_p);
}
players[p].bot = !!(READUINT8(demo_p));
if (players[p].bot)
{
players[p].botvars.difficulty = READUINT8(demo_p);
players[p].botvars.diffincrease = READUINT8(demo_p); // needed to avoid having to duplicate logic
players[p].botvars.rival = (boolean)READUINT8(demo_p);
i &= ~DXD_PST_ISBOT;
}
}
if (extradata & DXD_PLAYSTATE)
{
i = READUINT8(demo_p);
switch (i) {
case DXD_PST_PLAYING:
@ -334,14 +299,6 @@ void G_ReadDemoExtraData(void)
K_CheckBumpers();
P_CheckRacers();
}
if (extradata & DXD_RESPAWN)
{
if (players[p].mo)
{
// Is this how this should work..?
K_DoIngameRespawn(&players[p]);
}
}
if (extradata & DXD_SKIN)
{
UINT8 skinid;
@ -397,6 +354,14 @@ void G_ReadDemoExtraData(void)
}
}
}
if (extradata & DXD_RESPAWN)
{
if (players[p].mo)
{
// Is this how this should work..?
K_DoIngameRespawn(&players[p]);
}
}
if (extradata & DXD_WEAPONPREF)
{
WeaponPref_Parse(&demo_p, p);
@ -454,6 +419,21 @@ void G_WriteDemoExtraData(void)
WRITEUINT8(demo_p, i);
WRITEUINT8(demo_p, demo_extradata[i]);
if (demo_extradata[i] & DXD_JOINDATA)
{
for (j = 0; j < MAXAVAILABILITY; j++)
{
WRITEUINT8(demo_p, players[i].availabilities[i]);
}
WRITEUINT8(demo_p, (UINT8)players[i].bot);
if (players[i].bot)
{
WRITEUINT8(demo_p, players[i].botvars.difficulty);
WRITEUINT8(demo_p, players[i].botvars.diffincrease); // needed to avoid having to duplicate logic
WRITEUINT8(demo_p, (UINT8)players[i].botvars.rival);
}
}
if (demo_extradata[i] & DXD_PLAYSTATE)
{
UINT8 pst = DXD_PST_PLAYING;
@ -472,19 +452,7 @@ void G_WriteDemoExtraData(void)
pst = DXD_PST_SPECTATING;
}
if (players[i].bot)
{
pst |= DXD_PST_ISBOT;
}
WRITEUINT8(demo_p, pst);
if (pst & DXD_PST_ISBOT)
{
WRITEUINT8(demo_p, players[i].botvars.difficulty);
WRITEUINT8(demo_p, players[i].botvars.diffincrease); // needed to avoid having to duplicate logic
WRITEUINT8(demo_p, (UINT8)players[i].botvars.rival);
}
}
//if (demo_extradata[i] & DXD_RESPAWN) has no extra data
if (demo_extradata[i] & DXD_SKIN)
@ -1218,8 +1186,14 @@ void G_GhostTicker(void)
if (ziptic == 0) // Only support player 0 info for now
{
ziptic = READUINT8(g->p);
if (ziptic & DXD_JOINDATA)
{
g->p += MAXAVAILABILITY;
if (READUINT8(g->p) != 0)
I_Error("Ghost is not a record attack ghost (bot JOINDATA)");
}
if (ziptic & DXD_PLAYSTATE && READUINT8(g->p) != DXD_PST_PLAYING)
I_Error("Ghost is not a record attack ghost PLAYSTATE"); //@TODO lmao don't blow up like this
I_Error("Ghost is not a record attack ghost (has PLAYSTATE)");
if (ziptic & DXD_SKIN)
g->p++; // We _could_ read this info, but it shouldn't change anything in record attack...
if (ziptic & DXD_COLOR)
@ -2248,6 +2222,7 @@ static void G_SaveDemoSkins(UINT8 **pp)
{
char skin[16];
UINT8 i;
UINT8 *availabilitiesbuffer = R_GetSkinAvailabilities(true);
WRITEUINT8((*pp), numskins);
for (i = 0; i < numskins; i++)
@ -2262,12 +2237,17 @@ static void G_SaveDemoSkins(UINT8 **pp)
WRITEUINT8((*pp), skins[i].kartweight);
WRITEUINT32((*pp), skins[i].flags);
}
for (i = 0; i < MAXAVAILABILITY; i++)
{
WRITEUINT8((*pp), availabilitiesbuffer[i]);
}
}
static democharlist_t *G_LoadDemoSkins(UINT8 **pp, UINT8 *worknumskins, boolean getclosest)
{
char skin[17];
UINT8 i;
UINT8 i, byte, shif;
democharlist_t *skinlist = NULL;
(*worknumskins) = READUINT8((*pp));
@ -2300,7 +2280,7 @@ static democharlist_t *G_LoadDemoSkins(UINT8 **pp, UINT8 *worknumskins, boolean
}
else
{
result = GetSkinNumClosestToStats(skinlist[i].kartspeed, skinlist[i].kartweight, skinlist[i].flags);
result = GetSkinNumClosestToStats(skinlist[i].kartspeed, skinlist[i].kartweight, skinlist[i].flags, true);
}
}
@ -2310,6 +2290,24 @@ static democharlist_t *G_LoadDemoSkins(UINT8 **pp, UINT8 *worknumskins, boolean
}
}
for (byte = 0; byte < MAXAVAILABILITY; byte++)
{
UINT8 availabilitiesbuffer = READUINT8((*pp));
for (shif = 0; shif < 8; shif++)
{
i = (byte*8) + shif;
if (i >= (*worknumskins))
break;
if (availabilitiesbuffer & (1 << shif))
{
skinlist[i].unlockrequired = true;
}
}
}
return skinlist;
}
@ -2326,6 +2324,8 @@ static void G_SkipDemoSkins(UINT8 **pp)
(*pp)++; // kartweight
(*pp) += 4; // flags
}
(*pp) += MAXAVAILABILITY;
}
void G_BeginRecording(void)
@ -2358,7 +2358,11 @@ void G_BeginRecording(void)
// Full replay title
demo_p += 64;
snprintf(demo.titlename, 64, "%s - %s", G_BuildMapTitle(gamemap), modeattacking ? "Record Attack" : connectedservername);
{
char *title = G_BuildMapTitle(gamemap);
snprintf(demo.titlename, 64, "%s - %s", title, modeattacking ? "Record Attack" : connectedservername);
Z_Free(title);
}
// demo checksum
demo_p += 16;
@ -2443,6 +2447,11 @@ void G_BeginRecording(void)
M_Memcpy(demo_p,name,16);
demo_p += 16;
for (j = 0; j < MAXAVAILABILITY; j++)
{
WRITEUINT8(demo_p, player->availabilities[j]);
}
// Skin (now index into demo.skinlist)
WRITEUINT8(demo_p, player->skin);
WRITEUINT8(demo_p, player->lastfakeskin);
@ -2928,6 +2937,7 @@ void G_DoPlayDemo(char *defdemoname)
UINT8 i, p, numslots = 0;
lumpnum_t l;
char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],*n,*pdemoname;
UINT8 availabilities[MAXPLAYERS][MAXAVAILABILITY];
UINT8 version,subversion;
UINT32 randseed[PRNUMCLASS];
char msg[1024];
@ -3293,6 +3303,11 @@ void G_DoPlayDemo(char *defdemoname)
M_Memcpy(player_names[p],demo_p,16);
demo_p += 16;
for (i = 0; i < MAXAVAILABILITY; i++)
{
availabilities[p][i] = READUINT8(demo_p);
}
// Skin
i = READUINT8(demo_p);
@ -3376,6 +3391,8 @@ void G_DoPlayDemo(char *defdemoname)
for (i = 0; i < numslots; i++)
{
UINT8 j;
p = slots[i];
if (players[p].mo)
{
@ -3392,6 +3409,11 @@ void G_DoPlayDemo(char *defdemoname)
players[p].kartweight = ghostext[p].kartweight = demo.skinlist[demo.currentskinid[p]].kartweight;
players[p].charflags = ghostext[p].charflags = demo.skinlist[demo.currentskinid[p]].flags;
players[p].lastfakeskin = lastfakeskin[p];
for (j = 0; j < MAXAVAILABILITY; j++)
{
players[p].availabilities[j] = availabilities[p][j];
}
}
demo.deferstart = true;

View file

@ -33,6 +33,7 @@ struct democharlist_t {
UINT8 kartspeed;
UINT8 kartweight;
UINT32 flags;
boolean unlockrequired;
};
// Publicly-accessible demo vars
@ -119,20 +120,19 @@ typedef enum
extern UINT8 demo_extradata[MAXPLAYERS];
extern UINT8 demo_writerng;
#define DXD_PLAYSTATE 0x01 // state changed between playing, spectating, or not in-game
#define DXD_RESPAWN 0x02 // "respawn" command in console
#define DXD_JOINDATA 0x01 // join-specific data
#define DXD_PLAYSTATE 0x02 // state changed between playing, spectating, or not in-game
#define DXD_SKIN 0x04 // skin changed
#define DXD_NAME 0x08 // name changed
#define DXD_COLOR 0x10 // color changed
#define DXD_FOLLOWER 0x20 // follower was changed
#define DXD_WEAPONPREF 0x40 // netsynced playsim settings were changed
#define DXD_RESPAWN 0x40 // "respawn" command in console
#define DXD_WEAPONPREF 0x80 // netsynced playsim settings were changed
#define DXD_PST_PLAYING 0x01
#define DXD_PST_SPECTATING 0x02
#define DXD_PST_LEFT 0x03
#define DXD_PST_ISBOT 0x80 // extra flag
// Record/playback tics
void G_ReadDemoExtraData(void);
void G_WriteDemoExtraData(void);

View file

@ -189,7 +189,8 @@ struct quake quake;
// Map Header Information
mapheader_t** mapheaderinfo = {NULL};
INT32 nummapheaders, mapallocsize = 0;
INT32 nummapheaders = 0;
INT32 mapallocsize = 0;
// Kart cup definitions
cupheader_t *kartcupheaders = NULL;
@ -203,14 +204,6 @@ UINT8 stagefailed; // Used for GEMS BONUS? Also to see if you beat the stage.
UINT16 emeralds;
INT32 luabanks[NUM_LUABANKS];
UINT32 token; // Number of tokens collected in a level
UINT32 tokenlist; // List of tokens collected
boolean gottoken; // Did you get a token? Used for end of act
INT32 tokenbits; // Used for setting token bits
tic_t totalplaytime;
UINT32 matchesplayed; // SRB2Kart
boolean gamedataloaded = false;
// Temporary holding place for nights data for the current map
//nightsdata_t ntemprecords;
@ -340,9 +333,6 @@ static void G_ResetRandMapBuffer(void)
//intentionally not resetting randmaps.counttogametype here
}
// Grading
UINT32 timesBeaten;
typedef struct joystickvector2_s
{
INT32 xaxis;
@ -600,10 +590,7 @@ static void G_UpdateRecordReplays(void)
if ((earnedEmblems = M_CheckLevelEmblems()))
CONS_Printf(M_GetText("\x82" "Earned %hu medal%s for Record Attack records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
if (M_UpdateUnlockablesAndExtraEmblems())
S_StartSound(NULL, sfx_ncitem);
// SRB2Kart - save here so you NEVER lose your earned times/medals.
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
}
@ -2194,9 +2181,8 @@ static inline void G_PlayerFinishLevel(INT32 player)
{
if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified)
{
matchesplayed++;
if (M_UpdateUnlockablesAndExtraEmblems())
S_StartSound(NULL, sfx_ncitem);
gamedata->matchesplayed++;
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
}
@ -2238,7 +2224,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
UINT8 latestlap;
UINT16 skincolor;
INT32 skin;
UINT32 availabilities;
UINT8 availabilities[MAXAVAILABILITY];
UINT8 fakeskin;
UINT8 lastfakeskin;
@ -2306,7 +2292,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
followercolor = players[player].followercolor;
followerskin = players[player].followerskin;
availabilities = players[player].availabilities;
memcpy(availabilities, players[player].availabilities, sizeof(availabilities));
followitem = players[player].followitem;
@ -2429,7 +2415,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
p->charflags = charflags;
p->lastfakeskin = lastfakeskin;
p->availabilities = availabilities;
memcpy(players[player].availabilities, availabilities, sizeof(availabilities));
p->followitem = followitem;
p->starpostnum = starpostnum;
@ -2918,7 +2904,7 @@ void G_AddPlayer(INT32 playernum)
{
player_t *p = &players[playernum];
p->playerstate = PST_REBORN;
demo_extradata[playernum] |= DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything
demo_extradata[playernum] |= DXD_JOINDATA|DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything
}
void G_ExitLevel(void)
@ -3315,7 +3301,7 @@ INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype)
// Most of the gametype references in this condition are intentionally not prefgametype.
// This is so a server CAN continue playing a gametype if they like the taste of it.
// The encore check needs prefgametype so can't use G_RaceGametype...
boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE) || encorescramble == 1)
boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE, false) || encorescramble == 1)
&& ((gametyperules|gametypedefaultrules[prefgametype]) & GTR_CIRCUIT));
UINT8 encoremodifier = 0;
@ -3418,23 +3404,27 @@ INT16 G_GetFirstMapOfGametype(UINT8 pgametype)
{
UINT8 i = 0;
INT16 mapnum = NEXTMAP_INVALID;
UINT32 tol = G_TOLFlag(pgametype);
levelsearch_t templevelsearch;
levellist.cupmode = (!(gametypedefaultrules[pgametype] & GTR_NOCUPSELECT));
levellist.timeattack = false;
templevelsearch.cup = NULL;
templevelsearch.typeoflevel = G_TOLFlag(pgametype);
templevelsearch.cupmode = (!(gametypedefaultrules[pgametype] & GTR_NOCUPSELECT));
templevelsearch.timeattack = false;
templevelsearch.checklocked = true;
if (levellist.cupmode)
if (templevelsearch.cupmode)
{
cupheader_t *cup = kartcupheaders;
while (cup && mapnum >= nummapheaders)
templevelsearch.cup = kartcupheaders;
while (templevelsearch.cup && mapnum >= nummapheaders)
{
mapnum = M_GetFirstLevelInList(&i, tol, cup);
mapnum = M_GetFirstLevelInList(&i, &templevelsearch);
i = 0;
templevelsearch.cup = templevelsearch.cup->next;
}
}
else
{
mapnum = M_GetFirstLevelInList(&i, tol, NULL);
mapnum = M_GetFirstLevelInList(&i, &templevelsearch);
}
return mapnum;
@ -3687,6 +3677,9 @@ 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);
G_SaveGameData();
}
static boolean CanSaveLevel(INT32 mapnum)
@ -3869,7 +3862,7 @@ static void G_GetNextMap(void)
while (cup)
{
// Not unlocked? Grab the next result afterwards
if (!marathonmode && cup->unlockrequired != -1 && !unlockables[cup->unlockrequired].unlocked)
if (!marathonmode && M_CupLocked(cup))
{
cup = cup->next;
gettingresult = 1;
@ -4223,10 +4216,6 @@ static void G_DoContinued(void)
// Reset score
pl->score = 0;
// Allow tokens to come back
tokenlist = 0;
token = 0;
if (!(netgame || multiplayer || demo.playback || demo.recording || metalrecording || modeattacking) && !usedCheats && cursaveslot > 0)
G_SaveGameOver((UINT32)cursaveslot, true);
@ -4310,7 +4299,8 @@ void G_LoadGameSettings(void)
Color_cons_t[MAXSKINCOLORS].strvalue = Followercolor_cons_t[MAXSKINCOLORS+2].strvalue = NULL;
}
#define GD_VERSIONCHECK 0xBA5ED444 // Change every major version, as usual
#define GD_VERSIONCHECK 0xBA5ED123 // Change every major version, as usual
#define GD_VERSIONMINOR 0 // Change every format update
// G_LoadGameData
// Loads the main data file, which stores information such as emblems found, etc.
@ -4319,21 +4309,22 @@ void G_LoadGameData(void)
size_t length;
UINT32 i, j;
UINT32 versionID;
UINT8 versionMinor;
UINT8 rtemp;
//For records
UINT32 numgamedatamapheaders;
// Stop saving, until we successfully load it again.
gamedataloaded = false;
gamedata->loaded = false;
// Clear things so previously read gamedata doesn't transfer
// to new gamedata
G_ClearRecords(); // main and nights records
G_ClearRecords(); // records
M_ClearSecrets(); // emblems, unlocks, maps visited, etc
totalplaytime = 0; // total play time (separate from all)
matchesplayed = 0; // SRB2Kart: matches played & finished
gamedata->totalplaytime = 0; // total play time (separate from all)
gamedata->matchesplayed = 0; // SRB2Kart: matches played & finished
if (M_CheckParm("-nodata"))
{
@ -4344,7 +4335,7 @@ void G_LoadGameData(void)
if (M_CheckParm("-resetdata"))
{
// Don't load, but do save. (essentially, reset)
gamedataloaded = true;
gamedata->loaded = true;
return;
}
@ -4352,7 +4343,7 @@ void G_LoadGameData(void)
if (!length)
{
// No gamedata. We can save a new one.
gamedataloaded = true;
gamedata->loaded = true;
return;
}
@ -4371,8 +4362,16 @@ void G_LoadGameData(void)
I_Error("Game data is not for Ring Racers v2.0.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder);
}
totalplaytime = READUINT32(save_p);
matchesplayed = READUINT32(save_p);
versionMinor = READUINT8(save_p);
if (versionMinor > GD_VERSIONMINOR)
{
Z_Free(savebuffer);
save_p = NULL;
I_Error("Game data is from the future! (expected %d, got %d)", GD_VERSIONMINOR, versionMinor);
}
gamedata->totalplaytime = READUINT32(save_p);
gamedata->matchesplayed = READUINT32(save_p);
{
// Quick & dirty hash for what mod this save file is for.
@ -4391,32 +4390,49 @@ void G_LoadGameData(void)
{
rtemp = READUINT8(save_p);
for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
emblemlocations[j+i].collected = ((rtemp >> j) & 1);
i += j;
}
for (i = 0; i < MAXEXTRAEMBLEMS;)
{
rtemp = READUINT8(save_p);
for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j)
extraemblems[j+i].collected = ((rtemp >> j) & 1);
gamedata->collected[j+i] = ((rtemp >> j) & 1);
i += j;
}
for (i = 0; i < MAXUNLOCKABLES;)
{
rtemp = READUINT8(save_p);
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
unlockables[j+i].unlocked = ((rtemp >> j) & 1);
gamedata->unlocked[j+i] = ((rtemp >> j) & 1);
i += j;
}
for (i = 0; i < MAXUNLOCKABLES;)
{
rtemp = READUINT8(save_p);
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
gamedata->unlockpending[j+i] = ((rtemp >> j) & 1);
i += j;
}
for (i = 0; i < MAXCONDITIONSETS;)
{
rtemp = READUINT8(save_p);
for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
conditionSets[j+i].achieved = ((rtemp >> j) & 1);
gamedata->achieved[j+i] = ((rtemp >> j) & 1);
i += j;
}
timesBeaten = READUINT32(save_p);
gamedata->challengegridwidth = READUINT16(save_p);
Z_Free(gamedata->challengegrid);
if (gamedata->challengegridwidth)
{
gamedata->challengegrid = Z_Malloc(
(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT8)),
PU_STATIC, NULL);
for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++)
{
gamedata->challengegrid[i] = READUINT8(save_p);
}
}
else
{
gamedata->challengegrid = NULL;
}
gamedata->timesBeaten = READUINT32(save_p);
// Main records
numgamedatamapheaders = READUINT32(save_p);
@ -4468,10 +4484,10 @@ void G_LoadGameData(void)
// 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!
gamedataloaded = true;
gamedata->loaded = true;
// Silent update unlockables in case they're out of sync with conditions
M_SilentUpdateUnlockablesAndEmblems();
M_UpdateUnlockablesAndExtraEmblems(false);
return;
@ -4497,10 +4513,22 @@ void G_SaveGameData(void)
INT32 i, j;
UINT8 btemp;
if (!gamedataloaded)
if (!gamedata->loaded)
return; // If never loaded (-nodata), don't save
length = (4+4+4+1+(MAXEMBLEMS)+MAXEXTRAEMBLEMS+MAXUNLOCKABLES+MAXCONDITIONSETS+4+4);
if (usedCheats)
{
#ifdef DEVELOP
CONS_Alert(CONS_WARNING, M_GetText("Cheats used - Gamedata will not be saved.\n"));
#endif
return;
}
length = (4+1+4+4+1+(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+4+4+2);
if (gamedata->challengegrid)
{
length += gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT;
}
length += nummapheaders * (MAXMAPLUMPNAME+1+4+4);
save_p = savebuffer = (UINT8 *)malloc(length);
@ -4510,18 +4538,12 @@ void G_SaveGameData(void)
return;
}
if (usedCheats)
{
free(savebuffer);
save_p = savebuffer = NULL;
return;
}
// Version test
WRITEUINT32(save_p, GD_VERSIONCHECK); // 4
WRITEUINT32(save_p, totalplaytime); // 4
WRITEUINT32(save_p, matchesplayed); // 4
WRITEUINT8(save_p, GD_VERSIONMINOR); // 1
WRITEUINT32(save_p, gamedata->totalplaytime); // 4
WRITEUINT32(save_p, gamedata->matchesplayed); // 4
WRITEUINT32(save_p, quickncasehash(timeattackfolder, 64));
// To save space, use one bit per collected/achieved/unlocked flag
@ -4529,36 +4551,52 @@ void G_SaveGameData(void)
{
btemp = 0;
for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j)
btemp |= (emblemlocations[j+i].collected << j);
WRITEUINT8(save_p, btemp);
i += j;
}
for (i = 0; i < MAXEXTRAEMBLEMS;) // MAXEXTRAEMBLEMS * 1;
{
btemp = 0;
for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j)
btemp |= (extraemblems[j+i].collected << j);
WRITEUINT8(save_p, btemp);
i += j;
}
for (i = 0; i < MAXUNLOCKABLES;) // MAXUNLOCKABLES * 1;
{
btemp = 0;
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
btemp |= (unlockables[j+i].unlocked << j);
WRITEUINT8(save_p, btemp);
i += j;
}
for (i = 0; i < MAXCONDITIONSETS;) // MAXCONDITIONSETS * 1;
{
btemp = 0;
for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
btemp |= (conditionSets[j+i].achieved << j);
btemp |= (gamedata->collected[j+i] << j);
WRITEUINT8(save_p, btemp);
i += j;
}
WRITEUINT32(save_p, timesBeaten); // 4
// MAXUNLOCKABLES * 2;
for (i = 0; i < MAXUNLOCKABLES;)
{
btemp = 0;
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
btemp |= (gamedata->unlocked[j+i] << j);
WRITEUINT8(save_p, btemp);
i += j;
}
for (i = 0; i < MAXUNLOCKABLES;)
{
btemp = 0;
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
btemp |= (gamedata->unlockpending[j+i] << j);
WRITEUINT8(save_p, btemp);
i += j;
}
for (i = 0; i < MAXCONDITIONSETS;) // MAXCONDITIONSETS * 1;
{
btemp = 0;
for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j)
btemp |= (gamedata->achieved[j+i] << j);
WRITEUINT8(save_p, btemp);
i += j;
}
if (gamedata->challengegrid) // 2 + gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT
{
WRITEUINT16(save_p, gamedata->challengegridwidth);
for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++)
{
WRITEUINT8(save_p, gamedata->challengegrid[i]);
}
}
else // 2
{
WRITEUINT16(save_p, 0);
}
WRITEUINT32(save_p, gamedata->timesBeaten); // 4
// Main records
WRITEUINT32(save_p, nummapheaders); // 4

View file

@ -98,7 +98,7 @@ static char hu_tick;
//-------------------------------------------
patch_t *missingpat;
patch_t *blanklvl;
patch_t *blanklvl, *nolvl;
// song credits
static patch_t *songcreditbg;
@ -186,6 +186,7 @@ void HU_LoadGraphics(void)
Font_Load();
HU_UpdatePatch(&blanklvl, "BLANKLVL");
HU_UpdatePatch(&nolvl, "M_NOLVL");
HU_UpdatePatch(&songcreditbg, "K_SONGCR");

View file

@ -7990,7 +7990,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
1000, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
8, // reactiontime
0, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance

View file

@ -11,6 +11,7 @@
#include "r_skins.h"
#include "p_local.h"
#include "p_mobj.h"
#include "m_cond.h"
INT32 numfollowers = 0;
follower_t followers[MAXSKINS];
@ -38,6 +39,50 @@ INT32 K_FollowerAvailable(const char *name)
return -1;
}
/*--------------------------------------------------
INT32 K_FollowerUsable(INT32 followernum)
See header file for description.
--------------------------------------------------*/
boolean K_FollowerUsable(INT32 skinnum)
{
// Unlike R_SkinUsable, not netsynced.
// Solely used to prevent an invalid value being sent over the wire.
UINT8 i;
INT32 fid;
if (skinnum == -1 || demo.playback)
{
// Simplifies things elsewhere, since there's already plenty of checks for less-than-0...
return true;
}
// Determine if this follower is supposed to be unlockable or not
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (unlockables[i].type != SECRET_FOLLOWER)
continue;
fid = M_UnlockableFollowerNum(&unlockables[i]);
if (fid != skinnum)
continue;
// i is now the unlockable index, we can use this later
break;
}
if (i == MAXUNLOCKABLES)
{
// Didn't trip anything, so we can use this follower.
return true;
}
// Use the unlockables table directly
// DEFINITELY not M_CheckNetUnlockByID
return (boolean)(gamedata->unlocked[i]);
}
/*--------------------------------------------------
boolean K_SetFollowerByName(INT32 playernum, const char *skinname)

View file

@ -115,6 +115,22 @@ extern followercategory_t followercategories[MAXFOLLOWERCATEGORIES];
INT32 K_FollowerAvailable(const char *name);
/*--------------------------------------------------
boolean K_FollowerUsable(INT32 followernum);
Check if a follower is usable or not.
Input Arguments:-
skinnum - The follower's skin ID
Return:-
true if it was a valid follower,
otherwise false.
--------------------------------------------------*/
boolean K_FollowerUsable(INT32 skinnum);
/*--------------------------------------------------
boolean K_SetFollowerByName(INT32 playernum, const char *skinname)

View file

@ -231,9 +231,9 @@ void K_InitGrandPrixBots(void)
// Rearrange usable bot skins list to prevent gaps for randomised selection
for (i = 0; i < usableskins; i++)
{
if (!(grabskins[i] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/))
if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true)))
continue;
while (usableskins > i && (grabskins[usableskins] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/))
while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true)))
{
usableskins--;
}
@ -557,9 +557,9 @@ void K_RetireBots(void)
// Rearrange usable bot skins list to prevent gaps for randomised selection
for (i = 0; i < usableskins; i++)
{
if (!(grabskins[i] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/))
if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true)))
continue;
while (usableskins > i && (grabskins[usableskins] == MAXSKINS /*|| K_SkinLocked(grabskins[i])*/))
while (usableskins > i && (grabskins[usableskins] == MAXSKINS || !R_SkinUsable(-1, grabskins[usableskins], true)))
usableskins--;
grabskins[i] = grabskins[usableskins];
grabskins[usableskins] = MAXSKINS;

View file

@ -70,7 +70,7 @@ static patch_t *kp_racefinish[6];
static patch_t *kp_positionnum[10][2][2]; // number, overlay or underlay, splitscreen
static patch_t *kp_facenum[MAXPLAYERS+1];
static patch_t *kp_facehighlight[8];
patch_t *kp_facehighlight[8];
static patch_t *kp_nocontestminimap;
static patch_t *kp_spbminimap;
@ -1014,6 +1014,40 @@ static void K_initKartHUD(void)
}
}
void K_DrawMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, UINT16 map, UINT8 *colormap)
{
patch_t *PictureOfLevel = NULL;
if (map >= nummapheaders || !mapheaderinfo[map])
{
PictureOfLevel = nolvl;
}
else if (!mapheaderinfo[map]->thumbnailPic)
{
PictureOfLevel = blanklvl;
}
else
{
PictureOfLevel = mapheaderinfo[map]->thumbnailPic;
}
K_DrawLikeMapThumbnail(x, y, width, flags, PictureOfLevel, colormap);
}
void K_DrawLikeMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, patch_t *patch, UINT8 *colormap)
{
if (flags & V_FLIP)
x += width;
V_DrawFixedPatch(
x, y,
FixedDiv(width, (320 << FRACBITS)),
flags,
patch,
colormap
);
}
// see also MT_PLAYERARROW mobjthinker in p_mobj.c
static void K_drawKartItem(void)
{
@ -1465,7 +1499,7 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI
static boolean canplaysound = true;
tic_t timetoreach = emblem->var;
if (emblem->collected)
if (gamedata->collected[(emblem-emblemlocations)])
{
emblempic[curemb] = W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE);
emblemcol[curemb] = R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE);

View file

@ -36,5 +36,9 @@ void K_drawKartHUD(void);
void K_drawKartFreePlay(void);
void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode);
void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol);
void K_DrawMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, UINT16 map, UINT8 *colormap);
void K_DrawLikeMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, patch_t *patch, UINT8 *colormap);
extern patch_t *kp_facehighlight[8];
#endif

View file

@ -398,12 +398,16 @@ extern menu_t EXTRAS_ReplayStartDef;
extern menuitem_t PAUSE_Main[];
extern menu_t PAUSE_MainDef;
// EXTRAS
extern menuitem_t MISC_Manual[];
extern menu_t MISC_ManualDef;
extern menuitem_t MISC_Addons[];
extern menu_t MISC_AddonsDef;
// MANUAL
extern menuitem_t MISC_Manual[];
extern menu_t MISC_ManualDef;
extern menuitem_t MISC_ChallengesStatsDummyMenu[];
extern menu_t MISC_ChallengesDef;
extern menu_t MISC_StatisticsDef;
// We'll need this since we're gonna have to dynamically enable and disable options depending on which state we're in.
typedef enum
@ -593,6 +597,9 @@ extern struct setup_chargrid_s {
UINT8 numskins;
} setup_chargrid[9][9];
extern UINT8 setup_followercategories[MAXFOLLOWERCATEGORIES][2];
extern UINT8 setup_numfollowercategories;
typedef enum
{
CSSTEP_NONE = 0,
@ -650,9 +657,9 @@ extern UINT8 setup_maxpage;
#define CSEXPLOSIONS 48
extern struct setup_explosions_s {
UINT8 x, y;
INT16 x, y;
UINT8 tics;
UINT8 color;
UINT16 color;
} setup_explosions[CSEXPLOSIONS];
typedef enum
@ -688,23 +695,28 @@ extern struct cupgrid_s {
boolean netgame; // Start the game in an actual server
} cupgrid;
typedef struct levelsearch_s {
UINT32 typeoflevel;
cupheader_t *cup;
boolean timeattack;
boolean cupmode;
boolean checklocked;
} levelsearch_t;
extern struct levellist_s {
SINT8 cursor;
UINT16 y;
UINT16 dest;
cupheader_t *selectedcup;
INT16 choosemap;
UINT8 newgametype;
UINT32 typeoflevel;
boolean cupmode;
boolean timeattack; // Setup time attack menu after picking
levelsearch_t levelsearch;
boolean netgame; // Start the game in an actual server
} levellist;
boolean M_CanShowLevelInList(INT16 mapnum, UINT32 tol, cupheader_t *cup);
UINT16 M_CountLevelsToShowInList(UINT32 tol, cupheader_t *cup);
UINT16 M_GetFirstLevelInList(UINT8 *i, UINT32 tol, cupheader_t *cup);
UINT16 M_GetNextLevelInList(UINT16 map, UINT8 *i, UINT32 tol, cupheader_t *cup);
boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch);
UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch);
UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch);
UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch);
void M_LevelSelectInit(INT32 choice);
void M_CupSelectHandler(INT32 choice);
@ -1081,6 +1093,59 @@ void M_DrawReplayStartMenu(void);
#define LOCATIONSTRING2 "Visit \x88SRB2.ORG/MODS\x80 to get & make addons!"
void M_DrawAddons(void);
// Challenges menu:
#define UNLOCKTIME 5
#define MAXUNLOCKTIME TICRATE
#define RIGHTUNLOCKSCROLL 3
#define LEFTUNLOCKSCROLL (RIGHTUNLOCKSCROLL-1)
#define CC_TOTAL 0
#define CC_UNLOCKED 1
#define CC_TALLY 2
#define CC_ANIM 3
#define CC_MAX 4
// Keep track of some pause menu data for visual goodness.
extern struct challengesmenu_s {
tic_t ticker; // How long the menu's been open for
INT16 offset; // To make the icons move smoothly when we transition!
UINT8 currentunlock;
char *unlockcondition;
tic_t unlockanim;
SINT8 row, hilix, focusx;
UINT8 col, hiliy;
UINT8 *extradata;
boolean pending;
boolean requestnew;
UINT8 unlockcount[CC_MAX];
UINT8 fade;
} challengesmenu;
menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu);
void M_Challenges(INT32 choice);
void M_DrawChallenges(void);
void M_ChallengesTick(void);
boolean M_ChallengesInputs(INT32 ch);
extern struct statisticsmenu_s {
INT32 location;
INT32 nummaps;
INT32 maxscroll;
UINT16 *maplist;
} statisticsmenu;
void M_Statistics(INT32 choice);
void M_DrawStatistics(void);
boolean M_StatisticsInputs(INT32 ch);
// These defines make it a little easier to make menus
#define DEFAULTMENUSTYLE(source, prev, x, y)\
{\

View file

@ -1496,14 +1496,14 @@ menuitem_t EXTRAS_Main[] =
{IT_STRING | IT_CALL, "Addons", "Add files to customize your experience.",
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, {NULL}, 0, 0},
{IT_STRING | IT_TRANSTEXT, "Extras Checklist", "View the requirement for some of the secret content you can unlock!",
NULL, {NULL}, 0, 0},
NULL, {.routine = M_Statistics}, 0, 0},
};
// the extras menu essentially reuses the options menu stuff
@ -1744,3 +1744,39 @@ menu_t MISC_AddonsDef = {
NULL,
NULL
};
// Challenges.
menuitem_t MISC_ChallengesStatsDummyMenu[] =
{
{IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0},
};
menu_t MISC_ChallengesDef = {
sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t),
&MainDef,
0,
MISC_ChallengesStatsDummyMenu,
BASEVIDWIDTH/2, 32,
0, 0,
98, 0,
M_DrawChallenges,
M_ChallengesTick,
NULL,
NULL,
M_ChallengesInputs,
};
menu_t MISC_StatisticsDef = {
sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t),
&MainDef,
0,
MISC_ChallengesStatsDummyMenu,
280, 185,
0, 0,
98, 0,
M_DrawStatistics,
NULL,
NULL,
NULL,
M_StatisticsInputs,
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -418,11 +418,7 @@ void K_CashInPowerLevels(void)
{
pr->powerlevels[powerType] = clientpowerlevels[i][powerType];
if (M_UpdateUnlockablesAndExtraEmblems())
{
S_StartSound(NULL, sfx_ncitem);
}
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
}
}
@ -642,11 +638,7 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss)
{
pr->powerlevels[powerType] = yourPower + inc;
if (M_UpdateUnlockablesAndExtraEmblems())
{
S_StartSound(NULL, sfx_ncitem);
}
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
}
}

View file

@ -2351,7 +2351,7 @@ static int lib_rSetPlayerSkin(lua_State *L)
return luaL_error(L, "skin %s (argument 2) is not loaded", skinname);
}
if (!R_SkinUsable(j, i))
if (!R_SkinUsable(j, i, false))
return luaL_error(L, "skin %d (argument 2) not usable - check with R_SkinUsable(player_t, skin) first.", i);
SetPlayerSkinByNum(j, i);
return 0;
@ -2381,7 +2381,7 @@ static int lib_rSkinUsable(lua_State *L)
return luaL_error(L, "skin %s (argument 2) is not loaded", skinname);
}
lua_pushboolean(L, R_SkinUsable(j, i));
lua_pushboolean(L, R_SkinUsable(j, i, false));
return 1;
}

View file

@ -2449,8 +2449,6 @@ static int mapheaderinfo_get(lua_State *L)
lua_pushinteger(L, header->palette);
else if (fastcmp(field,"numlaps"))
lua_pushinteger(L, header->numlaps);
else if (fastcmp(field,"unlockrequired"))
lua_pushinteger(L, header->unlockrequired);
else if (fastcmp(field,"levelselect"))
lua_pushinteger(L, header->levelselect);
else if (fastcmp(field,"levelflags"))

View file

@ -669,7 +669,7 @@ static int mobj_set(lua_State *L)
for (i = 0; i < numskins; i++)
if (fastcmp(skins[i].name, skin))
{
if (!mo->player || R_SkinUsable(mo->player-players, i))
if (!mo->player || R_SkinUsable(mo->player-players, i, false))
mo->skin = &skins[i];
return 0;
}

View file

@ -408,8 +408,6 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->skincolor);
else if (fastcmp(field,"skin"))
lua_pushinteger(L, plr->skin);
else if (fastcmp(field,"availabilities"))
lua_pushinteger(L, plr->availabilities);
else if (fastcmp(field,"fakeskin"))
lua_pushinteger(L, plr->fakeskin);
else if (fastcmp(field,"lastfakeskin"))
@ -577,8 +575,6 @@ static int player_set(lua_State *L)
}
else if (fastcmp(field,"skin"))
return NOSET;
else if (fastcmp(field,"availabilities"))
return NOSET;
else if (fastcmp(field,"fakeskin"))
return NOSET;
else if (fastcmp(field,"lastfakeskin"))

View file

@ -403,8 +403,6 @@ int LUA_WriteGlobals(lua_State *L, const char *word)
skincolor_bluering = (UINT16)luaL_checkinteger(L, 2);
else if (fastcmp(word, "emeralds"))
emeralds = (UINT16)luaL_checkinteger(L, 2);
else if (fastcmp(word, "token"))
token = (UINT32)luaL_checkinteger(L, 2);
else if (fastcmp(word, "gravity"))
gravity = (fixed_t)luaL_checkinteger(L, 2);
else if (fastcmp(word, "stoppedclock"))

View file

@ -76,16 +76,16 @@ static UINT8 cheatf_warp(void)
{
if (!unlockables[i].conditionset)
continue;
if (!unlockables[i].unlocked)
if (!gamedata->unlocked[i])
{
unlockables[i].unlocked = true;
gamedata->unlocked[i] = true;
success = true;
}
}
if (success)
{
G_SaveGameData(); //G_SetUsedCheats();
G_SetUsedCheats();
S_StartSound(0, sfx_kc42);
}
@ -111,7 +111,7 @@ static UINT8 cheatf_devmode(void)
// Just unlock all the things and turn on -debug and console devmode.
G_SetUsedCheats();
for (i = 0; i < MAXUNLOCKABLES; i++)
unlockables[i].unlocked = true;
gamedata->unlocked[i] = true;
devparm = true;
cht_debug |= 0x8000;

File diff suppressed because it is too large Load diff

View file

@ -30,9 +30,9 @@ typedef enum
UC_MAPENCORE, // MAPENCORE [map number]
UC_MAPTIME, // MAPTIME [map number] [time to beat, tics]
UC_TRIGGER, // TRIGGER [trigger number]
UC_TOTALEMBLEMS, // TOTALEMBLEMS [number of emblems]
UC_TOTALMEDALS, // TOTALMEDALS [number of emblems]
UC_EMBLEM, // EMBLEM [emblem number]
UC_EXTRAEMBLEM, // EXTRAEMBLEM [extra emblem number]
UC_UNLOCKABLE, // UNLOCKABLE [unlockable number]
UC_CONDITIONSET, // CONDITIONSET [condition set number]
} conditiontype_t;
@ -52,97 +52,147 @@ struct conditionset_t
{
UINT32 numconditions; /// <- number of conditions.
condition_t *condition; /// <- All conditionals to be checked.
UINT8 achieved; /// <- Whether this conditional has been achieved already or not.
/// (Conditional checking is skipped if true -- it's assumed you can't relock an unlockable)
};
// Emblem information
#define ET_GLOBAL 0 // Emblem with a position in space
#define ET_MAP 1 // Beat the map
#define ET_TIME 2 // Get the time
#define ET_GLOBAL 0 // Emblem with a position in space
#define ET_MAP 1 // Beat the map
#define ET_TIME 2 // Get the time
//#define ET_DEVTIME 3 // Time, but the value is tied to a Time Trial demo, not pre-defined
// Global emblem flags
// (N/A to Kart yet)
//#define GE_OH 1
#define GE_NOTMEDAL 1 // Doesn't count towards number of medals
#define GE_TIMED 2 // Disappears after var time
// Map emblem flags
#define ME_ENCORE 1
#define ME_ENCORE 1 // Achieve in Encore
struct emblem_t
{
UINT8 type; ///< Emblem type
INT16 tag; ///< Tag of emblem mapthing
char * level; ///< Level on which this emblem can be found.
UINT8 sprite; ///< emblem sprite to use, 0 - 25
UINT16 color; ///< skincolor to use
INT32 var; ///< If needed, specifies information on the target amount to achieve (or target skin)
char hint[110]; ///< Hint for emblem hints menu
UINT8 collected; ///< Do you have this emblem?
};
struct extraemblem_t
{
char name[20]; ///< Name of the goal (used in the "emblem awarded" cecho)
char description[40]; ///< Description of goal (used in statistics)
UINT8 conditionset; ///< Condition set that awards this emblem.
UINT8 showconditionset; ///< Condition set that shows this emblem.
UINT8 sprite; ///< emblem sprite to use, 0 - 25
UINT16 color; ///< skincolor to use
UINT8 collected; ///< Do you have this emblem?
UINT8 type; ///< Emblem type
INT16 tag; ///< Tag of emblem mapthing
char *level; ///< Level on which this emblem can be found.
INT16 levelCache; ///< Stored G_MapNumber()+1 result
UINT8 sprite; ///< emblem sprite to use, 0 - 25
UINT16 color; ///< skincolor to use
INT32 flags; ///< GE or ME constants
INT32 var; ///< If needed, specifies extra information
char *stringVar; ///< String version
};
// Unlockable information
struct unlockable_t
{
char name[64];
char objective[64];
char *icon;
UINT16 color;
UINT8 conditionset;
UINT8 showconditionset;
INT16 type;
INT16 variable;
UINT8 nocecho;
UINT8 nochecklist;
UINT8 unlocked;
char *stringVar;
INT16 stringVarCache;
UINT8 majorunlock;
};
#define SECRET_NONE 0 // Does nil. Use with levels locked by UnlockRequired
#define SECRET_HEADER 1 // Does nothing on its own, just serves as a header for the menu
typedef enum
{
SECRET_NONE = 0, // Does nil, useful as a default only
#define SECRET_SKIN 2 // Allow this character to be selected
#define SECRET_WARP 3 // Selectable warp
#define SECRET_LEVELSELECT 4 // Selectable level select
// One step above bragging rights
SECRET_EXTRAMEDAL, // Extra medal for your counter
#define SECRET_TIMEATTACK 5 // Enables Time Attack on the main menu
#define SECRET_BREAKTHECAPSULES 6 // Enables Break the Capsules on the main menu
#define SECRET_SOUNDTEST 7 // Sound Test
#define SECRET_CREDITS 8 // Enables Credits
// Level restrictions
SECRET_CUP, // Permit access to entire cup (overrides SECRET_MAP)
SECRET_MAP, // Permit access to single map
#define SECRET_ITEMFINDER 9 // Enables Item Finder/Emblem Radar
#define SECRET_EMBLEMHINTS 10 // Enables Emblem Hints
// Player restrictions
SECRET_SKIN, // Permit this character
SECRET_FOLLOWER, // Permit this follower
#define SECRET_ENCORE 11 // Enables Encore mode cvar
#define SECRET_HARDSPEED 12 // Enables Hard gamespeed
#define SECRET_HELLATTACK 13 // Map Hell in record attack
// Difficulty restrictions
SECRET_HARDSPEED, // Permit Hard gamespeed
SECRET_ENCORE, // Permit Encore option
SECRET_LEGACYBOXRUMMAGE, // Permit the Legacy Box for record attack, etc
#define SECRET_PANDORA 14 // Enables Pandora's Box
// Menu restrictions
SECRET_TIMEATTACK, // Permit Time attack
SECRET_BREAKTHECAPSULES, // Permit SP Capsules
SECRET_SOUNDTEST, // Permit Sound Test
SECRET_ALTTITLE, // Permit alternate titlescreen
// Assist restrictions
SECRET_ITEMFINDER, // Permit locating in-level secrets
} secrettype_t;
// If you have more secrets than these variables allow in your game,
// you seriously need to get a life.
#define MAXCONDITIONSETS 128
#define MAXCONDITIONSETS UINT8_MAX
#define MAXEMBLEMS 512
#define MAXEXTRAEMBLEMS 16
#define MAXUNLOCKABLES 32
#define MAXUNLOCKABLES MAXCONDITIONSETS
#define CHALLENGEGRIDHEIGHT 5
#ifdef DEVELOP
#define CHALLENGEGRIDLOOPWIDTH 3
#else
#define CHALLENGEGRIDLOOPWIDTH (BASEVIDWIDTH/16)
#endif
#define challengegridloops (gamedata->challengegridwidth >= CHALLENGEGRIDLOOPWIDTH)
// GAMEDATA STRUCTURE
// Everything that would get saved in gamedata.dat
struct gamedata_t
{
// WHENEVER OR NOT WE'RE READY TO SAVE
boolean loaded;
// CONDITION SETS ACHIEVED
boolean achieved[MAXCONDITIONSETS];
// EMBLEMS COLLECTED
boolean collected[MAXEMBLEMS];
// UNLOCKABLES UNLOCKED
boolean unlocked[MAXUNLOCKABLES];
boolean unlockpending[MAXUNLOCKABLES];
// CHALLENGE GRID
UINT16 challengegridwidth;
UINT8 *challengegrid;
// # OF TIMES THE GAME HAS BEEN BEATEN
UINT32 timesBeaten;
// PLAY TIME
UINT32 totalplaytime;
UINT32 matchesplayed;
};
extern gamedata_t *gamedata;
// Netsynced functional alternative to gamedata->unlocked
extern boolean netUnlocked[MAXUNLOCKABLES];
extern conditionset_t conditionSets[MAXCONDITIONSETS];
extern emblem_t emblemlocations[MAXEMBLEMS];
extern extraemblem_t extraemblems[MAXEXTRAEMBLEMS];
extern unlockable_t unlockables[MAXUNLOCKABLES];
extern INT32 numemblems;
extern INT32 numextraemblems;
extern UINT32 unlocktriggers;
void M_NewGameDataStruct(void);
// Challenges menu stuff
void M_PopulateChallengeGrid(void);
UINT8 *M_ChallengeGridExtraData(void);
#define CHE_NONE 0
#define CHE_HINT 1
#define CHE_CONNECTEDLEFT (1<<1)
#define CHE_CONNECTEDUP (1<<2)
#define CHE_DONTDRAW (CHE_CONNECTEDLEFT|CHE_CONNECTEDUP)
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);
@ -151,30 +201,36 @@ void M_ClearConditionSet(UINT8 set);
void M_ClearSecrets(void);
// Updating conditions and unlockables
void M_CheckUnlockConditions(void);
UINT8 M_CheckCondition(condition_t *cn);
UINT8 M_UpdateUnlockablesAndExtraEmblems(void);
void M_SilentUpdateUnlockablesAndEmblems(void);
boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud);
UINT8 M_GetNextAchievedUnlock(void);
UINT8 M_CheckLevelEmblems(void);
UINT8 M_CompletionEmblems(void);
// Checking unlockable status
UINT8 M_AnySecretUnlocked(void);
UINT8 M_SecretUnlocked(INT32 type);
UINT8 M_MapLocked(INT32 mapnum);
INT32 M_CountEmblems(void);
boolean M_CheckNetUnlockByID(UINT8 unlockid);
boolean M_SecretUnlocked(INT32 type, boolean local);
boolean M_CupLocked(cupheader_t *cup);
boolean M_MapLocked(INT32 mapnum);
INT32 M_CountMedals(boolean all, boolean extraonly);
// Emblem shit
emblem_t *M_GetLevelEmblems(INT32 mapnum);
skincolornum_t M_GetEmblemColor(emblem_t *em);
const char *M_GetEmblemPatch(emblem_t *em, boolean big);
skincolornum_t M_GetExtraEmblemColor(extraemblem_t *em);
const char *M_GetExtraEmblemPatch(extraemblem_t *em, boolean big);
// If you're looking to compare stats for unlocks or what not, use these
// They stop checking upon reaching the target number so they
// should be (theoretically?) slightly faster.
UINT8 M_GotEnoughEmblems(INT32 number);
UINT8 M_GotEnoughMedals(INT32 number);
UINT8 M_GotLowEnoughTime(INT32 tictime);
#define M_Achieved(a) ((a) >= MAXCONDITIONSETS || conditionSets[a].achieved)
INT32 M_UnlockableSkinNum(unlockable_t *unlock);
INT32 M_UnlockableFollowerNum(unlockable_t *unlock);
cupheader_t *M_UnlockableCup(unlockable_t *unlock);
UINT16 M_UnlockableMapNum(unlockable_t *unlock);
INT32 M_EmblemSkinNum(emblem_t *emblem);
UINT16 M_EmblemMapNum(emblem_t *emblem);
#define M_Achieved(a) ((a) >= MAXCONDITIONSETS || gamedata->achieved[a])

View file

@ -14,7 +14,7 @@
#include "doomdef.h"
#include "doomtype.h"
#include "doomstat.h" // totalplaytime
#include "m_cond.h" // gamedata->totalplaytime
#include "m_random.h"
#include "m_fixed.h"
@ -372,5 +372,5 @@ void P_ClearRandom(UINT32 seed)
*/
UINT32 M_RandomizedSeed(void)
{
return ((totalplaytime & 0xFFFF) << 16) | M_RandomFixed();
return ((gamedata->totalplaytime & 0xFFFF) << 16) | M_RandomFixed();
}

View file

@ -157,6 +157,40 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon)
return true;
}
boolean P_CanPickupEmblem(player_t *player, INT32 emblemID)
{
if (emblemID < 0 || emblemID >= MAXEMBLEMS)
{
// Invalid emblem ID, can't pickup.
return false;
}
if (demo.playback)
{
// Never collect emblems in replays.
return false;
}
if (player->bot)
{
// Your nefarious opponent puppy can't grab these for you.
return false;
}
return true;
}
boolean P_EmblemWasCollected(INT32 emblemID)
{
if (emblemID < 0 || emblemID >= numemblems)
{
// Invalid emblem ID, can't pickup.
return true;
}
return gamedata->collected[emblemID];
}
/** Takes action based on a ::MF_SPECIAL thing touched by a player.
* Actually, this just checks a few things (heights, toucher->player, no
* objectplace, no dead or disappearing things)
@ -501,12 +535,24 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
// Secret emblem thingy
case MT_EMBLEM:
{
if (demo.playback || special->health > MAXEMBLEMS)
boolean gotcollected = false;
if (!P_CanPickupEmblem(player, special->health - 1))
return;
emblemlocations[special->health-1].collected = true;
M_UpdateUnlockablesAndExtraEmblems();
G_SaveGameData();
if (P_IsLocalPlayer(player) && !gamedata->collected[special->health-1])
{
gamedata->collected[special->health-1] = gotcollected = true;
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
}
if (netgame)
{
// Don't delete the object in netgames, just fade it.
return;
}
break;
}

View file

@ -546,6 +546,8 @@ void P_CheckPointLimit(void);
boolean P_CheckRacers(void);
boolean P_CanPickupItem(player_t *player, UINT8 weapon);
boolean P_CanPickupEmblem(player_t *player, INT32 emblemID);
boolean P_EmblemWasCollected(INT32 emblemID);
//
// P_SPEC

View file

@ -6938,9 +6938,38 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
}
break;
case MT_EMBLEM:
if (mobj->flags2 & MF2_NIGHTSPULL)
P_NightsItemChase(mobj);
{
INT32 trans = 0;
mobj->frame &= ~FF_TRANSMASK;
mobj->renderflags &= ~RF_TRANSMASK;
if (P_EmblemWasCollected(mobj->health - 1) || !P_CanPickupEmblem(&players[consoleplayer], mobj->health - 1))
{
trans = tr_trans50;
mobj->renderflags |= (tr_trans50 << RF_TRANSSHIFT);
}
if (mobj->reactiontime > 0
&& leveltime > starttime)
{
INT32 diff = mobj->reactiontime - (signed)(leveltime - starttime);
if (diff < 10)
{
if (diff <= 0)
{
P_RemoveMobj(mobj);
return false;
}
trans = max(trans, 10-diff);
}
}
mobj->renderflags |= (trans << RF_TRANSSHIFT);
break;
}
case MT_FLOATINGITEM:
{
mobj->pitch = mobj->roll = 0;
@ -12007,7 +12036,7 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
if (!emblem)
{
CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, Tag_FGet(&mthing->tags));
CONS_Alert(CONS_WARNING, "P_SetupEmblem: No map emblem for map %d with tag %d found!\n", gamemap, Tag_FGet(&mthing->tags));
return false;
}
@ -12020,24 +12049,13 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting
mobj->color = (UINT16)emcolor;
if (emblemlocations[j].collected)
{
P_UnsetThingPosition(mobj);
mobj->flags |= MF_NOCLIP;
mobj->flags &= ~MF_SPECIAL;
mobj->flags |= MF_NOBLOCKMAP;
mobj->frame |= (tr_trans50 << FF_TRANSSHIFT);
P_SetThingPosition(mobj);
}
else
{
mobj->frame &= ~FF_TRANSMASK;
mobj->frame &= ~FF_TRANSMASK;
if (emblemlocations[j].type == ET_GLOBAL)
{
mobj->reactiontime = emblemlocations[j].var;
}
if (emblemlocations[j].flags & GE_TIMED)
{
mobj->reactiontime = emblemlocations[j].var;
}
return true;
}
@ -12481,7 +12499,10 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
case MT_EMBLEM:
{
if (!P_SetupEmblem(mthing, mobj))
{
P_RemoveMobj(mobj);
return false;
}
break;
}
case MT_SKYBOX:

View file

@ -35,6 +35,7 @@
#include "p_polyobj.h"
#include "lua_script.h"
#include "p_slopes.h"
#include "m_cond.h" // netUnlocked
// SRB2Kart
#include "k_battle.h"
@ -150,7 +151,12 @@ static void P_NetArchivePlayers(void)
WRITEUINT8(save_p, players[i].skincolor);
WRITEINT32(save_p, players[i].skin);
WRITEUINT32(save_p, players[i].availabilities);
for (j = 0; j < MAXAVAILABILITY; j++)
{
WRITEUINT8(save_p, players[i].availabilities[j]);
}
WRITEUINT8(save_p, players[i].fakeskin);
WRITEUINT8(save_p, players[i].lastfakeskin);
WRITEUINT32(save_p, players[i].score);
@ -514,7 +520,12 @@ static void P_NetUnArchivePlayers(void)
players[i].skincolor = READUINT8(save_p);
players[i].skin = READINT32(save_p);
players[i].availabilities = READUINT32(save_p);
for (j = 0; i < MAXAVAILABILITY; j++)
{
players[i].availabilities[j] = READUINT8(save_p);
}
players[i].fakeskin = READUINT8(save_p);
players[i].lastfakeskin = READUINT8(save_p);
players[i].score = READUINT32(save_p);
@ -4684,9 +4695,6 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
//lastmapsaved = gamemap;
lastmaploaded = gamemap;
tokenlist = 0;
token = 0;
savedata.emeralds = READUINT16(save_p)-357;
READSTRINGN(save_p, testname, sizeof(testname));
@ -4705,7 +4713,7 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
static void P_NetArchiveMisc(boolean resending)
{
size_t i;
size_t i, j;
WRITEUINT32(save_p, ARCHIVEBLOCK_MISC);
@ -4726,7 +4734,14 @@ static void P_NetArchiveMisc(boolean resending)
WRITEUINT32(save_p, pig);
}
WRITEUINT32(save_p, tokenlist);
for (i = 0; i < MAXUNLOCKABLES;)
{
UINT8 btemp = 0;
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
btemp |= (netUnlocked[j+i] << j);
WRITEUINT8(save_p, btemp);
i += j;
}
WRITEUINT8(save_p, encoremode);
@ -4755,7 +4770,6 @@ static void P_NetArchiveMisc(boolean resending)
WRITEUINT8(save_p, globools);
}
WRITEUINT32(save_p, token);
WRITEUINT32(save_p, bluescore);
WRITEUINT32(save_p, redscore);
@ -4851,7 +4865,7 @@ static void P_NetArchiveMisc(boolean resending)
static inline boolean P_NetUnArchiveMisc(boolean reloading)
{
size_t i;
size_t i, j;
size_t numTasks;
if (READUINT32(save_p) != ARCHIVEBLOCK_MISC)
@ -4885,7 +4899,13 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
}
}
tokenlist = READUINT32(save_p);
for (i = 0; i < MAXUNLOCKABLES;)
{
UINT8 rtemp = READUINT8(save_p);
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
netUnlocked[j+i] = ((rtemp >> j) & 1);
i += j;
}
encoremode = (boolean)READUINT8(save_p);
@ -4918,7 +4938,6 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
stoppedclock = !!(globools & (1<<1));
}
token = READUINT32(save_p);
bluescore = READUINT32(save_p);
redscore = READUINT32(save_p);

View file

@ -400,7 +400,6 @@ static void P_ClearSingleMapHeaderInfo(INT16 num)
mapheaderinfo[num]->palette = UINT16_MAX;
mapheaderinfo[num]->encorepal = UINT16_MAX;
mapheaderinfo[num]->numlaps = NUMLAPS_DEFAULT;
mapheaderinfo[num]->unlockrequired = -1;
mapheaderinfo[num]->levelselect = 0;
mapheaderinfo[num]->levelflags = 0;
mapheaderinfo[num]->menuflags = 0;
@ -6807,7 +6806,6 @@ static void P_InitLevelSettings(void)
modulothing = 0;
// special stage tokens, emeralds, and ring total
tokenbits = 0;
runemeraldmanager = false;
emeraldspawndelay = 60*TICRATE;
@ -7564,10 +7562,13 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
nextmapoverride = 0;
skipstats = 0;
if (!(netgame || multiplayer || demo.playback) && !majormods)
if (!demo.playback)
{
mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED;
else if (!demo.playback)
mapheaderinfo[gamemap-1]->mapvisited |= MV_MP; // you want to record that you've been there this session, but not permanently
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
}
G_AddMapToBuffer(gamemap-1);
@ -8037,8 +8038,8 @@ UINT16 P_PartialAddWadFile(const char *wadfilename)
//
// look for skins
//
R_AddSkins(wadnum); // faB: wadfile index in wadfiles[]
R_PatchSkins(wadnum); // toast: PATCH PATCH
R_AddSkins(wadnum, false); // faB: wadfile index in wadfiles[]
R_PatchSkins(wadnum, false); // toast: PATCH PATCH
//
// edit music defs

View file

@ -1495,14 +1495,14 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller
{ // An unlockable itself must be unlocked!
INT32 unlockid = triggerline->args[1];
if ((modifiedgame && !savemoddata) || (netgame || multiplayer))
if (modifiedgame && !savemoddata)
return false;
else if (unlockid < 0 || unlockid >= MAXUNLOCKABLES) // limited by unlockable count
else if (unlockid <= 0 || unlockid > MAXUNLOCKABLES) // limited by unlockable count
{
CONS_Debug(DBG_GAMELOGIC, "Unlockable check (sidedef %hu): bad unlockable ID %d\n", triggerline->sidenum[0], unlockid);
return false;
}
else if (!(unlockables[unlockid-1].unlocked))
else if (!(M_CheckNetUnlockByID(unlockid)))
return false;
}
break;
@ -2752,7 +2752,7 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
break;
case 441: // Trigger unlockable
if ((!modifiedgame || savemoddata) && !(netgame || multiplayer))
if (!(demo.playback || netgame || multiplayer))
{
INT32 trigid = line->args[0];
@ -2763,9 +2763,8 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec)
unlocktriggers |= 1 << trigid;
// Unlocked something?
if (M_UpdateUnlockablesAndExtraEmblems())
if (M_UpdateUnlockablesAndExtraEmblems(true))
{
S_StartSound(NULL, sfx_s3k68);
G_SaveGameData(); // only save if unlocked something
}
}

View file

@ -20,6 +20,7 @@
#include "st_stuff.h"
#include "p_polyobj.h"
#include "m_random.h"
#include "m_cond.h" // gamedata->playtime
#include "lua_script.h"
#include "lua_hook.h"
#include "m_perfstats.h"
@ -628,7 +629,7 @@ void P_Ticker(boolean run)
// Keep track of how long they've been playing!
if (!demo.playback) // Don't increment if a demo is playing.
totalplaytime++;
gamedata->totalplaytime++;
// formality so kitemcap gets updated properly each frame.
P_RunKartItems();

View file

@ -821,7 +821,7 @@ struct patch_t
};
extern patch_t *missingpat;
extern patch_t *blanklvl;
extern patch_t *blanklvl, *nolvl;
#if defined(_MSC_VER)
#pragma pack(1)

View file

@ -32,6 +32,7 @@
#if 0
#include "k_kart.h" // K_KartResetPlayerColor
#endif
#include "k_grandprix.h" // K_CanChangeRules
#ifdef HWRENDER
#include "hardware/hw_md2.h"
#endif
@ -150,36 +151,52 @@ void R_InitSkins(void)
for (i = 0; i < numwadfiles; i++)
{
R_AddSkins((UINT16)i);
R_PatchSkins((UINT16)i);
R_AddSkins((UINT16)i, true);
R_PatchSkins((UINT16)i, true);
R_LoadSpriteInfoLumps(i, wadfiles[i]->numlumps);
}
ST_ReloadSkinFaceGraphics();
}
UINT32 R_GetSkinAvailabilities(void)
UINT8 *R_GetSkinAvailabilities(boolean demolock)
{
UINT8 i;
UINT32 response = 0;
UINT8 i, shif, byte;
INT32 skinid;
static UINT8 responsebuffer[MAXAVAILABILITY];
memset(&responsebuffer, 0, sizeof(responsebuffer));
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (unlockables[i].type == SECRET_SKIN && unlockables[i].unlocked)
{
UINT8 s = min(unlockables[i].variable, MAXSKINS);
response |= (1 << s);
}
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;
shif = (skinid % 8);
byte = (skinid / 8);
responsebuffer[byte] |= (1 << shif);
}
return response;
return responsebuffer;
}
// returns true if available in circumstances, otherwise nope
// warning don't use with an invalid skinnum other than -1 which always returns true
boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins)
{
boolean needsunlocked = false;
boolean useplayerstruct = (Playing() && playernum != -1);
UINT8 i;
INT32 skinid;
if (skinnum == -1)
{
@ -187,55 +204,55 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum)
return true;
}
if (modeattacking)
{
// If you have someone else's run, you should be able to take a look
return true;
}
if (netgame && (cv_forceskin.value == skinnum))
if (K_CanChangeRules(true) && (cv_forceskin.value == skinnum))
{
// Being forced to play as this character by the server
return true;
}
if (metalrecording)
{
// Recording a Metal Sonic race
const INT32 metalskin = R_SkinAvailable("metalsonic");
return (skinnum == metalskin);
}
// Determine if this character is supposed to be unlockable or not
for (i = 0; i < MAXUNLOCKABLES; i++)
if (useplayerstruct && demo.playback)
{
if (unlockables[i].type == SECRET_SKIN && unlockables[i].variable == skinnum)
if (!demoskins)
skinnum = demo.skinlist[skinnum].mapping;
needsunlocked = demo.skinlist[skinnum].unlockrequired;
}
else
{
for (i = 0; i < MAXUNLOCKABLES; i++)
{
if (unlockables[i].type != SECRET_SKIN)
continue;
skinid = M_UnlockableSkinNum(&unlockables[i]);
if (skinid != skinnum)
continue;
// i is now the unlockable index, we can use this later
needsunlocked = true;
break;
}
}
if (needsunlocked == true)
{
// You can use this character IF you have it unlocked.
if ((netgame || multiplayer) && playernum != -1)
{
// Use the netgame synchronized unlocks.
return (boolean)(!(players[playernum].availabilities & (1 << skinnum)));
}
else
{
// Use the unlockables table directly
return (boolean)(unlockables[i].unlocked);
}
}
else
if (needsunlocked == false)
{
// Didn't trip anything, so we can use this character.
return true;
}
// Ok, you can use this character IF you have it unlocked.
if (useplayerstruct)
{
// Use the netgame synchronized unlocks.
UINT8 shif = (skinnum % 8);
UINT8 byte = (skinnum / 8);
return !!(players[playernum].availabilities[byte] & (1 << shif));
}
// 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]);
}
// returns true if the skin name is found (loaded from pwad)
@ -253,15 +270,79 @@ INT32 R_SkinAvailable(const char *name)
return -1;
}
// Auxillary function that actually sets the skin
static void SetSkin(player_t *player, INT32 skinnum)
{
skin_t *skin = &skins[skinnum];
player->skin = skinnum;
player->followitem = skin->followitem;
player->kartspeed = skin->kartspeed;
player->kartweight = skin->kartweight;
player->charflags = skin->flags;
#if 0
if (!CV_CheatsEnabled() && !(netgame || multiplayer || demo.playback))
{
for (i = 0; i <= r_splitscreen; i++)
{
if (playernum == g_localplayers[i])
{
CV_StealthSetValue(&cv_playercolor[i], skin->prefcolor);
}
}
player->skincolor = skin->prefcolor;
K_KartResetPlayerColor(player);
}
#endif
if (player->followmobj)
{
P_RemoveMobj(player->followmobj);
P_SetTarget(&player->followmobj, NULL);
}
if (player->mo)
{
player->mo->skin = skin;
P_SetScale(player->mo, player->mo->scale);
P_SetPlayerMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames
}
// for replays: We have changed our skin mid-game; let the game know so it can do the same in the replay!
demo_extradata[(player-players)] |= DXD_SKIN;
}
// Gets the player to the first usuable skin in the game.
// (If your mod locked them all, then you kinda stupid)
static INT32 GetPlayerDefaultSkin(INT32 playernum)
{
INT32 i;
for (i = 0; i < numskins; i++)
{
if (R_SkinUsable(playernum, i, false))
{
return i;
}
}
I_Error("All characters are locked!");
return 0;
}
// network code calls this when a 'skin change' is received
void SetPlayerSkin(INT32 playernum, const char *skinname)
{
INT32 i = R_SkinAvailable(skinname);
player_t *player = &players[playernum];
if ((i != -1) && R_SkinUsable(playernum, i))
if ((i != -1) && R_SkinUsable(playernum, i, false))
{
SetPlayerSkinByNum(playernum, i);
SetSkin(player, i);
return;
}
@ -270,7 +351,7 @@ void SetPlayerSkin(INT32 playernum, const char *skinname)
else if(server || IsPlayerAdmin(consoleplayer))
CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) skin '%s' not found\n"), playernum, player_names[playernum], skinname);
SetPlayerSkinByNum(playernum, 0);
SetSkin(player, GetPlayerDefaultSkin(playernum));
}
// Same as SetPlayerSkin, but uses the skin #.
@ -278,53 +359,10 @@ void SetPlayerSkin(INT32 playernum, const char *skinname)
void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
{
player_t *player = &players[playernum];
skin_t *skin = &skins[skinnum];
//UINT16 newcolor = 0;
//UINT8 i;
if (skinnum >= 0 && skinnum < numskins && R_SkinUsable(playernum, skinnum)) // Make sure it exists!
if (skinnum >= 0 && skinnum < numskins && R_SkinUsable(playernum, skinnum, false)) // Make sure it exists!
{
player->skin = skinnum;
player->charflags = (UINT32)skin->flags;
player->followitem = skin->followitem;
player->kartspeed = skin->kartspeed;
player->kartweight = skin->kartweight;
#if 0
if (!CV_CheatsEnabled() && !(netgame || multiplayer || demo.playback))
{
for (i = 0; i <= r_splitscreen; i++)
{
if (playernum == g_localplayers[i])
{
CV_StealthSetValue(&cv_playercolor[i], skin->prefcolor);
}
}
player->skincolor = newcolor = skin->prefcolor;
K_KartResetPlayerColor(player);
}
#endif
if (player->followmobj)
{
P_RemoveMobj(player->followmobj);
P_SetTarget(&player->followmobj, NULL);
}
if (player->mo)
{
player->mo->skin = skin;
P_SetScale(player->mo, player->mo->scale);
P_SetPlayerMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames
}
// for replays: We have changed our skin mid-game; let the game know so it can do the same in the replay!
demo_extradata[playernum] |= DXD_SKIN;
SetSkin(player, skinnum);
return;
}
@ -333,7 +371,7 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
else if(server || IsPlayerAdmin(consoleplayer))
CONS_Alert(CONS_WARNING, "Player %d (%s) skin %d not found\n", playernum, player_names[playernum], skinnum);
SetPlayerSkinByNum(playernum, 0); // not found, put in the default skin
SetSkin(player, GetPlayerDefaultSkin(playernum)); // not found put the eggman skin
}
// Set mo skin but not player_t skin, for ironman
@ -383,11 +421,14 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast)
}
else if (skins[i].flags & SF_IRONMAN)
continue;
/*if (K_SkinLocked(i))
continue;*/
if (!R_SkinUsable(player-players, i, true))
continue;
grabskins[usableskins++] = i;
}
if (!usableskins)
I_Error("SetRandomFakePlayerSkin: No valid skins to pick from!?");
i = grabskins[P_RandomKey(PR_RANDOMSKIN, usableskins)];
SetFakePlayerSkin(player, i);
@ -466,6 +507,51 @@ void ClearFakePlayerSkin(player_t* player)
player->fakeskin = MAXSKINS;
}
// Finds a skin with the closest stats if the expected skin doesn't exist.
INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight, UINT32 flags, boolean unlock)
{
INT32 i, closest_skin = 0;
UINT8 closest_stats, stat_diff;
boolean doflagcheck = true;
UINT32 flagcheck = flags;
flaglessretry:
closest_stats = stat_diff = UINT8_MAX;
for (i = 0; i < numskins; i++)
{
if (!unlock && !R_SkinUsable(-1, i, false))
{
continue;
}
stat_diff = abs(skins[i].kartspeed - kartspeed) + abs(skins[i].kartweight - kartweight);
if (doflagcheck && (skins[i].flags & flagcheck) != flagcheck)
{
continue;
}
if (stat_diff < closest_stats)
{
closest_stats = stat_diff;
closest_skin = i;
}
}
if (stat_diff && (doflagcheck || closest_stats == UINT8_MAX))
{
// Just grab *any* SF_IRONMAN if we don't get it on the first pass.
if ((flagcheck & SF_IRONMAN) && (flagcheck != SF_IRONMAN))
{
flagcheck = SF_IRONMAN;
}
doflagcheck = false;
goto flaglessretry;
}
return closest_skin;
}
//
// Add skins from a pwad, each skin preceded by 'S_SKIN' marker
//
@ -659,7 +745,7 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
//
// Find skin sprites, sounds & optional status bar face, & add them
//
void R_AddSkins(UINT16 wadnum)
void R_AddSkins(UINT16 wadnum, boolean mainfile)
{
UINT16 lump, lastlump = 0;
char *buf;
@ -807,7 +893,8 @@ next_token:
R_FlushTranslationColormapCache();
CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name);
if (mainfile == false)
CONS_Printf(M_GetText("Added skin '%s'\n"), skin->name);
#ifdef SKINVALUES
skin_cons_t[numskins].value = numskins;
@ -831,7 +918,7 @@ next_token:
//
// Patch skin sprites
//
void R_PatchSkins(UINT16 wadnum)
void R_PatchSkins(UINT16 wadnum, boolean mainfile)
{
UINT16 lump, lastlump = 0;
char *buf;
@ -973,7 +1060,8 @@ next_token:
R_FlushTranslationColormapCache();
CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name);
if (mainfile == false)
CONS_Printf(M_GetText("Patched skin '%s'\n"), skin->name);
}
return;
}

View file

@ -84,11 +84,14 @@ void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002
void SetFakePlayerSkin(player_t* player, INT32 skinnum);
void SetRandomFakePlayerSkin(player_t* player, boolean fast);
void ClearFakePlayerSkin(player_t* player);
boolean R_SkinUsable(INT32 playernum, INT32 skinnum);
UINT32 R_GetSkinAvailabilities(void);
boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins);
INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight, UINT32 flags, boolean unlock);
UINT8 *R_GetSkinAvailabilities(boolean demolock);
INT32 R_SkinAvailable(const char *name);
void R_PatchSkins(UINT16 wadnum);
void R_AddSkins(UINT16 wadnum);
void R_PatchSkins(UINT16 wadnum, boolean mainfile);
void R_AddSkins(UINT16 wadnum, boolean mainfile);
UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player);

View file

@ -197,8 +197,8 @@ TYPEDEF (aatree_t);
TYPEDEF (condition_t);
TYPEDEF (conditionset_t);
TYPEDEF (emblem_t);
TYPEDEF (extraemblem_t);
TYPEDEF (unlockable_t);
TYPEDEF (gamedata_t);
// m_dllist.h
TYPEDEF (mdllistitem_t);

View file

@ -42,6 +42,7 @@
#include "m_random.h" // M_RandomKey
#include "g_input.h" // G_PlayerInputDown
#include "k_hud.h" // K_DrawMapThumbnail
#include "k_battle.h"
#include "k_boss.h"
#include "k_pwrlv.h"
@ -1006,34 +1007,11 @@ void Y_VoteDrawer(void)
y = (200-height)/2;
for (i = 0; i < 4; i++)
{
const char *str;
patch_t *pic;
UINT8 j, color;
if (i == 3)
{
str = "RANDOM";
pic = randomlvl;
}
else
{
str = levelinfo[i].str;
pic = NULL;
if (mapheaderinfo[votelevels[i][0]])
{
pic = mapheaderinfo[votelevels[i][0]]->thumbnailPic;
}
if (!pic)
{
pic = blanklvl;
}
}
if (selected[i])
{
const char *str;
UINT8 sizeadd = selected[i];
for (j = 0; j <= splitscreen; j++) // another loop for drawing the selection backgrounds in the right order, grumble grumble..
@ -1093,11 +1071,30 @@ void Y_VoteDrawer(void)
sizeadd--;
}
if (!levelinfo[i].encore)
V_DrawSmallScaledPatch(BASEVIDWIDTH-100, y, V_SNAPTORIGHT, pic);
if (i == 3)
{
str = "RANDOM";
K_DrawLikeMapThumbnail(
(BASEVIDWIDTH-100)<<FRACBITS, (y)<<FRACBITS,
80<<FRACBITS,
V_SNAPTORIGHT|(levelinfo[i].encore ? V_FLIP : 0),
randomlvl,
NULL
);
}
else
{
V_DrawFixedPatch((BASEVIDWIDTH-20)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/2, V_FLIP|V_SNAPTORIGHT, pic, 0);
str = levelinfo[i].str;
K_DrawMapThumbnail(
(BASEVIDWIDTH-100)<<FRACBITS, (y)<<FRACBITS,
80<<FRACBITS,
V_SNAPTORIGHT|(levelinfo[i].encore ? V_FLIP : 0),
votelevels[i][0],
NULL);
}
if (levelinfo[i].encore)
{
V_DrawFixedPatch((BASEVIDWIDTH-60)<<FRACBITS, ((y+25)<<FRACBITS) - (rubyheight<<1), FRACUNIT, V_SNAPTORIGHT, rubyicon, NULL);
}
@ -1117,11 +1114,28 @@ void Y_VoteDrawer(void)
}
else
{
if (!levelinfo[i].encore)
V_DrawTinyScaledPatch(BASEVIDWIDTH-60, y, V_SNAPTORIGHT, pic);
if (i == 3)
{
K_DrawLikeMapThumbnail(
(BASEVIDWIDTH-60)<<FRACBITS, (y)<<FRACBITS,
40<<FRACBITS,
V_SNAPTORIGHT|(levelinfo[i].encore ? V_FLIP : 0),
randomlvl,
NULL
);
}
else
{
V_DrawFixedPatch((BASEVIDWIDTH-20)<<FRACBITS, y<<FRACBITS, FRACUNIT/4, V_FLIP|V_SNAPTORIGHT, pic, 0);
K_DrawMapThumbnail(
(BASEVIDWIDTH-60)<<FRACBITS, (y)<<FRACBITS,
40<<FRACBITS,
V_SNAPTORIGHT|(levelinfo[i].encore ? V_FLIP : 0),
votelevels[i][0],
NULL);
}
if (levelinfo[i].encore)
{
V_DrawFixedPatch((BASEVIDWIDTH-40)<<FRACBITS, (y<<FRACBITS) + (25<<(FRACBITS-1)) - rubyheight, FRACUNIT/2, V_SNAPTORIGHT, rubyicon, NULL);
}
@ -1146,27 +1160,6 @@ void Y_VoteDrawer(void)
if ((playeringame[i] && !players[i].spectator) && votes[i] != -1)
{
patch_t *pic;
if (votes[i] >= 3 && (i != pickedvote || voteendtic == -1))
{
pic = randomlvl;
}
else
{
pic = NULL;
if (mapheaderinfo[votelevels[votes[i]][0]])
{
pic = mapheaderinfo[votelevels[votes[i]][0]]->thumbnailPic;
}
if (!pic)
{
pic = blanklvl;
}
}
if (!timer && i == voteclient.ranim)
{
V_DrawScaledPatch(x-18, y+9, V_SNAPTOLEFT, cursor);
@ -1176,11 +1169,28 @@ void Y_VoteDrawer(void)
V_DrawFill(x-1, y-1, 42, 27, levelinfo[votes[i]].gtc|V_SNAPTOLEFT);
}
if (!levelinfo[votes[i]].encore)
V_DrawTinyScaledPatch(x, y, V_SNAPTOLEFT, pic);
if (votes[i] >= 3 && (i != pickedvote || voteendtic == -1))
{
K_DrawLikeMapThumbnail(
(x)<<FRACBITS, (y)<<FRACBITS,
40<<FRACBITS,
V_SNAPTOLEFT|(levelinfo[votes[i]].encore ? V_FLIP : 0),
randomlvl,
NULL
);
}
else
{
V_DrawFixedPatch((x+40)<<FRACBITS, (y)<<FRACBITS, FRACUNIT/4, V_SNAPTOLEFT|V_FLIP, pic, 0);
K_DrawMapThumbnail(
(x)<<FRACBITS, (y)<<FRACBITS,
40<<FRACBITS,
V_SNAPTOLEFT|(levelinfo[votes[i]].encore ? V_FLIP : 0),
votelevels[votes[i]][0],
NULL);
}
if (levelinfo[votes[i]].encore)
{
V_DrawFixedPatch((x+20)<<FRACBITS, (y<<FRACBITS) + (25<<(FRACBITS-1)) - rubyheight, FRACUNIT/2, V_SNAPTOLEFT, rubyicon, NULL);
}