From 49fa46d80efd95047f05834bde41dbbd7311d418 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sun, 27 Feb 2022 16:52:05 -0500 Subject: [PATCH] Online emblems Currently, emblems share with everyone. Will add an option to toggle this. --- src/d_clisrv.c | 3 + src/d_main.c | 9 +- src/d_netcmd.c | 14 +-- src/deh_soc.c | 2 +- src/dehacked.c | 2 +- src/doomstat.h | 54 ---------- src/f_finale.c | 43 ++++---- src/g_game.c | 251 ++++++++++++++++++++++----------------------- src/g_game.h | 25 ++--- src/hu_stuff.c | 2 +- src/m_cheat.c | 4 +- src/m_cond.c | 248 +++++++++++++++++++++++++++++--------------- src/m_cond.h | 123 ++++++++++++++++++---- src/m_menu.c | 155 +++++++++++++++------------- src/m_random.c | 5 +- src/p_inter.c | 18 +++- src/p_mobj.c | 19 ++-- src/p_saveg.c | 225 ++++++++++++++++++++++++++++++++++++++++ src/p_setup.c | 11 +- src/p_spec.c | 7 +- src/p_tick.c | 5 +- src/r_skins.c | 13 +-- src/s_sound.c | 5 +- src/sdl/i_system.c | 6 +- src/st_stuff.c | 9 +- src/y_inter.c | 16 +-- 26 files changed, 824 insertions(+), 450 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 64e5aff6b..18f0179f8 100755 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -3645,6 +3645,9 @@ void SV_ResetServer(void) CV_RevertNetVars(); + // Ensure synched when creating a new server + M_CopyGameData(serverGamedata, clientGamedata); + DEBFILE("\n-=-=-=-=-=-=-= Server Reset =-=-=-=-=-=-=-\n\n"); } diff --git a/src/d_main.c b/src/d_main.c index 6506c9d4e..5861f9886 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1350,6 +1350,9 @@ void D_SRB2Main(void) CONS_Printf("Z_Init(): Init zone memory allocation daemon. \n"); Z_Init(); + clientGamedata = M_NewGameDataStruct(); + serverGamedata = M_NewGameDataStruct(); + // Do this up here so that WADs loaded through the command line can use ExecCfg COM_Init(); @@ -1479,7 +1482,9 @@ void D_SRB2Main(void) // confusion issues when loading mods. strlcpy(gamedatafilename, M_GetNextParm(), sizeof gamedatafilename); } - G_LoadGameData(); + + G_LoadGameData(clientGamedata); + M_CopyGameData(serverGamedata, clientGamedata); #if defined (__unix__) || defined (UNIXCOMMON) || defined (HAVE_SDL) VID_PrepareModeList(); // Regenerate Modelist according to cv_fullscreen @@ -1710,7 +1715,7 @@ void D_SRB2Main(void) // ... unless you're in a dedicated server. Yes, technically this means you can view any level by // running a dedicated server and joining it yourself, but that's better than making dedicated server's // lives hell. - else if (!dedicated && M_MapLocked(pstartmap)) + else if (!dedicated && M_MapLocked(pstartmap, serverGamedata)) I_Error("You need to unlock this level before you can warp to it!\n"); else { diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 80a084e16..af44e53d6 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2036,7 +2036,7 @@ static void Command_Map_f(void) // ... unless you're in a dedicated server. Yes, technically this means you can view any level by // running a dedicated server and joining it yourself, but that's better than making dedicated server's // lives hell. - if (!dedicated && M_MapLocked(newmapnum)) + if (!dedicated && M_MapLocked(newmapnum, serverGamedata)) { CONS_Alert(CONS_NOTICE, M_GetText("You need to unlock this level before you can warp to it!\n")); Z_Free(realmapname); @@ -3945,18 +3945,12 @@ void ItemFinder_OnChange(void) if (!cv_itemfinder.value) return; // it's fine. - if (!M_SecretUnlocked(SECRET_ITEMFINDER)) + if (!M_SecretUnlocked(SECRET_ITEMFINDER, clientGamedata)) { CONS_Printf(M_GetText("You haven't earned this yet.\n")); CV_StealthSetValue(&cv_itemfinder, 0); return; } - else if (netgame || multiplayer) - { - CONS_Printf(M_GetText("This only works in single player.\n")); - CV_StealthSetValue(&cv_itemfinder, 0); - return; - } } /** Deals with a pointlimit change by printing the change to the console. @@ -4305,7 +4299,7 @@ void D_GameTypeChanged(INT32 lastgametype) static void Ringslinger_OnChange(void) { - if (!M_SecretUnlocked(SECRET_PANDORA) && !netgame && cv_ringslinger.value && !cv_debug) + if (!M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !netgame && cv_ringslinger.value && !cv_debug) { CONS_Printf(M_GetText("You haven't earned this yet.\n")); CV_StealthSetValue(&cv_ringslinger, 0); @@ -4318,7 +4312,7 @@ static void Ringslinger_OnChange(void) static void Gravity_OnChange(void) { - if (!M_SecretUnlocked(SECRET_PANDORA) && !netgame && !cv_debug + if (!M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !netgame && !cv_debug && strcmp(cv_gravity.string, cv_gravity.defaultvalue)) { CONS_Printf(M_GetText("You haven't earned this yet.\n")); diff --git a/src/deh_soc.c b/src/deh_soc.c index 3a3942c14..81adbc9d2 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3849,7 +3849,7 @@ void readmaincfg(MYFILE *f) if (!GoodDataFileName(word2)) I_Error("Maincfg: bad data file name '%s'\n", word2); - G_SaveGameData(); + G_SaveGameData(clientGamedata); strlcpy(gamedatafilename, word2, sizeof (gamedatafilename)); strlwr(gamedatafilename); savemoddata = true; diff --git a/src/dehacked.c b/src/dehacked.c index 17768eb7f..fd2a70171 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -575,7 +575,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) } // end while if (gamedataadded) - G_LoadGameData(); + G_LoadGameData(clientGamedata); if (gamestate == GS_TITLESCREEN) { diff --git a/src/doomstat.h b/src/doomstat.h index 847c10b8c..5875bd01f 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -132,8 +132,6 @@ extern INT32 postimgparam2; extern INT32 viewwindowx, viewwindowy; extern INT32 viewwidth, scaledviewwidth; -extern boolean gamedataloaded; - // Player taking events, and displaying. extern INT32 consoleplayer; extern INT32 displayplayer; @@ -495,8 +493,6 @@ typedef struct extern tolinfo_t TYPEOFLEVEL[NUMTOLNAMES]; extern UINT32 lastcustomtol; -extern tic_t totalplaytime; - extern boolean stagefailed; // Emeralds stored as bits to throw savegame hackers off. @@ -515,52 +511,6 @@ extern INT32 luabanks[NUM_LUABANKS]; extern INT32 nummaprings; //keep track of spawned rings/coins -/** Time attack information, currently a very small structure. - */ -typedef struct -{ - tic_t time; ///< Time in which the level was finished. - UINT32 score; ///< Score when the level was finished. - UINT16 rings; ///< Rings when the level was finished. -} recorddata_t; - -/** Setup for one NiGHTS map. - * These are dynamically allocated because I am insane - */ -#define GRADE_F 0 -#define GRADE_E 1 -#define GRADE_D 2 -#define GRADE_C 3 -#define GRADE_B 4 -#define GRADE_A 5 -#define GRADE_S 6 - -typedef struct -{ - // 8 mares, 1 overall (0) - UINT8 nummares; - UINT32 score[9]; - UINT8 grade[9]; - tic_t time[9]; -} nightsdata_t; - -extern nightsdata_t *nightsrecords[NUMMAPS]; -extern recorddata_t *mainrecords[NUMMAPS]; - -// mapvisited is now a set of flags that says what we've done in the map. -#define MV_VISITED 1 -#define MV_BEATEN 2 -#define MV_ALLEMERALDS 4 -#define MV_ULTIMATE 8 -#define MV_PERFECT 16 -#define MV_PERFECTRA 32 -#define MV_MAX 63 // used in gamedata check, update whenever MV's are added -#define MV_MP 128 -extern UINT8 mapvisited[NUMMAPS]; - -// Temporary holding place for nights data for the current map -extern nightsdata_t ntemprecords; - 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 @@ -616,10 +566,6 @@ extern INT32 cheats; extern tic_t hidetime; -extern UINT32 timesBeaten; // # of times the game has been beaten. -extern UINT32 timesBeatenWithEmeralds; -extern UINT32 timesBeatenUltimate; - // =========================== // Internal parameters, fixed. // =========================== diff --git a/src/f_finale.c b/src/f_finale.c index 68b2641a1..4bb640d50 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1411,7 +1411,7 @@ boolean F_CreditResponder(event_t *event) break; } - if (!(timesBeaten) && !(netgame || multiplayer) && !cv_debug) + if (!(serverGamedata->timesBeaten) && !(netgame || multiplayer) && !cv_debug) return false; if (event->type != ev_keydown) @@ -1573,23 +1573,17 @@ void F_GameEvaluationDrawer(void) #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) { + INT32 startcoord = 32; V_DrawString(8, 16, V_YELLOWMAP, "Unlocked:"); - if (netgame) - V_DrawString(8, 96, V_YELLOWMAP, "Multiplayer games\ncan't unlock\nextras!"); - else + for (i = 0; i < MAXUNLOCKABLES; i++) { - 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].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; - } + if (clientGamedata->unlocked[i]) + V_DrawString(8, startcoord, 0, unlockables[i].name); + startcoord += 8; } } } @@ -1657,18 +1651,27 @@ void F_GameEvaluationTicker(void) } else { - ++timesBeaten; + serverGamedata->timesBeaten++; + clientGamedata->timesBeaten++; if (ALL7EMERALDS(emeralds)) - ++timesBeatenWithEmeralds; + { + serverGamedata->timesBeatenWithEmeralds++; + clientGamedata->timesBeatenWithEmeralds++; + } if (ultimatemode) - ++timesBeatenUltimate; + { + serverGamedata->timesBeatenUltimate++; + clientGamedata->timesBeatenUltimate++; + } - if (M_UpdateUnlockablesAndExtraEmblems()) + M_SilentUpdateUnlockablesAndEmblems(serverGamedata); + + if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata)) S_StartSound(NULL, sfx_s3k68); - G_SaveGameData(); + G_SaveGameData(clientGamedata); } } } @@ -2183,7 +2186,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<'|(trans<= STOPPINGPOINT-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<"); + V_DrawString(40, ((finalecount == STOPPINGPOINT-(20+TICRATE)) ? 1 : 0)+BASEVIDHEIGHT-16, ((serverGamedata->timesBeaten || finalecount >= STOPPINGPOINT-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<"); } if (finalecount > STOPPINGPOINT-(20+(2*TICRATE))) diff --git a/src/g_game.c b/src/g_game.c index 7386b2a84..854bf9bbb 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -185,18 +185,6 @@ INT32 tokenbits; // Used for setting token bits // Old Special Stage INT32 sstimer; // Time allotted in the special stage -tic_t totalplaytime; -boolean gamedataloaded = false; - -// Time attack data for levels -// These are dynamically allocated for space reasons now -recorddata_t *mainrecords[NUMMAPS] = {NULL}; -nightsdata_t *nightsrecords[NUMMAPS] = {NULL}; -UINT8 mapvisited[NUMMAPS]; - -// Temporary holding place for nights data for the current map -nightsdata_t ntemprecords; - UINT32 bluescore, redscore; // CTF and Team Match team scores // ring count... for PERFECT! @@ -252,11 +240,6 @@ INT32 cheats; //for multiplayer cheat commands tic_t hidetime; -// Grading -UINT32 timesBeaten; -UINT32 timesBeatenWithEmeralds; -UINT32 timesBeatenUltimate; - typedef struct joystickvector2_s { INT32 xaxis; @@ -452,86 +435,86 @@ INT16 rw_maximums[NUM_WEAPONS] = }; // Allocation for time and nights data -void G_AllocMainRecordData(INT16 i) +void G_AllocMainRecordData(INT16 i, gamedata_t *data) { - if (!mainrecords[i]) - mainrecords[i] = Z_Malloc(sizeof(recorddata_t), PU_STATIC, NULL); - memset(mainrecords[i], 0, sizeof(recorddata_t)); + if (!data->mainrecords[i]) + data->mainrecords[i] = Z_Malloc(sizeof(recorddata_t), PU_STATIC, NULL); + memset(data->mainrecords[i], 0, sizeof(recorddata_t)); } -void G_AllocNightsRecordData(INT16 i) +void G_AllocNightsRecordData(INT16 i, gamedata_t *data) { - if (!nightsrecords[i]) - nightsrecords[i] = Z_Malloc(sizeof(nightsdata_t), PU_STATIC, NULL); - memset(nightsrecords[i], 0, sizeof(nightsdata_t)); + if (!data->nightsrecords[i]) + data->nightsrecords[i] = Z_Malloc(sizeof(nightsdata_t), PU_STATIC, NULL); + memset(data->nightsrecords[i], 0, sizeof(nightsdata_t)); } // MAKE SURE YOU SAVE DATA BEFORE CALLING THIS -void G_ClearRecords(void) +void G_ClearRecords(gamedata_t *data) { INT16 i; for (i = 0; i < NUMMAPS; ++i) { - if (mainrecords[i]) + if (data->mainrecords[i]) { - Z_Free(mainrecords[i]); - mainrecords[i] = NULL; + Z_Free(data->mainrecords[i]); + data->mainrecords[i] = NULL; } - if (nightsrecords[i]) + if (data->nightsrecords[i]) { - Z_Free(nightsrecords[i]); - nightsrecords[i] = NULL; + Z_Free(data->nightsrecords[i]); + data->nightsrecords[i] = NULL; } } } // For easy retrieval of records -UINT32 G_GetBestScore(INT16 map) +UINT32 G_GetBestScore(INT16 map, gamedata_t *data) { - if (!mainrecords[map-1]) + if (!data->mainrecords[map-1]) return 0; - return mainrecords[map-1]->score; + return data->mainrecords[map-1]->score; } -tic_t G_GetBestTime(INT16 map) +tic_t G_GetBestTime(INT16 map, gamedata_t *data) { - if (!mainrecords[map-1] || mainrecords[map-1]->time <= 0) + if (!data->mainrecords[map-1] || data->mainrecords[map-1]->time <= 0) return (tic_t)UINT32_MAX; - return mainrecords[map-1]->time; + return data->mainrecords[map-1]->time; } -UINT16 G_GetBestRings(INT16 map) +UINT16 G_GetBestRings(INT16 map, gamedata_t *data) { - if (!mainrecords[map-1]) + if (!data->mainrecords[map-1]) return 0; - return mainrecords[map-1]->rings; + return data->mainrecords[map-1]->rings; } -UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare) +UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare, gamedata_t *data) { - if (!nightsrecords[map-1]) + if (!data->nightsrecords[map-1]) return 0; - return nightsrecords[map-1]->score[mare]; + return data->nightsrecords[map-1]->score[mare]; } -tic_t G_GetBestNightsTime(INT16 map, UINT8 mare) +tic_t G_GetBestNightsTime(INT16 map, UINT8 mare, gamedata_t *data) { - if (!nightsrecords[map-1] || nightsrecords[map-1]->time[mare] <= 0) + if (!data->nightsrecords[map-1] || data->nightsrecords[map-1]->time[mare] <= 0) return (tic_t)UINT32_MAX; - return nightsrecords[map-1]->time[mare]; + return data->nightsrecords[map-1]->time[mare]; } -UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare) +UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare, gamedata_t *data) { - if (!nightsrecords[map-1]) + if (!data->nightsrecords[map-1]) return 0; - return nightsrecords[map-1]->grade[mare]; + return data->nightsrecords[map-1]->grade[mare]; } // For easy adding of NiGHTS records @@ -553,7 +536,7 @@ void G_AddTempNightsRecords(UINT32 pscore, tic_t ptime, UINT8 mare) // Update replay files/data, etc. for Record Attack // See G_SetNightsRecords for NiGHTS Attack. // -static void G_UpdateRecordReplays(void) +static void G_UpdateRecordReplays(gamedata_t *data) { const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1; char *gpath; @@ -561,17 +544,17 @@ static void G_UpdateRecordReplays(void) UINT8 earnedEmblems; // Record new best time - if (!mainrecords[gamemap-1]) - G_AllocMainRecordData(gamemap-1); + if (!data->mainrecords[gamemap-1]) + G_AllocMainRecordData(gamemap-1, data); - if (players[consoleplayer].score > mainrecords[gamemap-1]->score) - mainrecords[gamemap-1]->score = players[consoleplayer].score; + if (players[consoleplayer].score > data->mainrecords[gamemap-1]->score) + data->mainrecords[gamemap-1]->score = players[consoleplayer].score; - if ((mainrecords[gamemap-1]->time == 0) || (players[consoleplayer].realtime < mainrecords[gamemap-1]->time)) - mainrecords[gamemap-1]->time = players[consoleplayer].realtime; + if ((data->mainrecords[gamemap-1]->time == 0) || (players[consoleplayer].realtime < data->mainrecords[gamemap-1]->time)) + data->mainrecords[gamemap-1]->time = players[consoleplayer].realtime; - if ((UINT16)(players[consoleplayer].rings) > mainrecords[gamemap-1]->rings) - mainrecords[gamemap-1]->rings = (UINT16)(players[consoleplayer].rings); + if ((UINT16)(players[consoleplayer].rings) > data->mainrecords[gamemap-1]->rings) + data->mainrecords[gamemap-1]->rings = (UINT16)(players[consoleplayer].rings); // Save demo! bestdemo[255] = '\0'; @@ -627,14 +610,14 @@ static void G_UpdateRecordReplays(void) free(gpath); // Check emblems when level data is updated - if ((earnedEmblems = M_CheckLevelEmblems())) + if ((earnedEmblems = M_CheckLevelEmblems(data))) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for Record Attack records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); // Update timeattack menu's replay availability. Nextmap_OnChange(); } -void G_SetNightsRecords(void) +void G_SetNightsRecords(gamedata_t *data) { INT32 i; UINT32 totalscore = 0; @@ -675,9 +658,9 @@ void G_SetNightsRecords(void) { nightsdata_t *maprecords; - if (!nightsrecords[gamemap-1]) - G_AllocNightsRecordData(gamemap-1); - maprecords = nightsrecords[gamemap-1]; + if (!data->nightsrecords[gamemap-1]) + G_AllocNightsRecordData(gamemap-1, data); + maprecords = data->nightsrecords[gamemap-1]; if (maprecords->nummares != ntemprecords.nummares) maprecords->nummares = ntemprecords.nummares; @@ -739,7 +722,7 @@ void G_SetNightsRecords(void) } free(gpath); - if ((earnedEmblems = M_CheckLevelEmblems())) + if ((earnedEmblems = M_CheckLevelEmblems(data))) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for NiGHTS records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); // If the mare count changed, this will update the score display @@ -3838,7 +3821,7 @@ static INT16 RandMap(UINT32 tolflags, INT16 pprevmap) for (ix = 0; ix < NUMMAPS; ix++) if (mapheaderinfo[ix] && (mapheaderinfo[ix]->typeoflevel & tolflags) == tolflags && ix != pprevmap // Don't pick the same map. - && (dedicated || !M_MapLocked(ix+1)) // Don't pick locked maps. + && (dedicated || !M_MapLocked(ix+1, serverGamedata)) // Don't pick locked maps. ) okmaps[numokmaps++] = ix; @@ -3855,7 +3838,7 @@ static INT16 RandMap(UINT32 tolflags, INT16 pprevmap) // // G_UpdateVisited // -static void G_UpdateVisited(void) +static void G_UpdateVisited(gamedata_t *data, boolean silent) { boolean spec = G_IsSpecialStage(gamemap); // Update visitation flags? @@ -3865,30 +3848,37 @@ static void G_UpdateVisited(void) UINT8 earnedEmblems; // Update visitation flags - mapvisited[gamemap-1] |= MV_BEATEN; + data->mapvisited[gamemap-1] |= MV_BEATEN; // eh, what the hell if (ultimatemode) - mapvisited[gamemap-1] |= MV_ULTIMATE; + data->mapvisited[gamemap-1] |= MV_ULTIMATE; // may seem incorrect but IS possible in what the main game uses as mp special stages, and nummaprings will be -1 in NiGHTS if (nummaprings > 0 && players[consoleplayer].rings >= nummaprings) { - mapvisited[gamemap-1] |= MV_PERFECT; + data->mapvisited[gamemap-1] |= MV_PERFECT; if (modeattacking) - mapvisited[gamemap-1] |= MV_PERFECTRA; + data->mapvisited[gamemap-1] |= MV_PERFECTRA; } if (!spec) { // not available to special stages because they can only really be done in one order in an unmodified game, so impossible for first six and trivial for seventh if (ALL7EMERALDS(emeralds)) - mapvisited[gamemap-1] |= MV_ALLEMERALDS; + data->mapvisited[gamemap-1] |= MV_ALLEMERALDS; } - if (modeattacking == ATTACKING_RECORD) - G_UpdateRecordReplays(); - else if (modeattacking == ATTACKING_NIGHTS) - G_SetNightsRecords(); + if (silent) + { + M_CheckLevelEmblems(data); + } + else + { + if (modeattacking == ATTACKING_RECORD) + G_UpdateRecordReplays(data); + else if (modeattacking == ATTACKING_NIGHTS) + G_SetNightsRecords(data); + } - if ((earnedEmblems = M_CompletionEmblems())) + if ((earnedEmblems = M_CompletionEmblems(data)) && !silent) CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); } } @@ -4091,7 +4081,8 @@ static void G_DoCompleted(void) if ((skipstats && !modeattacking) || (modeattacking && stagefailed) || (intertype == int_none)) { - G_UpdateVisited(); + G_UpdateVisited(serverGamedata, true); + G_UpdateVisited(clientGamedata, false); G_HandleSaveLevel(); G_AfterIntermission(); } @@ -4100,7 +4091,8 @@ static void G_DoCompleted(void) G_SetGamestate(GS_INTERMISSION); Y_StartIntermission(); Y_LoadIntermissionData(); - G_UpdateVisited(); + G_UpdateVisited(serverGamedata, true); + G_UpdateVisited(clientGamedata, false); G_HandleSaveLevel(); } } @@ -4287,7 +4279,7 @@ void G_LoadGameSettings(void) // G_LoadGameData // Loads the main data file, which stores information such as emblems found, etc. -void G_LoadGameData(void) +void G_LoadGameData(gamedata_t *data) { size_t length; INT32 i, j; @@ -4304,13 +4296,13 @@ void G_LoadGameData(void) INT32 curmare; // Stop saving, until we successfully load it again. - gamedataloaded = false; + data->loaded = false; // Clear things so previously read gamedata doesn't transfer // to new gamedata - G_ClearRecords(); // main and nights records - M_ClearSecrets(); // emblems, unlocks, maps visited, etc - totalplaytime = 0; // total play time (separate from all) + G_ClearRecords(data); // main and nights records + M_ClearSecrets(data); // emblems, unlocks, maps visited, etc + data->totalplaytime = 0; // total play time (separate from all) if (M_CheckParm("-nodata")) { @@ -4321,7 +4313,7 @@ void G_LoadGameData(void) if (M_CheckParm("-resetdata")) { // Don't load, but do save. (essentially, reset) - gamedataloaded = true; + data->loaded = true; return; } @@ -4329,7 +4321,7 @@ void G_LoadGameData(void) if (!length) { // No gamedata. We can save a new one. - gamedataloaded = true; + data->loaded = true; return; } @@ -4352,7 +4344,7 @@ void G_LoadGameData(void) I_Error("Game data is from another version of SRB2.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); } - totalplaytime = READUINT32(save_p); + data->totalplaytime = READUINT32(save_p); #ifdef COMPAT_GAMEDATA_ID if (versionID == COMPAT_GAMEDATA_ID) @@ -4386,7 +4378,7 @@ void G_LoadGameData(void) // TODO put another cipher on these things? meh, I don't care... for (i = 0; i < NUMMAPS; i++) - if ((mapvisited[i] = READUINT8(save_p)) > MV_MAX) + if ((data->mapvisited[i] = READUINT8(save_p)) > MV_MAX) goto datacorrupt; // To save space, use one bit per collected/achieved/unlocked flag @@ -4394,34 +4386,34 @@ void G_LoadGameData(void) { rtemp = READUINT8(save_p); for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j) - emblemlocations[j+i].collected = ((rtemp >> j) & 1); + data->collected[j+i] = ((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); + data->extraCollected[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); + data->unlocked[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); + data->achieved[j+i] = ((rtemp >> j) & 1); i += j; } - timesBeaten = READUINT32(save_p); - timesBeatenWithEmeralds = READUINT32(save_p); - timesBeatenUltimate = READUINT32(save_p); + data->timesBeaten = READUINT32(save_p); + data->timesBeatenWithEmeralds = READUINT32(save_p); + data->timesBeatenUltimate = READUINT32(save_p); // Main records for (i = 0; i < NUMMAPS; ++i) @@ -4436,10 +4428,10 @@ void G_LoadGameData(void) if (recscore || rectime || recrings) { - G_AllocMainRecordData((INT16)i); - mainrecords[i]->score = recscore; - mainrecords[i]->time = rectime; - mainrecords[i]->rings = recrings; + G_AllocMainRecordData((INT16)i, data); + data->mainrecords[i]->score = recscore; + data->mainrecords[i]->time = rectime; + data->mainrecords[i]->rings = recrings; } } @@ -4449,19 +4441,21 @@ void G_LoadGameData(void) if ((recmares = READUINT8(save_p)) == 0) continue; - G_AllocNightsRecordData((INT16)i); + G_AllocNightsRecordData((INT16)i, data); for (curmare = 0; curmare < (recmares+1); ++curmare) { - nightsrecords[i]->score[curmare] = READUINT32(save_p); - nightsrecords[i]->grade[curmare] = READUINT8(save_p); - nightsrecords[i]->time[curmare] = (tic_t)READUINT32(save_p); + data->nightsrecords[i]->score[curmare] = READUINT32(save_p); + data->nightsrecords[i]->grade[curmare] = READUINT8(save_p); + data->nightsrecords[i]->time[curmare] = (tic_t)READUINT32(save_p); - if (nightsrecords[i]->grade[curmare] > GRADE_S) + if (data->nightsrecords[i]->grade[curmare] > GRADE_S) + { goto datacorrupt; + } } - nightsrecords[i]->nummares = recmares; + data->nightsrecords[i]->nummares = recmares; } // done @@ -4472,10 +4466,11 @@ 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; + data->loaded = true; // Silent update unlockables in case they're out of sync with conditions - M_SilentUpdateUnlockablesAndEmblems(); + M_SilentUpdateUnlockablesAndEmblems(data); + M_SilentUpdateSkinAvailabilites(); return; @@ -4495,7 +4490,7 @@ void G_LoadGameData(void) // G_SaveGameData // Saves the main data file, which stores information such as emblems found, etc. -void G_SaveGameData(void) +void G_SaveGameData(gamedata_t *data) { size_t length; INT32 i, j; @@ -4503,7 +4498,7 @@ void G_SaveGameData(void) INT32 curmare; - if (!gamedataloaded) + if (!data->loaded) return; // If never loaded (-nodata), don't save save_p = savebuffer = (UINT8 *)malloc(GAMEDATASIZE); @@ -4523,20 +4518,20 @@ void G_SaveGameData(void) // Version test WRITEUINT32(save_p, GAMEDATA_ID); - WRITEUINT32(save_p, totalplaytime); + WRITEUINT32(save_p, data->totalplaytime); WRITEUINT32(save_p, quickncasehash(timeattackfolder, sizeof timeattackfolder)); // TODO put another cipher on these things? meh, I don't care... for (i = 0; i < NUMMAPS; i++) - WRITEUINT8(save_p, (mapvisited[i] & MV_MAX)); + WRITEUINT8(save_p, (data->mapvisited[i] & MV_MAX)); // To save space, use one bit per collected/achieved/unlocked flag for (i = 0; i < MAXEMBLEMS;) { btemp = 0; for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j) - btemp |= (emblemlocations[j+i].collected << j); + btemp |= (data->collected[j+i] << j); WRITEUINT8(save_p, btemp); i += j; } @@ -4544,7 +4539,7 @@ void G_SaveGameData(void) { btemp = 0; for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j) - btemp |= (extraemblems[j+i].collected << j); + btemp |= (data->extraCollected[j+i] << j); WRITEUINT8(save_p, btemp); i += j; } @@ -4552,7 +4547,7 @@ void G_SaveGameData(void) { btemp = 0; for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) - btemp |= (unlockables[j+i].unlocked << j); + btemp |= (data->unlocked[j+i] << j); WRITEUINT8(save_p, btemp); i += j; } @@ -4560,23 +4555,23 @@ void G_SaveGameData(void) { btemp = 0; for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j) - btemp |= (conditionSets[j+i].achieved << j); + btemp |= (data->achieved[j+i] << j); WRITEUINT8(save_p, btemp); i += j; } - WRITEUINT32(save_p, timesBeaten); - WRITEUINT32(save_p, timesBeatenWithEmeralds); - WRITEUINT32(save_p, timesBeatenUltimate); + WRITEUINT32(save_p, data->timesBeaten); + WRITEUINT32(save_p, data->timesBeatenWithEmeralds); + WRITEUINT32(save_p, data->timesBeatenUltimate); // Main records for (i = 0; i < NUMMAPS; i++) { - if (mainrecords[i]) + if (data->mainrecords[i]) { - WRITEUINT32(save_p, mainrecords[i]->score); - WRITEUINT32(save_p, mainrecords[i]->time); - WRITEUINT16(save_p, mainrecords[i]->rings); + WRITEUINT32(save_p, data->mainrecords[i]->score); + WRITEUINT32(save_p, data->mainrecords[i]->time); + WRITEUINT16(save_p, data->mainrecords[i]->rings); } else { @@ -4590,19 +4585,19 @@ void G_SaveGameData(void) // NiGHTS records for (i = 0; i < NUMMAPS; i++) { - if (!nightsrecords[i] || !nightsrecords[i]->nummares) + if (!data->nightsrecords[i] || !data->nightsrecords[i]->nummares) { WRITEUINT8(save_p, 0); continue; } - WRITEUINT8(save_p, nightsrecords[i]->nummares); + WRITEUINT8(save_p, data->nightsrecords[i]->nummares); - for (curmare = 0; curmare < (nightsrecords[i]->nummares + 1); ++curmare) + for (curmare = 0; curmare < (data->nightsrecords[i]->nummares + 1); ++curmare) { - WRITEUINT32(save_p, nightsrecords[i]->score[curmare]); - WRITEUINT8(save_p, nightsrecords[i]->grade[curmare]); - WRITEUINT32(save_p, nightsrecords[i]->time[curmare]); + WRITEUINT32(save_p, data->nightsrecords[i]->score[curmare]); + WRITEUINT8(save_p, data->nightsrecords[i]->grade[curmare]); + WRITEUINT32(save_p, data->nightsrecords[i]->time[curmare]); } } diff --git a/src/g_game.h b/src/g_game.h index 144360db4..6cda7ca9c 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -19,6 +19,7 @@ #include "d_event.h" #include "g_demo.h" #include "m_cheat.h" // objectplacing +#include "m_cond.h" extern char gamedatafilename[64]; extern char timeattackfolder[64]; @@ -183,7 +184,7 @@ boolean G_IsTitleCardAvailable(void); // Can be called by the startup code or M_Responder, calls P_SetupLevel. void G_LoadGame(UINT32 slot, INT16 mapoverride); -void G_SaveGameData(void); +void G_SaveGameData(gamedata_t *data); void G_SaveGame(UINT32 slot, INT16 mapnum); @@ -239,7 +240,7 @@ void G_SetModeAttackRetryFlag(void); void G_ClearModeAttackRetryFlag(void); boolean G_GetModeAttackRetryFlag(void); -void G_LoadGameData(void); +void G_LoadGameData(gamedata_t *data); void G_LoadGameSettings(void); void G_SetGameModified(boolean silent); @@ -248,19 +249,19 @@ void G_SetUsedCheats(boolean silent); void G_SetGamestate(gamestate_t newstate); // Gamedata record shit -void G_AllocMainRecordData(INT16 i); -void G_AllocNightsRecordData(INT16 i); -void G_ClearRecords(void); +void G_AllocMainRecordData(INT16 i, gamedata_t *data); +void G_AllocNightsRecordData(INT16 i, gamedata_t *data); +void G_ClearRecords(gamedata_t *data); -UINT32 G_GetBestScore(INT16 map); -tic_t G_GetBestTime(INT16 map); -UINT16 G_GetBestRings(INT16 map); -UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare); -tic_t G_GetBestNightsTime(INT16 map, UINT8 mare); -UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare); +UINT32 G_GetBestScore(INT16 map, gamedata_t *data); +tic_t G_GetBestTime(INT16 map, gamedata_t *data); +UINT16 G_GetBestRings(INT16 map, gamedata_t *data); +UINT32 G_GetBestNightsScore(INT16 map, UINT8 mare, gamedata_t *data); +tic_t G_GetBestNightsTime(INT16 map, UINT8 mare, gamedata_t *data); +UINT8 G_GetBestNightsGrade(INT16 map, UINT8 mare, gamedata_t *data); void G_AddTempNightsRecords(UINT32 pscore, tic_t ptime, UINT8 mare); -void G_SetNightsRecords(void); +void G_SetNightsRecords(gamedata_t *data); FUNCMATH INT32 G_TicsToHours(tic_t tics); FUNCMATH INT32 G_TicsToMinutes(tic_t tics, boolean full); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 55ebe0d4d..eceb6bbaf 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2996,7 +2996,7 @@ static void HU_DrawCoopOverlay(void) if (LUA_HudEnabled(hud_tabemblems)) { - V_DrawString(160, 144, 0, va("- %d/%d", M_CountEmblems(), numemblems+numextraemblems)); + V_DrawString(160, 144, 0, va("- %d/%d", M_CountEmblems(clientGamedata), numemblems+numextraemblems)); V_DrawScaledPatch(128, 144 - emblemicon->height/4, 0, emblemicon); } diff --git a/src/m_cheat.c b/src/m_cheat.c index 9d257b48b..934779982 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -102,7 +102,7 @@ static UINT8 cheatf_devmode(void) // Just unlock all the things and turn on -debug and console devmode. G_SetUsedCheats(false); for (i = 0; i < MAXUNLOCKABLES; i++) - unlockables[i].unlocked = true; + clientGamedata->unlocked[i] = true; devparm = true; cv_debug |= 0x8000; @@ -238,7 +238,7 @@ boolean cht_Responder(event_t *ev) } // Console cheat commands rely on these a lot... -#define REQUIRE_PANDORA if (!M_SecretUnlocked(SECRET_PANDORA) && !cv_debug)\ +#define REQUIRE_PANDORA if (!M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !cv_debug)\ { CONS_Printf(M_GetText("You haven't earned this yet.\n")); return; } #define REQUIRE_DEVMODE if (!cv_debug)\ diff --git a/src/m_cond.c b/src/m_cond.c index bd6faadfd..55f35830a 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -21,6 +21,9 @@ #include "r_skins.h" // numskins #include "r_draw.h" // R_GetColorByName +gamedata_t *clientGamedata; // Our gamedata +gamedata_t *serverGamedata; // Server's gamedata + // Map triggers for linedef executors // 32 triggers, one bit each UINT32 unlocktriggers; @@ -41,6 +44,70 @@ unlockable_t unlockables[MAXUNLOCKABLES]; INT32 numemblems = 0; INT32 numextraemblems = 0; +// Temporary holding place for nights data for the current map +nightsdata_t ntemprecords; + +// Create a new gamedata_t, for start-up +gamedata_t *M_NewGameDataStruct(void) +{ + gamedata_t *data = Z_Calloc(sizeof (*data), PU_STATIC, NULL); + M_ClearSecrets(data); + G_ClearRecords(data); + return data; +} + +void M_CopyGameData(gamedata_t *dest, gamedata_t *src) +{ + INT32 i, j; + + M_ClearSecrets(dest); + G_ClearRecords(dest); + + dest->loaded = src->loaded; + dest->totalplaytime = src->totalplaytime; + + dest->timesBeaten = src->timesBeaten; + dest->timesBeatenWithEmeralds = src->timesBeatenWithEmeralds; + dest->timesBeatenUltimate = src->timesBeatenUltimate; + + memcpy(dest->achieved, src->achieved, sizeof(dest->achieved)); + memcpy(dest->collected, src->collected, sizeof(dest->collected)); + memcpy(dest->extraCollected, src->extraCollected, sizeof(dest->extraCollected)); + memcpy(dest->unlocked, src->unlocked, sizeof(dest->unlocked)); + + memcpy(dest->mapvisited, src->mapvisited, sizeof(dest->mapvisited)); + + // Main records + for (i = 0; i < NUMMAPS; ++i) + { + if (!src->mainrecords[i]) + continue; + + G_AllocMainRecordData((INT16)i, dest); + dest->mainrecords[i]->score = src->mainrecords[i]->score; + dest->mainrecords[i]->time = src->mainrecords[i]->time; + dest->mainrecords[i]->rings = src->mainrecords[i]->rings; + } + + // Nights records + for (i = 0; i < NUMMAPS; ++i) + { + if (!src->nightsrecords[i] || !src->nightsrecords[i]->nummares) + continue; + + G_AllocNightsRecordData((INT16)i, dest); + + for (j = 0; j < (src->nightsrecords[i]->nummares + 1); j++) + { + dest->nightsrecords[i]->score[j] = src->nightsrecords[i]->score[j]; + dest->nightsrecords[i]->grade[j] = src->nightsrecords[i]->grade[j]; + dest->nightsrecords[i]->time[j] = src->nightsrecords[i]->time[j]; + } + + dest->nightsrecords[i]->nummares = src->nightsrecords[i]->nummares; + } +} + void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2) { condition_t *cond; @@ -70,89 +137,90 @@ void M_ClearConditionSet(UINT8 set) conditionSets[set - 1].condition = NULL; conditionSets[set - 1].numconditions = 0; } - conditionSets[set - 1].achieved = false; + clientGamedata->achieved[set - 1] = serverGamedata->achieved[set - 1] = false; } // Clear ALL secrets. -void M_ClearSecrets(void) +void M_ClearSecrets(gamedata_t *data) { INT32 i; - memset(mapvisited, 0, sizeof(mapvisited)); + memset(data->mapvisited, 0, sizeof(data->mapvisited)); for (i = 0; i < MAXEMBLEMS; ++i) - emblemlocations[i].collected = false; + data->collected[i] = false; for (i = 0; i < MAXEXTRAEMBLEMS; ++i) - extraemblems[i].collected = false; + data->extraCollected[i] = false; for (i = 0; i < MAXUNLOCKABLES; ++i) - unlockables[i].unlocked = false; + data->unlocked[i] = false; for (i = 0; i < MAXCONDITIONSETS; ++i) - conditionSets[i].achieved = false; + data->achieved[i] = false; - timesBeaten = timesBeatenWithEmeralds = timesBeatenUltimate = 0; + data->timesBeaten = data->timesBeatenWithEmeralds = data->timesBeatenUltimate = 0; // Re-unlock any always unlocked things - M_SilentUpdateUnlockablesAndEmblems(); + M_SilentUpdateUnlockablesAndEmblems(data); + M_SilentUpdateSkinAvailabilites(); } // ---------------------- // Condition set checking // ---------------------- -static UINT8 M_CheckCondition(condition_t *cn) +static UINT8 M_CheckCondition(condition_t *cn, gamedata_t *data) { switch (cn->type) { case UC_PLAYTIME: // Requires total playing time >= x - return (totalplaytime >= (unsigned)cn->requirement); + return (data->totalplaytime >= (unsigned)cn->requirement); case UC_GAMECLEAR: // Requires game beaten >= x times - return (timesBeaten >= (unsigned)cn->requirement); + return (data->timesBeaten >= (unsigned)cn->requirement); case UC_ALLEMERALDS: // Requires game beaten with all 7 emeralds >= x times - return (timesBeatenWithEmeralds >= (unsigned)cn->requirement); + return (data->timesBeatenWithEmeralds >= (unsigned)cn->requirement); case UC_ULTIMATECLEAR: // Requires game beaten on ultimate >= x times (in other words, never) - return (timesBeatenUltimate >= (unsigned)cn->requirement); + return (data->timesBeatenUltimate >= (unsigned)cn->requirement); case UC_OVERALLSCORE: // Requires overall score >= x - return (M_GotHighEnoughScore(cn->requirement)); + return (M_GotHighEnoughScore(cn->requirement, data)); case UC_OVERALLTIME: // Requires overall time <= x - return (M_GotLowEnoughTime(cn->requirement)); + return (M_GotLowEnoughTime(cn->requirement, data)); case UC_OVERALLRINGS: // Requires overall rings >= x - return (M_GotHighEnoughRings(cn->requirement)); + return (M_GotHighEnoughRings(cn->requirement, data)); case UC_MAPVISITED: // Requires map x to be visited - return ((mapvisited[cn->requirement - 1] & MV_VISITED) == MV_VISITED); + return ((data->mapvisited[cn->requirement - 1] & MV_VISITED) == MV_VISITED); case UC_MAPBEATEN: // Requires map x to be beaten - return ((mapvisited[cn->requirement - 1] & MV_BEATEN) == MV_BEATEN); + return ((data->mapvisited[cn->requirement - 1] & MV_BEATEN) == MV_BEATEN); case UC_MAPALLEMERALDS: // Requires map x to be beaten with all emeralds in possession - return ((mapvisited[cn->requirement - 1] & MV_ALLEMERALDS) == MV_ALLEMERALDS); + return ((data->mapvisited[cn->requirement - 1] & MV_ALLEMERALDS) == MV_ALLEMERALDS); case UC_MAPULTIMATE: // Requires map x to be beaten on ultimate - return ((mapvisited[cn->requirement - 1] & MV_ULTIMATE) == MV_ULTIMATE); + return ((data->mapvisited[cn->requirement - 1] & MV_ULTIMATE) == MV_ULTIMATE); case UC_MAPPERFECT: // Requires map x to be beaten with a perfect bonus - return ((mapvisited[cn->requirement - 1] & MV_PERFECT) == MV_PERFECT); + return ((data->mapvisited[cn->requirement - 1] & MV_PERFECT) == MV_PERFECT); case UC_MAPSCORE: // Requires score on map >= x - return (G_GetBestScore(cn->extrainfo1) >= (unsigned)cn->requirement); + return (G_GetBestScore(cn->extrainfo1, data) >= (unsigned)cn->requirement); case UC_MAPTIME: // Requires time on map <= x - return (G_GetBestTime(cn->extrainfo1) <= (unsigned)cn->requirement); + return (G_GetBestTime(cn->extrainfo1, data) <= (unsigned)cn->requirement); case UC_MAPRINGS: // Requires rings on map >= x - return (G_GetBestRings(cn->extrainfo1) >= cn->requirement); + return (G_GetBestRings(cn->extrainfo1, data) >= cn->requirement); case UC_NIGHTSSCORE: - return (G_GetBestNightsScore(cn->extrainfo1, (UINT8)cn->extrainfo2) >= (unsigned)cn->requirement); + return (G_GetBestNightsScore(cn->extrainfo1, (UINT8)cn->extrainfo2, data) >= (unsigned)cn->requirement); case UC_NIGHTSTIME: - return (G_GetBestNightsTime(cn->extrainfo1, (UINT8)cn->extrainfo2) <= (unsigned)cn->requirement); + return (G_GetBestNightsTime(cn->extrainfo1, (UINT8)cn->extrainfo2, data) <= (unsigned)cn->requirement); case UC_NIGHTSGRADE: - return (G_GetBestNightsGrade(cn->extrainfo1, (UINT8)cn->extrainfo2) >= cn->requirement); + return (G_GetBestNightsGrade(cn->extrainfo1, (UINT8)cn->extrainfo2, data) >= cn->requirement); case UC_TRIGGER: // requires map trigger set return !!(unlocktriggers & (1 << cn->requirement)); case UC_TOTALEMBLEMS: // Requires number of emblems >= x - return (M_GotEnoughEmblems(cn->requirement)); + return (M_GotEnoughEmblems(cn->requirement, data)); case UC_EMBLEM: // Requires emblem x to be obtained - return emblemlocations[cn->requirement-1].collected; + return data->collected[cn->requirement-1]; case UC_EXTRAEMBLEM: // Requires extra emblem x to be obtained - return extraemblems[cn->requirement-1].collected; + return data->extraCollected[cn->requirement-1]; case UC_CONDITIONSET: // requires condition set x to already be achieved - return M_Achieved(cn->requirement-1); + return M_Achieved(cn->requirement-1, data); } return false; } -static UINT8 M_CheckConditionSet(conditionset_t *c) +static UINT8 M_CheckConditionSet(conditionset_t *c, gamedata_t *data) { UINT32 i; UINT32 lastID = 0; @@ -173,13 +241,13 @@ static UINT8 M_CheckConditionSet(conditionset_t *c) continue; lastID = cn->id; - achievedSoFar = M_CheckCondition(cn); + achievedSoFar = M_CheckCondition(cn, data); } return achievedSoFar; } -void M_CheckUnlockConditions(void) +void M_CheckUnlockConditions(gamedata_t *data) { INT32 i; conditionset_t *c; @@ -187,27 +255,27 @@ void M_CheckUnlockConditions(void) for (i = 0; i < MAXCONDITIONSETS; ++i) { c = &conditionSets[i]; - if (!c->numconditions || c->achieved) + if (!c->numconditions || data->achieved[i]) continue; - c->achieved = (M_CheckConditionSet(c)); + data->achieved[i] = (M_CheckConditionSet(c, data)); } } -UINT8 M_UpdateUnlockablesAndExtraEmblems(void) +UINT8 M_UpdateUnlockablesAndExtraEmblems(gamedata_t *data) { INT32 i; char cechoText[992] = ""; UINT8 cechoLines = 0; - M_CheckUnlockConditions(); + M_CheckUnlockConditions(data); // Go through extra emblems for (i = 0; i < numextraemblems; ++i) { - if (extraemblems[i].collected || !extraemblems[i].conditionset) + if (data->extraCollected[i] || !extraemblems[i].conditionset) continue; - if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false) + if ((data->extraCollected[i] = M_Achieved(extraemblems[i].conditionset - 1, data)) != false) { strcat(cechoText, va(M_GetText("Got \"%s\" emblem!\\"), extraemblems[i].name)); ++cechoLines; @@ -217,14 +285,14 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(void) // Fun part: if any of those unlocked we need to go through the // unlock conditions AGAIN just in case an emblem reward was reached if (cechoLines) - M_CheckUnlockConditions(); + M_CheckUnlockConditions(data); // Go through unlockables for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (unlockables[i].unlocked || !unlockables[i].conditionset) + if (data->unlocked[i] || !unlockables[i].conditionset) continue; - if ((unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1)) != false) + if ((data->unlocked[i] = M_Achieved(unlockables[i].conditionset - 1, data)) != false) { if (unlockables[i].nocecho) continue; @@ -248,45 +316,49 @@ UINT8 M_UpdateUnlockablesAndExtraEmblems(void) HU_DoCEcho(slashed); return true; } + return false; } // Used when loading gamedata to make sure all unlocks are synched with conditions -void M_SilentUpdateUnlockablesAndEmblems(void) +void M_SilentUpdateUnlockablesAndEmblems(gamedata_t *data) { INT32 i; boolean checkAgain = false; // Just in case they aren't to sync - M_CheckUnlockConditions(); - M_CheckLevelEmblems(); + M_CheckUnlockConditions(data); + M_CheckLevelEmblems(data); // Go through extra emblems for (i = 0; i < numextraemblems; ++i) { - if (extraemblems[i].collected || !extraemblems[i].conditionset) + if (data->extraCollected[i] || !extraemblems[i].conditionset) continue; - if ((extraemblems[i].collected = M_Achieved(extraemblems[i].conditionset - 1)) != false) + if ((data->extraCollected[i] = M_Achieved(extraemblems[i].conditionset - 1, data)) != false) checkAgain = true; } // check again if extra emblems unlocked, blah blah, etc if (checkAgain) - M_CheckUnlockConditions(); + M_CheckUnlockConditions(data); // Go through unlockables for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (unlockables[i].unlocked || !unlockables[i].conditionset) + if (data->unlocked[i] || !unlockables[i].conditionset) continue; - unlockables[i].unlocked = M_Achieved(unlockables[i].conditionset - 1); + data->unlocked[i] = M_Achieved(unlockables[i].conditionset - 1, data); } +} +void M_SilentUpdateSkinAvailabilites(void) +{ players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p } // Emblem unlocking shit -UINT8 M_CheckLevelEmblems(void) +UINT8 M_CheckLevelEmblems(gamedata_t *data) { INT32 i; INT32 valToReach; @@ -297,7 +369,7 @@ UINT8 M_CheckLevelEmblems(void) // Update Score, Time, Rings emblems for (i = 0; i < numemblems; ++i) { - if (emblemlocations[i].type <= ET_SKIN || emblemlocations[i].type == ET_MAP || emblemlocations[i].collected) + if (emblemlocations[i].type <= ET_SKIN || emblemlocations[i].type == ET_MAP || data->collected[i]) continue; levelnum = emblemlocations[i].level; @@ -306,32 +378,32 @@ UINT8 M_CheckLevelEmblems(void) switch (emblemlocations[i].type) { case ET_SCORE: // Requires score on map >= x - res = (G_GetBestScore(levelnum) >= (unsigned)valToReach); + res = (G_GetBestScore(levelnum, data) >= (unsigned)valToReach); break; case ET_TIME: // Requires time on map <= x - res = (G_GetBestTime(levelnum) <= (unsigned)valToReach); + res = (G_GetBestTime(levelnum, data) <= (unsigned)valToReach); break; case ET_RINGS: // Requires rings on map >= x - res = (G_GetBestRings(levelnum) >= valToReach); + res = (G_GetBestRings(levelnum, data) >= valToReach); break; case ET_NGRADE: // Requires NiGHTS grade on map >= x - res = (G_GetBestNightsGrade(levelnum, 0) >= valToReach); + res = (G_GetBestNightsGrade(levelnum, 0, data) >= valToReach); break; case ET_NTIME: // Requires NiGHTS time on map <= x - res = (G_GetBestNightsTime(levelnum, 0) <= (unsigned)valToReach); + res = (G_GetBestNightsTime(levelnum, 0, data) <= (unsigned)valToReach); break; default: // unreachable but shuts the compiler up. continue; } - emblemlocations[i].collected = res; + data->collected[i] = res; if (res) ++somethingUnlocked; } return somethingUnlocked; } -UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough. +UINT8 M_CompletionEmblems(gamedata_t *data) // Bah! Duplication sucks, but it's for a separate print when awarding emblems and it's sorta different enough. { INT32 i; INT32 embtype; @@ -342,7 +414,7 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa for (i = 0; i < numemblems; ++i) { - if (emblemlocations[i].type != ET_MAP || emblemlocations[i].collected) + if (emblemlocations[i].type != ET_MAP || data->collected[i]) continue; levelnum = emblemlocations[i].level; @@ -358,9 +430,9 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa if (embtype & ME_PERFECT) flags |= MV_PERFECT; - res = ((mapvisited[levelnum - 1] & flags) == flags); + res = ((data->mapvisited[levelnum - 1] & flags) == flags); - emblemlocations[i].collected = res; + data->collected[i] = res; if (res) ++somethingUnlocked; } @@ -370,48 +442,54 @@ UINT8 M_CompletionEmblems(void) // Bah! Duplication sucks, but it's for a separa // ------------------- // Quick unlock checks // ------------------- -UINT8 M_AnySecretUnlocked(void) +UINT8 M_AnySecretUnlocked(gamedata_t *data) { INT32 i; for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (!unlockables[i].nocecho && unlockables[i].unlocked) + if (!unlockables[i].nocecho && data->unlocked[i]) return true; } return false; } -UINT8 M_SecretUnlocked(INT32 type) +UINT8 M_SecretUnlocked(INT32 type, gamedata_t *data) { INT32 i; for (i = 0; i < MAXUNLOCKABLES; ++i) { - if (unlockables[i].type == type && unlockables[i].unlocked) + if (unlockables[i].type == type && data->unlocked[i]) return true; } return false; } -UINT8 M_MapLocked(INT32 mapnum) +UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data) { if (!mapheaderinfo[mapnum-1] || mapheaderinfo[mapnum-1]->unlockrequired < 0) + { return false; - if (!unlockables[mapheaderinfo[mapnum-1]->unlockrequired].unlocked) + } + + if (!data->unlocked[mapheaderinfo[mapnum-1]->unlockrequired]) + { return true; + } + return false; } -INT32 M_CountEmblems(void) +INT32 M_CountEmblems(gamedata_t *data) { INT32 found = 0, i; for (i = 0; i < numemblems; ++i) { - if (emblemlocations[i].collected) + if (data->collected[i]) found++; } for (i = 0; i < numextraemblems; ++i) { - if (extraemblems[i].collected) + if (data->extraCollected[i]) found++; } return found; @@ -423,23 +501,23 @@ INT32 M_CountEmblems(void) // Theoretically faster than using M_CountEmblems() // Stops when it reaches the target number of emblems. -UINT8 M_GotEnoughEmblems(INT32 number) +UINT8 M_GotEnoughEmblems(INT32 number, gamedata_t *data) { INT32 i, gottenemblems = 0; for (i = 0; i < numemblems; ++i) { - if (emblemlocations[i].collected) + if (data->collected[i]) if (++gottenemblems >= number) return true; } for (i = 0; i < numextraemblems; ++i) { - if (extraemblems[i].collected) + if (data->extraCollected[i]) if (++gottenemblems >= number) return true; } return false; } -UINT8 M_GotHighEnoughScore(INT32 tscore) +UINT8 M_GotHighEnoughScore(INT32 tscore, gamedata_t *data) { INT32 mscore = 0; INT32 i; @@ -448,16 +526,16 @@ UINT8 M_GotHighEnoughScore(INT32 tscore) { if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK)) continue; - if (!mainrecords[i]) + if (!data->mainrecords[i]) continue; - if ((mscore += mainrecords[i]->score) > tscore) + if ((mscore += data->mainrecords[i]->score) > tscore) return true; } return false; } -UINT8 M_GotLowEnoughTime(INT32 tictime) +UINT8 M_GotLowEnoughTime(INT32 tictime, gamedata_t *data) { INT32 curtics = 0; INT32 i; @@ -467,15 +545,15 @@ UINT8 M_GotLowEnoughTime(INT32 tictime) if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK)) continue; - if (!mainrecords[i] || !mainrecords[i]->time) + if (!data->mainrecords[i] || !data->mainrecords[i]->time) return false; - else if ((curtics += mainrecords[i]->time) > tictime) + else if ((curtics += data->mainrecords[i]->time) > tictime) return false; } return true; } -UINT8 M_GotHighEnoughRings(INT32 trings) +UINT8 M_GotHighEnoughRings(INT32 trings, gamedata_t *data) { INT32 mrings = 0; INT32 i; @@ -484,10 +562,10 @@ UINT8 M_GotHighEnoughRings(INT32 trings) { if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK)) continue; - if (!mainrecords[i]) + if (!data->mainrecords[i]) continue; - if ((mrings += mainrecords[i]->rings) > trings) + if ((mrings += data->mainrecords[i]->rings) > trings) return true; } return false; diff --git a/src/m_cond.h b/src/m_cond.h index d49dc920b..6a3da79ec 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -10,7 +10,11 @@ /// \file m_cond.h /// \brief Unlockable condition system for SRB2 version 2.1 +#ifndef __M_COND__ +#define __M_COND__ + #include "doomdef.h" +#include "doomdata.h" // -------- // Typedefs @@ -61,8 +65,6 @@ typedef struct { 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) } conditionset_t; // Emblem information @@ -94,7 +96,6 @@ typedef struct INT32 var; ///< If needed, specifies information on the target amount to achieve (or target skin) char *stringVar; ///< String version char hint[110]; ///< Hint for emblem hints menu - UINT8 collected; ///< Do you have this emblem? } emblem_t; typedef struct { @@ -104,7 +105,6 @@ typedef struct 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? } extraemblem_t; // Unlockable information @@ -120,7 +120,6 @@ typedef struct char *stringVar; UINT8 nocecho; UINT8 nochecklist; - UINT8 unlocked; } unlockable_t; #define SECRET_NONE -6 // Does nil. Use with levels locked by UnlockRequired @@ -143,6 +142,83 @@ typedef struct #define MAXEXTRAEMBLEMS 16 #define MAXUNLOCKABLES 32 +/** Time attack information, currently a very small structure. + */ +typedef struct +{ + tic_t time; ///< Time in which the level was finished. + UINT32 score; ///< Score when the level was finished. + UINT16 rings; ///< Rings when the level was finished. +} recorddata_t; + +/** Setup for one NiGHTS map. + * These are dynamically allocated because I am insane + */ +#define GRADE_F 0 +#define GRADE_E 1 +#define GRADE_D 2 +#define GRADE_C 3 +#define GRADE_B 4 +#define GRADE_A 5 +#define GRADE_S 6 + +typedef struct +{ + // 8 mares, 1 overall (0) + UINT8 nummares; + UINT32 score[9]; + UINT8 grade[9]; + tic_t time[9]; +} nightsdata_t; + +// mapvisited is now a set of flags that says what we've done in the map. +#define MV_VISITED 1 +#define MV_BEATEN 2 +#define MV_ALLEMERALDS 4 +#define MV_ULTIMATE 8 +#define MV_PERFECT 16 +#define MV_PERFECTRA 32 +#define MV_MAX 63 // used in gamedata check, update whenever MV's are added + +// Temporary holding place for nights data for the current map +extern nightsdata_t ntemprecords; + +// GAMEDATA STRUCTURE +// Everything that would get saved in gamedata.dat +typedef struct +{ + // WHENEVER OR NOT WE'RE READY TO SAVE + boolean loaded; + + // CONDITION SETS ACHIEVED + boolean achieved[MAXCONDITIONSETS]; + + // EMBLEMS COLLECTED + boolean collected[MAXEMBLEMS]; + + // EXTRA EMBLEMS COLLECTED + boolean extraCollected[MAXEXTRAEMBLEMS]; + + // UNLOCKABLES UNLOCKED + boolean unlocked[MAXUNLOCKABLES]; + + // TIME ATTACK DATA + recorddata_t *mainrecords[NUMMAPS]; + nightsdata_t *nightsrecords[NUMMAPS]; + UINT8 mapvisited[NUMMAPS]; + + // # OF TIMES THE GAME HAS BEEN BEATEN + UINT32 timesBeaten; + UINT32 timesBeatenWithEmeralds; + UINT32 timesBeatenUltimate; + + // PLAY TIME + UINT32 totalplaytime; +} gamedata_t; + +extern gamedata_t *clientGamedata; +extern gamedata_t *serverGamedata; + extern conditionset_t conditionSets[MAXCONDITIONSETS]; extern emblem_t emblemlocations[MAXEMBLEMS]; extern extraemblem_t extraemblems[MAXEXTRAEMBLEMS]; @@ -153,25 +229,30 @@ extern INT32 numextraemblems; extern UINT32 unlocktriggers; +gamedata_t *M_NewGameDataStruct(void); +void M_CopyGameData(gamedata_t *dest, gamedata_t *src); + // Condition set setup void M_AddRawCondition(UINT8 set, UINT8 id, conditiontype_t c, INT32 r, INT16 x1, INT16 x2); // Clearing secrets void M_ClearConditionSet(UINT8 set); -void M_ClearSecrets(void); +void M_ClearSecrets(gamedata_t *data); // Updating conditions and unlockables -void M_CheckUnlockConditions(void); -UINT8 M_UpdateUnlockablesAndExtraEmblems(void); -void M_SilentUpdateUnlockablesAndEmblems(void); -UINT8 M_CheckLevelEmblems(void); -UINT8 M_CompletionEmblems(void); +void M_CheckUnlockConditions(gamedata_t *data); +UINT8 M_UpdateUnlockablesAndExtraEmblems(gamedata_t *data); +void M_SilentUpdateUnlockablesAndEmblems(gamedata_t *data); +UINT8 M_CheckLevelEmblems(gamedata_t *data); +UINT8 M_CompletionEmblems(gamedata_t *data); + +void M_SilentUpdateSkinAvailabilites(void); // Checking unlockable status -UINT8 M_AnySecretUnlocked(void); -UINT8 M_SecretUnlocked(INT32 type); -UINT8 M_MapLocked(INT32 mapnum); -INT32 M_CountEmblems(void); +UINT8 M_AnySecretUnlocked(gamedata_t *data); +UINT8 M_SecretUnlocked(INT32 type, gamedata_t *data); +UINT8 M_MapLocked(INT32 mapnum, gamedata_t *data); +INT32 M_CountEmblems(gamedata_t *data); // Emblem shit emblem_t *M_GetLevelEmblems(INT32 mapnum); @@ -183,12 +264,14 @@ 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_GotHighEnoughScore(INT32 tscore); -UINT8 M_GotLowEnoughTime(INT32 tictime); -UINT8 M_GotHighEnoughRings(INT32 trings); +UINT8 M_GotEnoughEmblems(INT32 number, gamedata_t *data); +UINT8 M_GotHighEnoughScore(INT32 tscore, gamedata_t *data); +UINT8 M_GotLowEnoughTime(INT32 tictime, gamedata_t *data); +UINT8 M_GotHighEnoughRings(INT32 trings, gamedata_t *data); INT32 M_UnlockableSkinNum(unlockable_t *unlock); INT32 M_EmblemSkinNum(emblem_t *emblem); -#define M_Achieved(a) ((a) >= MAXCONDITIONSETS || conditionSets[a].achieved) +#define M_Achieved(a, data) ((a) >= MAXCONDITIONSETS || data->achieved[a]) + +#endif diff --git a/src/m_menu.c b/src/m_menu.c index 64a1c9404..2214e6a6a 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -2284,6 +2284,7 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt); // Nextmap. Used for Level select. void Nextmap_OnChange(void) { + gamedata_t *data = clientGamedata; char *leveltitle; char tabase[256]; #ifdef OLDNREPLAYNAME @@ -2301,7 +2302,7 @@ void Nextmap_OnChange(void) { CV_StealthSetValue(&cv_dummymares, 0); // Hide the record changing CVAR if only one mare is available. - if (!nightsrecords[cv_nextmap.value-1] || nightsrecords[cv_nextmap.value-1]->nummares < 2) + if (!data->nightsrecords[cv_nextmap.value-1] || data->nightsrecords[cv_nextmap.value-1]->nummares < 2) SP_NightsAttackMenu[narecords].status = IT_DISABLED; else SP_NightsAttackMenu[narecords].status = IT_STRING|IT_CVAR; @@ -2432,14 +2433,15 @@ void Nextmap_OnChange(void) static void Dummymares_OnChange(void) { - if (!nightsrecords[cv_nextmap.value-1]) + gamedata_t *data = clientGamedata; + if (!data->nightsrecords[cv_nextmap.value-1]) { CV_StealthSetValue(&cv_dummymares, 0); return; } else { - UINT8 mares = nightsrecords[cv_nextmap.value-1]->nummares; + UINT8 mares = data->nightsrecords[cv_nextmap.value-1]->nummares; if (cv_dummymares.value < 0) CV_StealthSetValue(&cv_dummymares, mares); @@ -3670,9 +3672,9 @@ void M_StartControlPanel(void) if (!Playing()) { // Secret menu! - MainMenu[singleplr].alphaKey = (M_AnySecretUnlocked()) ? 76 : 84; - MainMenu[multiplr].alphaKey = (M_AnySecretUnlocked()) ? 84 : 92; - MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); + MainMenu[singleplr].alphaKey = (M_AnySecretUnlocked(clientGamedata)) ? 76 : 84; + MainMenu[multiplr].alphaKey = (M_AnySecretUnlocked(clientGamedata)) ? 84 : 92; + MainMenu[secrets].status = (M_AnySecretUnlocked(clientGamedata)) ? (IT_STRING | IT_CALL) : (IT_DISABLED); currentMenu = &MainDef; itemOn = singleplr; @@ -3680,14 +3682,14 @@ void M_StartControlPanel(void) else if (modeattacking) { currentMenu = &MAPauseDef; - MAPauseMenu[mapause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS)) ? (IT_STRING | IT_CALL) : (IT_DISABLED); + MAPauseMenu[mapause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS, clientGamedata)) ? (IT_STRING | IT_CALL) : (IT_DISABLED); itemOn = mapause_continue; } else if (!(netgame || multiplayer)) // Single Player { if (gamestate != GS_LEVEL || ultimatemode) // intermission, so gray out stuff. { - SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA)) ? (IT_GRAYEDOUT) : (IT_DISABLED); + SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA, serverGamedata)) ? (IT_GRAYEDOUT) : (IT_DISABLED); SPauseMenu[spause_retry].status = IT_GRAYEDOUT; } else @@ -3696,7 +3698,7 @@ void M_StartControlPanel(void) if (players[consoleplayer].playerstate != PST_LIVE) ++numlives; - SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED); + SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA, serverGamedata) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED); // The list of things that can disable retrying is (was?) a little too complex // for me to want to use the short if statement syntax @@ -3710,7 +3712,7 @@ void M_StartControlPanel(void) SPauseMenu[spause_levelselect].status = (gamecomplete == 1) ? (IT_STRING | IT_CALL) : (IT_DISABLED); // And emblem hints. - SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED); + SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS, clientGamedata) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED); // Shift up Pandora's Box if both pandora and levelselect are active /*if (SPauseMenu[spause_pandora].status != (IT_DISABLED) @@ -4259,7 +4261,7 @@ static void M_DrawMapEmblems(INT32 mapnum, INT32 x, INT32 y, boolean norecordatt x -= 4; lasttype = curtype; - if (emblem->collected) + if (clientGamedata->collected[emblem - emblemlocations]) V_DrawSmallMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH), R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE)); else @@ -4692,6 +4694,8 @@ static void M_DrawGenericScrollMenu(void) static void M_DrawPauseMenu(void) { + gamedata_t *data = clientGamedata; + if (!netgame && !multiplayer && (gamestate == GS_LEVEL || gamestate == GS_INTERMISSION)) { emblem_t *emblem_detail[3] = {NULL, NULL, NULL}; @@ -4720,7 +4724,7 @@ static void M_DrawPauseMenu(void) { case ET_SCORE: snprintf(targettext, 9, "%d", emblem->var); - snprintf(currenttext, 9, "%u", G_GetBestScore(gamemap)); + snprintf(currenttext, 9, "%u", G_GetBestScore(gamemap, data)); targettext[8] = 0; currenttext[8] = 0; @@ -4734,7 +4738,7 @@ static void M_DrawPauseMenu(void) G_TicsToSeconds((tic_t)emblemslot), G_TicsToCentiseconds((tic_t)emblemslot)); - emblemslot = (INT32)G_GetBestTime(gamemap); // dumb hack pt ii + emblemslot = (INT32)G_GetBestTime(gamemap, data); // dumb hack pt ii if ((tic_t)emblemslot == UINT32_MAX) snprintf(currenttext, 9, "-:--.--"); else @@ -4750,7 +4754,7 @@ static void M_DrawPauseMenu(void) break; case ET_RINGS: snprintf(targettext, 9, "%d", emblem->var); - snprintf(currenttext, 9, "%u", G_GetBestRings(gamemap)); + snprintf(currenttext, 9, "%u", G_GetBestRings(gamemap, data)); targettext[8] = 0; currenttext[8] = 0; @@ -4758,8 +4762,8 @@ static void M_DrawPauseMenu(void) emblemslot = 2; break; case ET_NGRADE: - snprintf(targettext, 9, "%u", P_GetScoreForGradeOverall(gamemap, emblem->var)); - snprintf(currenttext, 9, "%u", G_GetBestNightsScore(gamemap, 0)); + snprintf(targettext, 9, "%u", P_GetScoreForGrade(gamemap, 0, emblem->var)); + snprintf(currenttext, 9, "%u", G_GetBestNightsScore(gamemap, 0, data)); targettext[8] = 0; currenttext[8] = 0; @@ -4773,7 +4777,7 @@ static void M_DrawPauseMenu(void) G_TicsToSeconds((tic_t)emblemslot), G_TicsToCentiseconds((tic_t)emblemslot)); - emblemslot = (INT32)G_GetBestNightsTime(gamemap, 0); // dumb hack pt iv + emblemslot = (INT32)G_GetBestNightsTime(gamemap, 0, data); // dumb hack pt iv if ((tic_t)emblemslot == UINT32_MAX) snprintf(currenttext, 9, "-:--.--"); else @@ -4807,7 +4811,7 @@ static void M_DrawPauseMenu(void) if (!emblem) continue; - if (emblem->collected) + if (data->collected[emblem - emblemlocations]) V_DrawSmallMappedPatch(40, 44 + (i*8), 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH), R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE)); else @@ -5019,7 +5023,9 @@ static void M_PatchSkinNameTable(void) // static boolean M_LevelAvailableOnPlatter(INT32 mapnum) { - if (M_MapLocked(mapnum+1)) + gamedata_t *data = serverGamedata; + + if (M_MapLocked(mapnum+1, data)) return false; // not unlocked switch (levellistmode) @@ -5032,7 +5038,7 @@ static boolean M_LevelAvailableOnPlatter(INT32 mapnum) return true; #ifndef DEVELOP - if (mapvisited[mapnum]) // MV_MP + if (data->mapvisited[mapnum]) #endif return true; @@ -5040,7 +5046,7 @@ static boolean M_LevelAvailableOnPlatter(INT32 mapnum) case LLM_RECORDATTACK: case LLM_NIGHTSATTACK: #ifndef DEVELOP - if (mapvisited[mapnum] & MV_MAX) + if (data->mapvisited[mapnum]) return true; if (mapheaderinfo[mapnum]->menuflags & LF2_NOVISITNEEDED) @@ -5071,7 +5077,7 @@ static boolean M_CanShowLevelOnPlatter(INT32 mapnum, INT32 gt) if (!mapheaderinfo[mapnum]->lvlttl[0]) return false; - /*if (M_MapLocked(mapnum+1)) + /*if (M_MapLocked(mapnum+1, serverGamedata)) return false; // not unlocked*/ switch (levellistmode) @@ -6904,7 +6910,7 @@ static void M_HandleAddons(INT32 choice) closefilemenu(true); // secrets disabled by addfile... - MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED); + MainMenu[secrets].status = (M_AnySecretUnlocked(clientGamedata)) ? (IT_STRING | IT_CALL) : (IT_DISABLED); if (currentMenu->prevMenu) M_SetupNextMenu(currentMenu->prevMenu); @@ -7150,7 +7156,9 @@ static boolean checklist_cangodown; // uuuueeerggghhhh HACK static void M_HandleChecklist(INT32 choice) { + gamedata_t *data = clientGamedata; INT32 j; + switch (choice) { case KEY_DOWNARROW: @@ -7167,7 +7175,7 @@ static void M_HandleChecklist(INT32 choice) continue; if (unlockables[j].conditionset > MAXCONDITIONSETS) continue; - if (!unlockables[j].unlocked && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset)) + if (!data->unlocked[j] && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset, data)) continue; if (unlockables[j].conditionset == unlockables[check_on].conditionset) continue; @@ -7192,7 +7200,7 @@ static void M_HandleChecklist(INT32 choice) continue; if (unlockables[j].conditionset > MAXCONDITIONSETS) continue; - if (!unlockables[j].unlocked && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset)) + if (!data->unlocked[j] && unlockables[j].showconditionset && !M_Achieved(unlockables[j].showconditionset, data)) continue; if (j && unlockables[j].conditionset == unlockables[j-1].conditionset) continue; @@ -7218,6 +7226,9 @@ static void M_HandleChecklist(INT32 choice) static void M_DrawChecklist(void) { + gamedata_t *data = clientGamedata; + INT32 emblemCount = M_CountEmblems(data); + INT32 i = check_on, j = 0, y = currentMenu->y, emblems = numemblems+numextraemblems; UINT32 condnum, previd, maxcond; condition_t *cond; @@ -7228,7 +7239,7 @@ static void M_DrawChecklist(void) // draw emblem counter if (emblems > 0) { - V_DrawString(42, 20, (emblems == M_CountEmblems()) ? V_GREENMAP : 0, va("%d/%d", M_CountEmblems(), emblems)); + V_DrawString(42, 20, (emblems == emblemCount) ? V_GREENMAP : 0, va("%d/%d", emblemCount, emblems)); V_DrawSmallScaledPatch(28, 20, 0, W_CachePatchName("EMBLICON", PU_PATCH)); } @@ -7239,13 +7250,13 @@ static void M_DrawChecklist(void) { if (unlockables[i].name[0] == 0 //|| unlockables[i].nochecklist || !unlockables[i].conditionset || unlockables[i].conditionset > MAXCONDITIONSETS - || (!unlockables[i].unlocked && unlockables[i].showconditionset && !M_Achieved(unlockables[i].showconditionset))) + || (!data->unlocked[i] && unlockables[i].showconditionset && !M_Achieved(unlockables[i].showconditionset, data))) { i += 1; continue; } - V_DrawString(currentMenu->x, y, ((unlockables[i].unlocked) ? V_GREENMAP : V_TRANSLUCENT)|V_ALLOWLOWERCASE, ((unlockables[i].unlocked || !unlockables[i].nochecklist) ? unlockables[i].name : M_CreateSecretMenuOption(unlockables[i].name))); + V_DrawString(currentMenu->x, y, ((data->unlocked[i]) ? V_GREENMAP : V_TRANSLUCENT)|V_ALLOWLOWERCASE, ((data->unlocked[i] || !unlockables[i].nochecklist) ? unlockables[i].name : M_CreateSecretMenuOption(unlockables[i].name))); for (j = i+1; j < MAXUNLOCKABLES; j++) { @@ -7323,7 +7334,7 @@ static void M_DrawChecklist(void) if (title) { - const char *level = ((M_MapLocked(cond[condnum].requirement) || !((mapheaderinfo[cond[condnum].requirement-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].requirement-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title); + const char *level = ((M_MapLocked(cond[condnum].requirement, data) || !((mapheaderinfo[cond[condnum].requirement-1]->menuflags & LF2_NOVISITNEEDED) || (data->mapvisited[cond[condnum].requirement-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title); switch (cond[condnum].type) { @@ -7356,7 +7367,7 @@ static void M_DrawChecklist(void) if (title) { - const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title); + const char *level = ((M_MapLocked(cond[condnum].extrainfo1, data) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (data->mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title); switch (cond[condnum].type) { @@ -7425,7 +7436,7 @@ static void M_DrawChecklist(void) if (title) { - const char *level = ((M_MapLocked(cond[condnum].extrainfo1) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title); + const char *level = ((M_MapLocked(cond[condnum].extrainfo1, data) || !((mapheaderinfo[cond[condnum].extrainfo1-1]->menuflags & LF2_NOVISITNEEDED) || (data->mapvisited[cond[condnum].extrainfo1-1] & MV_MAX))) ? M_CreateSecretMenuOption(title) : title); switch (cond[condnum].type) { @@ -7488,7 +7499,7 @@ static void M_DrawChecklist(void) /*V_DrawString(160, 8+(24*j), V_RETURN8, V_WordWrap(160, 292, 0, unlockables[i].objective)); - if (unlockables[i].unlocked) + if (data->unlocked[i]) V_DrawString(308, 8+(24*j), V_YELLOWMAP, "Y"); else V_DrawString(308, 8+(24*j), V_YELLOWMAP, "N");*/ @@ -7517,7 +7528,7 @@ static void M_EmblemHints(INT32 choice) (void)choice; SR_EmblemHintMenu[0].status = (local > NUMHINTS*2) ? (IT_STRING | IT_ARROWS) : (IT_DISABLED); - SR_EmblemHintMenu[1].status = (M_SecretUnlocked(SECRET_ITEMFINDER)) ? (IT_CVAR|IT_STRING) : (IT_SECRET); + SR_EmblemHintMenu[1].status = (M_SecretUnlocked(SECRET_ITEMFINDER, clientGamedata)) ? (IT_CVAR|IT_STRING) : (IT_SECRET); hintpage = 1; SR_EmblemHintDef.prevMenu = currentMenu; M_SetupNextMenu(&SR_EmblemHintDef); @@ -7577,7 +7588,7 @@ static void M_DrawEmblemHints(void) if (totalemblems >= ((hintpage-1)*(NUMHINTS*2) + 1) && totalemblems < (hintpage*NUMHINTS*2)+1){ - if (emblem->collected) + if (clientGamedata->collected[i]) { collected = V_GREENMAP; V_DrawMappedPatch(x, y+4, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_PATCH), @@ -8117,7 +8128,7 @@ static void M_SecretsMenu(INT32 choice) SR_MainMenu[i].status = IT_SECRET; - if (unlockables[ul].unlocked) + if (clientGamedata->unlocked[ul]) { switch (unlockables[ul].type) { @@ -8216,7 +8227,7 @@ static void M_SinglePlayerMenu(INT32 choice) levellistmode = LLM_RECORDATTACK; if (M_GametypeHasLevels(-1)) - SP_MainMenu[sprecordattack].status = (M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING : IT_SECRET; + SP_MainMenu[sprecordattack].status = (M_SecretUnlocked(SECRET_RECORDATTACK, clientGamedata)) ? IT_CALL|IT_STRING : IT_SECRET; else // If Record Attack is nonexistent in the current add-on... { SP_MainMenu[sprecordattack].status = IT_NOTHING|IT_DISABLED; // ...hide and disable the Record Attack option... @@ -8226,7 +8237,7 @@ static void M_SinglePlayerMenu(INT32 choice) levellistmode = LLM_NIGHTSATTACK; if (M_GametypeHasLevels(-1)) - SP_MainMenu[spnightsmode].status = (M_SecretUnlocked(SECRET_NIGHTSMODE)) ? IT_CALL|IT_STRING : IT_SECRET; + SP_MainMenu[spnightsmode].status = (M_SecretUnlocked(SECRET_NIGHTSMODE, clientGamedata)) ? IT_CALL|IT_STRING : IT_SECRET; else // If NiGHTS Mode is nonexistent in the current add-on... { SP_MainMenu[spnightsmode].status = IT_NOTHING|IT_DISABLED; // ...hide and disable the NiGHTS Mode option... @@ -8249,7 +8260,7 @@ static void M_SinglePlayerMenu(INT32 choice) SP_MainMenu[spnightsmode] .alphaKey += 8; } else // Otherwise, if Marathon Run is allowed and Record Attack is unlocked, unlock Marathon Run! - SP_MainMenu[spmarathon].status = (M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING|IT_CALL_NOTMODIFIED : IT_SECRET; + SP_MainMenu[spmarathon].status = (M_SecretUnlocked(SECRET_RECORDATTACK, clientGamedata)) ? IT_CALL|IT_STRING|IT_CALL_NOTMODIFIED : IT_SECRET; if (tutorialmap) // If there's a tutorial available in the current add-on... @@ -9626,7 +9637,7 @@ static void M_Statistics(INT32 choice) if (!(mapheaderinfo[i]->typeoflevel & TOL_SP) || (mapheaderinfo[i]->menuflags & LF2_HIDEINSTATS)) continue; - if (!(mapvisited[i] & MV_MAX)) + if (!(clientGamedata->mapvisited[i] & MV_MAX)) continue; statsMapList[j++] = i; @@ -9643,6 +9654,7 @@ static void M_Statistics(INT32 choice) static void M_DrawStatsMaps(int location) { + gamedata_t *data = clientGamedata; INT32 y = 80, i = -1; INT16 mnum; extraemblem_t *exemblem; @@ -9710,14 +9722,14 @@ static void M_DrawStatsMaps(int location) { exemblem = &extraemblems[i]; - if (exemblem->collected) + if (data->extraCollected[i]) V_DrawSmallMappedPatch(292, y, 0, W_CachePatchName(M_GetExtraEmblemPatch(exemblem, false), PU_PATCH), R_GetTranslationColormap(TC_DEFAULT, M_GetExtraEmblemColor(exemblem), GTC_CACHE)); else V_DrawSmallScaledPatch(292, y, 0, W_CachePatchName("NEEDIT", PU_PATCH)); V_DrawString(20, y, V_YELLOWMAP|V_ALLOWLOWERCASE, - (!exemblem->collected && exemblem->showconditionset && !M_Achieved(exemblem->showconditionset)) + (!data->extraCollected[i] && exemblem->showconditionset && !M_Achieved(exemblem->showconditionset, data)) ? M_CreateSecretMenuOption(exemblem->description) : exemblem->description); } @@ -9734,6 +9746,7 @@ bottomarrow: static void M_DrawLevelStats(void) { + gamedata_t *data = clientGamedata; char beststr[40]; tic_t besttime = 0; @@ -9748,9 +9761,9 @@ static void M_DrawLevelStats(void) V_DrawString(20, 24, V_YELLOWMAP, "Total Play Time:"); V_DrawCenteredString(BASEVIDWIDTH/2, 32, 0, va("%i hours, %i minutes, %i seconds", - G_TicsToHours(totalplaytime), - G_TicsToMinutes(totalplaytime, false), - G_TicsToSeconds(totalplaytime))); + G_TicsToHours(data->totalplaytime), + G_TicsToMinutes(data->totalplaytime, false), + G_TicsToSeconds(data->totalplaytime))); for (i = 0; i < NUMMAPS; i++) { @@ -9759,25 +9772,25 @@ static void M_DrawLevelStats(void) if (!mapheaderinfo[i] || !(mapheaderinfo[i]->menuflags & LF2_RECORDATTACK)) continue; - if (!mainrecords[i]) + if (!data->mainrecords[i]) { mapsunfinished++; bestunfinished[0] = bestunfinished[1] = bestunfinished[2] = true; continue; } - if (mainrecords[i]->score > 0) - bestscore += mainrecords[i]->score; + if (data->mainrecords[i]->score > 0) + bestscore += data->mainrecords[i]->score; else mapunfinished = bestunfinished[0] = true; - if (mainrecords[i]->time > 0) - besttime += mainrecords[i]->time; + if (data->mainrecords[i]->time > 0) + besttime += data->mainrecords[i]->time; else mapunfinished = bestunfinished[1] = true; - if (mainrecords[i]->rings > 0) - bestrings += mainrecords[i]->rings; + if (data->mainrecords[i]->rings > 0) + bestrings += data->mainrecords[i]->rings; else mapunfinished = bestunfinished[2] = true; @@ -9792,7 +9805,7 @@ static void M_DrawLevelStats(void) else V_DrawString(20, 56, V_GREENMAP, "(complete)"); - V_DrawString(36, 64, 0, va("x %d/%d", M_CountEmblems(), numemblems+numextraemblems)); + V_DrawString(36, 64, 0, va("x %d/%d", M_CountEmblems(data), numemblems+numextraemblems)); V_DrawSmallScaledPatch(20, 64, 0, W_CachePatchName("EMBLICON", PU_PATCH)); sprintf(beststr, "%u", bestscore); @@ -9859,6 +9872,7 @@ static void M_HandleLevelStats(INT32 choice) // Drawing function for Time Attack void M_DrawTimeAttackMenu(void) { + gamedata_t *data = clientGamedata; INT32 i, x, y, empatx, empaty, cursory = 0; UINT16 dispstatus; patch_t *PictureOfUrFace; // my WHAT @@ -10017,7 +10031,7 @@ void M_DrawTimeAttackMenu(void) empatx = empatch->leftoffset / 2; empaty = empatch->topoffset / 2; - if (em->collected) + if (data->collected[em - emblemlocations]) V_DrawSmallMappedPatch(104+76+empatx, yHeight+lsheadingheight/2+empaty, 0, empatch, R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE)); else @@ -10030,34 +10044,34 @@ void M_DrawTimeAttackMenu(void) // Draw in-level emblems. M_DrawMapEmblems(cv_nextmap.value, 288, 28, true); - if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->score) + if (!data->mainrecords[cv_nextmap.value-1] || !data->mainrecords[cv_nextmap.value-1]->score) sprintf(beststr, "(none)"); else - sprintf(beststr, "%u", mainrecords[cv_nextmap.value-1]->score); + sprintf(beststr, "%u", data->mainrecords[cv_nextmap.value-1]->score); V_DrawString(104-72, 33+lsheadingheight/2, V_YELLOWMAP, "SCORE:"); V_DrawRightAlignedString(104+64, 33+lsheadingheight/2, V_ALLOWLOWERCASE, beststr); V_DrawRightAlignedString(104+72, 43+lsheadingheight/2, V_ALLOWLOWERCASE, reqscore); - if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->time) + if (!data->mainrecords[cv_nextmap.value-1] || !data->mainrecords[cv_nextmap.value-1]->time) sprintf(beststr, "(none)"); else - sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(mainrecords[cv_nextmap.value-1]->time, true), - G_TicsToSeconds(mainrecords[cv_nextmap.value-1]->time), - G_TicsToCentiseconds(mainrecords[cv_nextmap.value-1]->time)); + sprintf(beststr, "%i:%02i.%02i", G_TicsToMinutes(data->mainrecords[cv_nextmap.value-1]->time, true), + G_TicsToSeconds(data->mainrecords[cv_nextmap.value-1]->time), + G_TicsToCentiseconds(data->mainrecords[cv_nextmap.value-1]->time)); V_DrawString(104-72, 53+lsheadingheight/2, V_YELLOWMAP, "TIME:"); V_DrawRightAlignedString(104+64, 53+lsheadingheight/2, V_ALLOWLOWERCASE, beststr); V_DrawRightAlignedString(104+72, 63+lsheadingheight/2, V_ALLOWLOWERCASE, reqtime); - if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->rings) + if (!data->mainrecords[cv_nextmap.value-1] || !data->mainrecords[cv_nextmap.value-1]->rings) sprintf(beststr, "(none)"); else - sprintf(beststr, "%hu", mainrecords[cv_nextmap.value-1]->rings); + sprintf(beststr, "%hu", data->mainrecords[cv_nextmap.value-1]->rings); V_DrawString(104-72, 73+lsheadingheight/2, V_YELLOWMAP, "RINGS:"); - V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE|((mapvisited[cv_nextmap.value-1] & MV_PERFECTRA) ? V_YELLOWMAP : 0), beststr); + V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE|((data->mapvisited[cv_nextmap.value-1] & MV_PERFECTRA) ? V_YELLOWMAP : 0), beststr); V_DrawRightAlignedString(104+72, 83+lsheadingheight/2, V_ALLOWLOWERCASE, reqrings); } @@ -10151,6 +10165,7 @@ static void M_TimeAttack(INT32 choice) // Drawing function for Nights Attack void M_DrawNightsAttackMenu(void) { + gamedata_t *data = clientGamedata; INT32 i, x, y, cursory = 0; UINT16 dispstatus; @@ -10217,10 +10232,10 @@ void M_DrawNightsAttackMenu(void) lumpnum_t lumpnum; char beststr[40]; - //UINT8 bestoverall = G_GetBestNightsGrade(cv_nextmap.value, 0); - UINT8 bestgrade = G_GetBestNightsGrade(cv_nextmap.value, cv_dummymares.value); - UINT32 bestscore = G_GetBestNightsScore(cv_nextmap.value, cv_dummymares.value); - tic_t besttime = G_GetBestNightsTime(cv_nextmap.value, cv_dummymares.value); + //UINT8 bestoverall = G_GetBestNightsGrade(cv_nextmap.value, 0, data); + UINT8 bestgrade = G_GetBestNightsGrade(cv_nextmap.value, cv_dummymares.value, data); + UINT32 bestscore = G_GetBestNightsScore(cv_nextmap.value, cv_dummymares.value, data); + tic_t besttime = G_GetBestNightsTime(cv_nextmap.value, cv_dummymares.value, data); M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true, false); @@ -10301,7 +10316,7 @@ void M_DrawNightsAttackMenu(void) goto skipThisOne; } - if (em->collected) + if (data->collected[em - emblemlocations]) V_DrawSmallMappedPatch(xpos, yHeight+lsheadingheight/2, 0, W_CachePatchName(M_GetEmblemPatch(em, false), PU_PATCH), R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(em), GTC_CACHE)); else @@ -12544,12 +12559,12 @@ static void M_EraseDataResponse(INT32 ch) // Delete the data if (erasecontext != 1) - G_ClearRecords(); + G_ClearRecords(clientGamedata); if (erasecontext != 0) - M_ClearSecrets(); + M_ClearSecrets(clientGamedata); if (erasecontext == 2) { - totalplaytime = 0; + clientGamedata->totalplaytime = 0; F_StartIntro(); } BwehHehHe(); diff --git a/src/m_random.c b/src/m_random.c index 3d0774a60..8b5138b9c 100644 --- a/src/m_random.c +++ b/src/m_random.c @@ -14,12 +14,11 @@ #include "doomdef.h" #include "doomtype.h" -#include "doomstat.h" // totalplaytime #include "m_random.h" #include "m_fixed.h" - +#include "m_cond.h" // totalplaytime // --------------------------- // RNG functions (not synched) @@ -252,5 +251,5 @@ void P_SetRandSeedD(const char *rfile, INT32 rline, UINT32 seed) */ UINT32 M_RandomizedSeed(void) { - return ((totalplaytime & 0xFFFF) << 16)|M_RandomFixed(); + return ((serverGamedata->totalplaytime & 0xFFFF) << 16) | M_RandomFixed(); } diff --git a/src/p_inter.c b/src/p_inter.c index 873448dcd..f2d20912f 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -740,10 +740,22 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) { if (demoplayback || (player->bot && player->bot != BOT_MPAI) || special->health <= 0 || special->health > MAXEMBLEMS) return; - emblemlocations[special->health-1].collected = true; - M_UpdateUnlockablesAndExtraEmblems(); - G_SaveGameData(); + if (emblemlocations[special->health-1].type == ET_SKIN) + { + INT32 skinnum = M_EmblemSkinNum(&emblemlocations[special->health-1]); + + if (player->skin != skinnum) + { + return; + } + } + + clientGamedata->collected[special->health-1] = serverGamedata->collected[special->health-1] = true; + + M_SilentUpdateUnlockablesAndEmblems(serverGamedata); + M_UpdateUnlockablesAndExtraEmblems(clientGamedata); + G_SaveGameData(clientGamedata); break; } diff --git a/src/p_mobj.c b/src/p_mobj.c index eeaf54776..6563e6f0a 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12004,10 +12004,6 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) return false; // You already got this token break; - case MT_EMBLEM: - if (netgame || multiplayer) - return false; // Single player (You're next on my shit list) - break; default: break; } @@ -12175,15 +12171,20 @@ 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; - validEmblem = !emblemlocations[j].collected; + validEmblem = true; - if (emblemlocations[j].type == ET_SKIN) + if (!netgame) { - INT32 skinnum = M_EmblemSkinNum(&emblemlocations[j]); + validEmblem = !serverGamedata->collected[j]; - if (players[0].skin != skinnum) + if (emblemlocations[j].type == ET_SKIN && !multiplayer) { - validEmblem = false; + INT32 skinnum = M_EmblemSkinNum(&emblemlocations[j]); + + if (players[0].skin != skinnum) + { + validEmblem = false; + } } } diff --git a/src/p_saveg.c b/src/p_saveg.c index 8c8a78322..c18319c69 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -47,6 +47,7 @@ UINT8 *save_p; #define ARCHIVEBLOCK_POBJS 0x7F928546 #define ARCHIVEBLOCK_THINKERS 0x7F37037C #define ARCHIVEBLOCK_SPECIALS 0x7F228378 +#define ARCHIVEBLOCK_EMBLEMS 0x7F4A5445 // Note: This cannot be bigger // than an UINT16 @@ -4339,6 +4340,8 @@ static void P_NetArchiveMisc(boolean resending) WRITEUINT32(save_p, hidetime); + WRITEUINT32(save_p, unlocktriggers); + // Is it paused? if (paused) WRITEUINT8(save_p, 0x2f); @@ -4437,6 +4440,8 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) hidetime = READUINT32(save_p); + unlocktriggers = READUINT32(save_p); + // Is it paused? if (READUINT8(save_p) == 0x2f) paused = true; @@ -4444,6 +4449,224 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) return true; } + +static inline void P_NetArchiveEmblems(void) +{ + gamedata_t *data = serverGamedata; + INT32 i, j; + UINT8 btemp; + INT32 curmare; + + WRITEUINT32(save_p, ARCHIVEBLOCK_EMBLEMS); + + // These should be synchronized before savegame loading by the wad files being the same anyway, + // but just in case, for now, we'll leave them here for testing. It would be very bad if they mismatch. + WRITEUINT8(save_p, (UINT8)savemoddata); + WRITEINT32(save_p, numemblems); + WRITEINT32(save_p, numextraemblems); + + // The rest of this is lifted straight from G_SaveGameData in g_game.c + // TODO: Optimize this to only send information about emblems, unlocks, etc. which actually exist + // There is no need to go all the way up to MAXEMBLEMS when wads are guaranteed to be the same. + + WRITEUINT32(save_p, data->totalplaytime); + + // TODO put another cipher on these things? meh, I don't care... + for (i = 0; i < NUMMAPS; i++) + WRITEUINT8(save_p, (data->mapvisited[i] & MV_MAX)); + + // To save space, use one bit per collected/achieved/unlocked flag + for (i = 0; i < MAXEMBLEMS;) + { + btemp = 0; + for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j) + btemp |= (data->collected[j+i] << j); + WRITEUINT8(save_p, btemp); + i += j; + } + for (i = 0; i < MAXEXTRAEMBLEMS;) + { + btemp = 0; + for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j) + btemp |= (data->extraCollected[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 |= (data->unlocked[j+i] << j); + WRITEUINT8(save_p, btemp); + i += j; + } + for (i = 0; i < MAXCONDITIONSETS;) + { + btemp = 0; + for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j) + btemp |= (data->achieved[j+i] << j); + WRITEUINT8(save_p, btemp); + i += j; + } + + WRITEUINT32(save_p, data->timesBeaten); + WRITEUINT32(save_p, data->timesBeatenWithEmeralds); + WRITEUINT32(save_p, data->timesBeatenUltimate); + + // Main records + for (i = 0; i < NUMMAPS; i++) + { + if (data->mainrecords[i]) + { + WRITEUINT32(save_p, data->mainrecords[i]->score); + WRITEUINT32(save_p, data->mainrecords[i]->time); + WRITEUINT16(save_p, data->mainrecords[i]->rings); + } + else + { + WRITEUINT32(save_p, 0); + WRITEUINT32(save_p, 0); + WRITEUINT16(save_p, 0); + } + } + + // NiGHTS records + for (i = 0; i < NUMMAPS; i++) + { + if (!data->nightsrecords[i] || !data->nightsrecords[i]->nummares) + { + WRITEUINT8(save_p, 0); + continue; + } + + WRITEUINT8(save_p, data->nightsrecords[i]->nummares); + + for (curmare = 0; curmare < (data->nightsrecords[i]->nummares + 1); ++curmare) + { + WRITEUINT32(save_p, data->nightsrecords[i]->score[curmare]); + WRITEUINT8(save_p, data->nightsrecords[i]->grade[curmare]); + WRITEUINT32(save_p, data->nightsrecords[i]->time[curmare]); + } + } +} + +static inline void P_NetUnArchiveEmblems(void) +{ + gamedata_t *data = serverGamedata; + INT32 i, j; + UINT8 rtemp; + UINT32 recscore; + tic_t rectime; + UINT16 recrings; + UINT8 recmares; + INT32 curmare; + + if (READUINT32(save_p) != ARCHIVEBLOCK_EMBLEMS) + I_Error("Bad $$$.sav at archive block Emblems"); + + savemoddata = (boolean)READUINT8(save_p); // this one is actually necessary because savemoddata stays false otherwise for some reason. + + if (numemblems != READINT32(save_p)) + I_Error("numemblems mismatch"); + if (numextraemblems != READINT32(save_p)) + I_Error("numextraemblems mismatch"); + + // This shouldn't happen, but if something really fucked up happens and you transfer + // the SERVER player's gamedata over your own CLIENT gamedata, + // then this prevents it from being saved over yours. + data->loaded = false; + + M_ClearSecrets(data); + G_ClearRecords(data); + + // The rest of this is lifted straight from G_LoadGameData in g_game.c + // TODO: Optimize this to only read information about emblems, unlocks, etc. which actually exist + // There is no need to go all the way up to MAXEMBLEMS when wads are guaranteed to be the same. + + data->totalplaytime = READUINT32(save_p); + + // TODO put another cipher on these things? meh, I don't care... + for (i = 0; i < NUMMAPS; i++) + if ((data->mapvisited[i] = READUINT8(save_p)) > MV_MAX) + I_Error("Bad $$$.sav dearchiving Emblems"); + + // To save space, use one bit per collected/achieved/unlocked flag + for (i = 0; i < MAXEMBLEMS;) + { + rtemp = READUINT8(save_p); + for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j) + data->collected[j+i] = ((rtemp >> j) & 1); + i += j; + } + for (i = 0; i < MAXEXTRAEMBLEMS;) + { + rtemp = READUINT8(save_p); + for (j = 0; j < 8 && j+i < MAXEXTRAEMBLEMS; ++j) + data->extraCollected[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) + data->unlocked[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) + data->achieved[j+i] = ((rtemp >> j) & 1); + i += j; + } + + data->timesBeaten = READUINT32(save_p); + data->timesBeatenWithEmeralds = READUINT32(save_p); + data->timesBeatenUltimate = READUINT32(save_p); + + // Main records + for (i = 0; i < NUMMAPS; ++i) + { + recscore = READUINT32(save_p); + rectime = (tic_t)READUINT32(save_p); + recrings = READUINT16(save_p); + + if (recrings > 10000 || recscore > MAXSCORE) + I_Error("Bad $$$.sav dearchiving Emblems"); + + if (recscore || rectime || recrings) + { + G_AllocMainRecordData((INT16)i, data); + data->mainrecords[i]->score = recscore; + data->mainrecords[i]->time = rectime; + data->mainrecords[i]->rings = recrings; + } + } + + // Nights records + for (i = 0; i < NUMMAPS; ++i) + { + if ((recmares = READUINT8(save_p)) == 0) + continue; + + G_AllocNightsRecordData((INT16)i, data); + + for (curmare = 0; curmare < (recmares+1); ++curmare) + { + data->nightsrecords[i]->score[curmare] = READUINT32(save_p); + data->nightsrecords[i]->grade[curmare] = READUINT8(save_p); + data->nightsrecords[i]->time[curmare] = (tic_t)READUINT32(save_p); + + if (data->nightsrecords[i]->grade[curmare] > GRADE_S) + { + I_Error("Bad $$$.sav dearchiving Emblems"); + } + } + + data->nightsrecords[i]->nummares = recmares; + } +} + static inline void P_ArchiveLuabanksAndConsistency(void) { UINT8 i, banksinuse = NUM_LUABANKS; @@ -4507,6 +4730,7 @@ void P_SaveNetGame(boolean resending) CV_SaveNetVars(&save_p); P_NetArchiveMisc(resending); + P_NetArchiveEmblems(); // Assign the mobjnumber for pointer tracking for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) @@ -4559,6 +4783,7 @@ boolean P_LoadNetGame(boolean reloading) CV_LoadNetVars(&save_p); if (!P_NetUnArchiveMisc(reloading)) return false; + P_NetUnArchiveEmblems(); P_NetUnArchivePlayers(); if (gamestate == GS_LEVEL) { diff --git a/src/p_setup.c b/src/p_setup.c index c8b0936b8..70a2c0a8b 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7460,7 +7460,7 @@ static void P_WriteLetter(void) { char *buf, *b; - if (!unlockables[28].unlocked) // pandora's box + if (!serverGamedata->unlocked[28]) // pandora's box return; if (modeattacking) @@ -7804,10 +7804,11 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) nextmapoverride = 0; skipstats = 0; - if (!(netgame || multiplayer || demoplayback)) - mapvisited[gamemap-1] |= MV_VISITED; - else if (netgame || multiplayer) - mapvisited[gamemap-1] |= MV_MP; // you want to record that you've been there this session, but not permanently + if (!demoplayback) + { + clientGamedata->mapvisited[gamemap-1] |= MV_VISITED; + serverGamedata->mapvisited[gamemap-1] |= MV_VISITED; + } levelloading = false; diff --git a/src/p_spec.c b/src/p_spec.c index aa04a723e..48185e908 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2937,7 +2937,6 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) break; case 441: // Trigger unlockable - if (!(netgame || multiplayer)) { INT32 trigid = line->args[0]; @@ -2948,10 +2947,12 @@ static void P_ProcessLineSpecial(line_t *line, mobj_t *mo, sector_t *callsec) unlocktriggers |= 1 << trigid; // Unlocked something? - if (M_UpdateUnlockablesAndExtraEmblems()) + M_SilentUpdateUnlockablesAndEmblems(serverGamedata); + + if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata)) { S_StartSound(NULL, sfx_s3k68); - G_SaveGameData(); // only save if unlocked something + G_SaveGameData(clientGamedata); // only save if unlocked something } } } diff --git a/src/p_tick.c b/src/p_tick.c index 0357258e8..b1fd367ed 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -675,7 +675,10 @@ void P_Ticker(boolean run) // Keep track of how long they've been playing! if (!demoplayback) // Don't increment if a demo is playing. - totalplaytime++; + { + clientGamedata->totalplaytime++; + serverGamedata->totalplaytime++; + } if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap)) P_DoSpecialStageStuff(); diff --git a/src/r_skins.c b/src/r_skins.c index e59e085b8..2c031ee85 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -194,7 +194,7 @@ UINT32 R_GetSkinAvailabilities(void) return 0; } - if (unlockables[i].unlocked) + if (clientGamedata->unlocked[i]) { response |= (1 << unlockShift); } @@ -242,11 +242,12 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum) // Force 3. return true; } + if (playernum != -1 && players[playernum].bot) - { - //Force 4. - return true; - } + { + // Force 4. + return true; + } // We will now check if this skin is supposed to be locked or not. @@ -284,7 +285,7 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum) else { // We want to check our global unlockables. - return (unlockables[unlockID].unlocked); + return (clientGamedata->unlocked[unlockID]); } } diff --git a/src/s_sound.c b/src/s_sound.c index 111b6ce25..ada1a0fd2 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -1692,6 +1692,7 @@ UINT8 soundtestpage = 1; // boolean S_PrepareSoundTest(void) { + gamedata_t *data = clientGamedata; musicdef_t *def; INT32 pos = numsoundtestdefs = 0; @@ -1717,9 +1718,9 @@ boolean S_PrepareSoundTest(void) if (!(def->soundtestpage & soundtestpage)) continue; soundtestdefs[pos++] = def; - if (def->soundtestcond > 0 && !(mapvisited[def->soundtestcond-1] & MV_BEATEN)) + if (def->soundtestcond > 0 && !(data->mapvisited[def->soundtestcond-1] & MV_BEATEN)) continue; - if (def->soundtestcond < 0 && !M_Achieved(-1-def->soundtestcond)) + if (def->soundtestcond < 0 && !M_Achieved(-1-def->soundtestcond, data)) continue; def->allowed = true; } diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index 67ee8d668..66eeffa30 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -2352,7 +2352,7 @@ void I_Quit(void) #ifndef NONET D_SaveBan(); // save the ban list #endif - G_SaveGameData(); // Tails 12-08-2002 + G_SaveGameData(clientGamedata); // Tails 12-08-2002 //added:16-02-98: when recording a demo, should exit using 'q' key, // but sometimes we forget and use 'F10'.. so save here too. @@ -2436,7 +2436,7 @@ void I_Error(const char *error, ...) if (errorcount == 8) { M_SaveConfig(NULL); - G_SaveGameData(); + G_SaveGameData(clientGamedata); } if (errorcount > 20) { @@ -2469,7 +2469,7 @@ void I_Error(const char *error, ...) #ifndef NONET D_SaveBan(); // save the ban list #endif - G_SaveGameData(); // Tails 12-08-2002 + G_SaveGameData(clientGamedata); // Tails 12-08-2002 // Shutdown. Here might be other errors. if (demorecording) diff --git a/src/st_stuff.c b/src/st_stuff.c index 206c93273..1f0ca277f 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1697,7 +1697,7 @@ static void ST_drawNightsRecords(void) ST_DrawNightsOverlayNum((BASEVIDWIDTH/2 + 56)<lastmarescore, nightsnum, SKINCOLOR_AZURE); // If new record, say so! - if (!(netgame || multiplayer) && G_GetBestNightsScore(gamemap, stplyr->lastmare + 1) <= stplyr->lastmarescore) + if (!(netgame || multiplayer) && G_GetBestNightsScore(gamemap, stplyr->lastmare + 1, clientGamedata) <= stplyr->lastmarescore) { if (stplyr->texttimer & 16) V_DrawCenteredString(BASEVIDWIDTH/2, 184, V_YELLOWMAP|aflag, "* NEW RECORD *"); @@ -2563,8 +2563,11 @@ static void ST_doItemFinderIconsAndSound(void) emblems[stemblems++] = i; - if (!emblemlocations[i].collected) + if (!(clientGamedata->collected[i] && serverGamedata->collected[i])) + { + // It can be worth collecting again if the server doesn't have it. ++stunfound; + } if (stemblems >= 16) break; @@ -2723,7 +2726,7 @@ static void ST_overlayDrawer(void) ST_drawRaceHUD(); // Emerald Hunt Indicators - if (cv_itemfinder.value && M_SecretUnlocked(SECRET_ITEMFINDER)) + if (cv_itemfinder.value && M_SecretUnlocked(SECRET_ITEMFINDER, clientGamedata)) ST_doItemFinderIconsAndSound(); else ST_doHuntIconsAndSound(); diff --git a/src/y_inter.c b/src/y_inter.c index 02d01233e..6e7d362a7 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1092,12 +1092,14 @@ void Y_Ticker(void) S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching! // Update when done with tally - if (!(netgame || multiplayer) && !demoplayback) + if (!demoplayback) { - if (M_UpdateUnlockablesAndExtraEmblems()) + M_SilentUpdateUnlockablesAndEmblems(serverGamedata); + + if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata)) S_StartSound(NULL, sfx_s3k68); - G_SaveGameData(); + G_SaveGameData(clientGamedata); } } else if (!(intertic & 1)) @@ -1228,12 +1230,14 @@ void Y_Ticker(void) S_StartSound(NULL, (gottoken ? sfx_token : sfx_chchng)); // cha-ching! // Update when done with tally - if (!(netgame || multiplayer) && !demoplayback) + if (!demoplayback) { - if (M_UpdateUnlockablesAndExtraEmblems()) + M_SilentUpdateUnlockablesAndEmblems(serverGamedata); + + if (M_UpdateUnlockablesAndExtraEmblems(clientGamedata)) S_StartSound(NULL, sfx_s3k68); - G_SaveGameData(); + G_SaveGameData(clientGamedata); } } else if (!(intertic & 1))