From d95ac73f3bb0b1336d55afe181b4dcda1fc88433 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 24 Dec 2022 17:03:36 +0000 Subject: [PATCH 001/128] Remove Gametype Preference and Voting Rule Change cvars * The consensus has moved against gametype changes in voting, so strip it out * Encorescramble already independently controls setting Encore, so don't keep the outdated randomisation method Done in this branch because it uses gametype constants directly, both in a cvar and function, and I want to get rid of as many of those as possible --- src/d_clisrv.c | 5 +--- src/d_netcmd.c | 17 ++++-------- src/d_netcmd.h | 2 -- src/g_game.c | 74 ++++++++----------------------------------------- src/g_game.h | 2 +- src/k_kart.c | 2 -- src/k_menudef.c | 3 -- 7 files changed, 19 insertions(+), 86 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 27a489916..224ae709d 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -899,9 +899,6 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) UINT8 *p; size_t mirror_length; const char *httpurl = cv_httpsource.string; - UINT8 prefgametype = (cv_kartgametypepreference.value == -1) - ? gametype - : cv_kartgametypepreference.value; netbuffer->packettype = PT_SERVERINFO; netbuffer->u.serverinfo._255 = 255; @@ -933,7 +930,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) else netbuffer->u.serverinfo.refusereason = 0; - strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[prefgametype], + strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[gametype], sizeof netbuffer->u.serverinfo.gametypename); netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame; netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled(); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 9679b5631..a8241c14f 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -399,10 +399,6 @@ consvar_t cv_kartbumpers = CVAR_INIT ("battlebumpers", "3", CV_NETVAR, kartbumpe consvar_t cv_kartfrantic = CVAR_INIT ("franticitems", "Off", CV_NETVAR|CV_CALL|CV_NOINIT, CV_OnOff, KartFrantic_OnChange); static CV_PossibleValue_t kartencore_cons_t[] = {{-1, "Auto"}, {0, "Off"}, {1, "On"}, {0, NULL}}; consvar_t cv_kartencore = CVAR_INIT ("encore", "Auto", CV_NETVAR|CV_CALL|CV_NOINIT, kartencore_cons_t, KartEncore_OnChange); -static CV_PossibleValue_t kartvoterulechanges_cons_t[] = {{0, "Never"}, {1, "Sometimes"}, {2, "Frequent"}, {3, "Always"}, {0, NULL}}; -consvar_t cv_kartvoterulechanges = CVAR_INIT ("voterulechanges", "Frequent", CV_NETVAR, kartvoterulechanges_cons_t, NULL); -static CV_PossibleValue_t kartgametypepreference_cons_t[] = {{-1, "None"}, {GT_RACE, "Race"}, {GT_BATTLE, "Battle"}, {0, NULL}}; -consvar_t cv_kartgametypepreference = CVAR_INIT ("gametypepreference", "None", CV_NETVAR, kartgametypepreference_cons_t, NULL); static CV_PossibleValue_t kartspeedometer_cons_t[] = {{0, "Off"}, {1, "Percentage"}, {2, "Kilometers"}, {3, "Miles"}, {4, "Fracunits"}, {0, NULL}}; consvar_t cv_kartspeedometer = CVAR_INIT ("speedometer", "Percentage", CV_SAVE, kartspeedometer_cons_t, NULL); // use tics in display static CV_PossibleValue_t kartvoices_cons_t[] = {{0, "Never"}, {1, "Tasteful"}, {2, "Meme"}, {0, NULL}}; @@ -2596,14 +2592,13 @@ void D_SetupVote(void) UINT8 buf[5*2]; // four UINT16 maps (at twice the width of a UINT8), and two gametypes UINT8 *p = buf; INT32 i; - UINT8 gt = (cv_kartgametypepreference.value == -1) ? gametype : cv_kartgametypepreference.value; - UINT8 secondgt = G_SometimesGetDifferentGametype(gt); + UINT8 secondgt = G_SometimesGetDifferentGametype(); INT16 votebuffer[4] = {-1,-1,-1,0}; - if ((cv_kartencore.value == 1) && (gametypedefaultrules[gt] & GTR_CIRCUIT)) - WRITEUINT8(p, (gt|VOTEMODIFIER_ENCORE)); + if ((cv_kartencore.value == 1) && (gametypedefaultrules[gametype] & GTR_CIRCUIT)) + WRITEUINT8(p, (gametype|VOTEMODIFIER_ENCORE)); else - WRITEUINT8(p, gt); + WRITEUINT8(p, gametype); WRITEUINT8(p, secondgt); secondgt &= ~VOTEMODIFIER_ENCORE; @@ -2613,9 +2608,9 @@ void D_SetupVote(void) if (i == 2) // sometimes a different gametype m = G_RandMap(G_TOLFlag(secondgt), prevmap, ((secondgt != gametype) ? 2 : 0), 0, true, votebuffer); else if (i >= 3) // unknown-random and formerly force-unknown MAP HELL - m = G_RandMap(G_TOLFlag(gt), prevmap, 0, (i-2), (i < 4), votebuffer); + m = G_RandMap(G_TOLFlag(gametype), prevmap, 0, (i-2), (i < 4), votebuffer); else - m = G_RandMap(G_TOLFlag(gt), prevmap, 0, 0, true, votebuffer); + m = G_RandMap(G_TOLFlag(gametype), prevmap, 0, 0, true, votebuffer); if (i < 3) votebuffer[i] = m; WRITEUINT16(p, m); diff --git a/src/d_netcmd.h b/src/d_netcmd.h index ee1fb6ec7..7fcdfb4b3 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -79,8 +79,6 @@ extern consvar_t cv_kartspeed; extern consvar_t cv_kartbumpers; extern consvar_t cv_kartfrantic; extern consvar_t cv_kartencore; -extern consvar_t cv_kartvoterulechanges; -extern consvar_t cv_kartgametypepreference; extern consvar_t cv_kartspeedometer; extern consvar_t cv_kartvoices; extern consvar_t cv_kartbot; diff --git a/src/g_game.c b/src/g_game.c index fb49b598c..8df843a1f 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -318,9 +318,8 @@ typedef struct { INT16 *mapbuffer; // Pointer to zone memory INT32 lastnummapheaders; // Reset if nummapheaders != this - UINT8 counttogametype; // Time to gametype change event } randmaps_t; -static randmaps_t randmaps = {NULL, 0, 0}; +static randmaps_t randmaps = {NULL, 0}; static void G_ResetRandMapBuffer(void) { @@ -330,7 +329,6 @@ static void G_ResetRandMapBuffer(void) randmaps.mapbuffer = Z_Malloc(randmaps.lastnummapheaders * sizeof(INT16), PU_STATIC, NULL); for (i = 0; i < randmaps.lastnummapheaders; i++) randmaps.mapbuffer[i] = -1; - //intentionally not resetting randmaps.counttogametype here } typedef struct joystickvector2_s @@ -3294,80 +3292,30 @@ boolean G_GametypeHasSpectators(void) // // G_SometimesGetDifferentGametype // -// Oh, yeah, and we sometimes flip encore mode on here too. +// Because gametypes are no longer on the vote screen, all this does is sometimes flip encore mode. +// However, it remains a seperate function for long-term possibility. // -INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype) +INT16 G_SometimesGetDifferentGametype(void) { - // Most of the gametype references in this condition are intentionally not prefgametype. - // This is so a server CAN continue playing a gametype if they like the taste of it. - // The encore check needs prefgametype so can't use G_RaceGametype... boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE, false) || encorescramble == 1) - && ((gametyperules|gametypedefaultrules[prefgametype]) & GTR_CIRCUIT)); + && ((gametyperules|gametypedefaultrules[gametype]) & GTR_CIRCUIT)); UINT8 encoremodifier = 0; // -- the below is only necessary if you want to use randmaps.mapbuffer here //if (randmaps.lastnummapheaders != nummapheaders) //G_ResetRandMapBuffer(); - if (encorepossible) + // FORCE to what was scrambled on intermission? + if (encorepossible && encorescramble != -1) { - if (encorescramble != -1) + // FORCE to what was scrambled on intermission + if ((encorescramble != 0) != (cv_kartencore.value == 1)) { - encorepossible = (boolean)encorescramble; // FORCE to what was scrambled on intermission - } - else - { - switch (cv_kartvoterulechanges.value) - { - case 3: // always - encorepossible = true; - break; - case 2: // frequent - encorepossible = M_RandomChance(FRACUNIT>>1); - break; - case 1: // sometimes - encorepossible = M_RandomChance(FRACUNIT>>2); - break; - default: - break; - } - } - if (encorepossible != (cv_kartencore.value == 1)) encoremodifier = VOTEMODIFIER_ENCORE; + } } - if (!cv_kartvoterulechanges.value) // never - return (gametype|encoremodifier); - - if (randmaps.counttogametype > 0 && (cv_kartvoterulechanges.value != 3)) - { - randmaps.counttogametype--; - return (gametype|encoremodifier); - } - - switch (cv_kartvoterulechanges.value) // okay, we're having a gametype change! when's the next one, luv? - { - case 1: // sometimes - randmaps.counttogametype = 5; // per "cup" - break; - default: - // fallthrough - happens when clearing buffer, but needs a reasonable countdown if cvar is modified - case 2: // frequent - randmaps.counttogametype = 2; // ...every 1/2th-ish cup? - break; - } - - // Only this response is prefgametype-based. - // todo custom gametypes - if (prefgametype == GT_BATTLE) - { - // Intentionally does not use encoremodifier! - if (cv_kartencore.value == 1) - return (GT_RACE|VOTEMODIFIER_ENCORE); - return (GT_RACE); - } - // This might appear wrong HERE, but the game will display the Encore possibility on the second voting choice instead. - return (GT_BATTLE|encoremodifier); + return (gametype|encoremodifier); } // diff --git a/src/g_game.h b/src/g_game.h index 0108cc06d..3a8d77941 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -195,7 +195,7 @@ boolean G_GametypeUsesLives(void); boolean G_GametypeHasTeams(void); boolean G_GametypeHasSpectators(void); #define VOTEMODIFIER_ENCORE 0x80 -INT16 G_SometimesGetDifferentGametype(UINT8 prefgametype); +INT16 G_SometimesGetDifferentGametype(void); UINT8 G_GetGametypeColor(INT16 gt); void G_ExitLevel(void); void G_NextLevel(void); diff --git a/src/k_kart.c b/src/k_kart.c index f1c0b8be2..88a07bc3e 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -310,8 +310,6 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartbumpers); CV_RegisterVar(&cv_kartfrantic); CV_RegisterVar(&cv_kartencore); - CV_RegisterVar(&cv_kartvoterulechanges); - CV_RegisterVar(&cv_kartgametypepreference); CV_RegisterVar(&cv_kartspeedometer); CV_RegisterVar(&cv_kartvoices); CV_RegisterVar(&cv_kartbot); diff --git a/src/k_menudef.c b/src/k_menudef.c index 99903d788..f100db28d 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -1132,9 +1132,6 @@ menuitem_t OPTIONS_Server[] = {IT_STRING | IT_CVAR, "Vote Timer", "Set how long players have to vote.", NULL, {.cvar = &cv_votetime}, 0, 0}, - {IT_STRING | IT_CVAR, "Vote Mode Change", "Set how often voting proposes a different gamemode.", - NULL, {.cvar = &cv_kartvoterulechanges}, 0, 0}, - {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, From ab63be694a3835b465f2e1a884ccedd31da1c751 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 24 Dec 2022 21:01:25 +0000 Subject: [PATCH 002/128] Correct NUMBASETOLNAMES, which was incorrectly set too low. --- src/doomstat.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/doomstat.h b/src/doomstat.h index 045924213..d5559361b 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -525,7 +525,7 @@ enum TypeOfLevel }; #define MAXTOL (1<<31) -#define NUMBASETOLNAMES (4) +#define NUMBASETOLNAMES (5) #define NUMTOLNAMES (NUMBASETOLNAMES + NUMGAMETYPEFREESLOTS) struct tolinfo_t From fbc3af20967b382f5c3d6f9cf8d1f991e1263405 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 24 Dec 2022 21:21:03 +0000 Subject: [PATCH 003/128] Pre-emptive fixes for custom gametypes - Handle mismatched gametypes for client and server on voting screen - I_Error when running out of gametypes - Reduce gametype freeslots slightly to avoid colliding with VOTEMODIFIER_ENCORE --- src/d_netcmd.c | 18 ++++++++++++++++++ src/deh_soc.c | 2 +- src/doomstat.h | 4 ++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index a8241c14f..bcab54eef 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -5313,6 +5313,24 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) gt &= ~VOTEMODIFIER_ENCORE; } + if ((gt & ~VOTEMODIFIER_ENCORE) >= gametypecount) + { + gt &= ~VOTEMODIFIER_ENCORE; + if (server) + I_Error("Got_SetupVotecmd: Internal gametype ID %d not found (gametypecount = %d)", gt, gametypecount); + CONS_Alert(CONS_WARNING, M_GetText("Vote setup with bad gametype ID %d received from %s\n"), gt, player_names[playernum]); + return; + } + + if ((secondgt & ~VOTEMODIFIER_ENCORE) >= gametypecount) + { + secondgt &= ~VOTEMODIFIER_ENCORE; + if (server) + I_Error("Got_SetupVotecmd: Internal second gametype ID %d not found (gametypecount = %d)", secondgt, gametypecount); + CONS_Alert(CONS_WARNING, M_GetText("Vote setup with bad second gametype ID %d received from %s\n"), secondgt, player_names[playernum]); + return; + } + for (i = 0; i < 4; i++) { tempvotelevels[i][0] = (UINT16)READUINT16(*cp); diff --git a/src/deh_soc.c b/src/deh_soc.c index 1d7b94214..9d39652f0 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -881,7 +881,7 @@ void readgametype(MYFILE *f, char *gtname) // Ran out of gametype slots if (gametypecount == NUMGAMETYPEFREESLOTS) { - CONS_Alert(CONS_WARNING, "Ran out of free gametype slots!\n"); + I_Error("Out of Gametype Freeslots while allocating \"%s\"\nLoad less addons to fix this.", gtname); return; } diff --git a/src/doomstat.h b/src/doomstat.h index d5559361b..5eae8208f 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -454,7 +454,7 @@ extern mapheader_t** mapheaderinfo; extern INT32 nummapheaders, mapallocsize; // Gametypes -#define NUMGAMETYPEFREESLOTS 128 +#define NUMGAMETYPEFREESLOTS (NUMGAMETYPES-GT_FIRSTFREESLOT) enum GameType { @@ -462,7 +462,7 @@ enum GameType GT_BATTLE, GT_FIRSTFREESLOT, - GT_LASTFREESLOT = GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1, + GT_LASTFREESLOT = 127, // Previously (GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1) - it would be necessary to rewrite VOTEMODIFIER_ENCORE to go higher than this. NUMGAMETYPES }; // If you alter this list, update deh_tables.c, MISC_ChangeGameTypeMenu in m_menu.c, and Gametype_Names in g_game.c From 8431e526871b1694a7014f77fde1921e664b9dc0 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 24 Dec 2022 22:43:00 +0000 Subject: [PATCH 004/128] All playsim-internal distinctions between Race and Battle are now gametype rules. New: - GTR_POWERSTONES - Handles spawning in Battle Emeralds (currently only works stacked with GTR_PAPERITEMS) - GTR_ENCORE - Codifies that Race can use Encore and Battle can't. - GTR_CLOSERPLAYERS - A gametype where players are encouraged/expected to be closer together. (All of the following was GT_BATTLE specific) - Drafting/tether has increased strength/effective distance - Spindashing is stronger - Invincibility chaining has less effect - Grow has a lower total duration - Flame shield is more uncontrollable Extra functionality - GTR_CAPSULES - Prevents usage of lives in Grand Prix (so Race, and the upcoming Special and Boss gametypes, can have 'em) - GTR_CIRCUIT - When not present, Flame Shield has perma-full meter - When not present, overrides gamespeed with KARTSPEED_EASY - Presence of Best Lap sticker in Time Attack menu - Seperation between Time Attack and Break The Capsules modeattacking roulettes - GTR_POINTLIMIT - Handles the switch between a gametype recording/displaying Times and Scores in a few places - Handles displaying "WANTED" players on the minimap Missing simple substitutions - A whole bunch of cases where player->bumpers was checked with gametype == GT_BATTLE rather than GTR_BUMPER - GTR_OVERTIME handles the overtime special icon on the minimap - GTR_BATTLESTARTS is honoured in K_DoIngamerespawn - The Replay hut is closer to supporting custom gametypes Removals - GTR_LIVES - GTR_SPECIALBOTS - Given that grand prix persists between modes, these are special game-controlled features and not gametype-specific. - GTR_WANTED - WANTED as it existed is functionally dead --- src/d_netcmd.c | 10 +++--- src/discord.c | 2 +- src/doomstat.h | 9 ++--- src/g_game.c | 14 +++----- src/k_battle.c | 7 +++- src/k_hud.c | 48 +++++++++++++-------------- src/k_kart.c | 86 ++++++++++++++++++++++-------------------------- src/k_menudraw.c | 74 +++++++++++++++++++++++++---------------- src/k_respawn.c | 18 ++++------ src/k_roulette.c | 28 +++++++--------- src/p_inter.c | 2 +- src/p_mobj.c | 8 ++--- src/p_setup.c | 7 ++-- 13 files changed, 156 insertions(+), 157 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index bcab54eef..ffc53e02d 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2595,7 +2595,7 @@ void D_SetupVote(void) UINT8 secondgt = G_SometimesGetDifferentGametype(); INT16 votebuffer[4] = {-1,-1,-1,0}; - if ((cv_kartencore.value == 1) && (gametypedefaultrules[gametype] & GTR_CIRCUIT)) + if ((cv_kartencore.value == 1) && (gametypedefaultrules[gametype] & GTR_ENCORE)) WRITEUINT8(p, (gametype|VOTEMODIFIER_ENCORE)); else WRITEUINT8(p, gametype); @@ -3029,7 +3029,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) else if (gametype != lastgametype) D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype - if (!(gametyperules & GTR_CIRCUIT) && !bossinfo.boss) + if (!(gametyperules & GTR_ENCORE) && !bossinfo.boss) pencoremode = false; skipprecutscene = ((flags & (1<<2)) != 0); @@ -5308,7 +5308,7 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) // Strip illegal Encore flag. if ((gt & VOTEMODIFIER_ENCORE) - && !(gametypedefaultrules[(gt & ~VOTEMODIFIER_ENCORE)] & GTR_CIRCUIT)) + && !(gametypedefaultrules[(gt & ~VOTEMODIFIER_ENCORE)] & GTR_ENCORE)) { gt &= ~VOTEMODIFIER_ENCORE; } @@ -5346,11 +5346,11 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) // If third entry has an illelegal Encore flag... (illelegal!?) if ((secondgt & VOTEMODIFIER_ENCORE) - && !(gametypedefaultrules[(secondgt & ~VOTEMODIFIER_ENCORE)] & GTR_CIRCUIT)) + && !(gametypedefaultrules[(secondgt & ~VOTEMODIFIER_ENCORE)] & GTR_ENCORE)) { secondgt &= ~VOTEMODIFIER_ENCORE; // Apply it to the second entry instead, gametype permitting! - if (gametypedefaultrules[gt] & GTR_CIRCUIT) + if (gametypedefaultrules[gt] & GTR_ENCORE) { tempvotelevels[1][1] |= VOTEMODIFIER_ENCORE; } diff --git a/src/discord.c b/src/discord.c index 28381d250..b5c6a5a88 100644 --- a/src/discord.c +++ b/src/discord.c @@ -500,7 +500,7 @@ void DRPC_UpdatePresence(void) { snprintf(detailstr, 48, "%s%s%s", gametype_cons_t[gametype].strvalue, - (gametype == GT_RACE) ? va(" | %s", kartspeed_cons_t[gamespeed].strvalue) : "", + (gametyperules & GTR_CIRCUIT) ? va(" | %s", kartspeed_cons_t[gamespeed].strvalue) : "", (encoremode == true) ? " | Encore" : "" ); discordPresence.details = detailstr; diff --git a/src/doomstat.h b/src/doomstat.h index 5eae8208f..038ce8572 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -478,10 +478,10 @@ enum GameTypeRules GTR_BUMPERS = 1<<3, // Enables the bumper health system GTR_SPHERES = 1<<4, // Replaces rings with blue spheres GTR_PAPERITEMS = 1<<5, // Replaces item boxes with paper item spawners - GTR_WANTED = 1<<6, // unused + GTR_POWERSTONES = 1<<6, // Battle Emerald collectables. GTR_KARMA = 1<<7, // Enables the Karma system if you're out of bumpers GTR_ITEMARROWS = 1<<8, // Show item box arrows above players - GTR_CAPSULES = 1<<9, // Enables the wanted anti-camping system + GTR_CAPSULES = 1<<9, // Can enter Break The Capsules mode GTR_BATTLESTARTS = 1<<10, // Use Battle Mode start positions. GTR_POINTLIMIT = 1<<11, // Reaching point limit ends the round @@ -495,10 +495,11 @@ enum GameTypeRules // Grand Prix rules GTR_CAMPAIGN = 1<<17, // Handles cup-based progression - GTR_LIVES = 1<<18, // Lives system, players are forced to spectate during Game Over. - GTR_SPECIALBOTS = 1<<19, // Bot difficulty gets stronger between rounds, and the rival system is enabled. + // To be rearranged later GTR_NOCUPSELECT = 1<<20, // Your maps are not selected via cup. ...mutually exclusive with GTR_CAMPAIGN. + GTR_CLOSERPLAYERS = 1<<21, // Buffs spindash and draft power to bring everyone together, nerfs invincibility and grow to prevent excessive combos + GTR_ENCORE = 1<<22, // Alternate Encore mirroring, scripting, and texture remapping // free: to and including 1<<31 }; diff --git a/src/g_game.c b/src/g_game.c index 8df843a1f..64afccba9 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3016,9 +3016,9 @@ const char *Gametype_ConstantNames[NUMGAMETYPES] = UINT32 gametypedefaultrules[NUMGAMETYPES] = { // Race - GTR_CAMPAIGN|GTR_CIRCUIT|GTR_BOTS, + GTR_CAMPAIGN|GTR_CIRCUIT|GTR_BOTS|GTR_ENCORE, // Battle - GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_KARMA|GTR_ITEMARROWS|GTR_CAPSULES|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME + GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_POWERSTONES|GTR_KARMA|GTR_ITEMARROWS|GTR_CAPSULES|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_CLOSERPLAYERS }; // @@ -3241,13 +3241,7 @@ boolean G_GametypeUsesLives(void) return false; if ((grandprixinfo.gp == true) // In Grand Prix - && (gametype == GT_RACE) // NOT in bonus round - && grandprixinfo.eventmode == GPEVENT_NONE) // NOT in bonus - { - return true; - } - - if (bossinfo.boss == true) // Fighting a boss? + && !(gametyperules & GTR_CAPSULES)) // NOT in Break The Capsules { return true; } @@ -3298,7 +3292,7 @@ boolean G_GametypeHasSpectators(void) INT16 G_SometimesGetDifferentGametype(void) { boolean encorepossible = ((M_SecretUnlocked(SECRET_ENCORE, false) || encorescramble == 1) - && ((gametyperules|gametypedefaultrules[gametype]) & GTR_CIRCUIT)); + && (gametyperules & GTR_ENCORE)); UINT8 encoremodifier = 0; // -- the below is only necessary if you want to use randmaps.mapbuffer here diff --git a/src/k_battle.c b/src/k_battle.c index 15b81f81c..781ff108c 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -196,6 +196,11 @@ void K_CheckEmeralds(player_t *player) { UINT8 i; + if (!(gametyperules & GTR_POWERSTONES)) + { + return; + } + if (!ALLCHAOSEMERALDS(player->emeralds)) { return; @@ -341,7 +346,7 @@ void K_RunPaperItemSpawners(void) const boolean overtime = (battleovertime.enabled >= 10*TICRATE); tic_t interval = 8*TICRATE; - const boolean canmakeemeralds = true; //(!(battlecapsules || bossinfo.boss)); + const boolean canmakeemeralds = (gametyperules & GTR_POWERSTONES); UINT32 emeraldsSpawned = 0; UINT32 firstUnspawnedEmerald = 0; diff --git a/src/k_hud.c b/src/k_hud.c index 8da9ca089..d55c4d822 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1863,7 +1863,7 @@ static boolean K_drawKartPositionFaces(void) ranklines--; i = ranklines; - if (gametype == GT_BATTLE || strank <= 2) // too close to the top, or playing battle, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2) + if ((gametyperules & GTR_POINTLIMIT) || strank <= 2) // too close to the top, or playing battle, or a spectator? would have had (strank == -1) called out, but already caught by (strank <= 2) { if (i > 4) // could be both... i = 4; @@ -1951,7 +1951,7 @@ static boolean K_drawKartPositionFaces(void) if (i == strank) V_DrawScaledPatch(FACE_X, Y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facehighlight[(leveltime / 4) % 8]); - if (gametype == GT_BATTLE && players[rankplayer[i]].bumpers <= 0) + if ((gametyperules & GTR_BUMPERS) && players[rankplayer[i]].bumpers <= 0) V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_ranknobumpers); else { @@ -2265,7 +2265,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN colormap = R_GetTranslationColormap(players[tab[i].num].skin, players[tab[i].num].mo->color, GTC_CACHE); V_DrawMappedPatch(x, y-4, 0, faceprefix[players[tab[i].num].skin][FACE_RANK], colormap); - /*if (gametype == GT_BATTLE && players[tab[i].num].bumpers > 0) -- not enough space for this + /*if ((gametyperules & GTR_BUMPERS) && players[tab[i].num].bumpers > 0) -- not enough space for this { INT32 bumperx = x+19; V_DrawMappedPatch(bumperx-2, y-4, 0, kp_tinybumper[0], colormap); @@ -2280,7 +2280,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN if (tab[i].num == whiteplayer) V_DrawScaledPatch(x, y-4, 0, kp_facehighlight[(leveltime / 4) % 8]); - if (gametype == GT_BATTLE && players[tab[i].num].bumpers <= 0) + if ((gametyperules & GTR_BUMPERS) && players[tab[i].num].bumpers <= 0) V_DrawScaledPatch(x-4, y-7, 0, kp_ranknobumpers); else { @@ -2291,7 +2291,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN V_DrawScaledPatch(x-5, y+6, 0, kp_facenum[pos]); } - if (gametype == GT_RACE) + if ((gametyperules & GTR_CIRCUIT)) { #define timestring(time) va("%i'%02i\"%02i", G_TicsToMinutes(time, true), G_TicsToSeconds(time), G_TicsToCentiseconds(time)) if (scorelines >= 8) @@ -2801,6 +2801,7 @@ static void K_drawKartBumpersOrKarma(void) } } +#if 0 static void K_drawKartWanted(void) { UINT8 i, numwanted = 0; @@ -2875,6 +2876,7 @@ static void K_drawKartWanted(void) } } } +#endif //if 0 static void K_drawKartPlayerCheck(void) { @@ -3810,7 +3812,7 @@ static void K_drawKartMinimap(void) y -= SHORT(AutomapPic->topoffset); // Draw the super item in Battle - if (gametype == GT_BATTLE && battleovertime.enabled) + if ((gametyperules & GTR_OVERTIME) && battleovertime.enabled) { if (battleovertime.enabled >= 10*TICRATE || (battleovertime.enabled & 1)) { @@ -3920,8 +3922,8 @@ static void K_drawKartMinimap(void) K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, faceprefix[skin][FACE_MINIMAP], colormap, AutomapPic); // Target reticule - if ((gametype == GT_RACE && players[i].position == spbplace) - || (gametype == GT_BATTLE && K_IsPlayerWanted(&players[i]))) + if (((gametyperules & GTR_CIRCUIT) && players[i].position == spbplace) + || ((gametyperules & GTR_POINTLIMIT) && K_IsPlayerWanted(&players[i]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); } @@ -4047,8 +4049,8 @@ static void K_drawKartMinimap(void) K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, workingPic, colormap, AutomapPic); // Target reticule - if ((gametype == GT_RACE && players[localplayers[i]].position == spbplace) - || (gametype == GT_BATTLE && K_IsPlayerWanted(&players[localplayers[i]]))) + if (((gametyperules & GTR_CIRCUIT) && players[localplayers[i]].position == spbplace) + || ((gametyperules & GTR_POINTLIMIT) && K_IsPlayerWanted(&players[localplayers[i]]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); } @@ -5043,11 +5045,13 @@ void K_drawKartHUD(void) K_drawKartNameTags(); // Draw WANTED status +#if 0 if (gametype == GT_BATTLE) { if (LUA_HudEnabled(hud_wanted)) K_drawKartWanted(); } +#endif if (LUA_HudEnabled(hud_minimap)) K_drawKartMinimap(); @@ -5098,21 +5102,15 @@ void K_drawKartHUD(void) { K_drawBossHealthBar(); } - else if (gametype == GT_RACE) // Race-only elements (not currently gametyperuleable) + else if (freecam) + ; + else if ((gametyperules & GTR_POWERSTONES)) { - if (!islonesome) - { - // Draw the numerical position - K_DrawKartPositionNum(stplyr->position); - } - } - else if (gametype == GT_BATTLE) // Battle-only (ditto) - { - if (!freecam && !battlecapsules) - { + if (!battlecapsules) K_drawKartEmeralds(); - } } + else if (!islonesome) + K_DrawKartPositionNum(stplyr->position); } if (LUA_HudEnabled(hud_gametypeinfo)) @@ -5172,10 +5170,12 @@ void K_drawKartHUD(void) } // Race overlays - if (gametype == GT_RACE && !freecam) + if (!freecam) { if (stplyr->exiting) K_drawKartFinish(true); + else if (!(gametyperules & GTR_CIRCUIT)) + ; else if (stplyr->karthud[khud_lapanimation] && !r_splitscreen) K_drawLapStartAnim(); } @@ -5187,7 +5187,7 @@ void K_drawKartHUD(void) if (modeattacking || freecam) // everything after here is MP and debug only return; - if (gametype == GT_BATTLE && !r_splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM * + if ((gametyperules & GTR_KARMA) && !r_splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM * V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); // Draw FREE PLAY. diff --git a/src/k_kart.c b/src/k_kart.c index 88a07bc3e..932ac0b28 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1270,10 +1270,9 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed player->draftpower += add; } - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) { - // TODO: gametyperules - // Double speed in Battle + // Double speed in smaller environments player->draftpower += add; } } @@ -1333,9 +1332,8 @@ static void K_UpdateDraft(player_t *player) minDist = 640 * player->mo->scale; - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) { - // TODO: gametyperules minDist /= 4; draftdistance *= 2; leniency *= 4; @@ -2452,7 +2450,7 @@ void K_PlayOvertakeSound(mobj_t *source) { boolean tasteful = (!source->player || !source->player->karthud[khud_voices]); - if (!gametype == GT_RACE) // Only in race + if (!(gametyperules & GTR_CIRCUIT)) // Only in race return; // 4 seconds from before race begins, 10 seconds afterwards @@ -2961,8 +2959,7 @@ fixed_t K_GetSpindashChargeSpeed(player_t *player) // (can be higher than this value when overcharged) const fixed_t val = (10*FRACUNIT/277) + (((player->kartspeed + player->kartweight) + 2) * FRACUNIT) / 45; - // TODO: gametyperules - return (gametype == GT_BATTLE) ? (4 * val) : val; + return (gametyperules & GTR_CLOSERPLAYERS) ? (4 * val) : val; } // sets boostpower, speedboost, accelboost, and handleboost to whatever we need it to be @@ -3097,9 +3094,8 @@ static void K_GetKartBoostPower(player_t *player) // 30% - 44%, each point of speed adds 1.75% fixed_t draftspeed = ((3*FRACUNIT)/10) + ((player->kartspeed-1) * ((7*FRACUNIT)/400)); - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) { - // TODO: gametyperules draftspeed *= 2; } @@ -3222,7 +3218,7 @@ fixed_t K_GetKartAccel(player_t *player) k_accel += 17 * (9 - player->kartspeed); // 121 - 257 // karma bomb gets 2x acceleration - if (gametype == GT_BATTLE && player->bumpers <= 0) + if ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0) k_accel *= 2; // Marble Garden Top gets 1200% accel @@ -3236,9 +3232,8 @@ UINT16 K_GetKartFlashing(player_t *player) { UINT16 tics = flashingtics; - if (gametype == GT_BATTLE) + if (gametyperules & GTR_BUMPERS) { - // TODO: gametyperules return 1; } @@ -3543,6 +3538,12 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN UINT8 points = 1; boolean trapItem = false; + if (!(gametyperules & GTR_POINTLIMIT)) + { + // No points in this gametype. + return; + } + if (player == NULL || victim == NULL) { // Invalid player or victim @@ -3578,11 +3579,8 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN } } - if (gametyperules & GTR_POINTLIMIT) - { - P_AddPlayerScore(player, points); - K_SpawnBattlePoints(player, victim, points); - } + P_AddPlayerScore(player, points); + K_SpawnBattlePoints(player, victim, points); } void K_SpinPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 type) @@ -5054,7 +5052,7 @@ void K_SpawnDraftDust(mobj_t *mo) { UINT8 leniency = (3*TICRATE)/4 + ((mo->player->kartweight-1) * (TICRATE/4)); - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) leniency *= 4; ang = mo->player->drawangle; @@ -7470,7 +7468,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_SpawnGrowShrinkParticles(player->mo, player->growshrinktimer); } - if (gametype == GT_RACE && player->rings <= 0) // spawn ring debt indicator + if (!(gametyperules & GTR_SPHERES) && player->rings <= 0) // spawn ring debt indicator { mobj_t *debtflag = P_SpawnMobj(player->mo->x + player->mo->momx, player->mo->y + player->mo->momy, player->mo->z + P_GetMobjZMovement(player->mo) + player->mo->height + (24*player->mo->scale), MT_THOK); @@ -7788,7 +7786,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->eggmanexplode) { - if (player->spectator || (gametype == GT_BATTLE && !player->bumpers)) + if (player->spectator || ((gametyperules & GTR_BUMPERS) && player->bumpers <= 0)) player->eggmanexplode = 0; else { @@ -9330,15 +9328,18 @@ static INT32 K_FlameShieldMax(player_t *player) UINT8 numplayers = 0; UINT8 i; - for (i = 0; i < MAXPLAYERS; i++) + if (gametyperules & GTR_CIRCUIT) { - if (playeringame[i] && !players[i].spectator) - numplayers++; - if (players[i].position == 1) - disttofinish = players[i].distancetofinish; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator) + numplayers++; + if (players[i].position == 1) + disttofinish = players[i].distancetofinish; + } } - if (numplayers <= 1 || gametype == GT_BATTLE) + if (numplayers <= 1) { return 16; // max when alone, for testing // and when in battle, for chaos @@ -9592,8 +9593,7 @@ static void K_KartSpindash(player_t *player) { fixed_t thrust = FixedMul(player->mo->scale, player->spindash*FRACUNIT/5); - // TODO: gametyperules - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) thrust *= 2; // Give a bit of a boost depending on charge. @@ -10400,8 +10400,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE); } - // TODO: gametyperules - player->growshrinktimer = max(player->growshrinktimer, (gametype == GT_BATTLE ? 8 : 12) * TICRATE); + player->growshrinktimer = max(player->growshrinktimer, ((gametyperules & GTR_CLOSERPLAYERS) ? 8 : 12) * TICRATE); if (player->invincibilitytimer > 0) { @@ -10559,8 +10558,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if ((cmd->buttons & BT_ATTACK) && (player->pflags & PF_HOLDREADY)) { - // TODO: gametyperules - const INT32 incr = gametype == GT_BATTLE ? 4 : 2; + const INT32 incr = (gametyperules & GTR_CLOSERPLAYERS) ? 4 : 2; if (player->flamedash == 0) { @@ -10596,8 +10594,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { player->pflags |= PF_HOLDREADY; - // TODO: gametyperules - if (gametype != GT_BATTLE || leveltime % 6 == 0) + if (!(gametyperules & GTR_CLOSERPLAYERS) || leveltime % 6 == 0) { if (player->flamemeter > 0) player->flamemeter--; @@ -10719,18 +10716,13 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (player->hyudorotimer > 0) { - INT32 hyu = hyudorotime; - - if (gametype == GT_RACE) - hyu *= 2; // double in race - if (leveltime & 1) { player->mo->renderflags |= RF_DONTDRAW; } else { - if (player->hyudorotimer >= (TICRATE/2) && player->hyudorotimer <= hyu-(TICRATE/2)) + if (player->hyudorotimer >= (TICRATE/2) && player->hyudorotimer <= hyudorotime-(TICRATE/2)) player->mo->renderflags &= ~K_GetPlayerDontDrawFlag(player); else player->mo->renderflags &= ~RF_DONTDRAW; @@ -10743,17 +10735,17 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->mo->renderflags &= ~RF_DONTDRAW; } - if (gametype == GT_BATTLE && player->bumpers <= 0) // dead in match? you da bomb + if (!(gametyperules & GTR_BUMPERS) || player->bumpers > 0) + { + player->mo->renderflags &= ~(RF_TRANSMASK|RF_BRIGHTMASK); + } + else // dead in match? you da bomb { K_DropItems(player); //K_StripItems(player); K_StripOther(player); player->mo->renderflags |= RF_GHOSTLY; player->flashing = player->karmadelay; } - else if (gametype == GT_RACE || player->bumpers > 0) - { - player->mo->renderflags &= ~(RF_TRANSMASK|RF_BRIGHTMASK); - } if (player->trickpanel == 1) { @@ -10966,7 +10958,7 @@ void K_CheckSpectateStatus(void) continue; if (leveltime > (starttime + 20*TICRATE)) // DON'T allow if the match is 20 seconds in return; - if (gametype == GT_RACE && players[i].laps >= 2) // DON'T allow if the race is at 2 laps + if ((gametyperules & GTR_CIRCUIT) && players[i].laps >= 2) // DON'T allow if the race is at 2 laps return; continue; } diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 9071907bc..b1e3557c7 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2261,7 +2261,7 @@ void M_DrawTimeAttack(void) laprec = mapheaderinfo[map]->mainrecord->lap; } - if (levellist.newgametype != GT_BATTLE) + if (gametypedefaultrules[levellist.newgametype] & GTR_CIRCUIT) { V_DrawRightAlignedString(rightedge-12, timeheight, highlightflags, "BEST LAP:"); K_drawKartTimestamp(laprec, 162+t, timeheight+6, 0, 2); @@ -3964,9 +3964,20 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) if (demoref->numlaps) V_DrawThinString(x, y+9, V_SNAPTOTOP|V_ALLOWLOWERCASE, va("(%d laps)", demoref->numlaps)); - V_DrawString(x, y+20, V_SNAPTOTOP|V_ALLOWLOWERCASE, demoref->gametype == GT_RACE ? - va("Race (%s speed)", kartspeed_cons_t[(demoref->kartspeed & ~DF_ENCORE) + 1].strvalue) : - "Battle Mode"); + { + const char *gtstring = "???"; + if (demoref->gametype >= gametypecount) + ; + else + { + gtstring = Gametype_Names[demoref->gametype]; + + if ((gametypedefaultrules[demoref->gametype] & GTR_CIRCUIT)) + gtstring = va("%s (%s)", gtstring, kartspeed_cons_t[(demoref->kartspeed & ~DF_ENCORE) + 1].strvalue); + } + + V_DrawString(x, y+20, V_SNAPTOTOP|V_ALLOWLOWERCASE, gtstring); + } if (!demoref->standings[0].ranking) { @@ -3979,30 +3990,33 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) V_DrawThinString(x, y+29, V_SNAPTOTOP|highlightflags, "WINNER"); V_DrawString(x+38, y+30, V_SNAPTOTOP|V_ALLOWLOWERCASE, demoref->standings[0].name); - if (demoref->gametype == GT_RACE) + if (demoref->gametype < gametypecount) { - V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "TIME"); - } - else - { - V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "SCORE"); - } + if (gametypedefaultrules[demoref->gametype] & GTR_POINTLIMIT) + { + V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "SCORE"); + } + else + { + V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "TIME"); + } - if (demoref->standings[0].timeorscore == (UINT32_MAX-1)) - { - V_DrawThinString(x+32, y+39, V_SNAPTOTOP, "NO CONTEST"); - } - else if (demoref->gametype == GT_RACE) - { - V_DrawRightAlignedString(x+84, y+40, V_SNAPTOTOP, va("%d'%02d\"%02d", - G_TicsToMinutes(demoref->standings[0].timeorscore, true), - G_TicsToSeconds(demoref->standings[0].timeorscore), - G_TicsToCentiseconds(demoref->standings[0].timeorscore) - )); - } - else - { - V_DrawString(x+32, y+40, V_SNAPTOTOP, va("%d", demoref->standings[0].timeorscore)); + if (demoref->standings[0].timeorscore == (UINT32_MAX-1)) + { + V_DrawThinString(x+32, y+39, V_SNAPTOTOP, "NO CONTEST"); + } + else if (gametypedefaultrules[demoref->gametype] & GTR_POINTLIMIT) + { + V_DrawString(x+32, y+40, V_SNAPTOTOP, va("%d", demoref->standings[0].timeorscore)); + } + else + { + V_DrawRightAlignedString(x+84, y+40, V_SNAPTOTOP, va("%d'%02d\"%02d", + G_TicsToMinutes(demoref->standings[0].timeorscore, true), + G_TicsToSeconds(demoref->standings[0].timeorscore), + G_TicsToCentiseconds(demoref->standings[0].timeorscore) + )); + } } // Character face! @@ -4197,14 +4211,16 @@ void M_DrawReplayStartMenu(void) if (demoref->standings[i].timeorscore == UINT32_MAX-1) V_DrawThinString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, "NO CONTEST"); - else if (demoref->gametype == GT_RACE) + else if (demoref->gametype >= gametypecount) + ; + else if (gametypedefaultrules[demoref->gametype] & GTR_POINTLIMIT) + V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", demoref->standings[i].timeorscore)); + else V_DrawRightAlignedString(BASEVIDWIDTH-40, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d'%02d\"%02d", G_TicsToMinutes(demoref->standings[i].timeorscore, true), G_TicsToSeconds(demoref->standings[i].timeorscore), G_TicsToCentiseconds(demoref->standings[i].timeorscore) )); - else - V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", demoref->standings[i].timeorscore)); // Character face! diff --git a/src/k_respawn.c b/src/k_respawn.c index 0b4c02ccf..5e3a3ecc6 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -175,14 +175,14 @@ void K_DoIngameRespawn(player_t *player) mapthing_t *beststart = NULL; UINT8 numstarts = 0; - if (gametype == GT_RACE) - { - numstarts = numcoopstarts; - } - else if (gametype == GT_BATTLE) + if (gametyperules & GTR_BATTLESTARTS) { numstarts = numdmstarts; } + else + { + numstarts = numcoopstarts; + } if (numstarts > 0) { @@ -193,17 +193,13 @@ void K_DoIngameRespawn(player_t *player) UINT32 dist = UINT32_MAX; mapthing_t *checkstart = NULL; - if (gametype == GT_RACE) - { - checkstart = playerstarts[i]; - } - else if (gametype == GT_BATTLE) + if (gametyperules & GTR_BATTLESTARTS) { checkstart = deathmatchstarts[i]; } else { - break; + checkstart = playerstarts[i]; } dist = (UINT32)P_AproxDistance((player->mo->x >> FRACBITS) - checkstart->x, diff --git a/src/k_roulette.c b/src/k_roulette.c index 4f3de015f..7c283ca1f 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1125,27 +1125,23 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } else if (K_TimeAttackRules() == true) { - switch (gametype) + kartitems_t *presetlist = K_KartItemReelTimeAttack; + + // If the objective is not to go fast, it's to cause serious damage. + if (!(gametyperules & GTR_CIRCUIT)) { - case GT_RACE: - default: + presetlist = K_KartItemReelBreakTheCapsules; + for (i = 0; K_KartItemReelBreakTheCapsules[i] != KITEM_NONE; i++) { - for (i = 0; K_KartItemReelTimeAttack[i] != KITEM_NONE; i++) - { - K_PushToRouletteItemList(roulette, K_KartItemReelTimeAttack[i]); - } - break; - } - case GT_BATTLE: - { - for (i = 0; K_KartItemReelBreakTheCapsules[i] != KITEM_NONE; i++) - { - K_PushToRouletteItemList(roulette, K_KartItemReelBreakTheCapsules[i]); - } - break; + K_PushToRouletteItemList(roulette, K_KartItemReelBreakTheCapsules[i]); } } + for (i = 0; presetlist[i] != KITEM_NONE; i++) + { + K_PushToRouletteItemList(roulette, presetlist[i]); + } + return; } diff --git a/src/p_inter.c b/src/p_inter.c index ff6ba99b0..cb1de723d 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -2198,7 +2198,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da { tic_t kinvextend; - if (gametype == GT_BATTLE) + if (gametyperules & GTR_CLOSERPLAYERS) kinvextend = 2*TICRATE; else kinvextend = 5*TICRATE; diff --git a/src/p_mobj.c b/src/p_mobj.c index 26b38d631..50717459a 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6163,7 +6163,7 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->color = mobj->target->color; K_MatchGenericExtraFlags(mobj, mobj->target); - if ((gametype == GT_RACE || mobj->target->player->bumpers <= 0) + if ((!(gametyperules & GTR_BUMPERS) || mobj->target->player->bumpers <= 0) #if 1 // Set to 0 to test without needing to host || (P_IsDisplayPlayer(mobj->target->player)) #endif @@ -8362,7 +8362,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) statenum_t state = (mobj->state-states); if (!mobj->target || !mobj->target->health || !mobj->target->player || mobj->target->player->spectator - || (gametype == GT_RACE || mobj->target->player->bumpers)) + || (!(gametyperules & GTR_BUMPERS) || mobj->target->player->bumpers)) { P_RemoveMobj(mobj); return false; @@ -9389,12 +9389,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } // FALLTHRU case MT_SPHEREBOX: - if (gametype == GT_BATTLE && mobj->threshold == 70) + if (mobj->threshold == 70) { mobj->color = K_RainbowColor(leveltime); mobj->colorized = true; - if (battleovertime.enabled) + if ((gametyperules & GTR_OVERTIME) && battleovertime.enabled) { angle_t ang = FixedAngle((leveltime % 360) << FRACBITS); fixed_t z = battleovertime.z; diff --git a/src/p_setup.c b/src/p_setup.c index 61c2a9ec3..7934874a5 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -6857,7 +6857,7 @@ static void P_InitLevelSettings(void) // SRB2Kart: map load variables if (grandprixinfo.gp == true) { - if ((gametyperules & GTR_BUMPERS)) + if (!(gametyperules & GTR_CIRCUIT)) { gamespeed = KARTSPEED_EASY; } @@ -6875,8 +6875,7 @@ static void P_InitLevelSettings(void) } else if (modeattacking) { - // Just play it safe and set everything - if ((gametyperules & GTR_BUMPERS)) + if (!(gametyperules & GTR_CIRCUIT)) gamespeed = KARTSPEED_EASY; else gamespeed = KARTSPEED_HARD; @@ -6884,7 +6883,7 @@ static void P_InitLevelSettings(void) } else { - if ((gametyperules & GTR_BUMPERS)) + if (!(gametyperules & GTR_CIRCUIT)) gamespeed = KARTSPEED_EASY; else { From 59bc094face4379f4c5ddcb5601e34b48fc5c2e9 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 24 Dec 2022 22:44:40 +0000 Subject: [PATCH 005/128] We don't have *one* Single Player mode - we have as many gametypes as there are, now. --- src/d_netcmd.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index ffc53e02d..6b8199409 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2880,8 +2880,7 @@ static void Command_Map_f(void) mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype) )) { - CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), - (multiplayer ? gametype_cons_t[newgametype].strvalue : "Single Player")); + CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), (gametype_cons_t[newgametype].strvalue)); Z_Free(realmapname); Z_Free(mapname); return; @@ -4807,12 +4806,6 @@ static void Command_ShowGametype_f(void) { const char *gametypestr = NULL; - if (!(netgame || multiplayer)) // print "Single player" instead of "Race" - { - CONS_Printf(M_GetText("Current gametype is %s\n"), "Single Player"); - return; - } - // get name string for current gametype if (gametype >= 0 && gametype < gametypecount) gametypestr = Gametype_Names[gametype]; From 185b36bd27de557116deb3af5e9cb3499d911b30 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 26 Dec 2022 01:07:57 +0000 Subject: [PATCH 006/128] gametype_t - New array of pointers to structures in memory (currently mixing static for base-game and Callocated for custom) - Centralises a metric-ton of previously seperately handled properties into one struct - Gametype_Names[] - Gametype_ConstantNames[] - gametypetol[] - timelimits[] - pointlimits[] - gametypedefaultrules[] - Don't attempt to guess custom gametype in Replay Hut (requires more work to make custom gametypes behave across the entire experience) - I_Error if invalid gametype set - gametyperules is deprecated since it will never be modified seperately from gametype (temporarily a #define, don't wanna bloat this commit too much) --- src/d_clisrv.c | 2 +- src/d_main.c | 7 +-- src/d_netcmd.c | 49 +++++++-------- src/deh_lua.c | 9 ++- src/deh_soc.c | 49 +++++++-------- src/doomstat.h | 40 +++++++----- src/g_game.c | 151 +++++++++++++++++++--------------------------- src/g_game.h | 8 +-- src/hu_stuff.c | 4 +- src/k_kart.c | 2 +- src/k_menudraw.c | 22 +++---- src/k_menufunc.c | 4 +- src/lua_baselib.c | 54 ++++++++--------- src/p_setup.c | 1 - src/typedef.h | 1 + src/y_inter.c | 19 ++---- src/y_inter.h | 1 - 17 files changed, 189 insertions(+), 234 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 224ae709d..f3b2fc391 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -930,7 +930,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) else netbuffer->u.serverinfo.refusereason = 0; - strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[gametype], + strncpy(netbuffer->u.serverinfo.gametypename, gametypes[gametype]->name, sizeof netbuffer->u.serverinfo.gametypename); netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame; netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled(); diff --git a/src/d_main.c b/src/d_main.c index 8ba822e21..f8b7b0845 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1516,11 +1516,6 @@ void D_SRB2Main(void) CON_SetLoadingProgress(LOADED_HUINIT); - memset(timelimits, 0, sizeof(timelimits)); - memset(pointlimits, 0, sizeof(pointlimits)); - - timelimits[GT_BATTLE] = 2; - D_RegisterServerCommands(); D_RegisterClientCommands(); // be sure that this is called before D_CheckNetGame R_RegisterEngineStuff(); @@ -1802,7 +1797,7 @@ void D_SRB2Main(void) if (newgametype == -1) // reached end of the list with no match { j = atoi(sgametype); // assume they gave us a gametype number, which is okay too - if (j >= 0 && j < gametypecount) + if (j >= 0 && j < numgametypes) newgametype = (INT16)j; } diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 6b8199409..2e0e30dac 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -481,10 +481,6 @@ consvar_t cv_timelimit = CVAR_INIT ("timelimit", "None", CV_NETVAR|CV_CALL|CV_NO static CV_PossibleValue_t numlaps_cons_t[] = {{1, "MIN"}, {MAX_LAPS, "MAX"}, {0, "Map default"}, {0, NULL}}; consvar_t cv_numlaps = CVAR_INIT ("numlaps", "Map default", CV_SAVE|CV_NETVAR|CV_CALL|CV_CHEAT, numlaps_cons_t, NumLaps_OnChange); -// Point and time limits for every gametype -INT32 pointlimits[NUMGAMETYPES]; -INT32 timelimits[NUMGAMETYPES]; - consvar_t cv_forceskin = CVAR_INIT ("forceskin", "None", CV_NETVAR|CV_CALL|CV_CHEAT, NULL, ForceSkin_OnChange); consvar_t cv_downloading = CVAR_INIT ("downloading", "On", 0, CV_OnOff, NULL); @@ -553,8 +549,7 @@ char timedemo_csv_id[256]; boolean timedemo_quit; INT16 gametype = GT_RACE; -UINT32 gametyperules = 0; -INT16 gametypecount = GT_FIRSTFREESLOT; +INT16 numgametypes = GT_FIRSTFREESLOT; boolean forceresetplayers = false; boolean deferencoremode = false; @@ -2526,7 +2521,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d pencoremode=%d resetplayers=%d delay=%d skipprecutscene=%d\n", mapnum, newgametype, pencoremode, resetplayers, delay, skipprecutscene); - if ((netgame || multiplayer) && !((gametype == newgametype) && (gametypedefaultrules[newgametype] & GTR_CAMPAIGN))) + if ((netgame || multiplayer) && !((gametype == newgametype) && (gametypes[newgametype]->rules & GTR_CAMPAIGN))) FLS = false; // Too lazy to change the input value for every instance of this function....... @@ -2595,7 +2590,7 @@ void D_SetupVote(void) UINT8 secondgt = G_SometimesGetDifferentGametype(); INT16 votebuffer[4] = {-1,-1,-1,0}; - if ((cv_kartencore.value == 1) && (gametypedefaultrules[gametype] & GTR_ENCORE)) + if ((cv_kartencore.value == 1) && (gametyperules & GTR_ENCORE)) WRITEUINT8(p, (gametype|VOTEMODIFIER_ENCORE)); else WRITEUINT8(p, gametype); @@ -2817,7 +2812,7 @@ static void Command_Map_f(void) if (isdigit(gametypename[0])) { d = atoi(gametypename); - if (d >= 0 && d < gametypecount) + if (d >= 0 && d < numgametypes) newgametype = d; else { @@ -2825,7 +2820,7 @@ static void Command_Map_f(void) "Gametype number %d is out of range. Use a number between" " 0 and %d inclusive. ...Or just use the name. :v\n", d, - gametypecount-1); + numgametypes-1); Z_Free(realmapname); Z_Free(mapname); return; @@ -2890,7 +2885,7 @@ static void Command_Map_f(void) fromlevelselect = ( netgame || multiplayer ) && newgametype == gametype && - gametypedefaultrules[newgametype] & GTR_CAMPAIGN; + gametypes[newgametype]->rules & GTR_CAMPAIGN; } } @@ -3023,7 +3018,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) gametype = READUINT8(*cp); G_SetGametype(gametype); // I fear putting that macro as an argument - if (gametype < 0 || gametype >= gametypecount) + if (gametype < 0 || gametype >= numgametypes) gametype = lastgametype; else if (gametype != lastgametype) D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype @@ -4807,8 +4802,8 @@ static void Command_ShowGametype_f(void) const char *gametypestr = NULL; // get name string for current gametype - if (gametype >= 0 && gametype < gametypecount) - gametypestr = Gametype_Names[gametype]; + if (gametype >= 0 && gametype < numgametypes) + gametypestr = gametypes[gametype]->name; if (gametypestr) CONS_Printf(M_GetText("Current gametype is %s\n"), gametypestr); @@ -4987,10 +4982,10 @@ void D_GameTypeChanged(INT32 lastgametype) { const char *oldgt = NULL, *newgt = NULL; - if (lastgametype >= 0 && lastgametype < gametypecount) - oldgt = Gametype_Names[lastgametype]; - if (gametype >= 0 && lastgametype < gametypecount) - newgt = Gametype_Names[gametype]; + if (lastgametype >= 0 && lastgametype < numgametypes) + oldgt = gametypes[lastgametype]->name; + if (gametype >= 0 && gametype < numgametypes) + newgt = gametypes[gametype]->name; if (oldgt && newgt) CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), oldgt, newgt); @@ -5002,11 +4997,11 @@ void D_GameTypeChanged(INT32 lastgametype) { if (!cv_timelimit.changed) // user hasn't changed limits { - CV_SetValue(&cv_timelimit, timelimits[gametype]); + CV_SetValue(&cv_timelimit, gametypes[gametype]->timelimit); } if (!cv_pointlimit.changed) { - CV_SetValue(&cv_pointlimit, pointlimits[gametype]); + CV_SetValue(&cv_pointlimit, gametypes[gametype]->pointlimit); } } @@ -5301,25 +5296,25 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) // Strip illegal Encore flag. if ((gt & VOTEMODIFIER_ENCORE) - && !(gametypedefaultrules[(gt & ~VOTEMODIFIER_ENCORE)] & GTR_ENCORE)) + && !(gametypes[(gt & ~VOTEMODIFIER_ENCORE)]->rules & GTR_ENCORE)) { gt &= ~VOTEMODIFIER_ENCORE; } - if ((gt & ~VOTEMODIFIER_ENCORE) >= gametypecount) + if ((gt & ~VOTEMODIFIER_ENCORE) >= numgametypes) { gt &= ~VOTEMODIFIER_ENCORE; if (server) - I_Error("Got_SetupVotecmd: Internal gametype ID %d not found (gametypecount = %d)", gt, gametypecount); + I_Error("Got_SetupVotecmd: Internal gametype ID %d not found (numgametypes = %d)", gt, numgametypes); CONS_Alert(CONS_WARNING, M_GetText("Vote setup with bad gametype ID %d received from %s\n"), gt, player_names[playernum]); return; } - if ((secondgt & ~VOTEMODIFIER_ENCORE) >= gametypecount) + if ((secondgt & ~VOTEMODIFIER_ENCORE) >= numgametypes) { secondgt &= ~VOTEMODIFIER_ENCORE; if (server) - I_Error("Got_SetupVotecmd: Internal second gametype ID %d not found (gametypecount = %d)", secondgt, gametypecount); + I_Error("Got_SetupVotecmd: Internal second gametype ID %d not found (numgametypes = %d)", secondgt, numgametypes); CONS_Alert(CONS_WARNING, M_GetText("Vote setup with bad second gametype ID %d received from %s\n"), secondgt, player_names[playernum]); return; } @@ -5339,11 +5334,11 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) // If third entry has an illelegal Encore flag... (illelegal!?) if ((secondgt & VOTEMODIFIER_ENCORE) - && !(gametypedefaultrules[(secondgt & ~VOTEMODIFIER_ENCORE)] & GTR_ENCORE)) + && !(gametypes[(secondgt & ~VOTEMODIFIER_ENCORE)]->rules & GTR_ENCORE)) { secondgt &= ~VOTEMODIFIER_ENCORE; // Apply it to the second entry instead, gametype permitting! - if (gametypedefaultrules[gt] & GTR_ENCORE) + if (gametypes[gt]->rules & GTR_ENCORE) { tempvotelevels[1][1] |= VOTEMODIFIER_ENCORE; } diff --git a/src/deh_lua.c b/src/deh_lua.c index 56bfdf4ea..f131617c6 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -309,11 +309,16 @@ static inline int lib_getenum(lua_State *L) } else if (fastncmp("GT_", word, 3)) { p = word; - for (i = 0; Gametype_ConstantNames[i]; i++) - if (fastcmp(p, Gametype_ConstantNames[i])) { + i = 0; + while (gametypes[i] != NULL) + { + if (fastcmp(p, gametypes[i]->constant)) + { lua_pushinteger(L, i); return 1; } + i++; + } if (mathlib) return luaL_error(L, "gametype '%s' could not be found.\n", word); return 0; } diff --git a/src/deh_soc.c b/src/deh_soc.c index 9d39652f0..65a347169 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -764,13 +764,13 @@ void readgametype(MYFILE *f, char *gtname) char *tmp; INT32 i, j; - INT16 newgtidx = 0; + gametype_t *newgametype = NULL; + UINT32 newgtrules = 0; UINT32 newgttol = 0; INT32 newgtpointlimit = 0; INT32 newgttimelimit = 0; - INT16 newgtrankingstype = -1; - int newgtinttype = 0; + UINT8 newgtinttype = 0; char gtconst[MAXLINELEN]; // Empty strings. @@ -821,12 +821,6 @@ void readgametype(MYFILE *f, char *gtname) newgtpointlimit = (INT32)i; else if (fastcmp(word, "DEFAULTTIMELIMIT")) newgttimelimit = (INT32)i; - // Rankings type - else if (fastcmp(word, "RANKINGTYPE")) - { - // Case insensitive - newgtrankingstype = (int)get_number(word2); - } // Intermission type else if (fastcmp(word, "INTERMISSIONTYPE")) { @@ -879,36 +873,35 @@ void readgametype(MYFILE *f, char *gtname) Z_Free(word2lwr); // Ran out of gametype slots - if (gametypecount == NUMGAMETYPEFREESLOTS) + if (numgametypes == GT_LASTFREESLOT) { I_Error("Out of Gametype Freeslots while allocating \"%s\"\nLoad less addons to fix this.", gtname); - return; } // Add the new gametype - newgtidx = G_AddGametype(newgtrules); - G_AddGametypeTOL(newgtidx, newgttol); + newgametype = Z_Calloc(sizeof (gametype_t), PU_STATIC, NULL); + if (!newgametype) + { + I_Error("Out of memory allocating gametype \"%s\"", gtname); + } - // Not covered by G_AddGametype alone. - if (newgtrankingstype == -1) - newgtrankingstype = newgtidx; - gametyperankings[newgtidx] = newgtrankingstype; - intermissiontypes[newgtidx] = newgtinttype; - pointlimits[newgtidx] = newgtpointlimit; - timelimits[newgtidx] = newgttimelimit; - - // Write the new gametype name. - Gametype_Names[newgtidx] = Z_StrDup((const char *)gtname); - - // Write the constant name. if (gtconst[0] == '\0') strncpy(gtconst, gtname, MAXLINELEN); - G_AddGametypeConstant(newgtidx, (const char *)gtconst); + + newgametype->name = Z_StrDup((const char *)gtname); + newgametype->rules = newgtrules; + newgametype->constant = G_PrepareGametypeConstant((const char *)gtconst); + newgametype->tol = newgttol; + newgametype->intermission = newgtinttype; + newgametype->pointlimit = newgtpointlimit; + newgametype->timelimit = newgttimelimit; + + gametypes[numgametypes++] = newgametype; // Update gametype_cons_t accordingly. G_UpdateGametypeSelections(); - CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]); + CONS_Printf("Added gametype %s\n", gtname); } void readlevelheader(MYFILE *f, char * name) @@ -3670,7 +3663,7 @@ sfxenum_t get_sfx(const char *word) return atoi(word); if (fastncmp("GT_",word,3)) word += 3; // take off the GT_ - for (i = 0; i < NUMGAMETYPES; i++) + for (i = 0; i < MAXGAMETYPES; i++) if (fastcmp(word, Gametype_ConstantNames[i]+3)) return i; deh_warning("Couldn't find gametype named 'GT_%s'",word); diff --git a/src/doomstat.h b/src/doomstat.h index 038ce8572..8c01cd474 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -147,11 +147,6 @@ extern boolean addedtogame; // true after the server has added you // Only true if >1 player. netgame => multiplayer but not (multiplayer=>netgame) extern boolean multiplayer; -extern INT16 gametype; - -extern UINT32 gametyperules; -extern INT16 gametypecount; - extern UINT8 splitscreen; extern int r_splitscreen; @@ -454,7 +449,7 @@ extern mapheader_t** mapheaderinfo; extern INT32 nummapheaders, mapallocsize; // Gametypes -#define NUMGAMETYPEFREESLOTS (NUMGAMETYPES-GT_FIRSTFREESLOT) +#define NUMGAMETYPEFREESLOTS (MAXGAMETYPES-GT_FIRSTFREESLOT) enum GameType { @@ -463,9 +458,29 @@ enum GameType GT_FIRSTFREESLOT, GT_LASTFREESLOT = 127, // Previously (GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1) - it would be necessary to rewrite VOTEMODIFIER_ENCORE to go higher than this. - NUMGAMETYPES + MAXGAMETYPES }; -// If you alter this list, update deh_tables.c, MISC_ChangeGameTypeMenu in m_menu.c, and Gametype_Names in g_game.c +// If you alter this list, update defaultgametypes and *gametypes in g_game.c + +#define MAXTOL (1<<31) +#define NUMBASETOLNAMES (5) +#define NUMTOLNAMES (NUMBASETOLNAMES + NUMGAMETYPEFREESLOTS) + +struct gametype_t +{ + const char *name; + const char *constant; + UINT32 rules; + UINT32 tol; + UINT8 intermission; + INT32 pointlimit; + INT32 timelimit; +}; + +extern gametype_t *gametypes[MAXGAMETYPES+1]; +extern INT16 numgametypes; + +extern INT16 gametype; // Gametype rules enum GameTypeRules @@ -504,13 +519,8 @@ enum GameTypeRules // free: to and including 1<<31 }; -// String names for gametypes -extern const char *Gametype_Names[NUMGAMETYPES]; -extern const char *Gametype_ConstantNames[NUMGAMETYPES]; - -// Point and time limits for every gametype -extern INT32 pointlimits[NUMGAMETYPES]; -extern INT32 timelimits[NUMGAMETYPES]; +// TODO: replace every instance +#define gametyperules (gametypes[gametype]->rules) // TypeOfLevel things enum TypeOfLevel diff --git a/src/g_game.c b/src/g_game.c index 64afccba9..dc7c905e9 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2998,28 +2998,55 @@ void G_ExitLevel(void) } } -// See also the enum GameType in doomstat.h -const char *Gametype_Names[NUMGAMETYPES] = +static gametype_t defaultgametypes[] = { - "Race", // GT_RACE - "Battle" // GT_BATTLE + // GT_RACE + { + "Race", + "GT_RACE", + GTR_CAMPAIGN|GTR_CIRCUIT|GTR_BOTS|GTR_ENCORE, + TOL_RACE, + int_race, + 0, + 0, + }, + + // GT_BATTLE + { + "Battle", + "GT_BATTLE", + GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_POWERSTONES|GTR_KARMA|GTR_ITEMARROWS|GTR_CAPSULES|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_CLOSERPLAYERS, + TOL_BATTLE, + int_battle, + 0, + 2, + }, }; -// For dehacked -const char *Gametype_ConstantNames[NUMGAMETYPES] = +gametype_t *gametypes[MAXGAMETYPES+1] = { - "GT_RACE", // GT_RACE - "GT_BATTLE" // GT_BATTLE + &defaultgametypes[GT_RACE], + &defaultgametypes[GT_BATTLE], }; -// Gametype rules -UINT32 gametypedefaultrules[NUMGAMETYPES] = +// +// G_GetGametypeByName +// +// Returns the number for the given gametype name string, or -1 if not valid. +// +INT32 G_GetGametypeByName(const char *gametypestr) { - // Race - GTR_CAMPAIGN|GTR_CIRCUIT|GTR_BOTS|GTR_ENCORE, - // Battle - GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_POWERSTONES|GTR_KARMA|GTR_ITEMARROWS|GTR_CAPSULES|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_CLOSERPLAYERS -}; + INT32 i = 0; + + while (gametypes[i] != NULL) + { + if (!stricmp(gametypestr, gametypes[i]->name)) + return i; + i++; + } + + return -1; // unknown gametype +} // // G_SetGametype @@ -3028,41 +3055,26 @@ UINT32 gametypedefaultrules[NUMGAMETYPES] = // void G_SetGametype(INT16 gtype) { + if (gtype < 0 || gtype > numgametypes) + { + I_Error("G_SetGametype: Bad gametype change %d (was %d/\"%s\")", gtype, gametype, gametypes[gametype]->name); + } + gametype = gtype; - gametyperules = gametypedefaultrules[gametype]; } // -// G_AddGametype -// -// Add a gametype. Returns the new gametype number. -// -INT16 G_AddGametype(UINT32 rules) -{ - INT16 newgtype = gametypecount; - gametypecount++; - - // Set gametype rules. - gametypedefaultrules[newgtype] = rules; - Gametype_Names[newgtype] = "???"; - - // Update gametype_cons_t accordingly. - G_UpdateGametypeSelections(); - - return newgtype; -} - -// -// G_AddGametypeConstant +// G_PrepareGametypeConstant // // Self-explanatory. Filters out "bad" characters. // -void G_AddGametypeConstant(INT16 gtype, const char *newgtconst) +char *G_PrepareGametypeConstant(const char *newgtconst) { size_t r = 0; // read size_t w = 0; // write - char *gtconst = Z_Calloc(strlen(newgtconst) + 4, PU_STATIC, NULL); - char *tmpconst = Z_Calloc(strlen(newgtconst) + 1, PU_STATIC, NULL); + size_t len = strlen(newgtconst); + char *gtconst = Z_Calloc(len + 4, PU_STATIC, NULL); + char *tmpconst = Z_Calloc(len + 1, PU_STATIC, NULL); // Copy the gametype name. strcpy(tmpconst, newgtconst); @@ -3122,8 +3134,8 @@ void G_AddGametypeConstant(INT16 gtype, const char *newgtconst) // Free the temporary string. Z_Free(tmpconst); - // Finally, set the constant string. - Gametype_ConstantNames[gtype] = gtconst; + // Finally, return the constant string. + return gtconst; } // @@ -3134,30 +3146,15 @@ void G_AddGametypeConstant(INT16 gtype, const char *newgtconst) void G_UpdateGametypeSelections(void) { INT32 i; - for (i = 0; i < gametypecount; i++) + for (i = 0; i < numgametypes; i++) { gametype_cons_t[i].value = i; - gametype_cons_t[i].strvalue = Gametype_Names[i]; + gametype_cons_t[i].strvalue = gametypes[i]->name; } - gametype_cons_t[NUMGAMETYPES].value = 0; - gametype_cons_t[NUMGAMETYPES].strvalue = NULL; + gametype_cons_t[numgametypes].value = 0; + gametype_cons_t[numgametypes].strvalue = NULL; } -// Gametype rankings -INT16 gametyperankings[NUMGAMETYPES] = -{ - GT_RACE, - GT_BATTLE, -}; - -// Gametype to TOL (Type Of Level) -UINT32 gametypetol[NUMGAMETYPES] = -{ - TOL_RACE, // Race - TOL_BATTLE, // Battle - TOL_TV, // Midnight Channel effect -}; - tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = { {"RACE",TOL_RACE}, {"BATTLE",TOL_BATTLE}, @@ -3184,32 +3181,6 @@ void G_AddTOL(UINT32 newtol, const char *tolname) TYPEOFLEVEL[i].flag = newtol; } -// -// G_AddGametypeTOL -// -// Assigns a type of level to a gametype. -// -void G_AddGametypeTOL(INT16 gtype, UINT32 newtol) -{ - gametypetol[gtype] = newtol; -} - -// -// G_GetGametypeByName -// -// Returns the number for the given gametype name string, or -1 if not valid. -// -INT32 G_GetGametypeByName(const char *gametypestr) -{ - INT32 i; - - for (i = 0; i < gametypecount; i++) - if (!stricmp(gametypestr, Gametype_Names[i])) - return i; - - return -1; // unknown gametype -} - // // G_IsSpecialStage // @@ -3339,7 +3310,9 @@ UINT8 G_GetGametypeColor(INT16 gt) */ UINT32 G_TOLFlag(INT32 pgametype) { - return gametypetol[pgametype]; + if (pgametype >= 0 && pgametype < numgametypes) + return gametypes[pgametype]->tol; + return 0; } INT16 G_GetFirstMapOfGametype(UINT8 pgametype) @@ -3350,7 +3323,7 @@ INT16 G_GetFirstMapOfGametype(UINT8 pgametype) templevelsearch.cup = NULL; templevelsearch.typeoflevel = G_TOLFlag(pgametype); - templevelsearch.cupmode = (!(gametypedefaultrules[pgametype] & GTR_NOCUPSELECT)); + templevelsearch.cupmode = (!(gametypes[pgametype]->rules & GTR_NOCUPSELECT)); templevelsearch.timeattack = false; templevelsearch.checklocked = true; diff --git a/src/g_game.h b/src/g_game.h index 3a8d77941..04a9df9c9 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -179,16 +179,10 @@ void G_SaveGame(UINT32 slot, INT16 mapnum); void G_SaveGameOver(UINT32 slot, boolean modifylives); -extern UINT32 gametypedefaultrules[NUMGAMETYPES]; -extern UINT32 gametypetol[NUMGAMETYPES]; -extern INT16 gametyperankings[NUMGAMETYPES]; - void G_SetGametype(INT16 gametype); -INT16 G_AddGametype(UINT32 rules); -void G_AddGametypeConstant(INT16 gtype, const char *newgtconst); +char *G_PrepareGametypeConstant(const char *newgtconst); void G_UpdateGametypeSelections(void); void G_AddTOL(UINT32 newtol, const char *tolname); -void G_AddGametypeTOL(INT16 gtype, UINT32 newtol); INT32 G_GetGametypeByName(const char *gametypestr); boolean G_IsSpecialStage(INT32 mapnum); boolean G_GametypeUsesLives(void); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index bbccb232a..e9d2a7fbd 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2406,8 +2406,8 @@ static void HU_DrawRankings(void) // draw the current gametype in the lower right if (modeattacking) V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Record Attack"); - else - V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, Gametype_Names[gametype]); + else if (gametype >= 0 && gametype < numgametypes) + V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametypes[gametype]->name); if ((gametyperules & (GTR_TIMELIMIT|GTR_POINTLIMIT)) && !bossinfo.boss) { diff --git a/src/k_kart.c b/src/k_kart.c index 932ac0b28..3aa21376a 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -162,7 +162,7 @@ void K_TimerInit(void) } else { - timelimitintics = timelimits[gametype] * (60*TICRATE); + timelimitintics = gametypes[gametype]->timelimit * (60*TICRATE); } } else diff --git a/src/k_menudraw.c b/src/k_menudraw.c index b1e3557c7..dc2101f8c 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2002,7 +2002,7 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup) else { if (currentMenu == &PLAY_LevelSelectDef) - V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, va("%s Mode", Gametype_Names[levellist.newgametype])); + V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, va("%s Mode", gametypes[levellist.newgametype]->name)); } } @@ -2261,7 +2261,7 @@ void M_DrawTimeAttack(void) laprec = mapheaderinfo[map]->mainrecord->lap; } - if (gametypedefaultrules[levellist.newgametype] & GTR_CIRCUIT) + if (gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) { V_DrawRightAlignedString(rightedge-12, timeheight, highlightflags, "BEST LAP:"); K_drawKartTimestamp(laprec, 162+t, timeheight+6, 0, 2); @@ -3966,13 +3966,13 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) { const char *gtstring = "???"; - if (demoref->gametype >= gametypecount) - ; + if (demoref->gametype >= GT_FIRSTFREESLOT) + ; // TODO: Support custom gametypes in netreplays (would require deeper changes than this) else { - gtstring = Gametype_Names[demoref->gametype]; + gtstring = gametypes[demoref->gametype]->name; - if ((gametypedefaultrules[demoref->gametype] & GTR_CIRCUIT)) + if ((gametypes[demoref->gametype]->rules & GTR_CIRCUIT)) gtstring = va("%s (%s)", gtstring, kartspeed_cons_t[(demoref->kartspeed & ~DF_ENCORE) + 1].strvalue); } @@ -3990,9 +3990,9 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) V_DrawThinString(x, y+29, V_SNAPTOTOP|highlightflags, "WINNER"); V_DrawString(x+38, y+30, V_SNAPTOTOP|V_ALLOWLOWERCASE, demoref->standings[0].name); - if (demoref->gametype < gametypecount) + if (demoref->gametype < GT_FIRSTFREESLOT) { - if (gametypedefaultrules[demoref->gametype] & GTR_POINTLIMIT) + if (gametypes[demoref->gametype]->rules & GTR_POINTLIMIT) { V_DrawThinString(x, y+39, V_SNAPTOTOP|highlightflags, "SCORE"); } @@ -4005,7 +4005,7 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) { V_DrawThinString(x+32, y+39, V_SNAPTOTOP, "NO CONTEST"); } - else if (gametypedefaultrules[demoref->gametype] & GTR_POINTLIMIT) + else if (gametypes[demoref->gametype]->rules & GTR_POINTLIMIT) { V_DrawString(x+32, y+40, V_SNAPTOTOP, va("%d", demoref->standings[0].timeorscore)); } @@ -4211,9 +4211,9 @@ void M_DrawReplayStartMenu(void) if (demoref->standings[i].timeorscore == UINT32_MAX-1) V_DrawThinString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, "NO CONTEST"); - else if (demoref->gametype >= gametypecount) + else if (demoref->gametype >= GT_FIRSTFREESLOT) ; - else if (gametypedefaultrules[demoref->gametype] & GTR_POINTLIMIT) + else if (gametypes[demoref->gametype]->rules & GTR_POINTLIMIT) V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", demoref->standings[i].timeorscore)); else V_DrawRightAlignedString(BASEVIDWIDTH-40, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d'%02d\"%02d", diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 71a447a9e..264c91505 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -149,7 +149,7 @@ static consvar_t cv_menujam = CVAR_INIT ("menujam", "0", CV_SAVE, menujam_cons_t // This gametype list is integral for many different reasons. // When you add gametypes here, don't forget to update them in dehacked.c and doomstat.h! -CV_PossibleValue_t gametype_cons_t[NUMGAMETYPES+1]; +CV_PossibleValue_t gametype_cons_t[MAXGAMETYPES+1]; static CV_PossibleValue_t serversort_cons_t[] = { {0,"Ping"}, @@ -3537,7 +3537,7 @@ static void M_LevelListFromGametype(INT16 gt) { levellist.newgametype = gt; levellist.levelsearch.typeoflevel = G_TOLFlag(gt); - levellist.levelsearch.cupmode = (!(gametypedefaultrules[gt] & GTR_NOCUPSELECT)); + levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); levellist.levelsearch.cup = NULL; first = false; } diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 87e720bea..16347dfe2 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -2971,15 +2971,15 @@ static int lib_gAddGametype(lua_State *L) const char *k; lua_Integer i; + gametype_t *newgametype = NULL; + const char *gtname = NULL; const char *gtconst = NULL; - INT16 newgtidx = 0; UINT32 newgtrules = 0; UINT32 newgttol = 0; INT32 newgtpointlimit = 0; INT32 newgttimelimit = 0; - INT16 newgtrankingstype = -1; - int newgtinttype = 0; + UINT8 newgtinttype = 0; luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); // Clear out all other possible arguments, leaving only the first one. @@ -2988,8 +2988,10 @@ static int lib_gAddGametype(lua_State *L) return luaL_error(L, "This function cannot be called from within a hook or coroutine!"); // Ran out of gametype slots - if (gametypecount == NUMGAMETYPEFREESLOTS) - return luaL_error(L, "Ran out of free gametype slots!"); + if (numgametypes == GT_LASTFREESLOT) + { + I_Error("Out of Gametype Freeslots while allocating \"%s\"\nLoad less addons to fix this.", gtname); + } #define FIELDERROR(f, e) luaL_error(L, "bad value for " LUA_QL(f) " in table passed to " LUA_QL("G_AddGametype") " (%s)", e); #define TYPEERROR(f, t) FIELDERROR(f, va("%s expected, got %s", lua_typename(L, t), luaL_typename(L, -1))) @@ -3022,19 +3024,15 @@ static int lib_gAddGametype(lua_State *L) if (!lua_isnumber(L, 3)) TYPEERROR("typeoflevel", LUA_TNUMBER) newgttol = (UINT32)lua_tointeger(L, 3); - } else if (i == 5 || (k && fasticmp(k, "rankingtype"))) { - if (!lua_isnumber(L, 3)) - TYPEERROR("rankingtype", LUA_TNUMBER) - newgtrankingstype = (INT16)lua_tointeger(L, 3); - } else if (i == 6 || (k && fasticmp(k, "intermissiontype"))) { + } else if (i == 5 || (k && fasticmp(k, "intermissiontype"))) { if (!lua_isnumber(L, 3)) TYPEERROR("intermissiontype", LUA_TNUMBER) newgtinttype = (int)lua_tointeger(L, 3); - } else if (i == 7 || (k && fasticmp(k, "defaultpointlimit"))) { + } else if (i == 6 || (k && fasticmp(k, "defaultpointlimit"))) { if (!lua_isnumber(L, 3)) TYPEERROR("defaultpointlimit", LUA_TNUMBER) newgtpointlimit = (INT32)lua_tointeger(L, 3); - } else if (i == 8 || (k && fasticmp(k, "defaulttimelimit"))) { + } else if (i == 7 || (k && fasticmp(k, "defaulttimelimit"))) { if (!lua_isnumber(L, 3)) TYPEERROR("defaulttimelimit", LUA_TNUMBER) newgttimelimit = (INT32)lua_tointeger(L, 3); @@ -3053,30 +3051,30 @@ static int lib_gAddGametype(lua_State *L) gtname = Z_StrDup("Unnamed gametype"); // Add the new gametype - newgtidx = G_AddGametype(newgtrules); - G_AddGametypeTOL(newgtidx, newgttol); + newgametype = Z_Calloc(sizeof (gametype_t), PU_STATIC, NULL); + if (!newgametype) + { + I_Error("Out of memory allocating gametype \"%s\"", gtname); + } - // Not covered by G_AddGametype alone. - if (newgtrankingstype == -1) - newgtrankingstype = newgtidx; - gametyperankings[newgtidx] = newgtrankingstype; - intermissiontypes[newgtidx] = newgtinttype; - pointlimits[newgtidx] = newgtpointlimit; - timelimits[newgtidx] = newgttimelimit; - - // Write the new gametype name. - Gametype_Names[newgtidx] = gtname; - - // Write the constant name. if (gtconst == NULL) gtconst = gtname; - G_AddGametypeConstant(newgtidx, gtconst); + + newgametype->name = gtname; + newgametype->rules = newgtrules; + newgametype->constant = G_PrepareGametypeConstant(gtconst); + newgametype->tol = newgttol; + newgametype->intermission = newgtinttype; + newgametype->pointlimit = newgtpointlimit; + newgametype->timelimit = newgttimelimit; + + gametypes[numgametypes++] = newgametype; // Update gametype_cons_t accordingly. G_UpdateGametypeSelections(); // done - CONS_Printf("Added gametype %s\n", Gametype_Names[newgtidx]); + CONS_Printf("Added gametype %s\n", gtname); return 0; } diff --git a/src/p_setup.c b/src/p_setup.c index 7934874a5..66f2bebc4 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7221,7 +7221,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // This is needed. Don't touch. maptol = mapheaderinfo[gamemap-1]->typeoflevel; - gametyperules = gametypedefaultrules[gametype]; CON_Drawer(); // let the user know what we are going to do I_FinishUpdate(); // page flip or blit buffer diff --git a/src/typedef.h b/src/typedef.h index 34c652f5e..a43064404 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -104,6 +104,7 @@ TYPEDEF (textpage_t); TYPEDEF (textprompt_t); TYPEDEF (mappoint_t); TYPEDEF (customoption_t); +TYPEDEF (gametype_t); TYPEDEF (mapheader_t); TYPEDEF (tolinfo_t); TYPEDEF (cupheader_t); diff --git a/src/y_inter.c b/src/y_inter.c index 8cb21fe04..413e29e8d 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -98,7 +98,6 @@ static INT32 endtic = -1; static INT32 sorttic = -1; intertype_t intertype = int_none; -intertype_t intermissiontypes[NUMGAMETYPES]; static huddrawlist_h luahuddrawlist_intermission; @@ -751,15 +750,14 @@ void Y_Ticker(void) // void Y_DetermineIntermissionType(void) { - // set to int_none initially - intertype = int_none; + // set initially + intertype = gametypes[gametype]->intermission; - if (gametype == GT_RACE) - intertype = int_race; - else if (gametype == GT_BATTLE) + // TODO: special cases + if (gametype == GT_BATTLE) { if (grandprixinfo.gp == true && bossinfo.boss == false) - intertype = int_none; + return; else { UINT8 i = 0, nump = 0; @@ -772,8 +770,6 @@ void Y_DetermineIntermissionType(void) intertype = (nump < 2 ? int_battletime : int_battle); } } - else //if (intermissiontypes[gametype] != int_none) - intertype = intermissiontypes[gametype]; } // @@ -830,9 +826,6 @@ void Y_StartIntermission(void) sorttic = max((timer/2) - 2*TICRATE, 2*TICRATE); } - if (intermissiontypes[gametype] != int_none) - intertype = intermissiontypes[gametype]; - // We couldn't display the intermission even if we wanted to. // But we still need to give the players their score bonuses, dummy. //if (dedicated) return; @@ -1571,7 +1564,7 @@ void Y_StartVote(void) // set up the gtc and gts levelinfo[i].gtc = G_GetGametypeColor(votelevels[i][1]); if (i == 2 && votelevels[i][1] != votelevels[0][1]) - levelinfo[i].gts = Gametype_Names[votelevels[i][1]]; + levelinfo[i].gts = gametypes[votelevels[i][1]]->name; else levelinfo[i].gts = NULL; } diff --git a/src/y_inter.h b/src/y_inter.h index 6a887dea8..5bcd0be17 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -32,4 +32,3 @@ typedef enum } intertype_t; extern intertype_t intertype; -extern intertype_t intermissiontypes[NUMGAMETYPES]; From eb74ec390488be75e4558b6dc198736d55f016f0 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 26 Dec 2022 22:06:26 +0000 Subject: [PATCH 007/128] Change the rules for some things selected in the previous commit. - Only show lap count and gamespeed on rankings if GTR_CIRCUIT - Adjust offsets for speedometer/accessability icons with GTR_BUMPERS|GTR_SPHERES - Only show Karma on Bumpers hud with GTR_KARMA - Permit battle fullscreen without GTR_KARMA - Make the Break The Capsules roulette show up with GTR_CAPSULES, not absence of GTR_CIRCUIT - Don't push Break The Capsules roulette to the item list twice - Make the special mode switching of int_battle happen for all instances of int_battle, not just GT_BATTLE --- src/hu_stuff.c | 2 +- src/k_hud.c | 14 +++++++------- src/k_roulette.c | 6 +----- src/y_inter.c | 8 +++++--- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/hu_stuff.c b/src/hu_stuff.c index e9d2a7fbd..d9d2cb6d2 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2438,7 +2438,7 @@ static void HU_DrawRankings(void) V_DrawCenteredString(256, 16, hilicol, va("%d", cv_pointlimit.value)); } } - else + else if (gametyperules & GTR_CIRCUIT) { if (circuitmap) { diff --git a/src/k_hud.c b/src/k_hud.c index d55c4d822..4c2b3fe16 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -2498,7 +2498,7 @@ static void K_drawKartAccessibilityIcons(INT32 fx) if (r_splitscreen < 2) // adjust to speedometer height { - if (gametype == GT_BATTLE) + if (gametyperules & (GTR_BUMPERS|GTR_SPHERES)) fy -= 4; } else @@ -2588,7 +2588,7 @@ static void K_drawKartSpeedometer(void) numbers[1] = ((convSpeed / 10) % 10); numbers[2] = (convSpeed % 10); - if (gametype == GT_BATTLE) + if (gametyperules & (GTR_BUMPERS|GTR_SPHERES)) battleoffset = -4; V_DrawScaledPatch(LAPS_X, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometersticker); @@ -2793,10 +2793,10 @@ static void K_drawKartBumpersOrKarma(void) else V_DrawMappedPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_bumpersticker, colormap); - if (bossinfo.boss) - V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", stplyr->bumpers, maxbumper)); - else // TODO BETTER HUD + if (gametyperules & GTR_KARMA) // TODO BETTER HUD V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d %d", stplyr->bumpers, maxbumper, stplyr->overtimekarma / TICRATE)); + else + V_DrawKartString(LAPS_X+47, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", stplyr->bumpers, maxbumper)); } } } @@ -5026,10 +5026,10 @@ void K_drawKartHUD(void) return; } - battlefullscreen = ((gametyperules & (GTR_BUMPERS|GTR_KARMA)) == (GTR_BUMPERS|GTR_KARMA) + battlefullscreen = ((gametyperules & (GTR_BUMPERS)) && (stplyr->exiting || (stplyr->bumpers <= 0 - && stplyr->karmadelay > 0 + && ((gametyperules & GTR_KARMA) && (stplyr->karmadelay > 0)) && !(stplyr->pflags & PF_ELIMINATED) && stplyr->playerstate == PST_LIVE))); diff --git a/src/k_roulette.c b/src/k_roulette.c index 7c283ca1f..20d7d19b6 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1128,13 +1128,9 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet kartitems_t *presetlist = K_KartItemReelTimeAttack; // If the objective is not to go fast, it's to cause serious damage. - if (!(gametyperules & GTR_CIRCUIT)) + if (gametyperules & GTR_CAPSULES) { presetlist = K_KartItemReelBreakTheCapsules; - for (i = 0; K_KartItemReelBreakTheCapsules[i] != KITEM_NONE; i++) - { - K_PushToRouletteItemList(roulette, K_KartItemReelBreakTheCapsules[i]); - } } for (i = 0; presetlist[i] != KITEM_NONE; i++) diff --git a/src/y_inter.c b/src/y_inter.c index 413e29e8d..24c24dd3f 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -754,10 +754,12 @@ void Y_DetermineIntermissionType(void) intertype = gametypes[gametype]->intermission; // TODO: special cases - if (gametype == GT_BATTLE) + if (intertype == int_battle) { - if (grandprixinfo.gp == true && bossinfo.boss == false) - return; + if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) + { + intertype = int_none; + } else { UINT8 i = 0, nump = 0; From a8a60460fbfb88e7bd612c4594b359c4d02ad42d Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 26 Dec 2022 22:16:14 +0000 Subject: [PATCH 008/128] Server startup cleanup * Don't do a shoddy attempt at guessing initial gametype in G_DeferedInitNew * Fix map command on main menu regression (no longer HOM void) --- src/d_clisrv.c | 3 ++- src/g_game.c | 12 +----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index f3b2fc391..e2e3da806 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2081,8 +2081,9 @@ static void CL_ConnectToServer(void) Y_EndVote(); DEBFILE(va("waiting %d nodes\n", doomcom->numnodes)); - M_ClearMenus(true); G_SetGamestate(GS_WAITINGPLAYERS); + if (wipegamestate == GS_MENU) + M_ClearMenus(true); wipegamestate = GS_WAITINGPLAYERS; ClearAdminPlayers(); diff --git a/src/g_game.c b/src/g_game.c index dc7c905e9..827cd2966 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4816,7 +4816,6 @@ cleanup: void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ssplayers, boolean FLS) { UINT16 color = SKINCOLOR_NONE; - INT32 dogametype; paused = false; @@ -4827,17 +4826,8 @@ void G_DeferedInitNew(boolean pencoremode, INT32 map, INT32 pickedchar, UINT8 ss G_ResetRandMapBuffer(); - if ((modeattacking == ATTACKING_CAPSULES) || (bossinfo.boss == true)) - { - dogametype = GT_BATTLE; - } - else - { - dogametype = GT_RACE; - } - // this leave the actual game if needed - SV_StartSinglePlayerServer(dogametype, false); + SV_StartSinglePlayerServer(gametype, false); if (splitscreen != ssplayers) { From d29e43f80d2d0eb278ec4f8335ccf1bffd0865a7 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 26 Dec 2022 22:29:31 +0000 Subject: [PATCH 009/128] Remove GTR_CAMPAIGN Controlled three things: Vintage SRB2 cutscenes, a certain type of reset on map commands, and whether to go to ceremony/evaluation/credits. All three should be controlled by grandprixinfo.gp instead, since that persists cross-gametype. --- src/d_netcmd.c | 5 ++--- src/doomstat.h | 3 --- src/g_game.c | 8 ++++---- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 2e0e30dac..f5576696d 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2521,7 +2521,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d pencoremode=%d resetplayers=%d delay=%d skipprecutscene=%d\n", mapnum, newgametype, pencoremode, resetplayers, delay, skipprecutscene); - if ((netgame || multiplayer) && !((gametype == newgametype) && (gametypes[newgametype]->rules & GTR_CAMPAIGN))) + if ((netgame || multiplayer) && (grandprixinfo.gp != false)) FLS = false; // Too lazy to change the input value for every instance of this function....... @@ -2884,8 +2884,7 @@ static void Command_Map_f(void) { fromlevelselect = ( netgame || multiplayer ) && - newgametype == gametype && - gametypes[newgametype]->rules & GTR_CAMPAIGN; + grandprixinfo.gp != false; } } diff --git a/src/doomstat.h b/src/doomstat.h index 8c01cd474..588a548c5 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -508,9 +508,6 @@ enum GameTypeRules GTR_NOTEAMS = 1<<15, // Teams are forced off GTR_TEAMSTARTS = 1<<16, // Use team-based start positions - // Grand Prix rules - GTR_CAMPAIGN = 1<<17, // Handles cup-based progression - // To be rearranged later GTR_NOCUPSELECT = 1<<20, // Your maps are not selected via cup. ...mutually exclusive with GTR_CAMPAIGN. GTR_CLOSERPLAYERS = 1<<21, // Buffs spindash and draft power to bring everyone together, nerfs invincibility and grow to prevent excessive combos diff --git a/src/g_game.c b/src/g_game.c index 827cd2966..e7ec8ffa4 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3004,7 +3004,7 @@ static gametype_t defaultgametypes[] = { "Race", "GT_RACE", - GTR_CAMPAIGN|GTR_CIRCUIT|GTR_BOTS|GTR_ENCORE, + GTR_CIRCUIT|GTR_BOTS|GTR_ENCORE, TOL_RACE, int_race, 0, @@ -4026,7 +4026,7 @@ void G_AfterIntermission(void) G_HandleSaveLevel(); } - if ((gametyperules & GTR_CAMPAIGN) && mapheaderinfo[prevmap]->cutscenenum && !modeattacking && skipstats <= 1 && (gamecomplete || !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene. + if ((grandprixinfo.gp == true) && mapheaderinfo[prevmap]->cutscenenum && !modeattacking && skipstats <= 1 && (gamecomplete || !(marathonmode & MA_NOCUTSCENES))) // Start a custom cutscene. F_StartCustomCutscene(mapheaderinfo[prevmap]->cutscenenum-1, false, false); else { @@ -4163,7 +4163,7 @@ void G_EndGame(void) } // Only do evaluation and credits in singleplayer contexts - if (!netgame && (gametyperules & GTR_CAMPAIGN)) + if (!netgame && grandprixinfo.gp == true) { if (nextmap == NEXTMAP_CEREMONY) // end game with ceremony { @@ -4927,7 +4927,7 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr automapactive = false; imcontinuing = false; - if ((gametyperules & GTR_CAMPAIGN) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene. + if ((grandprixinfo.gp == true) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene. F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer); else { From 17dd15b99853fc13947b7d156fedd6d9891add39 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 26 Dec 2022 23:06:24 +0000 Subject: [PATCH 010/128] "Special Mode" (Sealed Stars) and "Versus Mode" (bosses) are now gametypes * The existing structs are now exclusively for handling extra data. * `specialStage` has been renamed to `specialstageinfo`, to reflect that it is not the sole arbiter. * `specialstageinfo.valid` and `bossinfo.valid` are what must be checked before grabbing data from either struct. * These are turned on when the gametype extra data is successfully initialised, not on map start. * `K_InitBossHealthBar(...)` for `bossinfo.valid` * `K_InitSpecialStage(void)` for `specialstageinfo.valid` * `K_CanChangeRules(...)` no longer checks these * No longer uses duplicate encore information. * The map command (and -warp) now guesses gametype using a general `G_GuessGametypeByTOL(UINT32)` function * Grabs the first gametype with an overlap between the requested TOL and the gametype's TOL. * The cool Versus-specific intro is now checked via `K_CheckBossIntro()`. --- src/d_clisrv.c | 1 - src/d_main.c | 30 ++++++------ src/d_netcmd.c | 64 ++++++++++--------------- src/doomstat.h | 9 +++- src/g_game.c | 112 ++++++++++++++++++++++++------------------- src/g_game.h | 2 + src/hu_stuff.c | 3 +- src/k_battle.c | 5 +- src/k_boss.c | 39 +++++++++++++-- src/k_boss.h | 17 ++++++- src/k_bot.c | 3 +- src/k_grandprix.c | 17 +------ src/k_hud.c | 14 +++--- src/k_kart.c | 34 ++++++------- src/k_pwrlv.c | 3 +- src/k_roulette.c | 18 +++---- src/k_specialstage.c | 26 ++++++---- src/k_specialstage.h | 7 ++- src/objects/spb.c | 8 ++-- src/objects/ufo.c | 10 ++-- src/p_inter.c | 1 - src/p_mobj.c | 7 ++- src/p_setup.c | 47 +++++------------- src/p_tick.c | 2 +- src/p_user.c | 5 +- src/s_sound.c | 1 - src/st_stuff.c | 4 +- src/v_video.c | 2 +- src/y_inter.c | 4 +- 29 files changed, 257 insertions(+), 238 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index e2e3da806..ac602a28f 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -54,7 +54,6 @@ #include "k_pwrlv.h" #include "k_bot.h" #include "k_grandprix.h" -#include "k_boss.h" #include "doomstat.h" #include "s_sound.h" // sfx_syfail #include "m_cond.h" // netUnlocked diff --git a/src/d_main.c b/src/d_main.c index f8b7b0845..75101ceb6 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -72,7 +72,6 @@ // SRB2Kart #include "k_grandprix.h" -#include "k_boss.h" #include "doomstat.h" #include "m_random.h" // P_ClearRandom #include "k_specialstage.h" @@ -969,12 +968,6 @@ void D_StartTitle(void) // Reset GP memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); - // Reset boss info - K_ResetBossInfo(); - - // Reset Special Stage - K_ResetSpecialStage(); - // empty maptol so mario/etc sounds don't play in sound test when they shouldn't maptol = 0; @@ -1204,13 +1197,14 @@ D_ConvertVersionNumbers (void) // void D_SRB2Main(void) { - INT32 i, p; + INT32 i, j, p; #ifdef DEVELOP INT32 pstartmap = 1; // default to first loaded map (Test Run) #else INT32 pstartmap = 0; // default to random map (0 is not a valid map number) #endif boolean autostart = false; + INT32 newgametype = -1; /* break the version string into version numbers, for netplay */ D_ConvertVersionNumbers(); @@ -1788,8 +1782,6 @@ void D_SRB2Main(void) if (M_CheckParm("-gametype") && M_IsNextParm()) { // from Command_Map_f - INT32 j; - INT16 newgametype = -1; const char *sgametype = M_GetNextParm(); newgametype = G_GetGametypeByName(sgametype); @@ -1811,7 +1803,6 @@ void D_SRB2Main(void) if (M_CheckParm("-skill") && M_IsNextParm()) { - INT32 j; INT16 newskill = -1; const char *sskill = M_GetNextParm(); @@ -1864,11 +1855,20 @@ void D_SRB2Main(void) if (grandprixinfo.gp == true && mapheaderinfo[pstartmap-1]) { - if (mapheaderinfo[pstartmap-1]->typeoflevel & TOL_SPECIAL) + if (newgametype == -1) { - specialStage.active = true; - specialStage.encore = grandprixinfo.encore; - grandprixinfo.eventmode = GPEVENT_SPECIAL; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[pstartmap-1]->typeoflevel); + if (newgametype != -1) + { + j = gametype; + G_SetGametype(newgametype); + D_GameTypeChanged(j); + } + + if (gametyperules & (GTR_BOSS|GTR_CATCHER)) + grandprixinfo.eventmode = GPEVENT_SPECIAL; + else if (gametype != GT_RACE) + grandprixinfo.eventmode = GPEVENT_BONUS; } G_SetUsedCheats(); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index f5576696d..32fe1fc8c 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -57,7 +57,6 @@ #include "k_color.h" #include "k_respawn.h" #include "k_grandprix.h" -#include "k_boss.h" #include "k_follower.h" #include "doomstat.h" #include "deh_tables.h" @@ -2525,15 +2524,7 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pencoremode, boolean r FLS = false; // Too lazy to change the input value for every instance of this function....... - if (bossinfo.boss == true) - { - pencoremode = bossinfo.encore; - } - else if (specialStage.active == true) - { - pencoremode = specialStage.encore; - } - else if (grandprixinfo.gp == true) + if (grandprixinfo.gp == true) { pencoremode = grandprixinfo.encore; } @@ -2843,7 +2834,15 @@ static void Command_Map_f(void) if (mapheaderinfo[newmapnum-1]) { // Let's just guess so we don't have to specify the gametype EVERY time... - newgametype = (mapheaderinfo[newmapnum-1]->typeoflevel & (TOL_BATTLE|TOL_BOSS)) ? GT_BATTLE : GT_RACE; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[newmapnum-1]->typeoflevel); + + if (newgametype == -1) + { + CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support any known gametype!\n"), realmapname, G_BuildMapName(newmapnum)); + Z_Free(realmapname); + Z_Free(mapname); + return; + } } } @@ -2855,6 +2854,8 @@ static void Command_Map_f(void) if (!M_SecretUnlocked(SECRET_ENCORE, false) && newencoremode == true && !usingcheats) { CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n")); + Z_Free(realmapname); + Z_Free(mapname); return; } } @@ -2875,7 +2876,7 @@ static void Command_Map_f(void) mapheaderinfo[newmapnum-1]->typeoflevel & G_TOLFlag(newgametype) )) { - CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), (gametype_cons_t[newgametype].strvalue)); + CONS_Alert(CONS_WARNING, M_GetText("%s (%s) doesn't support %s mode!\n(Use -force to override)\n"), realmapname, G_BuildMapName(newmapnum), gametypes[newgametype]->name); Z_Free(realmapname); Z_Free(mapname); return; @@ -2940,35 +2941,18 @@ static void Command_Map_f(void) grandprixinfo.eventmode = GPEVENT_NONE; - if (newgametype == GT_BATTLE) + if (gametypes[newgametype]->rules & (GTR_BOSS|GTR_CATCHER)) + { + grandprixinfo.eventmode = GPEVENT_SPECIAL; + } + else if (newgametype != GT_RACE) { grandprixinfo.eventmode = GPEVENT_BONUS; - - if (mapheaderinfo[newmapnum-1] && - mapheaderinfo[newmapnum-1]->typeoflevel & TOL_BOSS) - { - bossinfo.boss = true; - bossinfo.encore = newencoremode; - } - else - { - bossinfo.boss = false; - K_ResetBossInfo(); - } } - else + + if (!Playing()) { - if (mapheaderinfo[newmapnum-1] && - mapheaderinfo[newmapnum-1]->typeoflevel & TOL_SPECIAL) // Special Stage - { - specialStage.active = true; - specialStage.encore = newencoremode; - grandprixinfo.eventmode = GPEVENT_SPECIAL; - } - else - { - specialStage.active = false; - } + multiplayer = true; } } @@ -3022,7 +3006,7 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum) else if (gametype != lastgametype) D_GameTypeChanged(lastgametype); // emulate consvar_t behavior for gametype - if (!(gametyperules & GTR_ENCORE) && !bossinfo.boss) + if (!(gametyperules & GTR_ENCORE)) pencoremode = false; skipprecutscene = ((flags & (1<<2)) != 0); @@ -5737,11 +5721,11 @@ void Command_Retry_f(void) { CONS_Printf(M_GetText("You must be in a level to use this.\n")); } - else if (grandprixinfo.gp == false && bossinfo.boss == false) + else if (grandprixinfo.gp == false) { CONS_Printf(M_GetText("This only works in singleplayer games.\n")); } - else if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) + else if (grandprixinfo.eventmode == GPEVENT_BONUS) { CONS_Printf(M_GetText("You can't retry right now!\n")); } diff --git a/src/doomstat.h b/src/doomstat.h index 588a548c5..1e2c23ee0 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -455,6 +455,8 @@ enum GameType { GT_RACE = 0, GT_BATTLE, + GT_SPECIAL, + GT_VERSUS, GT_FIRSTFREESLOT, GT_LASTFREESLOT = 127, // Previously (GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1) - it would be necessary to rewrite VOTEMODIFIER_ENCORE to go higher than this. @@ -509,12 +511,16 @@ enum GameTypeRules GTR_TEAMSTARTS = 1<<16, // Use team-based start positions // To be rearranged later - GTR_NOCUPSELECT = 1<<20, // Your maps are not selected via cup. ...mutually exclusive with GTR_CAMPAIGN. + GTR_CATCHER = 1<<17, // UFO Catcher (only works with GTR_CIRCUIT) + GTR_BOSS = 1<<18, // Boss intro and spawning + + GTR_NOCUPSELECT = 1<<20, // Your maps are not selected via cup. GTR_CLOSERPLAYERS = 1<<21, // Buffs spindash and draft power to bring everyone together, nerfs invincibility and grow to prevent excessive combos GTR_ENCORE = 1<<22, // Alternate Encore mirroring, scripting, and texture remapping // free: to and including 1<<31 }; +// Remember to update GAMETYPERULE_LIST in deh_soc.c // TODO: replace every instance #define gametyperules (gametypes[gametype]->rules) @@ -531,6 +537,7 @@ enum TypeOfLevel // Modifiers TOL_TV = 0x0100 ///< Midnight Channel specific: draw TV like overlay on HUD }; +// Make sure to update TYPEOFLEVEL too #define MAXTOL (1<<31) #define NUMBASETOLNAMES (5) diff --git a/src/g_game.c b/src/g_game.c index e7ec8ffa4..045255fd4 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1425,9 +1425,9 @@ void G_StartTitleCard(void) // play the sound { sfxenum_t kstart = sfx_kstart; - if (bossinfo.boss) + if (K_CheckBossIntro() == true) kstart = sfx_ssa021; - else if (encoremode) + else if (encoremode == true) kstart = sfx_ruby2; S_StartSound(NULL, kstart); } @@ -2790,22 +2790,9 @@ mapthing_t *G_FindMapStart(INT32 playernum) if (!playeringame[playernum]) return NULL; - // -- Spectators -- - // Order in platform gametypes: Race->DM->CTF - // And, with deathmatch starts: DM->CTF->Race - if (players[playernum].spectator) - { - // In platform gametypes, spawn in Co-op starts first - // Overriden by GTR_BATTLESTARTS. - if (gametyperules & GTR_BATTLESTARTS && bossinfo.boss == false) - spawnpoint = G_FindBattleStartOrFallback(playernum); - else - spawnpoint = G_FindRaceStartOrFallback(playernum); - } - - // -- Grand Prix / Time Attack -- + // -- Time Attack -- // Order: Race->DM->CTF - else if (grandprixinfo.gp || modeattacking) + if (K_TimeAttackRules() == true) spawnpoint = G_FindRaceStartOrFallback(playernum); // -- CTF -- @@ -2911,7 +2898,7 @@ void G_ExitLevel(void) { UINT8 i; boolean youlost = false; - if (bossinfo.boss == true) + if (gametyperules & GTR_BOSS) { youlost = true; for (i = 0; i < MAXPLAYERS; i++) @@ -3021,12 +3008,35 @@ static gametype_t defaultgametypes[] = 0, 2, }, + // GT_SPECIAL + { + "Special", + "GT_SPECIAL", + GTR_CATCHER|GTR_CIRCUIT, + TOL_SPECIAL, + int_race, + 0, + 0, + }, + + // GT_VERSUS + { + "Versus", + "GT_VERSUS", + GTR_BOSS|GTR_SPHERES|GTR_BUMPERS|GTR_POINTLIMIT|GTR_CLOSERPLAYERS|GTR_NOCUPSELECT|GTR_ENCORE, + TOL_BOSS, + int_battle, + 0, + 0, + }, }; gametype_t *gametypes[MAXGAMETYPES+1] = { &defaultgametypes[GT_RACE], &defaultgametypes[GT_BATTLE], + &defaultgametypes[GT_SPECIAL], + &defaultgametypes[GT_VERSUS], }; // @@ -3048,6 +3058,25 @@ INT32 G_GetGametypeByName(const char *gametypestr) return -1; // unknown gametype } +// +// G_GuessGametypeByTOL +// +// Returns the first valid number for the given typeoflevel, or -1 if not valid. +// +INT32 G_GuessGametypeByTOL(UINT32 tol) +{ + INT32 i = 0; + + while (gametypes[i] != NULL) + { + if (tol & gametypes[i]->tol) + return i; + i++; + } + + return -1; // unknown gametype +} + // // G_SetGametype // @@ -3651,7 +3680,7 @@ static void G_GetNextMap(void) } else { - INT32 lastgametype = gametype; + INT32 lastgametype = gametype, newgametype = GT_RACE; // 5 levels, 2 bonus stages: after rounds 2 and 4 (but flexible enough to accomodate other solutions) UINT8 bonusmodulo = (grandprixinfo.cup->numlevels+1)/(grandprixinfo.cup->numbonus+1); UINT8 bonusindex = (grandprixinfo.roundnum / bonusmodulo) - 1; @@ -3668,9 +3697,6 @@ static void G_GetNextMap(void) G_SetGametype(GT_RACE); if (gametype != lastgametype) D_GameTypeChanged(lastgametype); - - specialStage.active = false; - bossinfo.boss = false; } // Special stage else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) @@ -3691,11 +3717,11 @@ static void G_GetNextMap(void) if (totaltotalring >= 50) { const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_SPECIAL]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] - && mapheaderinfo[cupLevelNum]->typeoflevel & (TOL_SPECIAL|TOL_BOSS|TOL_BATTLE)) + if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) { grandprixinfo.eventmode = GPEVENT_SPECIAL; nextmap = cupLevelNum; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel); } } } @@ -3706,37 +3732,27 @@ static void G_GetNextMap(void) // todo any other condition? { const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[CUPCACHE_BONUS + bonusindex]; - if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] - && mapheaderinfo[cupLevelNum]->typeoflevel & (TOL_BOSS|TOL_BATTLE)) + if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum]) { grandprixinfo.eventmode = GPEVENT_BONUS; nextmap = cupLevelNum; + newgametype = G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel); } } } + if (newgametype == -1) + { + // Don't permit invalid changes. + grandprixinfo.eventmode = GPEVENT_NONE; + newgametype = gametype; + } + if (grandprixinfo.eventmode != GPEVENT_NONE) { - // nextmap is set above - const INT32 newtol = mapheaderinfo[nextmap]->typeoflevel; - - if (newtol & TOL_SPECIAL) - { - specialStage.active = true; - specialStage.encore = grandprixinfo.encore; - } - else //(if newtol & (TOL_BATTLE|TOL_BOSS)) -- safe to assume?? - { - G_SetGametype(GT_BATTLE); - if (gametype != lastgametype) - D_GameTypeChanged(lastgametype); - if (newtol & TOL_BOSS) - { - K_ResetBossInfo(); - bossinfo.boss = true; - bossinfo.encore = grandprixinfo.encore; - } - } + G_SetGametype(newgametype); + if (gametype != lastgametype) + D_GameTypeChanged(lastgametype); } else if (grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) // On final map { @@ -3760,10 +3776,6 @@ static void G_GetNextMap(void) } } } - else if (bossinfo.boss == true) - { - nextmap = NEXTMAP_TITLE; // temporary - } else { UINT32 tolflag = G_TOLFlag(gametype); diff --git a/src/g_game.h b/src/g_game.h index 04a9df9c9..407e12d64 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -184,6 +184,8 @@ char *G_PrepareGametypeConstant(const char *newgtconst); void G_UpdateGametypeSelections(void); void G_AddTOL(UINT32 newtol, const char *tolname); INT32 G_GetGametypeByName(const char *gametypestr); +INT32 G_GuessGametypeByTOL(UINT32 tol); + boolean G_IsSpecialStage(INT32 mapnum); boolean G_GametypeUsesLives(void); boolean G_GametypeHasTeams(void); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index d9d2cb6d2..95087a389 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -55,7 +55,6 @@ // SRB2Kart #include "s_sound.h" // song credits #include "k_kart.h" -#include "k_boss.h" #include "k_color.h" #include "k_hud.h" #include "r_fps.h" @@ -2409,7 +2408,7 @@ static void HU_DrawRankings(void) else if (gametype >= 0 && gametype < numgametypes) V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametypes[gametype]->name); - if ((gametyperules & (GTR_TIMELIMIT|GTR_POINTLIMIT)) && !bossinfo.boss) + if ((gametyperules & (GTR_TIMELIMIT|GTR_POINTLIMIT))) { if ((gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) { diff --git a/src/k_battle.c b/src/k_battle.c index 781ff108c..26b1265f3 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -2,7 +2,6 @@ /// \brief SRB2Kart Battle Mode specific code #include "k_battle.h" -#include "k_boss.h" #include "k_kart.h" #include "doomtype.h" #include "doomdata.h" @@ -357,7 +356,7 @@ void K_RunPaperItemSpawners(void) UINT8 pcount = 0; INT16 i; - if (battlecapsules || bossinfo.boss) + if (battlecapsules) { // Gametype uses paper items, but this specific expression doesn't return; @@ -794,7 +793,7 @@ void K_BattleInit(boolean singleplayercontext) { size_t i; - if ((gametyperules & GTR_CAPSULES) && singleplayercontext && !battlecapsules && !bossinfo.boss) + if ((gametyperules & GTR_CAPSULES) && singleplayercontext && !battlecapsules) { mapthing_t *mt = mapthings; for (i = 0; i < nummapthings; i++, mt++) diff --git a/src/k_boss.c b/src/k_boss.c index 490adb27e..ff10231ea 100644 --- a/src/k_boss.c +++ b/src/k_boss.c @@ -23,7 +23,7 @@ struct bossinfo bossinfo; /*-------------------------------------------------- - void K_ClearBossInfo(void) + void K_ResetBossInfo(void) See header file for description. --------------------------------------------------*/ @@ -32,6 +32,8 @@ void K_ResetBossInfo(void) Z_Free(bossinfo.enemyname); Z_Free(bossinfo.subtitle); memset(&bossinfo, 0, sizeof(struct bossinfo)); + bossinfo.barlen = BOSSHEALTHBARLEN; + bossinfo.titlesound = sfx_typri1; } /*-------------------------------------------------- @@ -43,7 +45,7 @@ void K_BossInfoTicker(void) { UINT8 i; - if (bossinfo.boss == false) + if (bossinfo.valid == false) return; // Update healthbar data. (only if the hud is visible) @@ -55,7 +57,7 @@ void K_BossInfoTicker(void) bossinfo.visualbar--; // If the boss is dying, start shrinking the healthbar. if (bossinfo.visualbar == 0) - bossinfo.barlen-= 2; + bossinfo.barlen -= 2; } // Less than the actual health? else if (bossinfo.visualbar < bossinfo.healthbar) @@ -108,6 +110,18 @@ void K_BossInfoTicker(void) void K_InitBossHealthBar(const char *enemyname, const char *subtitle, sfxenum_t titlesound, fixed_t pinchmagnitude, UINT8 divisions) { + if (!(gametyperules & GTR_BOSS)) + { + return; + } + + bossinfo.valid = true; + + if (!leveltime) + { + bossinfo.coolintro = true; + } + if (enemyname && enemyname[0]) { Z_Free(bossinfo.enemyname); @@ -158,6 +172,9 @@ void K_InitBossHealthBar(const char *enemyname, const char *subtitle, sfxenum_t void K_UpdateBossHealthBar(fixed_t magnitude, tic_t jitterlen) { + if (bossinfo.valid == false) + return; + if (magnitude > FRACUNIT) magnitude = FRACUNIT; else if (magnitude < 0) @@ -177,6 +194,9 @@ void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean { UINT8 i; + if (bossinfo.valid == false) + return; + // First check whether the spot is already in the list and simply redeclaring weakness (for example, a vulnerable moment in the pattern). for (i = 0; i < NUMWEAKSPOTS; i++) if (bossinfo.weakspots[i].spot == spot) @@ -206,3 +226,16 @@ void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean bossinfo.doweakspotsound = spottype; } } + +/*-------------------------------------------------- + boolean K_CheckBossIntro(void); + + See header file for description. +--------------------------------------------------*/ + +boolean K_CheckBossIntro(void) +{ + if (bossinfo.valid == false) + return false; + return bossinfo.coolintro; +} diff --git a/src/k_boss.h b/src/k_boss.h index f24365b29..6c77bd800 100644 --- a/src/k_boss.h +++ b/src/k_boss.h @@ -39,7 +39,8 @@ struct weakspot_t extern struct bossinfo { - boolean boss; ///< If true, then we are fighting a boss + boolean valid; ///< If true, then data in this struct is valid + UINT8 healthbar; ///< Actual health bar fill amount UINT8 visualbar; ///< Tracks above, but with delay fixed_t visualdiv; ///< How far apart health bar divisions should appear @@ -48,7 +49,7 @@ extern struct bossinfo UINT8 barlen; ///< The length of the bar (only reduced when a boss is deceased) char *enemyname; ///< The name next to the bar weakspot_t weakspots[NUMWEAKSPOTS]; ///< Array of weak spots (for minimap/object tracking) - boolean encore; ///< Copy of encore, just to make sure you can't cheat it with cvars + boolean coolintro; ///< Determines whether the map start(s/ed) with a boss-specific intro. spottype_t doweakspotsound; ///< If nonzero, at least one weakspot was declared this tic tic_t titleshow; ///< Show this many letters on the titlecard sfxenum_t titlesound; ///< Sound to play when title typing @@ -112,4 +113,16 @@ void K_UpdateBossHealthBar(fixed_t magnitude, tic_t jitterlen); void K_DeclareWeakspot(mobj_t *spot, spottype_t spottype, UINT16 color, boolean minimap); +/*-------------------------------------------------- + boolean K_CheckBossIntro(void); + + Checks whether the Versus-specific intro is playing for this map start. + + Return:- + true if cool intro in action, + otherwise false. +--------------------------------------------------*/ + +boolean K_CheckBossIntro(void); + #endif diff --git a/src/k_bot.c b/src/k_bot.c index caf6aa55f..e6604d338 100644 --- a/src/k_bot.c +++ b/src/k_bot.c @@ -27,7 +27,6 @@ #include "m_random.h" #include "r_things.h" // numskins #include "k_race.h" // finishBeamLine -#include "k_boss.h" #include "m_perfstats.h" @@ -168,7 +167,7 @@ void K_UpdateMatchRaceBots(void) } } - if (difficulty == 0 || !(gametyperules & GTR_BOTS) || bossinfo.boss == true) + if (difficulty == 0 || !(gametyperules & GTR_BOTS)) { wantedbots = 0; } diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 221e9d10c..4ca90fe04 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -11,7 +11,6 @@ /// \brief Grand Prix mode game logic & bot behaviors #include "k_grandprix.h" -#include "k_boss.h" #include "k_specialstage.h" #include "doomdef.h" #include "d_player.h" @@ -529,7 +528,7 @@ void K_RetireBots(void) UINT8 i; if (grandprixinfo.gp == true - && ((grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) + && ((grandprixinfo.cup != NULL && grandprixinfo.roundnum >= grandprixinfo.cup->numlevels) || grandprixinfo.eventmode != GPEVENT_NONE)) { // No replacement. @@ -703,24 +702,12 @@ void K_PlayerLoseLife(player_t *player) --------------------------------------------------*/ boolean K_CanChangeRules(boolean allowdemos) { - if (grandprixinfo.gp == true && grandprixinfo.roundnum > 0) + if (grandprixinfo.gp == true /*&& grandprixinfo.roundnum > 0*/) { // Don't cheat the rules of the GP! return false; } - if (bossinfo.boss == true) - { - // Don't cheat the boss! - return false; - } - - if (specialStage.active == true) - { - // Don't cheat special stages! - return false; - } - if (marathonmode) { // Don't cheat the endurance challenge! diff --git a/src/k_hud.c b/src/k_hud.c index 4c2b3fe16..0d92d2035 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -3360,7 +3360,7 @@ static void K_drawKartNameTags(void) c.z = viewz; // Maybe shouldn't be handling this here... but the camera info is too good. - if (bossinfo.boss) + if (bossinfo.valid == true) { weakspotdraw_t weakspotdraw[NUMWEAKSPOTS]; UINT8 numdraw = 0; @@ -3923,7 +3923,7 @@ static void K_drawKartMinimap(void) // Target reticule if (((gametyperules & GTR_CIRCUIT) && players[i].position == spbplace) - || ((gametyperules & GTR_POINTLIMIT) && K_IsPlayerWanted(&players[i]))) + || ((gametyperules & (GTR_BOSS|GTR_POINTLIMIT)) == GTR_POINTLIMIT && K_IsPlayerWanted(&players[i]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); } @@ -3981,7 +3981,7 @@ static void K_drawKartMinimap(void) } // ...but first, any boss targets. - if (bossinfo.boss) + if (bossinfo.valid == true) { for (i = 0; i < NUMWEAKSPOTS; i++) { @@ -4050,7 +4050,7 @@ static void K_drawKartMinimap(void) // Target reticule if (((gametyperules & GTR_CIRCUIT) && players[localplayers[i]].position == spbplace) - || ((gametyperules & GTR_POINTLIMIT) && K_IsPlayerWanted(&players[localplayers[i]]))) + || ((gametyperules & (GTR_BOSS|GTR_POINTLIMIT)) == GTR_POINTLIMIT && K_IsPlayerWanted(&players[localplayers[i]]))) { K_drawKartMinimapIcon(interpx, interpy, x, y, splitflags, kp_wantedreticle, NULL, AutomapPic); } @@ -4838,7 +4838,7 @@ void K_drawKartFreePlay(void) if (!LUA_HudEnabled(hud_freeplay)) return; - if (modeattacking || grandprixinfo.gp || bossinfo.boss || stplyr->spectator) + if (modeattacking || grandprixinfo.gp || bossinfo.valid || stplyr->spectator) return; if (lt_exitticker < TICRATE/2) @@ -5098,7 +5098,7 @@ void K_drawKartHUD(void) { if (LUA_HudEnabled(hud_position)) { - if (bossinfo.boss) + if (bossinfo.valid) { K_drawBossHealthBar(); } @@ -5140,7 +5140,7 @@ void K_drawKartHUD(void) K_drawBlueSphereMeter(); } - if (modeattacking && !bossinfo.boss) + if (modeattacking && !bossinfo.valid) { // Draw the input UI if (LUA_HudEnabled(hud_position)) diff --git a/src/k_kart.c b/src/k_kart.c index 3aa21376a..296b8cb69 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6,7 +6,6 @@ #include "k_kart.h" #include "k_battle.h" -#include "k_boss.h" #include "k_pwrlv.h" #include "k_color.h" #include "k_respawn.h" @@ -41,6 +40,7 @@ #include "k_follower.h" #include "k_objects.h" #include "k_grandprix.h" +#include "k_boss.h" #include "k_specialstage.h" #include "k_roulette.h" @@ -109,11 +109,13 @@ void K_TimerInit(void) boolean domodeattack = ((modeattacking != ATTACKING_NONE) || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)); - if (specialStage.active == true) + if ((gametyperules & (GTR_CATCHER|GTR_CIRCUIT)) == (GTR_CATCHER|GTR_CIRCUIT)) { K_InitSpecialStage(); } - else if (bossinfo.boss == false) + else if (K_CheckBossIntro() == true) + ; + else { if (!domodeattack) { @@ -152,7 +154,7 @@ void K_TimerInit(void) K_BattleInit(domodeattack); - if ((gametyperules & GTR_TIMELIMIT) && !bossinfo.boss && !modeattacking) + if ((gametyperules & GTR_TIMELIMIT) && !modeattacking) { if (!K_CanChangeRules(true)) { @@ -343,7 +345,7 @@ boolean K_IsPlayerLosing(player_t *player) if (battlecapsules && numtargets == 0) return true; // Didn't even TRY? - if (battlecapsules || bossinfo.boss) + if (battlecapsules || (gametyperules & GTR_BOSS)) return (player->bumpers <= 0); // anything short of DNF is COOL if (player->position == 1) @@ -513,7 +515,7 @@ boolean K_TimeAttackRules(void) UINT8 playing = 0; UINT8 i; - if (specialStage.active == true) + if ((gametyperules & (GTR_CATCHER|GTR_CIRCUIT)) == (GTR_CATCHER|GTR_CIRCUIT)) { // Kind of a hack -- Special Stages // are expected to be 1-player, so @@ -1301,8 +1303,8 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed */ static void K_UpdateDraft(player_t *player) { - const boolean addUfo = ((specialStage.active == true) - && (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false)); + const boolean addUfo = ((specialstageinfo.valid == true) + && (specialstageinfo.ufo != NULL && P_MobjWasRemoved(specialstageinfo.ufo) == false)); fixed_t topspd = K_GetKartSpeed(player, false, false); fixed_t draftdistance; @@ -1345,7 +1347,7 @@ static void K_UpdateDraft(player_t *player) if (addUfo == true) { // Tether off of the UFO! - if (K_TryDraft(player, specialStage.ufo, minDist, draftdistance, leniency) == true) + if (K_TryDraft(player, specialstageinfo.ufo, minDist, draftdistance, leniency) == true) { return; // Finished doing our draft. } @@ -1402,8 +1404,8 @@ static void K_UpdateDraft(player_t *player) else if (addUfo == true) { // kind of a hack to not have to mess with how lastdraft works - fixed_t dist = P_AproxDistance(P_AproxDistance(specialStage.ufo->x - player->mo->x, specialStage.ufo->y - player->mo->y), specialStage.ufo->z - player->mo->z); - K_DrawDraftCombiring(player, specialStage.ufo, dist, draftdistance, true); + fixed_t dist = P_AproxDistance(P_AproxDistance(specialstageinfo.ufo->x - player->mo->x, specialstageinfo.ufo->y - player->mo->y), specialstageinfo.ufo->z - player->mo->z); + K_DrawDraftCombiring(player, specialstageinfo.ufo, dist, draftdistance, true); } } else // Remove draft speed boost. @@ -4128,7 +4130,7 @@ void K_HandleBumperChanges(player_t *player, UINT8 prevBumpers) player->karmadelay = comebacktime; - if (bossinfo.boss) + if (gametyperules & GTR_BOSS) { P_DoTimeOver(player); } @@ -6789,10 +6791,10 @@ mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) mobj_t *wtarg = NULL; INT32 i; - if (specialStage.active == true) + if (specialstageinfo.valid == true) { // Always target the UFO. - return specialStage.ufo; + return specialstageinfo.ufo; } for (i = 0; i < MAXPLAYERS; i++) @@ -8028,10 +8030,10 @@ void K_KartPlayerAfterThink(player_t *player) mobj_t *ret = NULL; - if (specialStage.active == true && lastTargID == MAXPLAYERS) + if (specialstageinfo.valid == true && lastTargID == MAXPLAYERS) { // Aiming at the UFO. - lastTarg = specialStage.ufo; + lastTarg = specialstageinfo.ufo; } else if ((lastTargID >= 0 && lastTargID <= MAXPLAYERS) && playeringame[lastTargID] == true) diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index 6ad088cae..db46a9266 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -16,7 +16,6 @@ #include "m_cond.h" // M_UpdateUnlockablesAndExtraEmblems #include "p_tick.h" // leveltime #include "k_grandprix.h" -#include "k_boss.h" #include "k_profiles.h" // Client-sided calculations done for Power Levels. @@ -38,7 +37,7 @@ SINT8 K_UsingPowerLevels(void) { SINT8 pt = PWRLV_DISABLED; - if (!cv_kartusepwrlv.value || !(netgame || (demo.playback && demo.netgame)) || grandprixinfo.gp == true || bossinfo.boss == true) + if (!cv_kartusepwrlv.value || !(netgame || (demo.playback && demo.netgame)) || grandprixinfo.gp == true) { return PWRLV_DISABLED; } diff --git a/src/k_roulette.c b/src/k_roulette.c index 20d7d19b6..9a5442a03 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -359,7 +359,7 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers return 0; } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { UINT32 ufoDis = K_GetSpecialUFODistance(); @@ -506,7 +506,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table newOdds = K_KartItemOddsBattle[item-1][pos]; } - else if (specialStage.active == true) + else if (specialstageinfo.valid == true) { I_Assert(pos < 4); // Ditto newOdds = K_KartItemOddsSpecial[item-1][pos]; @@ -573,7 +573,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, return 0; } - if (specialStage.active == false) + if (specialstageinfo.valid == false) { if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done || position == 1) // No SPB for 1st ever @@ -705,7 +705,7 @@ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulett oddsValid[i] = false; continue; } - else if (specialStage.active == true && i > 3) + else if (specialstageinfo.valid == true && i > 3) { oddsValid[i] = false; continue; @@ -734,7 +734,7 @@ static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulett } else { - if (specialStage.active == true) // Special Stages + if (specialstageinfo.valid == true) // Special Stages { SETUPDISTTABLE(0,2); SETUPDISTTABLE(1,2); @@ -808,7 +808,7 @@ static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulett return false; } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { return false; } @@ -904,7 +904,7 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->exiting++; } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { UINT32 dis = K_UndoMapScaling(players[i].distancetofinish); if (dis < roulette->secondDist) @@ -926,7 +926,7 @@ static void K_InitRoulette(itemroulette_t *const roulette) } } - if (specialStage.active == true) + if (specialstageinfo.valid == true) { roulette->firstDist = K_UndoMapScaling(K_GetSpecialUFODistance()); } @@ -1114,7 +1114,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // SPECIAL CASE No. 2: // Use a special, pre-determined item reel for Time Attack / Free Play - if (bossinfo.boss == true) + if (gametyperules & GTR_BOSS) { for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++) { diff --git a/src/k_specialstage.c b/src/k_specialstage.c index e0551bef1..88ce729bb 100644 --- a/src/k_specialstage.c +++ b/src/k_specialstage.c @@ -22,7 +22,7 @@ #include "k_waypoint.h" #include "k_objects.h" -struct specialStage specialStage; +struct specialstageinfo specialstageinfo; /*-------------------------------------------------- void K_ResetSpecialStage(void) @@ -31,7 +31,8 @@ struct specialStage specialStage; --------------------------------------------------*/ void K_ResetSpecialStage(void) { - memset(&specialStage, 0, sizeof(struct specialStage)); + memset(&specialstageinfo, 0, sizeof(struct specialstageinfo)); + specialstageinfo.beamDist = UINT32_MAX; } /*-------------------------------------------------- @@ -43,8 +44,15 @@ void K_InitSpecialStage(void) { INT32 i; - specialStage.beamDist = UINT32_MAX; // TODO: make proper value - P_SetTarget(&specialStage.ufo, Obj_CreateSpecialUFO()); + if ((gametyperules & (GTR_CATCHER|GTR_CIRCUIT)) != (GTR_CATCHER|GTR_CIRCUIT)) + { + return; + } + + specialstageinfo.valid = true; + + specialstageinfo.beamDist = UINT32_MAX; // TODO: make proper value + P_SetTarget(&specialstageinfo.ufo, Obj_CreateSpecialUFO()); for (i = 0; i < MAXPLAYERS; i++) { @@ -88,15 +96,15 @@ static void K_MoveExitBeam(void) moveDist = (8 * mapobjectscale) / FRACUNIT; - if (specialStage.beamDist <= moveDist) + if (specialstageinfo.beamDist <= moveDist) { - specialStage.beamDist = 0; + specialstageinfo.beamDist = 0; // TODO: Fail Special Stage } else { - specialStage.beamDist -= moveDist; + specialstageinfo.beamDist -= moveDist; } // Find players who are now outside of the level. @@ -118,7 +126,7 @@ static void K_MoveExitBeam(void) continue; } - if (player->distancetofinish > specialStage.beamDist) + if (player->distancetofinish > specialstageinfo.beamDist) { P_DoTimeOver(player); } @@ -132,7 +140,7 @@ static void K_MoveExitBeam(void) --------------------------------------------------*/ void K_TickSpecialStage(void) { - if (specialStage.active == false) + if (specialstageinfo.valid == false) { return; } diff --git a/src/k_specialstage.h b/src/k_specialstage.h index c93136b99..091a39f7d 100644 --- a/src/k_specialstage.h +++ b/src/k_specialstage.h @@ -16,14 +16,13 @@ #include "doomdef.h" #include "doomstat.h" -extern struct specialStage +extern struct specialstageinfo { - boolean active; ///< If true, then we are in a special stage - boolean encore; ///< Copy of encore, just to make sure you can't cheat it with cvars + boolean valid; ///< If true, then data in this struct is valid UINT32 beamDist; ///< Where the exit beam is. mobj_t *ufo; ///< The Chaos Emerald capsule. -} specialStage; +} specialstageinfo; /*-------------------------------------------------- void K_ResetSpecialStage(void); diff --git a/src/objects/spb.c b/src/objects/spb.c index b76dfd4f8..19bb4d7a1 100644 --- a/src/objects/spb.c +++ b/src/objects/spb.c @@ -549,7 +549,7 @@ static void SPBSeek(mobj_t *spb, mobj_t *bestMobj) SetSPBSpeed(spb, xySpeed, zSpeed); - if (specialStage.active == false) + if (specialstageinfo.valid == false) { // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh! steerDist = 1536 * mapobjectscale; @@ -874,12 +874,12 @@ void Obj_SPBThink(mobj_t *spb) } else { - if (specialStage.active == true) + if (specialstageinfo.valid == true) { - if (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false) + if (specialstageinfo.ufo != NULL && P_MobjWasRemoved(specialstageinfo.ufo) == false) { bestRank = 1; - bestMobj = specialStage.ufo; + bestMobj = specialstageinfo.ufo; } } diff --git a/src/objects/ufo.c b/src/objects/ufo.c index 5eaf7f645..d2fffc008 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -239,9 +239,9 @@ static void UFOUpdateAngle(mobj_t *ufo) waypoint_t *K_GetSpecialUFOWaypoint(mobj_t *ufo) { - if ((ufo == NULL) && (specialStage.active == true)) + if ((ufo == NULL) && (specialstageinfo.valid == true)) { - ufo = specialStage.ufo; + ufo = specialstageinfo.ufo; } if (ufo != NULL && P_MobjWasRemoved(ufo) == false @@ -820,11 +820,11 @@ mobj_t *Obj_CreateSpecialUFO(void) UINT32 K_GetSpecialUFODistance(void) { - if (specialStage.active == true) + if (specialstageinfo.valid == true) { - if (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false) + if (specialstageinfo.ufo != NULL && P_MobjWasRemoved(specialstageinfo.ufo) == false) { - return (UINT32)ufo_distancetofinish(specialStage.ufo); + return (UINT32)ufo_distancetofinish(specialstageinfo.ufo); } } diff --git a/src/p_inter.c b/src/p_inter.c index cb1de723d..77020eb08 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -34,7 +34,6 @@ #include "k_battle.h" #include "k_pwrlv.h" #include "k_grandprix.h" -#include "k_boss.h" #include "k_respawn.h" #include "p_spec.h" #include "k_objects.h" diff --git a/src/p_mobj.c b/src/p_mobj.c index 50717459a..a9562b38a 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -39,7 +39,6 @@ // SRB2kart #include "k_kart.h" #include "k_battle.h" -#include "k_boss.h" #include "k_color.h" #include "k_respawn.h" #include "k_bot.h" @@ -9366,7 +9365,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) { if (gametyperules & GTR_PAPERITEMS) { - if (battlecapsules == true || bossinfo.boss == true) + if (battlecapsules == true) { ; } @@ -12075,7 +12074,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) // No bosses outside of a combat situation. // (just in case we want boss arenas to do double duty as battle maps) - if (!bossinfo.boss && (mobjinfo[i].flags & MF_BOSS)) + if (!(gametyperules & GTR_BOSS) && (mobjinfo[i].flags & MF_BOSS)) { return false; } @@ -12096,7 +12095,7 @@ static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i) if ((i == MT_RING) && (gametyperules & GTR_SPHERES)) return MT_BLUESPHERE; - if ((i == MT_RANDOMITEM) && (gametyperules & (GTR_PAPERITEMS|GTR_CIRCUIT)) == (GTR_PAPERITEMS|GTR_CIRCUIT) && !bossinfo.boss) + if ((i == MT_RANDOMITEM) && (gametyperules & (GTR_PAPERITEMS|GTR_CIRCUIT)) == (GTR_PAPERITEMS|GTR_CIRCUIT)) return MT_PAPERITEMSPOT; return i; diff --git a/src/p_setup.c b/src/p_setup.c index 66f2bebc4..f35f32382 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -6844,7 +6844,7 @@ static void P_InitLevelSettings(void) if (playeringame[i] && !players[i].spectator) p++; - if (grandprixinfo.gp == false && bossinfo.boss == false) + if (grandprixinfo.gp == false) players[i].lives = 3; G_PlayerReborn(i, true); @@ -6854,38 +6854,27 @@ static void P_InitLevelSettings(void) racecountdown = exitcountdown = exitfadestarted = 0; curlap = bestlap = 0; // SRB2Kart - // SRB2Kart: map load variables + // Gamespeed and frantic items + gamespeed = KARTSPEED_EASY; + franticitems = false; + if (grandprixinfo.gp == true) { - if (!(gametyperules & GTR_CIRCUIT)) - { - gamespeed = KARTSPEED_EASY; - } - else + if (gametyperules & GTR_CIRCUIT) { gamespeed = grandprixinfo.gamespeed; } - - franticitems = false; - } - else if (bossinfo.boss) - { - gamespeed = KARTSPEED_EASY; - franticitems = false; } else if (modeattacking) { - if (!(gametyperules & GTR_CIRCUIT)) - gamespeed = KARTSPEED_EASY; - else + if (gametyperules & GTR_CIRCUIT) + { gamespeed = KARTSPEED_HARD; - franticitems = false; + } } else { - if (!(gametyperules & GTR_CIRCUIT)) - gamespeed = KARTSPEED_EASY; - else + if (gametyperules & GTR_CIRCUIT) { if (cv_kartspeed.value == KARTSPEED_AUTO) gamespeed = ((speedscramble == -1) ? KARTSPEED_NORMAL : (UINT8)speedscramble); @@ -6900,6 +6889,9 @@ static void P_InitLevelSettings(void) memset(&battleovertime, 0, sizeof(struct battleovertime)); speedscramble = encorescramble = -1; + + K_ResetSpecialStage(); + K_ResetBossInfo(); } #if 0 @@ -7611,19 +7603,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) K_UpdateMatchRaceBots(); } - if (bossinfo.boss) - { - // Reset some pesky boss state that can't be handled elsewhere. - bossinfo.barlen = BOSSHEALTHBARLEN; - bossinfo.visualbar = 0; - Z_Free(bossinfo.enemyname); - Z_Free(bossinfo.subtitle); - bossinfo.enemyname = bossinfo.subtitle = NULL; - bossinfo.titleshow = 0; - bossinfo.titlesound = sfx_typri1; - memset(&(bossinfo.weakspots), 0, sizeof(weakspot_t)*NUMWEAKSPOTS); - } - if (!fromnetsave) // uglier hack { // to make a newly loaded level start on the second frame. INT32 buf = gametic % BACKUPTICS; diff --git a/src/p_tick.c b/src/p_tick.c index 8312a18a8..4ad34d57e 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -646,7 +646,7 @@ void P_Ticker(boolean run) P_PlayerAfterThink(&players[i]); // Bosses have a punchy start, so no position. - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true) { if (leveltime == 3) { diff --git a/src/p_user.c b/src/p_user.c index ac96ea345..25698bca8 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -828,7 +828,8 @@ void P_RestoreMusic(player_t *player) return; // Event - Level Start - if (bossinfo.boss == false && (leveltime < (starttime + (TICRATE/2)))) // see also where time overs are handled + if ((K_CheckBossIntro() == false) + && (leveltime < (starttime + (TICRATE/2)))) // see also where time overs are handled return; { @@ -3612,7 +3613,7 @@ void P_DoTimeOver(player_t *player) legitimateexit = true; // SRB2kart: losing a race is still seeing it through to the end :p } - if (netgame && !player->bot && !bossinfo.boss) + if (netgame && !player->bot && !(gametyperules & GTR_BOSS)) { CON_LogMessage(va(M_GetText("%s ran out of time.\n"), player_names[player-players])); } diff --git a/src/s_sound.c b/src/s_sound.c index 96211d653..a721a9ff4 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -30,7 +30,6 @@ #include "m_misc.h" // for tunes command #include "m_cond.h" // for conditionsets #include "lua_hook.h" // MusicChange hook -#include "k_boss.h" // bossinfo #include "byteptr.h" #ifdef HW3SOUND diff --git a/src/st_stuff.c b/src/st_stuff.c index 5c2def212..7583b4c37 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -597,7 +597,7 @@ void ST_runTitleCard(void) // SRB2KART // side Zig-Zag positions... - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true) { // Handle name info... if (bossinfo.enemyname) @@ -792,7 +792,7 @@ void ST_drawTitleCard(void) if (lt_ticker < TTANIMSTART) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol); - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true) { // WARNING! // https://twitter.com/matthewseiji/status/1485003284196716544 diff --git a/src/v_video.c b/src/v_video.c index d65b34cea..9fcda2425 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -589,7 +589,7 @@ void V_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 du { const tic_t length = TICRATE/4; tic_t timer = lt_exitticker; - if (bossinfo.boss == true) + if (K_CheckBossIntro() == true) { if (leveltime <= 3) timer = 0; diff --git a/src/y_inter.c b/src/y_inter.c index 24c24dd3f..601be46e3 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -209,7 +209,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) else { // set up the levelstring - if (bossinfo.boss == true && bossinfo.enemyname) + if (bossinfo.valid == true && bossinfo.enemyname) { snprintf(data.levelstring, sizeof data.levelstring, @@ -574,7 +574,7 @@ skiptallydrawer: if (!LUA_HudEnabled(hud_intermissionmessages)) return; - if (timer && grandprixinfo.gp == false && bossinfo.boss == false && !modeattacking) + if (timer && grandprixinfo.gp == false && !modeattacking) { char *string; INT32 tickdown = (timer+1)/TICRATE; From 8bb29e340c38126fa81a9086f58be1dbd732da9d Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 26 Dec 2022 23:07:32 +0000 Subject: [PATCH 011/128] Fix a crash in K_drawBossHealthBar exposed by the new random functions --- src/k_hud.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 0d92d2035..ebf6f9ceb 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1973,7 +1973,7 @@ static void K_drawBossHealthBar(void) UINT8 i = 0, barstatus = 1, randlen = 0, darken = 0; const INT32 startx = BASEVIDWIDTH - 23; INT32 starty = BASEVIDHEIGHT - 25; - INT32 rolrand = 0; + INT32 rolrand = 0, randtemp = 0; boolean randsign = false; if (bossinfo.barlen <= 1) @@ -2019,7 +2019,9 @@ static void K_drawBossHealthBar(void) barstatus = 2; } - randlen = M_RandomKey(bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)))+1; + randtemp = bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)); + if (randtemp > 0) + randlen = M_RandomKey(randtemp)+1; randsign = M_RandomChance(FRACUNIT/2); // Right wing. @@ -2034,7 +2036,9 @@ static void K_drawBossHealthBar(void) randlen--; if (!randlen) { - randlen = M_RandomKey(bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)))+1; + randtemp = bossinfo.visualbar-(bossinfo.visualdiv/(2*FRACUNIT)); + if (randtemp > 0) + randlen = M_RandomKey(randtemp)+1; if (barstatus > 1) { rolrand = M_RandomKey(barstatus)+1; From a3d0c197ecf473f868760c56b20cf7583250751b Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 26 Dec 2022 23:08:06 +0000 Subject: [PATCH 012/128] Fix GAMETYPERULE_LIST --- src/deh_tables.c | 49 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 8e7bee9c1..40009f21a 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5791,38 +5791,31 @@ const char *const PLAYERFLAG_LIST[] = { }; const char *const GAMETYPERULE_LIST[] = { - "CAMPAIGN", - "RINGSLINGER", - "SPECTATORS", - "LIVES", - "TEAMS", - "FIRSTPERSON", + "CIRCUIT", + "BOTS", + + "BUMPERS", + "SPHERES", + "PAPERITEMS", "POWERSTONES", - "TEAMFLAGS", - "FRIENDLY", - "SPECIALSTAGES", - "EMERALDTOKENS", - "EMERALDHUNT", - "RACE", - "TAG", + "KARMA", + "ITEMARROWS", + "CAPSULES", + "BATTLESTARTS", + "POINTLIMIT", "TIMELIMIT", "OVERTIME", - "HURTMESSAGES", - "FRIENDLYFIRE", - "STARTCOUNTDOWN", - "HIDEFROZEN", - "BLINDFOLDED", - "RESPAWNDELAY", - "PITYSHIELD", - "DEATHPENALTY", - "NOSPECTATORSPAWN", - "DEATHMATCHSTARTS", - "SPAWNINVUL", - "SPAWNENEMIES", - "ALLOWEXIT", - "NOTITLECARD", - "CUTSCENES", + + "TEAMS", + "NOTEAMS", + "TEAMSTARTS", + + "CATCHER", + "BOSS", + "NOCUPSELECT", + "CLOSERPLAYERS", + "ENCORE", NULL }; From 38a515f8e5ce21865537b5333785cc6247953162 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 26 Dec 2022 23:11:23 +0000 Subject: [PATCH 013/128] Also make Grand Prix bots spectators if the gametype doesn't support them (or if the initialisation happens into a GPEVENT) --- src/d_clisrv.c | 3 +++ src/k_grandprix.c | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index ac602a28f..0be6c8d96 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -3748,6 +3748,9 @@ static void Got_AddBot(UINT8 **p, INT32 playernum) sprintf(player_names[newplayernum], "%s", skins[skinnum].realname); SetPlayerSkinByNum(newplayernum, skinnum); + players[newplayernum].spectator = !(gametyperules & GTR_BOTS) + || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE); + if (netgame) { HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false); diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 4ca90fe04..d26787100 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -338,7 +338,7 @@ void K_UpdateGrandPrixBots(void) continue; } - players[i].spectator = (grandprixinfo.eventmode != GPEVENT_NONE); + players[i].spectator = !(gametyperules & GTR_BOTS) || (grandprixinfo.eventmode != GPEVENT_NONE); } // Find the rival. From 470f82104d5f74f00ff22ef0cc9c17842b7f4089 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 27 Dec 2022 14:33:58 +0000 Subject: [PATCH 014/128] Rework intertype_t - Now you select based on whether you want to rank by - Time always (Race, Special) - Score always (might be useful for custom gametypes..?) - Time in 1P, Score otherwise (Battle, Versus) - No longer has gametype-specific text colours on the intermission - Also cleans up a case where invalid music could play for winning a custom gametype without GTR_CIRCUIT *or* GTR_BUMPERS --- src/deh_tables.c | 6 ++--- src/g_game.c | 9 ++++--- src/p_user.c | 9 ++++--- src/y_inter.c | 64 ++++++++++++++++++++---------------------------- src/y_inter.h | 6 ++--- 5 files changed, 44 insertions(+), 50 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 40009f21a..817551bcf 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -6396,9 +6396,9 @@ struct int_const_s const INT_CONST[] = { // Intermission types {"int_none",int_none}, - {"int_race",int_race}, - {"int_battle",int_battle}, - {"int_battletime", int_battletime}, + {"int_time",int_time}, + {"int_score",int_score}, + {"int_scoreortimeattack", int_scoreortimeattack}, // Jingles (jingletype_t) {"JT_NONE",JT_NONE}, diff --git a/src/g_game.c b/src/g_game.c index f44bd1c2f..92c2ab068 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2995,7 +2995,7 @@ static gametype_t defaultgametypes[] = "GT_RACE", GTR_CIRCUIT|GTR_BOTS|GTR_ENCORE, TOL_RACE, - int_race, + int_time, 0, 0, }, @@ -3006,17 +3006,18 @@ static gametype_t defaultgametypes[] = "GT_BATTLE", GTR_SPHERES|GTR_BUMPERS|GTR_PAPERITEMS|GTR_POWERSTONES|GTR_KARMA|GTR_ITEMARROWS|GTR_CAPSULES|GTR_BATTLESTARTS|GTR_POINTLIMIT|GTR_TIMELIMIT|GTR_OVERTIME|GTR_CLOSERPLAYERS, TOL_BATTLE, - int_battle, + int_scoreortimeattack, 0, 2, }, + // GT_SPECIAL { "Special", "GT_SPECIAL", GTR_CATCHER|GTR_CIRCUIT, TOL_SPECIAL, - int_race, + int_time, 0, 0, }, @@ -3027,7 +3028,7 @@ static gametype_t defaultgametypes[] = "GT_VERSUS", GTR_BOSS|GTR_SPHERES|GTR_BUMPERS|GTR_POINTLIMIT|GTR_CLOSERPLAYERS|GTR_NOCUPSELECT|GTR_ENCORE, TOL_BOSS, - int_battle, + int_scoreortimeattack, 0, 0, }, diff --git a/src/p_user.c b/src/p_user.c index 25698bca8..b333eabe3 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -720,6 +720,7 @@ boolean P_EndingMusic(player_t *player) { char buffer[9]; boolean looping = true; + boolean racetracks = !!(gametyperules & GTR_CIRCUIT); INT32 bestlocalpos, test; player_t *bestlocalplayer; @@ -773,7 +774,7 @@ boolean P_EndingMusic(player_t *player) #undef getplayerpos - if ((gametyperules & GTR_CIRCUIT) && bestlocalpos == MAXPLAYERS+1) + if (racetracks == true && bestlocalpos == MAXPLAYERS+1) sprintf(buffer, "k*fail"); // F-Zero death results theme else { @@ -787,9 +788,11 @@ boolean P_EndingMusic(player_t *player) S_SpeedMusic(1.0f); - if ((gametyperules & GTR_CIRCUIT)) + if (racetracks == true) + { buffer[1] = 'r'; - else if ((gametyperules & GTR_BUMPERS)) + } + else { buffer[1] = 'b'; looping = false; diff --git a/src/y_inter.c b/src/y_inter.c index 601be46e3..1d4393dc3 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -334,7 +334,7 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) // void Y_IntermissionDrawer(void) { - INT32 i, whiteplayer = MAXPLAYERS, x = 4, hilicol = V_YELLOWMAP; // fallback + INT32 i, whiteplayer = MAXPLAYERS, x = 4, hilicol = highlightflags; if (intertype == int_none || rendermode == render_none) return; @@ -357,11 +357,6 @@ void Y_IntermissionDrawer(void) if (!r_splitscreen) whiteplayer = demo.playback ? displayplayers[0] : consoleplayer; - if (modeattacking) - hilicol = V_ORANGEMAP; - else - hilicol = ((intertype == int_race) ? V_SKYMAP : V_REDMAP); - if (sorttic != -1 && intertic > sorttic) { INT32 count = (intertic - sorttic); @@ -374,7 +369,7 @@ void Y_IntermissionDrawer(void) x += (((16 - count) * vid.width) / (8 * vid.dupx)); } - if (intertype == int_race || intertype == int_battle || intertype == int_battletime) + if (intertype == int_time || intertype == int_score) { #define NUMFORNEWCOLUMN 8 INT32 y = 41, gutter = ((data.numplayers > NUMFORNEWCOLUMN) ? 0 : (BASEVIDWIDTH/2)); @@ -397,7 +392,7 @@ void Y_IntermissionDrawer(void) { switch (intertype) { - case int_battle: + case int_score: timeheader = "SCORE"; break; default: @@ -532,7 +527,7 @@ void Y_IntermissionDrawer(void) V_DrawRightAlignedThinString(x+152+gutter, y-1, (data.numplayers > NUMFORNEWCOLUMN ? V_6WIDTHSPACE : 0), "NO CONTEST."); else { - if (intertype == int_race || intertype == int_battletime) + if (intertype == int_time) { snprintf(strtime, sizeof strtime, "%i'%02i\"%02i", G_TicsToMinutes(data.val[i], true), G_TicsToSeconds(data.val[i]), G_TicsToCentiseconds(data.val[i])); @@ -668,7 +663,7 @@ void Y_Ticker(void) if (intertic < TICRATE || intertic & 1 || endtic != -1) return; - if (intertype == int_race || intertype == int_battle || intertype == int_battletime) + if (intertype == int_time || intertype == int_score) { { if (!data.rankingsmode && sorttic != -1 && (intertic >= sorttic + 8)) @@ -750,27 +745,27 @@ void Y_Ticker(void) // void Y_DetermineIntermissionType(void) { + // no intermission for GP events + if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) + { + intertype = int_none; + return; + } + // set initially intertype = gametypes[gametype]->intermission; - // TODO: special cases - if (intertype == int_battle) + // special cases + if (intertype == int_scoreortimeattack) { - if (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE) + UINT8 i = 0, nump = 0; + for (i = 0; i < MAXPLAYERS; i++) { - intertype = int_none; - } - else - { - UINT8 i = 0, nump = 0; - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i] || players[i].spectator) - continue; - nump++; - } - intertype = (nump < 2 ? int_battletime : int_battle); + if (!playeringame[i] || players[i].spectator) + continue; + nump++; } + intertype = (nump < 2 ? int_time : int_score); } } @@ -836,23 +831,18 @@ void Y_StartIntermission(void) if (prevmap >= nummapheaders || !mapheaderinfo[prevmap]) I_Error("Y_StartIntermission: Internal map ID %d not found (nummapheaders = %d)", prevmap, nummapheaders); + if (!(gametyperules & GTR_CIRCUIT) && (timer > 1)) + S_ChangeMusicInternal("racent", true); // loop it + switch (intertype) { - case int_battle: - case int_battletime: + case int_score: { - if (timer > 1) - S_ChangeMusicInternal("racent", true); // loop it - // Calculate who won - if (intertype == int_battle) - { - Y_CalculateMatchData(0, Y_CompareScore); - break; - } + Y_CalculateMatchData(0, Y_CompareScore); + break; } - // FALLTHRU - case int_race: + case int_time: { // Calculate who won Y_CalculateMatchData(0, Y_CompareTime); diff --git a/src/y_inter.h b/src/y_inter.h index 5bcd0be17..991c3f5d4 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -26,9 +26,9 @@ void Y_SetupVoteFinish(SINT8 pick, SINT8 level); typedef enum { int_none, - int_race, // Race - int_battle, // Battle (score-based) - int_battletime, // Battle (time-based) + int_time, // Always time + int_score, // Always score + int_scoreortimeattack, // Score unless 1P } intertype_t; extern intertype_t intertype; From 1ac0c44f77730dd734e82f33e903196fdc667652 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 27 Dec 2022 14:34:54 +0000 Subject: [PATCH 015/128] Permit using the gametype option for Command_Map_f when not playing multiplayer --- src/d_netcmd.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 54743e331..0d4a8da9d 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2739,13 +2739,16 @@ static void Command_Map_f(void) if (option_gametype) { +#if 0 if (!multiplayer) { CONS_Printf(M_GetText( "You can't switch gametypes in single player!\n")); return; } - else if (COM_Argc() < option_gametype + 2)/* no argument after? */ + else +#endif //#if 0 + if (COM_Argc() < option_gametype + 2)/* no argument after? */ { CONS_Alert(CONS_ERROR, "No gametype name follows parameter '%s'.\n", From f8999bea363480e0c4495dde3f96555ef252d444 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 27 Dec 2022 16:06:10 +0000 Subject: [PATCH 016/128] Changes to tab rankings * No longer has gametype-specific highlight * Shows Grand Prix and Capsules instead of gametype name if relevant * Operates on a heirarchy for important information * Shows grand prix round on left if in GP (resolves #360) * Shows capsules remaining on right if in BTC * Attempts to draw timelimit and pointlimit on both left and right * Number of laps remaining/gamespeed is least priority for left and right sides --- src/hu_stuff.c | 113 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 29 deletions(-) diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 95087a389..2a75663e2 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -55,6 +55,8 @@ // SRB2Kart #include "s_sound.h" // song credits #include "k_kart.h" +#include "k_battle.h" +#include "k_grandprix.h" #include "k_color.h" #include "k_hud.h" #include "r_fps.h" @@ -2391,51 +2393,67 @@ static inline void HU_DrawSpectatorTicker(void) static void HU_DrawRankings(void) { playersort_t tab[MAXPLAYERS]; - INT32 i, j, scorelines, hilicol, numplayersingame = 0; + INT32 i, j, scorelines, numplayersingame = 0, hilicol = highlightflags; boolean completed[MAXPLAYERS]; UINT32 whiteplayer = MAXPLAYERS; + boolean timedone = false, pointsdone = false; V_DrawFadeScreen(0xFF00, 16); // A little more readable, and prevents cheating the fades under other circumstances. - if (modeattacking) - hilicol = V_ORANGEMAP; - else - hilicol = ((gametype == GT_RACE) ? V_SKYMAP : V_REDMAP); - // draw the current gametype in the lower right - if (modeattacking) - V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Record Attack"); + if (grandprixinfo.gp == true) + V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Grand Prix"); + else if (battlecapsules) + V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, "Capsules"); else if (gametype >= 0 && gametype < numgametypes) V_DrawString(4, 188, hilicol|V_SNAPTOBOTTOM|V_SNAPTOLEFT, gametypes[gametype]->name); - if ((gametyperules & (GTR_TIMELIMIT|GTR_POINTLIMIT))) + // Left hand side + if (grandprixinfo.gp == true) { - if ((gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) + const char *roundstr = NULL; + V_DrawCenteredString(64, 8, 0, "ROUND"); + switch (grandprixinfo.eventmode) { - UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); - if (timeval > timelimitintics+1) - timeval = timelimitintics+1; - timeval /= TICRATE; + case GPEVENT_BONUS: + roundstr = "BONUS"; + break; + case GPEVENT_SPECIAL: + roundstr = "SPECIAL"; + break; + default: + roundstr = va("%d", grandprixinfo.roundnum); + break; + } + V_DrawCenteredString(64, 16, hilicol, roundstr); + } + else if ((gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) + { + UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); + if (timeval > timelimitintics+1) + timeval = timelimitintics+1; + timeval /= TICRATE; - if (leveltime <= (timelimitintics + starttime)) - { - V_DrawCenteredString(64, 8, 0, "TIME LEFT"); - V_DrawCenteredString(64, 16, hilicol, va("%u", timeval)); - } - - // overtime - if (!players[consoleplayer].exiting && (leveltime > (timelimitintics + starttime + TICRATE/2)) && cv_overtime.value) - { - V_DrawCenteredString(64, 8, 0, "TIME LEFT"); - V_DrawCenteredString(64, 16, hilicol, "OVERTIME"); - } + if (leveltime <= (timelimitintics + starttime)) + { + V_DrawCenteredString(64, 8, 0, "TIME LEFT"); + V_DrawCenteredString(64, 16, hilicol, va("%u", timeval)); } - if ((gametyperules & GTR_POINTLIMIT) && cv_pointlimit.value > 0) + // overtime + if (!players[consoleplayer].exiting && (leveltime > (timelimitintics + starttime + TICRATE/2)) && cv_overtime.value) { - V_DrawCenteredString(256, 8, 0, "POINT LIMIT"); - V_DrawCenteredString(256, 16, hilicol, va("%d", cv_pointlimit.value)); + V_DrawCenteredString(64, 8, 0, "TIME LEFT"); + V_DrawCenteredString(64, 16, hilicol, "OVERTIME"); } + + timedone = true; + } + else if ((gametyperules & GTR_POINTLIMIT) && cv_pointlimit.value > 0) + { + V_DrawCenteredString(64, 8, 0, "POINT LIMIT"); + V_DrawCenteredString(64, 16, hilicol, va("%d", cv_pointlimit.value)); + pointsdone = true; } else if (gametyperules & GTR_CIRCUIT) { @@ -2444,7 +2462,44 @@ static void HU_DrawRankings(void) V_DrawCenteredString(64, 8, 0, "LAP COUNT"); V_DrawCenteredString(64, 16, hilicol, va("%d", numlaps)); } + } + // Right hand side + if (battlecapsules == true) + { + if (numtargets < maptargets) + { + V_DrawCenteredString(256, 8, 0, "CAPSULES"); + V_DrawCenteredString(256, 16, hilicol, va("%d", maptargets - numtargets)); + } + } + else if (!timedone && (gametyperules & GTR_TIMELIMIT) && timelimitintics > 0) + { + UINT32 timeval = (timelimitintics + starttime + 1 - leveltime); + if (timeval > timelimitintics+1) + timeval = timelimitintics+1; + timeval /= TICRATE; + + if (leveltime <= (timelimitintics + starttime)) + { + V_DrawCenteredString(256, 8, 0, "TIME LEFT"); + V_DrawCenteredString(256, 16, hilicol, va("%u", timeval)); + } + + // overtime + if (!players[consoleplayer].exiting && (leveltime > (timelimitintics + starttime + TICRATE/2)) && cv_overtime.value) + { + V_DrawCenteredString(256, 8, 0, "TIME LEFT"); + V_DrawCenteredString(256, 16, hilicol, "OVERTIME"); + } + } + else if (!pointsdone && (gametyperules & GTR_POINTLIMIT) && cv_pointlimit.value > 0) + { + V_DrawCenteredString(256, 8, 0, "POINT LIMIT"); + V_DrawCenteredString(256, 16, hilicol, va("%d", cv_pointlimit.value)); + } + else if (gametyperules & GTR_CIRCUIT) + { V_DrawCenteredString(256, 8, 0, "GAME SPEED"); V_DrawCenteredString(256, 16, hilicol, kartspeed_cons_t[1+gamespeed].strvalue); } From 03c8fd543f3e82705b941c6b0d14a9058e69a8ca Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 27 Dec 2022 17:58:35 +0000 Subject: [PATCH 017/128] More fun rules for Special Stages - GTR_ROLLINGSTART * Initial instathrust, as before * Forced MAXPLMOVE forward * Disable finish line beam * "Super transformation" sound - Reference to the previous entry in the series' Perfect Startboost mechanic - GTR_SPECIALSTART * Instant white fade * No titlecard (overridden by Boss intro) * Starting warp sound - Match slidein time with no traditional titlecard to the end of the playsim intro fade - Remove G_IsSpecialStage --- src/deh_tables.c | 2 ++ src/doomstat.h | 3 ++- src/g_game.c | 36 ++++++++++++------------------------ src/g_game.h | 1 - src/k_kart.c | 33 ++++++++++++++++++++++++++++++++- src/k_race.c | 2 +- src/k_specialstage.c | 26 -------------------------- src/lua_baselib.c | 10 ---------- src/p_setup.c | 22 ++++++++++++++-------- src/p_spec.c | 4 ++-- src/st_stuff.c | 2 +- src/v_video.c | 8 ++++---- 12 files changed, 70 insertions(+), 79 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 817551bcf..8b9be00ff 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5813,9 +5813,11 @@ const char *const GAMETYPERULE_LIST[] = { "CATCHER", "BOSS", + "ROLLINGSTART", "NOCUPSELECT", "CLOSERPLAYERS", "ENCORE", + "SPECIALSTART", NULL }; diff --git a/src/doomstat.h b/src/doomstat.h index 1e2c23ee0..9222e7381 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -513,10 +513,11 @@ enum GameTypeRules // To be rearranged later GTR_CATCHER = 1<<17, // UFO Catcher (only works with GTR_CIRCUIT) GTR_BOSS = 1<<18, // Boss intro and spawning - + GTR_ROLLINGSTART = 1<<19, // Rolling start (only works with GTR_CIRCUIT) GTR_NOCUPSELECT = 1<<20, // Your maps are not selected via cup. GTR_CLOSERPLAYERS = 1<<21, // Buffs spindash and draft power to bring everyone together, nerfs invincibility and grow to prevent excessive combos GTR_ENCORE = 1<<22, // Alternate Encore mirroring, scripting, and texture remapping + GTR_SPECIALSTART = 1<<23, // White fade instant start // free: to and including 1<<31 }; diff --git a/src/g_game.c b/src/g_game.c index 92c2ab068..2c7f85efb 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1412,7 +1412,7 @@ void G_StartTitleCard(void) { // The title card has been disabled for this map. // Oh well. - if (!G_IsTitleCardAvailable() || demo.rewinding) + if (demo.rewinding || !G_IsTitleCardAvailable()) { WipeStageTitle = false; return; @@ -1476,11 +1476,17 @@ void G_PreLevelTitleCard(void) // boolean G_IsTitleCardAvailable(void) { -#if 0 + // Overwrites all other title card exceptions. + if (K_CheckBossIntro() == true) + return true; + // The current level has no name. if (!mapheaderinfo[gamemap-1]->lvlttl[0]) return false; -#endif + + // Instant white fade. + if (gametyperules & GTR_SPECIALSTART) + return false; // The title card is available. return true; @@ -3015,7 +3021,7 @@ static gametype_t defaultgametypes[] = { "Special", "GT_SPECIAL", - GTR_CATCHER|GTR_CIRCUIT, + GTR_CATCHER|GTR_SPECIALSTART|GTR_ROLLINGSTART|GTR_CIRCUIT, TOL_SPECIAL, int_time, 0, @@ -3213,25 +3219,6 @@ void G_AddTOL(UINT32 newtol, const char *tolname) TYPEOFLEVEL[i].flag = newtol; } -// -// G_IsSpecialStage -// -// Returns TRUE if -// the given map is a special stage. -// -boolean G_IsSpecialStage(INT32 mapnum) -{ - mapnum--; // gamemap-based to 0 indexed - - if (mapnum > nummapheaders || !mapheaderinfo[mapnum]) - return false; - - if (!mapheaderinfo[mapnum]->cup || mapheaderinfo[mapnum]->cup->cachedlevels[CUPCACHE_SPECIAL] != mapnum) - return false; - - return true; -} - // // G_GametypeUsesLives // @@ -3666,7 +3653,6 @@ static void G_HandleSaveLevel(void) static void G_GetNextMap(void) { - boolean spec = G_IsSpecialStage(prevmap+1); INT32 i; // go to next level @@ -3917,7 +3903,9 @@ static void G_GetNextMap(void) if (nextmap == NEXTMAP_INVALID || (nextmap < NEXTMAP_SPECIAL && (nextmap >= nummapheaders || !mapheaderinfo[nextmap] || mapheaderinfo[nextmap]->lumpnum == LUMPERROR))) I_Error("G_GetNextMap: Internal map ID %d not found (nummapheaders = %d)\n", nextmap, nummapheaders); +#if 0 // This is a surprise tool that will help us later. if (!spec) +#endif //#if 0 lastmap = nextmap; } diff --git a/src/g_game.h b/src/g_game.h index 407e12d64..5675da0dd 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -186,7 +186,6 @@ void G_AddTOL(UINT32 newtol, const char *tolname); INT32 G_GetGametypeByName(const char *gametypestr); INT32 G_GuessGametypeByTOL(UINT32 tol); -boolean G_IsSpecialStage(INT32 mapnum); boolean G_GametypeUsesLives(void); boolean G_GametypeHasTeams(void); boolean G_GametypeHasSpectators(void); diff --git a/src/k_kart.c b/src/k_kart.c index 296b8cb69..652ec5fb6 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -109,6 +109,36 @@ void K_TimerInit(void) boolean domodeattack = ((modeattacking != ATTACKING_NONE) || (grandprixinfo.gp == true && grandprixinfo.eventmode != GPEVENT_NONE)); + // Rooooooolllling staaaaaaart + if ((gametyperules & (GTR_ROLLINGSTART|GTR_CIRCUIT)) == (GTR_ROLLINGSTART|GTR_CIRCUIT)) + { + S_StartSound(NULL, sfx_s25f); + + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *player = NULL; + + if (playeringame[i] == false) + { + continue; + } + + player = &players[i]; + if (player->spectator == true) + { + continue; + } + + if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) + { + continue; + } + + // Rolling start? lol + P_InstaThrust(player->mo, player->mo->angle, K_GetKartSpeed(player, false, false)); + } + } + if ((gametyperules & (GTR_CATCHER|GTR_CIRCUIT)) == (GTR_CATCHER|GTR_CIRCUIT)) { K_InitSpecialStage(); @@ -3298,7 +3328,8 @@ SINT8 K_GetForwardMove(player_t *player) return 0; } - if (player->sneakertimer || player->spindashboost) + if (player->sneakertimer || player->spindashboost + || (((gametyperules & (GTR_ROLLINGSTART|GTR_CIRCUIT)) == (GTR_ROLLINGSTART|GTR_CIRCUIT)) && (leveltime < TICRATE/2))) { return MAXPLMOVE; } diff --git a/src/k_race.c b/src/k_race.c index 2e7f477e9..0bbe0bfe0 100644 --- a/src/k_race.c +++ b/src/k_race.c @@ -400,7 +400,7 @@ static void K_DrawFinishLineBeamForLine(fixed_t offset, angle_t aiming, line_t * void K_RunFinishLineBeam(void) { - if (!(leveltime < starttime || rainbowstartavailable == true)) + if ((gametyperules & GTR_ROLLINGSTART) || !(leveltime < starttime || rainbowstartavailable == true)) { return; } diff --git a/src/k_specialstage.c b/src/k_specialstage.c index 88ce729bb..76f986bcb 100644 --- a/src/k_specialstage.c +++ b/src/k_specialstage.c @@ -42,8 +42,6 @@ void K_ResetSpecialStage(void) --------------------------------------------------*/ void K_InitSpecialStage(void) { - INT32 i; - if ((gametyperules & (GTR_CATCHER|GTR_CIRCUIT)) != (GTR_CATCHER|GTR_CIRCUIT)) { return; @@ -53,30 +51,6 @@ void K_InitSpecialStage(void) specialstageinfo.beamDist = UINT32_MAX; // TODO: make proper value P_SetTarget(&specialstageinfo.ufo, Obj_CreateSpecialUFO()); - - for (i = 0; i < MAXPLAYERS; i++) - { - player_t *player = NULL; - - if (playeringame[i] == false) - { - continue; - } - - player = &players[i]; - if (player->spectator == true) - { - continue; - } - - if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) - { - continue; - } - - // Rolling start? lol - P_InstaThrust(player->mo, player->mo->angle, K_GetKartSpeed(player, false, false)); - } } /*-------------------------------------------------- diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 16347dfe2..61587bd62 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -3282,15 +3282,6 @@ static int lib_gExitLevel(lua_State *L) return 0; } -static int lib_gIsSpecialStage(lua_State *L) -{ - INT32 mapnum = luaL_optinteger(L, 1, gamemap); - //HUDSAFE - INLEVEL - lua_pushboolean(L, G_IsSpecialStage(mapnum)); - return 1; -} - static int lib_gGametypeUsesLives(lua_State *L) { //HUDSAFE @@ -4099,7 +4090,6 @@ static luaL_Reg lib[] = { {"G_DoReborn",lib_gDoReborn}, {"G_SetCustomExitVars",lib_gSetCustomExitVars}, {"G_ExitLevel",lib_gExitLevel}, - {"G_IsSpecialStage",lib_gIsSpecialStage}, {"G_GametypeUsesLives",lib_gGametypeUsesLives}, {"G_GametypeHasTeams",lib_gGametypeHasTeams}, {"G_GametypeHasSpectators",lib_gGametypeHasSpectators}, diff --git a/src/p_setup.c b/src/p_setup.c index f35f32382..e09141271 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7326,13 +7326,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } G_ClearModeAttackRetryFlag(); } - /* - else if (rendermode != render_none && G_IsSpecialStage(gamemap)) - { - P_RunSpecialStageWipe(); - ranspecialwipe = 1; - } - */ // Make sure all sounds are stopped before Z_FreeTags. S_StopSounds(); @@ -7367,7 +7360,20 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) S_Start(); } - levelfadecol = (encoremode ? 0 : 31); + if (gametyperules & GTR_SPECIALSTART) + { + if (ranspecialwipe != 2) + S_StartSound(NULL, sfx_s3kaf); + levelfadecol = 0; + } + else if (encoremode) + { + levelfadecol = 0; + } + else + { + levelfadecol = 31; + } if (rendermode != render_none) { diff --git a/src/p_spec.c b/src/p_spec.c index 4988ad881..b85746db6 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1843,7 +1843,7 @@ static void K_HandleLapIncrement(player_t *player) { if (player) { - if (leveltime < starttime) + if (leveltime < starttime && !(gametyperules & GTR_ROLLINGSTART)) { // Will fault the player K_DoIngameRespawn(player); @@ -1902,7 +1902,7 @@ static void K_HandleLapIncrement(player_t *player) if (P_IsDisplayPlayer(player)) { - if (player->laps == numlaps) // final lap + if (numlaps > 1 && player->laps == numlaps) // final lap S_StartSound(NULL, sfx_s3k68); else if ((player->laps > 1) && (player->laps < numlaps)) // non-final lap S_StartSound(NULL, sfx_s221); diff --git a/src/st_stuff.c b/src/st_stuff.c index 7583b4c37..76ad09b9b 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -802,7 +802,7 @@ void ST_drawTitleCard(void) #define HITIME 15 patch_t *localwarn = (encoremode ? twarn2 : twarn); INT32 transp = (lt_ticker+HITIME) % (LOTIME+HITIME); - boolean encorehack = (encoremode && lt_ticker <= PRELEVELTIME+4); + boolean encorehack = ((levelfadecol == 0) && lt_ticker <= PRELEVELTIME+4); if ((localwarn->width > 0) && (lt_ticker + (HITIME-transp) <= lt_endtime)) { diff --git a/src/v_video.c b/src/v_video.c index 9fcda2425..e633b4e3f 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -585,16 +585,16 @@ void V_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 du } } - if (options & V_SLIDEIN) + if ((options & V_SLIDEIN)) { const tic_t length = TICRATE/4; tic_t timer = lt_exitticker; - if (K_CheckBossIntro() == true) + if (K_CheckBossIntro() == true || G_IsTitleCardAvailable() == false) { - if (leveltime <= 3) + if (leveltime <= 16) timer = 0; else - timer = leveltime-3; + timer = leveltime-16; } if (timer < length) From 15587417c7965b5c4f08e8cefd3a2cdd002043e3 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 27 Dec 2022 22:24:18 +0000 Subject: [PATCH 018/128] Coherency changes to special stage behaviour in anticipation of timeattack support * Make everyone PF_NOCONTEST (but not explode) if the UFO/emerald reaches the end of its waypoint path. - Possibly temporary: Make the UFO/emerald go straight up at its final waypoint * If you have PF_NOCONTEST, K_IsPlayerLosing is true * If special stage in action and the only reason you'd be behind is your position, nobody loses * Never eliminate last in special stage * Time Over funny camera no longer occurs when PF_NOCONTEST but not dead --- src/k_kart.c | 8 ++++++-- src/objects/ufo.c | 29 ++++++++++++++++++++++++----- src/p_inter.c | 3 ++- src/p_user.c | 14 ++++++++++++-- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 652ec5fb6..ea05bbde1 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -372,6 +372,9 @@ boolean K_IsPlayerLosing(player_t *player) INT32 winningpos = 1; UINT8 i, pcount = 0; + if (player->pflags & PF_NOCONTEST) + return true; + if (battlecapsules && numtargets == 0) return true; // Didn't even TRY? @@ -381,6 +384,9 @@ boolean K_IsPlayerLosing(player_t *player) if (player->position == 1) return false; + if (specialstageinfo.valid == true) + return false; // anything short of DNF is COOL + for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) @@ -7567,8 +7573,6 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) //CONS_Printf("cam: %d, dest: %d\n", player->karthud[khud_boostcam], player->karthud[khud_destboostcam]); } - player->karthud[khud_timeovercam] = 0; - // Make ABSOLUTELY SURE that your flashing tics don't get set WHILE you're still in hit animations. if (player->spinouttimer != 0 || player->wipeoutslow != 0) { diff --git a/src/objects/ufo.c b/src/objects/ufo.c index d2fffc008..6fe0af881 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -281,10 +281,11 @@ static void UFOMove(mobj_t *ufo) if (curWaypoint == NULL || destWaypoint == NULL) { // Waypoints aren't valid. - // Just stand still. + // Just go straight up. + // :japanese_ogre: : "Abrupt and funny is the funniest way to end the special stage anyways" ufo->momx = 0; ufo->momy = 0; - ufo->momz = 0; + ufo->momz = ufo_speed(ufo); return; } @@ -365,8 +366,23 @@ static void UFOMove(mobj_t *ufo) if (reachedEnd == true) { - CONS_Printf("You lost...\n"); - ufo_waypoint(ufo) = -1; // Invalidate + UINT8 i; + + // Invalidate UFO/emerald + ufo_waypoint(ufo) = -1; + ufo->flags &= ~(MF_SPECIAL|MF_PICKUPFROMBELOW); + + // Disable player + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + if (players[i].spectator) + continue; + + players[i].pflags |= PF_NOCONTEST; + P_DoPlayerExit(&players[i]); + } } if (pathfindsuccess == true) @@ -655,7 +671,10 @@ void Obj_UFOPieceThink(mobj_t *piece) fixed_t sc = FixedDiv(FixedDiv(ufo->ceilingz - stemZ, piece->scale), 15 * FRACUNIT); UFOMoveTo(piece, ufo->x, ufo->y, stemZ); - piece->spriteyscale = sc; + if (sc > 0) + { + piece->spriteyscale = sc; + } break; } default: diff --git a/src/p_inter.c b/src/p_inter.c index 77020eb08..89229f8ac 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -32,6 +32,7 @@ // SRB2kart #include "k_kart.h" #include "k_battle.h" +#include "k_specialstage.h" #include "k_pwrlv.h" #include "k_grandprix.h" #include "k_respawn.h" @@ -892,7 +893,7 @@ boolean P_CheckRacers(void) } } - if (numPlaying <= 1) + if (numPlaying <= 1 || specialstageinfo.valid == true) { // Never do this without enough players. eliminateLast = false; diff --git a/src/p_user.c b/src/p_user.c index b333eabe3..a27890817 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -54,6 +54,7 @@ #include "k_bot.h" #include "k_grandprix.h" #include "k_boss.h" +#include "k_specialstage.h" #include "k_terrain.h" // K_SpawnSplashForMobj #include "k_color.h" #include "k_follower.h" @@ -1307,7 +1308,16 @@ void P_DoPlayerExit(player_t *player) P_EndingMusic(player); if (P_CheckRacers() && !exitcountdown) - exitcountdown = raceexittime+1; + { + if (specialstageinfo.valid == true && losing == true) + { + exitcountdown = (5*TICRATE)/2; + } + else + { + exitcountdown = raceexittime+1; + } + } } else if ((gametyperules & GTR_BUMPERS)) // Battle Mode exiting { @@ -3048,7 +3058,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall return true; } - if ((player->pflags & PF_NOCONTEST) && (gametyperules & GTR_CIRCUIT)) // 1 for momentum keep, 2 for turnaround + if ((player->pflags & PF_NOCONTEST) && (gametyperules & GTR_CIRCUIT) && player->karthud[khud_timeovercam] != 0) // 1 for momentum keep, 2 for turnaround timeover = (player->karthud[khud_timeovercam] > 2*TICRATE ? 2 : 1); else timeover = 0; From 04f2ac41212e4467db220bae368fdd8de0c4e4f6 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 27 Dec 2022 22:34:02 +0000 Subject: [PATCH 019/128] "Special" time attack mode for 1P. * Both GT_SPECIAL and GT_VERSUS. * Access controlled by SECRET_SPECIALATTACK. (You're blue now.) Related changes to precipitate: * Cups that only have one map in them get selected immediately, rather than off-the-cuff. * Done by seperating out a new function M_LevelSelected from M_LevelSelectHandler * Maps that only have one lap in them don't have a visible lap timestamp sticker. * Fix a cup with *no* valid maps for the current ruleset being hypothetically selectable --- src/deh_soc.c | 2 + src/k_menu.h | 1 + src/k_menudef.c | 3 + src/k_menudraw.c | 10 +- src/k_menufunc.c | 247 ++++++++++++++++++++++++++++------------------- src/m_cond.h | 3 +- 6 files changed, 162 insertions(+), 104 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 65a347169..fdba5c507 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2260,6 +2260,8 @@ void readunlockable(MYFILE *f, INT32 num) unlockables[num].type = SECRET_TIMEATTACK; else if (fastcmp(word2, "BREAKTHECAPSULES")) unlockables[num].type = SECRET_BREAKTHECAPSULES; + else if (fastcmp(word2, "SPECIALATTACK")) + unlockables[num].type = SECRET_SPECIALATTACK; else if (fastcmp(word2, "SOUNDTEST")) unlockables[num].type = SECRET_SOUNDTEST; else if (fastcmp(word2, "ALTTITLE")) diff --git a/src/k_menu.h b/src/k_menu.h index eb57069b7..832e2146e 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -709,6 +709,7 @@ extern struct levellist_s { UINT16 dest; INT16 choosemap; UINT8 newgametype; + UINT8 guessgt; levelsearch_t levelsearch; boolean netgame; // Start the game in an actual server } levellist; diff --git a/src/k_menudef.c b/src/k_menudef.c index f100db28d..3f10aa313 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -95,6 +95,9 @@ menuitem_t PLAY_GamemodesMenu[] = {IT_STRING | IT_CALL, "Capsules", "Bust up all of the capsules in record time!", NULL, {.routine = M_LevelSelectInit}, 1, GT_BATTLE}, + {IT_STRING | IT_CALL, "Special", "Strike your target and secure the prize!", + NULL, {.routine = M_LevelSelectInit}, 1, GT_SPECIAL}, + {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, }; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index dc2101f8c..1a3f7a8d8 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1933,7 +1933,7 @@ static void M_DrawCupPreview(INT16 y, levelsearch_t *levelsearch) V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); - if (levelsearch->cup && !M_CupLocked(levelsearch->cup)) + if (levelsearch->cup && !M_CupLocked(levelsearch->cup) && maxlevels > 0) { add = (cupgrid.previewanim / 82) % maxlevels; map = start; @@ -2002,7 +2002,10 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup) else { if (currentMenu == &PLAY_LevelSelectDef) - V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, va("%s Mode", gametypes[levellist.newgametype]->name)); + { + UINT8 namedgt = (levellist.guessgt != MAXGAMETYPES) ? levellist.guessgt : levellist.newgametype; + V_DrawCenteredLSTitleLowString(BASEVIDWIDTH/2, y+6, 0, va("%s Mode", gametypes[namedgt]->name)); + } } } @@ -2261,7 +2264,8 @@ void M_DrawTimeAttack(void) laprec = mapheaderinfo[map]->mainrecord->lap; } - if (gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) + if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) + && (mapheaderinfo[map]->numlaps != 1)) { V_DrawRightAlignedString(rightedge-12, timeheight, highlightflags, "BEST LAP:"); K_drawKartTimestamp(laprec, 162+t, timeheight+6, 0, 2); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 264c91505..89aba301b 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3295,25 +3295,40 @@ void M_SetupGametypeMenu(INT32 choice) PLAY_GamemodesDef.prevMenu = currentMenu; - // Battle and Capsules disabled + // Battle and Capsules (and Special) disabled PLAY_GamemodesMenu[1].status = IT_DISABLED; PLAY_GamemodesMenu[2].status = IT_DISABLED; + PLAY_GamemodesMenu[3].status = IT_DISABLED; if (cv_splitplayers.value > 1) { // Re-add Battle PLAY_GamemodesMenu[1].status = IT_STRING | IT_CALL; } - else if (M_SecretUnlocked(SECRET_BREAKTHECAPSULES, true)) - { - // Re-add Capsules - PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL; - } else { - // Only one non-Back entry, let's skip straight to Race. - M_SetupRaceMenu(-1); - return; + boolean anyunlocked = false; + + if (M_SecretUnlocked(SECRET_BREAKTHECAPSULES, true)) + { + // Re-add Capsules + PLAY_GamemodesMenu[2].status = IT_STRING | IT_CALL; + anyunlocked = true; + } + + if (M_SecretUnlocked(SECRET_SPECIALATTACK, true)) + { + // Re-add Special + PLAY_GamemodesMenu[3].status = IT_STRING | IT_CALL; + anyunlocked = true; + } + + if (!anyunlocked) + { + // Only one non-Back entry, let's skip straight to Race. + M_SetupRaceMenu(-1); + return; + } } M_SetupNextMenu(&PLAY_GamemodesDef, false); @@ -3533,12 +3548,25 @@ static void M_LevelSelectScrollDest(void) static void M_LevelListFromGametype(INT16 gt) { static boolean first = true; - if (first || gt != levellist.newgametype) + if (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES) { levellist.newgametype = gt; + levellist.levelsearch.typeoflevel = G_TOLFlag(gt); + if (levellist.levelsearch.timeattack == true && gt == GT_SPECIAL) + { + // Sneak in an extra. + levellist.levelsearch.typeoflevel |= G_TOLFlag(GT_VERSUS); + levellist.guessgt = gt; + } + else + { + levellist.guessgt = MAXGAMETYPES; + } + levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); levellist.levelsearch.cup = NULL; + first = false; } @@ -3680,6 +3708,100 @@ void M_LevelSelectInit(INT32 choice) M_LevelListFromGametype(currentMenu->menuitems[itemOn].mvar2); } +static void M_LevelSelected(INT16 add) +{ + UINT8 i = 0; + INT16 map = M_GetFirstLevelInList(&i, &levellist.levelsearch); + + while (add > 0) + { + map = M_GetNextLevelInList(map, &i, &levellist.levelsearch); + + if (map >= nummapheaders) + { + break; + } + + add--; + } + + if (map >= nummapheaders) + { + // This shouldn't happen + return; + } + + levellist.choosemap = map; + + if (levellist.levelsearch.timeattack) + { + S_StartSound(NULL, sfx_s3k63); + + if (levellist.guessgt != MAXGAMETYPES) + levellist.newgametype = G_GuessGametypeByTOL(levellist.levelsearch.typeoflevel); + + PLAY_TimeAttackDef.prevMenu = currentMenu; + M_SetupNextMenu(&PLAY_TimeAttackDef, false); + } + else + { + if (gamestate == GS_MENU) + { + UINT8 ssplayers = cv_splitplayers.value-1; + + netgame = false; + multiplayer = true; + + strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); + + // Still need to reset devmode + cht_debug = 0; + + if (demo.playback) + G_StopDemo(); + if (metalrecording) + G_StopMetalDemo(); + + /*if (levellist.choosemap == 0) + levellist.choosemap = G_RandMap(G_TOLFlag(levellist.newgametype), -1, 0, 0, false, NULL);*/ + + if (cv_maxconnections.value < ssplayers+1) + CV_SetValue(&cv_maxconnections, ssplayers+1); + + if (splitscreen != ssplayers) + { + splitscreen = ssplayers; + SplitScreen_OnChange(); + } + + S_StartSound(NULL, sfx_s3k63); + + paused = false; + + // Early fadeout to let the sound finish playing + F_WipeStartScreen(); + V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); + F_WipeEndScreen(); + F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); + + SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); + + CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); + CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); + CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto" : cv_dummykartspeed.string); + + D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); + } + else + { + // directly do the map change + D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); + } + + M_ClearMenus(true); + } +} + void M_CupSelectHandler(INT32 choice) { const UINT8 pid = 0; @@ -3733,13 +3855,18 @@ void M_CupSelectHandler(INT32 choice) if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) { + INT16 count; cupheader_t *newcup = cupgrid.builtgrid[CUPMENU_CURSORID]; + cupheader_t *oldcup = levellist.levelsearch.cup; M_SetMenuDelay(pid); + levellist.levelsearch.cup = newcup; + count = M_CountLevelsToShowInList(&levellist.levelsearch); + if ((!newcup) - || (M_CupLocked(newcup)) - || (newcup->cachedlevels[0] == NEXTMAP_INVALID)) + || (count <= 0) + || (cupgrid.grandprix == true && newcup->cachedlevels[0] == NEXTMAP_INVALID)) { S_StartSound(NULL, sfx_s3kb2); return; @@ -3803,13 +3930,17 @@ void M_CupSelectHandler(INT32 choice) M_ClearMenus(true); } + else if (count == 1) + { + PLAY_TimeAttackDef.transitionID = currentMenu->transitionID+1; + M_LevelSelected(0); + } else { // Keep cursor position if you select the same cup again, reset if it's a different cup - if (levellist.levelsearch.cup != newcup) + if (oldcup != newcup || levellist.cursor >= count) { levellist.cursor = 0; - levellist.levelsearch.cup = newcup; } M_LevelSelectScrollDest(); @@ -3868,94 +3999,10 @@ void M_LevelSelectHandler(INT32 choice) if (M_MenuConfirmPressed(pid) /*|| M_MenuButtonPressed(pid, MBT_START)*/) { - UINT8 i = 0; - INT16 map = M_GetFirstLevelInList(&i, &levellist.levelsearch); - INT16 add = levellist.cursor; - M_SetMenuDelay(pid); - while (add > 0) - { - map = M_GetNextLevelInList(map, &i, &levellist.levelsearch); - - if (map >= nummapheaders) - { - break; - } - - add--; - } - - if (map >= nummapheaders) - { - // This shouldn't happen - return; - } - - levellist.choosemap = map; - - if (levellist.levelsearch.timeattack) - { - M_SetupNextMenu(&PLAY_TimeAttackDef, false); - S_StartSound(NULL, sfx_s3k63); - } - else - { - if (gamestate == GS_MENU) - { - UINT8 ssplayers = cv_splitplayers.value-1; - - netgame = false; - multiplayer = true; - - strncpy(connectedservername, cv_servername.string, MAXSERVERNAME); - - // Still need to reset devmode - cht_debug = 0; - - if (demo.playback) - G_StopDemo(); - if (metalrecording) - G_StopMetalDemo(); - - /*if (levellist.choosemap == 0) - levellist.choosemap = G_RandMap(G_TOLFlag(levellist.newgametype), -1, 0, 0, false, NULL);*/ - - if (cv_maxconnections.value < ssplayers+1) - CV_SetValue(&cv_maxconnections, ssplayers+1); - - if (splitscreen != ssplayers) - { - splitscreen = ssplayers; - SplitScreen_OnChange(); - } - - S_StartSound(NULL, sfx_s3k63); - - paused = false; - - // Early fadeout to let the sound finish playing - F_WipeStartScreen(); - V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); - F_WipeEndScreen(); - F_RunWipe(wipedefs[wipe_level_toblack], false, "FADEMAP0", false, false); - - SV_StartSinglePlayerServer(levellist.newgametype, levellist.netgame); - - CV_StealthSet(&cv_kartbot, cv_dummymatchbots.string); - CV_StealthSet(&cv_kartencore, (cv_dummygpencore.value == 1) ? "On" : "Auto"); - CV_StealthSet(&cv_kartspeed, (cv_dummykartspeed.value == KARTSPEED_NORMAL) ? "Auto" : cv_dummykartspeed.string); - - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - } - else - { - // directly do the map change - D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); - } - - M_ClearMenus(true); - } + PLAY_TimeAttackDef.transitionID = currentMenu->transitionID; + M_LevelSelected(levellist.cursor); } else if (M_MenuBackPressed(pid)) { diff --git a/src/m_cond.h b/src/m_cond.h index 5c50146f0..9f4ede99b 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -116,7 +116,8 @@ typedef enum // Menu restrictions SECRET_TIMEATTACK, // Permit Time attack - SECRET_BREAKTHECAPSULES, // Permit SP Capsules + SECRET_BREAKTHECAPSULES, // Permit SP Capsule attack + SECRET_SPECIALATTACK, // Permit Special attack (You're blue now!) SECRET_SOUNDTEST, // Permit Sound Test SECRET_ALTTITLE, // Permit alternate titlescreen From 978040ca3f94b2e6669f81bdfc22587f9dff969e Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 27 Dec 2022 22:36:20 +0000 Subject: [PATCH 020/128] Two menu bugbears - Clear console prints immediately after menu clear (fixes the prints on the black screen before loading level from menu) - Last kart standing, not last hedgehog standing :face_holding_back_tears: --- src/k_menudef.c | 2 +- src/k_menufunc.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/k_menudef.c b/src/k_menudef.c index 3f10aa313..b0de23761 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -89,7 +89,7 @@ menuitem_t PLAY_GamemodesMenu[] = {IT_STRING | IT_CALL, "Race", "A contest to see who's the fastest of them all!", NULL, {.routine = M_SetupRaceMenu}, 0, 0}, - {IT_STRING | IT_CALL, "Battle", "It's last hedgehog standing in this free-for-all!", + {IT_STRING | IT_CALL, "Battle", "It's last kart standing in this free-for-all!", "MENIMG00", {.routine = M_LevelSelectInit}, 0, GT_BATTLE}, {IT_STRING | IT_CALL, "Capsules", "Bust up all of the capsules in record time!", diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 89aba301b..6ee35a9dd 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -1009,6 +1009,8 @@ void M_ClearMenus(boolean callexitmenufunc) if (!menuactive) return; + CON_ClearHUD(); + if (currentMenu->quitroutine && callexitmenufunc && !currentMenu->quitroutine()) return; // we can't quit this menu (also used to set parameter from the menu) From 03c6eb3e305ee65baae4b27237dd5e7ca8858935 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 27 Dec 2022 22:41:30 +0000 Subject: [PATCH 021/128] Another bugbear: start on the Start for timeattack menu flow --- src/k_menufunc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 6ee35a9dd..bbe0b353c 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3742,6 +3742,7 @@ static void M_LevelSelected(INT16 add) if (levellist.guessgt != MAXGAMETYPES) levellist.newgametype = G_GuessGametypeByTOL(levellist.levelsearch.typeoflevel); + PLAY_TimeAttackDef.lastOn = ta_start; PLAY_TimeAttackDef.prevMenu = currentMenu; M_SetupNextMenu(&PLAY_TimeAttackDef, false); } From edea4e21104f9eac12f707c9627e8fc8a455dcb6 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 27 Dec 2022 22:56:07 +0000 Subject: [PATCH 022/128] Message says return to menu instead of title screen --- src/k_menufunc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index bbe0b353c..3ffc8403a 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6319,7 +6319,7 @@ void M_EndGame(INT32 choice) if (!Playing()) return; - M_StartMessage(M_GetText("Are you sure you want to return\nto the title screen?\nPress (A) to confirm or (B) to cancel\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); + M_StartMessage(M_GetText("Are you sure you want to return\nto the menu?\nPress (A) to confirm or (B) to cancel\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); } From c47faa37ab195730534a5de05be2fcf76f13a555 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 27 Dec 2022 23:12:04 +0000 Subject: [PATCH 023/128] ONLY have Super Ring in item reel if UFO is busted --- src/k_roulette.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 9a5442a03..1c3c2db62 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -176,6 +176,12 @@ static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = { 0, 0, 0, 0 } // Gachabom x3 }; +static kartitems_t K_KartItemReelSpecialEnd[] = +{ + KITEM_SUPERRING, + KITEM_NONE +}; + static kartitems_t K_KartItemReelTimeAttack[] = { KITEM_SNEAKER, @@ -1113,8 +1119,21 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } // SPECIAL CASE No. 2: - // Use a special, pre-determined item reel for Time Attack / Free Play - if (gametyperules & GTR_BOSS) + // Use a special, pre-determined item reel for Time Attack / Free Play / End of Sealed Stars + if (specialstageinfo.valid) + { + if (specialstageinfo.ufo == NULL + || P_MobjWasRemoved(specialstageinfo.ufo) + || specialstageinfo.ufo->health == 1) + { + for (i = 0; K_KartItemReelSpecialEnd[i] != KITEM_NONE; i++) + { + K_PushToRouletteItemList(roulette, K_KartItemReelSpecialEnd[i]); + } + return; + } + } + else if (gametyperules & GTR_BOSS) { for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++) { From c992438e3c93ed0427b3efbfdd8751c2b0e26a75 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 27 Dec 2022 23:13:02 +0000 Subject: [PATCH 024/128] Only tether off UFO if it's not just a teeny emerald --- src/k_kart.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index ea05bbde1..9bea5e721 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1340,7 +1340,8 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed static void K_UpdateDraft(player_t *player) { const boolean addUfo = ((specialstageinfo.valid == true) - && (specialstageinfo.ufo != NULL && P_MobjWasRemoved(specialstageinfo.ufo) == false)); + && (specialstageinfo.ufo != NULL && P_MobjWasRemoved(specialstageinfo.ufo) == false) + && specialstageinfo.ufo->health > 1); fixed_t topspd = K_GetKartSpeed(player, false, false); fixed_t draftdistance; From e45282bdff1e2407bf1da605132bc394ffe9ad73 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 28 Dec 2022 23:31:46 +0000 Subject: [PATCH 025/128] K_GetPossibleSpecialTarget() * Standardises conditions under which the UFO Catcher can be "targeted" (Jawz, SPB, tether) * Makes SPBs explode a little ahead of driving distance if UFO Catcher is no longer targetable --- src/k_kart.c | 25 ++++++++++++------------- src/k_roulette.c | 4 +--- src/k_specialstage.c | 15 +++++++++++++++ src/k_specialstage.h | 11 +++++++++++ src/objects/spb.c | 23 ++++++++++++++--------- 5 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 9bea5e721..80ff8790f 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1339,9 +1339,7 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed */ static void K_UpdateDraft(player_t *player) { - const boolean addUfo = ((specialstageinfo.valid == true) - && (specialstageinfo.ufo != NULL && P_MobjWasRemoved(specialstageinfo.ufo) == false) - && specialstageinfo.ufo->health > 1); + mobj_t *addUfo = K_GetPossibleSpecialTarget(); fixed_t topspd = K_GetKartSpeed(player, false, false); fixed_t draftdistance; @@ -1381,10 +1379,10 @@ static void K_UpdateDraft(player_t *player) // Not enough speed to draft. if (player->speed >= 20 * player->mo->scale) { - if (addUfo == true) + if (addUfo != NULL) { // Tether off of the UFO! - if (K_TryDraft(player, specialstageinfo.ufo, minDist, draftdistance, leniency) == true) + if (K_TryDraft(player, addUfo, minDist, draftdistance, leniency) == true) { return; // Finished doing our draft. } @@ -1438,11 +1436,11 @@ static void K_UpdateDraft(player_t *player) fixed_t dist = P_AproxDistance(P_AproxDistance(victim->mo->x - player->mo->x, victim->mo->y - player->mo->y), victim->mo->z - player->mo->z); K_DrawDraftCombiring(player, victim->mo, dist, draftdistance, true); } - else if (addUfo == true) + else if (addUfo != NULL) { // kind of a hack to not have to mess with how lastdraft works - fixed_t dist = P_AproxDistance(P_AproxDistance(specialstageinfo.ufo->x - player->mo->x, specialstageinfo.ufo->y - player->mo->y), specialstageinfo.ufo->z - player->mo->z); - K_DrawDraftCombiring(player, specialstageinfo.ufo, dist, draftdistance, true); + fixed_t dist = P_AproxDistance(P_AproxDistance(addUfo->x - player->mo->x, addUfo->y - player->mo->y), addUfo->z - player->mo->z); + K_DrawDraftCombiring(player, addUfo, dist, draftdistance, true); } } else // Remove draft speed boost. @@ -6831,8 +6829,8 @@ mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) if (specialstageinfo.valid == true) { - // Always target the UFO. - return specialstageinfo.ufo; + // Always target the UFO (but not the emerald) + return K_GetPossibleSpecialTarget(); } for (i = 0; i < MAXPLAYERS; i++) @@ -8066,10 +8064,11 @@ void K_KartPlayerAfterThink(player_t *player) mobj_t *ret = NULL; - if (specialstageinfo.valid == true && lastTargID == MAXPLAYERS) + if (specialstageinfo.valid == true + && lastTargID == MAXPLAYERS) { - // Aiming at the UFO. - lastTarg = specialstageinfo.ufo; + // Aiming at the UFO (but never the emerald). + lastTarg = K_GetPossibleSpecialTarget(); } else if ((lastTargID >= 0 && lastTargID <= MAXPLAYERS) && playeringame[lastTargID] == true) diff --git a/src/k_roulette.c b/src/k_roulette.c index 1c3c2db62..da12d5209 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1122,9 +1122,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // Use a special, pre-determined item reel for Time Attack / Free Play / End of Sealed Stars if (specialstageinfo.valid) { - if (specialstageinfo.ufo == NULL - || P_MobjWasRemoved(specialstageinfo.ufo) - || specialstageinfo.ufo->health == 1) + if (K_GetPossibleSpecialTarget() == NULL) { for (i = 0; K_KartItemReelSpecialEnd[i] != KITEM_NONE; i++) { diff --git a/src/k_specialstage.c b/src/k_specialstage.c index 76f986bcb..d5eb172ce 100644 --- a/src/k_specialstage.c +++ b/src/k_specialstage.c @@ -121,3 +121,18 @@ void K_TickSpecialStage(void) K_MoveExitBeam(); } + +mobj_t *K_GetPossibleSpecialTarget(void) +{ + if (specialstageinfo.valid == false) + return NULL; + + if (specialstageinfo.ufo == NULL + || P_MobjWasRemoved(specialstageinfo.ufo)) + return NULL; + + if (specialstageinfo.ufo->health <= 1) //UFOEmeraldChase(specialstageinfo.ufo) + return NULL; + + return specialstageinfo.ufo; +} diff --git a/src/k_specialstage.h b/src/k_specialstage.h index 091a39f7d..d12691144 100644 --- a/src/k_specialstage.h +++ b/src/k_specialstage.h @@ -50,5 +50,16 @@ void K_InitSpecialStage(void); void K_TickSpecialStage(void); +/*-------------------------------------------------- + mobj_t *K_GetPossibleSpecialTarget(void) + + Gets the global special stage target if valid + (for Jawz, tether, etc) + + Return:- + Target or NULL +--------------------------------------------------*/ + +mobj_t *K_GetPossibleSpecialTarget(void); #endif diff --git a/src/objects/spb.c b/src/objects/spb.c index 19bb4d7a1..cdb654c5f 100644 --- a/src/objects/spb.c +++ b/src/objects/spb.c @@ -857,6 +857,20 @@ void Obj_SPBThink(mobj_t *spb) ghost->colorized = true; } + if (spb_nothink(spb) <= 1) + { + if (specialstageinfo.valid == true) + { + bestRank = 0; + + if ((bestMobj = K_GetPossibleSpecialTarget()) == NULL) + { + spb->fuse = TICRATE/3; + spb_nothink(spb) = spb->fuse + 2; + } + } + } + if (spb_nothink(spb) > 0) { // Init values, don't think yet. @@ -874,15 +888,6 @@ void Obj_SPBThink(mobj_t *spb) } else { - if (specialstageinfo.valid == true) - { - if (specialstageinfo.ufo != NULL && P_MobjWasRemoved(specialstageinfo.ufo) == false) - { - bestRank = 1; - bestMobj = specialstageinfo.ufo; - } - } - // Find the player with the best rank for (i = 0; i < MAXPLAYERS; i++) { From 7482e11992229b6ee852b7efa4a986b737b15935 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 28 Dec 2022 23:35:27 +0000 Subject: [PATCH 026/128] SPB special stage experiment If you smuggle an SPB to after the death of the UFO Catcher, spawn a single, free-on-straightways manta ring before it blows up. Testing shows that you can get hit by the SPB's explosion only if you use the free manta ring just before a sneaker panel... but also Sal thinks it's cool and I think it adds depth --- src/objects/spb.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/objects/spb.c b/src/objects/spb.c index cdb654c5f..af1041049 100644 --- a/src/objects/spb.c +++ b/src/objects/spb.c @@ -865,6 +865,13 @@ void Obj_SPBThink(mobj_t *spb) if ((bestMobj = K_GetPossibleSpecialTarget()) == NULL) { + // experimental - I think it's interesting IMO + Obj_MantaRingCreate( + spb, + spb_owner(spb), + NULL + ); + spb->fuse = TICRATE/3; spb_nothink(spb) = spb->fuse + 2; } From df3624d96cabd9d57f0c85050f8f2e89768301fd Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 29 Dec 2022 17:44:19 +0000 Subject: [PATCH 027/128] Executor calls for UFO Catcher special events Requested by Charyb. - LE_PINCHPHASE (-2 or 65534) for busting open the Catcher - LE_BOSSDEAD (-4 or 65532) for grabbing the emerald Would like to use args for UDMF compatibility but there is no spawnpoint for the Catcher so it's hardcoded for now --- src/objects/ufo.c | 2 ++ src/p_inter.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/objects/ufo.c b/src/objects/ufo.c index 6fe0af881..f9a44127b 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -607,6 +607,8 @@ boolean Obj_SpecialUFODamage(mobj_t *ufo, mobj_t *inflictor, mobj_t *source, UIN ufo->flags = (ufo->flags & ~MF_SHOOTABLE) | (MF_SPECIAL|MF_PICKUPFROMBELOW); ufo->shadowscale = FRACUNIT/3; + P_LinedefExecute(LE_PINCHPHASE, ufo, NULL); + ufo_speed(ufo) += addSpeed; // Even more speed! return true; } diff --git a/src/p_inter.c b/src/p_inter.c index 89229f8ac..a630d96bf 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -414,6 +414,8 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (toucher->hitlag > 0) return; + P_LinedefExecute(LE_BOSSDEAD, ufo, NULL); + CONS_Printf("You win!\n"); break; /* From d1852adf30066ebc54f26aedda6b415ed1890cdf Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 29 Dec 2022 17:46:11 +0000 Subject: [PATCH 028/128] Forgot to stage this --- src/p_inter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_inter.c b/src/p_inter.c index a630d96bf..8944b8b50 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -414,7 +414,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (toucher->hitlag > 0) return; - P_LinedefExecute(LE_BOSSDEAD, ufo, NULL); + P_LinedefExecute(LE_BOSSDEAD, toucher, NULL); CONS_Printf("You win!\n"); break; From 881507889dfb8950a3bebc1f7725209c1a5b38aa Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 29 Dec 2022 17:54:19 +0000 Subject: [PATCH 029/128] Fix Broly SIGFPE - Don't spawn the KI if the duration <= 0 - If the KI spawned but the duration <= 0 for any other reason remove the object instead of performing the division --- src/k_objects.h | 2 +- src/objects/broly.c | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/k_objects.h b/src/k_objects.h index 284ec1c61..222ead574 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -56,7 +56,7 @@ void Obj_DuelBombInit(mobj_t *bomb); /* Broly Ki */ mobj_t *Obj_SpawnBrolyKi(mobj_t *source, tic_t duration); -void Obj_BrolyKiThink(mobj_t *ki); +boolean Obj_BrolyKiThink(mobj_t *ki); /* Special Stage UFO */ waypoint_t *K_GetSpecialUFOWaypoint(mobj_t *ufo); diff --git a/src/objects/broly.c b/src/objects/broly.c index d041c23b7..4c283a175 100644 --- a/src/objects/broly.c +++ b/src/objects/broly.c @@ -34,14 +34,16 @@ Obj_SpawnBrolyKi ( mobj_t * source, tic_t duration) { - mobj_t *x = P_SpawnMobjFromMobj( - source, 0, 0, 0, MT_BROLY); + mobj_t *x; - if (duration == 0) + if (duration <= 0) { - return x; + return NULL; } + x = P_SpawnMobjFromMobj( + source, 0, 0, 0, MT_BROLY); + // Shrink into center of source object. x->z = (source->z + source->height / 2); @@ -61,12 +63,20 @@ Obj_SpawnBrolyKi return x; } -void +boolean Obj_BrolyKiThink (mobj_t *x) { + if (broly_duration(x) <= 0) + { + P_RemoveMobj(x); + return false; + } + const fixed_t t = get_unit_linear(x), n = Easing_OutSine(t, 0, broly_maxscale(x)); P_InstaScale(x, n); + + return true; } From 1f18aa727a883e5c03e7f60e397cc9c7845f1484 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 29 Dec 2022 17:57:19 +0000 Subject: [PATCH 030/128] Forgot to *stash* this time --- src/p_mobj.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 26b38d631..2b8101aeb 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6534,7 +6534,10 @@ static void P_MobjSceneryThink(mobj_t *mobj) mobj->renderflags ^= RF_DONTDRAW; break; case MT_BROLY: - Obj_BrolyKiThink(mobj); + if (Obj_BrolyKiThink(mobj) == false) + { + return; + } break; case MT_VWREF: case MT_VWREB: From 834150585b2977a05c9789acc8ace8006d5affe7 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 29 Dec 2022 21:30:04 +0000 Subject: [PATCH 031/128] Rework demo time/laptime handling * `ATTACKING_` constants have been changed to be flags - `ATTACKING_TIME` contains time data for all gametypes - `ATTACKING_LAPS` contains laps data for `GTR_CIRCUIT` on maps with more than 1 lap * `demoflags` now contains raw `ATTACKING_` flags * Best time/best lap demo files will now be saved properly again (broken since `new-menus`) * Ghosts will now be loaded properly again (broken since `unlockables-undefeatable`) --- src/doomstat.h | 6 +- src/g_demo.c | 140 +++++++++++++++++++---------------------------- src/g_game.c | 50 ++++++++++------- src/k_menufunc.c | 12 ++-- src/p_setup.c | 17 +++--- 5 files changed, 104 insertions(+), 121 deletions(-) diff --git a/src/doomstat.h b/src/doomstat.h index 9222e7381..63739358f 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -131,9 +131,9 @@ extern boolean usedCheats; extern boolean imcontinuing; // Temporary flag while continuing extern boolean metalrecording; -#define ATTACKING_NONE 0 -#define ATTACKING_TIME 1 -#define ATTACKING_CAPSULES 2 +#define ATTACKING_NONE 0 +#define ATTACKING_TIME 1 +#define ATTACKING_LAP (1<<1) extern UINT8 modeattacking; // menu demo things diff --git a/src/g_demo.c b/src/g_demo.c index e47d8ba28..ddf0b342f 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -120,18 +120,14 @@ demoghost *ghosts = NULL; #define DEMOVERSION 0x0007 #define DEMOHEADER "\xF0" "KartReplay" "\x0F" -#define DF_GHOST 0x01 // This demo contains ghost data too! -#define DF_TIMEATTACK 0x02 // This demo is from Time Attack and contains its final completion time & best lap! -#define DF_BREAKTHECAPSULES 0x04 // This demo is from Break the Capsules and contains its final completion time! -#define DF_ATTACKMASK 0x06 // This demo is from ??? attack and contains ??? +#define DF_ATTACKMASK (ATTACKING_TIME|ATTACKING_LAP) // This demo contains time/lap data -// 0x08 free +#define DF_GHOST 0x08 // This demo contains ghost data too! #define DF_NONETMP 0x10 // multiplayer but not netgame #define DF_LUAVARS 0x20 // this demo contains extra lua vars -#define DF_ATTACKSHIFT 1 #define DF_ENCORE 0x40 #define DF_MULTIPLAYER 0x80 // This demo was recorded in multiplayer mode! @@ -2339,10 +2335,19 @@ void G_BeginRecording(void) memset(name,0,sizeof(name)); demo_p = demobuffer; - demoflags = DF_GHOST|(multiplayer ? DF_MULTIPLAYER : (modeattacking<>DF_ATTACKSHIFT) + if ((demoflags & DF_ATTACKMASK)) { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - demotime_p = demo_p; + demotime_p = demo_p; + + if (demoflags & ATTACKING_TIME) WRITEUINT32(demo_p,UINT32_MAX); // time + if (demoflags & ATTACKING_LAP) WRITEUINT32(demo_p,UINT32_MAX); // lap - break; - case ATTACKING_CAPSULES: // 2 - demotime_p = demo_p; - WRITEUINT32(demo_p,UINT32_MAX); // time - break; - default: // 3 - break; + } + else + { + demotime_p = NULL; } for (i = 0; i < PRNUMCLASS; i++) @@ -2600,18 +2602,15 @@ void G_SetDemoTime(UINT32 ptime, UINT32 plap) { if (!demo.recording || !demotime_p) return; - if (demoflags & DF_TIMEATTACK) + if (demoflags & ATTACKING_TIME) { WRITEUINT32(demotime_p, ptime); + } + if (demoflags & ATTACKING_LAP) + { WRITEUINT32(demotime_p, plap); - demotime_p = NULL; - } - else if (demoflags & DF_BREAKTHECAPSULES) - { - WRITEUINT32(demotime_p, ptime); - (void)plap; - demotime_p = NULL; } + demotime_p = NULL; } // Returns bitfield: @@ -2622,7 +2621,8 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) { UINT8 *buffer,*p; UINT8 flags; - UINT32 oldtime, newtime, oldlap, newlap; + UINT32 oldtime = UINT32_MAX, newtime = UINT32_MAX; + UINT32 oldlap = UINT32_MAX, newlap = UINT32_MAX; UINT16 oldversion; size_t bufsize ATTRUNUSED; UINT8 c; @@ -2658,17 +2658,16 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) G_SkipDemoSkins(&p); - aflags = flags & (DF_TIMEATTACK|DF_BREAKTHECAPSULES); + aflags = flags & DF_ATTACKMASK; I_Assert(aflags); - if (flags & DF_TIMEATTACK) - uselaps = true; // get around uninitalized error + if (aflags & ATTACKING_LAP) + uselaps = true; - newtime = READUINT32(p); + if (aflags & ATTACKING_TIME) + newtime = READUINT32(p); if (uselaps) newlap = READUINT32(p); - else - newlap = UINT32_MAX; Z_Free(buffer); @@ -2724,11 +2723,10 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) G_SkipDemoSkins(&p); - oldtime = READUINT32(p); + if (flags & ATTACKING_TIME) + oldtime = READUINT32(p); if (uselaps) oldlap = READUINT32(p); - else - oldlap = 0; Z_Free(buffer); @@ -3136,7 +3134,7 @@ void G_DoPlayDemo(char *defdemoname) return; } - modeattacking = (demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT; + modeattacking = (demoflags & DF_ATTACKMASK); multiplayer = !!(demoflags & DF_MULTIPLAYER); demo.netgame = (multiplayer && !(demoflags & DF_NONETMP)); CON_ToggleOff(); @@ -3144,21 +3142,10 @@ void G_DoPlayDemo(char *defdemoname) hu_demotime = UINT32_MAX; hu_demolap = UINT32_MAX; - switch (modeattacking) - { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - hu_demotime = READUINT32(demo_p); - hu_demolap = READUINT32(demo_p); - break; - case ATTACKING_CAPSULES: // 2 - hu_demotime = READUINT32(demo_p); - break; - default: // 3 - modeattacking = ATTACKING_NONE; - break; - } + if (modeattacking & ATTACKING_TIME) + hu_demotime = READUINT32(demo_p); + if (modeattacking & ATTACKING_LAP) + hu_demolap = READUINT32(demo_p); // Random seed for (i = 0; i < PRNUMCLASS; i++) @@ -3545,19 +3532,10 @@ void G_AddGhost(char *defdemoname) return; } - switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) - { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - p += 8; // demo time, lap - break; - case ATTACKING_CAPSULES: // 2 - p += 4; // demo time - break; - default: // 3 - break; - } + if (flags & ATTACKING_TIME) + p += 4; + if (flags & ATTACKING_LAP) + p += 4; for (i = 0; i < PRNUMCLASS; i++) { @@ -3587,9 +3565,10 @@ void G_AddGhost(char *defdemoname) p++; // player number - doesn't really need to be checked, TODO maybe support adding multiple players' ghosts at once // any invalidating flags? - if ((READUINT8(p) & (DEMO_SPECTATOR|DEMO_BOT)) != 0) + i = READUINT8(p); + if ((i & (DEMO_SPECTATOR|DEMO_BOT)) != 0) { - CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot.\n"), pdemoname); + CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot (spectator/bot)\n"), pdemoname); Z_Free(skinlist); Z_Free(pdemoname); Z_Free(buffer); @@ -3600,6 +3579,8 @@ void G_AddGhost(char *defdemoname) M_Memcpy(name, p, 16); p += 16; + p += MAXAVAILABILITY; + // Skin i = READUINT8(p); if (i < worknumskins) @@ -3620,7 +3601,7 @@ void G_AddGhost(char *defdemoname) if (READUINT8(p) != 0xFF) { - CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot.\n"), pdemoname); + CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot (bad terminator)\n"), pdemoname); Z_Free(skinlist); Z_Free(pdemoname); Z_Free(buffer); @@ -3761,19 +3742,10 @@ void G_UpdateStaffGhostName(lumpnum_t l) G_SkipDemoSkins(&p); - switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) - { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - p += 8; // demo time, lap - break; - case ATTACKING_CAPSULES: // 2 - p += 4; // demo time - break; - default: // 3 - break; - } + if (flags & ATTACKING_TIME) + p += 4; + if (flags & ATTACKING_LAP) + p += 4; for (i = 0; i < PRNUMCLASS; i++) { diff --git a/src/g_game.c b/src/g_game.c index 2c7f85efb..8ba6a4aa5 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -520,13 +520,18 @@ static void G_UpdateRecordReplays(void) players[consoleplayer].realtime = UINT32_MAX; } - if (((mapheaderinfo[gamemap-1]->mainrecord->time == 0) || (players[consoleplayer].realtime < mapheaderinfo[gamemap-1]->mainrecord->time)) - && (players[consoleplayer].realtime < UINT32_MAX)) // DNF + if (modeattacking & ATTACKING_TIME) { - mapheaderinfo[gamemap-1]->mainrecord->time = players[consoleplayer].realtime; + if (((mapheaderinfo[gamemap-1]->mainrecord->time == 0) || (players[consoleplayer].realtime < mapheaderinfo[gamemap-1]->mainrecord->time)) + && (players[consoleplayer].realtime < UINT32_MAX)) // DNF + mapheaderinfo[gamemap-1]->mainrecord->time = players[consoleplayer].realtime; + } + else + { + mapheaderinfo[gamemap-1]->mainrecord->time = 0; } - if (modeattacking == ATTACKING_TIME) + if (modeattacking & ATTACKING_LAP) { if ((mapheaderinfo[gamemap-1]->mainrecord->lap == 0) || (bestlap < mapheaderinfo[gamemap-1]->mainrecord->lap)) mapheaderinfo[gamemap-1]->mainrecord->lap = bestlap; @@ -549,27 +554,32 @@ static void G_UpdateRecordReplays(void) strcat(gpath, PATHSEP); strcat(gpath, G_BuildMapName(gamemap)); - snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, cv_chooseskin.string); + snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, cv_skin[0].string); - gpath = Z_StrDup(gpath); - - if (FIL_FileExists(lastdemo)) + if (modeattacking != ATTACKING_NONE && FIL_FileExists(lastdemo)) { UINT8 *buf; - size_t len = FIL_ReadFile(lastdemo, &buf); + size_t len; - snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, cv_chooseskin.string); - if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1) - { // Better time, save this demo. - if (FIL_FileExists(bestdemo)) - remove(bestdemo); - FIL_WriteFile(bestdemo, buf, len); - CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo); + gpath = Z_StrDup(gpath); + + len = FIL_ReadFile(lastdemo, &buf); + + if (modeattacking & ATTACKING_TIME) + { + snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, cv_skin[0].string); + if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1) + { // Better time, save this demo. + if (FIL_FileExists(bestdemo)) + remove(bestdemo); + FIL_WriteFile(bestdemo, buf, len); + CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo); + } } - if (modeattacking == ATTACKING_TIME) + if (modeattacking & ATTACKING_LAP) { - snprintf(bestdemo, 255, "%s-%s-lap-best.lmp", gpath, cv_chooseskin.string); + snprintf(bestdemo, 255, "%s-%s-lap-best.lmp", gpath, cv_skin[0].string); if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & (1<<1)) { // Better lap time, save this demo. if (FIL_FileExists(bestdemo)) @@ -582,9 +592,9 @@ static void G_UpdateRecordReplays(void) //CONS_Printf("%s '%s'\n", M_GetText("Saved replay as"), lastdemo); Z_Free(buf); - } - Z_Free(gpath); + Z_Free(gpath); + } // Check emblems when level data is updated if ((earnedEmblems = M_CheckLevelEmblems())) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 3ffc8403a..abd3baa99 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -4056,14 +4056,12 @@ void M_StartTimeAttack(INT32 choice) (void)choice; - switch (levellist.newgametype) + modeattacking = ATTACKING_TIME; + + if ((gametypes[levellist.newgametype]->rules & GTR_CIRCUIT) + && (mapheaderinfo[levellist.choosemap]->numlaps != 1)) { - case GT_BATTLE: - modeattacking = ATTACKING_CAPSULES; - break; - default: - modeattacking = ATTACKING_TIME; - break; + modeattacking |= ATTACKING_LAP; } // Still need to reset devmode diff --git a/src/p_setup.c b/src/p_setup.c index e09141271..36a589f22 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -6993,20 +6993,23 @@ static void P_LoadRecordGhosts(void) gpath = Z_StrDup(va("%s"PATHSEP"media"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap))); // Best Time ghost - if (cv_ghost_besttime.value) + if (modeattacking & ATTACKING_TIME) { - for (i = 0; i < numskins; ++i) + if (cv_ghost_besttime.value) { - if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i) - continue; + for (i = 0; i < numskins; ++i) + { + if (cv_ghost_besttime.value == 1 && players[consoleplayer].skin != i) + continue; - if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name))) - G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name)); + if (FIL_FileExists(va("%s-%s-time-best.lmp", gpath, skins[i].name))) + G_AddGhost(va("%s-%s-time-best.lmp", gpath, skins[i].name)); + } } } // Best Lap ghost - if (modeattacking != ATTACKING_CAPSULES) + if (modeattacking & ATTACKING_LAP) { if (cv_ghost_bestlap.value) { From 52040c1248676a2ec3b1454b8fac5980b93831d4 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 29 Dec 2022 23:13:15 +0000 Subject: [PATCH 032/128] Replays (net and timeeattack) now recognise gametypes by name - Should support custom gametypes, but haven't been thoroughly testing those - Custom gametypes must now be unique by name - Custom gametypes now have a maximum name length of 31 --- src/deh_soc.c | 22 ++++++++++++++++++++++ src/doomstat.h | 1 + src/g_demo.c | 43 ++++++++++++++++++++++++++++++++----------- src/g_demo.h | 2 +- src/k_menudraw.c | 12 +++++++----- src/lua_baselib.c | 18 ++++++++++++++---- 6 files changed, 77 insertions(+), 21 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index fdba5c507..241ddb8eb 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -878,6 +878,28 @@ void readgametype(MYFILE *f, char *gtname) I_Error("Out of Gametype Freeslots while allocating \"%s\"\nLoad less addons to fix this.", gtname); } + if (gtname[0] == '\0') + { + deh_warning("Custom gametype must have a name"); + return; + } + + if (strlen(gtname) >= MAXGAMETYPELENGTH) + { + deh_warning("Custom gametype \"%s\"'s name must be %d long at most", gtname, MAXGAMETYPELENGTH-1); + return; + } + + for (i = 0; i < numgametypes; i++) + if (fastcmp(gtname, gametypes[i]->name)) + break; + + if (i < numgametypes) + { + deh_warning("Custom gametype \"%s\"'s name is already in use", gtname); + return; + } + // Add the new gametype newgametype = Z_Calloc(sizeof (gametype_t), PU_STATIC, NULL); if (!newgametype) diff --git a/src/doomstat.h b/src/doomstat.h index 63739358f..e7ff2f093 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -450,6 +450,7 @@ extern INT32 nummapheaders, mapallocsize; // Gametypes #define NUMGAMETYPEFREESLOTS (MAXGAMETYPES-GT_FIRSTFREESLOT) +#define MAXGAMETYPELENGTH 32 enum GameType { diff --git a/src/g_demo.c b/src/g_demo.c index ddf0b342f..f82e96325 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -2378,7 +2378,9 @@ void G_BeginRecording(void) M_Memcpy(demo_p, mapmd5, 16); demo_p += 16; WRITEUINT8(demo_p, demoflags); - WRITEUINT8(demo_p, gametype & 0xFF); + + WRITESTRINGN(demo_p, gametypes[gametype]->name, MAXGAMETYPELENGTH); + WRITEUINT8(demo_p, numlaps); // file list @@ -2652,7 +2654,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) SKIPSTRING(p); // gamemap p += 16; // map md5 flags = READUINT8(p); // demoflags - p++; // gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); @@ -2711,7 +2713,7 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) SKIPSTRING(p); // gamemap p += 16; // mapmd5 flags = READUINT8(p); - p++; // gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); if (!(flags & aflags)) @@ -2756,7 +2758,7 @@ void G_LoadDemoInfo(menudemo_t *pdemo) UINT8 version, subversion, pdemoflags, worknumskins, skinid; democharlist_t *skinlist = NULL; UINT16 pdemoversion, count; - char mapname[MAXMAPLUMPNAME]; + char mapname[MAXMAPLUMPNAME],gtname[MAXGAMETYPELENGTH]; INT32 i; if (!FIL_ReadFile(pdemo->filepath, &infobuffer)) @@ -2820,7 +2822,9 @@ void G_LoadDemoInfo(menudemo_t *pdemo) return; } - pdemo->gametype = READUINT8(info_p); + READSTRINGN(info_p, gtname, sizeof(gtname)); // gametype + pdemo->gametype = G_GetGametypeByName(gtname); + pdemo->numlaps = READUINT8(info_p); pdemo->addonstatus = G_CheckDemoExtraFiles(&info_p, true); @@ -2932,9 +2936,11 @@ void G_DeferedPlayDemo(const char *name) void G_DoPlayDemo(char *defdemoname) { - UINT8 i, p, numslots = 0; + INT32 i; + UINT8 p, numslots = 0; lumpnum_t l; - char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],*n,*pdemoname; + char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],gtname[MAXGAMETYPELENGTH]; + char *n,*pdemoname; UINT8 availabilities[MAXPLAYERS][MAXAVAILABILITY]; UINT8 version,subversion; UINT32 randseed[PRNUMCLASS]; @@ -2951,6 +2957,7 @@ void G_DoPlayDemo(char *defdemoname) follower[16] = '\0'; color[MAXCOLORNAME] = '\0'; + gtname[MAXGAMETYPELENGTH-1] = '\0'; // No demo name means we're restarting the current demo if (defdemoname == NULL) @@ -3060,8 +3067,22 @@ void G_DoPlayDemo(char *defdemoname) demo_p += 16; // mapmd5 demoflags = READUINT8(demo_p); - gametype = READUINT8(demo_p); - G_SetGametype(gametype); + + READSTRINGN(demo_p, gtname, sizeof(gtname)); // gametype + i = G_GetGametypeByName(gtname); + if (i < 0) + { + snprintf(msg, 1024, M_GetText("%s is in a gametype that is not currently loaded and cannot be played.\n"), pdemoname); + CONS_Alert(CONS_ERROR, "%s", msg); + M_StartMessage(msg, NULL, MM_NOTHING); + Z_Free(pdemoname); + Z_Free(demobuffer); + demo.playback = false; + demo.title = false; + return; + } + G_SetGametype(i); + numlaps = READUINT8(demo_p); if (demo.title) // Titledemos should always play and ought to always be compatible with whatever wadlist is running. @@ -3519,7 +3540,7 @@ void G_AddGhost(char *defdemoname) return; } - p++; // gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); // Don't wanna modify the file list for ghosts. @@ -3736,7 +3757,7 @@ void G_UpdateStaffGhostName(lumpnum_t l) goto fail; // we don't NEED to do it here, but whatever } - p++; // Gametype + SKIPSTRING(p); // gametype p++; // numlaps G_SkipDemoExtraFiles(&p); diff --git a/src/g_demo.h b/src/g_demo.h index db5158b9a..8ecf08448 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -84,7 +84,7 @@ struct menudemo_t { char title[65]; // Null-terminated for string prints UINT16 map; UINT8 addonstatus; // What do we need to do addon-wise to play this demo? - UINT8 gametype; + INT16 gametype; SINT8 kartspeed; // Add OR DF_ENCORE for encore mode, idk UINT8 numlaps; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 1a3f7a8d8..f47689f8b 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3969,9 +3969,11 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) V_DrawThinString(x, y+9, V_SNAPTOTOP|V_ALLOWLOWERCASE, va("(%d laps)", demoref->numlaps)); { - const char *gtstring = "???"; - if (demoref->gametype >= GT_FIRSTFREESLOT) - ; // TODO: Support custom gametypes in netreplays (would require deeper changes than this) + const char *gtstring; + if (demoref->gametype < 0) + { + gtstring = "Custom (not loaded)"; + } else { gtstring = gametypes[demoref->gametype]->name; @@ -3994,7 +3996,7 @@ static void M_DrawReplayHutReplayInfo(menudemo_t *demoref) V_DrawThinString(x, y+29, V_SNAPTOTOP|highlightflags, "WINNER"); V_DrawString(x+38, y+30, V_SNAPTOTOP|V_ALLOWLOWERCASE, demoref->standings[0].name); - if (demoref->gametype < GT_FIRSTFREESLOT) + if (demoref->gametype >= 0) { if (gametypes[demoref->gametype]->rules & GTR_POINTLIMIT) { @@ -4215,7 +4217,7 @@ void M_DrawReplayStartMenu(void) if (demoref->standings[i].timeorscore == UINT32_MAX-1) V_DrawThinString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, "NO CONTEST"); - else if (demoref->gametype >= GT_FIRSTFREESLOT) + else if (demoref->gametype < 0) ; else if (gametypes[demoref->gametype]->rules & GTR_POINTLIMIT) V_DrawString(BASEVIDWIDTH-92, STARTY + i*20 + 9, V_SNAPTOTOP, va("%d", demoref->standings[i].timeorscore)); diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 61587bd62..1a33438fb 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -2980,6 +2980,7 @@ static int lib_gAddGametype(lua_State *L) INT32 newgtpointlimit = 0; INT32 newgttimelimit = 0; UINT8 newgtinttype = 0; + INT16 j; luaL_checktype(L, 1, LUA_TTABLE); lua_settop(L, 1); // Clear out all other possible arguments, leaving only the first one. @@ -3043,13 +3044,22 @@ static int lib_gAddGametype(lua_State *L) #undef FIELDERROR #undef TYPEERROR + if (gtname == NULL) + return luaL_error(L, "Custom gametype must have a name"); + + if (strlen(gtname) >= MAXGAMETYPELENGTH) + return luaL_error(L, "Custom gametype \"%s\"'s name must be %d long at most", gtname, MAXGAMETYPELENGTH-1); + + for (j = 0; j < numgametypes; j++) + if (!strcmp(gtname, gametypes[j]->name)) + break; + + if (j < numgametypes) + return luaL_error(L, "Custom gametype \"%s\"'s name is already in use", gtname); + // pop gametype table lua_pop(L, 1); - // Set defaults - if (gtname == NULL) - gtname = Z_StrDup("Unnamed gametype"); - // Add the new gametype newgametype = Z_Calloc(sizeof (gametype_t), PU_STATIC, NULL); if (!newgametype) From 3ee8713e46355566931e50762b27efb755e757fb Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 30 Dec 2022 15:23:26 +0000 Subject: [PATCH 033/128] Dehardcode menu gametype selection, part 1 - Introduce `menugametype` - Controlled by IT_KEYHANDLER/M_HandleMenuGametype - Excludes gametypes that do not support multiplayer by default - GTR_CAPSULES and GTR_BOSS for now, but also user-specifiable GTR_NOMP - Remove gametype_cons_t and G_UpdateGametypeSelections, an obstacle in the way of infinitely allocatable custom gametypes --- src/d_netcmd.c | 2 +- src/deh_soc.c | 3 -- src/discord.c | 2 +- src/doomstat.h | 3 ++ src/g_game.c | 17 --------- src/g_game.h | 1 - src/hu_stuff.c | 2 +- src/k_menu.h | 4 +-- src/k_menudef.c | 4 +-- src/k_menudraw.c | 14 ++++++++ src/k_menufunc.c | 91 +++++++++++++++++++++++++++++++++-------------- src/lua_baselib.c | 3 -- 12 files changed, 89 insertions(+), 57 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 0d4a8da9d..2cdb92bd2 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -3091,7 +3091,7 @@ static void Command_RandomMap(void) } else { - newgametype = cv_dummygametype.value; // Changed from cv_newgametype to match newmenus + newgametype = menugametype; newencoremode = false; newresetplayers = true; oldmapnum = -1; diff --git a/src/deh_soc.c b/src/deh_soc.c index 241ddb8eb..930ace846 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -920,9 +920,6 @@ void readgametype(MYFILE *f, char *gtname) gametypes[numgametypes++] = newgametype; - // Update gametype_cons_t accordingly. - G_UpdateGametypeSelections(); - CONS_Printf("Added gametype %s\n", gtname); } diff --git a/src/discord.c b/src/discord.c index b5c6a5a88..9d6011c11 100644 --- a/src/discord.c +++ b/src/discord.c @@ -499,7 +499,7 @@ void DRPC_UpdatePresence(void) else { snprintf(detailstr, 48, "%s%s%s", - gametype_cons_t[gametype].strvalue, + gametypes[gametype]->name, (gametyperules & GTR_CIRCUIT) ? va(" | %s", kartspeed_cons_t[gamespeed].strvalue) : "", (encoremode == true) ? " | Encore" : "" ); diff --git a/src/doomstat.h b/src/doomstat.h index e7ff2f093..7520342a9 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -519,11 +519,14 @@ enum GameTypeRules GTR_CLOSERPLAYERS = 1<<21, // Buffs spindash and draft power to bring everyone together, nerfs invincibility and grow to prevent excessive combos GTR_ENCORE = 1<<22, // Alternate Encore mirroring, scripting, and texture remapping GTR_SPECIALSTART = 1<<23, // White fade instant start + GTR_NOMP = 1<<24, // No multiplayer // free: to and including 1<<31 }; // Remember to update GAMETYPERULE_LIST in deh_soc.c +#define GTR_FORBIDMP (GTR_NOMP|GTR_CATCHER|GTR_BOSS) + // TODO: replace every instance #define gametyperules (gametypes[gametype]->rules) diff --git a/src/g_game.c b/src/g_game.c index 8ba6a4aa5..ac0190c4e 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3186,23 +3186,6 @@ char *G_PrepareGametypeConstant(const char *newgtconst) return gtconst; } -// -// G_UpdateGametypeSelections -// -// Updates gametype_cons_t. -// -void G_UpdateGametypeSelections(void) -{ - INT32 i; - for (i = 0; i < numgametypes; i++) - { - gametype_cons_t[i].value = i; - gametype_cons_t[i].strvalue = gametypes[i]->name; - } - gametype_cons_t[numgametypes].value = 0; - gametype_cons_t[numgametypes].strvalue = NULL; -} - tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = { {"RACE",TOL_RACE}, {"BATTLE",TOL_BATTLE}, diff --git a/src/g_game.h b/src/g_game.h index 5675da0dd..227274590 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -181,7 +181,6 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives); void G_SetGametype(INT16 gametype); char *G_PrepareGametypeConstant(const char *newgtconst); -void G_UpdateGametypeSelections(void); void G_AddTOL(UINT32 newtol, const char *tolname); INT32 G_GetGametypeByName(const char *gametypestr); INT32 G_GuessGametypeByTOL(UINT32 tol); diff --git a/src/hu_stuff.c b/src/hu_stuff.c index 2a75663e2..dd682e1bd 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -16,7 +16,7 @@ #include "hu_stuff.h" #include "font.h" -#include "k_menu.h" // gametype_cons_t +#include "k_menu.h" // highlightflags #include "m_cond.h" // emblems #include "m_misc.h" // word jumping diff --git a/src/k_menu.h b/src/k_menu.h index 832e2146e..4dd5db602 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -115,7 +115,8 @@ struct menucolor_t { extern menucolor_t *menucolorhead, *menucolortail; -extern CV_PossibleValue_t gametype_cons_t[]; +extern INT16 menugametype; +void M_HandleMenuGametype(INT32 choice); // // MENU TYPEDEFS @@ -770,7 +771,6 @@ void M_MPOptSelect(INT32 choice); void M_MPOptSelectInit(INT32 choice); void M_MPOptSelectTick(void); boolean M_MPResetOpts(void); -extern consvar_t cv_dummygametype; // lazy hack to allow us to select the GT on the server host submenu extern consvar_t cv_dummyip; // I HAVE // HAVE YOUR IP ADDRESS (This just the hack Cvar we'll type into and then it apends itself to "connect" in the console for IP join) diff --git a/src/k_menudef.c b/src/k_menudef.c index b0de23761..374c44968 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -362,8 +362,8 @@ menuitem_t PLAY_MP_Host[] = {IT_STRING | IT_CVAR, "Max. Players", "Set how many players can play at once. Others will spectate.", NULL, {.cvar = &cv_maxplayers}, 0, 0}, - {IT_STRING | IT_CVAR, "Gamemode", "Are we racing? Or perhaps battling?", - NULL, {.cvar = &cv_dummygametype}, 0, 0}, + {IT_STRING | IT_KEYHANDLER, "Gamemode", "Choose the type of play on your server.", + NULL, {.routine = M_HandleMenuGametype}, 0, 0}, {IT_STRING | IT_CALL, "GO", "Select a map with the currently selected gamemode", NULL, {.routine = M_MPSetupNetgameMapSelect}, 0, 0}, diff --git a/src/k_menudraw.c b/src/k_menudraw.c index f47689f8b..4087fdd7a 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2466,6 +2466,20 @@ void M_DrawMPHost(void) } break; } + case IT_KEYHANDLER: + { + if (currentMenu->menuitems[i].itemaction.routine != M_HandleMenuGametype) + break; + + w = V_ThinStringWidth(gametypes[menugametype]->name, V_6WIDTHSPACE); + V_DrawThinString(xp + 138 - w, yp, highlightflags|V_6WIDTHSPACE, gametypes[menugametype]->name); + if (i == itemOn) + { + V_DrawCharacter(xp + 138 - 10 - w - (skullAnimCounter/5), yp, '\x1C' | highlightflags, false); // left arrow + V_DrawCharacter(xp + 138 + 2 + (skullAnimCounter/5), yp, '\x1D' | highlightflags, false); // right arrow + } + break; + } } xp += 5; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index abd3baa99..0cd21d572 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -147,10 +147,6 @@ consvar_t cv_menujam_update = CVAR_INIT ("menujam_update", "Off", CV_SAVE, CV_On static CV_PossibleValue_t menujam_cons_t[] = {{0, "menu"}, {1, "menu2"}, {2, "menu3"}, {0, NULL}}; static consvar_t cv_menujam = CVAR_INIT ("menujam", "0", CV_SAVE, menujam_cons_t, NULL); -// This gametype list is integral for many different reasons. -// When you add gametypes here, don't forget to update them in dehacked.c and doomstat.h! -CV_PossibleValue_t gametype_cons_t[MAXGAMETYPES+1]; - static CV_PossibleValue_t serversort_cons_t[] = { {0,"Ping"}, {1,"AVG. Power Level"}, @@ -189,18 +185,18 @@ static CV_PossibleValue_t dummyteam_cons_t[] = {{0, "Spectator"}, {1, "Red"}, {2 static CV_PossibleValue_t dummyspectate_cons_t[] = {{0, "Spectator"}, {1, "Playing"}, {0, NULL}}; static CV_PossibleValue_t dummyscramble_cons_t[] = {{0, "Random"}, {1, "Points"}, {0, NULL}}; static CV_PossibleValue_t dummystaff_cons_t[] = {{0, "MIN"}, {100, "MAX"}, {0, NULL}}; -static CV_PossibleValue_t dummygametype_cons_t[] = {{0, "Race"}, {1, "Battle"}, {0, NULL}}; //static consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); static consvar_t cv_dummyteam = CVAR_INIT ("dummyteam", "Spectator", CV_HIDDEN, dummyteam_cons_t, NULL); //static cv_dummyspectate = CVAR_INITconsvar_t ("dummyspectate", "Spectator", CV_HIDDEN, dummyspectate_cons_t, NULL); static consvar_t cv_dummyscramble = CVAR_INIT ("dummyscramble", "Random", CV_HIDDEN, dummyscramble_cons_t, NULL); static consvar_t cv_dummystaff = CVAR_INIT ("dummystaff", "0", CV_HIDDEN|CV_CALL, dummystaff_cons_t, Dummystaff_OnChange); -consvar_t cv_dummygametype = CVAR_INIT ("dummygametype", "Race", CV_HIDDEN, dummygametype_cons_t, NULL); consvar_t cv_dummyip = CVAR_INIT ("dummyip", "", CV_HIDDEN, NULL, NULL); consvar_t cv_dummymenuplayer = CVAR_INIT ("dummymenuplayer", "P1", CV_HIDDEN|CV_CALL, dummymenuplayer_cons_t, Dummymenuplayer_OnChange); consvar_t cv_dummyspectate = CVAR_INIT ("dummyspectate", "Spectator", CV_HIDDEN, dummyspectate_cons_t, NULL); +INT16 menugametype = GT_RACE; + consvar_t cv_dummyprofilename = CVAR_INIT ("dummyprofilename", "", CV_HIDDEN, NULL, NULL); consvar_t cv_dummyprofileplayername = CVAR_INIT ("dummyprofileplayername", "", CV_HIDDEN, NULL, NULL); consvar_t cv_dummyprofilekickstart = CVAR_INIT ("dummyprofilekickstart", "Off", CV_HIDDEN, CV_OnOff, NULL); @@ -1715,7 +1711,6 @@ void M_Init(void) CV_RegisterVar(&cv_dummyspectate); CV_RegisterVar(&cv_dummyscramble); CV_RegisterVar(&cv_dummystaff); - CV_RegisterVar(&cv_dummygametype); CV_RegisterVar(&cv_dummyip); CV_RegisterVar(&cv_dummyprofilename); @@ -4167,10 +4162,70 @@ void M_MPHostInit(INT32 choice) itemOn = mhost_go; } +void M_HandleMenuGametype(INT32 choice) +{ + const UINT8 pid = 0; + const INT16 currentmenugametype = menugametype; + UINT32 forbidden = GTR_FORBIDMP; + + (void)choice; + + if (currentMenu->menuitems[itemOn].mvar1 != 0) + forbidden = currentMenu->menuitems[itemOn].mvar1; + + if (menucmd[pid].dpad_lr > 0 || M_MenuConfirmPressed(pid)) + { + do + { + menugametype++; + if (menugametype >= numgametypes) + menugametype = 0; + + if (!(gametypes[menugametype]->rules & forbidden)) + break; + } while (menugametype != currentmenugametype); + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_lr < 0) + { + do + { + if (menugametype == 0) + menugametype = numgametypes; + menugametype--; + + if (!(gametypes[menugametype]->rules & forbidden)) + break; + } while (menugametype != currentmenugametype); + + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (M_MenuBackPressed(pid)) + { + M_GoBack(0); + M_SetMenuDelay(pid); + return; + } + + if (menucmd[pid].dpad_ud > 0) + { + M_NextOpt(); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_ud < 0) + { + M_PrevOpt(); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } +} + void M_MPSetupNetgameMapSelect(INT32 choice) { - - INT16 gt = GT_RACE; (void)choice; // Yep, we'll be starting a netgame. @@ -4181,26 +4236,10 @@ void M_MPSetupNetgameMapSelect(INT32 choice) levellist.levelsearch.checklocked = true; cupgrid.grandprix = false; - // In case we ever want to add new gamemodes there somehow, have at it! - switch (cv_dummygametype.value) - { - case 1: // Battle - { - gt = GT_BATTLE; - break; - } - - default: - { - gt = GT_RACE; - break; - } - } - // okay this is REALLY stupid but this fixes the host menu re-folding on itself when we go back. mpmenu.modewinextend[0][0] = 1; - M_LevelListFromGametype(gt); // Setup the level select. + M_LevelListFromGametype(menugametype); // Setup the level select. // (This will also automatically send us to the apropriate menu) } diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 1a33438fb..54f3bacf1 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -3080,9 +3080,6 @@ static int lib_gAddGametype(lua_State *L) gametypes[numgametypes++] = newgametype; - // Update gametype_cons_t accordingly. - G_UpdateGametypeSelections(); - // done CONS_Printf("Added gametype %s\n", gtname); return 0; From ebb2a79666d037cc1b12626296c5c09ab7d729e1 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 30 Dec 2022 20:34:35 +0000 Subject: [PATCH 034/128] Dehardcode menu gametype selection, part 2 - Add "Gametype" toggle option to pause menu for admins - A/Confirm button on any gametype other than current to do random map in new gametype - C/Extra button to return to current gametype - A/Confirm button on current gametype does a funny noise and nothing else right now, idk what to do - `randomlevel` supports the same `-gametype` parameter as `map` - Both `randomlevel` and `map`'s `-gametype` parameter prohibits `GTR_FORBIDMP` mask gametype changes in netgames - `randomlevel` properly sets encore based on `cv_kartencore`'s value (and permitting `GTR_ENCORE`) - Split out `menugametype` change functions - `yellowmap` now remaps all intermediary shades of grey to match funny pause menu remaps --- src/console.c | 15 ++++++ src/d_netcmd.c | 90 +++++++++++++++++++++++++++++------- src/k_menu.h | 4 +- src/k_menudef.c | 5 +- src/k_menudraw.c | 26 ++++++++--- src/k_menufunc.c | 118 ++++++++++++++++++++++++++++++++--------------- 6 files changed, 196 insertions(+), 62 deletions(-) diff --git a/src/console.c b/src/console.c index ede2f633a..10baa4254 100644 --- a/src/console.c +++ b/src/console.c @@ -358,7 +358,22 @@ static void CON_SetupColormaps(void) *memorysrc = (UINT8)(i & 0xFF); // remap each color to itself... purplemap[0] = (UINT8)163; + yellowmap[0] = (UINT8)73; + yellowmap[1] = (UINT8)73; + yellowmap[3] = (UINT8)74; + yellowmap[6] = (UINT8)74; + yellowmap[7] = (UINT8)190; + yellowmap[8] = (UINT8)190; + yellowmap[10] = (UINT8)190; + yellowmap[12] = (UINT8)190; + yellowmap[14] = (UINT8)149; + yellowmap[15] = (UINT8)149; + yellowmap[16] = (UINT8)149; + yellowmap[21] = (UINT8)152; + yellowmap[23] = (UINT8)173; + yellowmap[24] = (UINT8)167; + greenmap[0] = (UINT8)98; bluemap[0] = (UINT8)148; redmap[0] = (UINT8)34; // battle diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 2cdb92bd2..e7a343a03 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2739,15 +2739,6 @@ static void Command_Map_f(void) if (option_gametype) { -#if 0 - if (!multiplayer) - { - CONS_Printf(M_GetText( - "You can't switch gametypes in single player!\n")); - return; - } - else -#endif //#if 0 if (COM_Argc() < option_gametype + 2)/* no argument after? */ { CONS_Alert(CONS_ERROR, @@ -2823,13 +2814,23 @@ static void Command_Map_f(void) else { CONS_Alert(CONS_ERROR, - "'%s' is not a gametype.\n", + "'%s' is not a valid gametype.\n", gametypename); Z_Free(realmapname); Z_Free(mapname); return; } } + + if (Playing() && netgame && (gametypes[newgametype]->rules & GTR_FORBIDMP)) + { + CONS_Alert(CONS_ERROR, + "'%s' is not a net-compatible gametype.\n", + gametypename); + Z_Free(realmapname); + Z_Free(mapname); + return; + } } else if (!Playing()) { @@ -3061,9 +3062,10 @@ static void Command_RandomMap(void) { INT32 oldmapnum; INT32 newmapnum; - INT32 newgametype; - boolean newencoremode; + INT32 newgametype = (Playing() ? gametype : menugametype); + boolean newencore = false; boolean newresetplayers; + size_t option_gametype; if (client && !IsPlayerAdmin(consoleplayer)) { @@ -3071,13 +3073,69 @@ static void Command_RandomMap(void) return; } + if ((option_gametype = COM_CheckPartialParm("-g"))) + { + const char *gametypename; + + if (COM_Argc() < option_gametype + 2)/* no argument after? */ + { + CONS_Alert(CONS_ERROR, + "No gametype name follows parameter '%s'.\n", + COM_Argv(option_gametype)); + return; + } + + // new gametype value + // use current one by default + gametypename = COM_Argv(option_gametype + 1); + + newgametype = G_GetGametypeByName(gametypename); + + if (newgametype == -1) // reached end of the list with no match + { + /* Did they give us a gametype number? That's okay too! */ + if (isdigit(gametypename[0])) + { + INT16 d = atoi(gametypename); + if (d >= 0 && d < numgametypes) + newgametype = d; + else + { + CONS_Alert(CONS_ERROR, + "Gametype number %d is out of range. Use a number between" + " 0 and %d inclusive. ...Or just use the name. :v\n", + d, + numgametypes-1); + return; + } + } + else + { + CONS_Alert(CONS_ERROR, + "'%s' is not a valid gametype.\n", + gametypename); + return; + } + } + + if (Playing() && netgame && (gametypes[newgametype]->rules & GTR_FORBIDMP)) + { + CONS_Alert(CONS_ERROR, + "'%s' is not a net-compatible gametype.\n", + gametypename); + return; + } + } + // TODO: Handle singleplayer conditions. // The existing ones are way too annoyingly complicated and "anti-cheat" for my tastes. if (Playing()) { - newgametype = gametype; - newencoremode = encoremode; + if (cv_kartencore.value == 1 && (gametypes[newgametype]->rules & GTR_ENCORE)) + { + newencore = true; + } newresetplayers = false; if (gamestate == GS_LEVEL) @@ -3091,14 +3149,12 @@ static void Command_RandomMap(void) } else { - newgametype = menugametype; - newencoremode = false; newresetplayers = true; oldmapnum = -1; } newmapnum = G_RandMap(G_TOLFlag(newgametype), oldmapnum, 0, 0, false, NULL) + 1; - D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, false); + D_MapChange(newmapnum, newgametype, newencore, newresetplayers, 0, false, false); } static void Command_RestartLevel(void) diff --git a/src/k_menu.h b/src/k_menu.h index 4dd5db602..9382c8647 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -116,7 +116,8 @@ struct menucolor_t { extern menucolor_t *menucolorhead, *menucolortail; extern INT16 menugametype; -void M_HandleMenuGametype(INT32 choice); +void M_HandleHostMenuGametype(INT32 choice); +void M_HandlePauseMenuGametype(INT32 choice); // // MENU TYPEDEFS @@ -414,6 +415,7 @@ extern menu_t MISC_StatisticsDef; typedef enum { mpause_addons = 0, + mpause_changegametype, mpause_switchmap, mpause_restartmap, mpause_tryagain, diff --git a/src/k_menudef.c b/src/k_menudef.c index 374c44968..74260d6e4 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -363,7 +363,7 @@ menuitem_t PLAY_MP_Host[] = NULL, {.cvar = &cv_maxplayers}, 0, 0}, {IT_STRING | IT_KEYHANDLER, "Gamemode", "Choose the type of play on your server.", - NULL, {.routine = M_HandleMenuGametype}, 0, 0}, + NULL, {.routine = M_HandleHostMenuGametype}, 0, 0}, {IT_STRING | IT_CALL, "GO", "Select a map with the currently selected gamemode", NULL, {.routine = M_MPSetupNetgameMapSelect}, 0, 0}, @@ -1593,6 +1593,9 @@ menuitem_t PAUSE_Main[] = {IT_STRING | IT_CALL, "ADDONS", "M_ICOADD", NULL, {.routine = M_Addons}, 0, 0}, + {IT_STRING | IT_KEYHANDLER, "GAMETYPE", "M_ICOGAM", + NULL, {.routine = M_HandlePauseMenuGametype}, 0, 0}, + {IT_STRING | IT_SUBMENU, "CHANGE MAP", "M_ICOMAP", NULL, {.submenu = &PAUSE_GamemodesDef}, 0, 0}, diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 4087fdd7a..b872c768d 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -2468,7 +2468,7 @@ void M_DrawMPHost(void) } case IT_KEYHANDLER: { - if (currentMenu->menuitems[i].itemaction.routine != M_HandleMenuGametype) + if (currentMenu->menuitems[i].itemaction.routine != M_HandleHostMenuGametype) break; w = V_ThinStringWidth(gametypes[menugametype]->name, V_6WIDTHSPACE); @@ -3778,12 +3778,26 @@ void M_DrawPause(void) word1[word1len] = '\0'; word2[word2len] = '\0'; - // If there's no 2nd word, take this opportunity to center this line of text. - if (word1len) - V_DrawCenteredLSTitleHighString(220 + offset*2, 75 + (!word2len ? 10 : 0), 0, word1); + if (itemOn == mpause_changegametype) + { + INT32 w = V_LSTitleLowStringWidth(gametypes[menugametype]->name, 0)/2; - if (word2len) - V_DrawCenteredLSTitleLowString(220 + offset*2, 103, 0, word2); + if (word1len) + V_DrawCenteredLSTitleHighString(220 + offset*2, 75, 0, word1); + + V_DrawLSTitleLowString(220-w + offset*2, 103, V_YELLOWMAP, gametypes[menugametype]->name); + V_DrawCharacter(220-w + offset*2 - 8 - (skullAnimCounter/5), 103+6, '\x1C' | V_YELLOWMAP, false); // left arrow + V_DrawCharacter(220+w + offset*2 + 4 + (skullAnimCounter/5), 103+6, '\x1D' | V_YELLOWMAP, false); // right arrow + } + else + { + // If there's no 2nd word, take this opportunity to center this line of text. + if (word1len) + V_DrawCenteredLSTitleHighString(220 + offset*2, 75 + (!word2len ? 10 : 0), 0, word1); + + if (word2len) + V_DrawCenteredLSTitleLowString(220 + offset*2, 103, 0, word2); + } } tic_t playback_last_menu_interaction_leveltime = 0; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 0cd21d572..759e35de9 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -4162,10 +4162,37 @@ void M_MPHostInit(INT32 choice) itemOn = mhost_go; } -void M_HandleMenuGametype(INT32 choice) +static void M_NextMenuGametype(UINT32 forbidden) +{ + const INT16 currentmenugametype = menugametype; + do + { + menugametype++; + if (menugametype >= numgametypes) + menugametype = 0; + + if (!(gametypes[menugametype]->rules & forbidden)) + break; + } while (menugametype != currentmenugametype); +} + +static void M_PrevMenuGametype(UINT32 forbidden) +{ + const INT16 currentmenugametype = menugametype; + do + { + if (menugametype == 0) + menugametype = numgametypes; + menugametype--; + + if (!(gametypes[menugametype]->rules & forbidden)) + break; + } while (menugametype != currentmenugametype); +} + +void M_HandleHostMenuGametype(INT32 choice) { const UINT8 pid = 0; - const INT16 currentmenugametype = menugametype; UINT32 forbidden = GTR_FORBIDMP; (void)choice; @@ -4173,42 +4200,24 @@ void M_HandleMenuGametype(INT32 choice) if (currentMenu->menuitems[itemOn].mvar1 != 0) forbidden = currentMenu->menuitems[itemOn].mvar1; - if (menucmd[pid].dpad_lr > 0 || M_MenuConfirmPressed(pid)) + if (M_MenuBackPressed(pid)) { - do - { - menugametype++; - if (menugametype >= numgametypes) - menugametype = 0; - - if (!(gametypes[menugametype]->rules & forbidden)) - break; - } while (menugametype != currentmenugametype); - + M_GoBack(0); + M_SetMenuDelay(pid); + return; + } + else if (menucmd[pid].dpad_lr > 0 || M_MenuConfirmPressed(pid)) + { + M_NextMenuGametype(forbidden); S_StartSound(NULL, sfx_s3k5b); M_SetMenuDelay(pid); } else if (menucmd[pid].dpad_lr < 0) { - do - { - if (menugametype == 0) - menugametype = numgametypes; - menugametype--; - - if (!(gametypes[menugametype]->rules & forbidden)) - break; - } while (menugametype != currentmenugametype); - + M_PrevMenuGametype(forbidden); S_StartSound(NULL, sfx_s3k5b); M_SetMenuDelay(pid); } - else if (M_MenuBackPressed(pid)) - { - M_GoBack(0); - M_SetMenuDelay(pid); - return; - } if (menucmd[pid].dpad_ud > 0) { @@ -6150,6 +6159,7 @@ void M_OpenPauseMenu(void) // By default, disable anything sensitive: PAUSE_Main[mpause_addons].status = IT_DISABLED; + PAUSE_Main[mpause_changegametype].status = IT_DISABLED; PAUSE_Main[mpause_switchmap].status = IT_DISABLED; PAUSE_Main[mpause_restartmap].status = IT_DISABLED; PAUSE_Main[mpause_tryagain].status = IT_DISABLED; @@ -6171,14 +6181,8 @@ void M_OpenPauseMenu(void) if (server || IsPlayerAdmin(consoleplayer)) { - PAUSE_Main[mpause_switchmap].status = IT_STRING | IT_SUBMENU; - for (i = 0; i < PAUSE_GamemodesDef.numitems; i++) - { - if (PAUSE_GamemodesMenu[i].mvar2 != gametype) - continue; - PAUSE_GamemodesDef.lastOn = i; - break; - } + PAUSE_Main[mpause_changegametype].status = IT_STRING | IT_KEYHANDLER; + PAUSE_Main[mpause_switchmap].status = IT_STRING | IT_CALL; PAUSE_Main[mpause_restartmap].status = IT_STRING | IT_CALL; PAUSE_Main[mpause_addons].status = IT_STRING | IT_CALL; } @@ -6279,6 +6283,46 @@ boolean M_PauseInputs(INT32 ch) return false; } +// Change gametype +void M_HandlePauseMenuGametype(INT32 choice) +{ + const UINT8 pid = 0; + UINT32 forbidden = GTR_FORBIDMP; + + (void)choice; + + if (M_MenuConfirmPressed(pid)) + { + if (menugametype != gametype) + { + M_ClearMenus(true); + COM_ImmedExecute(va("randommap -gt %s", gametypes[menugametype]->name)); + return; + } + + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k7b); + } + else if (M_MenuExtraPressed(pid)) + { + menugametype = gametype; + M_SetMenuDelay(pid); + S_StartSound(NULL, sfx_s3k7b); + } + else if (menucmd[pid].dpad_lr > 0) + { + M_NextMenuGametype(forbidden); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } + else if (menucmd[pid].dpad_lr < 0) + { + M_PrevMenuGametype(forbidden); + S_StartSound(NULL, sfx_s3k5b); + M_SetMenuDelay(pid); + } +} + // Restart map void M_RestartMap(INT32 choice) { From be49e99a9e909366a99ef3aa2ae6959d5dd520d7 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 30 Dec 2022 23:37:29 +0000 Subject: [PATCH 035/128] Guarantee `menugametype`'s value is good before it's relevant --- src/k_menu.h | 2 ++ src/k_menufunc.c | 18 +++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 9382c8647..6db215834 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -116,6 +116,8 @@ struct menucolor_t { extern menucolor_t *menucolorhead, *menucolortail; extern INT16 menugametype; +void M_NextMenuGametype(UINT32 forbidden); +void M_PrevMenuGametype(UINT32 forbidden); void M_HandleHostMenuGametype(INT32 choice); void M_HandlePauseMenuGametype(INT32 choice); diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 759e35de9..120d39f3c 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -4121,6 +4121,7 @@ void M_MPOptSelectInit(INT32 choice) { INT16 arrcpy[3][3] = {{0,68,0}, {0,12,0}, {0,74,0}}; UINT8 i = 0, j = 0; // To copy the array into the struct + const UINT32 forbidden = GTR_FORBIDMP; (void)choice; @@ -4131,6 +4132,10 @@ void M_MPOptSelectInit(INT32 choice) for (j = 0; j < 3; j++) mpmenu.modewinextend[i][j] = arrcpy[i][j]; // I miss Lua already + // Guarantee menugametype is good + M_NextMenuGametype(forbidden); + M_PrevMenuGametype(forbidden); + M_SetupNextMenu(&PLAY_MP_OptSelectDef, false); } @@ -4162,7 +4167,7 @@ void M_MPHostInit(INT32 choice) itemOn = mhost_go; } -static void M_NextMenuGametype(UINT32 forbidden) +void M_NextMenuGametype(UINT32 forbidden) { const INT16 currentmenugametype = menugametype; do @@ -4176,7 +4181,7 @@ static void M_NextMenuGametype(UINT32 forbidden) } while (menugametype != currentmenugametype); } -static void M_PrevMenuGametype(UINT32 forbidden) +void M_PrevMenuGametype(UINT32 forbidden) { const INT16 currentmenugametype = menugametype; do @@ -4193,13 +4198,10 @@ static void M_PrevMenuGametype(UINT32 forbidden) void M_HandleHostMenuGametype(INT32 choice) { const UINT8 pid = 0; - UINT32 forbidden = GTR_FORBIDMP; + const UINT32 forbidden = GTR_FORBIDMP; (void)choice; - if (currentMenu->menuitems[itemOn].mvar1 != 0) - forbidden = currentMenu->menuitems[itemOn].mvar1; - if (M_MenuBackPressed(pid)) { M_GoBack(0); @@ -6182,6 +6184,8 @@ void M_OpenPauseMenu(void) if (server || IsPlayerAdmin(consoleplayer)) { PAUSE_Main[mpause_changegametype].status = IT_STRING | IT_KEYHANDLER; + menugametype = gametype; + PAUSE_Main[mpause_switchmap].status = IT_STRING | IT_CALL; PAUSE_Main[mpause_restartmap].status = IT_STRING | IT_CALL; PAUSE_Main[mpause_addons].status = IT_STRING | IT_CALL; @@ -6287,7 +6291,7 @@ boolean M_PauseInputs(INT32 ch) void M_HandlePauseMenuGametype(INT32 choice) { const UINT8 pid = 0; - UINT32 forbidden = GTR_FORBIDMP; + const UINT32 forbidden = GTR_FORBIDMP; (void)choice; From b09045f6578d2fb749da2f3f20df41bb8cb3bc93 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 30 Dec 2022 23:58:22 +0000 Subject: [PATCH 036/128] Dehardcode menu gametype selection, part 3 Multiplayer map select now uses the value of `menugametype` accessible just above it on the menu, instead of having to select from a hardcoded set of options --- src/k_menu.h | 3 --- src/k_menudef.c | 18 ++---------------- src/k_menufunc.c | 9 ++++++++- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 6db215834..2ac8ecf35 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -436,9 +436,6 @@ typedef enum mpause_title, } mpause_e; -extern menuitem_t PAUSE_GamemodesMenu[]; -extern menu_t PAUSE_GamemodesDef; - extern menuitem_t PAUSE_PlaybackMenu[]; extern menu_t PAUSE_PlaybackMenuDef; diff --git a/src/k_menudef.c b/src/k_menudef.c index 74260d6e4..a4b8de7bd 100644 --- a/src/k_menudef.c +++ b/src/k_menudef.c @@ -1596,8 +1596,8 @@ menuitem_t PAUSE_Main[] = {IT_STRING | IT_KEYHANDLER, "GAMETYPE", "M_ICOGAM", NULL, {.routine = M_HandlePauseMenuGametype}, 0, 0}, - {IT_STRING | IT_SUBMENU, "CHANGE MAP", "M_ICOMAP", - NULL, {.submenu = &PAUSE_GamemodesDef}, 0, 0}, + {IT_STRING | IT_CALL, "CHANGE MAP", "M_ICOMAP", + NULL, {.routine = M_LevelSelectInit}, 0, -1}, {IT_STRING | IT_CALL, "RESTART MAP", "M_ICORE", NULL, {.routine = M_RestartMap}, 0, 0}, @@ -1650,20 +1650,6 @@ menu_t PAUSE_MainDef = { M_PauseInputs }; -// PAUSE : Map switching gametype selection (In case you want to pick from battle / race...) -menuitem_t PAUSE_GamemodesMenu[] = -{ - {IT_STRING | IT_CALL, "Race", "Select which gamemode to choose a new map from.", - NULL, {.routine = M_LevelSelectInit}, 0, GT_RACE}, - - {IT_STRING | IT_CALL, "Battle", "Select which gamemode to choose a new map from.", - NULL, {.routine = M_LevelSelectInit}, 0, GT_BATTLE}, - - {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, -}; - -menu_t PAUSE_GamemodesDef = KARTGAMEMODEMENU(PAUSE_GamemodesMenu, &PAUSE_MainDef); - // Replay popup menu menuitem_t PAUSE_PlaybackMenu[] = { diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 120d39f3c..a421ba8a5 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3676,6 +3676,8 @@ static void M_LevelListFromGametype(INT16 gt) void M_LevelSelectInit(INT32 choice) { + INT32 gt = currentMenu->menuitems[itemOn].mvar2; + (void)choice; // Make sure this is reset as we'll only be using this function for offline games! @@ -3702,7 +3704,12 @@ void M_LevelSelectInit(INT32 choice) return; } - M_LevelListFromGametype(currentMenu->menuitems[itemOn].mvar2); + if (gt == -1) + { + gt = menugametype; + } + + M_LevelListFromGametype(gt); } static void M_LevelSelected(INT16 add) From 21858082d25f72033585d3f1688e3534eede8f85 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 00:01:44 +0000 Subject: [PATCH 037/128] EXPERIMENTAL: Use `yellowmap` for all pause menu icons (except Try Again/Restart Map) instead of custom Will make long-term management of this menu easier, and pushes aside some inconsistencies between graphics May be reverted in part or full if it doesn't work for particular assets --- src/k_menudraw.c | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index b872c768d..8ef321bbe 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -3698,27 +3698,19 @@ void M_DrawPause(void) case IT_STRING: { patch_t *pp; + UINT8 *colormap = NULL; - if (i == itemOn) + if (i == itemOn && (i == mpause_restartmap || i == mpause_tryagain)) { - if (i == mpause_restartmap || i == mpause_tryagain) - { - pp = W_CachePatchName( - va("M_ICOR2%c", ('A'+(pausemenu.ticker & 1))), - PU_CACHE); - } - else - { - char iconame[9]; // 8 chars + \0 - strcpy(iconame, currentMenu->menuitems[i].tooltip); - iconame[7] = '2'; // Yes this is a stupid hack. Replace the last character with a 2 when we're selecting this graphic. - - pp = W_CachePatchName(iconame, PU_CACHE); - } + pp = W_CachePatchName( + va("M_ICOR2%c", ('A'+(pausemenu.ticker & 1))), + PU_CACHE); } else { pp = W_CachePatchName(currentMenu->menuitems[i].tooltip, PU_CACHE); + if (i == itemOn) + colormap = yellowmap; } // 294 - 261 = 33 @@ -3729,7 +3721,7 @@ void M_DrawPause(void) // This double ternary is awful, yes. dypos = ypos + pausemenu.offset; - V_DrawFixedPatch( ((i == itemOn ? (294 - pausemenu.offset*2/3 * (dypos > 100 ? 1 : -1)) : 261) + offset) << FRACBITS, (dypos)*FRACUNIT, FRACUNIT, 0, pp, NULL); + V_DrawFixedPatch( ((i == itemOn ? (294 - pausemenu.offset*2/3 * (dypos > 100 ? 1 : -1)) : 261) + offset) << FRACBITS, (dypos)*FRACUNIT, FRACUNIT, 0, pp, colormap); ypos += 50; itemsdrawn++; // We drew that! From dc7222821cb211173ced7c110feef44194a9094b Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 16:05:04 +0000 Subject: [PATCH 038/128] Move M_Init() to fix saving of some menu-specific cvars --- src/d_main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 07c61d841..8384de07c 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1520,14 +1520,14 @@ void D_SRB2Main(void) I_RegisterSysCommands(); + M_Init(); + //--------------------------------------------------------- CONFIG.CFG M_FirstLoadConfig(); // WARNING : this do a "COM_BufExecute()" // Load Profiles now that default controls have been defined PR_LoadProfiles(); // load control profiles - M_Init(); - #if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL) VID_PrepareModeList(); // Regenerate Modelist according to cv_fullscreen #endif From 2db7562c24f0452979902f13fa789fb94d64cf87 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 16:22:46 +0000 Subject: [PATCH 039/128] LF2_FINISHNEEDED - Replacement for LF2_VISITNEEDED, which was itself a Kart-specific inversion of LF2_NOVISITNEEDED - Prevents selection in time attack before you've beaten the level in another context (GP, or MP if applicable) - Should be used for Sealed Stars (and Adventure Example) exclusively IMO, but usable for others too Related adjustments: - Allow a map to have a `TypeOfLevel` of 0 without error - Change the Condition String for UC_MAPVISITED to "Finish a round on X" (previously "Beat X") --- src/deh_soc.c | 8 ++++---- src/deh_tables.c | 2 +- src/doomstat.h | 8 ++++---- src/k_menufunc.c | 21 +++++++++++++++++++-- src/m_cond.c | 7 +++++-- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/deh_soc.c b/src/deh_soc.c index 930ace846..4fb40b087 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -1140,7 +1140,7 @@ void readlevelheader(MYFILE *f, char * name) } else if (fastcmp(word, "TYPEOFLEVEL")) { - if (i) // it's just a number + if (i || isdigit(word2[0])) // it's just a number mapheaderinfo[num]->typeoflevel = (UINT32)i; else { @@ -1279,12 +1279,12 @@ void readlevelheader(MYFILE *f, char * name) else mapheaderinfo[num]->menuflags &= ~LF2_NOTIMEATTACK; } - else if (fastcmp(word, "VISITNEEDED")) + else if (fastcmp(word, "FINISHNEEDED")) { if (i || word2[0] == 'T' || word2[0] == 'Y') - mapheaderinfo[num]->menuflags |= LF2_VISITNEEDED; + mapheaderinfo[num]->menuflags |= LF2_FINISHNEEDED; else - mapheaderinfo[num]->menuflags &= ~LF2_VISITNEEDED; + mapheaderinfo[num]->menuflags &= ~LF2_FINISHNEEDED; } else if (fastcmp(word, "GRAVITY")) mapheaderinfo[num]->gravity = FLOAT_TO_FIXED(atof(word2)); diff --git a/src/deh_tables.c b/src/deh_tables.c index 8b9be00ff..5217e3c6c 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -6300,7 +6300,7 @@ struct int_const_s const INT_CONST[] = { {"LF2_HIDEINMENU",LF2_HIDEINMENU}, {"LF2_HIDEINSTATS",LF2_HIDEINSTATS}, {"LF2_NOTIMEATTACK",LF2_NOTIMEATTACK}, - {"LF2_VISITNEEDED",LF2_VISITNEEDED}, + {"LF2_FINISHNEEDED",LF2_FINISHNEEDED}, // Emeralds {"EMERALD_CHAOS1",EMERALD_CHAOS1}, diff --git a/src/doomstat.h b/src/doomstat.h index 7520342a9..76ec0cb25 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -440,10 +440,10 @@ struct mapheader_t #define LF_SECTIONRACE (1<<2) ///< Section race level #define LF_SUBTRACTNUM (1<<3) ///< Use subtractive position number (for bright levels) -#define LF2_HIDEINMENU (1<<0) ///< Hide in the multiplayer menu -#define LF2_HIDEINSTATS (1<<1) ///< Hide in the statistics screen -#define LF2_NOTIMEATTACK (1<<2) ///< Hide this map in Time Attack modes -#define LF2_VISITNEEDED (1<<3) ///< Not available in Time Attack modes until you visit the level +#define LF2_HIDEINMENU (1<<0) ///< Hide in the multiplayer menu +#define LF2_HIDEINSTATS (1<<1) ///< Hide in the statistics screen +#define LF2_NOTIMEATTACK (1<<2) ///< Hide this map in Time Attack modes +#define LF2_FINISHNEEDED (1<<3) ///< Not available in Time Attack modes until you beat the level extern mapheader_t** mapheaderinfo; extern INT32 nummapheaders, mapallocsize; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index a421ba8a5..6e7c9e4dd 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3418,8 +3418,18 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) if (mapheaderinfo[mapnum]->lumpnum == LUMPERROR) return false; - if (levelsearch->checklocked && M_MapLocked(mapnum+1)) - return false; // not unlocked + // Does the map have lock conditions? + if (levelsearch->checklocked) + { + // Check for completion + if ((mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED) + && !(mapheaderinfo[mapnum]->mapvisited & MV_BEATEN)) + return false; + + // Check for unlock + if (M_MapLocked(mapnum+1)) + return false; + } // Check for TOL if (!(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) @@ -7688,9 +7698,16 @@ void M_Statistics(INT32 choice) if (!mapheaderinfo[i]) continue; + // Check for no visibility + legacy box if (mapheaderinfo[i]->menuflags & (LF2_NOTIMEATTACK|LF2_HIDEINSTATS|LF2_HIDEINMENU)) continue; + // Check for completion + if ((mapheaderinfo[i]->menuflags & LF2_FINISHNEEDED) + && !(mapheaderinfo[i]->mapvisited & MV_BEATEN)) + continue; + + // Check for unlock if (M_MapLocked(i+1)) continue; diff --git a/src/m_cond.c b/src/m_cond.c index 052f23dc0..14ad2b6b4 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -570,7 +570,10 @@ static char *M_BuildConditionTitle(UINT16 map) { char *title, *ref; - if (M_MapLocked(map+1)) + if (((mapheaderinfo[map]->menuflags & LF2_FINISHNEEDED) + // the following is intentionally not MV_BEATEN, just in case the title is for "Finish a round on X" + && !(mapheaderinfo[map]->mapvisited & MV_VISITED)) + || M_MapLocked(map+1)) return Z_StrDup("???"); title = ref = G_BuildMapTitle(map+1); @@ -629,7 +632,7 @@ static const char *M_GetConditionString(condition_t *cn) title = BUILDCONDITIONTITLE(cn->requirement); work = va("%s %s%s", - (cn->type == UC_MAPVISITED) ? "Visit" : "Beat", + (cn->type == UC_MAPVISITED) ? "Visit" : "Finish a round on", title, (cn->type == UC_MAPENCORE) ? " in Encore Mode" : ""); Z_Free(title); From 36b1f5f4880ded19a6aabc9759172142d63ed750 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 17:50:49 +0000 Subject: [PATCH 040/128] Have a Cup visibly locked if no levels are accessible by the current rules of `menusearch_t` - Solved by leveraging M_GetFirstLevelInList alongside existing M_CountLevelsInList calls - Also optimises the above calls in the M_CupLocked case, since that's no longer being directly used otherwise --- src/k_menudraw.c | 36 ++++++++++++++++++++---------------- src/k_menufunc.c | 42 +++++++++++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 8ef321bbe..6968bde6c 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1933,7 +1933,7 @@ static void M_DrawCupPreview(INT16 y, levelsearch_t *levelsearch) V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); - if (levelsearch->cup && !M_CupLocked(levelsearch->cup) && maxlevels > 0) + if (levelsearch->cup && maxlevels > 0) { add = (cupgrid.previewanim / 82) % maxlevels; map = start; @@ -1979,16 +1979,18 @@ static void M_DrawCupPreview(INT16 y, levelsearch_t *levelsearch) } } -static void M_DrawCupTitle(INT16 y, cupheader_t *cup) +static void M_DrawCupTitle(INT16 y, levelsearch_t *levelsearch) { + UINT8 temp = 0; + V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE)); - if (cup) + if (levelsearch->cup) { - boolean unlocked = !M_CupLocked(cup); + boolean unlocked = (M_GetFirstLevelInList(&temp, levelsearch) != NEXTMAP_INVALID); UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); - patch_t *icon = W_CachePatchName(cup->icon, PU_CACHE); - const char *str = (unlocked ? va("%s Cup", cup->name) : "???"); + patch_t *icon = W_CachePatchName(levelsearch->cup->icon, PU_CACHE); + const char *str = (unlocked ? va("%s Cup", levelsearch->cup->name) : "???"); INT16 offset = V_LSTitleLowStringWidth(str, 0) / 2; V_DrawLSTitleLowString(BASEVIDWIDTH/2 - offset, y+6, 0, str); @@ -2011,26 +2013,26 @@ static void M_DrawCupTitle(INT16 y, cupheader_t *cup) void M_DrawCupSelect(void) { - UINT8 i, j; + UINT8 i, j, temp = 0; levelsearch_t templevelsearch = levellist.levelsearch; // full copy - templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID]; for (i = 0; i < CUPMENU_COLUMNS; i++) { for (j = 0; j < CUPMENU_ROWS; j++) { size_t id = (i + (j * CUPMENU_COLUMNS)) + (cupgrid.pageno * (CUPMENU_COLUMNS * CUPMENU_ROWS)); - cupheader_t *iconcup = cupgrid.builtgrid[id]; patch_t *patch = NULL; INT16 x, y; INT16 icony = 7; - if (!iconcup) + if (!cupgrid.builtgrid[id]) break; - /*if (iconcup->emeraldnum == 0) + templevelsearch.cup = cupgrid.builtgrid[id]; + + /*if (templevelsearch.cup->emeraldnum == 0) patch = W_CachePatchName("CUPMON3A", PU_CACHE); - else*/ if (iconcup->emeraldnum > 7) + else*/ if (templevelsearch.cup->emeraldnum > 7) { patch = W_CachePatchName("CUPMON2A", PU_CACHE); icony = 5; @@ -2043,14 +2045,14 @@ void M_DrawCupSelect(void) V_DrawScaledPatch(x, y, 0, patch); - if (M_CupLocked(iconcup)) + if (M_GetFirstLevelInList(&temp, &templevelsearch) == NEXTMAP_INVALID) { patch_t *st = W_CachePatchName(va("ICONST0%d", (cupgrid.previewanim % 4) + 1), PU_CACHE); V_DrawScaledPatch(x + 8, y + icony, 0, st); } else { - V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName(iconcup->icon, PU_CACHE)); + V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName(templevelsearch.cup->icon, PU_CACHE)); V_DrawScaledPatch(x + 8, y + icony, 0, W_CachePatchName("CUPBOX", PU_CACHE)); } } @@ -2061,8 +2063,10 @@ void M_DrawCupSelect(void) 0, W_CachePatchName("CUPCURS", PU_CACHE) ); + templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID]; + M_DrawCupPreview(146 + (24*menutransition.tics), &templevelsearch); - M_DrawCupTitle(120 - (24*menutransition.tics), templevelsearch.cup); + M_DrawCupTitle(120 - (24*menutransition.tics), &templevelsearch); } static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map) @@ -2228,7 +2232,7 @@ void M_DrawLevelSelect(void) map = M_GetNextLevelInList(map, &j, &levellist.levelsearch); } - M_DrawCupTitle(tay, levellist.levelsearch.cup); + M_DrawCupTitle(tay, &levellist.levelsearch); } void M_DrawTimeAttack(void) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 6e7c9e4dd..2440d2a4d 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3418,19 +3418,6 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) if (mapheaderinfo[mapnum]->lumpnum == LUMPERROR) return false; - // Does the map have lock conditions? - if (levelsearch->checklocked) - { - // Check for completion - if ((mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED) - && !(mapheaderinfo[mapnum]->mapvisited & MV_BEATEN)) - return false; - - // Check for unlock - if (M_MapLocked(mapnum+1)) - return false; - } - // Check for TOL if (!(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) return false; @@ -3449,6 +3436,19 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch) && mapheaderinfo[mapnum]->cup != levelsearch->cup) return false; + // Finally, the most complex check: does the map have lock conditions? + if (levelsearch->checklocked) + { + // Check for completion + if ((mapheaderinfo[mapnum]->menuflags & LF2_FINISHNEEDED) + && !(mapheaderinfo[mapnum]->mapvisited & MV_BEATEN)) + return false; + + // Check for unlock + if (M_MapLocked(mapnum+1)) + return false; + } + // Survived our checks. return true; } @@ -3462,6 +3462,9 @@ UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch) if (levelsearch->cup) { + if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) + return 0; + for (i = 0; i < CUPCACHE_MAX; i++) { if (!M_CanShowLevelInList(levelsearch->cup->cachedlevels[i], levelsearch)) @@ -3488,6 +3491,12 @@ UINT16 M_GetFirstLevelInList(UINT8 *i, levelsearch_t *levelsearch) if (levelsearch->cup) { + if (levelsearch->checklocked && M_CupLocked(levelsearch->cup)) + { + *i = CUPCACHE_MAX; + return NEXTMAP_INVALID; + } + *i = 0; mapnum = NEXTMAP_INVALID; for (; *i < CUPCACHE_MAX; (*i)++) @@ -3555,6 +3564,8 @@ static void M_LevelSelectScrollDest(void) static void M_LevelListFromGametype(INT16 gt) { static boolean first = true; + UINT8 temp = 0; + if (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES) { levellist.newgametype = gt; @@ -3589,7 +3600,6 @@ static void M_LevelListFromGametype(INT16 gt) const size_t unitlen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); templevelsearch.cup = kartcupheaders; - templevelsearch.checklocked = false; // Make sure there's valid cups before going to this menu. if (templevelsearch.cup == NULL) @@ -3612,6 +3622,7 @@ static void M_LevelListFromGametype(INT16 gt) while (templevelsearch.cup) { + templevelsearch.checklocked = false; if (!M_CountLevelsToShowInList(&templevelsearch)) { // No valid maps, skip. @@ -3638,7 +3649,8 @@ static void M_LevelListFromGametype(INT16 gt) cupgrid.builtgrid[currentid] = templevelsearch.cup; - if (!M_CupLocked(templevelsearch.cup)) + templevelsearch.checklocked = true; + if (M_GetFirstLevelInList(&temp, &templevelsearch) != NEXTMAP_INVALID) { highestunlockedid = currentid; if (Playing() && mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->cup == templevelsearch.cup) From 44555be7043308051a4b5ac100e19d64882ce8d4 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 17:57:22 +0000 Subject: [PATCH 041/128] Change the conditions for forced retry at end of round - If modeattacking, never - If Sealed Star, if the UFO still exists or the emerald hasn't been picked up --- src/g_game.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index ac0190c4e..156ba98e2 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2915,17 +2915,24 @@ void G_ExitLevel(void) if (gamestate == GS_LEVEL) { UINT8 i; - boolean youlost = false; - if (gametyperules & GTR_BOSS) + boolean doretry = false; + + if (modeattacking != ATTACKING_NONE) + ; + else if (specialstageinfo.valid == true) { - youlost = true; + doretry = (specialstageinfo.ufo != NULL); + } + else if (gametyperules & GTR_BOSS) + { + doretry = true; for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator && !players[i].bot) { if (players[i].bumpers > 0) { - youlost = false; + doretry = false; break; } } @@ -2933,10 +2940,10 @@ void G_ExitLevel(void) } else if (grandprixinfo.gp == true && grandprixinfo.eventmode == GPEVENT_NONE) { - youlost = (grandprixinfo.wonround != true); + doretry = (grandprixinfo.wonround != true); } - if (youlost) + if (doretry) { // You didn't win... From 39e68d1ae3af924801ca8a3cdb42f88dd5fc05ee Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 18:45:04 +0000 Subject: [PATCH 042/128] Clear UFO pointer if its object was removed (matches up with general mobjthinker P_SetTarget handling) --- src/k_specialstage.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/k_specialstage.c b/src/k_specialstage.c index d5eb172ce..b0defccb9 100644 --- a/src/k_specialstage.c +++ b/src/k_specialstage.c @@ -119,6 +119,11 @@ void K_TickSpecialStage(void) return; } + if (P_MobjWasRemoved(specialstageinfo.ufo)) + { + P_SetTarget(&specialstageinfo.ufo, NULL); + } + K_MoveExitBeam(); } From a86ed5c6cb6f2f947635aec304f2ce9fb02899bc Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 18:59:56 +0000 Subject: [PATCH 043/128] Rework the conditions for the previous commit - The "never force a retry" condition is now `!G_GametypeUsesLives()` - In GT_SPECIAL *and* GT_VERSUS, check for K_IsPlayerLosing before retry, instead of bespoke check --- src/g_game.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 156ba98e2..d87be770c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2917,20 +2917,16 @@ void G_ExitLevel(void) UINT8 i; boolean doretry = false; - if (modeattacking != ATTACKING_NONE) - ; - else if (specialstageinfo.valid == true) - { - doretry = (specialstageinfo.ufo != NULL); - } - else if (gametyperules & GTR_BOSS) + if (!G_GametypeUsesLives()) + ; // never force a retry + else if (specialstageinfo.valid == true || (gametyperules & GTR_BOSS) { doretry = true; for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && !players[i].spectator && !players[i].bot) { - if (players[i].bumpers > 0) + if (!K_IsPlayerLosing(&players[i])) { doretry = false; break; From 5b027ff2feec68328c954ff59abc4dd671dd1970 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 19:00:52 +0000 Subject: [PATCH 044/128] Instead of preventing lives for GTR_CAPSULES, prevent them for it being GPEVENT_BONUS --- src/g_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index d87be770c..c2de77e36 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3227,7 +3227,7 @@ boolean G_GametypeUsesLives(void) return false; if ((grandprixinfo.gp == true) // In Grand Prix - && !(gametyperules & GTR_CAPSULES)) // NOT in Break The Capsules + && grandprixinfo.eventmode != GPEVENT_BONUS) // NOT in bonus round { return true; } From 6b18f869b9e90a1a401453e0325fe2e657d521d3 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 19:02:22 +0000 Subject: [PATCH 045/128] Prevent a special stage win by sneaking ahead of the UFO to the goal line --- src/p_spec.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/p_spec.c b/src/p_spec.c index b85746db6..208e6c04f 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -43,6 +43,7 @@ // SRB2kart #include "k_kart.h" +#include "k_specialstage.h" #include "console.h" // CON_LogMessage #include "k_respawn.h" #include "k_terrain.h" @@ -1925,6 +1926,14 @@ static void K_HandleLapIncrement(player_t *player) // finished race exit setup if (player->laps > numlaps) { + if (specialstageinfo.valid == true) + { + // Don't permit a win just by sneaking ahead of the UFO/emerald. + if (!(specialstageinfo.ufo == NULL || P_MobjWasRemoved(specialstageinfo.ufo))) + { + player->pflags |= PF_NOCONTEST; + } + } P_DoPlayerExit(player); P_SetupSignExit(player); } From c857153c29ea8e709d8c1e1e3771158c769fbcec Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 19:03:13 +0000 Subject: [PATCH 046/128] Unstaged closing brace --- src/g_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index c2de77e36..47e144c42 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2919,7 +2919,7 @@ void G_ExitLevel(void) if (!G_GametypeUsesLives()) ; // never force a retry - else if (specialstageinfo.valid == true || (gametyperules & GTR_BOSS) + else if (specialstageinfo.valid == true || (gametyperules & GTR_BOSS)) { doretry = true; for (i = 0; i < MAXPLAYERS; i++) From 76ea72ceac563527e5298633e7cdbc1308478204 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 20:33:31 +0000 Subject: [PATCH 047/128] Prevent entering the cup or level select if there's no valid maps to pick from. --- src/k_menufunc.c | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 2440d2a4d..29efb0b49 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3561,7 +3561,7 @@ static void M_LevelSelectScrollDest(void) } // Builds the level list we'll be using from the gametype we're choosing and send us to the apropriate menu. -static void M_LevelListFromGametype(INT16 gt) +static boolean M_LevelListFromGametype(INT16 gt) { static boolean first = true; UINT8 temp = 0; @@ -3588,8 +3588,6 @@ static void M_LevelListFromGametype(INT16 gt) first = false; } - PLAY_CupSelectDef.prevMenu = currentMenu; - // Obviously go to Cup Select in gametypes that have cups. // Use a really long level select in gametypes that don't use cups. @@ -3598,12 +3596,15 @@ static void M_LevelListFromGametype(INT16 gt) levelsearch_t templevelsearch = levellist.levelsearch; // full copy size_t currentid = 0, highestunlockedid = 0; const size_t unitlen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); + boolean foundany = false; templevelsearch.cup = kartcupheaders; - // Make sure there's valid cups before going to this menu. +#if 0 + // Make sure there's valid cups before going to this menu. -- rip sweet prince if (templevelsearch.cup == NULL) I_Error("Can you really call this a racing game, I didn't recieve any Cups on my pillow or anything"); +#endif if (!cupgrid.builtgrid) { @@ -3630,6 +3631,8 @@ static void M_LevelListFromGametype(INT16 gt) continue; } + foundany = true; + if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * unitlen) { // Double the size of the buffer, and clear the other stuff. @@ -3665,16 +3668,29 @@ static void M_LevelListFromGametype(INT16 gt) templevelsearch.cup = templevelsearch.cup->next; } + if (foundany == false) + { + return false; + } + cupgrid.numpages = (highestunlockedid / (CUPMENU_COLUMNS * CUPMENU_ROWS)) + 1; if (cupgrid.pageno >= cupgrid.numpages) { cupgrid.pageno = 0; } + PLAY_CupSelectDef.prevMenu = currentMenu; PLAY_LevelSelectDef.prevMenu = &PLAY_CupSelectDef; M_SetupNextMenu(&PLAY_CupSelectDef, false); - return; + return true; + } + + // Okay, just a list of maps then. + + if (M_GetFirstLevelInList(&temp, &levellist.levelsearch) == NEXTMAP_INVALID) + { + return false; } // Reset position properly if you go back & forth between gametypes @@ -3690,6 +3706,7 @@ static void M_LevelListFromGametype(INT16 gt) PLAY_LevelSelectDef.prevMenu = currentMenu; M_SetupNextMenu(&PLAY_LevelSelectDef, false); + return true; } // Init level select for use in local play using the last choice we made. @@ -3731,7 +3748,11 @@ void M_LevelSelectInit(INT32 choice) gt = menugametype; } - M_LevelListFromGametype(gt); + if (!M_LevelListFromGametype(gt)) + { + S_StartSound(NULL, sfx_s3kb2); + M_StartMessage(va("No levels available for\n%s Mode!\n\nPress (B)\n", gametypes[gt]->name), NULL, MM_NOTHING); + } } static void M_LevelSelected(INT16 add) @@ -4279,8 +4300,11 @@ void M_MPSetupNetgameMapSelect(INT32 choice) // okay this is REALLY stupid but this fixes the host menu re-folding on itself when we go back. mpmenu.modewinextend[0][0] = 1; - M_LevelListFromGametype(menugametype); // Setup the level select. - // (This will also automatically send us to the apropriate menu) + if (!M_LevelListFromGametype(menugametype)) + { + S_StartSound(NULL, sfx_s3kb2); + M_StartMessage(va("No levels available for\n%s Mode!\n\nPress (B)\n", gametypes[menugametype]->name), NULL, MM_NOTHING); + } } // MULTIPLAYER JOIN BY IP From e0c4c235dfc23b6f43635a659281f4281b938a65 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 20:34:11 +0000 Subject: [PATCH 048/128] Move "return" in "Are you sure you want to return to the menu?" to the next line, to balance the lines out a little better --- src/k_menufunc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 29efb0b49..03bcfc9d0 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6457,7 +6457,7 @@ void M_EndGame(INT32 choice) if (!Playing()) return; - M_StartMessage(M_GetText("Are you sure you want to return\nto the menu?\nPress (A) to confirm or (B) to cancel\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); + M_StartMessage(M_GetText("Are you sure you want to\nreturn to the menu?\nPress (A) to confirm or (B) to cancel\n"), FUNCPTRCAST(M_ExitGameResponse), MM_YESNO); } From a2c5ff6ac8ed82307c3ea10bff0b1a3d84147af9 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 20:56:00 +0000 Subject: [PATCH 049/128] Remove the duplication of `levellist.netgame` into `cupgrid.netgame` --- src/k_menu.h | 1 - src/k_menufunc.c | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/k_menu.h b/src/k_menu.h index 2ac8ecf35..9b46cc776 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -694,7 +694,6 @@ extern struct cupgrid_s { size_t cappages; tic_t previewanim; boolean grandprix; // Setup grand prix server after picking - boolean netgame; // Start the game in an actual server } cupgrid; typedef struct levelsearch_s { diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 03bcfc9d0..73fab9aaa 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3720,7 +3720,6 @@ void M_LevelSelectInit(INT32 choice) (void)choice; // Make sure this is reset as we'll only be using this function for offline games! - cupgrid.netgame = false; levellist.netgame = false; levellist.levelsearch.checklocked = true; @@ -4291,7 +4290,6 @@ void M_MPSetupNetgameMapSelect(INT32 choice) // Yep, we'll be starting a netgame. levellist.netgame = true; - cupgrid.netgame = true; // Make sure we reset those levellist.levelsearch.timeattack = false; levellist.levelsearch.checklocked = true; From 05241341b8f011091d730f8240ea6904d5ac5c85 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 21:38:52 +0000 Subject: [PATCH 050/128] Remove the last few instances of gametype-specific colorisation --- src/g_game.c | 20 -------------------- src/g_game.h | 1 - src/y_inter.c | 10 +++------- 3 files changed, 3 insertions(+), 28 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 47e144c42..5ce2dc0a4 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3298,26 +3298,6 @@ INT16 G_SometimesGetDifferentGametype(void) return (gametype|encoremodifier); } -// -// G_GetGametypeColor -// -// Pretty and consistent ^u^ -// See also M_GetGametypeColor (if that still exists). -// -UINT8 G_GetGametypeColor(INT16 gt) -{ - if (modeattacking) // == ATTACKING_RECORD - return orangemap[0]; - - if (gt == GT_BATTLE) - return redmap[0]; - - if (gt == GT_RACE) - return skymap[0]; - - return 255; // FALLBACK -} - /** Get the typeoflevel flag needed to indicate support of a gametype. * \param gametype The gametype for which support is desired. * \return The typeoflevel flag to check for that gametype. diff --git a/src/g_game.h b/src/g_game.h index 227274590..eebb956e9 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -190,7 +190,6 @@ boolean G_GametypeHasTeams(void); boolean G_GametypeHasSpectators(void); #define VOTEMODIFIER_ENCORE 0x80 INT16 G_SometimesGetDifferentGametype(void); -UINT8 G_GetGametypeColor(INT16 gt); void G_ExitLevel(void); void G_NextLevel(void); void G_Continue(void); diff --git a/src/y_inter.c b/src/y_inter.c index 1d4393dc3..9d7a62f3e 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1209,12 +1209,8 @@ void Y_VoteDrawer(void) if (timer) { - INT32 hilicol, tickdown = (timer+1)/TICRATE; - if (gametype == GT_RACE) - hilicol = V_SKYMAP; - else //if (gametype == GT_BATTLE) - hilicol = V_REDMAP; - V_DrawCenteredString(BASEVIDWIDTH/2, 188, hilicol, + INT32 tickdown = (timer+1)/TICRATE; + V_DrawCenteredString(BASEVIDWIDTH/2, 188, V_YELLOWMAP, va("Vote ends in %d", tickdown)); } } @@ -1554,7 +1550,7 @@ void Y_StartVote(void) levelinfo[i].str[sizeof levelinfo[i].str - 1] = '\0'; // set up the gtc and gts - levelinfo[i].gtc = G_GetGametypeColor(votelevels[i][1]); + levelinfo[i].gtc = 255; // TODO rewrite vote screen if (i == 2 && votelevels[i][1] != votelevels[0][1]) levelinfo[i].gts = gametypes[votelevels[i][1]]->name; else From 36d6fcaba497e1d45fc9fceec40e2b7a7c748f67 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 21:43:39 +0000 Subject: [PATCH 051/128] Add a preview for Special Attack unlock type --- src/k_menudraw.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 6968bde6c..937839cd2 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4738,6 +4738,16 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) specialmap = btcmapcache; break; } + case SECRET_SPECIALATTACK: + { + static UINT16 sscmapcache = NEXTMAP_INVALID; + if (sscmapcache > nummapheaders) + { + sscmapcache = G_RandMap(G_TOLFlag(GT_SPECIAL), -1, 2, 0, false, NULL); + } + specialmap = sscmapcache; + break; + } case SECRET_HARDSPEED: { static UINT16 hardmapcache = NEXTMAP_INVALID; From 98646ef65b4febaab94bd986b598703800d2979d Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 22:33:09 +0000 Subject: [PATCH 052/128] Fix arrangement of gametype rules between SOC and constants --- src/deh_tables.c | 17 ++++++++------- src/doomstat.h | 54 +++++++++++++++++++++++++----------------------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 5217e3c6c..0c369b92e 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5796,28 +5796,31 @@ const char *const GAMETYPERULE_LIST[] = { "BUMPERS", "SPHERES", + "CLOSERPLAYERS", + + "BATTLESTARTS", "PAPERITEMS", "POWERSTONES", "KARMA", "ITEMARROWS", + "CAPSULES", - "BATTLESTARTS", + "CATCHER", + "ROLLINGSTART", + "SPECIALSTART", + "BOSS", "POINTLIMIT", "TIMELIMIT", "OVERTIME", + "ENCORE", "TEAMS", "NOTEAMS", "TEAMSTARTS", - "CATCHER", - "BOSS", - "ROLLINGSTART", + "NOMP", "NOCUPSELECT", - "CLOSERPLAYERS", - "ENCORE", - "SPECIALSTART", NULL }; diff --git a/src/doomstat.h b/src/doomstat.h index 76ec0cb25..bf1295078 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -489,37 +489,39 @@ extern INT16 gametype; enum GameTypeRules { // Race rules - GTR_CIRCUIT = 1, // Enables the finish line, laps, and the waypoint system. - GTR_BOTS = 1<<2, // Allows bots in this gametype. Combine with BotTiccmd hooks to make bots support your gametype. + GTR_CIRCUIT = 1, // Enables the finish line, laps, and the waypoint system. + GTR_BOTS = 1<<1, // Allows bots in this gametype. Combine with BotTiccmd hooks to make bots support your gametype. // Battle gametype rules - GTR_BUMPERS = 1<<3, // Enables the bumper health system - GTR_SPHERES = 1<<4, // Replaces rings with blue spheres - GTR_PAPERITEMS = 1<<5, // Replaces item boxes with paper item spawners - GTR_POWERSTONES = 1<<6, // Battle Emerald collectables. - GTR_KARMA = 1<<7, // Enables the Karma system if you're out of bumpers - GTR_ITEMARROWS = 1<<8, // Show item box arrows above players - GTR_CAPSULES = 1<<9, // Can enter Break The Capsules mode - GTR_BATTLESTARTS = 1<<10, // Use Battle Mode start positions. + GTR_BUMPERS = 1<<2, // Enables the bumper health system + GTR_SPHERES = 1<<3, // Replaces rings with blue spheres + GTR_CLOSERPLAYERS = 1<<4, // Buffs spindash and draft power to bring everyone together, nerfs invincibility and grow to prevent excessive combos - GTR_POINTLIMIT = 1<<11, // Reaching point limit ends the round - GTR_TIMELIMIT = 1<<12, // Reaching time limit ends the round - GTR_OVERTIME = 1<<13, // Allow overtime behavior + GTR_BATTLESTARTS = 1<<5, // Use Battle Mode start positions. + GTR_PAPERITEMS = 1<<6, // Replaces item boxes with paper item spawners + GTR_POWERSTONES = 1<<7, // Battle Emerald collectables. + GTR_KARMA = 1<<8, // Enables the Karma system if you're out of bumpers + GTR_ITEMARROWS = 1<<9, // Show item box arrows above players - // Custom gametype rules - GTR_TEAMS = 1<<14, // Teams are forced on - GTR_NOTEAMS = 1<<15, // Teams are forced off - GTR_TEAMSTARTS = 1<<16, // Use team-based start positions + // Bonus gametype rules + GTR_CAPSULES = 1<<10, // Can enter Break The Capsules mode + GTR_CATCHER = 1<<11, // UFO Catcher (only works with GTR_CIRCUIT) + GTR_ROLLINGSTART = 1<<12, // Rolling start (only works with GTR_CIRCUIT) + GTR_SPECIALSTART = 1<<13, // White fade instant start + GTR_BOSS = 1<<14, // Boss intro and spawning - // To be rearranged later - GTR_CATCHER = 1<<17, // UFO Catcher (only works with GTR_CIRCUIT) - GTR_BOSS = 1<<18, // Boss intro and spawning - GTR_ROLLINGSTART = 1<<19, // Rolling start (only works with GTR_CIRCUIT) - GTR_NOCUPSELECT = 1<<20, // Your maps are not selected via cup. - GTR_CLOSERPLAYERS = 1<<21, // Buffs spindash and draft power to bring everyone together, nerfs invincibility and grow to prevent excessive combos - GTR_ENCORE = 1<<22, // Alternate Encore mirroring, scripting, and texture remapping - GTR_SPECIALSTART = 1<<23, // White fade instant start - GTR_NOMP = 1<<24, // No multiplayer + // General purpose rules + GTR_POINTLIMIT = 1<<15, // Reaching point limit ends the round + GTR_TIMELIMIT = 1<<16, // Reaching time limit ends the round + GTR_OVERTIME = 1<<17, // Allow overtime behavior + GTR_ENCORE = 1<<18, // Alternate Encore mirroring, scripting, and texture remapping + + GTR_TEAMS = 1<<19, // Teams are forced on + GTR_NOTEAMS = 1<<20, // Teams are forced off + GTR_TEAMSTARTS = 1<<21, // Use team-based start positions + + GTR_NOMP = 1<<22, // No multiplayer + GTR_NOCUPSELECT = 1<<23, // Your maps are not selected via cup. // free: to and including 1<<31 }; From 94bcf797109d56a2ab95cb927d9df46d384fce5c Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 22:34:19 +0000 Subject: [PATCH 053/128] Restrict collecting rings in GTR_SPHERES, not GTR_BUMPERS! --- src/p_user.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_user.c b/src/p_user.c index a27890817..a51b161a3 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -502,7 +502,7 @@ INT32 P_GivePlayerRings(player_t *player, INT32 num_rings) if (!player->mo) return 0; - if ((gametyperules & GTR_BUMPERS)) // No rings in Battle Mode + if ((gametyperules & GTR_SPHERES)) // No rings in Battle Mode return 0; test = player->rings + num_rings; From 3357f56c9e5488ba219ad65d6a0192580ce033b2 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 22:37:26 +0000 Subject: [PATCH 054/128] G_RandMap should consider & all valid flags for gametype, not ==. --- src/g_game.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index 5ce2dc0a4..47b5105fc 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -3410,7 +3410,7 @@ tryagain: if (!mapheaderinfo[ix] || mapheaderinfo[ix]->lumpnum == LUMPERROR) continue; - if ((mapheaderinfo[ix]->typeoflevel & tolflags) != tolflags + if (!(mapheaderinfo[ix]->typeoflevel & tolflags) || ix == pprevmap || M_MapLocked(ix+1) || (usehellmaps != (mapheaderinfo[ix]->menuflags & LF2_HIDEINMENU))) // this is bad From c23a2d8dcac605fc5884688a3185fe0d79c29bf3 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 22:49:12 +0000 Subject: [PATCH 055/128] Don't force a reset of the level in GTR_BUMPER when lonesome if not GTR_CAPSULES --- src/k_battle.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_battle.c b/src/k_battle.c index 26b1265f3..d0f0451c2 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -145,7 +145,7 @@ void K_CheckBumpers(void) } else if (numingame <= 1) { - if (!battlecapsules) + if ((gametyperules & GTR_CAPSULES) && !battlecapsules) { // Reset map to turn on battle capsules if (server) From 425a02d09bc56055524c59e5666622f487f4c6a9 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 23:12:41 +0000 Subject: [PATCH 056/128] Make GTR_BUMPERS less monolithic * Battle-like item box respawn handling inverse of GTR_CIRCUIT * Make battle fullscreen overlay forbid GTR_CIRCUIT * Make GTR_ITEMARROWS actually do something * Make everything related to points actually use GTR_POINTLIMIT * Fobid sphere addition without GTR_SPHERES, to mirror ring addition forbidding --- src/d_netcmd.c | 2 +- src/k_hud.c | 4 ++-- src/p_enemy.c | 2 +- src/p_inter.c | 2 +- src/p_mobj.c | 9 ++++++--- src/p_tick.c | 4 ++-- src/p_user.c | 5 ++++- 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index e7a343a03..de2a9f9ec 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -3773,7 +3773,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum) // Clear player score and rings if a spectator. if (players[playernum].spectator) { - if (gametyperules & GTR_BUMPERS) // SRB2kart + if (gametyperules & GTR_POINTLIMIT) // SRB2kart { players[playernum].roundscore = 0; K_CalculateBattleWanted(); diff --git a/src/k_hud.c b/src/k_hud.c index ebf6f9ceb..e80135f17 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -5030,9 +5030,9 @@ void K_drawKartHUD(void) return; } - battlefullscreen = ((gametyperules & (GTR_BUMPERS)) + battlefullscreen = (!(gametyperules & GTR_CIRCUIT) && (stplyr->exiting - || (stplyr->bumpers <= 0 + || ((gametyperules & GTR_BUMPERS) && (stplyr->bumpers <= 0) && ((gametyperules & GTR_KARMA) && (stplyr->karmadelay > 0)) && !(stplyr->pflags & PF_ELIMINATED) && stplyr->playerstate == PST_LIVE))); diff --git a/src/p_enemy.c b/src/p_enemy.c index 2e1ed8557..fc67cc882 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -13053,7 +13053,7 @@ void A_ItemPop(mobj_t *actor) } // Here at mapload in battle? - if ((gametyperules & GTR_BUMPERS) && (actor->flags2 & MF2_BOSSNOTRAP)) + if (!(gametyperules & GTR_CIRCUIT) && (actor->flags2 & MF2_BOSSNOTRAP)) { numgotboxes++; diff --git a/src/p_inter.c b/src/p_inter.c index 8944b8b50..9caf37531 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1099,7 +1099,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget { P_SetTarget(&target->target, source); - if (gametyperules & GTR_BUMPERS) + if (!(gametyperules & GTR_CIRCUIT)) { target->fuse = 2; } diff --git a/src/p_mobj.c b/src/p_mobj.c index a9562b38a..839468ce9 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9581,7 +9581,7 @@ static boolean P_FuseThink(mobj_t *mobj) { ; } - else if ((gametyperules & GTR_BUMPERS) && (mobj->state == &states[S_INVISIBLE])) + else if (!(gametyperules & GTR_CIRCUIT) && (mobj->state == &states[S_INVISIBLE])) { break; } @@ -11419,7 +11419,7 @@ void P_RespawnBattleBoxes(void) { thinker_t *th; - if (!(gametyperules & GTR_BUMPERS)) + if (gametyperules & GTR_CIRCUIT) return; for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) @@ -11684,13 +11684,16 @@ void P_SpawnPlayer(INT32 playernum) K_InitStumbleIndicator(p); - if (gametyperules & GTR_BUMPERS) + if (gametyperules & GTR_ITEMARROWS) { mobj_t *overheadarrow = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height + 16*FRACUNIT, MT_PLAYERARROW); P_SetTarget(&overheadarrow->target, mobj); overheadarrow->renderflags |= RF_DONTDRAW; P_SetScale(overheadarrow, mobj->destscale); + } + if (gametyperules & GTR_BUMPERS) + { if (p->spectator) { // HEY! No being cheap... diff --git a/src/p_tick.c b/src/p_tick.c index 4ad34d57e..ce390bd1a 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -361,7 +361,7 @@ static inline void P_RunThinkers(void) if (gametyperules & GTR_PAPERITEMS) K_RunPaperItemSpawners(); - if ((gametyperules & GTR_BUMPERS) && battleovertime.enabled) + if ((gametyperules & GTR_OVERTIME) && battleovertime.enabled) K_RunBattleOvertime(); } @@ -719,7 +719,7 @@ void P_Ticker(boolean run) K_TickSpecialStage(); - if ((gametyperules & GTR_BUMPERS)) + if ((gametyperules & GTR_POINTLIMIT)) { if (wantedcalcdelay && --wantedcalcdelay <= 0) K_CalculateBattleWanted(); diff --git a/src/p_user.c b/src/p_user.c index a51b161a3..7de98c92b 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -520,6 +520,9 @@ INT32 P_GivePlayerSpheres(player_t *player, INT32 num_spheres) { num_spheres += player->spheres; + if (!(gametyperules & GTR_SPHERES)) // No spheres in Race mode) + return 0; + // Not alive if ((gametyperules & GTR_BUMPERS) && (player->bumpers <= 0)) return 0; @@ -555,7 +558,7 @@ void P_GivePlayerLives(player_t *player, INT32 numlives) // Adds to the player's score void P_AddPlayerScore(player_t *player, UINT32 amount) { - if (!((gametyperules & GTR_BUMPERS))) + if (!((gametyperules & GTR_POINTLIMIT))) return; if (player->exiting) // srb2kart From 84b066245e91aaf38a06c681465695891991bd4a Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 23:14:43 +0000 Subject: [PATCH 057/128] Demo savemode text is consistent colour now --- src/st_stuff.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/st_stuff.c b/src/st_stuff.c index 76ad09b9b..3f7e793eb 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1253,15 +1253,15 @@ void ST_Drawer(void) switch (demo.savemode) { case DSM_NOTSAVING: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|((gametyperules & GTR_BUMPERS) ? V_REDMAP : V_SKYMAP), "(B) or (X): Save replay"); + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|V_YELLOWMAP, "(B) or (X): Save replay"); break; case DSM_WILLAUTOSAVE: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|((gametyperules & GTR_BUMPERS) ? V_REDMAP : V_SKYMAP), "Replay will be saved. (Look Backward: Change title)"); + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|V_YELLOWMAP, "Replay will be saved. (Look Backward: Change title)"); break; case DSM_WILLSAVE: - V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|((gametyperules & GTR_BUMPERS) ? V_REDMAP : V_SKYMAP), "Replay will be saved."); + V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, V_HUDTRANS|V_SNAPTOTOP|V_SNAPTORIGHT|V_ALLOWLOWERCASE|V_YELLOWMAP, "Replay will be saved."); break; case DSM_TITLEENTRY: From 42a6b03e488a99f8e1a7dd4553fcfd4582100778 Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 31 Dec 2022 23:27:59 +0000 Subject: [PATCH 058/128] Remove "circuitmap" An effectively useless global scope boolean that literally checks for the presence of a finish line, created in and used only by things we inherited from vanilla SRB2 --- src/d_netcmd.c | 1 - src/doomstat.h | 1 - src/hu_stuff.c | 14 +++++--------- src/k_hud.c | 4 ++-- src/lua_script.c | 3 --- src/p_setup.c | 1 - src/p_spec.c | 6 ------ src/p_user.c | 4 ++-- 8 files changed, 9 insertions(+), 25 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index de2a9f9ec..98dbb7fc9 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -553,7 +553,6 @@ INT16 numgametypes = GT_FIRSTFREESLOT; boolean forceresetplayers = false; boolean deferencoremode = false; UINT8 splitscreen = 0; -boolean circuitmap = false; INT32 adminplayers[MAXPLAYERS]; // Scheduled commands. diff --git a/src/doomstat.h b/src/doomstat.h index bf1295078..6d4fd80a5 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -150,7 +150,6 @@ extern boolean multiplayer; extern UINT8 splitscreen; extern int r_splitscreen; -extern boolean circuitmap; // Does this level have 'circuit mode'? extern boolean fromlevelselect; extern boolean forceresetplayers, deferencoremode; diff --git a/src/hu_stuff.c b/src/hu_stuff.c index dd682e1bd..ed0ab076f 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -2457,11 +2457,8 @@ static void HU_DrawRankings(void) } else if (gametyperules & GTR_CIRCUIT) { - if (circuitmap) - { - V_DrawCenteredString(64, 8, 0, "LAP COUNT"); - V_DrawCenteredString(64, 16, hilicol, va("%d", numlaps)); - } + V_DrawCenteredString(64, 8, 0, "LAPS"); + V_DrawCenteredString(64, 16, hilicol, va("%d", numlaps)); } // Right hand side @@ -2548,13 +2545,12 @@ static void HU_DrawRankings(void) if ((gametyperules & GTR_CIRCUIT)) { - if (circuitmap) - tab[scorelines].count = players[i].laps; - else - tab[scorelines].count = players[i].realtime; + tab[scorelines].count = players[i].laps; } else + { tab[scorelines].count = players[i].roundscore; + } scorelines++; diff --git a/src/k_hud.c b/src/k_hud.c index e80135f17..f8b6bc387 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -2304,7 +2304,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN V_DrawRightAlignedThinString(x+rightoffset, y-1, hilicol|V_6WIDTHSPACE, timestring(players[tab[i].num].realtime)); else if (players[tab[i].num].pflags & PF_NOCONTEST) V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, "NO CONTEST."); - else if (circuitmap) + else V_DrawRightAlignedThinString(x+rightoffset, y-1, V_6WIDTHSPACE, va("Lap %d", tab[i].count)); } else @@ -2313,7 +2313,7 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN V_DrawRightAlignedString(x+rightoffset, y, hilicol, timestring(players[tab[i].num].realtime)); else if (players[tab[i].num].pflags & PF_NOCONTEST) V_DrawRightAlignedThinString(x+rightoffset, y-1, 0, "NO CONTEST."); - else if (circuitmap) + else V_DrawRightAlignedString(x+rightoffset, y, 0, va("Lap %d", tab[i].count)); } #undef timestring diff --git a/src/lua_script.c b/src/lua_script.c index 4f9eab969..cde3e2f46 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -162,9 +162,6 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"maptol")) { lua_pushinteger(L, maptol); return 1; - } else if (fastcmp(word,"circuitmap")) { - lua_pushboolean(L, circuitmap); - return 1; } else if (fastcmp(word,"stoppedclock")) { lua_pushboolean(L, stoppedclock); return 1; diff --git a/src/p_setup.c b/src/p_setup.c index 36a589f22..d890868f8 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -6822,7 +6822,6 @@ static void P_InitLevelSettings(void) rflagpoint = bflagpoint = NULL; // circuit, race and competition stuff - circuitmap = false; numstarposts = 0; timeinmap = 0; diff --git a/src/p_spec.c b/src/p_spec.c index 208e6c04f..48d38a125 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -6671,12 +6671,6 @@ void P_SpawnSpecials(boolean fromnetsave) break; } - // SRB2Kart - case 2001: // Finish Line - if ((gametyperules & GTR_CIRCUIT)) - circuitmap = true; - break; - default: break; } diff --git a/src/p_user.c b/src/p_user.c index 7de98c92b..be70fd0e0 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -2639,7 +2639,7 @@ static void P_DeathThink(player_t *player) player->realtime = leveltime - starttime; if (player == &players[consoleplayer]) { - if (player->spectator || !circuitmap) + if (player->spectator) curlap = 0; else if (curlap != UINT32_MAX) curlap++; // This is too complicated to sync to realtime, just sorta hope for the best :V @@ -3998,7 +3998,7 @@ void P_PlayerThink(player_t *player) player->realtime = leveltime - starttime; if (player == &players[consoleplayer]) { - if (player->spectator || !circuitmap) + if (player->spectator) curlap = 0; else if (curlap != UINT32_MAX) curlap++; // This is too complicated to sync to realtime, just sorta hope for the best :V From 38a35b6f78eec1ac42b83418ad9b107bafefcde4 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Fri, 23 Dec 2022 08:32:01 -0500 Subject: [PATCH 059/128] Make save_p / savebuffers not global This caused some scary issues with P_SaveNetGame the other day, and it's making ACS net sync harder. Let's just cut this off right now. Also fixed some scary mix-ups in some of the Lua archiving code. --- src/d_clisrv.c | 80 +- src/d_netcmd.c | 23 +- src/g_demo.c | 4 +- src/g_game.c | 203 +-- src/k_profiles.c | 83 +- src/lua_script.c | 26 +- src/lua_script.h | 4 +- src/p_saveg.c | 3477 +++++++++++++++++++++++----------------------- src/p_saveg.h | 15 +- src/p_setup.c | 24 +- src/typedef.h | 1 + 11 files changed, 1976 insertions(+), 1964 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 27a489916..360bb05b7 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1143,28 +1143,28 @@ static boolean SV_ResendingSavegameToAnyone(void) static void SV_SendSaveGame(INT32 node, boolean resending) { size_t length, compressedlen; - UINT8 *savebuffer; + savebuffer_t save; UINT8 *compressedsave; UINT8 *buffertosend; // first save it in a malloced buffer - savebuffer = (UINT8 *)malloc(SAVEGAMESIZE); - if (!savebuffer) + save.buffer = (UINT8 *)malloc(SAVEGAMESIZE); + if (!save.buffer) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); return; } // Leave room for the uncompressed length. - save_p = savebuffer + sizeof(UINT32); + save.p = save.buffer + sizeof(UINT32); - P_SaveNetGame(resending); + P_SaveNetGame(&save, resending); - length = save_p - savebuffer; + length = save.p - save.buffer; if (length > SAVEGAMESIZE) { - free(savebuffer); - save_p = NULL; + free(save.buffer); + save.p = NULL; I_Error("Savegame buffer overrun"); } @@ -1178,11 +1178,11 @@ static void SV_SendSaveGame(INT32 node, boolean resending) } // Attempt to compress it. - if((compressedlen = lzf_compress(savebuffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1))) + if((compressedlen = lzf_compress(save.buffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1))) { // Compressing succeeded; send compressed data - free(savebuffer); + free(save.buffer); // State that we're compressed. buffertosend = compressedsave; @@ -1196,12 +1196,12 @@ static void SV_SendSaveGame(INT32 node, boolean resending) free(compressedsave); // State that we're not compressed - buffertosend = savebuffer; - WRITEUINT32(savebuffer, 0); + buffertosend = save.buffer; + WRITEUINT32(save.buffer, 0); } AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0); - save_p = NULL; + save.p = NULL; // Remember when we started sending the savegame so we can handle timeouts sendingsavegame[node] = true; @@ -1215,7 +1215,7 @@ static consvar_t cv_dumpconsistency = CVAR_INIT ("dumpconsistency", "Off", CV_SA static void SV_SavedGame(void) { size_t length; - UINT8 *savebuffer; + savebuffer_t save; char tmpsave[256]; if (!cv_dumpconsistency.value) @@ -1224,29 +1224,29 @@ static void SV_SavedGame(void) sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); // first save it in a malloced buffer - save_p = savebuffer = (UINT8 *)malloc(SAVEGAMESIZE); - if (!save_p) + save.p = save.buffer = (UINT8 *)malloc(SAVEGAMESIZE); + if (!save.p) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); return; } - P_SaveNetGame(false); + P_SaveNetGame(&save, false); - length = save_p - savebuffer; + length = save.p - save.buffer; if (length > SAVEGAMESIZE) { - free(savebuffer); - save_p = NULL; + free(save.buffer); + save.p = NULL; I_Error("Savegame buffer overrun"); } // then save it! - if (!FIL_WriteFile(tmpsave, savebuffer, length)) + if (!FIL_WriteFile(tmpsave, save.buffer, length)) CONS_Printf(M_GetText("Didn't save %s for netgame"), tmpsave); - free(savebuffer); - save_p = NULL; + free(save.buffer); + save.p = NULL; } #undef TMPSAVENAME @@ -1256,13 +1256,13 @@ static void SV_SavedGame(void) static void CL_LoadReceivedSavegame(boolean reloading) { - UINT8 *savebuffer = NULL; + savebuffer_t save; size_t length, decompressedlen; char tmpsave[256]; sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); - length = FIL_ReadFile(tmpsave, &savebuffer); + length = FIL_ReadFile(tmpsave, &save.buffer); CONS_Printf(M_GetText("Loading savegame length %s\n"), sizeu1(length)); if (!length) @@ -1271,16 +1271,16 @@ static void CL_LoadReceivedSavegame(boolean reloading) return; } - save_p = savebuffer; + save.p = save.buffer; // Decompress saved game if necessary. - decompressedlen = READUINT32(save_p); + decompressedlen = READUINT32(save.p); if(decompressedlen > 0) { UINT8 *decompressedbuffer = Z_Malloc(decompressedlen, PU_STATIC, NULL); - lzf_decompress(save_p, length - sizeof(UINT32), decompressedbuffer, decompressedlen); - Z_Free(savebuffer); - save_p = savebuffer = decompressedbuffer; + lzf_decompress(save.p, length - sizeof(UINT32), decompressedbuffer, decompressedlen); + Z_Free(save.buffer); + save.p = save.buffer = decompressedbuffer; } paused = false; @@ -1290,7 +1290,7 @@ static void CL_LoadReceivedSavegame(boolean reloading) automapactive = false; // load a base level - if (P_LoadNetGame(reloading)) + if (P_LoadNetGame(&save, reloading)) { if (!reloading) { @@ -1312,8 +1312,8 @@ static void CL_LoadReceivedSavegame(boolean reloading) } // done - Z_Free(savebuffer); - save_p = NULL; + Z_Free(save.buffer); + save.p = NULL; if (unlink(tmpsave) == -1) CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave); consistancy[gametic%BACKUPTICS] = Consistancy(); @@ -6064,6 +6064,7 @@ void CL_ClearRewinds(void) rewind_t *CL_SaveRewindPoint(size_t demopos) { + savebuffer_t save; rewind_t *rewind; if (rewindhead && rewindhead->leveltime + REWIND_POINT_INTERVAL > leveltime) @@ -6073,8 +6074,10 @@ rewind_t *CL_SaveRewindPoint(size_t demopos) if (!rewind) return NULL; - save_p = rewind->savebuffer; - P_SaveNetGame(false); + save.buffer = save.p = rewind->savebuffer; + + P_SaveNetGame(&save, false); + rewind->leveltime = leveltime; rewind->next = rewindhead; rewind->demopos = demopos; @@ -6085,6 +6088,7 @@ rewind_t *CL_SaveRewindPoint(size_t demopos) rewind_t *CL_RewindToTime(tic_t time) { + savebuffer_t save; rewind_t *rewind; while (rewindhead && rewindhead->leveltime > time) @@ -6097,8 +6101,10 @@ rewind_t *CL_RewindToTime(tic_t time) if (!rewindhead) return NULL; - save_p = rewindhead->savebuffer; - P_LoadNetGame(false); + save.buffer = save.p = rewindhead->savebuffer; + + P_LoadNetGame(&save, false); + wipegamestate = gamestate; // No fading back in! timeinmap = leveltime; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index df3b86bcf..6a416cbfd 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -5776,10 +5776,9 @@ static void Command_Togglemodified_f(void) modifiedgame = !modifiedgame; } -extern UINT8 *save_p; static void Command_Archivetest_f(void) { - UINT8 *buf; + savebuffer_t save; UINT32 i, wrote; thinker_t *th; if (gamestate != GS_LEVEL) @@ -5795,28 +5794,28 @@ static void Command_Archivetest_f(void) ((mobj_t *)th)->mobjnum = i++; // allocate buffer - buf = save_p = ZZ_Alloc(1024); + save.buffer = save.p = ZZ_Alloc(1024); // test archive CONS_Printf("LUA_Archive...\n"); - LUA_Archive(&save_p); - WRITEUINT8(save_p, 0x7F); - wrote = (UINT32)(save_p-buf); + LUA_Archive(&save.p, true); + WRITEUINT8(save.p, 0x7F); + wrote = (UINT32)(save.p - save.buffer); // clear Lua state, so we can really see what happens! CONS_Printf("Clearing state!\n"); LUA_ClearExtVars(); // test unarchive - save_p = buf; + save.p = save.buffer; CONS_Printf("LUA_UnArchive...\n"); - LUA_UnArchive(&save_p); - i = READUINT8(save_p); - if (i != 0x7F || wrote != (UINT32)(save_p-buf)) - CONS_Printf("Savegame corrupted. (write %u, read %u)\n", wrote, (UINT32)(save_p-buf)); + LUA_UnArchive(&save.p, true); + i = READUINT8(save.p); + if (i != 0x7F || wrote != (UINT32)(save.p - save.buffer)) + CONS_Printf("Savegame corrupted. (write %u, read %u)\n", wrote, (UINT32)(save.p - save.buffer)); // free buffer - Z_Free(buf); + Z_Free(save.buffer); CONS_Printf("Done. No crash.\n"); } #endif diff --git a/src/g_demo.c b/src/g_demo.c index e47d8ba28..298f887e5 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -2502,7 +2502,7 @@ void G_BeginRecording(void) // player lua vars, always saved even if empty if (demoflags & DF_LUAVARS) - LUA_Archive(&demo_p); + LUA_Archive(&demo_p, false); memset(&oldcmd,0,sizeof(oldcmd)); memset(&oldghost,0,sizeof(oldghost)); @@ -3366,7 +3366,7 @@ void G_DoPlayDemo(char *defdemoname) LUA_ClearState(); // No modeattacking check, DF_LUAVARS won't be present here. - LUA_UnArchive(&demo_p); + LUA_UnArchive(&demo_p, false); } splitscreen = 0; diff --git a/src/g_game.c b/src/g_game.c index a79dd5219..60cacbd66 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -345,8 +345,6 @@ boolean precache = true; // if true, load all graphics at start INT16 prevmap, nextmap; -static UINT8 *savebuffer; - static void weaponPrefChange(void); static void weaponPrefChange2(void); static void weaponPrefChange3(void); @@ -4313,6 +4311,7 @@ void G_LoadGameData(void) UINT32 versionID; UINT8 versionMinor; UINT8 rtemp; + savebuffer_t save; //For records UINT32 numgamedatamapheaders; @@ -4341,7 +4340,7 @@ void G_LoadGameData(void) return; } - length = FIL_ReadFile(va(pandf, srb2home, gamedatafilename), &savebuffer); + length = FIL_ReadFile(va(pandf, srb2home, gamedatafilename), &save.buffer); if (!length) { // No gamedata. We can save a new one. @@ -4349,35 +4348,35 @@ void G_LoadGameData(void) return; } - save_p = savebuffer; + save.p = save.buffer; // Version check - versionID = READUINT32(save_p); + versionID = READUINT32(save.p); if (versionID != GD_VERSIONCHECK) { const char *gdfolder = "the Ring Racers folder"; if (strcmp(srb2home,".")) gdfolder = srb2home; - Z_Free(savebuffer); - save_p = NULL; + Z_Free(save.buffer); + save.p = NULL; I_Error("Game data is not for Ring Racers v2.0.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); } - versionMinor = READUINT8(save_p); + versionMinor = READUINT8(save.p); if (versionMinor > GD_VERSIONMINOR) { - Z_Free(savebuffer); - save_p = NULL; + Z_Free(save.buffer); + save.p = NULL; I_Error("Game data is from the future! (expected %d, got %d)", GD_VERSIONMINOR, versionMinor); } - gamedata->totalplaytime = READUINT32(save_p); - gamedata->matchesplayed = READUINT32(save_p); + gamedata->totalplaytime = READUINT32(save.p); + gamedata->matchesplayed = READUINT32(save.p); { // Quick & dirty hash for what mod this save file is for. - UINT32 modID = READUINT32(save_p); + UINT32 modID = READUINT32(save.p); UINT32 expectedID = quickncasehash(timeattackfolder, 64); if (modID != expectedID) @@ -4390,34 +4389,34 @@ void G_LoadGameData(void) // To save space, use one bit per collected/achieved/unlocked flag for (i = 0; i < MAXEMBLEMS;) { - rtemp = READUINT8(save_p); + rtemp = READUINT8(save.p); for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j) gamedata->collected[j+i] = ((rtemp >> j) & 1); i += j; } for (i = 0; i < MAXUNLOCKABLES;) { - rtemp = READUINT8(save_p); + rtemp = READUINT8(save.p); for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) gamedata->unlocked[j+i] = ((rtemp >> j) & 1); i += j; } for (i = 0; i < MAXUNLOCKABLES;) { - rtemp = READUINT8(save_p); + rtemp = READUINT8(save.p); for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) gamedata->unlockpending[j+i] = ((rtemp >> j) & 1); i += j; } for (i = 0; i < MAXCONDITIONSETS;) { - rtemp = READUINT8(save_p); + rtemp = READUINT8(save.p); for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j) gamedata->achieved[j+i] = ((rtemp >> j) & 1); i += j; } - gamedata->challengegridwidth = READUINT16(save_p); + gamedata->challengegridwidth = READUINT16(save.p); Z_Free(gamedata->challengegrid); if (gamedata->challengegridwidth) { @@ -4426,7 +4425,7 @@ void G_LoadGameData(void) PU_STATIC, NULL); for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++) { - gamedata->challengegrid[i] = READUINT8(save_p); + gamedata->challengegrid[i] = READUINT8(save.p); } } else @@ -4434,10 +4433,10 @@ void G_LoadGameData(void) gamedata->challengegrid = NULL; } - gamedata->timesBeaten = READUINT32(save_p); + gamedata->timesBeaten = READUINT32(save.p); // Main records - numgamedatamapheaders = READUINT32(save_p); + numgamedatamapheaders = READUINT32(save.p); if (numgamedatamapheaders >= NEXTMAP_SPECIAL) goto datacorrupt; @@ -4448,12 +4447,12 @@ void G_LoadGameData(void) tic_t rectime; tic_t reclap; - READSTRINGN(save_p, mapname, sizeof(mapname)); + READSTRINGN(save.p, mapname, sizeof(mapname)); mapnum = G_MapNumber(mapname); - rtemp = READUINT8(save_p); - rectime = (tic_t)READUINT32(save_p); - reclap = (tic_t)READUINT32(save_p); + rtemp = READUINT8(save.p); + rectime = (tic_t)READUINT32(save.p); + reclap = (tic_t)READUINT32(save.p); if (mapnum < nummapheaders && mapheaderinfo[mapnum]) { @@ -4479,8 +4478,8 @@ void G_LoadGameData(void) } // done - Z_Free(savebuffer); - save_p = NULL; + Z_Free(save.buffer); + save.p = NULL; // Don't consider loaded until it's a success! // It used to do this much earlier, but this would cause the gamedata to @@ -4500,8 +4499,8 @@ void G_LoadGameData(void) if (strcmp(srb2home,".")) gdfolder = srb2home; - Z_Free(savebuffer); - save_p = NULL; + Z_Free(save.buffer); + save.p = NULL; I_Error("Corrupt game data file.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); } @@ -4514,6 +4513,7 @@ void G_SaveGameData(void) size_t length; INT32 i, j; UINT8 btemp; + savebuffer_t save; if (!gamedata->loaded) return; // If never loaded (-nodata), don't save @@ -4533,8 +4533,8 @@ void G_SaveGameData(void) } length += nummapheaders * (MAXMAPLUMPNAME+1+4+4); - save_p = savebuffer = (UINT8 *)malloc(length); - if (!save_p) + save.p = save.buffer = (UINT8 *)malloc(length); + if (!save.p) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for saving game data\n")); return; @@ -4542,11 +4542,11 @@ void G_SaveGameData(void) // Version test - WRITEUINT32(save_p, GD_VERSIONCHECK); // 4 - WRITEUINT8(save_p, GD_VERSIONMINOR); // 1 - WRITEUINT32(save_p, gamedata->totalplaytime); // 4 - WRITEUINT32(save_p, gamedata->matchesplayed); // 4 - WRITEUINT32(save_p, quickncasehash(timeattackfolder, 64)); + WRITEUINT32(save.p, GD_VERSIONCHECK); // 4 + WRITEUINT8(save.p, GD_VERSIONMINOR); // 1 + WRITEUINT32(save.p, gamedata->totalplaytime); // 4 + WRITEUINT32(save.p, gamedata->matchesplayed); // 4 + WRITEUINT32(save.p, quickncasehash(timeattackfolder, 64)); // To save space, use one bit per collected/achieved/unlocked flag for (i = 0; i < MAXEMBLEMS;) // MAXEMBLEMS * 1; @@ -4554,7 +4554,7 @@ void G_SaveGameData(void) btemp = 0; for (j = 0; j < 8 && j+i < MAXEMBLEMS; ++j) btemp |= (gamedata->collected[j+i] << j); - WRITEUINT8(save_p, btemp); + WRITEUINT8(save.p, btemp); i += j; } @@ -4564,7 +4564,7 @@ void G_SaveGameData(void) btemp = 0; for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) btemp |= (gamedata->unlocked[j+i] << j); - WRITEUINT8(save_p, btemp); + WRITEUINT8(save.p, btemp); i += j; } for (i = 0; i < MAXUNLOCKABLES;) @@ -4572,7 +4572,7 @@ void G_SaveGameData(void) btemp = 0; for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) btemp |= (gamedata->unlockpending[j+i] << j); - WRITEUINT8(save_p, btemp); + WRITEUINT8(save.p, btemp); i += j; } @@ -4581,52 +4581,52 @@ void G_SaveGameData(void) btemp = 0; for (j = 0; j < 8 && j+i < MAXCONDITIONSETS; ++j) btemp |= (gamedata->achieved[j+i] << j); - WRITEUINT8(save_p, btemp); + WRITEUINT8(save.p, btemp); i += j; } if (gamedata->challengegrid) // 2 + gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT { - WRITEUINT16(save_p, gamedata->challengegridwidth); + WRITEUINT16(save.p, gamedata->challengegridwidth); for (i = 0; i < (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT); i++) { - WRITEUINT8(save_p, gamedata->challengegrid[i]); + WRITEUINT8(save.p, gamedata->challengegrid[i]); } } else // 2 { - WRITEUINT16(save_p, 0); + WRITEUINT16(save.p, 0); } - WRITEUINT32(save_p, gamedata->timesBeaten); // 4 + WRITEUINT32(save.p, gamedata->timesBeaten); // 4 // Main records - WRITEUINT32(save_p, nummapheaders); // 4 + WRITEUINT32(save.p, nummapheaders); // 4 for (i = 0; i < nummapheaders; i++) // nummapheaders * (255+1+4+4) { // For figuring out which header to assing it to on load - WRITESTRINGN(save_p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME); + WRITESTRINGN(save.p, mapheaderinfo[i]->lumpname, MAXMAPLUMPNAME); - WRITEUINT8(save_p, (mapheaderinfo[i]->mapvisited & MV_MAX)); + WRITEUINT8(save.p, (mapheaderinfo[i]->mapvisited & MV_MAX)); if (mapheaderinfo[i]->mainrecord) { - WRITEUINT32(save_p, mapheaderinfo[i]->mainrecord->time); - WRITEUINT32(save_p, mapheaderinfo[i]->mainrecord->lap); + WRITEUINT32(save.p, mapheaderinfo[i]->mainrecord->time); + WRITEUINT32(save.p, mapheaderinfo[i]->mainrecord->lap); } else { - WRITEUINT32(save_p, 0); - WRITEUINT32(save_p, 0); + WRITEUINT32(save.p, 0); + WRITEUINT32(save.p, 0); } } - length = save_p - savebuffer; + length = save.p - save.buffer; - FIL_WriteFile(va(pandf, srb2home, gamedatafilename), savebuffer, length); - free(savebuffer); - save_p = savebuffer = NULL; + FIL_WriteFile(va(pandf, srb2home, gamedatafilename), save.buffer, length); + free(save.buffer); + save.p = save.buffer = NULL; // Also save profiles here. PR_SaveProfiles(); @@ -4643,6 +4643,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) size_t length; char vcheck[VERSIONSIZE]; char savename[255]; + savebuffer_t save; // memset savedata to all 0, fixes calling perfectly valid saves corrupt because of bots memset(&savedata, 0, sizeof(savedata)); @@ -4657,18 +4658,18 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) else sprintf(savename, savegamename, slot); - length = FIL_ReadFile(savename, &savebuffer); + length = FIL_ReadFile(savename, &save.buffer); if (!length) { CONS_Printf(M_GetText("Couldn't read file %s\n"), savename); return; } - save_p = savebuffer; + save.p = save.buffer; memset(vcheck, 0, sizeof (vcheck)); sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); - if (strcmp((const char *)save_p, (const char *)vcheck)) + if (strcmp((const char *)save.p, (const char *)vcheck)) { #ifdef SAVEGAME_OTHERVERSIONS M_StartMessage(M_GetText("Save game from different version.\nYou can load this savegame, but\nsaving afterwards will be disabled.\n\nDo you want to continue anyway?\n\n(Press 'Y' to confirm)\n"), @@ -4678,15 +4679,15 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) M_ClearMenus(true); // so ESC backs out to title M_StartMessage(M_GetText("Save game from different version\n\nPress ESC\n"), NULL, MM_NOTHING); Command_ExitGame_f(); - Z_Free(savebuffer); - save_p = savebuffer = NULL; + Z_Free(save.buffer); + save.p = save.buffer = NULL; // no cheating! memset(&savedata, 0, sizeof(savedata)); #endif return; // bad version } - save_p += VERSIONSIZE; + save.p += VERSIONSIZE; if (demo.playback) // reset game engine G_StopDemo(); @@ -4695,13 +4696,13 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) // automapactive = false; // dearchive all the modifications - if (!P_LoadGame(mapoverride)) + if (!P_LoadGame(&save, mapoverride)) { M_ClearMenus(true); // so ESC backs out to title M_StartMessage(M_GetText("Savegame file corrupted\n\nPress ESC\n"), NULL, MM_NOTHING); Command_ExitGame_f(); - Z_Free(savebuffer); - save_p = savebuffer = NULL; + Z_Free(save.buffer); + save.p = save.buffer = NULL; // no cheating! memset(&savedata, 0, sizeof(savedata)); @@ -4709,13 +4710,13 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) } if (marathonmode) { - marathontime = READUINT32(save_p); - marathonmode |= READUINT8(save_p); + marathontime = READUINT32(save.p); + marathonmode |= READUINT8(save.p); } // done - Z_Free(savebuffer); - save_p = savebuffer = NULL; + Z_Free(save.buffer); + save.p = save.buffer = NULL; // gameaction = ga_nothing; // G_SetGamestate(GS_LEVEL); @@ -4741,6 +4742,7 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) boolean saved; char savename[256] = ""; const char *backup; + savebuffer_t save; if (marathonmode) strcpy(savename, liveeventbackup); @@ -4753,8 +4755,8 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) char name[VERSIONSIZE]; size_t length; - save_p = savebuffer = (UINT8 *)malloc(SAVEGAMESIZE); - if (!save_p) + save.p = save.buffer = (UINT8 *)malloc(SAVEGAMESIZE); + if (!save.p) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for saving game data\n")); return; @@ -4762,22 +4764,22 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) memset(name, 0, sizeof (name)); sprintf(name, (marathonmode ? "back-up %d" : "version %d"), VERSION); - WRITEMEM(save_p, name, VERSIONSIZE); + WRITEMEM(save.p, name, VERSIONSIZE); - P_SaveGame(mapnum); + P_SaveGame(&save, mapnum); if (marathonmode) { UINT32 writetime = marathontime; if (!(marathonmode & MA_INGAME)) writetime += TICRATE*5; // live event backup penalty because we don't know how long it takes to get to the next map - WRITEUINT32(save_p, writetime); - WRITEUINT8(save_p, (marathonmode & ~MA_INIT)); + WRITEUINT32(save.p, writetime); + WRITEUINT8(save.p, (marathonmode & ~MA_INIT)); } - length = save_p - savebuffer; - saved = FIL_WriteFile(backup, savebuffer, length); - free(savebuffer); - save_p = savebuffer = NULL; + length = save.p - save.buffer; + saved = FIL_WriteFile(backup, save.buffer, length); + free(save.buffer); + save.p = save.buffer = NULL; } gameaction = ga_nothing; @@ -4789,7 +4791,7 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) } #define BADSAVE goto cleanup; -#define CHECKPOS if (save_p >= end_p) BADSAVE +#define CHECKPOS if (save.p >= end_p) BADSAVE void G_SaveGameOver(UINT32 slot, boolean modifylives) { boolean saved = false; @@ -4797,6 +4799,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives) char vcheck[VERSIONSIZE]; char savename[255]; const char *backup; + savebuffer_t save; if (marathonmode) strcpy(savename, liveeventbackup); @@ -4804,7 +4807,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives) sprintf(savename, savegamename, slot); backup = va("%s",savename); - length = FIL_ReadFile(savename, &savebuffer); + length = FIL_ReadFile(savename, &save.buffer); if (!length) { CONS_Printf(M_GetText("Couldn't read file %s\n"), savename); @@ -4813,35 +4816,35 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives) { char temp[sizeof(timeattackfolder)]; - UINT8 *end_p = savebuffer + length; + UINT8 *end_p = save.buffer + length; UINT8 *lives_p; SINT8 pllives; - save_p = savebuffer; + save.p = save.buffer; // Version check memset(vcheck, 0, sizeof (vcheck)); sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); - if (strcmp((const char *)save_p, (const char *)vcheck)) BADSAVE - save_p += VERSIONSIZE; + if (strcmp((const char *)save.p, (const char *)vcheck)) BADSAVE + save.p += VERSIONSIZE; // P_UnArchiveMisc() - (void)READINT16(save_p); + (void)READINT16(save.p); CHECKPOS - (void)READUINT16(save_p); // emeralds + (void)READUINT16(save.p); // emeralds CHECKPOS - READSTRINGN(save_p, temp, sizeof(temp)); // mod it belongs to + READSTRINGN(save.p, temp, sizeof(temp)); // mod it belongs to if (strcmp(temp, timeattackfolder)) BADSAVE // P_UnArchivePlayer() CHECKPOS - (void)READUINT16(save_p); + (void)READUINT16(save.p); CHECKPOS - WRITEUINT8(save_p, numgameovers); + WRITEUINT8(save.p, numgameovers); CHECKPOS - lives_p = save_p; - pllives = READSINT8(save_p); // lives + lives_p = save.p; + pllives = READSINT8(save.p); // lives CHECKPOS if (modifylives && pllives < startinglivesbalance[numgameovers]) { @@ -4849,28 +4852,28 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives) WRITESINT8(lives_p, pllives); } - (void)READINT32(save_p); // Score + (void)READINT32(save.p); // Score CHECKPOS - (void)READINT32(save_p); // continues + (void)READINT32(save.p); // continues // File end marker check CHECKPOS - switch (READUINT8(save_p)) + switch (READUINT8(save.p)) { case 0xb7: { UINT8 i, banksinuse; CHECKPOS - banksinuse = READUINT8(save_p); + banksinuse = READUINT8(save.p); CHECKPOS if (banksinuse > NUM_LUABANKS) BADSAVE for (i = 0; i < banksinuse; i++) { - (void)READINT32(save_p); + (void)READINT32(save.p); CHECKPOS } - if (READUINT8(save_p) != 0x1d) + if (READUINT8(save.p) != 0x1d) BADSAVE } case 0x1d: @@ -4880,7 +4883,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives) } // done - saved = FIL_WriteFile(backup, savebuffer, length); + saved = FIL_WriteFile(backup, save.buffer, length); } cleanup: @@ -4888,8 +4891,8 @@ cleanup: CONS_Printf(M_GetText("Game saved.\n")); else if (!saved) CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename)); - Z_Free(savebuffer); - save_p = savebuffer = NULL; + Z_Free(save.buffer); + save.p = save.buffer = NULL; } #undef CHECKPOS diff --git a/src/k_profiles.c b/src/k_profiles.c index 8b3712ed0..f24d623ab 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -10,9 +10,10 @@ /// \file k_profiles.c /// \brief implements methods for profiles etc. +#include "doomtype.h" #include "d_main.h" // pandf #include "byteptr.h" // READ/WRITE macros -#include "p_saveg.h" // save_p +#include "p_saveg.h" // savebuffer_t #include "m_misc.h" //FIL_WriteFile() #include "k_profiles.h" #include "z_zone.h" @@ -211,68 +212,67 @@ void PR_InitNewProfile(void) PR_AddProfile(dprofile); } -static UINT8 *savebuffer; - void PR_SaveProfiles(void) { size_t length = 0; const size_t headerlen = strlen(PROFILEHEADER); UINT8 i, j, k; + savebuffer_t save; - save_p = savebuffer = (UINT8 *)malloc(sizeof(UINT32) + (numprofiles * sizeof(profile_t))); - if (!save_p) + save.p = save.buffer = (UINT8 *)malloc(sizeof(UINT32) + (numprofiles * sizeof(profile_t))); + if (!save.p) { I_Error("No more free memory for saving profiles\n"); return; } // Add header. - WRITESTRINGN(save_p, PROFILEHEADER, headerlen); - WRITEUINT8(save_p, PROFILEVER); - WRITEUINT8(save_p, numprofiles); + WRITESTRINGN(save.p, PROFILEHEADER, headerlen); + WRITEUINT8(save.p, PROFILEVER); + WRITEUINT8(save.p, numprofiles); for (i = 1; i < numprofiles; i++) { // Names. - WRITESTRINGN(save_p, profilesList[i]->profilename, PROFILENAMELEN); - WRITESTRINGN(save_p, profilesList[i]->playername, MAXPLAYERNAME); + WRITESTRINGN(save.p, profilesList[i]->profilename, PROFILENAMELEN); + WRITESTRINGN(save.p, profilesList[i]->playername, MAXPLAYERNAME); // Character and colour. - WRITESTRINGN(save_p, profilesList[i]->skinname, SKINNAMESIZE); - WRITEUINT16(save_p, profilesList[i]->color); + WRITESTRINGN(save.p, profilesList[i]->skinname, SKINNAMESIZE); + WRITEUINT16(save.p, profilesList[i]->color); // Follower and colour. - WRITESTRINGN(save_p, profilesList[i]->follower, SKINNAMESIZE); - WRITEUINT16(save_p, profilesList[i]->followercolor); + WRITESTRINGN(save.p, profilesList[i]->follower, SKINNAMESIZE); + WRITEUINT16(save.p, profilesList[i]->followercolor); // PWR. for (j = 0; j < PWRLV_NUMTYPES; j++) { - WRITEUINT16(save_p, profilesList[i]->powerlevels[j]); + WRITEUINT16(save.p, profilesList[i]->powerlevels[j]); } // Consvars. - WRITEUINT8(save_p, profilesList[i]->kickstartaccel); + WRITEUINT8(save.p, profilesList[i]->kickstartaccel); // Controls. for (j = 0; j < num_gamecontrols; j++) { for (k = 0; k < MAXINPUTMAPPING; k++) { - WRITEINT32(save_p, profilesList[i]->controls[j][k]); + WRITEINT32(save.p, profilesList[i]->controls[j][k]); } } } - length = save_p - savebuffer; + length = save.p - save.buffer; - if (!FIL_WriteFile(va(pandf, srb2home, PROFILESFILE), savebuffer, length)) + if (!FIL_WriteFile(va(pandf, srb2home, PROFILESFILE), save.buffer, length)) { - free(savebuffer); + free(save.buffer); I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?"); } - free(savebuffer); - save_p = savebuffer = NULL; + free(save.buffer); + save.p = save.buffer = NULL; } void PR_LoadProfiles(void) @@ -288,8 +288,9 @@ void PR_LoadProfiles(void) gamecontroldefault, true ); + savebuffer_t save; - length = FIL_ReadFile(va(pandf, srb2home, PROFILESFILE), &savebuffer); + length = FIL_ReadFile(va(pandf, srb2home, PROFILESFILE), &save.buffer); if (!length) { // No profiles. Add the default one. @@ -297,29 +298,29 @@ void PR_LoadProfiles(void) return; } - save_p = savebuffer; + save.p = save.buffer; - if (strncmp(PROFILEHEADER, (const char *)savebuffer, headerlen)) + if (strncmp(PROFILEHEADER, (const char *)save.buffer, headerlen)) { const char *gdfolder = "the Ring Racers folder"; if (strcmp(srb2home,".")) gdfolder = srb2home; - Z_Free(savebuffer); - save_p = NULL; + Z_Free(save.buffer); + save.p = NULL; I_Error("Not a valid Profile file.\nDelete %s (maybe in %s) and try again.", PROFILESFILE, gdfolder); } - save_p += headerlen; + save.p += headerlen; - version = READUINT8(save_p); + version = READUINT8(save.p); if (version > PROFILEVER) { - Z_Free(savebuffer); - save_p = NULL; + Z_Free(save.buffer); + save.p = NULL; I_Error("Existing %s is from the future! (expected %d, got %d)", PROFILESFILE, PROFILEVER, version); } - numprofiles = READUINT8(save_p); + numprofiles = READUINT8(save.p); if (numprofiles > MAXPROFILES) numprofiles = MAXPROFILES; @@ -331,12 +332,12 @@ void PR_LoadProfiles(void) profilesList[i]->version = PROFILEVER; // Names. - READSTRINGN(save_p, profilesList[i]->profilename, PROFILENAMELEN); - READSTRINGN(save_p, profilesList[i]->playername, MAXPLAYERNAME); + READSTRINGN(save.p, profilesList[i]->profilename, PROFILENAMELEN); + READSTRINGN(save.p, profilesList[i]->playername, MAXPLAYERNAME); // Character and colour. - READSTRINGN(save_p, profilesList[i]->skinname, SKINNAMESIZE); - profilesList[i]->color = READUINT16(save_p); + READSTRINGN(save.p, profilesList[i]->skinname, SKINNAMESIZE); + profilesList[i]->color = READUINT16(save.p); if (profilesList[i]->color == SKINCOLOR_NONE) { @@ -349,8 +350,8 @@ void PR_LoadProfiles(void) } // Follower and colour. - READSTRINGN(save_p, profilesList[i]->follower, SKINNAMESIZE); - profilesList[i]->followercolor = READUINT16(save_p); + READSTRINGN(save.p, profilesList[i]->follower, SKINNAMESIZE); + profilesList[i]->followercolor = READUINT16(save.p); if (profilesList[i]->followercolor == FOLLOWERCOLOR_MATCH || profilesList[i]->followercolor == FOLLOWERCOLOR_OPPOSITE) @@ -367,7 +368,7 @@ void PR_LoadProfiles(void) // PWR. for (j = 0; j < PWRLV_NUMTYPES; j++) { - profilesList[i]->powerlevels[j] = READUINT16(save_p); + profilesList[i]->powerlevels[j] = READUINT16(save.p); if (profilesList[i]->powerlevels[j] < PWRLVRECORD_MIN || profilesList[i]->powerlevels[j] > PWRLVRECORD_MAX) { @@ -377,7 +378,7 @@ void PR_LoadProfiles(void) } // Consvars. - profilesList[i]->kickstartaccel = (boolean)READUINT8(save_p); + profilesList[i]->kickstartaccel = (boolean)READUINT8(save.p); // Controls. for (j = 0; j < num_gamecontrols; j++) @@ -396,7 +397,7 @@ void PR_LoadProfiles(void) for (k = 0; k < MAXINPUTMAPPING; k++) { - profilesList[i]->controls[j][k] = READINT32(save_p); + profilesList[i]->controls[j][k] = READINT32(save.p); } } } diff --git a/src/lua_script.c b/src/lua_script.c index 4f9eab969..5f12ab416 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -41,6 +41,8 @@ lua_State *gL = NULL; int hook_defrosting; +static UINT8 **lua_save_p = NULL; // FIXME: Remove this horrible hack + // List of internal libraries to load from SRB2 static lua_CFunction liblist[] = { LUA_EnumLib, // global metatable for enums @@ -1248,10 +1250,10 @@ static UINT8 ArchiveValue(UINT8 **p, int TABLESINDEX, int myindex) { polyobj_t *polyobj = *((polyobj_t **)lua_touserdata(gL, myindex)); if (!polyobj) - WRITEUINT8(save_p, ARCH_NULL); + WRITEUINT8(*p, ARCH_NULL); else { - WRITEUINT8(save_p, ARCH_POLYOBJ); - WRITEUINT16(save_p, polyobj-PolyObjects); + WRITEUINT8(*p, ARCH_POLYOBJ); + WRITEUINT16(*p, polyobj-PolyObjects); } break; } @@ -1355,7 +1357,7 @@ static int NetArchive(lua_State *L) int TABLESINDEX = lua_upvalueindex(1); int i, n = lua_gettop(L); for (i = 1; i <= n; i++) - ArchiveValue(&save_p, TABLESINDEX, i); + ArchiveValue(lua_save_p, TABLESINDEX, i); return n; } @@ -1517,7 +1519,7 @@ static UINT8 UnArchiveValue(UINT8 **p, int TABLESINDEX) break; } case ARCH_POLYOBJ: - LUA_PushUserdata(gL, &PolyObjects[READUINT16(save_p)], META_POLYOBJ); + LUA_PushUserdata(gL, &PolyObjects[READUINT16(*p)], META_POLYOBJ); break; case ARCH_SLOPE: LUA_PushUserdata(gL, P_SlopeById(READUINT16(*p)), META_SLOPE); @@ -1568,7 +1570,7 @@ static int NetUnArchive(lua_State *L) int TABLESINDEX = lua_upvalueindex(1); int i, n = lua_gettop(L); for (i = 1; i <= n; i++) - UnArchiveValue(&save_p, TABLESINDEX); + UnArchiveValue(lua_save_p, TABLESINDEX); return n; } @@ -1602,7 +1604,7 @@ static void UnArchiveTables(UINT8 **p) lua_rawset(gL, -3); } - metatableid = READUINT16(save_p); + metatableid = READUINT16(*p); if (metatableid) { // setmetatable(table, registry.metatables[metatableid]) @@ -1626,7 +1628,7 @@ void LUA_Step(void) lua_gc(gL, LUA_GCSTEP, 1); } -void LUA_Archive(UINT8 **p) +void LUA_Archive(UINT8 **p, boolean network) { INT32 i; thinker_t *th; @@ -1642,7 +1644,7 @@ void LUA_Archive(UINT8 **p) ArchiveExtVars(p, &players[i], "player"); } - if (p == &save_p) + if (network == true) { if (gamestate == GS_LEVEL) { @@ -1659,6 +1661,7 @@ void LUA_Archive(UINT8 **p) WRITEUINT32(*p, UINT32_MAX); // end of mobjs marker, replaces mobjnum. + lua_save_p = p; LUA_HookNetArchive(NetArchive); // call the NetArchive hook in archive mode } @@ -1668,7 +1671,7 @@ void LUA_Archive(UINT8 **p) lua_pop(gL, 1); // pop tables } -void LUA_UnArchive(UINT8 **p) +void LUA_UnArchive(UINT8 **p, boolean network) { UINT32 mobjnum; INT32 i; @@ -1684,7 +1687,7 @@ void LUA_UnArchive(UINT8 **p) UnArchiveExtVars(p, &players[i]); } - if (p == &save_p) + if (network == true) { do { mobjnum = READUINT32(*p); // read a mobjnum @@ -1698,6 +1701,7 @@ void LUA_UnArchive(UINT8 **p) } } while(mobjnum != UINT32_MAX); // repeat until end of mobjs marker. + lua_save_p = p; LUA_HookNetArchive(NetUnArchive); // call the NetArchive hook in unarchive mode } diff --git a/src/lua_script.h b/src/lua_script.h index 632a1da3d..51a9af85e 100644 --- a/src/lua_script.h +++ b/src/lua_script.h @@ -57,8 +57,8 @@ void LUA_DumpFile(const char *filename); #endif fixed_t LUA_EvalMath(const char *word); void LUA_Step(void); -void LUA_Archive(UINT8 **p); -void LUA_UnArchive(UINT8 **p); +void LUA_Archive(UINT8 **p, boolean network); +void LUA_UnArchive(UINT8 **p, boolean network); int LUA_PushGlobals(lua_State *L, const char *word); int LUA_WriteGlobals(lua_State *L, const char *word); diff --git a/src/p_saveg.c b/src/p_saveg.c index 118706d4a..ed009c189 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -43,7 +43,6 @@ #include "k_terrain.h" savedata_t savedata; -UINT8 *save_p; // Block UINT32s to attempt to ensure that the correct data is // being sent and received @@ -69,7 +68,7 @@ typedef enum STUMBLE = 0x40, } player_saveflags; -static inline void P_ArchivePlayer(void) +static inline void P_ArchivePlayer(savebuffer_t *save) { const player_t *player = &players[consoleplayer]; INT16 skininfo = player->skin; @@ -77,39 +76,39 @@ static inline void P_ArchivePlayer(void) if (pllives < startinglivesbalance[numgameovers]) // Bump up to 3 lives if the player pllives = startinglivesbalance[numgameovers]; // has less than that. - WRITEUINT16(save_p, skininfo); - WRITEUINT8(save_p, numgameovers); - WRITESINT8(save_p, pllives); - WRITEUINT32(save_p, player->score); + WRITEUINT16(save->p, skininfo); + WRITEUINT8(save->p, numgameovers); + WRITESINT8(save->p, pllives); + WRITEUINT32(save->p, player->score); } -static inline void P_UnArchivePlayer(void) +static inline void P_UnArchivePlayer(savebuffer_t *save) { - INT16 skininfo = READUINT16(save_p); + INT16 skininfo = READUINT16(save->p); savedata.skin = skininfo; - savedata.numgameovers = READUINT8(save_p); - savedata.lives = READSINT8(save_p); - savedata.score = READUINT32(save_p); + savedata.numgameovers = READUINT8(save->p); + savedata.lives = READSINT8(save->p); + savedata.score = READUINT32(save->p); } -static void P_NetArchivePlayers(void) +static void P_NetArchivePlayers(savebuffer_t *save) { INT32 i, j; UINT16 flags; size_t q; - WRITEUINT32(save_p, ARCHIVEBLOCK_PLAYERS); + WRITEUINT32(save->p, ARCHIVEBLOCK_PLAYERS); for (i = 0; i < MAXPLAYERS; i++) { - WRITESINT8(save_p, (SINT8)adminplayers[i]); + WRITESINT8(save->p, (SINT8)adminplayers[i]); for (j = 0; j < PWRLV_NUMTYPES; j++) { - WRITEINT16(save_p, clientpowerlevels[i][j]); + WRITEINT16(save->p, clientpowerlevels[i][j]); } - WRITEINT16(save_p, clientPowerAdd[i]); + WRITEINT16(save->p, clientPowerAdd[i]); if (!playeringame[i]) continue; @@ -118,84 +117,84 @@ static void P_NetArchivePlayers(void) // no longer send ticcmds - WRITESTRINGN(save_p, player_names[i], MAXPLAYERNAME); + WRITESTRINGN(save->p, player_names[i], MAXPLAYERNAME); - WRITEUINT8(save_p, playerconsole[i]); - WRITEINT32(save_p, splitscreen_invitations[i]); - WRITEINT32(save_p, splitscreen_party_size[i]); - WRITEINT32(save_p, splitscreen_original_party_size[i]); + WRITEUINT8(save->p, playerconsole[i]); + WRITEINT32(save->p, splitscreen_invitations[i]); + WRITEINT32(save->p, splitscreen_party_size[i]); + WRITEINT32(save->p, splitscreen_original_party_size[i]); for (j = 0; j < MAXSPLITSCREENPLAYERS; ++j) { - WRITEINT32(save_p, splitscreen_party[i][j]); - WRITEINT32(save_p, splitscreen_original_party[i][j]); + WRITEINT32(save->p, splitscreen_party[i][j]); + WRITEINT32(save->p, splitscreen_original_party[i][j]); } - WRITEINT16(save_p, players[i].steering); - WRITEANGLE(save_p, players[i].angleturn); - WRITEANGLE(save_p, players[i].aiming); - WRITEANGLE(save_p, players[i].drawangle); - WRITEANGLE(save_p, players[i].viewrollangle); - WRITEANGLE(save_p, players[i].tilt); - WRITEANGLE(save_p, players[i].awayviewaiming); - WRITEINT32(save_p, players[i].awayviewtics); + WRITEINT16(save->p, players[i].steering); + WRITEANGLE(save->p, players[i].angleturn); + WRITEANGLE(save->p, players[i].aiming); + WRITEANGLE(save->p, players[i].drawangle); + WRITEANGLE(save->p, players[i].viewrollangle); + WRITEANGLE(save->p, players[i].tilt); + WRITEANGLE(save->p, players[i].awayviewaiming); + WRITEINT32(save->p, players[i].awayviewtics); - WRITEUINT8(save_p, players[i].playerstate); - WRITEUINT32(save_p, players[i].pflags); - WRITEUINT8(save_p, players[i].panim); - WRITEUINT8(save_p, players[i].spectator); - WRITEUINT32(save_p, players[i].spectatewait); + WRITEUINT8(save->p, players[i].playerstate); + WRITEUINT32(save->p, players[i].pflags); + WRITEUINT8(save->p, players[i].panim); + WRITEUINT8(save->p, players[i].spectator); + WRITEUINT32(save->p, players[i].spectatewait); - WRITEUINT16(save_p, players[i].flashpal); - WRITEUINT16(save_p, players[i].flashcount); + WRITEUINT16(save->p, players[i].flashpal); + WRITEUINT16(save->p, players[i].flashcount); - WRITEUINT8(save_p, players[i].skincolor); - WRITEINT32(save_p, players[i].skin); + WRITEUINT8(save->p, players[i].skincolor); + WRITEINT32(save->p, players[i].skin); for (j = 0; j < MAXAVAILABILITY; j++) { - WRITEUINT8(save_p, players[i].availabilities[j]); + WRITEUINT8(save->p, players[i].availabilities[j]); } - WRITEUINT8(save_p, players[i].fakeskin); - WRITEUINT8(save_p, players[i].lastfakeskin); - WRITEUINT32(save_p, players[i].score); - WRITESINT8(save_p, players[i].lives); - WRITESINT8(save_p, players[i].xtralife); - WRITEFIXED(save_p, players[i].speed); - WRITEFIXED(save_p, players[i].lastspeed); - WRITEINT32(save_p, players[i].deadtimer); - WRITEUINT32(save_p, players[i].exiting); + WRITEUINT8(save->p, players[i].fakeskin); + WRITEUINT8(save->p, players[i].lastfakeskin); + WRITEUINT32(save->p, players[i].score); + WRITESINT8(save->p, players[i].lives); + WRITESINT8(save->p, players[i].xtralife); + WRITEFIXED(save->p, players[i].speed); + WRITEFIXED(save->p, players[i].lastspeed); + WRITEINT32(save->p, players[i].deadtimer); + WRITEUINT32(save->p, players[i].exiting); //////////////////////////// // Conveyor Belt Movement // //////////////////////////// - WRITEFIXED(save_p, players[i].cmomx); // Conveyor momx - WRITEFIXED(save_p, players[i].cmomy); // Conveyor momy - WRITEFIXED(save_p, players[i].rmomx); // "Real" momx (momx - cmomx) - WRITEFIXED(save_p, players[i].rmomy); // "Real" momy (momy - cmomy) + WRITEFIXED(save->p, players[i].cmomx); // Conveyor momx + WRITEFIXED(save->p, players[i].cmomy); // Conveyor momy + WRITEFIXED(save->p, players[i].rmomx); // "Real" momx (momx - cmomx) + WRITEFIXED(save->p, players[i].rmomy); // "Real" momy (momy - cmomy) - WRITEINT16(save_p, players[i].totalring); - WRITEUINT32(save_p, players[i].realtime); - WRITEUINT8(save_p, players[i].laps); - WRITEUINT8(save_p, players[i].latestlap); - WRITEINT32(save_p, players[i].starpostnum); + WRITEINT16(save->p, players[i].totalring); + WRITEUINT32(save->p, players[i].realtime); + WRITEUINT8(save->p, players[i].laps); + WRITEUINT8(save->p, players[i].latestlap); + WRITEINT32(save->p, players[i].starpostnum); - WRITEUINT8(save_p, players[i].ctfteam); + WRITEUINT8(save->p, players[i].ctfteam); - WRITEUINT8(save_p, players[i].checkskip); + WRITEUINT8(save->p, players[i].checkskip); - WRITEINT16(save_p, players[i].lastsidehit); - WRITEINT16(save_p, players[i].lastlinehit); + WRITEINT16(save->p, players[i].lastsidehit); + WRITEINT16(save->p, players[i].lastlinehit); - WRITEINT32(save_p, players[i].onconveyor); + WRITEINT32(save->p, players[i].onconveyor); - WRITEUINT8(save_p, players[i].timeshit); - WRITEUINT8(save_p, players[i].timeshitprev); + WRITEUINT8(save->p, players[i].timeshit); + WRITEUINT8(save->p, players[i].timeshitprev); - WRITEUINT32(save_p, players[i].jointime); + WRITEUINT32(save->p, players[i].jointime); - WRITEUINT8(save_p, players[i].splitscreenindex); + WRITEUINT8(save->p, players[i].splitscreenindex); if (players[i].awayviewmobj) flags |= AWAYVIEW; @@ -218,270 +217,270 @@ static void P_NetArchivePlayers(void) if (players[i].stumbleIndicator) flags |= STUMBLE; - WRITEUINT16(save_p, flags); + WRITEUINT16(save->p, flags); if (flags & SKYBOXVIEW) - WRITEUINT32(save_p, players[i].skybox.viewpoint->mobjnum); + WRITEUINT32(save->p, players[i].skybox.viewpoint->mobjnum); if (flags & SKYBOXCENTER) - WRITEUINT32(save_p, players[i].skybox.centerpoint->mobjnum); + WRITEUINT32(save->p, players[i].skybox.centerpoint->mobjnum); if (flags & AWAYVIEW) - WRITEUINT32(save_p, players[i].awayviewmobj->mobjnum); + WRITEUINT32(save->p, players[i].awayviewmobj->mobjnum); if (flags & FOLLOWITEM) - WRITEUINT32(save_p, players[i].followmobj->mobjnum); + WRITEUINT32(save->p, players[i].followmobj->mobjnum); if (flags & HOVERHYUDORO) - WRITEUINT32(save_p, players[i].hoverhyudoro->mobjnum); + WRITEUINT32(save->p, players[i].hoverhyudoro->mobjnum); if (flags & STUMBLE) - WRITEUINT32(save_p, players[i].stumbleIndicator->mobjnum); + WRITEUINT32(save->p, players[i].stumbleIndicator->mobjnum); - WRITEUINT32(save_p, (UINT32)players[i].followitem); + WRITEUINT32(save->p, (UINT32)players[i].followitem); - WRITEUINT32(save_p, players[i].charflags); + WRITEUINT32(save->p, players[i].charflags); // SRB2kart - WRITEUINT8(save_p, players[i].kartspeed); - WRITEUINT8(save_p, players[i].kartweight); + WRITEUINT8(save->p, players[i].kartspeed); + WRITEUINT8(save->p, players[i].kartweight); - WRITEUINT8(save_p, players[i].followerskin); - WRITEUINT8(save_p, players[i].followerready); // booleans are really just numbers eh?? - WRITEUINT16(save_p, players[i].followercolor); + WRITEUINT8(save->p, players[i].followerskin); + WRITEUINT8(save->p, players[i].followerready); // booleans are really just numbers eh?? + WRITEUINT16(save->p, players[i].followercolor); if (flags & FOLLOWER) - WRITEUINT32(save_p, players[i].follower->mobjnum); + WRITEUINT32(save->p, players[i].follower->mobjnum); - WRITEUINT16(save_p, players[i].nocontrol); - WRITEUINT8(save_p, players[i].carry); - WRITEUINT16(save_p, players[i].dye); + WRITEUINT16(save->p, players[i].nocontrol); + WRITEUINT8(save->p, players[i].carry); + WRITEUINT16(save->p, players[i].dye); - WRITEUINT8(save_p, players[i].position); - WRITEUINT8(save_p, players[i].oldposition); - WRITEUINT8(save_p, players[i].positiondelay); - WRITEUINT32(save_p, players[i].distancetofinish); - WRITEUINT32(save_p, K_GetWaypointHeapIndex(players[i].currentwaypoint)); - WRITEUINT32(save_p, K_GetWaypointHeapIndex(players[i].nextwaypoint)); - WRITEUINT32(save_p, players[i].airtime); - WRITEUINT8(save_p, players[i].startboost); + WRITEUINT8(save->p, players[i].position); + WRITEUINT8(save->p, players[i].oldposition); + WRITEUINT8(save->p, players[i].positiondelay); + WRITEUINT32(save->p, players[i].distancetofinish); + WRITEUINT32(save->p, K_GetWaypointHeapIndex(players[i].currentwaypoint)); + WRITEUINT32(save->p, K_GetWaypointHeapIndex(players[i].nextwaypoint)); + WRITEUINT32(save->p, players[i].airtime); + WRITEUINT8(save->p, players[i].startboost); - WRITEUINT16(save_p, players[i].flashing); - WRITEUINT16(save_p, players[i].spinouttimer); - WRITEUINT8(save_p, players[i].spinouttype); - WRITEUINT8(save_p, players[i].instashield); - WRITEINT32(save_p, players[i].invulnhitlag); - WRITEUINT8(save_p, players[i].wipeoutslow); - WRITEUINT8(save_p, players[i].justbumped); - WRITEUINT8(save_p, players[i].tumbleBounces); - WRITEUINT16(save_p, players[i].tumbleHeight); + WRITEUINT16(save->p, players[i].flashing); + WRITEUINT16(save->p, players[i].spinouttimer); + WRITEUINT8(save->p, players[i].spinouttype); + WRITEUINT8(save->p, players[i].instashield); + WRITEINT32(save->p, players[i].invulnhitlag); + WRITEUINT8(save->p, players[i].wipeoutslow); + WRITEUINT8(save->p, players[i].justbumped); + WRITEUINT8(save->p, players[i].tumbleBounces); + WRITEUINT16(save->p, players[i].tumbleHeight); - WRITEUINT8(save_p, players[i].justDI); - WRITEUINT8(save_p, players[i].flipDI); + WRITEUINT8(save->p, players[i].justDI); + WRITEUINT8(save->p, players[i].flipDI); - WRITESINT8(save_p, players[i].drift); - WRITEFIXED(save_p, players[i].driftcharge); - WRITEUINT8(save_p, players[i].driftboost); - WRITEUINT8(save_p, players[i].strongdriftboost); + WRITESINT8(save->p, players[i].drift); + WRITEFIXED(save->p, players[i].driftcharge); + WRITEUINT8(save->p, players[i].driftboost); + WRITEUINT8(save->p, players[i].strongdriftboost); - WRITEUINT16(save_p, players[i].gateBoost); - WRITEUINT8(save_p, players[i].gateSound); + WRITEUINT16(save->p, players[i].gateBoost); + WRITEUINT8(save->p, players[i].gateSound); - WRITESINT8(save_p, players[i].aizdriftstrat); - WRITEINT32(save_p, players[i].aizdrifttilt); - WRITEINT32(save_p, players[i].aizdriftturn); + WRITESINT8(save->p, players[i].aizdriftstrat); + WRITEINT32(save->p, players[i].aizdrifttilt); + WRITEINT32(save->p, players[i].aizdriftturn); - WRITEINT32(save_p, players[i].underwatertilt); + WRITEINT32(save->p, players[i].underwatertilt); - WRITEFIXED(save_p, players[i].offroad); + WRITEFIXED(save->p, players[i].offroad); - WRITEUINT16(save_p, players[i].tiregrease); - WRITEUINT16(save_p, players[i].springstars); - WRITEUINT16(save_p, players[i].springcolor); - WRITEUINT8(save_p, players[i].dashpadcooldown); + WRITEUINT16(save->p, players[i].tiregrease); + WRITEUINT16(save->p, players[i].springstars); + WRITEUINT16(save->p, players[i].springcolor); + WRITEUINT8(save->p, players[i].dashpadcooldown); - WRITEUINT16(save_p, players[i].spindash); - WRITEFIXED(save_p, players[i].spindashspeed); - WRITEUINT8(save_p, players[i].spindashboost); + WRITEUINT16(save->p, players[i].spindash); + WRITEFIXED(save->p, players[i].spindashspeed); + WRITEUINT8(save->p, players[i].spindashboost); - WRITEFIXED(save_p, players[i].fastfall); + WRITEFIXED(save->p, players[i].fastfall); - WRITEUINT8(save_p, players[i].numboosts); - WRITEFIXED(save_p, players[i].boostpower); - WRITEFIXED(save_p, players[i].speedboost); - WRITEFIXED(save_p, players[i].accelboost); - WRITEFIXED(save_p, players[i].handleboost); - WRITEANGLE(save_p, players[i].boostangle); + WRITEUINT8(save->p, players[i].numboosts); + WRITEFIXED(save->p, players[i].boostpower); + WRITEFIXED(save->p, players[i].speedboost); + WRITEFIXED(save->p, players[i].accelboost); + WRITEFIXED(save->p, players[i].handleboost); + WRITEANGLE(save->p, players[i].boostangle); - WRITEFIXED(save_p, players[i].draftpower); - WRITEUINT16(save_p, players[i].draftleeway); - WRITESINT8(save_p, players[i].lastdraft); + WRITEFIXED(save->p, players[i].draftpower); + WRITEUINT16(save->p, players[i].draftleeway); + WRITESINT8(save->p, players[i].lastdraft); - WRITEUINT8(save_p, players[i].tripwireState); - WRITEUINT8(save_p, players[i].tripwirePass); - WRITEUINT16(save_p, players[i].tripwireLeniency); + WRITEUINT8(save->p, players[i].tripwireState); + WRITEUINT8(save->p, players[i].tripwirePass); + WRITEUINT16(save->p, players[i].tripwireLeniency); - WRITESINT8(save_p, players[i].itemtype); - WRITEUINT8(save_p, players[i].itemamount); - WRITESINT8(save_p, players[i].throwdir); + WRITESINT8(save->p, players[i].itemtype); + WRITEUINT8(save->p, players[i].itemamount); + WRITESINT8(save->p, players[i].throwdir); - WRITEUINT8(save_p, players[i].sadtimer); + WRITEUINT8(save->p, players[i].sadtimer); - WRITESINT8(save_p, players[i].rings); - WRITEUINT8(save_p, players[i].pickuprings); - WRITEUINT8(save_p, players[i].ringdelay); - WRITEUINT16(save_p, players[i].ringboost); - WRITEUINT8(save_p, players[i].sparkleanim); - WRITEUINT16(save_p, players[i].superring); + WRITESINT8(save->p, players[i].rings); + WRITEUINT8(save->p, players[i].pickuprings); + WRITEUINT8(save->p, players[i].ringdelay); + WRITEUINT16(save->p, players[i].ringboost); + WRITEUINT8(save->p, players[i].sparkleanim); + WRITEUINT16(save->p, players[i].superring); - WRITEUINT8(save_p, players[i].curshield); - WRITEUINT8(save_p, players[i].bubblecool); - WRITEUINT8(save_p, players[i].bubbleblowup); - WRITEUINT16(save_p, players[i].flamedash); - WRITEUINT16(save_p, players[i].flamemeter); - WRITEUINT8(save_p, players[i].flamelength); + WRITEUINT8(save->p, players[i].curshield); + WRITEUINT8(save->p, players[i].bubblecool); + WRITEUINT8(save->p, players[i].bubbleblowup); + WRITEUINT16(save->p, players[i].flamedash); + WRITEUINT16(save->p, players[i].flamemeter); + WRITEUINT8(save->p, players[i].flamelength); - WRITEUINT16(save_p, players[i].ballhogcharge); + WRITEUINT16(save->p, players[i].ballhogcharge); - WRITEUINT16(save_p, players[i].hyudorotimer); - WRITESINT8(save_p, players[i].stealingtimer); + WRITEUINT16(save->p, players[i].hyudorotimer); + WRITESINT8(save->p, players[i].stealingtimer); - WRITEUINT16(save_p, players[i].sneakertimer); - WRITEUINT8(save_p, players[i].numsneakers); - WRITEUINT8(save_p, players[i].floorboost); + WRITEUINT16(save->p, players[i].sneakertimer); + WRITEUINT8(save->p, players[i].numsneakers); + WRITEUINT8(save->p, players[i].floorboost); - WRITEINT16(save_p, players[i].growshrinktimer); - WRITEUINT16(save_p, players[i].rocketsneakertimer); - WRITEUINT16(save_p, players[i].invincibilitytimer); + WRITEINT16(save->p, players[i].growshrinktimer); + WRITEUINT16(save->p, players[i].rocketsneakertimer); + WRITEUINT16(save->p, players[i].invincibilitytimer); - WRITEUINT8(save_p, players[i].eggmanexplode); - WRITESINT8(save_p, players[i].eggmanblame); + WRITEUINT8(save->p, players[i].eggmanexplode); + WRITESINT8(save->p, players[i].eggmanblame); - WRITEUINT8(save_p, players[i].bananadrag); + WRITEUINT8(save->p, players[i].bananadrag); - WRITESINT8(save_p, players[i].lastjawztarget); - WRITEUINT8(save_p, players[i].jawztargetdelay); + WRITESINT8(save->p, players[i].lastjawztarget); + WRITEUINT8(save->p, players[i].jawztargetdelay); - WRITEUINT8(save_p, players[i].confirmVictim); - WRITEUINT8(save_p, players[i].confirmVictimDelay); + WRITEUINT8(save->p, players[i].confirmVictim); + WRITEUINT8(save->p, players[i].confirmVictimDelay); - WRITEUINT8(save_p, players[i].trickpanel); - WRITEUINT8(save_p, players[i].tricktime); - WRITEUINT32(save_p, players[i].trickboostpower); - WRITEUINT8(save_p, players[i].trickboostdecay); - WRITEUINT8(save_p, players[i].trickboost); + WRITEUINT8(save->p, players[i].trickpanel); + WRITEUINT8(save->p, players[i].tricktime); + WRITEUINT32(save->p, players[i].trickboostpower); + WRITEUINT8(save->p, players[i].trickboostdecay); + WRITEUINT8(save->p, players[i].trickboost); - WRITEUINT32(save_p, players[i].ebrakefor); + WRITEUINT32(save->p, players[i].ebrakefor); - WRITEUINT32(save_p, players[i].roundscore); - WRITEUINT8(save_p, players[i].emeralds); - WRITEUINT8(save_p, players[i].bumpers); - WRITEINT16(save_p, players[i].karmadelay); - WRITEUINT32(save_p, players[i].overtimekarma); - WRITEINT16(save_p, players[i].spheres); - WRITEUINT32(save_p, players[i].spheredigestion); + WRITEUINT32(save->p, players[i].roundscore); + WRITEUINT8(save->p, players[i].emeralds); + WRITEUINT8(save->p, players[i].bumpers); + WRITEINT16(save->p, players[i].karmadelay); + WRITEUINT32(save->p, players[i].overtimekarma); + WRITEINT16(save->p, players[i].spheres); + WRITEUINT32(save->p, players[i].spheredigestion); - WRITESINT8(save_p, players[i].glanceDir); + WRITESINT8(save->p, players[i].glanceDir); - WRITEUINT8(save_p, players[i].typing_timer); - WRITEUINT8(save_p, players[i].typing_duration); + WRITEUINT8(save->p, players[i].typing_timer); + WRITEUINT8(save->p, players[i].typing_duration); - WRITEUINT8(save_p, players[i].kickstartaccel); + WRITEUINT8(save->p, players[i].kickstartaccel); - WRITEUINT8(save_p, players[i].stairjank); - WRITEUINT8(save_p, players[i].topdriftheld); - WRITEUINT8(save_p, players[i].topinfirst); + WRITEUINT8(save->p, players[i].stairjank); + WRITEUINT8(save->p, players[i].topdriftheld); + WRITEUINT8(save->p, players[i].topinfirst); - WRITEUINT8(save_p, players[i].shrinkLaserDelay); + WRITEUINT8(save->p, players[i].shrinkLaserDelay); // respawnvars_t - WRITEUINT8(save_p, players[i].respawn.state); - WRITEUINT32(save_p, K_GetWaypointHeapIndex(players[i].respawn.wp)); - WRITEFIXED(save_p, players[i].respawn.pointx); - WRITEFIXED(save_p, players[i].respawn.pointy); - WRITEFIXED(save_p, players[i].respawn.pointz); - WRITEUINT8(save_p, players[i].respawn.flip); - WRITEUINT32(save_p, players[i].respawn.timer); - WRITEUINT32(save_p, players[i].respawn.airtimer); - WRITEUINT32(save_p, players[i].respawn.distanceleft); - WRITEUINT32(save_p, players[i].respawn.dropdash); - WRITEUINT8(save_p, players[i].respawn.truedeath); - WRITEUINT8(save_p, players[i].respawn.manual); + WRITEUINT8(save->p, players[i].respawn.state); + WRITEUINT32(save->p, K_GetWaypointHeapIndex(players[i].respawn.wp)); + WRITEFIXED(save->p, players[i].respawn.pointx); + WRITEFIXED(save->p, players[i].respawn.pointy); + WRITEFIXED(save->p, players[i].respawn.pointz); + WRITEUINT8(save->p, players[i].respawn.flip); + WRITEUINT32(save->p, players[i].respawn.timer); + WRITEUINT32(save->p, players[i].respawn.airtimer); + WRITEUINT32(save->p, players[i].respawn.distanceleft); + WRITEUINT32(save->p, players[i].respawn.dropdash); + WRITEUINT8(save->p, players[i].respawn.truedeath); + WRITEUINT8(save->p, players[i].respawn.manual); // botvars_t - WRITEUINT8(save_p, players[i].botvars.difficulty); - WRITEUINT8(save_p, players[i].botvars.diffincrease); - WRITEUINT8(save_p, players[i].botvars.rival); - WRITEFIXED(save_p, players[i].botvars.rubberband); - WRITEUINT16(save_p, players[i].botvars.controller); - WRITEUINT32(save_p, players[i].botvars.itemdelay); - WRITEUINT32(save_p, players[i].botvars.itemconfirm); - WRITESINT8(save_p, players[i].botvars.turnconfirm); - WRITEUINT32(save_p, players[i].botvars.spindashconfirm); + WRITEUINT8(save->p, players[i].botvars.difficulty); + WRITEUINT8(save->p, players[i].botvars.diffincrease); + WRITEUINT8(save->p, players[i].botvars.rival); + WRITEFIXED(save->p, players[i].botvars.rubberband); + WRITEUINT16(save->p, players[i].botvars.controller); + WRITEUINT32(save->p, players[i].botvars.itemdelay); + WRITEUINT32(save->p, players[i].botvars.itemconfirm); + WRITESINT8(save->p, players[i].botvars.turnconfirm); + WRITEUINT32(save->p, players[i].botvars.spindashconfirm); // itemroulette_t - WRITEUINT8(save_p, players[i].itemRoulette.active); + WRITEUINT8(save->p, players[i].itemRoulette.active); #ifdef ITEM_LIST_SIZE - WRITEUINT32(save_p, players[i].itemRoulette.itemListLen); + WRITEUINT32(save->p, players[i].itemRoulette.itemListLen); for (q = 0; q < ITEM_LIST_SIZE; q++) { if (q >= players[i].itemRoulette.itemListLen) { - WRITESINT8(save_p, KITEM_NONE); + WRITESINT8(save->p, KITEM_NONE); } else { - WRITESINT8(save_p, players[i].itemRoulette.itemList[q]); + WRITESINT8(save->p, players[i].itemRoulette.itemList[q]); } } #else if (players[i].itemRoulette.itemList == NULL) { - WRITEUINT32(save_p, 0); - WRITEUINT32(save_p, 0); + WRITEUINT32(save->p, 0); + WRITEUINT32(save->p, 0); } else { - WRITEUINT32(save_p, players[i].itemRoulette.itemListCap); - WRITEUINT32(save_p, players[i].itemRoulette.itemListLen); + WRITEUINT32(save->p, players[i].itemRoulette.itemListCap); + WRITEUINT32(save->p, players[i].itemRoulette.itemListLen); for (q = 0; q < players[i].itemRoulette.itemListLen; q++) { - WRITESINT8(save_p, players[i].itemRoulette.itemList[q]); + WRITESINT8(save->p, players[i].itemRoulette.itemList[q]); } } #endif - WRITEUINT8(save_p, players[i].itemRoulette.useOdds); - WRITEUINT32(save_p, players[i].itemRoulette.dist); - WRITEUINT32(save_p, players[i].itemRoulette.index); - WRITEUINT8(save_p, players[i].itemRoulette.sound); - WRITEUINT32(save_p, players[i].itemRoulette.speed); - WRITEUINT32(save_p, players[i].itemRoulette.tics); - WRITEUINT32(save_p, players[i].itemRoulette.elapsed); - WRITEUINT8(save_p, players[i].itemRoulette.eggman); + WRITEUINT8(save->p, players[i].itemRoulette.useOdds); + WRITEUINT32(save->p, players[i].itemRoulette.dist); + WRITEUINT32(save->p, players[i].itemRoulette.index); + WRITEUINT8(save->p, players[i].itemRoulette.sound); + WRITEUINT32(save->p, players[i].itemRoulette.speed); + WRITEUINT32(save->p, players[i].itemRoulette.tics); + WRITEUINT32(save->p, players[i].itemRoulette.elapsed); + WRITEUINT8(save->p, players[i].itemRoulette.eggman); } } -static void P_NetUnArchivePlayers(void) +static void P_NetUnArchivePlayers(savebuffer_t *save) { INT32 i, j; UINT16 flags; size_t q; - if (READUINT32(save_p) != ARCHIVEBLOCK_PLAYERS) + if (READUINT32(save->p) != ARCHIVEBLOCK_PLAYERS) I_Error("Bad $$$.sav at archive block Players"); for (i = 0; i < MAXPLAYERS; i++) { - adminplayers[i] = (INT32)READSINT8(save_p); + adminplayers[i] = (INT32)READSINT8(save->p); for (j = 0; j < PWRLV_NUMTYPES; j++) { - clientpowerlevels[i][j] = READINT16(save_p); + clientpowerlevels[i][j] = READINT16(save->p); } - clientPowerAdd[i] = READINT16(save_p); + clientPowerAdd[i] = READINT16(save->p); // Do NOT memset player struct to 0 // other areas may initialize data elsewhere @@ -491,299 +490,299 @@ static void P_NetUnArchivePlayers(void) // NOTE: sending tics should (hopefully) no longer be necessary - READSTRINGN(save_p, player_names[i], MAXPLAYERNAME); + READSTRINGN(save->p, player_names[i], MAXPLAYERNAME); - playerconsole[i] = READUINT8(save_p); - splitscreen_invitations[i] = READINT32(save_p); - splitscreen_party_size[i] = READINT32(save_p); - splitscreen_original_party_size[i] = READINT32(save_p); + playerconsole[i] = READUINT8(save->p); + splitscreen_invitations[i] = READINT32(save->p); + splitscreen_party_size[i] = READINT32(save->p); + splitscreen_original_party_size[i] = READINT32(save->p); for (j = 0; j < MAXSPLITSCREENPLAYERS; ++j) { - splitscreen_party[i][j] = READINT32(save_p); - splitscreen_original_party[i][j] = READINT32(save_p); + splitscreen_party[i][j] = READINT32(save->p); + splitscreen_original_party[i][j] = READINT32(save->p); } - players[i].steering = READINT16(save_p); - players[i].angleturn = READANGLE(save_p); - players[i].aiming = READANGLE(save_p); - players[i].drawangle = players[i].old_drawangle = READANGLE(save_p); - players[i].viewrollangle = READANGLE(save_p); - players[i].tilt = READANGLE(save_p); - players[i].awayviewaiming = READANGLE(save_p); - players[i].awayviewtics = READINT32(save_p); + players[i].steering = READINT16(save->p); + players[i].angleturn = READANGLE(save->p); + players[i].aiming = READANGLE(save->p); + players[i].drawangle = players[i].old_drawangle = READANGLE(save->p); + players[i].viewrollangle = READANGLE(save->p); + players[i].tilt = READANGLE(save->p); + players[i].awayviewaiming = READANGLE(save->p); + players[i].awayviewtics = READINT32(save->p); - players[i].playerstate = READUINT8(save_p); - players[i].pflags = READUINT32(save_p); - players[i].panim = READUINT8(save_p); - players[i].spectator = READUINT8(save_p); - players[i].spectatewait = READUINT32(save_p); + players[i].playerstate = READUINT8(save->p); + players[i].pflags = READUINT32(save->p); + players[i].panim = READUINT8(save->p); + players[i].spectator = READUINT8(save->p); + players[i].spectatewait = READUINT32(save->p); - players[i].flashpal = READUINT16(save_p); - players[i].flashcount = READUINT16(save_p); + players[i].flashpal = READUINT16(save->p); + players[i].flashcount = READUINT16(save->p); - players[i].skincolor = READUINT8(save_p); - players[i].skin = READINT32(save_p); + players[i].skincolor = READUINT8(save->p); + players[i].skin = READINT32(save->p); for (j = 0; j < MAXAVAILABILITY; j++) { - players[i].availabilities[j] = READUINT8(save_p); + players[i].availabilities[j] = READUINT8(save->p); } - players[i].fakeskin = READUINT8(save_p); - players[i].lastfakeskin = READUINT8(save_p); - players[i].score = READUINT32(save_p); - players[i].lives = READSINT8(save_p); - players[i].xtralife = READSINT8(save_p); // Ring Extra Life counter - players[i].speed = READFIXED(save_p); // Player's speed (distance formula of MOMX and MOMY values) - players[i].lastspeed = READFIXED(save_p); - players[i].deadtimer = READINT32(save_p); // End game if game over lasts too long - players[i].exiting = READUINT32(save_p); // Exitlevel timer + players[i].fakeskin = READUINT8(save->p); + players[i].lastfakeskin = READUINT8(save->p); + players[i].score = READUINT32(save->p); + players[i].lives = READSINT8(save->p); + players[i].xtralife = READSINT8(save->p); // Ring Extra Life counter + players[i].speed = READFIXED(save->p); // Player's speed (distance formula of MOMX and MOMY values) + players[i].lastspeed = READFIXED(save->p); + players[i].deadtimer = READINT32(save->p); // End game if game over lasts too long + players[i].exiting = READUINT32(save->p); // Exitlevel timer //////////////////////////// // Conveyor Belt Movement // //////////////////////////// - players[i].cmomx = READFIXED(save_p); // Conveyor momx - players[i].cmomy = READFIXED(save_p); // Conveyor momy - players[i].rmomx = READFIXED(save_p); // "Real" momx (momx - cmomx) - players[i].rmomy = READFIXED(save_p); // "Real" momy (momy - cmomy) + players[i].cmomx = READFIXED(save->p); // Conveyor momx + players[i].cmomy = READFIXED(save->p); // Conveyor momy + players[i].rmomx = READFIXED(save->p); // "Real" momx (momx - cmomx) + players[i].rmomy = READFIXED(save->p); // "Real" momy (momy - cmomy) - players[i].totalring = READINT16(save_p); // Total number of rings obtained for GP - players[i].realtime = READUINT32(save_p); // integer replacement for leveltime - players[i].laps = READUINT8(save_p); // Number of laps (optional) - players[i].latestlap = READUINT8(save_p); - players[i].starpostnum = READINT32(save_p); + players[i].totalring = READINT16(save->p); // Total number of rings obtained for GP + players[i].realtime = READUINT32(save->p); // integer replacement for leveltime + players[i].laps = READUINT8(save->p); // Number of laps (optional) + players[i].latestlap = READUINT8(save->p); + players[i].starpostnum = READINT32(save->p); - players[i].ctfteam = READUINT8(save_p); // 1 == Red, 2 == Blue + players[i].ctfteam = READUINT8(save->p); // 1 == Red, 2 == Blue - players[i].checkskip = READUINT8(save_p); + players[i].checkskip = READUINT8(save->p); - players[i].lastsidehit = READINT16(save_p); - players[i].lastlinehit = READINT16(save_p); + players[i].lastsidehit = READINT16(save->p); + players[i].lastlinehit = READINT16(save->p); - players[i].timeshit = READUINT8(save_p); - players[i].timeshitprev = READUINT8(save_p); + players[i].timeshit = READUINT8(save->p); + players[i].timeshitprev = READUINT8(save->p); - players[i].onconveyor = READINT32(save_p); + players[i].onconveyor = READINT32(save->p); - players[i].jointime = READUINT32(save_p); + players[i].jointime = READUINT32(save->p); - players[i].splitscreenindex = READUINT8(save_p); + players[i].splitscreenindex = READUINT8(save->p); - flags = READUINT16(save_p); + flags = READUINT16(save->p); if (flags & SKYBOXVIEW) - players[i].skybox.viewpoint = (mobj_t *)(size_t)READUINT32(save_p); + players[i].skybox.viewpoint = (mobj_t *)(size_t)READUINT32(save->p); if (flags & SKYBOXCENTER) - players[i].skybox.centerpoint = (mobj_t *)(size_t)READUINT32(save_p); + players[i].skybox.centerpoint = (mobj_t *)(size_t)READUINT32(save->p); if (flags & AWAYVIEW) - players[i].awayviewmobj = (mobj_t *)(size_t)READUINT32(save_p); + players[i].awayviewmobj = (mobj_t *)(size_t)READUINT32(save->p); if (flags & FOLLOWITEM) - players[i].followmobj = (mobj_t *)(size_t)READUINT32(save_p); + players[i].followmobj = (mobj_t *)(size_t)READUINT32(save->p); if (flags & HOVERHYUDORO) - players[i].hoverhyudoro = (mobj_t *)(size_t)READUINT32(save_p); + players[i].hoverhyudoro = (mobj_t *)(size_t)READUINT32(save->p); if (flags & STUMBLE) - players[i].stumbleIndicator = (mobj_t *)(size_t)READUINT32(save_p); + players[i].stumbleIndicator = (mobj_t *)(size_t)READUINT32(save->p); - players[i].followitem = (mobjtype_t)READUINT32(save_p); + players[i].followitem = (mobjtype_t)READUINT32(save->p); //SetPlayerSkinByNum(i, players[i].skin); - players[i].charflags = READUINT32(save_p); + players[i].charflags = READUINT32(save->p); // SRB2kart - players[i].kartspeed = READUINT8(save_p); - players[i].kartweight = READUINT8(save_p); + players[i].kartspeed = READUINT8(save->p); + players[i].kartweight = READUINT8(save->p); - players[i].followerskin = READUINT8(save_p); - players[i].followerready = READUINT8(save_p); - players[i].followercolor = READUINT16(save_p); + players[i].followerskin = READUINT8(save->p); + players[i].followerready = READUINT8(save->p); + players[i].followercolor = READUINT16(save->p); if (flags & FOLLOWER) - players[i].follower = (mobj_t *)(size_t)READUINT32(save_p); + players[i].follower = (mobj_t *)(size_t)READUINT32(save->p); - players[i].nocontrol = READUINT16(save_p); - players[i].carry = READUINT8(save_p); - players[i].dye = READUINT16(save_p); + players[i].nocontrol = READUINT16(save->p); + players[i].carry = READUINT8(save->p); + players[i].dye = READUINT16(save->p); - players[i].position = READUINT8(save_p); - players[i].oldposition = READUINT8(save_p); - players[i].positiondelay = READUINT8(save_p); - players[i].distancetofinish = READUINT32(save_p); - players[i].currentwaypoint = (waypoint_t *)(size_t)READUINT32(save_p); - players[i].nextwaypoint = (waypoint_t *)(size_t)READUINT32(save_p); - players[i].airtime = READUINT32(save_p); - players[i].startboost = READUINT8(save_p); + players[i].position = READUINT8(save->p); + players[i].oldposition = READUINT8(save->p); + players[i].positiondelay = READUINT8(save->p); + players[i].distancetofinish = READUINT32(save->p); + players[i].currentwaypoint = (waypoint_t *)(size_t)READUINT32(save->p); + players[i].nextwaypoint = (waypoint_t *)(size_t)READUINT32(save->p); + players[i].airtime = READUINT32(save->p); + players[i].startboost = READUINT8(save->p); - players[i].flashing = READUINT16(save_p); - players[i].spinouttimer = READUINT16(save_p); - players[i].spinouttype = READUINT8(save_p); - players[i].instashield = READUINT8(save_p); - players[i].invulnhitlag = READINT32(save_p); - players[i].wipeoutslow = READUINT8(save_p); - players[i].justbumped = READUINT8(save_p); - players[i].tumbleBounces = READUINT8(save_p); - players[i].tumbleHeight = READUINT16(save_p); + players[i].flashing = READUINT16(save->p); + players[i].spinouttimer = READUINT16(save->p); + players[i].spinouttype = READUINT8(save->p); + players[i].instashield = READUINT8(save->p); + players[i].invulnhitlag = READINT32(save->p); + players[i].wipeoutslow = READUINT8(save->p); + players[i].justbumped = READUINT8(save->p); + players[i].tumbleBounces = READUINT8(save->p); + players[i].tumbleHeight = READUINT16(save->p); - players[i].justDI = READUINT8(save_p); - players[i].flipDI = (boolean)READUINT8(save_p); + players[i].justDI = READUINT8(save->p); + players[i].flipDI = (boolean)READUINT8(save->p); - players[i].drift = READSINT8(save_p); - players[i].driftcharge = READFIXED(save_p); - players[i].driftboost = READUINT8(save_p); - players[i].strongdriftboost = READUINT8(save_p); + players[i].drift = READSINT8(save->p); + players[i].driftcharge = READFIXED(save->p); + players[i].driftboost = READUINT8(save->p); + players[i].strongdriftboost = READUINT8(save->p); - players[i].gateBoost = READUINT16(save_p); - players[i].gateSound = READUINT8(save_p); + players[i].gateBoost = READUINT16(save->p); + players[i].gateSound = READUINT8(save->p); - players[i].aizdriftstrat = READSINT8(save_p); - players[i].aizdrifttilt = READINT32(save_p); - players[i].aizdriftturn = READINT32(save_p); + players[i].aizdriftstrat = READSINT8(save->p); + players[i].aizdrifttilt = READINT32(save->p); + players[i].aizdriftturn = READINT32(save->p); - players[i].underwatertilt = READINT32(save_p); + players[i].underwatertilt = READINT32(save->p); - players[i].offroad = READFIXED(save_p); + players[i].offroad = READFIXED(save->p); - players[i].tiregrease = READUINT16(save_p); - players[i].springstars = READUINT16(save_p); - players[i].springcolor = READUINT16(save_p); - players[i].dashpadcooldown = READUINT8(save_p); + players[i].tiregrease = READUINT16(save->p); + players[i].springstars = READUINT16(save->p); + players[i].springcolor = READUINT16(save->p); + players[i].dashpadcooldown = READUINT8(save->p); - players[i].spindash = READUINT16(save_p); - players[i].spindashspeed = READFIXED(save_p); - players[i].spindashboost = READUINT8(save_p); + players[i].spindash = READUINT16(save->p); + players[i].spindashspeed = READFIXED(save->p); + players[i].spindashboost = READUINT8(save->p); - players[i].fastfall = READFIXED(save_p); + players[i].fastfall = READFIXED(save->p); - players[i].numboosts = READUINT8(save_p); - players[i].boostpower = READFIXED(save_p); - players[i].speedboost = READFIXED(save_p); - players[i].accelboost = READFIXED(save_p); - players[i].handleboost = READFIXED(save_p); - players[i].boostangle = READANGLE(save_p); + players[i].numboosts = READUINT8(save->p); + players[i].boostpower = READFIXED(save->p); + players[i].speedboost = READFIXED(save->p); + players[i].accelboost = READFIXED(save->p); + players[i].handleboost = READFIXED(save->p); + players[i].boostangle = READANGLE(save->p); - players[i].draftpower = READFIXED(save_p); - players[i].draftleeway = READUINT16(save_p); - players[i].lastdraft = READSINT8(save_p); + players[i].draftpower = READFIXED(save->p); + players[i].draftleeway = READUINT16(save->p); + players[i].lastdraft = READSINT8(save->p); - players[i].tripwireState = READUINT8(save_p); - players[i].tripwirePass = READUINT8(save_p); - players[i].tripwireLeniency = READUINT16(save_p); + players[i].tripwireState = READUINT8(save->p); + players[i].tripwirePass = READUINT8(save->p); + players[i].tripwireLeniency = READUINT16(save->p); - players[i].itemtype = READSINT8(save_p); - players[i].itemamount = READUINT8(save_p); - players[i].throwdir = READSINT8(save_p); + players[i].itemtype = READSINT8(save->p); + players[i].itemamount = READUINT8(save->p); + players[i].throwdir = READSINT8(save->p); - players[i].sadtimer = READUINT8(save_p); + players[i].sadtimer = READUINT8(save->p); - players[i].rings = READSINT8(save_p); - players[i].pickuprings = READUINT8(save_p); - players[i].ringdelay = READUINT8(save_p); - players[i].ringboost = READUINT16(save_p); - players[i].sparkleanim = READUINT8(save_p); - players[i].superring = READUINT16(save_p); + players[i].rings = READSINT8(save->p); + players[i].pickuprings = READUINT8(save->p); + players[i].ringdelay = READUINT8(save->p); + players[i].ringboost = READUINT16(save->p); + players[i].sparkleanim = READUINT8(save->p); + players[i].superring = READUINT16(save->p); - players[i].curshield = READUINT8(save_p); - players[i].bubblecool = READUINT8(save_p); - players[i].bubbleblowup = READUINT8(save_p); - players[i].flamedash = READUINT16(save_p); - players[i].flamemeter = READUINT16(save_p); - players[i].flamelength = READUINT8(save_p); + players[i].curshield = READUINT8(save->p); + players[i].bubblecool = READUINT8(save->p); + players[i].bubbleblowup = READUINT8(save->p); + players[i].flamedash = READUINT16(save->p); + players[i].flamemeter = READUINT16(save->p); + players[i].flamelength = READUINT8(save->p); - players[i].ballhogcharge = READUINT16(save_p); + players[i].ballhogcharge = READUINT16(save->p); - players[i].hyudorotimer = READUINT16(save_p); - players[i].stealingtimer = READSINT8(save_p); + players[i].hyudorotimer = READUINT16(save->p); + players[i].stealingtimer = READSINT8(save->p); - players[i].sneakertimer = READUINT16(save_p); - players[i].numsneakers = READUINT8(save_p); - players[i].floorboost = READUINT8(save_p); + players[i].sneakertimer = READUINT16(save->p); + players[i].numsneakers = READUINT8(save->p); + players[i].floorboost = READUINT8(save->p); - players[i].growshrinktimer = READINT16(save_p); - players[i].rocketsneakertimer = READUINT16(save_p); - players[i].invincibilitytimer = READUINT16(save_p); + players[i].growshrinktimer = READINT16(save->p); + players[i].rocketsneakertimer = READUINT16(save->p); + players[i].invincibilitytimer = READUINT16(save->p); - players[i].eggmanexplode = READUINT8(save_p); - players[i].eggmanblame = READSINT8(save_p); + players[i].eggmanexplode = READUINT8(save->p); + players[i].eggmanblame = READSINT8(save->p); - players[i].bananadrag = READUINT8(save_p); + players[i].bananadrag = READUINT8(save->p); - players[i].lastjawztarget = READSINT8(save_p); - players[i].jawztargetdelay = READUINT8(save_p); + players[i].lastjawztarget = READSINT8(save->p); + players[i].jawztargetdelay = READUINT8(save->p); - players[i].confirmVictim = READUINT8(save_p); - players[i].confirmVictimDelay = READUINT8(save_p); + players[i].confirmVictim = READUINT8(save->p); + players[i].confirmVictimDelay = READUINT8(save->p); - players[i].trickpanel = READUINT8(save_p); - players[i].tricktime = READUINT8(save_p); - players[i].trickboostpower = READUINT32(save_p); - players[i].trickboostdecay = READUINT8(save_p); - players[i].trickboost = READUINT8(save_p); + players[i].trickpanel = READUINT8(save->p); + players[i].tricktime = READUINT8(save->p); + players[i].trickboostpower = READUINT32(save->p); + players[i].trickboostdecay = READUINT8(save->p); + players[i].trickboost = READUINT8(save->p); - players[i].ebrakefor = READUINT32(save_p); + players[i].ebrakefor = READUINT32(save->p); - players[i].roundscore = READUINT32(save_p); - players[i].emeralds = READUINT8(save_p); - players[i].bumpers = READUINT8(save_p); - players[i].karmadelay = READINT16(save_p); - players[i].overtimekarma = READUINT32(save_p); - players[i].spheres = READINT16(save_p); - players[i].spheredigestion = READUINT32(save_p); + players[i].roundscore = READUINT32(save->p); + players[i].emeralds = READUINT8(save->p); + players[i].bumpers = READUINT8(save->p); + players[i].karmadelay = READINT16(save->p); + players[i].overtimekarma = READUINT32(save->p); + players[i].spheres = READINT16(save->p); + players[i].spheredigestion = READUINT32(save->p); - players[i].glanceDir = READSINT8(save_p); + players[i].glanceDir = READSINT8(save->p); - players[i].typing_timer = READUINT8(save_p); - players[i].typing_duration = READUINT8(save_p); + players[i].typing_timer = READUINT8(save->p); + players[i].typing_duration = READUINT8(save->p); - players[i].kickstartaccel = READUINT8(save_p); + players[i].kickstartaccel = READUINT8(save->p); - players[i].stairjank = READUINT8(save_p); - players[i].topdriftheld = READUINT8(save_p); - players[i].topinfirst = READUINT8(save_p); + players[i].stairjank = READUINT8(save->p); + players[i].topdriftheld = READUINT8(save->p); + players[i].topinfirst = READUINT8(save->p); - players[i].shrinkLaserDelay = READUINT8(save_p); + players[i].shrinkLaserDelay = READUINT8(save->p); // respawnvars_t - players[i].respawn.state = READUINT8(save_p); - players[i].respawn.wp = (waypoint_t *)(size_t)READUINT32(save_p); - players[i].respawn.pointx = READFIXED(save_p); - players[i].respawn.pointy = READFIXED(save_p); - players[i].respawn.pointz = READFIXED(save_p); - players[i].respawn.flip = (boolean)READUINT8(save_p); - players[i].respawn.timer = READUINT32(save_p); - players[i].respawn.airtimer = READUINT32(save_p); - players[i].respawn.distanceleft = READUINT32(save_p); - players[i].respawn.dropdash = READUINT32(save_p); - players[i].respawn.truedeath = READUINT8(save_p); - players[i].respawn.manual = READUINT8(save_p); + players[i].respawn.state = READUINT8(save->p); + players[i].respawn.wp = (waypoint_t *)(size_t)READUINT32(save->p); + players[i].respawn.pointx = READFIXED(save->p); + players[i].respawn.pointy = READFIXED(save->p); + players[i].respawn.pointz = READFIXED(save->p); + players[i].respawn.flip = (boolean)READUINT8(save->p); + players[i].respawn.timer = READUINT32(save->p); + players[i].respawn.airtimer = READUINT32(save->p); + players[i].respawn.distanceleft = READUINT32(save->p); + players[i].respawn.dropdash = READUINT32(save->p); + players[i].respawn.truedeath = READUINT8(save->p); + players[i].respawn.manual = READUINT8(save->p); // botvars_t - players[i].botvars.difficulty = READUINT8(save_p); - players[i].botvars.diffincrease = READUINT8(save_p); - players[i].botvars.rival = (boolean)READUINT8(save_p); - players[i].botvars.rubberband = READFIXED(save_p); - players[i].botvars.controller = READUINT16(save_p); - players[i].botvars.itemdelay = READUINT32(save_p); - players[i].botvars.itemconfirm = READUINT32(save_p); - players[i].botvars.turnconfirm = READSINT8(save_p); - players[i].botvars.spindashconfirm = READUINT32(save_p); + players[i].botvars.difficulty = READUINT8(save->p); + players[i].botvars.diffincrease = READUINT8(save->p); + players[i].botvars.rival = (boolean)READUINT8(save->p); + players[i].botvars.rubberband = READFIXED(save->p); + players[i].botvars.controller = READUINT16(save->p); + players[i].botvars.itemdelay = READUINT32(save->p); + players[i].botvars.itemconfirm = READUINT32(save->p); + players[i].botvars.turnconfirm = READSINT8(save->p); + players[i].botvars.spindashconfirm = READUINT32(save->p); // itemroulette_t - players[i].itemRoulette.active = (boolean)READUINT8(save_p); + players[i].itemRoulette.active = (boolean)READUINT8(save->p); #ifdef ITEM_LIST_SIZE - players[i].itemRoulette.itemListLen = (size_t)READUINT32(save_p); + players[i].itemRoulette.itemListLen = (size_t)READUINT32(save->p); for (q = 0; q < ITEM_LIST_SIZE; q++) { - players[i].itemRoulette.itemList[q] = READSINT8(save_p); + players[i].itemRoulette.itemList[q] = READSINT8(save->p); } #else - players[i].itemRoulette.itemListCap = (size_t)READUINT32(save_p); - players[i].itemRoulette.itemListLen = (size_t)READUINT32(save_p); + players[i].itemRoulette.itemListCap = (size_t)READUINT32(save->p); + players[i].itemRoulette.itemListLen = (size_t)READUINT32(save->p); if (players[i].itemRoulette.itemListCap > 0) { @@ -812,19 +811,19 @@ static void P_NetUnArchivePlayers(void) for (q = 0; q < players[i].itemRoulette.itemListLen; q++) { - players[i].itemRoulette.itemList[q] = READSINT8(save_p); + players[i].itemRoulette.itemList[q] = READSINT8(save->p); } } #endif - players[i].itemRoulette.useOdds = READUINT8(save_p); - players[i].itemRoulette.dist = READUINT32(save_p); - players[i].itemRoulette.index = (size_t)READUINT32(save_p); - players[i].itemRoulette.sound = READUINT8(save_p); - players[i].itemRoulette.speed = (tic_t)READUINT32(save_p); - players[i].itemRoulette.tics = (tic_t)READUINT32(save_p); - players[i].itemRoulette.elapsed = (tic_t)READUINT32(save_p); - players[i].itemRoulette.eggman = (boolean)READUINT8(save_p); + players[i].itemRoulette.useOdds = READUINT8(save->p); + players[i].itemRoulette.dist = READUINT32(save->p); + players[i].itemRoulette.index = (size_t)READUINT32(save->p); + players[i].itemRoulette.sound = READUINT8(save->p); + players[i].itemRoulette.speed = (tic_t)READUINT32(save->p); + players[i].itemRoulette.tics = (tic_t)READUINT32(save->p); + players[i].itemRoulette.elapsed = (tic_t)READUINT32(save->p); + players[i].itemRoulette.eggman = (boolean)READUINT8(save->p); //players[i].viewheight = P_GetPlayerViewHeight(players[i]); // scale cannot be factored in at this point } @@ -927,12 +926,12 @@ static void ClearNetColormaps(void) net_colormaps = NULL; } -static void P_NetArchiveColormaps(void) +static void P_NetArchiveColormaps(savebuffer_t *save) { // We save and then we clean up our colormap mess extracolormap_t *exc, *exc_next; UINT32 i = 0; - WRITEUINT32(save_p, num_net_colormaps); // save for safety + WRITEUINT32(save->p, num_net_colormaps); // save for safety for (exc = net_colormaps; i < num_net_colormaps; i++, exc = exc_next) { @@ -941,15 +940,15 @@ static void P_NetArchiveColormaps(void) if (!exc) exc = R_CreateDefaultColormap(false); - WRITEUINT8(save_p, exc->fadestart); - WRITEUINT8(save_p, exc->fadeend); - WRITEUINT8(save_p, exc->flags); + WRITEUINT8(save->p, exc->fadestart); + WRITEUINT8(save->p, exc->fadeend); + WRITEUINT8(save->p, exc->flags); - WRITEINT32(save_p, exc->rgba); - WRITEINT32(save_p, exc->fadergba); + WRITEINT32(save->p, exc->rgba); + WRITEINT32(save->p, exc->fadergba); #ifdef EXTRACOLORMAPLUMPS - WRITESTRINGN(save_p, exc->lumpname, 9); + WRITESTRINGN(save->p, exc->lumpname, 9); #endif exc_next = exc->next; @@ -961,7 +960,7 @@ static void P_NetArchiveColormaps(void) net_colormaps = NULL; } -static void P_NetUnArchiveColormaps(void) +static void P_NetUnArchiveColormaps(savebuffer_t *save) { // When we reach this point, we already populated our list with // dummy colormaps. Now that we are loading the color data, @@ -969,7 +968,7 @@ static void P_NetUnArchiveColormaps(void) extracolormap_t *exc, *existing_exc, *exc_next = NULL; UINT32 i = 0; - num_net_colormaps = READUINT32(save_p); + num_net_colormaps = READUINT32(save->p); for (exc = net_colormaps; i < num_net_colormaps; i++, exc = exc_next) { @@ -979,15 +978,15 @@ static void P_NetUnArchiveColormaps(void) char lumpname[9]; #endif - fadestart = READUINT8(save_p); - fadeend = READUINT8(save_p); - flags = READUINT8(save_p); + fadestart = READUINT8(save->p); + fadeend = READUINT8(save->p); + flags = READUINT8(save->p); - rgba = READINT32(save_p); - fadergba = READINT32(save_p); + rgba = READINT32(save->p); + fadergba = READINT32(save->p); #ifdef EXTRACOLORMAPLUMPS - READSTRINGN(save_p, lumpname, 9); + READSTRINGN(save->p, lumpname, 9); if (lumpname[0]) { @@ -1063,29 +1062,29 @@ static void P_NetUnArchiveColormaps(void) net_colormaps = NULL; } -static void P_NetArchiveTubeWaypoints(void) +static void P_NetArchiveTubeWaypoints(savebuffer_t *save) { INT32 i, j; for (i = 0; i < NUMTUBEWAYPOINTSEQUENCES; i++) { - WRITEUINT16(save_p, numtubewaypoints[i]); + WRITEUINT16(save->p, numtubewaypoints[i]); for (j = 0; j < numtubewaypoints[i]; j++) - WRITEUINT32(save_p, tubewaypoints[i][j] ? tubewaypoints[i][j]->mobjnum : 0); + WRITEUINT32(save->p, tubewaypoints[i][j] ? tubewaypoints[i][j]->mobjnum : 0); } } -static void P_NetUnArchiveTubeWaypoints(void) +static void P_NetUnArchiveTubeWaypoints(savebuffer_t *save) { INT32 i, j; UINT32 mobjnum; for (i = 0; i < NUMTUBEWAYPOINTSEQUENCES; i++) { - numtubewaypoints[i] = READUINT16(save_p); + numtubewaypoints[i] = READUINT16(save->p); for (j = 0; j < numtubewaypoints[i]; j++) { - mobjnum = READUINT32(save_p); + mobjnum = READUINT32(save->p); tubewaypoints[i][j] = (mobjnum == 0) ? NULL : P_FindNewPosition(mobjnum); } } @@ -1195,7 +1194,7 @@ static boolean CheckFFloorDiff(const sector_t *ss) // Special case: save the stats of all modified ffloors along with their ffloor "number"s // we don't bother with ffloors that haven't changed, that would just add to savegame even more than is really needed -static void ArchiveFFloors(const sector_t *ss) +static void ArchiveFFloors(savebuffer_t *save, const sector_t *ss) { size_t j = 0; // ss->ffloors is saved as ffloor #0, ss->ffloors->next is #1, etc ffloor_t *rover; @@ -1210,19 +1209,19 @@ static void ArchiveFFloors(const sector_t *ss) if (fflr_diff) { - WRITEUINT16(save_p, j); // save ffloor "number" - WRITEUINT8(save_p, fflr_diff); + WRITEUINT16(save->p, j); // save ffloor "number" + WRITEUINT8(save->p, fflr_diff); if (fflr_diff & FD_FLAGS) - WRITEUINT32(save_p, rover->fofflags); + WRITEUINT32(save->p, rover->fofflags); if (fflr_diff & FD_ALPHA) - WRITEINT16(save_p, rover->alpha); + WRITEINT16(save->p, rover->alpha); } j++; } - WRITEUINT16(save_p, 0xffff); + WRITEUINT16(save->p, 0xffff); } -static void UnArchiveFFloors(const sector_t *ss) +static void UnArchiveFFloors(savebuffer_t *save, const sector_t *ss) { UINT16 j = 0; // number of current ffloor in loop UINT16 fflr_i; // saved ffloor "number" of next modified ffloor @@ -1233,7 +1232,7 @@ static void UnArchiveFFloors(const sector_t *ss) if (!rover) // it is assumed sectors[i].ffloors actually exists, but just in case... I_Error("Sector does not have any ffloors!"); - fflr_i = READUINT16(save_p); // get first modified ffloor's number ready + fflr_i = READUINT16(save->p); // get first modified ffloor's number ready for (;;) // for some reason the usual for (rover = x; ...) thing doesn't work here? { if (fflr_i == 0xffff) // end of modified ffloors list, let's stop already @@ -1248,21 +1247,21 @@ static void UnArchiveFFloors(const sector_t *ss) continue; } - fflr_diff = READUINT8(save_p); + fflr_diff = READUINT8(save->p); if (fflr_diff & FD_FLAGS) - rover->fofflags = READUINT32(save_p); + rover->fofflags = READUINT32(save->p); if (fflr_diff & FD_ALPHA) - rover->alpha = READINT16(save_p); + rover->alpha = READINT16(save->p); - fflr_i = READUINT16(save_p); // get next ffloor "number" ready + fflr_i = READUINT16(save->p); // get next ffloor "number" ready j++; rover = rover->next; } } -static void ArchiveSectors(void) +static void ArchiveSectors(savebuffer_t *save) { size_t i, j; const sector_t *ss = sectors; @@ -1341,87 +1340,87 @@ static void ArchiveSectors(void) if (diff) { - WRITEUINT16(save_p, i); - WRITEUINT8(save_p, diff); + WRITEUINT16(save->p, i); + WRITEUINT8(save->p, diff); if (diff & SD_DIFF2) - WRITEUINT8(save_p, diff2); + WRITEUINT8(save->p, diff2); if (diff2 & SD_DIFF3) - WRITEUINT8(save_p, diff3); + WRITEUINT8(save->p, diff3); if (diff3 & SD_DIFF4) - WRITEUINT8(save_p, diff4); + WRITEUINT8(save->p, diff4); if (diff & SD_FLOORHT) - WRITEFIXED(save_p, ss->floorheight); + WRITEFIXED(save->p, ss->floorheight); if (diff & SD_CEILHT) - WRITEFIXED(save_p, ss->ceilingheight); + WRITEFIXED(save->p, ss->ceilingheight); if (diff & SD_FLOORPIC) - WRITEMEM(save_p, levelflats[ss->floorpic].name, 8); + WRITEMEM(save->p, levelflats[ss->floorpic].name, 8); if (diff & SD_CEILPIC) - WRITEMEM(save_p, levelflats[ss->ceilingpic].name, 8); + WRITEMEM(save->p, levelflats[ss->ceilingpic].name, 8); if (diff & SD_LIGHT) - WRITEINT16(save_p, ss->lightlevel); + WRITEINT16(save->p, ss->lightlevel); if (diff & SD_SPECIAL) - WRITEINT16(save_p, ss->special); + WRITEINT16(save->p, ss->special); if (diff2 & SD_FXOFFS) - WRITEFIXED(save_p, ss->floor_xoffs); + WRITEFIXED(save->p, ss->floor_xoffs); if (diff2 & SD_FYOFFS) - WRITEFIXED(save_p, ss->floor_yoffs); + WRITEFIXED(save->p, ss->floor_yoffs); if (diff2 & SD_CXOFFS) - WRITEFIXED(save_p, ss->ceiling_xoffs); + WRITEFIXED(save->p, ss->ceiling_xoffs); if (diff2 & SD_CYOFFS) - WRITEFIXED(save_p, ss->ceiling_yoffs); + WRITEFIXED(save->p, ss->ceiling_yoffs); if (diff2 & SD_FLOORANG) - WRITEANGLE(save_p, ss->floorpic_angle); + WRITEANGLE(save->p, ss->floorpic_angle); if (diff2 & SD_CEILANG) - WRITEANGLE(save_p, ss->ceilingpic_angle); + WRITEANGLE(save->p, ss->ceilingpic_angle); if (diff2 & SD_TAG) { - WRITEUINT32(save_p, ss->tags.count); + WRITEUINT32(save->p, ss->tags.count); for (j = 0; j < ss->tags.count; j++) - WRITEINT16(save_p, ss->tags.tags[j]); + WRITEINT16(save->p, ss->tags.tags[j]); } if (diff3 & SD_COLORMAP) - WRITEUINT32(save_p, CheckAddNetColormapToList(ss->extra_colormap)); + WRITEUINT32(save->p, CheckAddNetColormapToList(ss->extra_colormap)); // returns existing index if already added, or appends to net_colormaps and returns new index if (diff3 & SD_CRUMBLESTATE) - WRITEINT32(save_p, ss->crumblestate); + WRITEINT32(save->p, ss->crumblestate); if (diff3 & SD_FLOORLIGHT) { - WRITEINT16(save_p, ss->floorlightlevel); - WRITEUINT8(save_p, ss->floorlightabsolute); + WRITEINT16(save->p, ss->floorlightlevel); + WRITEUINT8(save->p, ss->floorlightabsolute); } if (diff3 & SD_CEILLIGHT) { - WRITEINT16(save_p, ss->ceilinglightlevel); - WRITEUINT8(save_p, ss->ceilinglightabsolute); + WRITEINT16(save->p, ss->ceilinglightlevel); + WRITEUINT8(save->p, ss->ceilinglightabsolute); } if (diff3 & SD_FLAG) - WRITEUINT32(save_p, ss->flags); + WRITEUINT32(save->p, ss->flags); if (diff3 & SD_SPECIALFLAG) - WRITEUINT32(save_p, ss->specialflags); + WRITEUINT32(save->p, ss->specialflags); if (diff4 & SD_DAMAGETYPE) - WRITEUINT8(save_p, ss->damagetype); + WRITEUINT8(save->p, ss->damagetype); if (diff4 & SD_TRIGGERTAG) - WRITEINT16(save_p, ss->triggertag); + WRITEINT16(save->p, ss->triggertag); if (diff4 & SD_TRIGGERER) - WRITEUINT8(save_p, ss->triggerer); + WRITEUINT8(save->p, ss->triggerer); if (diff4 & SD_GRAVITY) - WRITEFIXED(save_p, ss->gravity); + WRITEFIXED(save->p, ss->gravity); if (diff & SD_FFLOORS) - ArchiveFFloors(ss); + ArchiveFFloors(save, ss); } } - WRITEUINT16(save_p, 0xffff); + WRITEUINT16(save->p, 0xffff); } -static void UnArchiveSectors(void) +static void UnArchiveSectors(savebuffer_t *save) { UINT16 i, j; UINT8 diff, diff2, diff3, diff4; for (;;) { - i = READUINT16(save_p); + i = READUINT16(save->p); if (i == 0xffff) break; @@ -1429,54 +1428,54 @@ static void UnArchiveSectors(void) if (i > numsectors) I_Error("Invalid sector number %u from server (expected end at %s)", i, sizeu1(numsectors)); - diff = READUINT8(save_p); + diff = READUINT8(save->p); if (diff & SD_DIFF2) - diff2 = READUINT8(save_p); + diff2 = READUINT8(save->p); else diff2 = 0; if (diff2 & SD_DIFF3) - diff3 = READUINT8(save_p); + diff3 = READUINT8(save->p); else diff3 = 0; if (diff3 & SD_DIFF4) - diff4 = READUINT8(save_p); + diff4 = READUINT8(save->p); else diff4 = 0; if (diff & SD_FLOORHT) - sectors[i].floorheight = READFIXED(save_p); + sectors[i].floorheight = READFIXED(save->p); if (diff & SD_CEILHT) - sectors[i].ceilingheight = READFIXED(save_p); + sectors[i].ceilingheight = READFIXED(save->p); if (diff & SD_FLOORPIC) { - sectors[i].floorpic = P_AddLevelFlatRuntime((char *)save_p); - save_p += 8; + sectors[i].floorpic = P_AddLevelFlatRuntime((char *)save->p); + save->p += 8; } if (diff & SD_CEILPIC) { - sectors[i].ceilingpic = P_AddLevelFlatRuntime((char *)save_p); - save_p += 8; + sectors[i].ceilingpic = P_AddLevelFlatRuntime((char *)save->p); + save->p += 8; } if (diff & SD_LIGHT) - sectors[i].lightlevel = READINT16(save_p); + sectors[i].lightlevel = READINT16(save->p); if (diff & SD_SPECIAL) - sectors[i].special = READINT16(save_p); + sectors[i].special = READINT16(save->p); if (diff2 & SD_FXOFFS) - sectors[i].floor_xoffs = READFIXED(save_p); + sectors[i].floor_xoffs = READFIXED(save->p); if (diff2 & SD_FYOFFS) - sectors[i].floor_yoffs = READFIXED(save_p); + sectors[i].floor_yoffs = READFIXED(save->p); if (diff2 & SD_CXOFFS) - sectors[i].ceiling_xoffs = READFIXED(save_p); + sectors[i].ceiling_xoffs = READFIXED(save->p); if (diff2 & SD_CYOFFS) - sectors[i].ceiling_yoffs = READFIXED(save_p); + sectors[i].ceiling_yoffs = READFIXED(save->p); if (diff2 & SD_FLOORANG) - sectors[i].floorpic_angle = READANGLE(save_p); + sectors[i].floorpic_angle = READANGLE(save->p); if (diff2 & SD_CEILANG) - sectors[i].ceilingpic_angle = READANGLE(save_p); + sectors[i].ceilingpic_angle = READANGLE(save->p); if (diff2 & SD_TAG) { - size_t ncount = READUINT32(save_p); + size_t ncount = READUINT32(save->p); // Remove entries from global lists. for (j = 0; j < sectors[i].tags.count; j++) @@ -1490,7 +1489,7 @@ static void UnArchiveSectors(void) } for (j = 0; j < ncount; j++) - sectors[i].tags.tags[j] = READINT16(save_p); + sectors[i].tags.tags[j] = READINT16(save->p); // Add new entries. for (j = 0; j < sectors[i].tags.count; j++) @@ -1499,41 +1498,41 @@ static void UnArchiveSectors(void) if (diff3 & SD_COLORMAP) - sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(save_p)); + sectors[i].extra_colormap = GetNetColormapFromList(READUINT32(save->p)); if (diff3 & SD_CRUMBLESTATE) - sectors[i].crumblestate = READINT32(save_p); + sectors[i].crumblestate = READINT32(save->p); if (diff3 & SD_FLOORLIGHT) { - sectors[i].floorlightlevel = READINT16(save_p); - sectors[i].floorlightabsolute = READUINT8(save_p); + sectors[i].floorlightlevel = READINT16(save->p); + sectors[i].floorlightabsolute = READUINT8(save->p); } if (diff3 & SD_CEILLIGHT) { - sectors[i].ceilinglightlevel = READINT16(save_p); - sectors[i].ceilinglightabsolute = READUINT8(save_p); + sectors[i].ceilinglightlevel = READINT16(save->p); + sectors[i].ceilinglightabsolute = READUINT8(save->p); } if (diff3 & SD_FLAG) { - sectors[i].flags = READUINT32(save_p); + sectors[i].flags = READUINT32(save->p); CheckForReverseGravity |= (sectors[i].flags & MSF_GRAVITYFLIP); } if (diff3 & SD_SPECIALFLAG) - sectors[i].specialflags = READUINT32(save_p); + sectors[i].specialflags = READUINT32(save->p); if (diff4 & SD_DAMAGETYPE) - sectors[i].damagetype = READUINT8(save_p); + sectors[i].damagetype = READUINT8(save->p); if (diff4 & SD_TRIGGERTAG) - sectors[i].triggertag = READINT16(save_p); + sectors[i].triggertag = READINT16(save->p); if (diff4 & SD_TRIGGERER) - sectors[i].triggerer = READUINT8(save_p); + sectors[i].triggerer = READUINT8(save->p); if (diff4 & SD_GRAVITY) - sectors[i].gravity = READFIXED(save_p); + sectors[i].gravity = READFIXED(save->p); if (diff & SD_FFLOORS) - UnArchiveFFloors(§ors[i]); + UnArchiveFFloors(save, §ors[i]); } } -static void ArchiveLines(void) +static void ArchiveLines(savebuffer_t *save) { size_t i; const line_t *li = lines; @@ -1594,41 +1593,41 @@ static void ArchiveLines(void) if (diff) { - WRITEINT16(save_p, i); - WRITEUINT8(save_p, diff); + WRITEINT16(save->p, i); + WRITEUINT8(save->p, diff); if (diff & LD_DIFF2) - WRITEUINT8(save_p, diff2); + WRITEUINT8(save->p, diff2); if (diff & LD_FLAG) - WRITEUINT32(save_p, li->flags); + WRITEUINT32(save->p, li->flags); if (diff & LD_SPECIAL) - WRITEINT16(save_p, li->special); + WRITEINT16(save->p, li->special); if (diff & LD_CLLCOUNT) - WRITEINT16(save_p, li->callcount); + WRITEINT16(save->p, li->callcount); si = &sides[li->sidenum[0]]; if (diff & LD_S1TEXOFF) - WRITEFIXED(save_p, si->textureoffset); + WRITEFIXED(save->p, si->textureoffset); if (diff & LD_S1TOPTEX) - WRITEINT32(save_p, si->toptexture); + WRITEINT32(save->p, si->toptexture); if (diff & LD_S1BOTTEX) - WRITEINT32(save_p, si->bottomtexture); + WRITEINT32(save->p, si->bottomtexture); if (diff & LD_S1MIDTEX) - WRITEINT32(save_p, si->midtexture); + WRITEINT32(save->p, si->midtexture); si = &sides[li->sidenum[1]]; if (diff2 & LD_S2TEXOFF) - WRITEFIXED(save_p, si->textureoffset); + WRITEFIXED(save->p, si->textureoffset); if (diff2 & LD_S2TOPTEX) - WRITEINT32(save_p, si->toptexture); + WRITEINT32(save->p, si->toptexture); if (diff2 & LD_S2BOTTEX) - WRITEINT32(save_p, si->bottomtexture); + WRITEINT32(save->p, si->bottomtexture); if (diff2 & LD_S2MIDTEX) - WRITEINT32(save_p, si->midtexture); + WRITEINT32(save->p, si->midtexture); if (diff2 & LD_ARGS) { UINT8 j; for (j = 0; j < NUMLINEARGS; j++) - WRITEINT32(save_p, li->args[j]); + WRITEINT32(save->p, li->args[j]); } if (diff2 & LD_STRINGARGS) { @@ -1639,24 +1638,24 @@ static void ArchiveLines(void) if (!li->stringargs[j]) { - WRITEINT32(save_p, 0); + WRITEINT32(save->p, 0); continue; } len = strlen(li->stringargs[j]); - WRITEINT32(save_p, len); + WRITEINT32(save->p, len); for (k = 0; k < len; k++) - WRITECHAR(save_p, li->stringargs[j][k]); + WRITECHAR(save->p, li->stringargs[j][k]); } } if (diff2 & LD_EXECUTORDELAY) - WRITEINT32(save_p, li->executordelay); + WRITEINT32(save->p, li->executordelay); } } - WRITEUINT16(save_p, 0xffff); + WRITEUINT16(save->p, 0xffff); } -static void UnArchiveLines(void) +static void UnArchiveLines(savebuffer_t *save) { UINT16 i; line_t *li; @@ -1665,59 +1664,59 @@ static void UnArchiveLines(void) for (;;) { - i = READUINT16(save_p); + i = READUINT16(save->p); if (i == 0xffff) break; if (i > numlines) I_Error("Invalid line number %u from server", i); - diff = READUINT8(save_p); + diff = READUINT8(save->p); li = &lines[i]; if (diff & LD_DIFF2) - diff2 = READUINT8(save_p); + diff2 = READUINT8(save->p); else diff2 = 0; if (diff & LD_FLAG) - li->flags = READUINT32(save_p); + li->flags = READUINT32(save->p); if (diff & LD_SPECIAL) - li->special = READINT16(save_p); + li->special = READINT16(save->p); if (diff & LD_CLLCOUNT) - li->callcount = READINT16(save_p); + li->callcount = READINT16(save->p); si = &sides[li->sidenum[0]]; if (diff & LD_S1TEXOFF) - si->textureoffset = READFIXED(save_p); + si->textureoffset = READFIXED(save->p); if (diff & LD_S1TOPTEX) - si->toptexture = READINT32(save_p); + si->toptexture = READINT32(save->p); if (diff & LD_S1BOTTEX) - si->bottomtexture = READINT32(save_p); + si->bottomtexture = READINT32(save->p); if (diff & LD_S1MIDTEX) - si->midtexture = READINT32(save_p); + si->midtexture = READINT32(save->p); si = &sides[li->sidenum[1]]; if (diff2 & LD_S2TEXOFF) - si->textureoffset = READFIXED(save_p); + si->textureoffset = READFIXED(save->p); if (diff2 & LD_S2TOPTEX) - si->toptexture = READINT32(save_p); + si->toptexture = READINT32(save->p); if (diff2 & LD_S2BOTTEX) - si->bottomtexture = READINT32(save_p); + si->bottomtexture = READINT32(save->p); if (diff2 & LD_S2MIDTEX) - si->midtexture = READINT32(save_p); + si->midtexture = READINT32(save->p); if (diff2 & LD_ARGS) { UINT8 j; for (j = 0; j < NUMLINEARGS; j++) - li->args[j] = READINT32(save_p); + li->args[j] = READINT32(save->p); } if (diff2 & LD_STRINGARGS) { UINT8 j; for (j = 0; j < NUMLINESTRINGARGS; j++) { - size_t len = READINT32(save_p); + size_t len = READINT32(save->p); size_t k; if (!len) @@ -1729,33 +1728,33 @@ static void UnArchiveLines(void) li->stringargs[j] = Z_Realloc(li->stringargs[j], len + 1, PU_LEVEL, NULL); for (k = 0; k < len; k++) - li->stringargs[j][k] = READCHAR(save_p); + li->stringargs[j][k] = READCHAR(save->p); li->stringargs[j][len] = '\0'; } } if (diff2 & LD_EXECUTORDELAY) - li->executordelay = READINT32(save_p); + li->executordelay = READINT32(save->p); } } -static void P_NetArchiveWorld(void) +static void P_NetArchiveWorld(savebuffer_t *save) { // initialize colormap vars because paranoia ClearNetColormaps(); - WRITEUINT32(save_p, ARCHIVEBLOCK_WORLD); + WRITEUINT32(save->p, ARCHIVEBLOCK_WORLD); - ArchiveSectors(); - ArchiveLines(); + ArchiveSectors(save); + ArchiveLines(save); R_ClearTextureNumCache(false); } -static void P_NetUnArchiveWorld(void) +static void P_NetUnArchiveWorld(savebuffer_t *save) { UINT16 i; - if (READUINT32(save_p) != ARCHIVEBLOCK_WORLD) + if (READUINT32(save->p) != ARCHIVEBLOCK_WORLD) I_Error("Bad $$$.sav at archive block World"); // initialize colormap vars because paranoia @@ -1769,8 +1768,8 @@ static void P_NetUnArchiveWorld(void) num_ffloors++; } - UnArchiveSectors(); - UnArchiveLines(); + UnArchiveSectors(save); + UnArchiveLines(save); } // @@ -1924,7 +1923,7 @@ static UINT32 SaveSlope(const pslope_t *slope) return 0xFFFFFFFF; } -static void SaveMobjThinker(const thinker_t *th, const UINT8 type) +static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const mobj_t *mobj = (const mobj_t *)th; UINT32 diff; @@ -2102,28 +2101,28 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) if (mobj->type == MT_HOOPCENTER) diff = MD_SPAWNPOINT; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, diff); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, diff); if (diff & MD_MORE) - WRITEUINT32(save_p, diff2); + WRITEUINT32(save->p, diff2); // save pointer, at load time we will search this pointer to reinitilize pointers - WRITEUINT32(save_p, (size_t)mobj); + WRITEUINT32(save->p, (size_t)mobj); - WRITEFIXED(save_p, mobj->z); // Force this so 3dfloor problems don't arise. - WRITEFIXED(save_p, mobj->floorz); - WRITEFIXED(save_p, mobj->ceilingz); + WRITEFIXED(save->p, mobj->z); // Force this so 3dfloor problems don't arise. + WRITEFIXED(save->p, mobj->floorz); + WRITEFIXED(save->p, mobj->ceilingz); if (diff2 & MD2_FLOORROVER) { - WRITEUINT32(save_p, SaveSector(mobj->floorrover->target)); - WRITEUINT16(save_p, P_GetFFloorID(mobj->floorrover)); + WRITEUINT32(save->p, SaveSector(mobj->floorrover->target)); + WRITEUINT16(save->p, P_GetFFloorID(mobj->floorrover)); } if (diff2 & MD2_CEILINGROVER) { - WRITEUINT32(save_p, SaveSector(mobj->ceilingrover->target)); - WRITEUINT16(save_p, P_GetFFloorID(mobj->ceilingrover)); + WRITEUINT32(save->p, SaveSector(mobj->ceilingrover->target)); + WRITEUINT16(save->p, P_GetFFloorID(mobj->ceilingrover)); } if (diff & MD_SPAWNPOINT) @@ -2134,7 +2133,7 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) { if (&mapthings[z] != mobj->spawnpoint) continue; - WRITEUINT16(save_p, z); + WRITEUINT16(save->p, z); break; } if (mobj->type == MT_HOOPCENTER) @@ -2142,110 +2141,110 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) } if (diff & MD_TYPE) - WRITEUINT32(save_p, mobj->type); + WRITEUINT32(save->p, mobj->type); if (diff & MD_POS) { - WRITEFIXED(save_p, mobj->x); - WRITEFIXED(save_p, mobj->y); - WRITEANGLE(save_p, mobj->angle); - WRITEANGLE(save_p, mobj->pitch); - WRITEANGLE(save_p, mobj->roll); + WRITEFIXED(save->p, mobj->x); + WRITEFIXED(save->p, mobj->y); + WRITEANGLE(save->p, mobj->angle); + WRITEANGLE(save->p, mobj->pitch); + WRITEANGLE(save->p, mobj->roll); } if (diff & MD_MOM) { - WRITEFIXED(save_p, mobj->momx); - WRITEFIXED(save_p, mobj->momy); - WRITEFIXED(save_p, mobj->momz); - WRITEFIXED(save_p, mobj->pmomz); + WRITEFIXED(save->p, mobj->momx); + WRITEFIXED(save->p, mobj->momy); + WRITEFIXED(save->p, mobj->momz); + WRITEFIXED(save->p, mobj->pmomz); } if (diff & MD_RADIUS) - WRITEFIXED(save_p, mobj->radius); + WRITEFIXED(save->p, mobj->radius); if (diff & MD_HEIGHT) - WRITEFIXED(save_p, mobj->height); + WRITEFIXED(save->p, mobj->height); if (diff & MD_FLAGS) - WRITEUINT32(save_p, mobj->flags); + WRITEUINT32(save->p, mobj->flags); if (diff & MD_FLAGS2) - WRITEUINT32(save_p, mobj->flags2); + WRITEUINT32(save->p, mobj->flags2); if (diff & MD_HEALTH) - WRITEINT32(save_p, mobj->health); + WRITEINT32(save->p, mobj->health); if (diff & MD_RTIME) - WRITEINT32(save_p, mobj->reactiontime); + WRITEINT32(save->p, mobj->reactiontime); if (diff & MD_STATE) - WRITEUINT16(save_p, mobj->state-states); + WRITEUINT16(save->p, mobj->state-states); if (diff & MD_TICS) - WRITEINT32(save_p, mobj->tics); + WRITEINT32(save->p, mobj->tics); if (diff & MD_SPRITE) { - WRITEUINT16(save_p, mobj->sprite); + WRITEUINT16(save->p, mobj->sprite); if (mobj->sprite == SPR_PLAY) - WRITEUINT8(save_p, mobj->sprite2); + WRITEUINT8(save->p, mobj->sprite2); } if (diff & MD_FRAME) { - WRITEUINT32(save_p, mobj->frame); - WRITEUINT16(save_p, mobj->anim_duration); + WRITEUINT32(save->p, mobj->frame); + WRITEUINT16(save->p, mobj->anim_duration); } if (diff & MD_EFLAGS) - WRITEUINT16(save_p, mobj->eflags); + WRITEUINT16(save->p, mobj->eflags); if (diff & MD_PLAYER) - WRITEUINT8(save_p, mobj->player-players); + WRITEUINT8(save->p, mobj->player-players); if (diff & MD_MOVEDIR) - WRITEANGLE(save_p, mobj->movedir); + WRITEANGLE(save->p, mobj->movedir); if (diff & MD_MOVECOUNT) - WRITEINT32(save_p, mobj->movecount); + WRITEINT32(save->p, mobj->movecount); if (diff & MD_THRESHOLD) - WRITEINT32(save_p, mobj->threshold); + WRITEINT32(save->p, mobj->threshold); if (diff & MD_LASTLOOK) - WRITEINT32(save_p, mobj->lastlook); + WRITEINT32(save->p, mobj->lastlook); if (diff & MD_TARGET) - WRITEUINT32(save_p, mobj->target->mobjnum); + WRITEUINT32(save->p, mobj->target->mobjnum); if (diff & MD_TRACER) - WRITEUINT32(save_p, mobj->tracer->mobjnum); + WRITEUINT32(save->p, mobj->tracer->mobjnum); if (diff & MD_FRICTION) - WRITEFIXED(save_p, mobj->friction); + WRITEFIXED(save->p, mobj->friction); if (diff & MD_MOVEFACTOR) - WRITEFIXED(save_p, mobj->movefactor); + WRITEFIXED(save->p, mobj->movefactor); if (diff & MD_FUSE) - WRITEINT32(save_p, mobj->fuse); + WRITEINT32(save->p, mobj->fuse); if (diff & MD_WATERTOP) - WRITEFIXED(save_p, mobj->watertop); + WRITEFIXED(save->p, mobj->watertop); if (diff & MD_WATERBOTTOM) - WRITEFIXED(save_p, mobj->waterbottom); + WRITEFIXED(save->p, mobj->waterbottom); if (diff & MD_SCALE) - WRITEFIXED(save_p, mobj->scale); + WRITEFIXED(save->p, mobj->scale); if (diff & MD_DSCALE) - WRITEFIXED(save_p, mobj->destscale); + WRITEFIXED(save->p, mobj->destscale); if (diff2 & MD2_SCALESPEED) - WRITEFIXED(save_p, mobj->scalespeed); + WRITEFIXED(save->p, mobj->scalespeed); if (diff2 & MD2_CUSVAL) - WRITEINT32(save_p, mobj->cusval); + WRITEINT32(save->p, mobj->cusval); if (diff2 & MD2_CVMEM) - WRITEINT32(save_p, mobj->cvmem); + WRITEINT32(save->p, mobj->cvmem); if (diff2 & MD2_SKIN) - WRITEUINT8(save_p, (UINT8)((skin_t *)mobj->skin - skins)); + WRITEUINT8(save->p, (UINT8)((skin_t *)mobj->skin - skins)); if (diff2 & MD2_COLOR) - WRITEUINT16(save_p, mobj->color); + WRITEUINT16(save->p, mobj->color); if (diff2 & MD2_EXTVAL1) - WRITEINT32(save_p, mobj->extravalue1); + WRITEINT32(save->p, mobj->extravalue1); if (diff2 & MD2_EXTVAL2) - WRITEINT32(save_p, mobj->extravalue2); + WRITEINT32(save->p, mobj->extravalue2); if (diff2 & MD2_HNEXT) - WRITEUINT32(save_p, mobj->hnext->mobjnum); + WRITEUINT32(save->p, mobj->hnext->mobjnum); if (diff2 & MD2_HPREV) - WRITEUINT32(save_p, mobj->hprev->mobjnum); + WRITEUINT32(save->p, mobj->hprev->mobjnum); if (diff2 & MD2_ITNEXT) - WRITEUINT32(save_p, mobj->itnext->mobjnum); + WRITEUINT32(save->p, mobj->itnext->mobjnum); if (diff2 & MD2_SLOPE) - WRITEUINT16(save_p, mobj->standingslope->id); + WRITEUINT16(save->p, mobj->standingslope->id); if (diff2 & MD2_COLORIZED) - WRITEUINT8(save_p, mobj->colorized); + WRITEUINT8(save->p, mobj->colorized); if (diff2 & MD2_MIRRORED) - WRITEUINT8(save_p, mobj->mirrored); + WRITEUINT8(save->p, mobj->mirrored); if (diff2 & MD2_ROLLANGLE) - WRITEANGLE(save_p, mobj->rollangle); + WRITEANGLE(save->p, mobj->rollangle); if (diff2 & MD2_SHADOWSCALE) { - WRITEFIXED(save_p, mobj->shadowscale); - WRITEUINT8(save_p, mobj->whiteshadow); + WRITEFIXED(save->p, mobj->shadowscale); + WRITEUINT8(save->p, mobj->whiteshadow); } if (diff2 & MD2_RENDERFLAGS) { @@ -2259,553 +2258,553 @@ static void SaveMobjThinker(const thinker_t *th, const UINT8 type) && q != (RF_DONTDRAWP4|RF_DONTDRAWP2|RF_DONTDRAWP3)) rf &= ~q; - WRITEUINT32(save_p, rf); + WRITEUINT32(save->p, rf); } if (diff2 & MD2_SPRITEXSCALE) - WRITEFIXED(save_p, mobj->spritexscale); + WRITEFIXED(save->p, mobj->spritexscale); if (diff2 & MD2_SPRITEYSCALE) - WRITEFIXED(save_p, mobj->spriteyscale); + WRITEFIXED(save->p, mobj->spriteyscale); if (diff2 & MD2_SPRITEXOFFSET) - WRITEFIXED(save_p, mobj->spritexoffset); + WRITEFIXED(save->p, mobj->spritexoffset); if (diff2 & MD2_SPRITEYOFFSET) - WRITEFIXED(save_p, mobj->spriteyoffset); + WRITEFIXED(save->p, mobj->spriteyoffset); if (diff2 & MD2_FLOORSPRITESLOPE) { pslope_t *slope = mobj->floorspriteslope; - WRITEFIXED(save_p, slope->zdelta); - WRITEANGLE(save_p, slope->zangle); - WRITEANGLE(save_p, slope->xydirection); + WRITEFIXED(save->p, slope->zdelta); + WRITEANGLE(save->p, slope->zangle); + WRITEANGLE(save->p, slope->xydirection); - WRITEFIXED(save_p, slope->o.x); - WRITEFIXED(save_p, slope->o.y); - WRITEFIXED(save_p, slope->o.z); + WRITEFIXED(save->p, slope->o.x); + WRITEFIXED(save->p, slope->o.y); + WRITEFIXED(save->p, slope->o.z); - WRITEFIXED(save_p, slope->d.x); - WRITEFIXED(save_p, slope->d.y); + WRITEFIXED(save->p, slope->d.x); + WRITEFIXED(save->p, slope->d.y); - WRITEFIXED(save_p, slope->normal.x); - WRITEFIXED(save_p, slope->normal.y); - WRITEFIXED(save_p, slope->normal.z); + WRITEFIXED(save->p, slope->normal.x); + WRITEFIXED(save->p, slope->normal.y); + WRITEFIXED(save->p, slope->normal.z); } if (diff2 & MD2_HITLAG) { - WRITEINT32(save_p, mobj->hitlag); + WRITEINT32(save->p, mobj->hitlag); } if (diff2 & MD2_WATERSKIP) { - WRITEUINT8(save_p, mobj->waterskip); + WRITEUINT8(save->p, mobj->waterskip); } if (diff2 & MD2_DISPOFFSET) { - WRITEINT32(save_p, mobj->dispoffset); + WRITEINT32(save->p, mobj->dispoffset); } if (diff2 & MD2_LASTMOMZ) { - WRITEINT32(save_p, mobj->lastmomz); + WRITEINT32(save->p, mobj->lastmomz); } if (diff2 & MD2_TERRAIN) { - WRITEUINT32(save_p, K_GetTerrainHeapIndex(mobj->terrain)); - WRITEUINT32(save_p, SaveMobjnum(mobj->terrainOverlay)); + WRITEUINT32(save->p, K_GetTerrainHeapIndex(mobj->terrain)); + WRITEUINT32(save->p, SaveMobjnum(mobj->terrainOverlay)); } - WRITEUINT32(save_p, mobj->mobjnum); + WRITEUINT32(save->p, mobj->mobjnum); } -static void SaveNoEnemiesThinker(const thinker_t *th, const UINT8 type) +static void SaveNoEnemiesThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const noenemies_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveLine(ht->sourceline)); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveLine(ht->sourceline)); } -static void SaveBounceCheeseThinker(const thinker_t *th, const UINT8 type) +static void SaveBounceCheeseThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const bouncecheese_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveLine(ht->sourceline)); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEFIXED(save_p, ht->speed); - WRITEFIXED(save_p, ht->distance); - WRITEFIXED(save_p, ht->floorwasheight); - WRITEFIXED(save_p, ht->ceilingwasheight); - WRITECHAR(save_p, ht->low); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveLine(ht->sourceline)); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEFIXED(save->p, ht->speed); + WRITEFIXED(save->p, ht->distance); + WRITEFIXED(save->p, ht->floorwasheight); + WRITEFIXED(save->p, ht->ceilingwasheight); + WRITECHAR(save->p, ht->low); } -static void SaveContinuousFallThinker(const thinker_t *th, const UINT8 type) +static void SaveContinuousFallThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const continuousfall_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEFIXED(save_p, ht->speed); - WRITEINT32(save_p, ht->direction); - WRITEFIXED(save_p, ht->floorstartheight); - WRITEFIXED(save_p, ht->ceilingstartheight); - WRITEFIXED(save_p, ht->destheight); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEFIXED(save->p, ht->speed); + WRITEINT32(save->p, ht->direction); + WRITEFIXED(save->p, ht->floorstartheight); + WRITEFIXED(save->p, ht->ceilingstartheight); + WRITEFIXED(save->p, ht->destheight); } -static void SaveMarioBlockThinker(const thinker_t *th, const UINT8 type) +static void SaveMarioBlockThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const mariothink_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEFIXED(save_p, ht->speed); - WRITEINT32(save_p, ht->direction); - WRITEFIXED(save_p, ht->floorstartheight); - WRITEFIXED(save_p, ht->ceilingstartheight); - WRITEINT16(save_p, ht->tag); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEFIXED(save->p, ht->speed); + WRITEINT32(save->p, ht->direction); + WRITEFIXED(save->p, ht->floorstartheight); + WRITEFIXED(save->p, ht->ceilingstartheight); + WRITEINT16(save->p, ht->tag); } -static void SaveMarioCheckThinker(const thinker_t *th, const UINT8 type) +static void SaveMarioCheckThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const mariocheck_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveLine(ht->sourceline)); - WRITEUINT32(save_p, SaveSector(ht->sector)); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveLine(ht->sourceline)); + WRITEUINT32(save->p, SaveSector(ht->sector)); } -static void SaveThwompThinker(const thinker_t *th, const UINT8 type) +static void SaveThwompThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const thwomp_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveLine(ht->sourceline)); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEFIXED(save_p, ht->crushspeed); - WRITEFIXED(save_p, ht->retractspeed); - WRITEINT32(save_p, ht->direction); - WRITEFIXED(save_p, ht->floorstartheight); - WRITEFIXED(save_p, ht->ceilingstartheight); - WRITEINT32(save_p, ht->delay); - WRITEINT16(save_p, ht->tag); - WRITEUINT16(save_p, ht->sound); - WRITEINT32(save_p, ht->initDelay); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveLine(ht->sourceline)); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEFIXED(save->p, ht->crushspeed); + WRITEFIXED(save->p, ht->retractspeed); + WRITEINT32(save->p, ht->direction); + WRITEFIXED(save->p, ht->floorstartheight); + WRITEFIXED(save->p, ht->ceilingstartheight); + WRITEINT32(save->p, ht->delay); + WRITEINT16(save->p, ht->tag); + WRITEUINT16(save->p, ht->sound); + WRITEINT32(save->p, ht->initDelay); } -static void SaveFloatThinker(const thinker_t *th, const UINT8 type) +static void SaveFloatThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const floatthink_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveLine(ht->sourceline)); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEINT16(save_p, ht->tag); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveLine(ht->sourceline)); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEINT16(save->p, ht->tag); } -static void SaveEachTimeThinker(const thinker_t *th, const UINT8 type) +static void SaveEachTimeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const eachtime_t *ht = (const void *)th; size_t i; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveLine(ht->sourceline)); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveLine(ht->sourceline)); for (i = 0; i < MAXPLAYERS; i++) { - WRITECHAR(save_p, ht->playersInArea[i]); + WRITECHAR(save->p, ht->playersInArea[i]); } - WRITECHAR(save_p, ht->triggerOnExit); + WRITECHAR(save->p, ht->triggerOnExit); } -static void SaveRaiseThinker(const thinker_t *th, const UINT8 type) +static void SaveRaiseThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const raise_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT16(save_p, ht->tag); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEFIXED(save_p, ht->ceilingbottom); - WRITEFIXED(save_p, ht->ceilingtop); - WRITEFIXED(save_p, ht->basespeed); - WRITEFIXED(save_p, ht->extraspeed); - WRITEUINT8(save_p, ht->shaketimer); - WRITEUINT8(save_p, ht->flags); + WRITEUINT8(save->p, type); + WRITEINT16(save->p, ht->tag); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEFIXED(save->p, ht->ceilingbottom); + WRITEFIXED(save->p, ht->ceilingtop); + WRITEFIXED(save->p, ht->basespeed); + WRITEFIXED(save->p, ht->extraspeed); + WRITEUINT8(save->p, ht->shaketimer); + WRITEUINT8(save->p, ht->flags); } -static void SaveCeilingThinker(const thinker_t *th, const UINT8 type) +static void SaveCeilingThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const ceiling_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT8(save_p, ht->type); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEFIXED(save_p, ht->bottomheight); - WRITEFIXED(save_p, ht->topheight); - WRITEFIXED(save_p, ht->speed); - WRITEFIXED(save_p, ht->delay); - WRITEFIXED(save_p, ht->delaytimer); - WRITEUINT8(save_p, ht->crush); - WRITEINT32(save_p, ht->texture); - WRITEINT32(save_p, ht->direction); - WRITEINT16(save_p, ht->tag); - WRITEFIXED(save_p, ht->origspeed); - WRITEFIXED(save_p, ht->sourceline); + WRITEUINT8(save->p, type); + WRITEUINT8(save->p, ht->type); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEFIXED(save->p, ht->bottomheight); + WRITEFIXED(save->p, ht->topheight); + WRITEFIXED(save->p, ht->speed); + WRITEFIXED(save->p, ht->delay); + WRITEFIXED(save->p, ht->delaytimer); + WRITEUINT8(save->p, ht->crush); + WRITEINT32(save->p, ht->texture); + WRITEINT32(save->p, ht->direction); + WRITEINT16(save->p, ht->tag); + WRITEFIXED(save->p, ht->origspeed); + WRITEFIXED(save->p, ht->sourceline); } -static void SaveFloormoveThinker(const thinker_t *th, const UINT8 type) +static void SaveFloormoveThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const floormove_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT8(save_p, ht->type); - WRITEUINT8(save_p, ht->crush); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEINT32(save_p, ht->direction); - WRITEINT32(save_p, ht->texture); - WRITEFIXED(save_p, ht->floordestheight); - WRITEFIXED(save_p, ht->speed); - WRITEFIXED(save_p, ht->origspeed); - WRITEFIXED(save_p, ht->delay); - WRITEFIXED(save_p, ht->delaytimer); - WRITEINT16(save_p, ht->tag); - WRITEFIXED(save_p, ht->sourceline); + WRITEUINT8(save->p, type); + WRITEUINT8(save->p, ht->type); + WRITEUINT8(save->p, ht->crush); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEINT32(save->p, ht->direction); + WRITEINT32(save->p, ht->texture); + WRITEFIXED(save->p, ht->floordestheight); + WRITEFIXED(save->p, ht->speed); + WRITEFIXED(save->p, ht->origspeed); + WRITEFIXED(save->p, ht->delay); + WRITEFIXED(save->p, ht->delaytimer); + WRITEINT16(save->p, ht->tag); + WRITEFIXED(save->p, ht->sourceline); } -static void SaveLightflashThinker(const thinker_t *th, const UINT8 type) +static void SaveLightflashThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const lightflash_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEINT32(save_p, ht->maxlight); - WRITEINT32(save_p, ht->minlight); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEINT32(save->p, ht->maxlight); + WRITEINT32(save->p, ht->minlight); } -static void SaveStrobeThinker(const thinker_t *th, const UINT8 type) +static void SaveStrobeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const strobe_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEINT32(save_p, ht->count); - WRITEINT16(save_p, ht->minlight); - WRITEINT16(save_p, ht->maxlight); - WRITEINT32(save_p, ht->darktime); - WRITEINT32(save_p, ht->brighttime); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEINT32(save->p, ht->count); + WRITEINT16(save->p, ht->minlight); + WRITEINT16(save->p, ht->maxlight); + WRITEINT32(save->p, ht->darktime); + WRITEINT32(save->p, ht->brighttime); } -static void SaveGlowThinker(const thinker_t *th, const UINT8 type) +static void SaveGlowThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const glow_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEINT16(save_p, ht->minlight); - WRITEINT16(save_p, ht->maxlight); - WRITEINT16(save_p, ht->direction); - WRITEINT16(save_p, ht->speed); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEINT16(save->p, ht->minlight); + WRITEINT16(save->p, ht->maxlight); + WRITEINT16(save->p, ht->direction); + WRITEINT16(save->p, ht->speed); } -static inline void SaveFireflickerThinker(const thinker_t *th, const UINT8 type) +static inline void SaveFireflickerThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const fireflicker_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEINT32(save_p, ht->count); - WRITEINT32(save_p, ht->resetcount); - WRITEINT16(save_p, ht->maxlight); - WRITEINT16(save_p, ht->minlight); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEINT32(save->p, ht->count); + WRITEINT32(save->p, ht->resetcount); + WRITEINT16(save->p, ht->maxlight); + WRITEINT16(save->p, ht->minlight); } -static void SaveElevatorThinker(const thinker_t *th, const UINT8 type) +static void SaveElevatorThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const elevator_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT8(save_p, ht->type); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEUINT32(save_p, SaveSector(ht->actionsector)); - WRITEINT32(save_p, ht->direction); - WRITEFIXED(save_p, ht->floordestheight); - WRITEFIXED(save_p, ht->ceilingdestheight); - WRITEFIXED(save_p, ht->speed); - WRITEFIXED(save_p, ht->origspeed); - WRITEFIXED(save_p, ht->low); - WRITEFIXED(save_p, ht->high); - WRITEFIXED(save_p, ht->distance); - WRITEFIXED(save_p, ht->delay); - WRITEFIXED(save_p, ht->delaytimer); - WRITEFIXED(save_p, ht->floorwasheight); - WRITEFIXED(save_p, ht->ceilingwasheight); - WRITEUINT32(save_p, SaveLine(ht->sourceline)); + WRITEUINT8(save->p, type); + WRITEUINT8(save->p, ht->type); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEUINT32(save->p, SaveSector(ht->actionsector)); + WRITEINT32(save->p, ht->direction); + WRITEFIXED(save->p, ht->floordestheight); + WRITEFIXED(save->p, ht->ceilingdestheight); + WRITEFIXED(save->p, ht->speed); + WRITEFIXED(save->p, ht->origspeed); + WRITEFIXED(save->p, ht->low); + WRITEFIXED(save->p, ht->high); + WRITEFIXED(save->p, ht->distance); + WRITEFIXED(save->p, ht->delay); + WRITEFIXED(save->p, ht->delaytimer); + WRITEFIXED(save->p, ht->floorwasheight); + WRITEFIXED(save->p, ht->ceilingwasheight); + WRITEUINT32(save->p, SaveLine(ht->sourceline)); } -static void SaveCrumbleThinker(const thinker_t *th, const UINT8 type) +static void SaveCrumbleThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const crumble_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveLine(ht->sourceline)); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEUINT32(save_p, SaveSector(ht->actionsector)); - WRITEUINT32(save_p, SavePlayer(ht->player)); // was dummy - WRITEINT32(save_p, ht->direction); - WRITEINT32(save_p, ht->origalpha); - WRITEINT32(save_p, ht->timer); - WRITEFIXED(save_p, ht->speed); - WRITEFIXED(save_p, ht->floorwasheight); - WRITEFIXED(save_p, ht->ceilingwasheight); - WRITEUINT8(save_p, ht->flags); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveLine(ht->sourceline)); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEUINT32(save->p, SaveSector(ht->actionsector)); + WRITEUINT32(save->p, SavePlayer(ht->player)); // was dummy + WRITEINT32(save->p, ht->direction); + WRITEINT32(save->p, ht->origalpha); + WRITEINT32(save->p, ht->timer); + WRITEFIXED(save->p, ht->speed); + WRITEFIXED(save->p, ht->floorwasheight); + WRITEFIXED(save->p, ht->ceilingwasheight); + WRITEUINT8(save->p, ht->flags); } -static inline void SaveScrollThinker(const thinker_t *th, const UINT8 type) +static inline void SaveScrollThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const scroll_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEFIXED(save_p, ht->dx); - WRITEFIXED(save_p, ht->dy); - WRITEINT32(save_p, ht->affectee); - WRITEINT32(save_p, ht->control); - WRITEFIXED(save_p, ht->last_height); - WRITEFIXED(save_p, ht->vdx); - WRITEFIXED(save_p, ht->vdy); - WRITEINT32(save_p, ht->accel); - WRITEINT32(save_p, ht->exclusive); - WRITEUINT8(save_p, ht->type); + WRITEUINT8(save->p, type); + WRITEFIXED(save->p, ht->dx); + WRITEFIXED(save->p, ht->dy); + WRITEINT32(save->p, ht->affectee); + WRITEINT32(save->p, ht->control); + WRITEFIXED(save->p, ht->last_height); + WRITEFIXED(save->p, ht->vdx); + WRITEFIXED(save->p, ht->vdy); + WRITEINT32(save->p, ht->accel); + WRITEINT32(save->p, ht->exclusive); + WRITEUINT8(save->p, ht->type); } -static inline void SaveFrictionThinker(const thinker_t *th, const UINT8 type) +static inline void SaveFrictionThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const friction_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT32(save_p, ht->friction); - WRITEINT32(save_p, ht->movefactor); - WRITEINT32(save_p, ht->affectee); - WRITEINT32(save_p, ht->referrer); - WRITEUINT8(save_p, ht->roverfriction); + WRITEUINT8(save->p, type); + WRITEINT32(save->p, ht->friction); + WRITEINT32(save->p, ht->movefactor); + WRITEINT32(save->p, ht->affectee); + WRITEINT32(save->p, ht->referrer); + WRITEUINT8(save->p, ht->roverfriction); } -static inline void SavePusherThinker(const thinker_t *th, const UINT8 type) +static inline void SavePusherThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const pusher_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT8(save_p, ht->type); - WRITEFIXED(save_p, ht->x_mag); - WRITEFIXED(save_p, ht->y_mag); - WRITEFIXED(save_p, ht->z_mag); - WRITEINT32(save_p, ht->affectee); - WRITEUINT8(save_p, ht->roverpusher); - WRITEINT32(save_p, ht->referrer); - WRITEINT32(save_p, ht->exclusive); - WRITEINT32(save_p, ht->slider); + WRITEUINT8(save->p, type); + WRITEUINT8(save->p, ht->type); + WRITEFIXED(save->p, ht->x_mag); + WRITEFIXED(save->p, ht->y_mag); + WRITEFIXED(save->p, ht->z_mag); + WRITEINT32(save->p, ht->affectee); + WRITEUINT8(save->p, ht->roverpusher); + WRITEINT32(save->p, ht->referrer); + WRITEINT32(save->p, ht->exclusive); + WRITEINT32(save->p, ht->slider); } -static void SaveLaserThinker(const thinker_t *th, const UINT8 type) +static void SaveLaserThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const laserthink_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT16(save_p, ht->tag); - WRITEUINT32(save_p, SaveLine(ht->sourceline)); - WRITEUINT8(save_p, ht->nobosses); + WRITEUINT8(save->p, type); + WRITEINT16(save->p, ht->tag); + WRITEUINT32(save->p, SaveLine(ht->sourceline)); + WRITEUINT8(save->p, ht->nobosses); } -static void SaveLightlevelThinker(const thinker_t *th, const UINT8 type) +static void SaveLightlevelThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const lightlevel_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEINT16(save_p, ht->sourcelevel); - WRITEINT16(save_p, ht->destlevel); - WRITEFIXED(save_p, ht->fixedcurlevel); - WRITEFIXED(save_p, ht->fixedpertic); - WRITEINT32(save_p, ht->timer); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEINT16(save->p, ht->sourcelevel); + WRITEINT16(save->p, ht->destlevel); + WRITEFIXED(save->p, ht->fixedcurlevel); + WRITEFIXED(save->p, ht->fixedpertic); + WRITEINT32(save->p, ht->timer); } -static void SaveExecutorThinker(const thinker_t *th, const UINT8 type) +static void SaveExecutorThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const executor_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveLine(ht->line)); - WRITEUINT32(save_p, SaveMobjnum(ht->caller)); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEINT32(save_p, ht->timer); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveLine(ht->line)); + WRITEUINT32(save->p, SaveMobjnum(ht->caller)); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEINT32(save->p, ht->timer); } -static void SaveDisappearThinker(const thinker_t *th, const UINT8 type) +static void SaveDisappearThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const disappear_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, ht->appeartime); - WRITEUINT32(save_p, ht->disappeartime); - WRITEUINT32(save_p, ht->offset); - WRITEUINT32(save_p, ht->timer); - WRITEINT32(save_p, ht->affectee); - WRITEINT32(save_p, ht->sourceline); - WRITEINT32(save_p, ht->exists); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, ht->appeartime); + WRITEUINT32(save->p, ht->disappeartime); + WRITEUINT32(save->p, ht->offset); + WRITEUINT32(save->p, ht->timer); + WRITEINT32(save->p, ht->affectee); + WRITEINT32(save->p, ht->sourceline); + WRITEINT32(save->p, ht->exists); } -static void SaveFadeThinker(const thinker_t *th, const UINT8 type) +static void SaveFadeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const fade_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, CheckAddNetColormapToList(ht->dest_exc)); - WRITEUINT32(save_p, ht->sectornum); - WRITEUINT32(save_p, ht->ffloornum); - WRITEINT32(save_p, ht->alpha); - WRITEINT16(save_p, ht->sourcevalue); - WRITEINT16(save_p, ht->destvalue); - WRITEINT16(save_p, ht->destlightlevel); - WRITEINT16(save_p, ht->speed); - WRITEUINT8(save_p, (UINT8)ht->ticbased); - WRITEINT32(save_p, ht->timer); - WRITEUINT8(save_p, ht->doexists); - WRITEUINT8(save_p, ht->dotranslucent); - WRITEUINT8(save_p, ht->dolighting); - WRITEUINT8(save_p, ht->docolormap); - WRITEUINT8(save_p, ht->docollision); - WRITEUINT8(save_p, ht->doghostfade); - WRITEUINT8(save_p, ht->exactalpha); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, CheckAddNetColormapToList(ht->dest_exc)); + WRITEUINT32(save->p, ht->sectornum); + WRITEUINT32(save->p, ht->ffloornum); + WRITEINT32(save->p, ht->alpha); + WRITEINT16(save->p, ht->sourcevalue); + WRITEINT16(save->p, ht->destvalue); + WRITEINT16(save->p, ht->destlightlevel); + WRITEINT16(save->p, ht->speed); + WRITEUINT8(save->p, (UINT8)ht->ticbased); + WRITEINT32(save->p, ht->timer); + WRITEUINT8(save->p, ht->doexists); + WRITEUINT8(save->p, ht->dotranslucent); + WRITEUINT8(save->p, ht->dolighting); + WRITEUINT8(save->p, ht->docolormap); + WRITEUINT8(save->p, ht->docollision); + WRITEUINT8(save->p, ht->doghostfade); + WRITEUINT8(save->p, ht->exactalpha); } -static void SaveFadeColormapThinker(const thinker_t *th, const UINT8 type) +static void SaveFadeColormapThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const fadecolormap_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveSector(ht->sector)); - WRITEUINT32(save_p, CheckAddNetColormapToList(ht->source_exc)); - WRITEUINT32(save_p, CheckAddNetColormapToList(ht->dest_exc)); - WRITEUINT8(save_p, (UINT8)ht->ticbased); - WRITEINT32(save_p, ht->duration); - WRITEINT32(save_p, ht->timer); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveSector(ht->sector)); + WRITEUINT32(save->p, CheckAddNetColormapToList(ht->source_exc)); + WRITEUINT32(save->p, CheckAddNetColormapToList(ht->dest_exc)); + WRITEUINT8(save->p, (UINT8)ht->ticbased); + WRITEINT32(save->p, ht->duration); + WRITEINT32(save->p, ht->timer); } -static void SavePlaneDisplaceThinker(const thinker_t *th, const UINT8 type) +static void SavePlaneDisplaceThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const planedisplace_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT32(save_p, ht->affectee); - WRITEINT32(save_p, ht->control); - WRITEFIXED(save_p, ht->last_height); - WRITEFIXED(save_p, ht->speed); - WRITEUINT8(save_p, ht->type); + WRITEUINT8(save->p, type); + WRITEINT32(save->p, ht->affectee); + WRITEINT32(save->p, ht->control); + WRITEFIXED(save->p, ht->last_height); + WRITEFIXED(save->p, ht->speed); + WRITEUINT8(save->p, ht->type); } -static inline void SaveDynamicLineSlopeThinker(const thinker_t *th, const UINT8 type) +static inline void SaveDynamicLineSlopeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const dynlineplanethink_t* ht = (const void*)th; - WRITEUINT8(save_p, type); - WRITEUINT8(save_p, ht->type); - WRITEUINT32(save_p, SaveSlope(ht->slope)); - WRITEUINT32(save_p, SaveLine(ht->sourceline)); - WRITEFIXED(save_p, ht->extent); + WRITEUINT8(save->p, type); + WRITEUINT8(save->p, ht->type); + WRITEUINT32(save->p, SaveSlope(ht->slope)); + WRITEUINT32(save->p, SaveLine(ht->sourceline)); + WRITEFIXED(save->p, ht->extent); } -static inline void SaveDynamicVertexSlopeThinker(const thinker_t *th, const UINT8 type) +static inline void SaveDynamicVertexSlopeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { size_t i; const dynvertexplanethink_t* ht = (const void*)th; - WRITEUINT8(save_p, type); - WRITEUINT32(save_p, SaveSlope(ht->slope)); + WRITEUINT8(save->p, type); + WRITEUINT32(save->p, SaveSlope(ht->slope)); for (i = 0; i < 3; i++) - WRITEUINT32(save_p, SaveSector(ht->secs[i])); - WRITEMEM(save_p, ht->vex, sizeof(ht->vex)); - WRITEMEM(save_p, ht->origsecheights, sizeof(ht->origsecheights)); - WRITEMEM(save_p, ht->origvecheights, sizeof(ht->origvecheights)); - WRITEUINT8(save_p, ht->relative); + WRITEUINT32(save->p, SaveSector(ht->secs[i])); + WRITEMEM(save->p, ht->vex, sizeof(ht->vex)); + WRITEMEM(save->p, ht->origsecheights, sizeof(ht->origsecheights)); + WRITEMEM(save->p, ht->origvecheights, sizeof(ht->origvecheights)); + WRITEUINT8(save->p, ht->relative); } -static inline void SavePolyrotatetThinker(const thinker_t *th, const UINT8 type) +static inline void SavePolyrotatetThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const polyrotate_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT32(save_p, ht->polyObjNum); - WRITEINT32(save_p, ht->speed); - WRITEINT32(save_p, ht->distance); - WRITEUINT8(save_p, ht->turnobjs); + WRITEUINT8(save->p, type); + WRITEINT32(save->p, ht->polyObjNum); + WRITEINT32(save->p, ht->speed); + WRITEINT32(save->p, ht->distance); + WRITEUINT8(save->p, ht->turnobjs); } -static void SavePolymoveThinker(const thinker_t *th, const UINT8 type) +static void SavePolymoveThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const polymove_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT32(save_p, ht->polyObjNum); - WRITEINT32(save_p, ht->speed); - WRITEFIXED(save_p, ht->momx); - WRITEFIXED(save_p, ht->momy); - WRITEINT32(save_p, ht->distance); - WRITEANGLE(save_p, ht->angle); + WRITEUINT8(save->p, type); + WRITEINT32(save->p, ht->polyObjNum); + WRITEINT32(save->p, ht->speed); + WRITEFIXED(save->p, ht->momx); + WRITEFIXED(save->p, ht->momy); + WRITEINT32(save->p, ht->distance); + WRITEANGLE(save->p, ht->angle); } -static void SavePolywaypointThinker(const thinker_t *th, UINT8 type) +static void SavePolywaypointThinker(savebuffer_t *save, const thinker_t *th, UINT8 type) { const polywaypoint_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT32(save_p, ht->polyObjNum); - WRITEINT32(save_p, ht->speed); - WRITEINT32(save_p, ht->sequence); - WRITEINT32(save_p, ht->pointnum); - WRITEINT32(save_p, ht->direction); - WRITEUINT8(save_p, ht->returnbehavior); - WRITEUINT8(save_p, ht->continuous); - WRITEUINT8(save_p, ht->stophere); + WRITEUINT8(save->p, type); + WRITEINT32(save->p, ht->polyObjNum); + WRITEINT32(save->p, ht->speed); + WRITEINT32(save->p, ht->sequence); + WRITEINT32(save->p, ht->pointnum); + WRITEINT32(save->p, ht->direction); + WRITEUINT8(save->p, ht->returnbehavior); + WRITEUINT8(save->p, ht->continuous); + WRITEUINT8(save->p, ht->stophere); } -static void SavePolyslidedoorThinker(const thinker_t *th, const UINT8 type) +static void SavePolyslidedoorThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const polyslidedoor_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT32(save_p, ht->polyObjNum); - WRITEINT32(save_p, ht->delay); - WRITEINT32(save_p, ht->delayCount); - WRITEINT32(save_p, ht->initSpeed); - WRITEINT32(save_p, ht->speed); - WRITEINT32(save_p, ht->initDistance); - WRITEINT32(save_p, ht->distance); - WRITEUINT32(save_p, ht->initAngle); - WRITEUINT32(save_p, ht->angle); - WRITEUINT32(save_p, ht->revAngle); - WRITEFIXED(save_p, ht->momx); - WRITEFIXED(save_p, ht->momy); - WRITEUINT8(save_p, ht->closing); + WRITEUINT8(save->p, type); + WRITEINT32(save->p, ht->polyObjNum); + WRITEINT32(save->p, ht->delay); + WRITEINT32(save->p, ht->delayCount); + WRITEINT32(save->p, ht->initSpeed); + WRITEINT32(save->p, ht->speed); + WRITEINT32(save->p, ht->initDistance); + WRITEINT32(save->p, ht->distance); + WRITEUINT32(save->p, ht->initAngle); + WRITEUINT32(save->p, ht->angle); + WRITEUINT32(save->p, ht->revAngle); + WRITEFIXED(save->p, ht->momx); + WRITEFIXED(save->p, ht->momy); + WRITEUINT8(save->p, ht->closing); } -static void SavePolyswingdoorThinker(const thinker_t *th, const UINT8 type) +static void SavePolyswingdoorThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const polyswingdoor_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT32(save_p, ht->polyObjNum); - WRITEINT32(save_p, ht->delay); - WRITEINT32(save_p, ht->delayCount); - WRITEINT32(save_p, ht->initSpeed); - WRITEINT32(save_p, ht->speed); - WRITEINT32(save_p, ht->initDistance); - WRITEINT32(save_p, ht->distance); - WRITEUINT8(save_p, ht->closing); + WRITEUINT8(save->p, type); + WRITEINT32(save->p, ht->polyObjNum); + WRITEINT32(save->p, ht->delay); + WRITEINT32(save->p, ht->delayCount); + WRITEINT32(save->p, ht->initSpeed); + WRITEINT32(save->p, ht->speed); + WRITEINT32(save->p, ht->initDistance); + WRITEINT32(save->p, ht->distance); + WRITEUINT8(save->p, ht->closing); } -static void SavePolydisplaceThinker(const thinker_t *th, const UINT8 type) +static void SavePolydisplaceThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const polydisplace_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT32(save_p, ht->polyObjNum); - WRITEUINT32(save_p, SaveSector(ht->controlSector)); - WRITEFIXED(save_p, ht->dx); - WRITEFIXED(save_p, ht->dy); - WRITEFIXED(save_p, ht->oldHeights); + WRITEUINT8(save->p, type); + WRITEINT32(save->p, ht->polyObjNum); + WRITEUINT32(save->p, SaveSector(ht->controlSector)); + WRITEFIXED(save->p, ht->dx); + WRITEFIXED(save->p, ht->dy); + WRITEFIXED(save->p, ht->oldHeights); } -static void SavePolyrotdisplaceThinker(const thinker_t *th, const UINT8 type) +static void SavePolyrotdisplaceThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const polyrotdisplace_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT32(save_p, ht->polyObjNum); - WRITEUINT32(save_p, SaveSector(ht->controlSector)); - WRITEFIXED(save_p, ht->rotscale); - WRITEUINT8(save_p, ht->turnobjs); - WRITEFIXED(save_p, ht->oldHeights); + WRITEUINT8(save->p, type); + WRITEINT32(save->p, ht->polyObjNum); + WRITEUINT32(save->p, SaveSector(ht->controlSector)); + WRITEFIXED(save->p, ht->rotscale); + WRITEUINT8(save->p, ht->turnobjs); + WRITEFIXED(save->p, ht->oldHeights); } -static void SavePolyfadeThinker(const thinker_t *th, const UINT8 type) +static void SavePolyfadeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { const polyfade_t *ht = (const void *)th; - WRITEUINT8(save_p, type); - WRITEINT32(save_p, ht->polyObjNum); - WRITEINT32(save_p, ht->sourcevalue); - WRITEINT32(save_p, ht->destvalue); - WRITEUINT8(save_p, (UINT8)ht->docollision); - WRITEUINT8(save_p, (UINT8)ht->doghostfade); - WRITEUINT8(save_p, (UINT8)ht->ticbased); - WRITEINT32(save_p, ht->duration); - WRITEINT32(save_p, ht->timer); + WRITEUINT8(save->p, type); + WRITEINT32(save->p, ht->polyObjNum); + WRITEINT32(save->p, ht->sourcevalue); + WRITEINT32(save->p, ht->destvalue); + WRITEUINT8(save->p, (UINT8)ht->docollision); + WRITEUINT8(save->p, (UINT8)ht->doghostfade); + WRITEUINT8(save->p, (UINT8)ht->ticbased); + WRITEINT32(save->p, ht->duration); + WRITEINT32(save->p, ht->timer); } -static void P_NetArchiveThinkers(void) +static void P_NetArchiveThinkers(savebuffer_t *save) { const thinker_t *th; UINT32 i; - WRITEUINT32(save_p, ARCHIVEBLOCK_THINKERS); + WRITEUINT32(save->p, ARCHIVEBLOCK_THINKERS); for (i = 0; i < NUM_THINKERLISTS; i++) { @@ -2819,7 +2818,7 @@ static void P_NetArchiveThinkers(void) if (th->function.acp1 == (actionf_p1)P_MobjThinker) { - SaveMobjThinker(th, tc_mobj); + SaveMobjThinker(save, th, tc_mobj); continue; } #ifdef PARANOIA @@ -2827,202 +2826,202 @@ static void P_NetArchiveThinkers(void) #endif else if (th->function.acp1 == (actionf_p1)T_MoveCeiling) { - SaveCeilingThinker(th, tc_ceiling); + SaveCeilingThinker(save, th, tc_ceiling); continue; } else if (th->function.acp1 == (actionf_p1)T_CrushCeiling) { - SaveCeilingThinker(th, tc_crushceiling); + SaveCeilingThinker(save, th, tc_crushceiling); continue; } else if (th->function.acp1 == (actionf_p1)T_MoveFloor) { - SaveFloormoveThinker(th, tc_floor); + SaveFloormoveThinker(save, th, tc_floor); continue; } else if (th->function.acp1 == (actionf_p1)T_LightningFlash) { - SaveLightflashThinker(th, tc_flash); + SaveLightflashThinker(save, th, tc_flash); continue; } else if (th->function.acp1 == (actionf_p1)T_StrobeFlash) { - SaveStrobeThinker(th, tc_strobe); + SaveStrobeThinker(save, th, tc_strobe); continue; } else if (th->function.acp1 == (actionf_p1)T_Glow) { - SaveGlowThinker(th, tc_glow); + SaveGlowThinker(save, th, tc_glow); continue; } else if (th->function.acp1 == (actionf_p1)T_FireFlicker) { - SaveFireflickerThinker(th, tc_fireflicker); + SaveFireflickerThinker(save, th, tc_fireflicker); continue; } else if (th->function.acp1 == (actionf_p1)T_MoveElevator) { - SaveElevatorThinker(th, tc_elevator); + SaveElevatorThinker(save, th, tc_elevator); continue; } else if (th->function.acp1 == (actionf_p1)T_ContinuousFalling) { - SaveContinuousFallThinker(th, tc_continuousfalling); + SaveContinuousFallThinker(save, th, tc_continuousfalling); continue; } else if (th->function.acp1 == (actionf_p1)T_ThwompSector) { - SaveThwompThinker(th, tc_thwomp); + SaveThwompThinker(save, th, tc_thwomp); continue; } else if (th->function.acp1 == (actionf_p1)T_NoEnemiesSector) { - SaveNoEnemiesThinker(th, tc_noenemies); + SaveNoEnemiesThinker(save, th, tc_noenemies); continue; } else if (th->function.acp1 == (actionf_p1)T_EachTimeThinker) { - SaveEachTimeThinker(th, tc_eachtime); + SaveEachTimeThinker(save, th, tc_eachtime); continue; } else if (th->function.acp1 == (actionf_p1)T_RaiseSector) { - SaveRaiseThinker(th, tc_raisesector); + SaveRaiseThinker(save, th, tc_raisesector); continue; } else if (th->function.acp1 == (actionf_p1)T_CameraScanner) { - SaveElevatorThinker(th, tc_camerascanner); + SaveElevatorThinker(save, th, tc_camerascanner); continue; } else if (th->function.acp1 == (actionf_p1)T_Scroll) { - SaveScrollThinker(th, tc_scroll); + SaveScrollThinker(save, th, tc_scroll); continue; } else if (th->function.acp1 == (actionf_p1)T_Friction) { - SaveFrictionThinker(th, tc_friction); + SaveFrictionThinker(save, th, tc_friction); continue; } else if (th->function.acp1 == (actionf_p1)T_Pusher) { - SavePusherThinker(th, tc_pusher); + SavePusherThinker(save, th, tc_pusher); continue; } else if (th->function.acp1 == (actionf_p1)T_BounceCheese) { - SaveBounceCheeseThinker(th, tc_bouncecheese); + SaveBounceCheeseThinker(save, th, tc_bouncecheese); continue; } else if (th->function.acp1 == (actionf_p1)T_StartCrumble) { - SaveCrumbleThinker(th, tc_startcrumble); + SaveCrumbleThinker(save, th, tc_startcrumble); continue; } else if (th->function.acp1 == (actionf_p1)T_MarioBlock) { - SaveMarioBlockThinker(th, tc_marioblock); + SaveMarioBlockThinker(save, th, tc_marioblock); continue; } else if (th->function.acp1 == (actionf_p1)T_MarioBlockChecker) { - SaveMarioCheckThinker(th, tc_marioblockchecker); + SaveMarioCheckThinker(save, th, tc_marioblockchecker); continue; } else if (th->function.acp1 == (actionf_p1)T_FloatSector) { - SaveFloatThinker(th, tc_floatsector); + SaveFloatThinker(save, th, tc_floatsector); continue; } else if (th->function.acp1 == (actionf_p1)T_LaserFlash) { - SaveLaserThinker(th, tc_laserflash); + SaveLaserThinker(save, th, tc_laserflash); continue; } else if (th->function.acp1 == (actionf_p1)T_LightFade) { - SaveLightlevelThinker(th, tc_lightfade); + SaveLightlevelThinker(save, th, tc_lightfade); continue; } else if (th->function.acp1 == (actionf_p1)T_ExecutorDelay) { - SaveExecutorThinker(th, tc_executor); + SaveExecutorThinker(save, th, tc_executor); continue; } else if (th->function.acp1 == (actionf_p1)T_Disappear) { - SaveDisappearThinker(th, tc_disappear); + SaveDisappearThinker(save, th, tc_disappear); continue; } else if (th->function.acp1 == (actionf_p1)T_Fade) { - SaveFadeThinker(th, tc_fade); + SaveFadeThinker(save, th, tc_fade); continue; } else if (th->function.acp1 == (actionf_p1)T_FadeColormap) { - SaveFadeColormapThinker(th, tc_fadecolormap); + SaveFadeColormapThinker(save, th, tc_fadecolormap); continue; } else if (th->function.acp1 == (actionf_p1)T_PlaneDisplace) { - SavePlaneDisplaceThinker(th, tc_planedisplace); + SavePlaneDisplaceThinker(save, th, tc_planedisplace); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjRotate) { - SavePolyrotatetThinker(th, tc_polyrotate); + SavePolyrotatetThinker(save, th, tc_polyrotate); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjMove) { - SavePolymoveThinker(th, tc_polymove); + SavePolymoveThinker(save, th, tc_polymove); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjWaypoint) { - SavePolywaypointThinker(th, tc_polywaypoint); + SavePolywaypointThinker(save, th, tc_polywaypoint); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyDoorSlide) { - SavePolyslidedoorThinker(th, tc_polyslidedoor); + SavePolyslidedoorThinker(save, th, tc_polyslidedoor); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyDoorSwing) { - SavePolyswingdoorThinker(th, tc_polyswingdoor); + SavePolyswingdoorThinker(save, th, tc_polyswingdoor); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjFlag) { - SavePolymoveThinker(th, tc_polyflag); + SavePolymoveThinker(save, th, tc_polyflag); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjDisplace) { - SavePolydisplaceThinker(th, tc_polydisplace); + SavePolydisplaceThinker(save, th, tc_polydisplace); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjRotDisplace) { - SavePolyrotdisplaceThinker(th, tc_polyrotdisplace); + SavePolyrotdisplaceThinker(save, th, tc_polyrotdisplace); continue; } else if (th->function.acp1 == (actionf_p1)T_PolyObjFade) { - SavePolyfadeThinker(th, tc_polyfade); + SavePolyfadeThinker(save, th, tc_polyfade); continue; } else if (th->function.acp1 == (actionf_p1)T_DynamicSlopeLine) { - SaveDynamicLineSlopeThinker(th, tc_dynslopeline); + SaveDynamicLineSlopeThinker(save, th, tc_dynslopeline); continue; } else if (th->function.acp1 == (actionf_p1)T_DynamicSlopeVert) { - SaveDynamicVertexSlopeThinker(th, tc_dynslopevert); + SaveDynamicVertexSlopeThinker(save, th, tc_dynslopevert); continue; } #ifdef PARANOIA @@ -3033,34 +3032,34 @@ static void P_NetArchiveThinkers(void) CONS_Debug(DBG_NETPLAY, "%u thinkers saved in list %d\n", numsaved, i); - WRITEUINT8(save_p, tc_end); + WRITEUINT8(save->p, tc_end); } } -static void P_NetArchiveWaypoints(void) +static void P_NetArchiveWaypoints(savebuffer_t *save) { waypoint_t *waypoint; size_t i; size_t numWaypoints = K_GetNumWaypoints(); - WRITEUINT32(save_p, ARCHIVEBLOCK_WAYPOINTS); - WRITEUINT32(save_p, numWaypoints); + WRITEUINT32(save->p, ARCHIVEBLOCK_WAYPOINTS); + WRITEUINT32(save->p, numWaypoints); for (i = 0U; i < numWaypoints; i++) { waypoint = K_GetWaypointFromIndex(i); // The only thing we save for each waypoint is the mobj. // Waypoints should NEVER be completely created or destroyed mid-race as a result of this - WRITEUINT32(save_p, waypoint->mobj->mobjnum); + WRITEUINT32(save->p, waypoint->mobj->mobjnum); } } -static void P_NetUnArchiveWaypoints(void) +static void P_NetUnArchiveWaypoints(savebuffer_t *save) { - if (READUINT32(save_p) != ARCHIVEBLOCK_WAYPOINTS) + if (READUINT32(save->p) != ARCHIVEBLOCK_WAYPOINTS) I_Error("Bad $$$.sav at archive block Waypoints!"); else { - UINT32 numArchiveWaypoints = READUINT32(save_p); + UINT32 numArchiveWaypoints = READUINT32(save->p); size_t numSpawnedWaypoints = K_GetNumWaypoints(); if (numArchiveWaypoints != numSpawnedWaypoints) { @@ -3071,7 +3070,7 @@ static void P_NetUnArchiveWaypoints(void) UINT32 temp; for (i = 0U; i < numArchiveWaypoints; i++) { waypoint = K_GetWaypointFromIndex(i); - temp = READUINT32(save_p); + temp = READUINT32(save->p); if (!P_SetTarget(&waypoint->mobj, P_FindNewPosition(temp))) { CONS_Debug(DBG_GAMELOGIC, "waypoint mobj not found for %d\n", i); } @@ -3140,7 +3139,7 @@ static inline pslope_t *LoadSlope(UINT32 slopeid) return NULL; } -static thinker_t* LoadMobjThinker(actionf_p1 thinker) +static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) { thinker_t *next; mobj_t *mobj; @@ -3150,35 +3149,35 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) fixed_t z, floorz, ceilingz; ffloor_t *floorrover = NULL, *ceilingrover = NULL; - diff = READUINT32(save_p); + diff = READUINT32(save->p); if (diff & MD_MORE) - diff2 = READUINT32(save_p); + diff2 = READUINT32(save->p); else diff2 = 0; - next = (void *)(size_t)READUINT32(save_p); + next = (void *)(size_t)READUINT32(save->p); - z = READFIXED(save_p); // Force this so 3dfloor problems don't arise. - floorz = READFIXED(save_p); - ceilingz = READFIXED(save_p); + z = READFIXED(save->p); // Force this so 3dfloor problems don't arise. + floorz = READFIXED(save->p); + ceilingz = READFIXED(save->p); if (diff2 & MD2_FLOORROVER) { - sector_t *sec = LoadSector(READUINT32(save_p)); - UINT16 id = READUINT16(save_p); + sector_t *sec = LoadSector(READUINT32(save->p)); + UINT16 id = READUINT16(save->p); floorrover = P_GetFFloorByID(sec, id); } if (diff2 & MD2_CEILINGROVER) { - sector_t *sec = LoadSector(READUINT32(save_p)); - UINT16 id = READUINT16(save_p); + sector_t *sec = LoadSector(READUINT32(save->p)); + UINT16 id = READUINT16(save->p); ceilingrover = P_GetFFloorByID(sec, id); } if (diff & MD_SPAWNPOINT) { - UINT16 spawnpointnum = READUINT16(save_p); + UINT16 spawnpointnum = READUINT16(save->p); if (mapthings[spawnpointnum].type == 1713) // NiGHTS Hoop special case { @@ -3204,7 +3203,7 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) mobj->ceilingrover = ceilingrover; if (diff & MD_TYPE) - mobj->type = READUINT32(save_p); + mobj->type = READUINT32(save->p); else { for (i = 0; i < NUMMOBJTYPES; i++) @@ -3223,11 +3222,11 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) mobj->info = &mobjinfo[mobj->type]; if (diff & MD_POS) { - mobj->x = mobj->old_x = READFIXED(save_p); - mobj->y = mobj->old_y = READFIXED(save_p); - mobj->angle = mobj->old_angle = READANGLE(save_p); - mobj->pitch = mobj->old_pitch = READANGLE(save_p); - mobj->roll = mobj->old_roll = READANGLE(save_p); + mobj->x = mobj->old_x = READFIXED(save->p); + mobj->y = mobj->old_y = READFIXED(save->p); + mobj->angle = mobj->old_angle = READANGLE(save->p); + mobj->pitch = mobj->old_pitch = READANGLE(save->p); + mobj->roll = mobj->old_roll = READANGLE(save->p); } else { @@ -3239,47 +3238,47 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) } if (diff & MD_MOM) { - mobj->momx = READFIXED(save_p); - mobj->momy = READFIXED(save_p); - mobj->momz = READFIXED(save_p); - mobj->pmomz = READFIXED(save_p); + mobj->momx = READFIXED(save->p); + mobj->momy = READFIXED(save->p); + mobj->momz = READFIXED(save->p); + mobj->pmomz = READFIXED(save->p); } // otherwise they're zero, and the memset took care of it if (diff & MD_RADIUS) - mobj->radius = READFIXED(save_p); + mobj->radius = READFIXED(save->p); else mobj->radius = mobj->info->radius; if (diff & MD_HEIGHT) - mobj->height = READFIXED(save_p); + mobj->height = READFIXED(save->p); else mobj->height = mobj->info->height; if (diff & MD_FLAGS) - mobj->flags = READUINT32(save_p); + mobj->flags = READUINT32(save->p); else mobj->flags = mobj->info->flags; if (diff & MD_FLAGS2) - mobj->flags2 = READUINT32(save_p); + mobj->flags2 = READUINT32(save->p); if (diff & MD_HEALTH) - mobj->health = READINT32(save_p); + mobj->health = READINT32(save->p); else mobj->health = mobj->info->spawnhealth; if (diff & MD_RTIME) - mobj->reactiontime = READINT32(save_p); + mobj->reactiontime = READINT32(save->p); else mobj->reactiontime = mobj->info->reactiontime; if (diff & MD_STATE) - mobj->state = &states[READUINT16(save_p)]; + mobj->state = &states[READUINT16(save->p)]; else mobj->state = &states[mobj->info->spawnstate]; if (diff & MD_TICS) - mobj->tics = READINT32(save_p); + mobj->tics = READINT32(save->p); else mobj->tics = mobj->state->tics; if (diff & MD_SPRITE) { - mobj->sprite = READUINT16(save_p); + mobj->sprite = READUINT16(save->p); if (mobj->sprite == SPR_PLAY) - mobj->sprite2 = READUINT8(save_p); + mobj->sprite2 = READUINT8(save->p); } else { mobj->sprite = mobj->state->sprite; @@ -3288,8 +3287,8 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) } if (diff & MD_FRAME) { - mobj->frame = READUINT32(save_p); - mobj->anim_duration = READUINT16(save_p); + mobj->frame = READUINT32(save->p); + mobj->anim_duration = READUINT16(save->p); } else { @@ -3297,139 +3296,139 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) mobj->anim_duration = (UINT16)mobj->state->var2; } if (diff & MD_EFLAGS) - mobj->eflags = READUINT16(save_p); + mobj->eflags = READUINT16(save->p); if (diff & MD_PLAYER) { - i = READUINT8(save_p); + i = READUINT8(save->p); mobj->player = &players[i]; mobj->player->mo = mobj; } if (diff & MD_MOVEDIR) - mobj->movedir = READANGLE(save_p); + mobj->movedir = READANGLE(save->p); if (diff & MD_MOVECOUNT) - mobj->movecount = READINT32(save_p); + mobj->movecount = READINT32(save->p); if (diff & MD_THRESHOLD) - mobj->threshold = READINT32(save_p); + mobj->threshold = READINT32(save->p); if (diff & MD_LASTLOOK) - mobj->lastlook = READINT32(save_p); + mobj->lastlook = READINT32(save->p); else mobj->lastlook = -1; if (diff & MD_TARGET) - mobj->target = (mobj_t *)(size_t)READUINT32(save_p); + mobj->target = (mobj_t *)(size_t)READUINT32(save->p); if (diff & MD_TRACER) - mobj->tracer = (mobj_t *)(size_t)READUINT32(save_p); + mobj->tracer = (mobj_t *)(size_t)READUINT32(save->p); if (diff & MD_FRICTION) - mobj->friction = READFIXED(save_p); + mobj->friction = READFIXED(save->p); else mobj->friction = ORIG_FRICTION; if (diff & MD_MOVEFACTOR) - mobj->movefactor = READFIXED(save_p); + mobj->movefactor = READFIXED(save->p); else mobj->movefactor = FRACUNIT; if (diff & MD_FUSE) - mobj->fuse = READINT32(save_p); + mobj->fuse = READINT32(save->p); if (diff & MD_WATERTOP) - mobj->watertop = READFIXED(save_p); + mobj->watertop = READFIXED(save->p); if (diff & MD_WATERBOTTOM) - mobj->waterbottom = READFIXED(save_p); + mobj->waterbottom = READFIXED(save->p); if (diff & MD_SCALE) - mobj->scale = READFIXED(save_p); + mobj->scale = READFIXED(save->p); else mobj->scale = FRACUNIT; if (diff & MD_DSCALE) - mobj->destscale = READFIXED(save_p); + mobj->destscale = READFIXED(save->p); else mobj->destscale = mobj->scale; if (diff2 & MD2_SCALESPEED) - mobj->scalespeed = READFIXED(save_p); + mobj->scalespeed = READFIXED(save->p); else mobj->scalespeed = mapobjectscale/12; if (diff2 & MD2_CUSVAL) - mobj->cusval = READINT32(save_p); + mobj->cusval = READINT32(save->p); if (diff2 & MD2_CVMEM) - mobj->cvmem = READINT32(save_p); + mobj->cvmem = READINT32(save->p); if (diff2 & MD2_SKIN) - mobj->skin = &skins[READUINT8(save_p)]; + mobj->skin = &skins[READUINT8(save->p)]; if (diff2 & MD2_COLOR) - mobj->color = READUINT16(save_p); + mobj->color = READUINT16(save->p); if (diff2 & MD2_EXTVAL1) - mobj->extravalue1 = READINT32(save_p); + mobj->extravalue1 = READINT32(save->p); if (diff2 & MD2_EXTVAL2) - mobj->extravalue2 = READINT32(save_p); + mobj->extravalue2 = READINT32(save->p); if (diff2 & MD2_HNEXT) - mobj->hnext = (mobj_t *)(size_t)READUINT32(save_p); + mobj->hnext = (mobj_t *)(size_t)READUINT32(save->p); if (diff2 & MD2_HPREV) - mobj->hprev = (mobj_t *)(size_t)READUINT32(save_p); + mobj->hprev = (mobj_t *)(size_t)READUINT32(save->p); if (diff2 & MD2_ITNEXT) - mobj->itnext = (mobj_t *)(size_t)READUINT32(save_p); + mobj->itnext = (mobj_t *)(size_t)READUINT32(save->p); if (diff2 & MD2_SLOPE) - mobj->standingslope = P_SlopeById(READUINT16(save_p)); + mobj->standingslope = P_SlopeById(READUINT16(save->p)); if (diff2 & MD2_COLORIZED) - mobj->colorized = READUINT8(save_p); + mobj->colorized = READUINT8(save->p); if (diff2 & MD2_MIRRORED) - mobj->mirrored = READUINT8(save_p); + mobj->mirrored = READUINT8(save->p); if (diff2 & MD2_ROLLANGLE) - mobj->rollangle = READANGLE(save_p); + mobj->rollangle = READANGLE(save->p); if (diff2 & MD2_SHADOWSCALE) { - mobj->shadowscale = READFIXED(save_p); - mobj->whiteshadow = READUINT8(save_p); + mobj->shadowscale = READFIXED(save->p); + mobj->whiteshadow = READUINT8(save->p); } if (diff2 & MD2_RENDERFLAGS) - mobj->renderflags = READUINT32(save_p); + mobj->renderflags = READUINT32(save->p); if (diff2 & MD2_SPRITEXSCALE) - mobj->spritexscale = READFIXED(save_p); + mobj->spritexscale = READFIXED(save->p); else mobj->spritexscale = FRACUNIT; if (diff2 & MD2_SPRITEYSCALE) - mobj->spriteyscale = READFIXED(save_p); + mobj->spriteyscale = READFIXED(save->p); else mobj->spriteyscale = FRACUNIT; if (diff2 & MD2_SPRITEXOFFSET) - mobj->spritexoffset = READFIXED(save_p); + mobj->spritexoffset = READFIXED(save->p); if (diff2 & MD2_SPRITEYOFFSET) - mobj->spriteyoffset = READFIXED(save_p); + mobj->spriteyoffset = READFIXED(save->p); if (diff2 & MD2_FLOORSPRITESLOPE) { pslope_t *slope = (pslope_t *)P_CreateFloorSpriteSlope(mobj); - slope->zdelta = READFIXED(save_p); - slope->zangle = READANGLE(save_p); - slope->xydirection = READANGLE(save_p); + slope->zdelta = READFIXED(save->p); + slope->zangle = READANGLE(save->p); + slope->xydirection = READANGLE(save->p); - slope->o.x = READFIXED(save_p); - slope->o.y = READFIXED(save_p); - slope->o.z = READFIXED(save_p); + slope->o.x = READFIXED(save->p); + slope->o.y = READFIXED(save->p); + slope->o.z = READFIXED(save->p); - slope->d.x = READFIXED(save_p); - slope->d.y = READFIXED(save_p); + slope->d.x = READFIXED(save->p); + slope->d.y = READFIXED(save->p); - slope->normal.x = READFIXED(save_p); - slope->normal.y = READFIXED(save_p); - slope->normal.z = READFIXED(save_p); + slope->normal.x = READFIXED(save->p); + slope->normal.y = READFIXED(save->p); + slope->normal.z = READFIXED(save->p); P_UpdateSlopeLightOffset(slope); } if (diff2 & MD2_HITLAG) { - mobj->hitlag = READINT32(save_p); + mobj->hitlag = READINT32(save->p); } if (diff2 & MD2_WATERSKIP) { - mobj->waterskip = READUINT8(save_p); + mobj->waterskip = READUINT8(save->p); } if (diff2 & MD2_DISPOFFSET) { - mobj->dispoffset = READINT32(save_p); + mobj->dispoffset = READINT32(save->p); } if (diff2 & MD2_LASTMOMZ) { - mobj->lastmomz = READINT32(save_p); + mobj->lastmomz = READINT32(save->p); } if (diff2 & MD2_TERRAIN) { - mobj->terrain = (terrain_t *)(size_t)READUINT32(save_p); - mobj->terrainOverlay = (mobj_t *)(size_t)READUINT32(save_p); + mobj->terrain = (terrain_t *)(size_t)READUINT32(save->p); + mobj->terrainOverlay = (mobj_t *)(size_t)READUINT32(save->p); } else { @@ -3450,7 +3449,7 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) // set sprev, snext, bprev, bnext, subsector P_SetThingPosition(mobj); - mobj->mobjnum = READUINT32(save_p); + mobj->mobjnum = READUINT32(save->p); if (mobj->player) { @@ -3486,25 +3485,25 @@ static thinker_t* LoadMobjThinker(actionf_p1 thinker) return &mobj->thinker; } -static thinker_t* LoadNoEnemiesThinker(actionf_p1 thinker) +static thinker_t* LoadNoEnemiesThinker(savebuffer_t *save, actionf_p1 thinker) { noenemies_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sourceline = LoadLine(READUINT32(save_p)); + ht->sourceline = LoadLine(READUINT32(save->p)); return &ht->thinker; } -static thinker_t* LoadBounceCheeseThinker(actionf_p1 thinker) +static thinker_t* LoadBounceCheeseThinker(savebuffer_t *save, actionf_p1 thinker) { bouncecheese_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sourceline = LoadLine(READUINT32(save_p)); - ht->sector = LoadSector(READUINT32(save_p)); - ht->speed = READFIXED(save_p); - ht->distance = READFIXED(save_p); - ht->floorwasheight = READFIXED(save_p); - ht->ceilingwasheight = READFIXED(save_p); - ht->low = READCHAR(save_p); + ht->sourceline = LoadLine(READUINT32(save->p)); + ht->sector = LoadSector(READUINT32(save->p)); + ht->speed = READFIXED(save->p); + ht->distance = READFIXED(save->p); + ht->floorwasheight = READFIXED(save->p); + ht->ceilingwasheight = READFIXED(save->p); + ht->low = READCHAR(save->p); if (ht->sector) ht->sector->ceilingdata = ht; @@ -3512,16 +3511,16 @@ static thinker_t* LoadBounceCheeseThinker(actionf_p1 thinker) return &ht->thinker; } -static thinker_t* LoadContinuousFallThinker(actionf_p1 thinker) +static thinker_t* LoadContinuousFallThinker(savebuffer_t *save, actionf_p1 thinker) { continuousfall_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sector = LoadSector(READUINT32(save_p)); - ht->speed = READFIXED(save_p); - ht->direction = READINT32(save_p); - ht->floorstartheight = READFIXED(save_p); - ht->ceilingstartheight = READFIXED(save_p); - ht->destheight = READFIXED(save_p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->speed = READFIXED(save->p); + ht->direction = READINT32(save->p); + ht->floorstartheight = READFIXED(save->p); + ht->ceilingstartheight = READFIXED(save->p); + ht->destheight = READFIXED(save->p); if (ht->sector) { @@ -3532,16 +3531,16 @@ static thinker_t* LoadContinuousFallThinker(actionf_p1 thinker) return &ht->thinker; } -static thinker_t* LoadMarioBlockThinker(actionf_p1 thinker) +static thinker_t* LoadMarioBlockThinker(savebuffer_t *save, actionf_p1 thinker) { mariothink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sector = LoadSector(READUINT32(save_p)); - ht->speed = READFIXED(save_p); - ht->direction = READINT32(save_p); - ht->floorstartheight = READFIXED(save_p); - ht->ceilingstartheight = READFIXED(save_p); - ht->tag = READINT16(save_p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->speed = READFIXED(save->p); + ht->direction = READINT32(save->p); + ht->floorstartheight = READFIXED(save->p); + ht->ceilingstartheight = READFIXED(save->p); + ht->tag = READINT16(save->p); if (ht->sector) { @@ -3552,29 +3551,29 @@ static thinker_t* LoadMarioBlockThinker(actionf_p1 thinker) return &ht->thinker; } -static thinker_t* LoadMarioCheckThinker(actionf_p1 thinker) +static thinker_t* LoadMarioCheckThinker(savebuffer_t *save, actionf_p1 thinker) { mariocheck_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sourceline = LoadLine(READUINT32(save_p)); - ht->sector = LoadSector(READUINT32(save_p)); + ht->sourceline = LoadLine(READUINT32(save->p)); + ht->sector = LoadSector(READUINT32(save->p)); return &ht->thinker; } -static thinker_t* LoadThwompThinker(actionf_p1 thinker) +static thinker_t* LoadThwompThinker(savebuffer_t *save, actionf_p1 thinker) { thwomp_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sourceline = LoadLine(READUINT32(save_p)); - ht->sector = LoadSector(READUINT32(save_p)); - ht->crushspeed = READFIXED(save_p); - ht->retractspeed = READFIXED(save_p); - ht->direction = READINT32(save_p); - ht->floorstartheight = READFIXED(save_p); - ht->ceilingstartheight = READFIXED(save_p); - ht->delay = READINT32(save_p); - ht->tag = READINT16(save_p); - ht->sound = READUINT16(save_p); + ht->sourceline = LoadLine(READUINT32(save->p)); + ht->sector = LoadSector(READUINT32(save->p)); + ht->crushspeed = READFIXED(save->p); + ht->retractspeed = READFIXED(save->p); + ht->direction = READINT32(save->p); + ht->floorstartheight = READFIXED(save->p); + ht->ceilingstartheight = READFIXED(save->p); + ht->delay = READINT32(save->p); + ht->tag = READINT16(save->p); + ht->sound = READUINT16(save->p); if (ht->sector) { @@ -3585,163 +3584,163 @@ static thinker_t* LoadThwompThinker(actionf_p1 thinker) return &ht->thinker; } -static thinker_t* LoadFloatThinker(actionf_p1 thinker) +static thinker_t* LoadFloatThinker(savebuffer_t *save, actionf_p1 thinker) { floatthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sourceline = LoadLine(READUINT32(save_p)); - ht->sector = LoadSector(READUINT32(save_p)); - ht->tag = READINT16(save_p); + ht->sourceline = LoadLine(READUINT32(save->p)); + ht->sector = LoadSector(READUINT32(save->p)); + ht->tag = READINT16(save->p); return &ht->thinker; } -static thinker_t* LoadEachTimeThinker(actionf_p1 thinker) +static thinker_t* LoadEachTimeThinker(savebuffer_t *save, actionf_p1 thinker) { size_t i; eachtime_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sourceline = LoadLine(READUINT32(save_p)); + ht->sourceline = LoadLine(READUINT32(save->p)); for (i = 0; i < MAXPLAYERS; i++) { - ht->playersInArea[i] = READCHAR(save_p); + ht->playersInArea[i] = READCHAR(save->p); } - ht->triggerOnExit = READCHAR(save_p); + ht->triggerOnExit = READCHAR(save->p); return &ht->thinker; } -static thinker_t* LoadRaiseThinker(actionf_p1 thinker) +static thinker_t* LoadRaiseThinker(savebuffer_t *save, actionf_p1 thinker) { raise_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->tag = READINT16(save_p); - ht->sector = LoadSector(READUINT32(save_p)); - ht->ceilingbottom = READFIXED(save_p); - ht->ceilingtop = READFIXED(save_p); - ht->basespeed = READFIXED(save_p); - ht->extraspeed = READFIXED(save_p); - ht->shaketimer = READUINT8(save_p); - ht->flags = READUINT8(save_p); + ht->tag = READINT16(save->p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->ceilingbottom = READFIXED(save->p); + ht->ceilingtop = READFIXED(save->p); + ht->basespeed = READFIXED(save->p); + ht->extraspeed = READFIXED(save->p); + ht->shaketimer = READUINT8(save->p); + ht->flags = READUINT8(save->p); return &ht->thinker; } -static thinker_t* LoadCeilingThinker(actionf_p1 thinker) +static thinker_t* LoadCeilingThinker(savebuffer_t *save, actionf_p1 thinker) { ceiling_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->type = READUINT8(save_p); - ht->sector = LoadSector(READUINT32(save_p)); - ht->bottomheight = READFIXED(save_p); - ht->topheight = READFIXED(save_p); - ht->speed = READFIXED(save_p); - ht->delay = READFIXED(save_p); - ht->delaytimer = READFIXED(save_p); - ht->crush = READUINT8(save_p); - ht->texture = READINT32(save_p); - ht->direction = READINT32(save_p); - ht->tag = READINT16(save_p); - ht->origspeed = READFIXED(save_p); - ht->sourceline = READFIXED(save_p); + ht->type = READUINT8(save->p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->bottomheight = READFIXED(save->p); + ht->topheight = READFIXED(save->p); + ht->speed = READFIXED(save->p); + ht->delay = READFIXED(save->p); + ht->delaytimer = READFIXED(save->p); + ht->crush = READUINT8(save->p); + ht->texture = READINT32(save->p); + ht->direction = READINT32(save->p); + ht->tag = READINT16(save->p); + ht->origspeed = READFIXED(save->p); + ht->sourceline = READFIXED(save->p); if (ht->sector) ht->sector->ceilingdata = ht; return &ht->thinker; } -static thinker_t* LoadFloormoveThinker(actionf_p1 thinker) +static thinker_t* LoadFloormoveThinker(savebuffer_t *save, actionf_p1 thinker) { floormove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->type = READUINT8(save_p); - ht->crush = READUINT8(save_p); - ht->sector = LoadSector(READUINT32(save_p)); - ht->direction = READINT32(save_p); - ht->texture = READINT32(save_p); - ht->floordestheight = READFIXED(save_p); - ht->speed = READFIXED(save_p); - ht->origspeed = READFIXED(save_p); - ht->delay = READFIXED(save_p); - ht->delaytimer = READFIXED(save_p); - ht->tag = READINT16(save_p); - ht->sourceline = READFIXED(save_p); + ht->type = READUINT8(save->p); + ht->crush = READUINT8(save->p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->direction = READINT32(save->p); + ht->texture = READINT32(save->p); + ht->floordestheight = READFIXED(save->p); + ht->speed = READFIXED(save->p); + ht->origspeed = READFIXED(save->p); + ht->delay = READFIXED(save->p); + ht->delaytimer = READFIXED(save->p); + ht->tag = READINT16(save->p); + ht->sourceline = READFIXED(save->p); if (ht->sector) ht->sector->floordata = ht; return &ht->thinker; } -static thinker_t* LoadLightflashThinker(actionf_p1 thinker) +static thinker_t* LoadLightflashThinker(savebuffer_t *save, actionf_p1 thinker) { lightflash_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sector = LoadSector(READUINT32(save_p)); - ht->maxlight = READINT32(save_p); - ht->minlight = READINT32(save_p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->maxlight = READINT32(save->p); + ht->minlight = READINT32(save->p); if (ht->sector) ht->sector->lightingdata = ht; return &ht->thinker; } -static thinker_t* LoadStrobeThinker(actionf_p1 thinker) +static thinker_t* LoadStrobeThinker(savebuffer_t *save, actionf_p1 thinker) { strobe_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sector = LoadSector(READUINT32(save_p)); - ht->count = READINT32(save_p); - ht->minlight = READINT16(save_p); - ht->maxlight = READINT16(save_p); - ht->darktime = READINT32(save_p); - ht->brighttime = READINT32(save_p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->count = READINT32(save->p); + ht->minlight = READINT16(save->p); + ht->maxlight = READINT16(save->p); + ht->darktime = READINT32(save->p); + ht->brighttime = READINT32(save->p); if (ht->sector) ht->sector->lightingdata = ht; return &ht->thinker; } -static thinker_t* LoadGlowThinker(actionf_p1 thinker) +static thinker_t* LoadGlowThinker(savebuffer_t *save, actionf_p1 thinker) { glow_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sector = LoadSector(READUINT32(save_p)); - ht->minlight = READINT16(save_p); - ht->maxlight = READINT16(save_p); - ht->direction = READINT16(save_p); - ht->speed = READINT16(save_p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->minlight = READINT16(save->p); + ht->maxlight = READINT16(save->p); + ht->direction = READINT16(save->p); + ht->speed = READINT16(save->p); if (ht->sector) ht->sector->lightingdata = ht; return &ht->thinker; } -static thinker_t* LoadFireflickerThinker(actionf_p1 thinker) +static thinker_t* LoadFireflickerThinker(savebuffer_t *save, actionf_p1 thinker) { fireflicker_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sector = LoadSector(READUINT32(save_p)); - ht->count = READINT32(save_p); - ht->resetcount = READINT32(save_p); - ht->maxlight = READINT16(save_p); - ht->minlight = READINT16(save_p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->count = READINT32(save->p); + ht->resetcount = READINT32(save->p); + ht->maxlight = READINT16(save->p); + ht->minlight = READINT16(save->p); if (ht->sector) ht->sector->lightingdata = ht; return &ht->thinker; } -static thinker_t* LoadElevatorThinker(actionf_p1 thinker, boolean setplanedata) +static thinker_t* LoadElevatorThinker(savebuffer_t *save, actionf_p1 thinker, boolean setplanedata) { elevator_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->type = READUINT8(save_p); - ht->sector = LoadSector(READUINT32(save_p)); - ht->actionsector = LoadSector(READUINT32(save_p)); - ht->direction = READINT32(save_p); - ht->floordestheight = READFIXED(save_p); - ht->ceilingdestheight = READFIXED(save_p); - ht->speed = READFIXED(save_p); - ht->origspeed = READFIXED(save_p); - ht->low = READFIXED(save_p); - ht->high = READFIXED(save_p); - ht->distance = READFIXED(save_p); - ht->delay = READFIXED(save_p); - ht->delaytimer = READFIXED(save_p); - ht->floorwasheight = READFIXED(save_p); - ht->ceilingwasheight = READFIXED(save_p); - ht->sourceline = LoadLine(READUINT32(save_p)); + ht->type = READUINT8(save->p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->actionsector = LoadSector(READUINT32(save->p)); + ht->direction = READINT32(save->p); + ht->floordestheight = READFIXED(save->p); + ht->ceilingdestheight = READFIXED(save->p); + ht->speed = READFIXED(save->p); + ht->origspeed = READFIXED(save->p); + ht->low = READFIXED(save->p); + ht->high = READFIXED(save->p); + ht->distance = READFIXED(save->p); + ht->delay = READFIXED(save->p); + ht->delaytimer = READFIXED(save->p); + ht->floorwasheight = READFIXED(save->p); + ht->ceilingwasheight = READFIXED(save->p); + ht->sourceline = LoadLine(READUINT32(save->p)); if (ht->sector && setplanedata) { @@ -3752,21 +3751,21 @@ static thinker_t* LoadElevatorThinker(actionf_p1 thinker, boolean setplanedata) return &ht->thinker; } -static thinker_t* LoadCrumbleThinker(actionf_p1 thinker) +static thinker_t* LoadCrumbleThinker(savebuffer_t *save, actionf_p1 thinker) { crumble_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sourceline = LoadLine(READUINT32(save_p)); - ht->sector = LoadSector(READUINT32(save_p)); - ht->actionsector = LoadSector(READUINT32(save_p)); - ht->player = LoadPlayer(READUINT32(save_p)); - ht->direction = READINT32(save_p); - ht->origalpha = READINT32(save_p); - ht->timer = READINT32(save_p); - ht->speed = READFIXED(save_p); - ht->floorwasheight = READFIXED(save_p); - ht->ceilingwasheight = READFIXED(save_p); - ht->flags = READUINT8(save_p); + ht->sourceline = LoadLine(READUINT32(save->p)); + ht->sector = LoadSector(READUINT32(save->p)); + ht->actionsector = LoadSector(READUINT32(save->p)); + ht->player = LoadPlayer(READUINT32(save->p)); + ht->direction = READINT32(save->p); + ht->origalpha = READINT32(save->p); + ht->timer = READINT32(save->p); + ht->speed = READFIXED(save->p); + ht->floorwasheight = READFIXED(save->p); + ht->ceilingwasheight = READFIXED(save->p); + ht->flags = READUINT8(save->p); if (ht->sector) ht->sector->floordata = ht; @@ -3774,123 +3773,123 @@ static thinker_t* LoadCrumbleThinker(actionf_p1 thinker) return &ht->thinker; } -static thinker_t* LoadScrollThinker(actionf_p1 thinker) +static thinker_t* LoadScrollThinker(savebuffer_t *save, actionf_p1 thinker) { scroll_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->dx = READFIXED(save_p); - ht->dy = READFIXED(save_p); - ht->affectee = READINT32(save_p); - ht->control = READINT32(save_p); - ht->last_height = READFIXED(save_p); - ht->vdx = READFIXED(save_p); - ht->vdy = READFIXED(save_p); - ht->accel = READINT32(save_p); - ht->exclusive = READINT32(save_p); - ht->type = READUINT8(save_p); + ht->dx = READFIXED(save->p); + ht->dy = READFIXED(save->p); + ht->affectee = READINT32(save->p); + ht->control = READINT32(save->p); + ht->last_height = READFIXED(save->p); + ht->vdx = READFIXED(save->p); + ht->vdy = READFIXED(save->p); + ht->accel = READINT32(save->p); + ht->exclusive = READINT32(save->p); + ht->type = READUINT8(save->p); return &ht->thinker; } -static inline thinker_t* LoadFrictionThinker(actionf_p1 thinker) +static inline thinker_t* LoadFrictionThinker(savebuffer_t *save, actionf_p1 thinker) { friction_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->friction = READINT32(save_p); - ht->movefactor = READINT32(save_p); - ht->affectee = READINT32(save_p); - ht->referrer = READINT32(save_p); - ht->roverfriction = READUINT8(save_p); + ht->friction = READINT32(save->p); + ht->movefactor = READINT32(save->p); + ht->affectee = READINT32(save->p); + ht->referrer = READINT32(save->p); + ht->roverfriction = READUINT8(save->p); return &ht->thinker; } -static thinker_t* LoadPusherThinker(actionf_p1 thinker) +static thinker_t* LoadPusherThinker(savebuffer_t *save, actionf_p1 thinker) { pusher_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->type = READUINT8(save_p); - ht->x_mag = READFIXED(save_p); - ht->y_mag = READFIXED(save_p); - ht->z_mag = READFIXED(save_p); - ht->affectee = READINT32(save_p); - ht->roverpusher = READUINT8(save_p); - ht->referrer = READINT32(save_p); - ht->exclusive = READINT32(save_p); - ht->slider = READINT32(save_p); + ht->type = READUINT8(save->p); + ht->x_mag = READFIXED(save->p); + ht->y_mag = READFIXED(save->p); + ht->z_mag = READFIXED(save->p); + ht->affectee = READINT32(save->p); + ht->roverpusher = READUINT8(save->p); + ht->referrer = READINT32(save->p); + ht->exclusive = READINT32(save->p); + ht->slider = READINT32(save->p); return &ht->thinker; } -static inline thinker_t* LoadLaserThinker(actionf_p1 thinker) +static inline thinker_t* LoadLaserThinker(savebuffer_t *save, actionf_p1 thinker) { laserthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->tag = READINT16(save_p); - ht->sourceline = LoadLine(READUINT32(save_p)); - ht->nobosses = READUINT8(save_p); + ht->tag = READINT16(save->p); + ht->sourceline = LoadLine(READUINT32(save->p)); + ht->nobosses = READUINT8(save->p); return &ht->thinker; } -static inline thinker_t* LoadLightlevelThinker(actionf_p1 thinker) +static inline thinker_t* LoadLightlevelThinker(savebuffer_t *save, actionf_p1 thinker) { lightlevel_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sector = LoadSector(READUINT32(save_p)); - ht->sourcelevel = READINT16(save_p); - ht->destlevel = READINT16(save_p); - ht->fixedcurlevel = READFIXED(save_p); - ht->fixedpertic = READFIXED(save_p); - ht->timer = READINT32(save_p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->sourcelevel = READINT16(save->p); + ht->destlevel = READINT16(save->p); + ht->fixedcurlevel = READFIXED(save->p); + ht->fixedpertic = READFIXED(save->p); + ht->timer = READINT32(save->p); if (ht->sector) ht->sector->lightingdata = ht; return &ht->thinker; } -static inline thinker_t* LoadExecutorThinker(actionf_p1 thinker) +static inline thinker_t* LoadExecutorThinker(savebuffer_t *save, actionf_p1 thinker) { executor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->line = LoadLine(READUINT32(save_p)); - ht->caller = LoadMobj(READUINT32(save_p)); - ht->sector = LoadSector(READUINT32(save_p)); - ht->timer = READINT32(save_p); + ht->line = LoadLine(READUINT32(save->p)); + ht->caller = LoadMobj(READUINT32(save->p)); + ht->sector = LoadSector(READUINT32(save->p)); + ht->timer = READINT32(save->p); return &ht->thinker; } -static inline thinker_t* LoadDisappearThinker(actionf_p1 thinker) +static inline thinker_t* LoadDisappearThinker(savebuffer_t *save, actionf_p1 thinker) { disappear_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->appeartime = READUINT32(save_p); - ht->disappeartime = READUINT32(save_p); - ht->offset = READUINT32(save_p); - ht->timer = READUINT32(save_p); - ht->affectee = READINT32(save_p); - ht->sourceline = READINT32(save_p); - ht->exists = READINT32(save_p); + ht->appeartime = READUINT32(save->p); + ht->disappeartime = READUINT32(save->p); + ht->offset = READUINT32(save->p); + ht->timer = READUINT32(save->p); + ht->affectee = READINT32(save->p); + ht->sourceline = READINT32(save->p); + ht->exists = READINT32(save->p); return &ht->thinker; } -static inline thinker_t* LoadFadeThinker(actionf_p1 thinker) +static inline thinker_t* LoadFadeThinker(savebuffer_t *save, actionf_p1 thinker) { sector_t *ss; fade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->dest_exc = GetNetColormapFromList(READUINT32(save_p)); - ht->sectornum = READUINT32(save_p); - ht->ffloornum = READUINT32(save_p); - ht->alpha = READINT32(save_p); - ht->sourcevalue = READINT16(save_p); - ht->destvalue = READINT16(save_p); - ht->destlightlevel = READINT16(save_p); - ht->speed = READINT16(save_p); - ht->ticbased = (boolean)READUINT8(save_p); - ht->timer = READINT32(save_p); - ht->doexists = READUINT8(save_p); - ht->dotranslucent = READUINT8(save_p); - ht->dolighting = READUINT8(save_p); - ht->docolormap = READUINT8(save_p); - ht->docollision = READUINT8(save_p); - ht->doghostfade = READUINT8(save_p); - ht->exactalpha = READUINT8(save_p); + ht->dest_exc = GetNetColormapFromList(READUINT32(save->p)); + ht->sectornum = READUINT32(save->p); + ht->ffloornum = READUINT32(save->p); + ht->alpha = READINT32(save->p); + ht->sourcevalue = READINT16(save->p); + ht->destvalue = READINT16(save->p); + ht->destlightlevel = READINT16(save->p); + ht->speed = READINT16(save->p); + ht->ticbased = (boolean)READUINT8(save->p); + ht->timer = READINT32(save->p); + ht->doexists = READUINT8(save->p); + ht->dotranslucent = READUINT8(save->p); + ht->dolighting = READUINT8(save->p); + ht->docolormap = READUINT8(save->p); + ht->docollision = READUINT8(save->p); + ht->doghostfade = READUINT8(save->p); + ht->exactalpha = READUINT8(save->p); ss = LoadSector(ht->sectornum); if (ss) @@ -3911,176 +3910,176 @@ static inline thinker_t* LoadFadeThinker(actionf_p1 thinker) return &ht->thinker; } -static inline thinker_t* LoadFadeColormapThinker(actionf_p1 thinker) +static inline thinker_t* LoadFadeColormapThinker(savebuffer_t *save, actionf_p1 thinker) { fadecolormap_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->sector = LoadSector(READUINT32(save_p)); - ht->source_exc = GetNetColormapFromList(READUINT32(save_p)); - ht->dest_exc = GetNetColormapFromList(READUINT32(save_p)); - ht->ticbased = (boolean)READUINT8(save_p); - ht->duration = READINT32(save_p); - ht->timer = READINT32(save_p); + ht->sector = LoadSector(READUINT32(save->p)); + ht->source_exc = GetNetColormapFromList(READUINT32(save->p)); + ht->dest_exc = GetNetColormapFromList(READUINT32(save->p)); + ht->ticbased = (boolean)READUINT8(save->p); + ht->duration = READINT32(save->p); + ht->timer = READINT32(save->p); if (ht->sector) ht->sector->fadecolormapdata = ht; return &ht->thinker; } -static inline thinker_t* LoadPlaneDisplaceThinker(actionf_p1 thinker) +static inline thinker_t* LoadPlaneDisplaceThinker(savebuffer_t *save, actionf_p1 thinker) { planedisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->affectee = READINT32(save_p); - ht->control = READINT32(save_p); - ht->last_height = READFIXED(save_p); - ht->speed = READFIXED(save_p); - ht->type = READUINT8(save_p); + ht->affectee = READINT32(save->p); + ht->control = READINT32(save->p); + ht->last_height = READFIXED(save->p); + ht->speed = READFIXED(save->p); + ht->type = READUINT8(save->p); return &ht->thinker; } -static inline thinker_t* LoadDynamicLineSlopeThinker(actionf_p1 thinker) +static inline thinker_t* LoadDynamicLineSlopeThinker(savebuffer_t *save, actionf_p1 thinker) { dynlineplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->type = READUINT8(save_p); - ht->slope = LoadSlope(READUINT32(save_p)); - ht->sourceline = LoadLine(READUINT32(save_p)); - ht->extent = READFIXED(save_p); + ht->type = READUINT8(save->p); + ht->slope = LoadSlope(READUINT32(save->p)); + ht->sourceline = LoadLine(READUINT32(save->p)); + ht->extent = READFIXED(save->p); return &ht->thinker; } -static inline thinker_t* LoadDynamicVertexSlopeThinker(actionf_p1 thinker) +static inline thinker_t* LoadDynamicVertexSlopeThinker(savebuffer_t *save, actionf_p1 thinker) { size_t i; dynvertexplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->slope = LoadSlope(READUINT32(save_p)); + ht->slope = LoadSlope(READUINT32(save->p)); for (i = 0; i < 3; i++) - ht->secs[i] = LoadSector(READUINT32(save_p)); - READMEM(save_p, ht->vex, sizeof(ht->vex)); - READMEM(save_p, ht->origsecheights, sizeof(ht->origsecheights)); - READMEM(save_p, ht->origvecheights, sizeof(ht->origvecheights)); - ht->relative = READUINT8(save_p); + ht->secs[i] = LoadSector(READUINT32(save->p)); + READMEM(save->p, ht->vex, sizeof(ht->vex)); + READMEM(save->p, ht->origsecheights, sizeof(ht->origsecheights)); + READMEM(save->p, ht->origvecheights, sizeof(ht->origvecheights)); + ht->relative = READUINT8(save->p); return &ht->thinker; } -static inline thinker_t* LoadPolyrotatetThinker(actionf_p1 thinker) +static inline thinker_t* LoadPolyrotatetThinker(savebuffer_t *save, actionf_p1 thinker) { polyrotate_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->polyObjNum = READINT32(save_p); - ht->speed = READINT32(save_p); - ht->distance = READINT32(save_p); - ht->turnobjs = READUINT8(save_p); + ht->polyObjNum = READINT32(save->p); + ht->speed = READINT32(save->p); + ht->distance = READINT32(save->p); + ht->turnobjs = READUINT8(save->p); return &ht->thinker; } -static thinker_t* LoadPolymoveThinker(actionf_p1 thinker) +static thinker_t* LoadPolymoveThinker(savebuffer_t *save, actionf_p1 thinker) { polymove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->polyObjNum = READINT32(save_p); - ht->speed = READINT32(save_p); - ht->momx = READFIXED(save_p); - ht->momy = READFIXED(save_p); - ht->distance = READINT32(save_p); - ht->angle = READANGLE(save_p); + ht->polyObjNum = READINT32(save->p); + ht->speed = READINT32(save->p); + ht->momx = READFIXED(save->p); + ht->momy = READFIXED(save->p); + ht->distance = READINT32(save->p); + ht->angle = READANGLE(save->p); return &ht->thinker; } -static inline thinker_t* LoadPolywaypointThinker(actionf_p1 thinker) +static inline thinker_t* LoadPolywaypointThinker(savebuffer_t *save, actionf_p1 thinker) { polywaypoint_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->polyObjNum = READINT32(save_p); - ht->speed = READINT32(save_p); - ht->sequence = READINT32(save_p); - ht->pointnum = READINT32(save_p); - ht->direction = READINT32(save_p); - ht->returnbehavior = READUINT8(save_p); - ht->continuous = READUINT8(save_p); - ht->stophere = READUINT8(save_p); + ht->polyObjNum = READINT32(save->p); + ht->speed = READINT32(save->p); + ht->sequence = READINT32(save->p); + ht->pointnum = READINT32(save->p); + ht->direction = READINT32(save->p); + ht->returnbehavior = READUINT8(save->p); + ht->continuous = READUINT8(save->p); + ht->stophere = READUINT8(save->p); return &ht->thinker; } -static inline thinker_t* LoadPolyslidedoorThinker(actionf_p1 thinker) +static inline thinker_t* LoadPolyslidedoorThinker(savebuffer_t *save, actionf_p1 thinker) { polyslidedoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->polyObjNum = READINT32(save_p); - ht->delay = READINT32(save_p); - ht->delayCount = READINT32(save_p); - ht->initSpeed = READINT32(save_p); - ht->speed = READINT32(save_p); - ht->initDistance = READINT32(save_p); - ht->distance = READINT32(save_p); - ht->initAngle = READUINT32(save_p); - ht->angle = READUINT32(save_p); - ht->revAngle = READUINT32(save_p); - ht->momx = READFIXED(save_p); - ht->momy = READFIXED(save_p); - ht->closing = READUINT8(save_p); + ht->polyObjNum = READINT32(save->p); + ht->delay = READINT32(save->p); + ht->delayCount = READINT32(save->p); + ht->initSpeed = READINT32(save->p); + ht->speed = READINT32(save->p); + ht->initDistance = READINT32(save->p); + ht->distance = READINT32(save->p); + ht->initAngle = READUINT32(save->p); + ht->angle = READUINT32(save->p); + ht->revAngle = READUINT32(save->p); + ht->momx = READFIXED(save->p); + ht->momy = READFIXED(save->p); + ht->closing = READUINT8(save->p); return &ht->thinker; } -static inline thinker_t* LoadPolyswingdoorThinker(actionf_p1 thinker) +static inline thinker_t* LoadPolyswingdoorThinker(savebuffer_t *save, actionf_p1 thinker) { polyswingdoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->polyObjNum = READINT32(save_p); - ht->delay = READINT32(save_p); - ht->delayCount = READINT32(save_p); - ht->initSpeed = READINT32(save_p); - ht->speed = READINT32(save_p); - ht->initDistance = READINT32(save_p); - ht->distance = READINT32(save_p); - ht->closing = READUINT8(save_p); + ht->polyObjNum = READINT32(save->p); + ht->delay = READINT32(save->p); + ht->delayCount = READINT32(save->p); + ht->initSpeed = READINT32(save->p); + ht->speed = READINT32(save->p); + ht->initDistance = READINT32(save->p); + ht->distance = READINT32(save->p); + ht->closing = READUINT8(save->p); return &ht->thinker; } -static inline thinker_t* LoadPolydisplaceThinker(actionf_p1 thinker) +static inline thinker_t* LoadPolydisplaceThinker(savebuffer_t *save, actionf_p1 thinker) { polydisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->polyObjNum = READINT32(save_p); - ht->controlSector = LoadSector(READUINT32(save_p)); - ht->dx = READFIXED(save_p); - ht->dy = READFIXED(save_p); - ht->oldHeights = READFIXED(save_p); + ht->polyObjNum = READINT32(save->p); + ht->controlSector = LoadSector(READUINT32(save->p)); + ht->dx = READFIXED(save->p); + ht->dy = READFIXED(save->p); + ht->oldHeights = READFIXED(save->p); return &ht->thinker; } -static inline thinker_t* LoadPolyrotdisplaceThinker(actionf_p1 thinker) +static inline thinker_t* LoadPolyrotdisplaceThinker(savebuffer_t *save, actionf_p1 thinker) { polyrotdisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->polyObjNum = READINT32(save_p); - ht->controlSector = LoadSector(READUINT32(save_p)); - ht->rotscale = READFIXED(save_p); - ht->turnobjs = READUINT8(save_p); - ht->oldHeights = READFIXED(save_p); + ht->polyObjNum = READINT32(save->p); + ht->controlSector = LoadSector(READUINT32(save->p)); + ht->rotscale = READFIXED(save->p); + ht->turnobjs = READUINT8(save->p); + ht->oldHeights = READFIXED(save->p); return &ht->thinker; } -static thinker_t* LoadPolyfadeThinker(actionf_p1 thinker) +static thinker_t* LoadPolyfadeThinker(savebuffer_t *save, actionf_p1 thinker) { polyfade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); ht->thinker.function.acp1 = thinker; - ht->polyObjNum = READINT32(save_p); - ht->sourcevalue = READINT32(save_p); - ht->destvalue = READINT32(save_p); - ht->docollision = (boolean)READUINT8(save_p); - ht->doghostfade = (boolean)READUINT8(save_p); - ht->ticbased = (boolean)READUINT8(save_p); - ht->duration = READINT32(save_p); - ht->timer = READINT32(save_p); + ht->polyObjNum = READINT32(save->p); + ht->sourcevalue = READINT32(save->p); + ht->destvalue = READINT32(save->p); + ht->docollision = (boolean)READUINT8(save->p); + ht->doghostfade = (boolean)READUINT8(save->p); + ht->ticbased = (boolean)READUINT8(save->p); + ht->duration = READINT32(save->p); + ht->timer = READINT32(save->p); return &ht->thinker; } -static void P_NetUnArchiveThinkers(void) +static void P_NetUnArchiveThinkers(savebuffer_t *save) { thinker_t *currentthinker; thinker_t *next; @@ -4089,7 +4088,7 @@ static void P_NetUnArchiveThinkers(void) UINT32 i; UINT32 numloaded = 0; - if (READUINT32(save_p) != ARCHIVEBLOCK_THINKERS) + if (READUINT32(save->p) != ARCHIVEBLOCK_THINKERS) I_Error("Bad $$$.sav at archive block Thinkers"); // remove all the current thinkers @@ -4127,7 +4126,7 @@ static void P_NetUnArchiveThinkers(void) for (;;) { thinker_t* th = NULL; - tclass = READUINT8(save_p); + tclass = READUINT8(save->p); if (tclass == tc_end) break; // leave the saved thinker reading loop @@ -4136,167 +4135,167 @@ static void P_NetUnArchiveThinkers(void) switch (tclass) { case tc_mobj: - th = LoadMobjThinker((actionf_p1)P_MobjThinker); + th = LoadMobjThinker(save, (actionf_p1)P_MobjThinker); break; case tc_ceiling: - th = LoadCeilingThinker((actionf_p1)T_MoveCeiling); + th = LoadCeilingThinker(save, (actionf_p1)T_MoveCeiling); break; case tc_crushceiling: - th = LoadCeilingThinker((actionf_p1)T_CrushCeiling); + th = LoadCeilingThinker(save, (actionf_p1)T_CrushCeiling); break; case tc_floor: - th = LoadFloormoveThinker((actionf_p1)T_MoveFloor); + th = LoadFloormoveThinker(save, (actionf_p1)T_MoveFloor); break; case tc_flash: - th = LoadLightflashThinker((actionf_p1)T_LightningFlash); + th = LoadLightflashThinker(save, (actionf_p1)T_LightningFlash); break; case tc_strobe: - th = LoadStrobeThinker((actionf_p1)T_StrobeFlash); + th = LoadStrobeThinker(save, (actionf_p1)T_StrobeFlash); break; case tc_glow: - th = LoadGlowThinker((actionf_p1)T_Glow); + th = LoadGlowThinker(save, (actionf_p1)T_Glow); break; case tc_fireflicker: - th = LoadFireflickerThinker((actionf_p1)T_FireFlicker); + th = LoadFireflickerThinker(save, (actionf_p1)T_FireFlicker); break; case tc_elevator: - th = LoadElevatorThinker((actionf_p1)T_MoveElevator, true); + th = LoadElevatorThinker(save, (actionf_p1)T_MoveElevator, true); break; case tc_continuousfalling: - th = LoadContinuousFallThinker((actionf_p1)T_ContinuousFalling); + th = LoadContinuousFallThinker(save, (actionf_p1)T_ContinuousFalling); break; case tc_thwomp: - th = LoadThwompThinker((actionf_p1)T_ThwompSector); + th = LoadThwompThinker(save, (actionf_p1)T_ThwompSector); break; case tc_noenemies: - th = LoadNoEnemiesThinker((actionf_p1)T_NoEnemiesSector); + th = LoadNoEnemiesThinker(save, (actionf_p1)T_NoEnemiesSector); break; case tc_eachtime: - th = LoadEachTimeThinker((actionf_p1)T_EachTimeThinker); + th = LoadEachTimeThinker(save, (actionf_p1)T_EachTimeThinker); break; case tc_raisesector: - th = LoadRaiseThinker((actionf_p1)T_RaiseSector); + th = LoadRaiseThinker(save, (actionf_p1)T_RaiseSector); break; case tc_camerascanner: - th = LoadElevatorThinker((actionf_p1)T_CameraScanner, false); + th = LoadElevatorThinker(save, (actionf_p1)T_CameraScanner, false); break; case tc_bouncecheese: - th = LoadBounceCheeseThinker((actionf_p1)T_BounceCheese); + th = LoadBounceCheeseThinker(save, (actionf_p1)T_BounceCheese); break; case tc_startcrumble: - th = LoadCrumbleThinker((actionf_p1)T_StartCrumble); + th = LoadCrumbleThinker(save, (actionf_p1)T_StartCrumble); break; case tc_marioblock: - th = LoadMarioBlockThinker((actionf_p1)T_MarioBlock); + th = LoadMarioBlockThinker(save, (actionf_p1)T_MarioBlock); break; case tc_marioblockchecker: - th = LoadMarioCheckThinker((actionf_p1)T_MarioBlockChecker); + th = LoadMarioCheckThinker(save, (actionf_p1)T_MarioBlockChecker); break; case tc_floatsector: - th = LoadFloatThinker((actionf_p1)T_FloatSector); + th = LoadFloatThinker(save, (actionf_p1)T_FloatSector); break; case tc_laserflash: - th = LoadLaserThinker((actionf_p1)T_LaserFlash); + th = LoadLaserThinker(save, (actionf_p1)T_LaserFlash); break; case tc_lightfade: - th = LoadLightlevelThinker((actionf_p1)T_LightFade); + th = LoadLightlevelThinker(save, (actionf_p1)T_LightFade); break; case tc_executor: - th = LoadExecutorThinker((actionf_p1)T_ExecutorDelay); + th = LoadExecutorThinker(save, (actionf_p1)T_ExecutorDelay); restoreNum = true; break; case tc_disappear: - th = LoadDisappearThinker((actionf_p1)T_Disappear); + th = LoadDisappearThinker(save, (actionf_p1)T_Disappear); break; case tc_fade: - th = LoadFadeThinker((actionf_p1)T_Fade); + th = LoadFadeThinker(save, (actionf_p1)T_Fade); break; case tc_fadecolormap: - th = LoadFadeColormapThinker((actionf_p1)T_FadeColormap); + th = LoadFadeColormapThinker(save, (actionf_p1)T_FadeColormap); break; case tc_planedisplace: - th = LoadPlaneDisplaceThinker((actionf_p1)T_PlaneDisplace); + th = LoadPlaneDisplaceThinker(save, (actionf_p1)T_PlaneDisplace); break; case tc_polyrotate: - th = LoadPolyrotatetThinker((actionf_p1)T_PolyObjRotate); + th = LoadPolyrotatetThinker(save, (actionf_p1)T_PolyObjRotate); break; case tc_polymove: - th = LoadPolymoveThinker((actionf_p1)T_PolyObjMove); + th = LoadPolymoveThinker(save, (actionf_p1)T_PolyObjMove); break; case tc_polywaypoint: - th = LoadPolywaypointThinker((actionf_p1)T_PolyObjWaypoint); + th = LoadPolywaypointThinker(save, (actionf_p1)T_PolyObjWaypoint); break; case tc_polyslidedoor: - th = LoadPolyslidedoorThinker((actionf_p1)T_PolyDoorSlide); + th = LoadPolyslidedoorThinker(save, (actionf_p1)T_PolyDoorSlide); break; case tc_polyswingdoor: - th = LoadPolyswingdoorThinker((actionf_p1)T_PolyDoorSwing); + th = LoadPolyswingdoorThinker(save, (actionf_p1)T_PolyDoorSwing); break; case tc_polyflag: - th = LoadPolymoveThinker((actionf_p1)T_PolyObjFlag); + th = LoadPolymoveThinker(save, (actionf_p1)T_PolyObjFlag); break; case tc_polydisplace: - th = LoadPolydisplaceThinker((actionf_p1)T_PolyObjDisplace); + th = LoadPolydisplaceThinker(save, (actionf_p1)T_PolyObjDisplace); break; case tc_polyrotdisplace: - th = LoadPolyrotdisplaceThinker((actionf_p1)T_PolyObjRotDisplace); + th = LoadPolyrotdisplaceThinker(save, (actionf_p1)T_PolyObjRotDisplace); break; case tc_polyfade: - th = LoadPolyfadeThinker((actionf_p1)T_PolyObjFade); + th = LoadPolyfadeThinker(save, (actionf_p1)T_PolyObjFade); break; case tc_dynslopeline: - th = LoadDynamicLineSlopeThinker((actionf_p1)T_DynamicSlopeLine); + th = LoadDynamicLineSlopeThinker(save, (actionf_p1)T_DynamicSlopeLine); break; case tc_dynslopevert: - th = LoadDynamicVertexSlopeThinker((actionf_p1)T_DynamicSlopeVert); + th = LoadDynamicVertexSlopeThinker(save, (actionf_p1)T_DynamicSlopeVert); break; case tc_scroll: - th = LoadScrollThinker((actionf_p1)T_Scroll); + th = LoadScrollThinker(save, (actionf_p1)T_Scroll); break; case tc_friction: - th = LoadFrictionThinker((actionf_p1)T_Friction); + th = LoadFrictionThinker(save, (actionf_p1)T_Friction); break; case tc_pusher: - th = LoadPusherThinker((actionf_p1)T_Pusher); + th = LoadPusherThinker(save, (actionf_p1)T_Pusher); break; default: @@ -4332,29 +4331,29 @@ static void P_NetUnArchiveThinkers(void) #define PD_FLAGS 0x01 #define PD_TRANS 0x02 -static inline void P_ArchivePolyObj(polyobj_t *po) +static inline void P_ArchivePolyObj(savebuffer_t *save, polyobj_t *po) { UINT8 diff = 0; - WRITEINT32(save_p, po->id); - WRITEANGLE(save_p, po->angle); + WRITEINT32(save->p, po->id); + WRITEANGLE(save->p, po->angle); - WRITEFIXED(save_p, po->spawnSpot.x); - WRITEFIXED(save_p, po->spawnSpot.y); + WRITEFIXED(save->p, po->spawnSpot.x); + WRITEFIXED(save->p, po->spawnSpot.y); if (po->flags != po->spawnflags) diff |= PD_FLAGS; if (po->translucency != po->spawntrans) diff |= PD_TRANS; - WRITEUINT8(save_p, diff); + WRITEUINT8(save->p, diff); if (diff & PD_FLAGS) - WRITEINT32(save_p, po->flags); + WRITEINT32(save->p, po->flags); if (diff & PD_TRANS) - WRITEINT32(save_p, po->translucency); + WRITEINT32(save->p, po->translucency); } -static inline void P_UnArchivePolyObj(polyobj_t *po) +static inline void P_UnArchivePolyObj(savebuffer_t *save, polyobj_t *po) { INT32 id; UINT32 angle; @@ -4366,19 +4365,19 @@ static inline void P_UnArchivePolyObj(polyobj_t *po) // when they first start to run. po->thinker = NULL; - id = READINT32(save_p); + id = READINT32(save->p); - angle = READANGLE(save_p); + angle = READANGLE(save->p); - x = READFIXED(save_p); - y = READFIXED(save_p); + x = READFIXED(save->p); + y = READFIXED(save->p); - diff = READUINT8(save_p); + diff = READUINT8(save->p); if (diff & PD_FLAGS) - po->flags = READINT32(save_p); + po->flags = READINT32(save->p); if (diff & PD_TRANS) - po->translucency = READINT32(save_p); + po->translucency = READINT32(save->p); // if the object is bad or isn't in the id hash, we can do nothing more // with it, so return now @@ -4389,33 +4388,33 @@ static inline void P_UnArchivePolyObj(polyobj_t *po) Polyobj_MoveOnLoad(po, angle, x, y); } -static inline void P_ArchivePolyObjects(void) +static inline void P_ArchivePolyObjects(savebuffer_t *save) { INT32 i; - WRITEUINT32(save_p, ARCHIVEBLOCK_POBJS); + WRITEUINT32(save->p, ARCHIVEBLOCK_POBJS); // save number of polyobjects - WRITEINT32(save_p, numPolyObjects); + WRITEINT32(save->p, numPolyObjects); for (i = 0; i < numPolyObjects; ++i) - P_ArchivePolyObj(&PolyObjects[i]); + P_ArchivePolyObj(save, &PolyObjects[i]); } -static inline void P_UnArchivePolyObjects(void) +static inline void P_UnArchivePolyObjects(savebuffer_t *save) { INT32 i, numSavedPolys; - if (READUINT32(save_p) != ARCHIVEBLOCK_POBJS) + if (READUINT32(save->p) != ARCHIVEBLOCK_POBJS) I_Error("Bad $$$.sav at archive block Pobjs"); - numSavedPolys = READINT32(save_p); + numSavedPolys = READINT32(save->p); if (numSavedPolys != numPolyObjects) I_Error("P_UnArchivePolyObjects: polyobj count inconsistency\n"); for (i = 0; i < numSavedPolys; ++i) - P_UnArchivePolyObj(&PolyObjects[i]); + P_UnArchivePolyObj(save, &PolyObjects[i]); } static inline void P_FinishMobjs(void) @@ -4586,11 +4585,11 @@ static void P_RelinkPointers(void) } } -static inline void P_NetArchiveSpecials(void) +static inline void P_NetArchiveSpecials(savebuffer_t *save) { size_t i, z; - WRITEUINT32(save_p, ARCHIVEBLOCK_SPECIALS); + WRITEUINT32(save->p, ARCHIVEBLOCK_SPECIALS); // itemrespawn queue for deathmatch i = iquetail; @@ -4600,53 +4599,53 @@ static inline void P_NetArchiveSpecials(void) { if (&mapthings[z] == itemrespawnque[i]) { - WRITEUINT32(save_p, z); + WRITEUINT32(save->p, z); break; } } - WRITEUINT32(save_p, itemrespawntime[i]); + WRITEUINT32(save->p, itemrespawntime[i]); i = (i + 1) & (ITEMQUESIZE-1); } // end delimiter - WRITEUINT32(save_p, 0xffffffff); + WRITEUINT32(save->p, 0xffffffff); // Sky number - WRITESTRINGN(save_p, globallevelskytexture, 9); + WRITESTRINGN(save->p, globallevelskytexture, 9); // Current global weather type - WRITEUINT8(save_p, globalweather); + WRITEUINT8(save->p, globalweather); if (metalplayback) // Is metal sonic running? { - WRITEUINT8(save_p, 0x01); - G_SaveMetal(&save_p); + WRITEUINT8(save->p, 0x01); + G_SaveMetal(&save->p); } else - WRITEUINT8(save_p, 0x00); + WRITEUINT8(save->p, 0x00); } -static void P_NetUnArchiveSpecials(void) +static void P_NetUnArchiveSpecials(savebuffer_t *save) { char skytex[9]; size_t i; - if (READUINT32(save_p) != ARCHIVEBLOCK_SPECIALS) + if (READUINT32(save->p) != ARCHIVEBLOCK_SPECIALS) I_Error("Bad $$$.sav at archive block Specials"); // BP: added save itemrespawn queue for deathmatch iquetail = iquehead = 0; - while ((i = READUINT32(save_p)) != 0xffffffff) + while ((i = READUINT32(save->p)) != 0xffffffff) { itemrespawnque[iquehead] = &mapthings[i]; - itemrespawntime[iquehead++] = READINT32(save_p); + itemrespawntime[iquehead++] = READINT32(save->p); } - READSTRINGN(save_p, skytex, sizeof(skytex)); + READSTRINGN(save->p, skytex, sizeof(skytex)); if (strcmp(skytex, globallevelskytexture)) P_SetupLevelSky(skytex, true); - globalweather = READUINT8(save_p); + globalweather = READUINT8(save->p); if (globalweather) { @@ -4661,14 +4660,14 @@ static void P_NetUnArchiveSpecials(void) P_SwitchWeather(globalweather); } - if (READUINT8(save_p) == 0x01) // metal sonic - G_LoadMetal(&save_p); + if (READUINT8(save->p) == 0x01) // metal sonic + G_LoadMetal(&save->p); } // ======================================================================= // Misc // ======================================================================= -static inline void P_ArchiveMisc(INT16 mapnum) +static inline void P_ArchiveMisc(savebuffer_t *save, INT16 mapnum) { //lastmapsaved = mapnum; lastmaploaded = mapnum; @@ -4676,16 +4675,16 @@ static inline void P_ArchiveMisc(INT16 mapnum) if (gamecomplete) mapnum |= 8192; - WRITEINT16(save_p, mapnum); - WRITEUINT16(save_p, emeralds+357); - WRITESTRINGN(save_p, timeattackfolder, sizeof(timeattackfolder)); + WRITEINT16(save->p, mapnum); + WRITEUINT16(save->p, emeralds+357); + WRITESTRINGN(save->p, timeattackfolder, sizeof(timeattackfolder)); } -static inline void P_UnArchiveSPGame(INT16 mapoverride) +static inline void P_UnArchiveSPGame(savebuffer_t *save, INT16 mapoverride) { char testname[sizeof(timeattackfolder)]; - gamemap = READINT16(save_p); + gamemap = READINT16(save->p); if (mapoverride != 0) { @@ -4703,9 +4702,9 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride) //lastmapsaved = gamemap; lastmaploaded = gamemap; - savedata.emeralds = READUINT16(save_p)-357; + savedata.emeralds = READUINT16(save->p)-357; - READSTRINGN(save_p, testname, sizeof(testname)); + READSTRINGN(save->p, testname, sizeof(testname)); if (strcmp(testname, timeattackfolder)) { @@ -4719,27 +4718,27 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride) playeringame[consoleplayer] = true; } -static void P_NetArchiveMisc(boolean resending) +static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) { size_t i, j; - WRITEUINT32(save_p, ARCHIVEBLOCK_MISC); + WRITEUINT32(save->p, ARCHIVEBLOCK_MISC); if (resending) - WRITEUINT32(save_p, gametic); - WRITEINT16(save_p, gamemap); + WRITEUINT32(save->p, gametic); + WRITEINT16(save->p, gamemap); if (gamestate != GS_LEVEL) - WRITEINT16(save_p, GS_WAITINGPLAYERS); // nice hack to put people back into waitingplayers + WRITEINT16(save->p, GS_WAITINGPLAYERS); // nice hack to put people back into waitingplayers else - WRITEINT16(save_p, gamestate); - WRITEINT16(save_p, gametype); + WRITEINT16(save->p, gamestate); + WRITEINT16(save->p, gametype); { UINT32 pig = 0; for (i = 0; i < MAXPLAYERS; i++) pig |= (playeringame[i] != 0)<p, pig); } for (i = 0; i < MAXUNLOCKABLES;) @@ -4747,143 +4746,142 @@ static void P_NetArchiveMisc(boolean resending) UINT8 btemp = 0; for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) btemp |= (netUnlocked[j+i] << j); - WRITEUINT8(save_p, btemp); + WRITEUINT8(save->p, btemp); i += j; } - WRITEUINT8(save_p, encoremode); + WRITEUINT8(save->p, encoremode); - WRITEUINT32(save_p, leveltime); - WRITEINT16(save_p, lastmap); - WRITEUINT16(save_p, bossdisabled); + WRITEUINT32(save->p, leveltime); + WRITEINT16(save->p, lastmap); + WRITEUINT16(save->p, bossdisabled); for (i = 0; i < 4; i++) { - WRITEINT16(save_p, votelevels[i][0]); - WRITEINT16(save_p, votelevels[i][1]); + WRITEINT16(save->p, votelevels[i][0]); + WRITEINT16(save->p, votelevels[i][1]); } for (i = 0; i < MAXPLAYERS; i++) - WRITESINT8(save_p, votes[i]); + WRITESINT8(save->p, votes[i]); - WRITESINT8(save_p, pickedvote); + WRITESINT8(save->p, pickedvote); - WRITEUINT16(save_p, emeralds); + WRITEUINT16(save->p, emeralds); { UINT8 globools = 0; if (stagefailed) globools |= 1; if (stoppedclock) globools |= (1<<1); - WRITEUINT8(save_p, globools); + WRITEUINT8(save->p, globools); } - WRITEUINT32(save_p, bluescore); - WRITEUINT32(save_p, redscore); + WRITEUINT32(save->p, bluescore); + WRITEUINT32(save->p, redscore); - WRITEUINT16(save_p, skincolor_redteam); - WRITEUINT16(save_p, skincolor_blueteam); - WRITEUINT16(save_p, skincolor_redring); - WRITEUINT16(save_p, skincolor_bluering); + WRITEUINT16(save->p, skincolor_redteam); + WRITEUINT16(save->p, skincolor_blueteam); + WRITEUINT16(save->p, skincolor_redring); + WRITEUINT16(save->p, skincolor_bluering); - WRITEINT32(save_p, modulothing); + WRITEINT32(save->p, modulothing); - WRITEINT16(save_p, autobalance); - WRITEINT16(save_p, teamscramble); + WRITEINT16(save->p, autobalance); + WRITEINT16(save->p, teamscramble); for (i = 0; i < MAXPLAYERS; i++) - WRITEINT16(save_p, scrambleplayers[i]); + WRITEINT16(save->p, scrambleplayers[i]); for (i = 0; i < MAXPLAYERS; i++) - WRITEINT16(save_p, scrambleteams[i]); + WRITEINT16(save->p, scrambleteams[i]); - WRITEINT16(save_p, scrambletotal); - WRITEINT16(save_p, scramblecount); + WRITEINT16(save->p, scrambletotal); + WRITEINT16(save->p, scramblecount); - WRITEUINT32(save_p, racecountdown); - WRITEUINT32(save_p, exitcountdown); + WRITEUINT32(save->p, racecountdown); + WRITEUINT32(save->p, exitcountdown); - WRITEFIXED(save_p, gravity); - WRITEFIXED(save_p, mapobjectscale); + WRITEFIXED(save->p, gravity); + WRITEFIXED(save->p, mapobjectscale); // SRB2kart - WRITEINT32(save_p, numgotboxes); - WRITEUINT8(save_p, numtargets); - WRITEUINT8(save_p, battlecapsules); + WRITEINT32(save->p, numgotboxes); + WRITEUINT8(save->p, numtargets); + WRITEUINT8(save->p, battlecapsules); - WRITEUINT8(save_p, gamespeed); - WRITEUINT8(save_p, numlaps); - WRITEUINT8(save_p, franticitems); + WRITEUINT8(save->p, gamespeed); + WRITEUINT8(save->p, numlaps); + WRITEUINT8(save->p, franticitems); - WRITESINT8(save_p, speedscramble); - WRITESINT8(save_p, encorescramble); + WRITESINT8(save->p, speedscramble); + WRITESINT8(save->p, encorescramble); for (i = 0; i < 4; i++) - WRITESINT8(save_p, battlewanted[i]); + WRITESINT8(save->p, battlewanted[i]); // battleovertime_t - WRITEUINT16(save_p, battleovertime.enabled); - WRITEFIXED(save_p, battleovertime.radius); - WRITEFIXED(save_p, battleovertime.x); - WRITEFIXED(save_p, battleovertime.y); - WRITEFIXED(save_p, battleovertime.z); + WRITEUINT16(save->p, battleovertime.enabled); + WRITEFIXED(save->p, battleovertime.radius); + WRITEFIXED(save->p, battleovertime.x); + WRITEFIXED(save->p, battleovertime.y); + WRITEFIXED(save->p, battleovertime.z); - WRITEUINT32(save_p, wantedcalcdelay); + WRITEUINT32(save->p, wantedcalcdelay); for (i = 0; i < NUMKARTITEMS-1; i++) - WRITEUINT32(save_p, itemCooldowns[i]); - WRITEUINT32(save_p, mapreset); + WRITEUINT32(save->p, itemCooldowns[i]); + WRITEUINT32(save->p, mapreset); - WRITEUINT8(save_p, spectateGriefed); + WRITEUINT8(save->p, spectateGriefed); - WRITEUINT8(save_p, thwompsactive); - WRITEUINT8(save_p, lastLowestLap); - WRITESINT8(save_p, spbplace); - WRITEUINT8(save_p, rainbowstartavailable); - WRITEUINT8(save_p, inDuel); + WRITEUINT8(save->p, thwompsactive); + WRITEUINT8(save->p, lastLowestLap); + WRITESINT8(save->p, spbplace); + WRITEUINT8(save->p, rainbowstartavailable); + WRITEUINT8(save->p, inDuel); - WRITEUINT32(save_p, introtime); - WRITEUINT32(save_p, starttime); - WRITEUINT8(save_p, numbulbs); + WRITEUINT32(save->p, introtime); + WRITEUINT32(save->p, starttime); + WRITEUINT8(save->p, numbulbs); - WRITEUINT32(save_p, timelimitintics); - WRITEUINT32(save_p, extratimeintics); - WRITEUINT32(save_p, secretextratime); + WRITEUINT32(save->p, timelimitintics); + WRITEUINT32(save->p, extratimeintics); + WRITEUINT32(save->p, secretextratime); // Is it paused? if (paused) - WRITEUINT8(save_p, 0x2f); + WRITEUINT8(save->p, 0x2f); else - WRITEUINT8(save_p, 0x2e); + WRITEUINT8(save->p, 0x2e); - WRITEUINT32(save_p, livestudioaudience_timer); + WRITEUINT32(save->p, livestudioaudience_timer); // Only the server uses this, but it // needs synched for remote admins anyway. - WRITEUINT32(save_p, schedule_len); + WRITEUINT32(save->p, schedule_len); for (i = 0; i < schedule_len; i++) { scheduleTask_t *task = schedule[i]; - WRITEINT16(save_p, task->basetime); - WRITEINT16(save_p, task->timer); - WRITESTRING(save_p, task->command); + WRITEINT16(save->p, task->basetime); + WRITEINT16(save->p, task->timer); + WRITESTRING(save->p, task->command); } - WRITEUINT32(save_p, cht_debug); + WRITEUINT32(save->p, cht_debug); } -static inline boolean P_NetUnArchiveMisc(boolean reloading) +static inline boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) { size_t i, j; size_t numTasks; - UINT8 *old_save_p; - if (READUINT32(save_p) != ARCHIVEBLOCK_MISC) + if (READUINT32(save->p) != ARCHIVEBLOCK_MISC) I_Error("Bad $$$.sav at archive block Misc"); if (reloading) - gametic = READUINT32(save_p); + gametic = READUINT32(save->p); - gamemap = READINT16(save_p); + gamemap = READINT16(save->p); // gamemap changed; we assume that its map header is always valid, // so make it so @@ -4895,12 +4893,12 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) if (!reloading) mapmusflags |= MUSIC_RELOADRESET; - G_SetGamestate(READINT16(save_p)); + G_SetGamestate(READINT16(save->p)); - gametype = READINT16(save_p); + gametype = READINT16(save->p); { - UINT32 pig = READUINT32(save_p); + UINT32 pig = READUINT32(save->p); for (i = 0; i < MAXPLAYERS; i++) { playeringame[i] = (pig & (1<p); for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j) netUnlocked[j+i] = ((rtemp >> j) & 1); i += j; } - encoremode = (boolean)READUINT8(save_p); - - // FIXME: save_p should not be global!!! - old_save_p = save_p; + encoremode = (boolean)READUINT8(save->p); if (!P_LoadLevel(true, reloading)) { @@ -4927,132 +4922,130 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading) return false; } - save_p = old_save_p; - // get the time - leveltime = READUINT32(save_p); - lastmap = READINT16(save_p); - bossdisabled = READUINT16(save_p); + leveltime = READUINT32(save->p); + lastmap = READINT16(save->p); + bossdisabled = READUINT16(save->p); for (i = 0; i < 4; i++) { - votelevels[i][0] = READINT16(save_p); - votelevels[i][1] = READINT16(save_p); + votelevels[i][0] = READINT16(save->p); + votelevels[i][1] = READINT16(save->p); } for (i = 0; i < MAXPLAYERS; i++) - votes[i] = READSINT8(save_p); + votes[i] = READSINT8(save->p); - pickedvote = READSINT8(save_p); + pickedvote = READSINT8(save->p); - emeralds = READUINT16(save_p); + emeralds = READUINT16(save->p); { - UINT8 globools = READUINT8(save_p); + UINT8 globools = READUINT8(save->p); stagefailed = !!(globools & 1); stoppedclock = !!(globools & (1<<1)); } - bluescore = READUINT32(save_p); - redscore = READUINT32(save_p); + bluescore = READUINT32(save->p); + redscore = READUINT32(save->p); - skincolor_redteam = READUINT16(save_p); - skincolor_blueteam = READUINT16(save_p); - skincolor_redring = READUINT16(save_p); - skincolor_bluering = READUINT16(save_p); + skincolor_redteam = READUINT16(save->p); + skincolor_blueteam = READUINT16(save->p); + skincolor_redring = READUINT16(save->p); + skincolor_bluering = READUINT16(save->p); - modulothing = READINT32(save_p); + modulothing = READINT32(save->p); - autobalance = READINT16(save_p); - teamscramble = READINT16(save_p); + autobalance = READINT16(save->p); + teamscramble = READINT16(save->p); for (i = 0; i < MAXPLAYERS; i++) - scrambleplayers[i] = READINT16(save_p); + scrambleplayers[i] = READINT16(save->p); for (i = 0; i < MAXPLAYERS; i++) - scrambleteams[i] = READINT16(save_p); + scrambleteams[i] = READINT16(save->p); - scrambletotal = READINT16(save_p); - scramblecount = READINT16(save_p); + scrambletotal = READINT16(save->p); + scramblecount = READINT16(save->p); - racecountdown = READUINT32(save_p); - exitcountdown = READUINT32(save_p); + racecountdown = READUINT32(save->p); + exitcountdown = READUINT32(save->p); - gravity = READFIXED(save_p); - mapobjectscale = READFIXED(save_p); + gravity = READFIXED(save->p); + mapobjectscale = READFIXED(save->p); // SRB2kart - numgotboxes = READINT32(save_p); - numtargets = READUINT8(save_p); - battlecapsules = (boolean)READUINT8(save_p); + numgotboxes = READINT32(save->p); + numtargets = READUINT8(save->p); + battlecapsules = (boolean)READUINT8(save->p); - gamespeed = READUINT8(save_p); - numlaps = READUINT8(save_p); - franticitems = (boolean)READUINT8(save_p); + gamespeed = READUINT8(save->p); + numlaps = READUINT8(save->p); + franticitems = (boolean)READUINT8(save->p); - speedscramble = READSINT8(save_p); - encorescramble = READSINT8(save_p); + speedscramble = READSINT8(save->p); + encorescramble = READSINT8(save->p); for (i = 0; i < 4; i++) - battlewanted[i] = READSINT8(save_p); + battlewanted[i] = READSINT8(save->p); // battleovertime_t - battleovertime.enabled = READUINT16(save_p); - battleovertime.radius = READFIXED(save_p); - battleovertime.x = READFIXED(save_p); - battleovertime.y = READFIXED(save_p); - battleovertime.z = READFIXED(save_p); + battleovertime.enabled = READUINT16(save->p); + battleovertime.radius = READFIXED(save->p); + battleovertime.x = READFIXED(save->p); + battleovertime.y = READFIXED(save->p); + battleovertime.z = READFIXED(save->p); - wantedcalcdelay = READUINT32(save_p); + wantedcalcdelay = READUINT32(save->p); for (i = 0; i < NUMKARTITEMS-1; i++) - itemCooldowns[i] = READUINT32(save_p); - mapreset = READUINT32(save_p); + itemCooldowns[i] = READUINT32(save->p); + mapreset = READUINT32(save->p); - spectateGriefed = READUINT8(save_p); + spectateGriefed = READUINT8(save->p); - thwompsactive = (boolean)READUINT8(save_p); - lastLowestLap = READUINT8(save_p); - spbplace = READSINT8(save_p); - rainbowstartavailable = (boolean)READUINT8(save_p); - inDuel = (boolean)READUINT8(save_p); + thwompsactive = (boolean)READUINT8(save->p); + lastLowestLap = READUINT8(save->p); + spbplace = READSINT8(save->p); + rainbowstartavailable = (boolean)READUINT8(save->p); + inDuel = (boolean)READUINT8(save->p); - introtime = READUINT32(save_p); - starttime = READUINT32(save_p); - numbulbs = READUINT8(save_p); + introtime = READUINT32(save->p); + starttime = READUINT32(save->p); + numbulbs = READUINT8(save->p); - timelimitintics = READUINT32(save_p); - extratimeintics = READUINT32(save_p); - secretextratime = READUINT32(save_p); + timelimitintics = READUINT32(save->p); + extratimeintics = READUINT32(save->p); + secretextratime = READUINT32(save->p); // Is it paused? - if (READUINT8(save_p) == 0x2f) + if (READUINT8(save->p) == 0x2f) paused = true; - livestudioaudience_timer = READUINT32(save_p); + livestudioaudience_timer = READUINT32(save->p); // Only the server uses this, but it // needs synched for remote admins anyway. Schedule_Clear(); - numTasks = READUINT32(save_p); + numTasks = READUINT32(save->p); for (i = 0; i < numTasks; i++) { INT16 basetime; INT16 timer; char command[MAXTEXTCMD]; - basetime = READINT16(save_p); - timer = READINT16(save_p); - READSTRING(save_p, command); + basetime = READINT16(save->p); + timer = READINT16(save->p); + READSTRING(save->p, command); Schedule_Add(basetime, timer, command); } - cht_debug = READUINT32(save_p); + cht_debug = READUINT32(save->p); return true; } -static inline void P_ArchiveLuabanksAndConsistency(void) +static inline void P_ArchiveLuabanksAndConsistency(savebuffer_t *save) { UINT8 i, banksinuse = NUM_LUABANKS; @@ -5061,30 +5054,30 @@ static inline void P_ArchiveLuabanksAndConsistency(void) if (banksinuse) { - WRITEUINT8(save_p, 0xb7); // luabanks marker - WRITEUINT8(save_p, banksinuse); + WRITEUINT8(save->p, 0xb7); // luabanks marker + WRITEUINT8(save->p, banksinuse); for (i = 0; i < banksinuse; i++) - WRITEINT32(save_p, luabanks[i]); + WRITEINT32(save->p, luabanks[i]); } - WRITEUINT8(save_p, 0x1d); // consistency marker + WRITEUINT8(save->p, 0x1d); // consistency marker } -static inline boolean P_UnArchiveLuabanksAndConsistency(void) +static inline boolean P_UnArchiveLuabanksAndConsistency(savebuffer_t *save) { - switch (READUINT8(save_p)) + switch (READUINT8(save->p)) { case 0xb7: // luabanks marker { - UINT8 i, banksinuse = READUINT8(save_p); + UINT8 i, banksinuse = READUINT8(save->p); if (banksinuse > NUM_LUABANKS) { CONS_Alert(CONS_ERROR, M_GetText("Corrupt Luabanks! (Too many banks in use)\n")); return false; } for (i = 0; i < banksinuse; i++) - luabanks[i] = READINT32(save_p); - if (READUINT8(save_p) != 0x1d) // consistency marker + luabanks[i] = READINT32(save->p); + if (READUINT8(save->p) != 0x1d) // consistency marker { CONS_Alert(CONS_ERROR, M_GetText("Corrupt Luabanks! (Failed consistency check)\n")); return false; @@ -5100,50 +5093,50 @@ static inline boolean P_UnArchiveLuabanksAndConsistency(void) return true; } -static void P_NetArchiveRNG(void) +static void P_NetArchiveRNG(savebuffer_t *save) { size_t i; - WRITEUINT32(save_p, ARCHIVEBLOCK_RNG); + WRITEUINT32(save->p, ARCHIVEBLOCK_RNG); for (i = 0; i < PRNUMCLASS; i++) { - WRITEUINT32(save_p, P_GetInitSeed(i)); - WRITEUINT32(save_p, P_GetRandSeed(i)); + WRITEUINT32(save->p, P_GetInitSeed(i)); + WRITEUINT32(save->p, P_GetRandSeed(i)); } } -static inline void P_NetUnArchiveRNG(void) +static inline void P_NetUnArchiveRNG(savebuffer_t *save) { size_t i; - if (READUINT32(save_p) != ARCHIVEBLOCK_RNG) + if (READUINT32(save->p) != ARCHIVEBLOCK_RNG) I_Error("Bad $$$.sav at archive block RNG"); for (i = 0; i < PRNUMCLASS; i++) { - UINT32 init = READUINT32(save_p); - UINT32 seed = READUINT32(save_p); + UINT32 init = READUINT32(save->p); + UINT32 seed = READUINT32(save->p); P_SetRandSeedNet(i, init, seed); } } -void P_SaveGame(INT16 mapnum) +void P_SaveGame(savebuffer_t *save, INT16 mapnum) { - P_ArchiveMisc(mapnum); - P_ArchivePlayer(); - P_ArchiveLuabanksAndConsistency(); + P_ArchiveMisc(save, mapnum); + P_ArchivePlayer(save); + P_ArchiveLuabanksAndConsistency(save); } -void P_SaveNetGame(boolean resending) +void P_SaveNetGame(savebuffer_t *save, boolean resending) { thinker_t *th; mobj_t *mobj; INT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise - CV_SaveNetVars(&save_p); - P_NetArchiveMisc(resending); + CV_SaveNetVars(&save->p); + P_NetArchiveMisc(save, resending); // Assign the mobjnumber for pointer tracking if (gamestate == GS_LEVEL) @@ -5160,25 +5153,25 @@ void P_SaveNetGame(boolean resending) } } - P_NetArchivePlayers(); + P_NetArchivePlayers(save); if (gamestate == GS_LEVEL) { - P_NetArchiveWorld(); - P_ArchivePolyObjects(); - P_NetArchiveThinkers(); - P_NetArchiveSpecials(); - P_NetArchiveColormaps(); - P_NetArchiveTubeWaypoints(); - P_NetArchiveWaypoints(); + P_NetArchiveWorld(save); + P_ArchivePolyObjects(save); + P_NetArchiveThinkers(save); + P_NetArchiveSpecials(save); + P_NetArchiveColormaps(save); + P_NetArchiveTubeWaypoints(save); + P_NetArchiveWaypoints(save); } - LUA_Archive(&save_p); + LUA_Archive(&save->p, true); - P_NetArchiveRNG(); + P_NetArchiveRNG(save); - P_ArchiveLuabanksAndConsistency(); + P_ArchiveLuabanksAndConsistency(save); } -boolean P_LoadGame(INT16 mapoverride) +boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride) { if (gamestate == GS_INTERMISSION) Y_EndIntermission(); @@ -5186,10 +5179,10 @@ boolean P_LoadGame(INT16 mapoverride) Y_EndVote(); G_SetGamestate(GS_NULL); // should be changed in P_UnArchiveMisc - P_UnArchiveSPGame(mapoverride); - P_UnArchivePlayer(); + P_UnArchiveSPGame(save, mapoverride); + P_UnArchivePlayer(save); - if (!P_UnArchiveLuabanksAndConsistency()) + if (!P_UnArchiveLuabanksAndConsistency(save)) return false; // Only do this after confirming savegame is ok @@ -5199,31 +5192,31 @@ boolean P_LoadGame(INT16 mapoverride) return true; } -boolean P_LoadNetGame(boolean reloading) +boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) { - CV_LoadNetVars(&save_p); + CV_LoadNetVars(&save->p); - if (!P_NetUnArchiveMisc(reloading)) + if (!P_NetUnArchiveMisc(save, reloading)) return false; - P_NetUnArchivePlayers(); + P_NetUnArchivePlayers(save); if (gamestate == GS_LEVEL) { - P_NetUnArchiveWorld(); - P_UnArchivePolyObjects(); - P_NetUnArchiveThinkers(); - P_NetUnArchiveSpecials(); - P_NetUnArchiveColormaps(); - P_NetUnArchiveTubeWaypoints(); - P_NetUnArchiveWaypoints(); + P_NetUnArchiveWorld(save); + P_UnArchivePolyObjects(save); + P_NetUnArchiveThinkers(save); + P_NetUnArchiveSpecials(save); + P_NetUnArchiveColormaps(save); + P_NetUnArchiveTubeWaypoints(save); + P_NetUnArchiveWaypoints(save); P_RelinkPointers(); P_FinishMobjs(); } - LUA_UnArchive(&save_p); + LUA_UnArchive(&save->p, true); - P_NetUnArchiveRNG(); + P_NetUnArchiveRNG(save); // The precipitation would normally be spawned in P_SetupLevel, which is called by // P_NetUnArchiveMisc above. However, that would place it up before P_NetUnArchiveThinkers, @@ -5231,5 +5224,5 @@ boolean P_LoadNetGame(boolean reloading) // precipitation when loading a netgame save. Instead, precip has to be spawned here. // This is done in P_NetUnArchiveSpecials now. - return P_UnArchiveLuabanksAndConsistency(); + return P_UnArchiveLuabanksAndConsistency(save); } diff --git a/src/p_saveg.h b/src/p_saveg.h index a8acbe693..538febcf9 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -25,10 +25,10 @@ extern "C" { // Persistent storage/archiving. // These are the load / save game routines. -void P_SaveGame(INT16 mapnum); -void P_SaveNetGame(boolean resending); -boolean P_LoadGame(INT16 mapoverride); -boolean P_LoadNetGame(boolean reloading); +void P_SaveGame(savebuffer_t *save, INT16 mapnum); +void P_SaveNetGame(savebuffer_t *save, boolean resending); +boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride); +boolean P_LoadNetGame(savebuffer_t *save, boolean reloading); mobj_t *P_FindNewPosition(UINT32 oldposition); @@ -42,7 +42,12 @@ struct savedata_t }; extern savedata_t savedata; -extern UINT8 *save_p; + +struct savebuffer_t +{ + UINT8 *buffer; + UINT8 *p; +}; #ifdef __cplusplus } // extern "C" diff --git a/src/p_setup.c b/src/p_setup.c index 61c2a9ec3..b32c5126c 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -722,12 +722,12 @@ void P_WriteThings(void) const char * filename; size_t i, length; mapthing_t *mt; - UINT8 *savebuffer, *savebuf_p; + savebuffer_t save; INT16 temp; - savebuf_p = savebuffer = (UINT8 *)malloc(nummapthings * sizeof (mapthing_t)); + save.p = save.buffer = (UINT8 *)malloc(nummapthings * sizeof (mapthing_t)); - if (!savebuf_p) + if (!save.p) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for thing writing!\n")); return; @@ -736,23 +736,23 @@ void P_WriteThings(void) mt = mapthings; for (i = 0; i < nummapthings; i++, mt++) { - WRITEINT16(savebuf_p, mt->x); - WRITEINT16(savebuf_p, mt->y); + WRITEINT16(save.p, mt->x); + WRITEINT16(save.p, mt->y); - WRITEINT16(savebuf_p, mt->angle); + WRITEINT16(save.p, mt->angle); temp = (INT16)(mt->type + ((INT16)mt->extrainfo << 12)); - WRITEINT16(savebuf_p, temp); - WRITEUINT16(savebuf_p, mt->options); + WRITEINT16(save.p, temp); + WRITEUINT16(save.p, mt->options); } - length = savebuf_p - savebuffer; + length = save.p - save.buffer; filename = va("newthings-%s.lmp", G_BuildMapName(gamemap)); - FIL_WriteFile(filename, savebuffer, length); - free(savebuffer); - savebuf_p = NULL; + FIL_WriteFile(filename, save.buffer, length); + free(save.buffer); + save.p = NULL; CONS_Printf(M_GetText("%s saved.\n"), filename); } diff --git a/src/typedef.h b/src/typedef.h index 81ba62c31..57958b34e 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -265,6 +265,7 @@ TYPEDEF (polyfadedata_t); // p_saveg.h TYPEDEF (savedata_t); +TYPEDEF (savebuffer_t); // p_setup.h TYPEDEF (levelflat_t); From 061f8b058768314553c3af9bd9a0e0e3557e9c8e Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 25 Dec 2022 13:49:55 -0800 Subject: [PATCH 060/128] g_demo.c: use savebuffer_t --- src/g_demo.c | 656 +++++++++++++++++++++++++-------------------------- src/g_demo.h | 2 - 2 files changed, 328 insertions(+), 330 deletions(-) diff --git a/src/g_demo.c b/src/g_demo.c index 298f887e5..57dfdc9fa 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -39,6 +39,7 @@ #include "v_video.h" #include "lua_hook.h" #include "md5.h" // demo checksums +#include "p_saveg.h" // savebuffer_t // SRB2Kart #include "d_netfil.h" // nameonly @@ -69,9 +70,8 @@ boolean noblit; // for comparative timing purposes tic_t demostarttime; // for comparative timing purposes static char demoname[MAX_WADPATH]; -static UINT8 *demobuffer = NULL; +static savebuffer_t demobuf; static UINT8 *demotime_p, *demoinfo_p; -UINT8 *demo_p; static UINT8 *demoend; static UINT8 demoflags; boolean demosynced = true; // console warning message @@ -219,7 +219,7 @@ void G_ReadDemoExtraData(void) if (leveltime > starttime) { - rewind_t *rewind = CL_SaveRewindPoint(demo_p - demobuffer); + rewind_t *rewind = CL_SaveRewindPoint(demobuf.p - demobuf.buffer); if (rewind) { memcpy(rewind->oldcmd, oldcmd, sizeof (oldcmd)); @@ -229,11 +229,11 @@ void G_ReadDemoExtraData(void) memset(name, '\0', 17); - p = READUINT8(demo_p); + p = READUINT8(demobuf.p); while (p < DW_EXTRASTUFF) { - extradata = READUINT8(demo_p); + extradata = READUINT8(demobuf.p); if (extradata & DXD_JOINDATA) { @@ -247,20 +247,20 @@ void G_ReadDemoExtraData(void) for (i = 0; i < MAXAVAILABILITY; i++) { - players[p].availabilities[i] = READUINT8(demo_p); + players[p].availabilities[i] = READUINT8(demobuf.p); } - players[p].bot = !!(READUINT8(demo_p)); + players[p].bot = !!(READUINT8(demobuf.p)); if (players[p].bot) { - players[p].botvars.difficulty = READUINT8(demo_p); - players[p].botvars.diffincrease = READUINT8(demo_p); // needed to avoid having to duplicate logic - players[p].botvars.rival = (boolean)READUINT8(demo_p); + players[p].botvars.difficulty = READUINT8(demobuf.p); + players[p].botvars.diffincrease = READUINT8(demobuf.p); // needed to avoid having to duplicate logic + players[p].botvars.rival = (boolean)READUINT8(demobuf.p); } } if (extradata & DXD_PLAYSTATE) { - i = READUINT8(demo_p); + i = READUINT8(demobuf.p); switch (i) { case DXD_PST_PLAYING: @@ -305,7 +305,7 @@ void G_ReadDemoExtraData(void) // Skin - skinid = READUINT8(demo_p); + skinid = READUINT8(demobuf.p); if (skinid >= demo.numskins) skinid = 0; SetPlayerSkinByNum(p, demo.skinlist[skinid].mapping); @@ -318,8 +318,8 @@ void G_ReadDemoExtraData(void) if (extradata & DXD_COLOR) { // Color - M_Memcpy(name, demo_p, 16); - demo_p += 16; + M_Memcpy(name, demobuf.p, 16); + demobuf.p += 16; for (i = 0; i < numskincolors; i++) if (!stricmp(skincolors[i].name, name)) // SRB2kart { @@ -332,19 +332,19 @@ void G_ReadDemoExtraData(void) if (extradata & DXD_NAME) { // Name - M_Memcpy(player_names[p],demo_p,16); - demo_p += 16; + M_Memcpy(player_names[p],demobuf.p,16); + demobuf.p += 16; } if (extradata & DXD_FOLLOWER) { // Set our follower - M_Memcpy(name, demo_p, 16); - demo_p += 16; + M_Memcpy(name, demobuf.p, 16); + demobuf.p += 16; K_SetFollowerByName(p, name); // Follower's color - M_Memcpy(name, demo_p, 16); - demo_p += 16; + M_Memcpy(name, demobuf.p, 16); + demobuf.p += 16; for (i = 0; i < numskincolors +2; i++) // +2 because of Match and Opposite { if (!stricmp(Followercolor_cons_t[i].strvalue, name)) @@ -364,12 +364,12 @@ void G_ReadDemoExtraData(void) } if (extradata & DXD_WEAPONPREF) { - WeaponPref_Parse(&demo_p, p); + WeaponPref_Parse(&demobuf.p, p); //CONS_Printf("weaponpref is %d for player %d\n", i, p); } - p = READUINT8(demo_p); + p = READUINT8(demobuf.p); } while (p != DW_END) @@ -382,7 +382,7 @@ void G_ReadDemoExtraData(void) case DW_RNG: for (i = 0; i < PRNUMCLASS; i++) { - rng = READUINT32(demo_p); + rng = READUINT32(demobuf.p); if (P_GetRandSeed(i) != rng) { @@ -396,10 +396,10 @@ void G_ReadDemoExtraData(void) demosynced = storesynced; } - p = READUINT8(demo_p); + p = READUINT8(demobuf.p); } - if (!(demoflags & DF_GHOST) && *demo_p == DEMOMARKER) + if (!(demoflags & DF_GHOST) && *demobuf.p == DEMOMARKER) { // end of demo data stream G_CheckDemoStatus(); @@ -416,22 +416,22 @@ void G_WriteDemoExtraData(void) { if (demo_extradata[i]) { - WRITEUINT8(demo_p, i); - WRITEUINT8(demo_p, demo_extradata[i]); + WRITEUINT8(demobuf.p, i); + WRITEUINT8(demobuf.p, demo_extradata[i]); if (demo_extradata[i] & DXD_JOINDATA) { for (j = 0; j < MAXAVAILABILITY; j++) { - WRITEUINT8(demo_p, players[i].availabilities[i]); + WRITEUINT8(demobuf.p, players[i].availabilities[i]); } - WRITEUINT8(demo_p, (UINT8)players[i].bot); + WRITEUINT8(demobuf.p, (UINT8)players[i].bot); if (players[i].bot) { - WRITEUINT8(demo_p, players[i].botvars.difficulty); - WRITEUINT8(demo_p, players[i].botvars.diffincrease); // needed to avoid having to duplicate logic - WRITEUINT8(demo_p, (UINT8)players[i].botvars.rival); + WRITEUINT8(demobuf.p, players[i].botvars.difficulty); + WRITEUINT8(demobuf.p, players[i].botvars.diffincrease); // needed to avoid having to duplicate logic + WRITEUINT8(demobuf.p, (UINT8)players[i].botvars.rival); } } if (demo_extradata[i] & DXD_PLAYSTATE) @@ -452,29 +452,29 @@ void G_WriteDemoExtraData(void) pst = DXD_PST_SPECTATING; } - WRITEUINT8(demo_p, pst); + WRITEUINT8(demobuf.p, pst); } //if (demo_extradata[i] & DXD_RESPAWN) has no extra data if (demo_extradata[i] & DXD_SKIN) { // Skin - WRITEUINT8(demo_p, players[i].skin); + WRITEUINT8(demobuf.p, players[i].skin); } if (demo_extradata[i] & DXD_COLOR) { // Color memset(name, 0, 16); strncpy(name, skincolors[players[i].skincolor].name, 16); - M_Memcpy(demo_p,name,16); - demo_p += 16; + M_Memcpy(demobuf.p,name,16); + demobuf.p += 16; } if (demo_extradata[i] & DXD_NAME) { // Name memset(name, 0, 16); strncpy(name, player_names[i], 16); - M_Memcpy(demo_p,name,16); - demo_p += 16; + M_Memcpy(demobuf.p,name,16); + demobuf.p += 16; } if (demo_extradata[i] & DXD_FOLLOWER) { @@ -484,8 +484,8 @@ void G_WriteDemoExtraData(void) strncpy(name, "None", 16); else strncpy(name, followers[players[i].followerskin].name, 16); - M_Memcpy(demo_p, name, 16); - demo_p += 16; + M_Memcpy(demobuf.p, name, 16); + demobuf.p += 16; // write follower color memset(name, 0, 16); @@ -495,13 +495,13 @@ void G_WriteDemoExtraData(void) break; } strncpy(name, Followercolor_cons_t[j].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" - M_Memcpy(demo_p,name,16); - demo_p += 16; + M_Memcpy(demobuf.p,name,16); + demobuf.p += 16; } if (demo_extradata[i] & DXD_WEAPONPREF) { - WeaponPref_Save(&demo_p, i); + WeaponPref_Save(&demobuf.p, i); } } @@ -521,45 +521,45 @@ void G_WriteDemoExtraData(void) { demo_writerng = 0; timeout = 16; - WRITEUINT8(demo_p, DW_RNG); + WRITEUINT8(demobuf.p, DW_RNG); for (i = 0; i < PRNUMCLASS; i++) { - WRITEUINT32(demo_p, P_GetRandSeed(i)); + WRITEUINT32(demobuf.p, P_GetRandSeed(i)); } } } - WRITEUINT8(demo_p, DW_END); + WRITEUINT8(demobuf.p, DW_END); } void G_ReadDemoTiccmd(ticcmd_t *cmd, INT32 playernum) { UINT8 ziptic; - if (!demo_p || !demo.deferstart) + if (!demobuf.p || !demo.deferstart) return; - ziptic = READUINT8(demo_p); + ziptic = READUINT8(demobuf.p); if (ziptic & ZT_FWD) - oldcmd[playernum].forwardmove = READSINT8(demo_p); + oldcmd[playernum].forwardmove = READSINT8(demobuf.p); if (ziptic & ZT_TURNING) - oldcmd[playernum].turning = READINT16(demo_p); + oldcmd[playernum].turning = READINT16(demobuf.p); if (ziptic & ZT_THROWDIR) - oldcmd[playernum].throwdir = READINT16(demo_p); + oldcmd[playernum].throwdir = READINT16(demobuf.p); if (ziptic & ZT_BUTTONS) - oldcmd[playernum].buttons = READUINT16(demo_p); + oldcmd[playernum].buttons = READUINT16(demobuf.p); if (ziptic & ZT_AIMING) - oldcmd[playernum].aiming = READINT16(demo_p); + oldcmd[playernum].aiming = READINT16(demobuf.p); if (ziptic & ZT_LATENCY) - oldcmd[playernum].latency = READUINT8(demo_p); + oldcmd[playernum].latency = READUINT8(demobuf.p); if (ziptic & ZT_FLAGS) - oldcmd[playernum].flags = READUINT8(demo_p); + oldcmd[playernum].flags = READUINT8(demobuf.p); G_CopyTiccmd(cmd, &oldcmd[playernum], 1); - if (!(demoflags & DF_GHOST) && *demo_p == DEMOMARKER) + if (!(demoflags & DF_GHOST) && *demobuf.p == DEMOMARKER) { // end of demo data stream G_CheckDemoStatus(); @@ -573,55 +573,55 @@ void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum) UINT8 *ziptic_p; (void)playernum; - if (!demo_p) + if (!demobuf.p) return; - ziptic_p = demo_p++; // the ziptic, written at the end of this function + ziptic_p = demobuf.p++; // the ziptic, written at the end of this function if (cmd->forwardmove != oldcmd[playernum].forwardmove) { - WRITESINT8(demo_p,cmd->forwardmove); + WRITESINT8(demobuf.p,cmd->forwardmove); oldcmd[playernum].forwardmove = cmd->forwardmove; ziptic |= ZT_FWD; } if (cmd->turning != oldcmd[playernum].turning) { - WRITEINT16(demo_p,cmd->turning); + WRITEINT16(demobuf.p,cmd->turning); oldcmd[playernum].turning = cmd->turning; ziptic |= ZT_TURNING; } if (cmd->throwdir != oldcmd[playernum].throwdir) { - WRITEINT16(demo_p,cmd->throwdir); + WRITEINT16(demobuf.p,cmd->throwdir); oldcmd[playernum].throwdir = cmd->throwdir; ziptic |= ZT_THROWDIR; } if (cmd->buttons != oldcmd[playernum].buttons) { - WRITEUINT16(demo_p,cmd->buttons); + WRITEUINT16(demobuf.p,cmd->buttons); oldcmd[playernum].buttons = cmd->buttons; ziptic |= ZT_BUTTONS; } if (cmd->aiming != oldcmd[playernum].aiming) { - WRITEINT16(demo_p,cmd->aiming); + WRITEINT16(demobuf.p,cmd->aiming); oldcmd[playernum].aiming = cmd->aiming; ziptic |= ZT_AIMING; } if (cmd->latency != oldcmd[playernum].latency) { - WRITEUINT8(demo_p,cmd->latency); + WRITEUINT8(demobuf.p,cmd->latency); oldcmd[playernum].latency = cmd->latency; ziptic |= ZT_LATENCY; } if (cmd->flags != oldcmd[playernum].flags) { - WRITEUINT8(demo_p,cmd->flags); + WRITEUINT8(demobuf.p,cmd->flags); oldcmd[playernum].flags = cmd->flags; ziptic |= ZT_FLAGS; } @@ -696,14 +696,14 @@ void G_WriteAllGhostTics(void) if (multiplayer && ((counter % cv_netdemosyncquality.value) != 0)) // Only write 1 in this many ghost datas per tic to cut down on multiplayer replay size. continue; - WRITEUINT8(demo_p, i); + WRITEUINT8(demobuf.p, i); G_WriteGhostTic(players[i].mo, i); } - WRITEUINT8(demo_p, 0xFF); + WRITEUINT8(demobuf.p, 0xFF); // attention here for the ticcmd size! // latest demos with mouse aiming byte in ticcmd - if (demo_p >= demoend - (13 + 9 + 9)) + if (demobuf.p >= demoend - (13 + 9 + 9)) { G_CheckDemoStatus(); // no more space return; @@ -716,12 +716,12 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) UINT8 *ziptic_p; UINT32 i; - if (!demo_p) + if (!demobuf.p) return; if (!(demoflags & DF_GHOST)) return; // No ghost data to write. - ziptic_p = demo_p++; // the ziptic, written at the end of this function + ziptic_p = demobuf.p++; // the ziptic, written at the end of this function #define MAXMOM (0xFFFF<<8) @@ -735,9 +735,9 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) oldghost[playernum].y = ghost->y; oldghost[playernum].z = ghost->z; ziptic |= GZT_XYZ; - WRITEFIXED(demo_p,oldghost[playernum].x); - WRITEFIXED(demo_p,oldghost[playernum].y); - WRITEFIXED(demo_p,oldghost[playernum].z); + WRITEFIXED(demobuf.p,oldghost[playernum].x); + WRITEFIXED(demobuf.p,oldghost[playernum].y); + WRITEFIXED(demobuf.p,oldghost[playernum].z); } else { @@ -751,8 +751,8 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) oldghost[playernum].momx = momx; oldghost[playernum].momy = momy; ziptic |= GZT_MOMXY; - WRITEFIXED(demo_p,momx); - WRITEFIXED(demo_p,momy); + WRITEFIXED(demobuf.p,momx); + WRITEFIXED(demobuf.p,momy); } momx = ghost->z-oldghost[playernum].z; @@ -760,7 +760,7 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) { oldghost[playernum].momz = momx; ziptic |= GZT_MOMZ; - WRITEFIXED(demo_p,momx); + WRITEFIXED(demobuf.p,momx); } // This SHOULD set oldghost.x/y/z to match ghost->x/y/z @@ -778,7 +778,7 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) { oldghost[playernum].angle = ghost->player->drawangle>>24; ziptic |= GZT_ANGLE; - WRITEUINT8(demo_p,oldghost[playernum].angle); + WRITEUINT8(demobuf.p,oldghost[playernum].angle); } // Store the sprite frame. @@ -786,7 +786,7 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) { oldghost[playernum].frame = (ghost->frame & FF_FRAMEMASK); ziptic |= GZT_FRAME; - WRITEUINT8(demo_p,oldghost[playernum].frame); + WRITEUINT8(demobuf.p,oldghost[playernum].frame); } if (ghost->sprite == SPR_PLAY @@ -794,7 +794,7 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) { oldghost[playernum].sprite2 = ghost->sprite2; ziptic |= GZT_SPR2; - WRITEUINT8(demo_p,oldghost[playernum].sprite2); + WRITEUINT8(demobuf.p,oldghost[playernum].sprite2); } // Check for sprite set changes @@ -839,30 +839,30 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) if (ghostext[playernum].scale == ghostext[playernum].lastscale) ghostext[playernum].flags &= ~EZT_SCALE; - WRITEUINT8(demo_p,ghostext[playernum].flags); + WRITEUINT8(demobuf.p,ghostext[playernum].flags); if (ghostext[playernum].flags & EZT_COLOR) { - WRITEUINT16(demo_p,ghostext[playernum].color); + WRITEUINT16(demobuf.p,ghostext[playernum].color); ghostext[playernum].lastcolor = ghostext[playernum].color; } if (ghostext[playernum].flags & EZT_SCALE) { - WRITEFIXED(demo_p,ghostext[playernum].scale); + WRITEFIXED(demobuf.p,ghostext[playernum].scale); ghostext[playernum].lastscale = ghostext[playernum].scale; } if (ghostext[playernum].flags & EZT_HIT) { - WRITEUINT16(demo_p,ghostext[playernum].hits); + WRITEUINT16(demobuf.p,ghostext[playernum].hits); for (i = 0; i < ghostext[playernum].hits; i++) { mobj_t *mo = ghostext[playernum].hitlist[i]; - //WRITEUINT32(demo_p,UINT32_MAX); // reserved for some method of determining exactly which mobj this is. (mobjnum doesn't work here.) - WRITEUINT32(demo_p,mo->type); - WRITEUINT16(demo_p,(UINT16)mo->health); - WRITEFIXED(demo_p,mo->x); - WRITEFIXED(demo_p,mo->y); - WRITEFIXED(demo_p,mo->z); - WRITEANGLE(demo_p,mo->angle); + //WRITEUINT32(demobuf.p,UINT32_MAX); // reserved for some method of determining exactly which mobj this is. (mobjnum doesn't work here.) + WRITEUINT32(demobuf.p,mo->type); + WRITEUINT16(demobuf.p,(UINT16)mo->health); + WRITEFIXED(demobuf.p,mo->x); + WRITEFIXED(demobuf.p,mo->y); + WRITEFIXED(demobuf.p,mo->z); + WRITEANGLE(demobuf.p,mo->angle); P_SetTarget(ghostext[playernum].hitlist+i, NULL); } Z_Free(ghostext[playernum].hitlist); @@ -870,19 +870,19 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) ghostext[playernum].hitlist = NULL; } if (ghostext[playernum].flags & EZT_SPRITE) - WRITEUINT16(demo_p,oldghost[playernum].sprite); + WRITEUINT16(demobuf.p,oldghost[playernum].sprite); if (ghostext[playernum].flags & EZT_ITEMDATA) { - WRITESINT8(demo_p, ghostext[playernum].itemtype); - WRITEUINT8(demo_p, ghostext[playernum].itemamount); - WRITEUINT8(demo_p, ghostext[playernum].bumpers); + WRITESINT8(demobuf.p, ghostext[playernum].itemtype); + WRITEUINT8(demobuf.p, ghostext[playernum].itemamount); + WRITEUINT8(demobuf.p, ghostext[playernum].bumpers); } if (ghostext[playernum].flags & EZT_STATDATA) { - WRITEUINT8(demo_p,ghostext[playernum].skinid); - WRITEUINT8(demo_p,ghostext[playernum].kartspeed); - WRITEUINT8(demo_p,ghostext[playernum].kartweight); - WRITEUINT32(demo_p, ghostext[playernum].charflags); + WRITEUINT8(demobuf.p,ghostext[playernum].skinid); + WRITEUINT8(demobuf.p,ghostext[playernum].kartspeed); + WRITEUINT8(demobuf.p,ghostext[playernum].kartweight); + WRITEUINT32(demobuf.p, ghostext[playernum].charflags); } ghostext[playernum].flags = 0; @@ -891,7 +891,7 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) if (ghost->player && ghost->player->followmobj&& !(ghost->player->followmobj->sprite == SPR_NULL || (ghost->player->followmobj->renderflags & RF_DONTDRAW) == RF_DONTDRAW)) // bloats tails runs but what can ya do { fixed_t temp; - UINT8 *followtic_p = demo_p++; + UINT8 *followtic_p = demobuf.p++; UINT8 followtic = 0; ziptic |= GZT_FOLLOW; @@ -902,33 +902,33 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) if (!(oldghost[playernum].flags2 & MF2_AMBUSH)) { followtic |= FZT_SPAWNED; - WRITEINT16(demo_p,ghost->player->followmobj->info->height>>FRACBITS); + WRITEINT16(demobuf.p,ghost->player->followmobj->info->height>>FRACBITS); if (ghost->player->followmobj->flags2 & MF2_LINKDRAW) followtic |= FZT_LINKDRAW; if (ghost->player->followmobj->colorized) followtic |= FZT_COLORIZED; if (followtic & FZT_SKIN) - WRITEUINT8(demo_p,(UINT8)(((skin_t *)(ghost->player->followmobj->skin))-skins)); + WRITEUINT8(demobuf.p,(UINT8)(((skin_t *)(ghost->player->followmobj->skin))-skins)); oldghost[playernum].flags2 |= MF2_AMBUSH; } if (ghost->player->followmobj->scale != ghost->scale) { followtic |= FZT_SCALE; - WRITEFIXED(demo_p,ghost->player->followmobj->scale); + WRITEFIXED(demobuf.p,ghost->player->followmobj->scale); } temp = ghost->player->followmobj->x-ghost->x; - WRITEFIXED(demo_p,temp); + WRITEFIXED(demobuf.p,temp); temp = ghost->player->followmobj->y-ghost->y; - WRITEFIXED(demo_p,temp); + WRITEFIXED(demobuf.p,temp); temp = ghost->player->followmobj->z-ghost->z; - WRITEFIXED(demo_p,temp); + WRITEFIXED(demobuf.p,temp); if (followtic & FZT_SKIN) - WRITEUINT8(demo_p,ghost->player->followmobj->sprite2); - WRITEUINT16(demo_p,ghost->player->followmobj->sprite); - WRITEUINT8(demo_p,(ghost->player->followmobj->frame & FF_FRAMEMASK)); - WRITEUINT16(demo_p,ghost->player->followmobj->color); + WRITEUINT8(demobuf.p,ghost->player->followmobj->sprite2); + WRITEUINT16(demobuf.p,ghost->player->followmobj->sprite); + WRITEUINT8(demobuf.p,(ghost->player->followmobj->frame & FF_FRAMEMASK)); + WRITEUINT16(demobuf.p,ghost->player->followmobj->color); *followtic_p = followtic; } @@ -942,18 +942,18 @@ void G_ConsAllGhostTics(void) { UINT8 p; - if (!demo_p || !demo.deferstart) + if (!demobuf.p || !demo.deferstart) return; - p = READUINT8(demo_p); + p = READUINT8(demobuf.p); while (p != 0xFF) { G_ConsGhostTic(p); - p = READUINT8(demo_p); + p = READUINT8(demobuf.p); } - if (*demo_p == DEMOMARKER) + if (*demobuf.p == DEMOMARKER) { // end of demo data stream G_CheckDemoStatus(); @@ -976,45 +976,45 @@ void G_ConsGhostTic(INT32 playernum) testmo = players[playernum].mo; // Grab ghost data. - ziptic = READUINT8(demo_p); + ziptic = READUINT8(demobuf.p); if (ziptic & GZT_XYZ) { - oldghost[playernum].x = READFIXED(demo_p); - oldghost[playernum].y = READFIXED(demo_p); - oldghost[playernum].z = READFIXED(demo_p); + oldghost[playernum].x = READFIXED(demobuf.p); + oldghost[playernum].y = READFIXED(demobuf.p); + oldghost[playernum].z = READFIXED(demobuf.p); syncleeway = 0; } else { if (ziptic & GZT_MOMXY) { - oldghost[playernum].momx = READFIXED(demo_p); - oldghost[playernum].momy = READFIXED(demo_p); + oldghost[playernum].momx = READFIXED(demobuf.p); + oldghost[playernum].momy = READFIXED(demobuf.p); } if (ziptic & GZT_MOMZ) - oldghost[playernum].momz = READFIXED(demo_p); + oldghost[playernum].momz = READFIXED(demobuf.p); oldghost[playernum].x += oldghost[playernum].momx; oldghost[playernum].y += oldghost[playernum].momy; oldghost[playernum].z += oldghost[playernum].momz; syncleeway = FRACUNIT; } if (ziptic & GZT_ANGLE) - demo_p++; + demobuf.p++; if (ziptic & GZT_FRAME) - demo_p++; + demobuf.p++; if (ziptic & GZT_SPR2) - demo_p++; + demobuf.p++; if (ziptic & GZT_EXTRA) { // But wait, there's more! - UINT8 xziptic = READUINT8(demo_p); + UINT8 xziptic = READUINT8(demobuf.p); if (xziptic & EZT_COLOR) - demo_p += sizeof(UINT16); + demobuf.p += sizeof(UINT16); if (xziptic & EZT_SCALE) - demo_p += sizeof(fixed_t); + demobuf.p += sizeof(fixed_t); if (xziptic & EZT_HIT) { // Resync mob damage. - UINT16 i, count = READUINT16(demo_p); + UINT16 i, count = READUINT16(demobuf.p); thinker_t *th; mobj_t *mobj; @@ -1026,13 +1026,13 @@ void G_ConsGhostTic(INT32 playernum) for (i = 0; i < count; i++) { - //demo_p += 4; // reserved. - type = READUINT32(demo_p); - health = READUINT16(demo_p); - x = READFIXED(demo_p); - y = READFIXED(demo_p); - z = READFIXED(demo_p); - demo_p += sizeof(angle_t); // angle, unnecessary for cons. + //demobuf.p += 4; // reserved. + type = READUINT32(demobuf.p); + health = READUINT16(demobuf.p); + x = READFIXED(demobuf.p); + y = READFIXED(demobuf.p); + z = READFIXED(demobuf.p); + demobuf.p += sizeof(angle_t); // angle, unnecessary for cons. mobj = NULL; for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) @@ -1053,42 +1053,42 @@ void G_ConsGhostTic(INT32 playernum) } } if (xziptic & EZT_SPRITE) - demo_p += sizeof(UINT16); + demobuf.p += sizeof(UINT16); if (xziptic & EZT_ITEMDATA) { - ghostext[playernum].itemtype = READSINT8(demo_p); - ghostext[playernum].itemamount = READUINT8(demo_p); - ghostext[playernum].bumpers = READUINT8(demo_p); + ghostext[playernum].itemtype = READSINT8(demobuf.p); + ghostext[playernum].itemamount = READUINT8(demobuf.p); + ghostext[playernum].bumpers = READUINT8(demobuf.p); } if (xziptic & EZT_STATDATA) { - ghostext[playernum].skinid = READUINT8(demo_p); + ghostext[playernum].skinid = READUINT8(demobuf.p); if (ghostext[playernum].skinid >= demo.numskins) ghostext[playernum].skinid = 0; - ghostext[playernum].kartspeed = READUINT8(demo_p); - ghostext[playernum].kartweight = READUINT8(demo_p); - ghostext[playernum].charflags = READUINT32(demo_p); + ghostext[playernum].kartspeed = READUINT8(demobuf.p); + ghostext[playernum].kartweight = READUINT8(demobuf.p); + ghostext[playernum].charflags = READUINT32(demobuf.p); } } if (ziptic & GZT_FOLLOW) { // Even more... - UINT8 followtic = READUINT8(demo_p); + UINT8 followtic = READUINT8(demobuf.p); if (followtic & FZT_SPAWNED) { - demo_p += sizeof(INT16); + demobuf.p += sizeof(INT16); if (followtic & FZT_SKIN) - demo_p++; + demobuf.p++; } if (followtic & FZT_SCALE) - demo_p += sizeof(fixed_t); + demobuf.p += sizeof(fixed_t); // momx, momy and momz - demo_p += sizeof(fixed_t) * 3; + demobuf.p += sizeof(fixed_t) * 3; if (followtic & FZT_SKIN) - demo_p++; - demo_p += sizeof(UINT16); - demo_p++; - demo_p += sizeof(UINT16); + demobuf.p++; + demobuf.p += sizeof(UINT16); + demobuf.p++; + demobuf.p += sizeof(UINT16); } if (testmo) @@ -1164,7 +1164,7 @@ void G_ConsGhostTic(INT32 playernum) } } - if (*demo_p == DEMOMARKER) + if (*demobuf.p == DEMOMARKER) { // end of demo data stream G_CheckDemoStatus(); @@ -1632,7 +1632,7 @@ void G_ConfirmRewind(tic_t rewindtime) if (rewind) { - demo_p = demobuffer + rewind->demopos; + demobuf.p = demobuf.buffer + rewind->demopos; memcpy(oldcmd, rewind->oldcmd, sizeof (oldcmd)); memcpy(oldghost, rewind->oldghost, sizeof (oldghost)); paused = false; @@ -1841,11 +1841,11 @@ void G_WriteMetalTic(mobj_t *metal) UINT8 ziptic = 0; UINT8 *ziptic_p; - if (!demo_p) // demo_p will be NULL until the race start linedef executor is activated! + if (!demobuf.p) // demobuf.p will be NULL until the race start linedef executor is activated! return; - WRITEUINT8(demo_p, METALSNICE); - ziptic_p = demo_p++; // the ziptic, written at the end of this function + WRITEUINT8(demobuf.p, METALSNICE); + ziptic_p = demobuf.p++; // the ziptic, written at the end of this function #define MAXMOM (0xFFFF<<8) @@ -1858,9 +1858,9 @@ void G_WriteMetalTic(mobj_t *metal) oldmetal.y = metal->y; oldmetal.z = metal->z; ziptic |= GZT_XYZ; - WRITEFIXED(demo_p,oldmetal.x); - WRITEFIXED(demo_p,oldmetal.y); - WRITEFIXED(demo_p,oldmetal.z); + WRITEFIXED(demobuf.p,oldmetal.x); + WRITEFIXED(demobuf.p,oldmetal.y); + WRITEFIXED(demobuf.p,oldmetal.z); } else { @@ -1874,15 +1874,15 @@ void G_WriteMetalTic(mobj_t *metal) oldmetal.momx = momx; oldmetal.momy = momy; ziptic |= GZT_MOMXY; - WRITEFIXED(demo_p,momx); - WRITEFIXED(demo_p,momy); + WRITEFIXED(demobuf.p,momx); + WRITEFIXED(demobuf.p,momy); } momx = metal->z-oldmetal.z; if (momx != oldmetal.momz) { oldmetal.momz = momx; ziptic |= GZT_MOMZ; - WRITEFIXED(demo_p,momx); + WRITEFIXED(demobuf.p,momx); } // This SHOULD set oldmetal.x/y/z to match metal->x/y/z @@ -1900,7 +1900,7 @@ void G_WriteMetalTic(mobj_t *metal) { oldmetal.angle = metal->player->drawangle>>24; ziptic |= GZT_ANGLE; - WRITEUINT8(demo_p,oldmetal.angle); + WRITEUINT8(demobuf.p,oldmetal.angle); } // Store the sprite frame. @@ -1908,7 +1908,7 @@ void G_WriteMetalTic(mobj_t *metal) { oldmetal.frame = metal->frame; // NOT & FF_FRAMEMASK here, so 32 bits ziptic |= GZT_FRAME; - WRITEUINT32(demo_p,oldmetal.frame); + WRITEUINT32(demobuf.p,oldmetal.frame); } if (metal->sprite == SPR_PLAY @@ -1916,7 +1916,7 @@ void G_WriteMetalTic(mobj_t *metal) { oldmetal.sprite2 = metal->sprite2; ziptic |= GZT_SPR2; - WRITEUINT8(demo_p,oldmetal.sprite2); + WRITEUINT8(demobuf.p,oldmetal.sprite2); } // Check for sprite set changes @@ -1933,21 +1933,21 @@ void G_WriteMetalTic(mobj_t *metal) if (ghostext[0].scale == ghostext[0].lastscale) ghostext[0].flags &= ~EZT_SCALE; - WRITEUINT8(demo_p,ghostext[0].flags); + WRITEUINT8(demobuf.p,ghostext[0].flags); if (ghostext[0].flags & EZT_SCALE) { - WRITEFIXED(demo_p,ghostext[0].scale); + WRITEFIXED(demobuf.p,ghostext[0].scale); ghostext[0].lastscale = ghostext[0].scale; } if (ghostext[0].flags & EZT_SPRITE) - WRITEUINT16(demo_p,oldmetal.sprite); + WRITEUINT16(demobuf.p,oldmetal.sprite); ghostext[0].flags = 0; } if (metal->player && metal->player->followmobj && !(metal->player->followmobj->sprite == SPR_NULL || (metal->player->followmobj->renderflags & RF_DONTDRAW) == RF_DONTDRAW)) { fixed_t temp; - UINT8 *followtic_p = demo_p++; + UINT8 *followtic_p = demobuf.p++; UINT8 followtic = 0; ziptic |= GZT_FOLLOW; @@ -1958,33 +1958,33 @@ void G_WriteMetalTic(mobj_t *metal) if (!(oldmetal.flags2 & MF2_AMBUSH)) { followtic |= FZT_SPAWNED; - WRITEINT16(demo_p,metal->player->followmobj->info->height>>FRACBITS); + WRITEINT16(demobuf.p,metal->player->followmobj->info->height>>FRACBITS); if (metal->player->followmobj->flags2 & MF2_LINKDRAW) followtic |= FZT_LINKDRAW; if (metal->player->followmobj->colorized) followtic |= FZT_COLORIZED; if (followtic & FZT_SKIN) - WRITEUINT8(demo_p,(UINT8)(((skin_t *)(metal->player->followmobj->skin))-skins)); + WRITEUINT8(demobuf.p,(UINT8)(((skin_t *)(metal->player->followmobj->skin))-skins)); oldmetal.flags2 |= MF2_AMBUSH; } if (metal->player->followmobj->scale != metal->scale) { followtic |= FZT_SCALE; - WRITEFIXED(demo_p,metal->player->followmobj->scale); + WRITEFIXED(demobuf.p,metal->player->followmobj->scale); } temp = metal->player->followmobj->x-metal->x; - WRITEFIXED(demo_p,temp); + WRITEFIXED(demobuf.p,temp); temp = metal->player->followmobj->y-metal->y; - WRITEFIXED(demo_p,temp); + WRITEFIXED(demobuf.p,temp); temp = metal->player->followmobj->z-metal->z; - WRITEFIXED(demo_p,temp); + WRITEFIXED(demobuf.p,temp); if (followtic & FZT_SKIN) - WRITEUINT8(demo_p,metal->player->followmobj->sprite2); - WRITEUINT16(demo_p,metal->player->followmobj->sprite); - WRITEUINT32(demo_p,metal->player->followmobj->frame); // NOT & FF_FRAMEMASK here, so 32 bits - WRITEUINT16(demo_p,metal->player->followmobj->color); + WRITEUINT8(demobuf.p,metal->player->followmobj->sprite2); + WRITEUINT16(demobuf.p,metal->player->followmobj->sprite); + WRITEUINT32(demobuf.p,metal->player->followmobj->frame); // NOT & FF_FRAMEMASK here, so 32 bits + WRITEUINT16(demobuf.p,metal->player->followmobj->color); *followtic_p = followtic; } @@ -1995,7 +1995,7 @@ void G_WriteMetalTic(mobj_t *metal) // attention here for the ticcmd size! // latest demos with mouse aiming byte in ticcmd - if (demo_p >= demoend - 32) + if (demobuf.p >= demoend - 32) { G_StopMetalRecording(false); // no more space return; @@ -2015,11 +2015,11 @@ void G_RecordDemo(const char *name) maxsize = 1024*1024*2; if (M_CheckParm("-maxdemo") && M_IsNextParm()) maxsize = atoi(M_GetNextParm()) * 1024; -// if (demobuffer) -// free(demobuffer); - demo_p = NULL; - demobuffer = malloc(maxsize); - demoend = demobuffer + maxsize; +// if (demobuf.buffer) +// free(demobuf.buffer); + demobuf.p = NULL; + demobuf.buffer = malloc(maxsize); + demoend = demobuf.buffer + maxsize; demo.recording = true; } @@ -2030,9 +2030,9 @@ void G_RecordMetal(void) maxsize = 1024*1024; if (M_CheckParm("-maxdemo") && M_IsNextParm()) maxsize = atoi(M_GetNextParm()) * 1024; - demo_p = NULL; - demobuffer = malloc(maxsize); - demoend = demobuffer + maxsize; + demobuf.p = NULL; + demobuf.buffer = malloc(maxsize); + demoend = demobuf.buffer + maxsize; metalrecording = true; } @@ -2334,11 +2334,11 @@ void G_BeginRecording(void) char name[MAXCOLORNAME+1]; player_t *player = &players[consoleplayer]; - if (demo_p) + if (demobuf.p) return; memset(name,0,sizeof(name)); - demo_p = demobuffer; + demobuf.p = demobuf.buffer; demoflags = DF_GHOST|(multiplayer ? DF_MULTIPLAYER : (modeattacking<lumpname, MAXMAPLUMPNAME); - M_Memcpy(demo_p, mapmd5, 16); demo_p += 16; + M_Memcpy(demobuf.p, "PLAY", 4); demobuf.p += 4; + WRITESTRINGN(demobuf.p, mapheaderinfo[gamemap-1]->lumpname, MAXMAPLUMPNAME); + M_Memcpy(demobuf.p, mapmd5, 16); demobuf.p += 16; - WRITEUINT8(demo_p, demoflags); - WRITEUINT8(demo_p, gametype & 0xFF); - WRITEUINT8(demo_p, numlaps); + WRITEUINT8(demobuf.p, demoflags); + WRITEUINT8(demobuf.p, gametype & 0xFF); + WRITEUINT8(demobuf.p, numlaps); // file list - G_SaveDemoExtraFiles(&demo_p); + G_SaveDemoExtraFiles(&demobuf.p); // character list - G_SaveDemoSkins(&demo_p); + G_SaveDemoSkins(&demobuf.p); switch ((demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) { case ATTACKING_NONE: // 0 break; case ATTACKING_TIME: // 1 - demotime_p = demo_p; - WRITEUINT32(demo_p,UINT32_MAX); // time - WRITEUINT32(demo_p,UINT32_MAX); // lap + demotime_p = demobuf.p; + WRITEUINT32(demobuf.p,UINT32_MAX); // time + WRITEUINT32(demobuf.p,UINT32_MAX); // lap break; case ATTACKING_CAPSULES: // 2 - demotime_p = demo_p; - WRITEUINT32(demo_p,UINT32_MAX); // time + demotime_p = demobuf.p; + WRITEUINT32(demobuf.p,UINT32_MAX); // time break; default: // 3 break; @@ -2401,15 +2401,15 @@ void G_BeginRecording(void) for (i = 0; i < PRNUMCLASS; i++) { - WRITEUINT32(demo_p, P_GetInitSeed(i)); + WRITEUINT32(demobuf.p, P_GetInitSeed(i)); } // Reserved for extrainfo location from start of file - demoinfo_p = demo_p; - WRITEUINT32(demo_p, 0); + demoinfo_p = demobuf.p; + WRITEUINT32(demobuf.p, 0); // Save netvar data - CV_SaveDemoVars(&demo_p); + CV_SaveDemoVars(&demobuf.p); // Now store some info for each in-game player @@ -2421,7 +2421,7 @@ void G_BeginRecording(void) for (p = 0; p < MAXPLAYERS; p++) { if (playeringame[p]) { player = &players[p]; - WRITEUINT8(demo_p, p); + WRITEUINT8(demobuf.p, p); i = 0; if (player->spectator == true) @@ -2432,35 +2432,35 @@ void G_BeginRecording(void) i |= DEMO_SHRINKME; if (player->bot == true) i |= DEMO_BOT; - WRITEUINT8(demo_p, i); + WRITEUINT8(demobuf.p, i); if (i & DEMO_BOT) { - WRITEUINT8(demo_p, player->botvars.difficulty); - WRITEUINT8(demo_p, player->botvars.diffincrease); // needed to avoid having to duplicate logic - WRITEUINT8(demo_p, (UINT8)player->botvars.rival); + WRITEUINT8(demobuf.p, player->botvars.difficulty); + WRITEUINT8(demobuf.p, player->botvars.diffincrease); // needed to avoid having to duplicate logic + WRITEUINT8(demobuf.p, (UINT8)player->botvars.rival); } // Name memset(name, 0, 16); strncpy(name, player_names[p], 16); - M_Memcpy(demo_p,name,16); - demo_p += 16; + M_Memcpy(demobuf.p,name,16); + demobuf.p += 16; for (j = 0; j < MAXAVAILABILITY; j++) { - WRITEUINT8(demo_p, player->availabilities[j]); + WRITEUINT8(demobuf.p, player->availabilities[j]); } // Skin (now index into demo.skinlist) - WRITEUINT8(demo_p, player->skin); - WRITEUINT8(demo_p, player->lastfakeskin); + WRITEUINT8(demobuf.p, player->skin); + WRITEUINT8(demobuf.p, player->lastfakeskin); // Color memset(name, 0, 16); strncpy(name, skincolors[player->skincolor].name, 16); - M_Memcpy(demo_p,name,16); - demo_p += 16; + M_Memcpy(demobuf.p,name,16); + demobuf.p += 16; // Save follower's skin name // PS: We must check for 'follower' to determine if the followerskin is valid. It's going to be 0 if we don't have a follower, but 0 is also absolutely a valid follower! @@ -2472,8 +2472,8 @@ void G_BeginRecording(void) else strncpy(name, "None", 16); // Say we don't have one, then. - M_Memcpy(demo_p,name,16); - demo_p += 16; + M_Memcpy(demobuf.p,name,16); + demobuf.p += 16; // Save follower's colour memset(name, 0, 16); @@ -2483,26 +2483,26 @@ void G_BeginRecording(void) break; } strncpy(name, Followercolor_cons_t[j].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" - M_Memcpy(demo_p, name, 16); - demo_p += 16; + M_Memcpy(demobuf.p, name, 16); + demobuf.p += 16; // Score, since Kart uses this to determine where you start on the map - WRITEUINT32(demo_p, player->score); + WRITEUINT32(demobuf.p, player->score); // Power Levels j = gametype == GT_BATTLE ? PWRLV_BATTLE : PWRLV_RACE; - WRITEUINT16(demo_p, clientpowerlevels[p][j]); + WRITEUINT16(demobuf.p, clientpowerlevels[p][j]); // And mobjtype_t is best with UINT32 too... - WRITEUINT32(demo_p, player->followitem); + WRITEUINT32(demobuf.p, player->followitem); } } - WRITEUINT8(demo_p, 0xFF); // Denote the end of the player listing + WRITEUINT8(demobuf.p, 0xFF); // Denote the end of the player listing // player lua vars, always saved even if empty if (demoflags & DF_LUAVARS) - LUA_Archive(&demo_p, false); + LUA_Archive(&demobuf.p, false); memset(&oldcmd,0,sizeof(oldcmd)); memset(&oldghost,0,sizeof(oldghost)); @@ -2536,22 +2536,22 @@ void G_BeginMetal(void) mobj_t *mo = players[consoleplayer].mo; #if 0 - if (demo_p) + if (demobuf.p) return; #endif - demo_p = demobuffer; + demobuf.p = demobuf.buffer; // Write header. - M_Memcpy(demo_p, DEMOHEADER, 12); demo_p += 12; - WRITEUINT8(demo_p,VERSION); - WRITEUINT8(demo_p,SUBVERSION); - WRITEUINT16(demo_p,DEMOVERSION); + M_Memcpy(demobuf.p, DEMOHEADER, 12); demobuf.p += 12; + WRITEUINT8(demobuf.p,VERSION); + WRITEUINT8(demobuf.p,SUBVERSION); + WRITEUINT16(demobuf.p,DEMOVERSION); // demo checksum - demo_p += 16; + demobuf.p += 16; - M_Memcpy(demo_p, "METL", 4); demo_p += 4; + M_Memcpy(demobuf.p, "METL", 4); demobuf.p += 4; memset(&ghostext,0,sizeof(ghostext)); ghostext[0].lastscale = ghostext[0].scale = FRACUNIT; @@ -2570,30 +2570,30 @@ void G_WriteStanding(UINT8 ranking, char *name, INT32 skinnum, UINT16 color, UIN if (demoinfo_p && *(UINT32 *)demoinfo_p == 0) { - WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker - *(UINT32 *)demoinfo_p = demo_p - demobuffer; + WRITEUINT8(demobuf.p, DEMOMARKER); // add the demo end marker + *(UINT32 *)demoinfo_p = demobuf.p - demobuf.buffer; } - WRITEUINT8(demo_p, DW_STANDING); - WRITEUINT8(demo_p, ranking); + WRITEUINT8(demobuf.p, DW_STANDING); + WRITEUINT8(demobuf.p, ranking); // Name memset(temp, 0, 16); strncpy(temp, name, 16); - M_Memcpy(demo_p,temp,16); - demo_p += 16; + M_Memcpy(demobuf.p,temp,16); + demobuf.p += 16; // Skin - WRITEUINT8(demo_p, skinnum); + WRITEUINT8(demobuf.p, skinnum); // Color memset(temp, 0, 16); strncpy(temp, skincolors[color].name, 16); - M_Memcpy(demo_p,temp,16); - demo_p += 16; + M_Memcpy(demobuf.p,temp,16); + demobuf.p += 16; // Score/time/whatever - WRITEUINT32(demo_p, val); + WRITEUINT32(demobuf.p, val); } void G_SetDemoTime(UINT32 ptime, UINT32 plap) @@ -2957,7 +2957,7 @@ void G_DoPlayDemo(char *defdemoname) // No demo name means we're restarting the current demo if (defdemoname == NULL) { - demo_p = demobuffer; + demobuf.p = demobuf.buffer; pdemoname = ZZ_Alloc(1); // Easier than adding checks for this everywhere it's freed } else @@ -2976,7 +2976,7 @@ void G_DoPlayDemo(char *defdemoname) if (FIL_CheckExtension(defdemoname)) { //FIL_DefaultExtension(defdemoname, ".lmp"); - if (!FIL_ReadFile(defdemoname, &demobuffer)) + if (!FIL_ReadFile(defdemoname, &demobuf.buffer)) { snprintf(msg, 1024, M_GetText("Failed to read file '%s'.\n"), defdemoname); CONS_Alert(CONS_ERROR, "%s", msg); @@ -2984,7 +2984,7 @@ void G_DoPlayDemo(char *defdemoname) M_StartMessage(msg, NULL, MM_NOTHING); return; } - demo_p = demobuffer; + demobuf.p = demobuf.buffer; } // load demo resource from WAD else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR) @@ -2997,7 +2997,7 @@ void G_DoPlayDemo(char *defdemoname) } else // it's an internal demo { - demobuffer = demo_p = W_CacheLumpNum(l, PU_STATIC); + demobuf.buffer = demobuf.p = W_CacheLumpNum(l, PU_STATIC); #if defined(SKIPERRORS) && !defined(DEVELOP) skiperrors = true; // SRB2Kart: Don't print warnings for staff ghosts, since they'll inevitably happen when we make bugfixes/changes... #endif @@ -3007,22 +3007,22 @@ void G_DoPlayDemo(char *defdemoname) // read demo header gameaction = ga_nothing; demo.playback = true; - if (memcmp(demo_p, DEMOHEADER, 12)) + if (memcmp(demobuf.p, DEMOHEADER, 12)) { snprintf(msg, 1024, M_GetText("%s is not a Ring Racers replay file.\n"), pdemoname); CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - Z_Free(demobuffer); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; } - demo_p += 12; // DEMOHEADER + demobuf.p += 12; // DEMOHEADER - version = READUINT8(demo_p); - subversion = READUINT8(demo_p); - demo.version = READUINT16(demo_p); + version = READUINT8(demobuf.p); + subversion = READUINT8(demobuf.p); + demo.version = READUINT16(demobuf.p); switch(demo.version) { case DEMOVERSION: // latest always supported @@ -3033,48 +3033,48 @@ void G_DoPlayDemo(char *defdemoname) CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - Z_Free(demobuffer); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; } // demo title - M_Memcpy(demo.titlename, demo_p, 64); - demo_p += 64; + M_Memcpy(demo.titlename, demobuf.p, 64); + demobuf.p += 64; - demo_p += 16; // demo checksum + demobuf.p += 16; // demo checksum - if (memcmp(demo_p, "PLAY", 4)) + if (memcmp(demobuf.p, "PLAY", 4)) { snprintf(msg, 1024, M_GetText("%s is the wrong type of recording and cannot be played.\n"), pdemoname); CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - Z_Free(demobuffer); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; } - demo_p += 4; // "PLAY" - READSTRINGN(demo_p, mapname, sizeof(mapname)); // gamemap + demobuf.p += 4; // "PLAY" + READSTRINGN(demobuf.p, mapname, sizeof(mapname)); // gamemap gamemap = G_MapNumber(mapname)+1; - demo_p += 16; // mapmd5 + demobuf.p += 16; // mapmd5 - demoflags = READUINT8(demo_p); - gametype = READUINT8(demo_p); + demoflags = READUINT8(demobuf.p); + gametype = READUINT8(demobuf.p); G_SetGametype(gametype); - numlaps = READUINT8(demo_p); + numlaps = READUINT8(demobuf.p); if (demo.title) // Titledemos should always play and ought to always be compatible with whatever wadlist is running. - G_SkipDemoExtraFiles(&demo_p); + G_SkipDemoExtraFiles(&demobuf.p); else if (demo.loadfiles) - G_LoadDemoExtraFiles(&demo_p); + G_LoadDemoExtraFiles(&demobuf.p); else if (demo.ignorefiles) - G_SkipDemoExtraFiles(&demo_p); + G_SkipDemoExtraFiles(&demobuf.p); else { - UINT8 error = G_CheckDemoExtraFiles(&demo_p, false); + UINT8 error = G_CheckDemoExtraFiles(&demobuf.p, false); if (error) { @@ -3115,7 +3115,7 @@ void G_DoPlayDemo(char *defdemoname) if (!CON_Ready()) // In the console they'll just see the notice there! No point pulling them out. M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - Z_Free(demobuffer); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3123,14 +3123,14 @@ void G_DoPlayDemo(char *defdemoname) } // character list - demo.skinlist = G_LoadDemoSkins(&demo_p, &demo.numskins, true); + demo.skinlist = G_LoadDemoSkins(&demobuf.p, &demo.numskins, true); if (!demo.skinlist) { snprintf(msg, 1024, M_GetText("%s has an invalid skin list and cannot be played.\n"), pdemoname); CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - Z_Free(demobuffer); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3149,11 +3149,11 @@ void G_DoPlayDemo(char *defdemoname) case ATTACKING_NONE: // 0 break; case ATTACKING_TIME: // 1 - hu_demotime = READUINT32(demo_p); - hu_demolap = READUINT32(demo_p); + hu_demotime = READUINT32(demobuf.p); + hu_demolap = READUINT32(demobuf.p); break; case ATTACKING_CAPSULES: // 2 - hu_demotime = READUINT32(demo_p); + hu_demotime = READUINT32(demobuf.p); break; default: // 3 modeattacking = ATTACKING_NONE; @@ -3163,10 +3163,10 @@ void G_DoPlayDemo(char *defdemoname) // Random seed for (i = 0; i < PRNUMCLASS; i++) { - randseed[i] = READUINT32(demo_p); + randseed[i] = READUINT32(demobuf.p); } - demo_p += 4; // Extrainfo location + demobuf.p += 4; // Extrainfo location // ...*map* not loaded? if (!gamemap || (gamemap > nummapheaders) || !mapheaderinfo[gamemap-1] || mapheaderinfo[gamemap-1]->lumpnum == LUMPERROR) @@ -3177,17 +3177,17 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - Z_Free(demobuffer); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; } // net var data - CV_LoadDemoVars(&demo_p); + CV_LoadDemoVars(&demobuf.p); // Sigh ... it's an empty demo. - if (*demo_p == DEMOMARKER) + if (*demobuf.p == DEMOMARKER) { snprintf(msg, 1024, M_GetText("%s contains no data to be played.\n"), pdemoname); CONS_Alert(CONS_ERROR, "%s", msg); @@ -3195,7 +3195,7 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - Z_Free(demobuffer); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3228,14 +3228,14 @@ void G_DoPlayDemo(char *defdemoname) memset(playeringame,0,sizeof(playeringame)); // Load players that were in-game when the map started - p = READUINT8(demo_p); + p = READUINT8(demobuf.p); for (i = 1; i < MAXSPLITSCREENPLAYERS; i++) displayplayers[i] = INT32_MAX; while (p != 0xFF) { - UINT8 flags = READUINT8(demo_p); + UINT8 flags = READUINT8(demobuf.p); spectator = !!(flags & DEMO_SPECTATOR); bot = !!(flags & DEMO_BOT); @@ -3250,7 +3250,7 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - Z_Free(demobuffer); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3268,7 +3268,7 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - Z_Free(demobuffer); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3292,35 +3292,35 @@ void G_DoPlayDemo(char *defdemoname) if ((players[p].bot = bot) == true) { - players[p].botvars.difficulty = READUINT8(demo_p); - players[p].botvars.diffincrease = READUINT8(demo_p); // needed to avoid having to duplicate logic - players[p].botvars.rival = (boolean)READUINT8(demo_p); + players[p].botvars.difficulty = READUINT8(demobuf.p); + players[p].botvars.diffincrease = READUINT8(demobuf.p); // needed to avoid having to duplicate logic + players[p].botvars.rival = (boolean)READUINT8(demobuf.p); } K_UpdateShrinkCheat(&players[p]); // Name - M_Memcpy(player_names[p],demo_p,16); - demo_p += 16; + M_Memcpy(player_names[p],demobuf.p,16); + demobuf.p += 16; for (i = 0; i < MAXAVAILABILITY; i++) { - availabilities[p][i] = READUINT8(demo_p); + availabilities[p][i] = READUINT8(demobuf.p); } // Skin - i = READUINT8(demo_p); + i = READUINT8(demobuf.p); if (i >= demo.numskins) i = 0; SetPlayerSkinByNum(p, demo.skinlist[i].mapping); demo.currentskinid[p] = ghostext[p].skinid = i; - lastfakeskin[p] = READUINT8(demo_p); + lastfakeskin[p] = READUINT8(demobuf.p); // Color - M_Memcpy(color,demo_p,16); - demo_p += 16; + M_Memcpy(color,demobuf.p,16); + demobuf.p += 16; for (i = 0; i < numskincolors; i++) if (!stricmp(skincolors[i].name,color)) // SRB2kart { @@ -3329,13 +3329,13 @@ void G_DoPlayDemo(char *defdemoname) } // Follower - M_Memcpy(follower, demo_p, 16); - demo_p += 16; + M_Memcpy(follower, demobuf.p, 16); + demobuf.p += 16; K_SetFollowerByName(p, follower); // Follower colour - M_Memcpy(color, demo_p, 16); - demo_p += 16; + M_Memcpy(color, demobuf.p, 16); + demobuf.p += 16; for (i = 0; i < numskincolors +2; i++) // +2 because of Match and Opposite { if (!stricmp(Followercolor_cons_t[i].strvalue, color)) @@ -3346,16 +3346,16 @@ void G_DoPlayDemo(char *defdemoname) } // Score, since Kart uses this to determine where you start on the map - players[p].score = READUINT32(demo_p); + players[p].score = READUINT32(demobuf.p); // Power Levels - clientpowerlevels[p][gametype == GT_BATTLE ? PWRLV_BATTLE : PWRLV_RACE] = READUINT16(demo_p); + clientpowerlevels[p][gametype == GT_BATTLE ? PWRLV_BATTLE : PWRLV_RACE] = READUINT16(demobuf.p); // Followitem - players[p].followitem = READUINT32(demo_p); + players[p].followitem = READUINT32(demobuf.p); // Look for the next player - p = READUINT8(demo_p); + p = READUINT8(demobuf.p); } // end of player read (the 0xFF marker) @@ -3366,7 +3366,7 @@ void G_DoPlayDemo(char *defdemoname) LUA_ClearState(); // No modeattacking check, DF_LUAVARS won't be present here. - LUA_UnArchive(&demo_p, false); + LUA_UnArchive(&demobuf.p, false); } splitscreen = 0; @@ -3909,13 +3909,13 @@ void G_DoneLevelLoad(void) // Writes the demo's checksum, or just random garbage if you can't do that for some reason. static void WriteDemoChecksum(void) { - UINT8 *p = demobuffer+16; // checksum position + UINT8 *p = demobuf.buffer+16; // checksum position #ifdef NOMD5 UINT8 i; for (i = 0; i < 16; i++, p++) *p = P_RandomByte(PR_UNDEFINED); // This MD5 was chosen by fair dice roll and most likely < 50% correct. #else - md5_buffer((char *)p+16, demo_p - (p+16), p); // make a checksum of everything after the checksum in the file. + md5_buffer((char *)p+16, demobuf.p - (p+16), p); // make a checksum of everything after the checksum in the file. #endif } @@ -3933,13 +3933,13 @@ void G_StopMetalDemo(void) ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill) { boolean saved = false; - if (demo_p) + if (demobuf.p) { - WRITEUINT8(demo_p, (kill) ? METALDEATH : DEMOMARKER); // add the demo end (or metal death) marker + WRITEUINT8(demobuf.p, (kill) ? METALDEATH : DEMOMARKER); // add the demo end (or metal death) marker WriteDemoChecksum(); - saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuffer, demo_p - demobuffer); // finally output the file. + saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuf.buffer, demobuf.p - demobuf.buffer); // finally output the file. } - free(demobuffer); + free(demobuf.buffer); metalrecording = false; if (saved) I_Error("Saved to %sMS.LMP", G_BuildMapName(gamemap)); @@ -4011,8 +4011,8 @@ static void G_StopTimingDemo(void) // called from stopdemo command, map command, and g_checkdemoStatus. void G_StopDemo(void) { - Z_Free(demobuffer); - demobuffer = NULL; + Z_Free(demobuf.buffer); + demobuf.buffer = NULL; demo.playback = false; if (demo.title) modeattacking = false; @@ -4089,7 +4089,7 @@ boolean G_CheckDemoStatus(void) void G_SaveDemo(void) { - UINT8 *p = demobuffer+16; // after version + UINT8 *p = demobuf.buffer+16; // after version UINT32 length; #ifdef NOMD5 UINT8 i; @@ -4098,10 +4098,10 @@ void G_SaveDemo(void) // Ensure extrainfo pointer is always available, even if no info is present. if (demoinfo_p && *(UINT32 *)demoinfo_p == 0) { - WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker - *(UINT32 *)demoinfo_p = demo_p - demobuffer; + WRITEUINT8(demobuf.p, DEMOMARKER); // add the demo end marker + *(UINT32 *)demoinfo_p = demobuf.p - demobuf.buffer; } - WRITEUINT8(demo_p, DW_END); // Mark end of demo extra data. + WRITEUINT8(demobuf.p, DW_END); // Mark end of demo extra data. M_Memcpy(p, demo.titlename, 64); // Write demo title here p += 64; @@ -4154,12 +4154,12 @@ void G_SaveDemo(void) *p = M_RandomByte(); // This MD5 was chosen by fair dice roll and most likely < 50% correct. #else // Make a checksum of everything after the checksum in the file up to the end of the standard data. Extrainfo is freely modifiable. - md5_buffer((char *)p+16, (demobuffer + length) - (p+16), p); + md5_buffer((char *)p+16, (demobuf.buffer + length) - (p+16), p); #endif - if (FIL_WriteFile(demoname, demobuffer, demo_p - demobuffer)) // finally output the file. + if (FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer)) // finally output the file. demo.savemode = DSM_SAVED; - free(demobuffer); + free(demobuf.buffer); demo.recording = false; if (!modeattacking) diff --git a/src/g_demo.h b/src/g_demo.h index e9e5be0a9..d24ed3bbb 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -22,8 +22,6 @@ extern "C" { #endif -extern UINT8 *demo_p; - // ====================================== // DEMO playback/recording related stuff. // ====================================== From 77d54a09fec93aa285fc579cdfd1f6d8e5cb858b Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 25 Dec 2022 14:11:38 -0800 Subject: [PATCH 061/128] Remove global lua_save_p and use savebuffer_t for LUA_Archive / LUA_UnArchive --- src/d_netcmd.c | 4 ++-- src/g_demo.c | 4 ++-- src/lua_hooklib.c | 7 ++++--- src/lua_script.c | 34 ++++++++++++++++------------------ src/lua_script.h | 6 +++--- src/p_saveg.c | 4 ++-- 6 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 6a416cbfd..5d749a72e 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -5798,7 +5798,7 @@ static void Command_Archivetest_f(void) // test archive CONS_Printf("LUA_Archive...\n"); - LUA_Archive(&save.p, true); + LUA_Archive(&save, true); WRITEUINT8(save.p, 0x7F); wrote = (UINT32)(save.p - save.buffer); @@ -5809,7 +5809,7 @@ static void Command_Archivetest_f(void) // test unarchive save.p = save.buffer; CONS_Printf("LUA_UnArchive...\n"); - LUA_UnArchive(&save.p, true); + LUA_UnArchive(&save, true); i = READUINT8(save.p); if (i != 0x7F || wrote != (UINT32)(save.p - save.buffer)) CONS_Printf("Savegame corrupted. (write %u, read %u)\n", wrote, (UINT32)(save.p - save.buffer)); diff --git a/src/g_demo.c b/src/g_demo.c index 57dfdc9fa..95353790e 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -2502,7 +2502,7 @@ void G_BeginRecording(void) // player lua vars, always saved even if empty if (demoflags & DF_LUAVARS) - LUA_Archive(&demobuf.p, false); + LUA_Archive(&demobuf, false); memset(&oldcmd,0,sizeof(oldcmd)); memset(&oldghost,0,sizeof(oldghost)); @@ -3366,7 +3366,7 @@ void G_DoPlayDemo(char *defdemoname) LUA_ClearState(); // No modeattacking check, DF_LUAVARS won't be present here. - LUA_UnArchive(&demobuf.p, false); + LUA_UnArchive(&demobuf, false); } splitscreen = 0; diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 142d01490..fd222cdd1 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -834,7 +834,7 @@ int LUA_HookHurtMsg(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 d return hook.status; } -void LUA_HookNetArchive(lua_CFunction archFunc) +void LUA_HookNetArchive(lua_CFunction archFunc, savebuffer_t *save) { const hook_t * map = &hookIds[HOOK(NetVars)]; Hook_State hook; @@ -852,8 +852,9 @@ void LUA_HookNetArchive(lua_CFunction archFunc) // tables becomes an upvalue of archFunc lua_pushvalue(gL, -1); - lua_pushcclosure(gL, archFunc, 1); - // stack: tables, archFunc + lua_pushlightuserdata(gL, save); + lua_pushcclosure(gL, archFunc, 2); + // stack: tables, savebuffer_t, archFunc init_hook_call(&hook, 0, res_none); call_mapped(&hook, map); diff --git a/src/lua_script.c b/src/lua_script.c index 5f12ab416..3a6d53c98 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -41,8 +41,6 @@ lua_State *gL = NULL; int hook_defrosting; -static UINT8 **lua_save_p = NULL; // FIXME: Remove this horrible hack - // List of internal libraries to load from SRB2 static lua_CFunction liblist[] = { LUA_EnumLib, // global metatable for enums @@ -1355,9 +1353,10 @@ static void ArchiveExtVars(UINT8 **p, void *pointer, const char *ptype) static int NetArchive(lua_State *L) { int TABLESINDEX = lua_upvalueindex(1); + savebuffer_t *save = lua_touserdata(L, lua_upvalueindex(2)); int i, n = lua_gettop(L); for (i = 1; i <= n; i++) - ArchiveValue(lua_save_p, TABLESINDEX, i); + ArchiveValue(&save->p, TABLESINDEX, i); return n; } @@ -1568,9 +1567,10 @@ static void UnArchiveExtVars(UINT8 **p, void *pointer) static int NetUnArchive(lua_State *L) { int TABLESINDEX = lua_upvalueindex(1); + savebuffer_t *save = lua_touserdata(L, lua_upvalueindex(2)); int i, n = lua_gettop(L); for (i = 1; i <= n; i++) - UnArchiveValue(lua_save_p, TABLESINDEX); + UnArchiveValue(&save->p, TABLESINDEX); return n; } @@ -1628,7 +1628,7 @@ void LUA_Step(void) lua_gc(gL, LUA_GCSTEP, 1); } -void LUA_Archive(UINT8 **p, boolean network) +void LUA_Archive(savebuffer_t *save, boolean network) { INT32 i; thinker_t *th; @@ -1641,7 +1641,7 @@ void LUA_Archive(UINT8 **p, boolean network) if (!playeringame[i] && i > 0) // NEVER skip player 0, this is for dedi servs. continue; // all players in game will be archived, even if they just add a 0. - ArchiveExtVars(p, &players[i], "player"); + ArchiveExtVars(&save->p, &players[i], "player"); } if (network == true) @@ -1655,23 +1655,22 @@ void LUA_Archive(UINT8 **p, boolean network) // archive function will determine when to skip mobjs, // and write mobjnum in otherwise. - ArchiveExtVars(p, th, "mobj"); + ArchiveExtVars(&save->p, th, "mobj"); } } - WRITEUINT32(*p, UINT32_MAX); // end of mobjs marker, replaces mobjnum. + WRITEUINT32(save->p, UINT32_MAX); // end of mobjs marker, replaces mobjnum. - lua_save_p = p; - LUA_HookNetArchive(NetArchive); // call the NetArchive hook in archive mode + LUA_HookNetArchive(NetArchive, save); // call the NetArchive hook in archive mode } - ArchiveTables(p); + ArchiveTables(&save->p); if (gL) lua_pop(gL, 1); // pop tables } -void LUA_UnArchive(UINT8 **p, boolean network) +void LUA_UnArchive(savebuffer_t *save, boolean network) { UINT32 mobjnum; INT32 i; @@ -1684,28 +1683,27 @@ void LUA_UnArchive(UINT8 **p, boolean network) { if (!playeringame[i] && i > 0) // same here, this is to synch dediservs properly. continue; - UnArchiveExtVars(p, &players[i]); + UnArchiveExtVars(&save->p, &players[i]); } if (network == true) { do { - mobjnum = READUINT32(*p); // read a mobjnum + mobjnum = READUINT32(save->p); // read a mobjnum for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) continue; if (((mobj_t *)th)->mobjnum != mobjnum) // find matching mobj continue; - UnArchiveExtVars(p, th); // apply variables + UnArchiveExtVars(&save->p, th); // apply variables } } while(mobjnum != UINT32_MAX); // repeat until end of mobjs marker. - lua_save_p = p; - LUA_HookNetArchive(NetUnArchive); // call the NetArchive hook in unarchive mode + LUA_HookNetArchive(NetUnArchive, save); // call the NetArchive hook in unarchive mode } - UnArchiveTables(p); + UnArchiveTables(&save->p); if (gL) lua_pop(gL, 1); // pop tables diff --git a/src/lua_script.h b/src/lua_script.h index 51a9af85e..9bfddc875 100644 --- a/src/lua_script.h +++ b/src/lua_script.h @@ -57,8 +57,8 @@ void LUA_DumpFile(const char *filename); #endif fixed_t LUA_EvalMath(const char *word); void LUA_Step(void); -void LUA_Archive(UINT8 **p, boolean network); -void LUA_UnArchive(UINT8 **p, boolean network); +void LUA_Archive(savebuffer_t *save, boolean network); +void LUA_UnArchive(savebuffer_t *save, boolean network); int LUA_PushGlobals(lua_State *L, const char *word); int LUA_WriteGlobals(lua_State *L, const char *word); @@ -67,7 +67,7 @@ void Got_Luacmd(UINT8 **cp, INT32 playernum); // lua_consolelib.c void LUA_CVarChanged(void *cvar); // lua_consolelib.c int Lua_optoption(lua_State *L, int narg, const char *def, const char *const lst[]); -void LUA_HookNetArchive(lua_CFunction archFunc); +void LUA_HookNetArchive(lua_CFunction archFunc, savebuffer_t *save); void LUA_PushTaggableObjectArray ( lua_State *L, diff --git a/src/p_saveg.c b/src/p_saveg.c index ed009c189..562616b63 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -5164,7 +5164,7 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) P_NetArchiveTubeWaypoints(save); P_NetArchiveWaypoints(save); } - LUA_Archive(&save->p, true); + LUA_Archive(save, true); P_NetArchiveRNG(save); @@ -5214,7 +5214,7 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) P_FinishMobjs(); } - LUA_UnArchive(&save->p, true); + LUA_UnArchive(save, true); P_NetUnArchiveRNG(save); From 761be01dbbd10bc54ed2c5412de328a2264fd14e Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 26 Dec 2022 17:45:25 -0500 Subject: [PATCH 062/128] Contain end & size into savebuffer_t I would've liked to make it use a single allocate function to do this very cleanly, but these cases were very clearly not meant to be standardized and use wildly different methods to allocate & free... --- src/d_clisrv.c | 33 ++++++++++++++++++++------------- src/d_clisrv.h | 3 ++- src/d_netcmd.c | 4 +++- src/g_demo.c | 26 ++++++++++++++++---------- src/g_game.c | 23 ++++++++++++----------- src/k_profiles.c | 5 +++-- src/p_saveg.h | 8 ++++++++ src/p_setup.c | 3 +++ 8 files changed, 67 insertions(+), 38 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 360bb05b7..07c756d53 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1128,8 +1128,6 @@ static boolean SV_SendServerConfig(INT32 node) return waspacketsent; } -#define SAVEGAMESIZE (768*1024) - static boolean SV_ResendingSavegameToAnyone(void) { INT32 i; @@ -1148,7 +1146,8 @@ static void SV_SendSaveGame(INT32 node, boolean resending) UINT8 *buffertosend; // first save it in a malloced buffer - save.buffer = (UINT8 *)malloc(SAVEGAMESIZE); + save.size = NETSAVEGAMESIZE; + save.buffer = (UINT8 *)malloc(save.size); if (!save.buffer) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); @@ -1157,14 +1156,14 @@ static void SV_SendSaveGame(INT32 node, boolean resending) // Leave room for the uncompressed length. save.p = save.buffer + sizeof(UINT32); + save.end = save.buffer + save.size; P_SaveNetGame(&save, resending); length = save.p - save.buffer; - if (length > SAVEGAMESIZE) + if (length > NETSAVEGAMESIZE) { free(save.buffer); - save.p = NULL; I_Error("Savegame buffer overrun"); } @@ -1178,10 +1177,9 @@ static void SV_SendSaveGame(INT32 node, boolean resending) } // Attempt to compress it. - if((compressedlen = lzf_compress(save.buffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1))) + if ((compressedlen = lzf_compress(save.buffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1))) { // Compressing succeeded; send compressed data - free(save.buffer); // State that we're compressed. @@ -1192,7 +1190,6 @@ static void SV_SendSaveGame(INT32 node, boolean resending) else { // Compression failed to make it smaller; send original - free(compressedsave); // State that we're not compressed @@ -1201,7 +1198,6 @@ static void SV_SendSaveGame(INT32 node, boolean resending) } AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0); - save.p = NULL; // Remember when we started sending the savegame so we can handle timeouts sendingsavegame[node] = true; @@ -1224,20 +1220,22 @@ static void SV_SavedGame(void) sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); // first save it in a malloced buffer - save.p = save.buffer = (UINT8 *)malloc(SAVEGAMESIZE); + save.size = NETSAVEGAMESIZE; + save.p = save.buffer = (UINT8 *)malloc(save.size); if (!save.p) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); return; } + save.end = save.buffer + save.size; + P_SaveNetGame(&save, false); length = save.p - save.buffer; - if (length > SAVEGAMESIZE) + if (length > NETSAVEGAMESIZE) { free(save.buffer); - save.p = NULL; I_Error("Savegame buffer overrun"); } @@ -1246,7 +1244,6 @@ static void SV_SavedGame(void) CONS_Printf(M_GetText("Didn't save %s for netgame"), tmpsave); free(save.buffer); - save.p = NULL; } #undef TMPSAVENAME @@ -1272,15 +1269,21 @@ static void CL_LoadReceivedSavegame(boolean reloading) } save.p = save.buffer; + save.size = length; + save.end = save.buffer + save.size; // Decompress saved game if necessary. decompressedlen = READUINT32(save.p); if(decompressedlen > 0) { UINT8 *decompressedbuffer = Z_Malloc(decompressedlen, PU_STATIC, NULL); + lzf_decompress(save.p, length - sizeof(UINT32), decompressedbuffer, decompressedlen); Z_Free(save.buffer); + save.p = save.buffer = decompressedbuffer; + save.size = decompressedlen; + save.end = save.buffer + decompressedlen; } paused = false; @@ -6075,6 +6078,8 @@ rewind_t *CL_SaveRewindPoint(size_t demopos) return NULL; save.buffer = save.p = rewind->savebuffer; + save.size = NETSAVEGAMESIZE; + save.end = save.buffer + save.size; P_SaveNetGame(&save, false); @@ -6102,6 +6107,8 @@ rewind_t *CL_RewindToTime(tic_t time) return NULL; save.buffer = save.p = rewindhead->savebuffer; + save.size = NETSAVEGAMESIZE; + save.end = save.buffer + save.size; P_LoadNetGame(&save, false); diff --git a/src/d_clisrv.h b/src/d_clisrv.h index e2e49ee2d..a648b07ae 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -22,6 +22,7 @@ #include "mserv.h" #include "k_pwrlv.h" // PWRLV_NUMTYPES +#include "p_saveg.h" // NETSAVEGAMESIZE #ifdef __cplusplus extern "C" { @@ -534,7 +535,7 @@ extern boolean hu_stopped; // struct rewind_t { - UINT8 savebuffer[(768*1024)]; + UINT8 savebuffer[NETSAVEGAMESIZE]; tic_t leveltime; size_t demopos; diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 5d749a72e..975c165ad 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -5794,7 +5794,9 @@ static void Command_Archivetest_f(void) ((mobj_t *)th)->mobjnum = i++; // allocate buffer - save.buffer = save.p = ZZ_Alloc(1024); + save.size = 1024; + save.buffer = save.p = ZZ_Alloc(save.size); + save.end = save.buffer + save.size; // test archive CONS_Printf("LUA_Archive...\n"); diff --git a/src/g_demo.c b/src/g_demo.c index 95353790e..579d786f5 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -72,7 +72,6 @@ tic_t demostarttime; // for comparative timing purposes static char demoname[MAX_WADPATH]; static savebuffer_t demobuf; static UINT8 *demotime_p, *demoinfo_p; -static UINT8 *demoend; static UINT8 demoflags; boolean demosynced = true; // console warning message @@ -151,7 +150,7 @@ demoghost *ghosts = NULL; #define ZT_FLAGS 0x80 // OUT OF ZIPTICS... -#define DEMOMARKER 0x80 // demoend +#define DEMOMARKER 0x80 // demobuf.end UINT8 demo_extradata[MAXPLAYERS]; UINT8 demo_writerng; // 0=no, 1=yes, 2=yes but on a timeout @@ -630,7 +629,7 @@ void G_WriteDemoTiccmd(ticcmd_t *cmd, INT32 playernum) // attention here for the ticcmd size! // latest demos with mouse aiming byte in ticcmd - if (!(demoflags & DF_GHOST) && ziptic_p > demoend - 9) + if (!(demoflags & DF_GHOST) && ziptic_p > demobuf.end - 9) { G_CheckDemoStatus(); // no more space return; @@ -703,7 +702,7 @@ void G_WriteAllGhostTics(void) // attention here for the ticcmd size! // latest demos with mouse aiming byte in ticcmd - if (demobuf.p >= demoend - (13 + 9 + 9)) + if (demobuf.p >= demobuf.end - (13 + 9 + 9)) { G_CheckDemoStatus(); // no more space return; @@ -1995,7 +1994,7 @@ void G_WriteMetalTic(mobj_t *metal) // attention here for the ticcmd size! // latest demos with mouse aiming byte in ticcmd - if (demobuf.p >= demoend - 32) + if (demobuf.p >= demobuf.end - 32) { G_StopMetalRecording(false); // no more space return; @@ -2013,13 +2012,17 @@ void G_RecordDemo(const char *name) strcat(demoname, ".lmp"); //@TODO make a maxdemosize cvar maxsize = 1024*1024*2; + if (M_CheckParm("-maxdemo") && M_IsNextParm()) maxsize = atoi(M_GetNextParm()) * 1024; + // if (demobuf.buffer) -// free(demobuf.buffer); +// P_SaveBufferFree(&demobuf); + + demobuf.size = maxsize; + demobuf.buffer = (UINT8 *)malloc(maxsize); demobuf.p = NULL; - demobuf.buffer = malloc(maxsize); - demoend = demobuf.buffer + maxsize; + demobuf.end = demobuf.buffer + demobuf.size; demo.recording = true; } @@ -2030,9 +2033,12 @@ void G_RecordMetal(void) maxsize = 1024*1024; if (M_CheckParm("-maxdemo") && M_IsNextParm()) maxsize = atoi(M_GetNextParm()) * 1024; + + demobuf.size = maxsize; + demobuf.buffer = (UINT8 *)malloc(maxsize); demobuf.p = NULL; - demobuf.buffer = malloc(maxsize); - demoend = demobuf.buffer + maxsize; + demobuf.end = demobuf.buffer + demobuf.size; + metalrecording = true; } diff --git a/src/g_game.c b/src/g_game.c index 60cacbd66..da3a58986 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -72,9 +72,6 @@ UINT8 ultimatemode = false; JoyType_t Joystick[MAXSPLITSCREENPLAYERS]; -// 1024 bytes is plenty for a savegame -#define SAVEGAMESIZE (1024) - // SRB2kart char gamedatafilename[64] = #if defined (TESTERS) || defined (HOSTTESTERS) @@ -4533,12 +4530,14 @@ void G_SaveGameData(void) } length += nummapheaders * (MAXMAPLUMPNAME+1+4+4); - save.p = save.buffer = (UINT8 *)malloc(length); + save.size = length; + save.p = save.buffer = (UINT8 *)malloc(save.size); if (!save.p) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for saving game data\n")); return; } + save.end = save.buffer + save.size; // Version test @@ -4626,7 +4625,6 @@ void G_SaveGameData(void) FIL_WriteFile(va(pandf, srb2home, gamedatafilename), save.buffer, length); free(save.buffer); - save.p = save.buffer = NULL; // Also save profiles here. PR_SaveProfiles(); @@ -4666,6 +4664,8 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) } save.p = save.buffer; + save.size = length; + save.end = save.buffer + save.size; memset(vcheck, 0, sizeof (vcheck)); sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); @@ -4680,7 +4680,6 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) M_StartMessage(M_GetText("Save game from different version\n\nPress ESC\n"), NULL, MM_NOTHING); Command_ExitGame_f(); Z_Free(save.buffer); - save.p = save.buffer = NULL; // no cheating! memset(&savedata, 0, sizeof(savedata)); @@ -4716,7 +4715,6 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) // done Z_Free(save.buffer); - save.p = save.buffer = NULL; // gameaction = ga_nothing; // G_SetGamestate(GS_LEVEL); @@ -4755,12 +4753,14 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) char name[VERSIONSIZE]; size_t length; - save.p = save.buffer = (UINT8 *)malloc(SAVEGAMESIZE); + save.size = SAVEGAMESIZE; + save.p = save.buffer = (UINT8 *)malloc(save.size); if (!save.p) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for saving game data\n")); return; } + save.end = save.buffer + save.size; memset(name, 0, sizeof (name)); sprintf(name, (marathonmode ? "back-up %d" : "version %d"), VERSION); @@ -4779,7 +4779,6 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) length = save.p - save.buffer; saved = FIL_WriteFile(backup, save.buffer, length); free(save.buffer); - save.p = save.buffer = NULL; } gameaction = ga_nothing; @@ -4791,7 +4790,7 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) } #define BADSAVE goto cleanup; -#define CHECKPOS if (save.p >= end_p) BADSAVE +#define CHECKPOS if (save.p >= save.end) BADSAVE void G_SaveGameOver(UINT32 slot, boolean modifylives) { boolean saved = false; @@ -4816,11 +4815,13 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives) { char temp[sizeof(timeattackfolder)]; - UINT8 *end_p = save.buffer + length; UINT8 *lives_p; SINT8 pllives; save.p = save.buffer; + save.size = length; + save.end = save.buffer + save.size; + // Version check memset(vcheck, 0, sizeof (vcheck)); sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); diff --git a/src/k_profiles.c b/src/k_profiles.c index f24d623ab..1163b1f8d 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -219,12 +219,14 @@ void PR_SaveProfiles(void) UINT8 i, j, k; savebuffer_t save; - save.p = save.buffer = (UINT8 *)malloc(sizeof(UINT32) + (numprofiles * sizeof(profile_t))); + save.size = sizeof(UINT32) + (numprofiles * sizeof(profile_t)); + save.p = save.buffer = (UINT8 *)malloc(save.size); if (!save.p) { I_Error("No more free memory for saving profiles\n"); return; } + save.end = save.buffer + save.size; // Add header. WRITESTRINGN(save.p, PROFILEHEADER, headerlen); @@ -272,7 +274,6 @@ void PR_SaveProfiles(void) I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?"); } free(save.buffer); - save.p = save.buffer = NULL; } void PR_LoadProfiles(void) diff --git a/src/p_saveg.h b/src/p_saveg.h index 538febcf9..a0b0f8587 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -22,6 +22,12 @@ extern "C" { #pragma interface #endif +// 1024 bytes is plenty for a savegame +#define SAVEGAMESIZE (1024) + +// For netgames +#define NETSAVEGAMESIZE (768*1024) + // Persistent storage/archiving. // These are the load / save game routines. @@ -47,6 +53,8 @@ struct savebuffer_t { UINT8 *buffer; UINT8 *p; + UINT8 *end; + size_t size; }; #ifdef __cplusplus diff --git a/src/p_setup.c b/src/p_setup.c index b32c5126c..e2e11a613 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -725,6 +725,7 @@ void P_WriteThings(void) savebuffer_t save; INT16 temp; + save.size = nummapthings * sizeof (mapthing_t); save.p = save.buffer = (UINT8 *)malloc(nummapthings * sizeof (mapthing_t)); if (!save.p) @@ -733,6 +734,8 @@ void P_WriteThings(void) return; } + save.end = save.buffer + save.size; + mt = mapthings; for (i = 0; i < nummapthings; i++, mt++) { From d5cd4f6798be7e9569cb22038f3400f85b0e4672 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 1 Jan 2023 14:54:58 +0000 Subject: [PATCH 063/128] Make placeholder for vote screen gametype color yellowmap[0] instead of 255 cyan --- src/y_inter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/y_inter.c b/src/y_inter.c index 9d7a62f3e..509f40451 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1550,7 +1550,7 @@ void Y_StartVote(void) levelinfo[i].str[sizeof levelinfo[i].str - 1] = '\0'; // set up the gtc and gts - levelinfo[i].gtc = 255; // TODO rewrite vote screen + levelinfo[i].gtc = yellowmap[0]; // TODO rewrite vote screen if (i == 2 && votelevels[i][1] != votelevels[0][1]) levelinfo[i].gts = gametypes[votelevels[i][1]]->name; else From 4bbe3d71778e30aa4718a57d7fb8316bca2ea9c4 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 1 Jan 2023 14:55:58 +0000 Subject: [PATCH 064/128] Draw Rings/Lives HUD even when not GTR_CIRCUIT --- src/k_hud.c | 141 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 93 insertions(+), 48 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index f8b6bc387..742d0506b 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -2330,40 +2330,13 @@ void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, IN } } -#define RINGANIM_FLIPFRAME (RINGANIM_NUMFRAMES/2) - -static void K_drawKartLapsAndRings(void) +static void K_drawKartLaps(void) { - const boolean uselives = G_GametypeUsesLives(); - SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; - UINT8 rn[2]; - INT32 ringflip = 0; - UINT8 *ringmap = NULL; - boolean colorring = false; - INT32 ringx = 0; - - rn[0] = ((abs(stplyr->rings) / 10) % 10); - rn[1] = (abs(stplyr->rings) % 10); - - if (stplyr->rings <= 0 && (leveltime/5 & 1)) // In debt - { - ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); - colorring = true; - } - else if (stplyr->rings >= 20) // Maxed out - ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); - - if (stplyr->karthud[khud_ringframe] > RINGANIM_FLIPFRAME) - { - ringflip = V_FLIP; - ringanim_realframe = RINGANIM_NUMFRAMES-stplyr->karthud[khud_ringframe]; - ringx += SHORT((r_splitscreen > 1) ? kp_smallring[ringanim_realframe]->width : kp_ring[ringanim_realframe]->width); - } if (r_splitscreen > 1) { - INT32 fx = 0, fy = 0, fr = 0; + INT32 fx = 0, fy = 0; INT32 flipflag = 0; // pain and suffering defined below @@ -2389,8 +2362,6 @@ static void K_drawKartLapsAndRings(void) } } - fr = fx; - // Laps V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); @@ -2417,6 +2388,75 @@ static void K_drawKartLapsAndRings(void) V_DrawScaledPatch(fx+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->laps) % 10]); V_DrawScaledPatch(fx+27, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(numlaps) % 10]); } + } + else + { + // Laps + V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_lapsticker); + V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", min(stplyr->laps, numlaps), numlaps)); + } +} + +#define RINGANIM_FLIPFRAME (RINGANIM_NUMFRAMES/2) + +static void K_drawRingCounter(void) +{ + const boolean uselives = G_GametypeUsesLives(); + SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; + INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; + UINT8 rn[2]; + INT32 ringflip = 0; + UINT8 *ringmap = NULL; + boolean colorring = false; + INT32 ringx = 0, fy = 0; + + rn[0] = ((abs(stplyr->rings) / 10) % 10); + rn[1] = (abs(stplyr->rings) % 10); + + if (stplyr->rings <= 0 && (leveltime/5 & 1)) // In debt + { + ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); + colorring = true; + } + else if (stplyr->rings >= 20) // Maxed out + ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); + + if (stplyr->karthud[khud_ringframe] > RINGANIM_FLIPFRAME) + { + ringflip = V_FLIP; + ringanim_realframe = RINGANIM_NUMFRAMES-stplyr->karthud[khud_ringframe]; + ringx += SHORT((r_splitscreen > 1) ? kp_smallring[ringanim_realframe]->width : kp_ring[ringanim_realframe]->width); + } + + if (r_splitscreen > 1) + { + INT32 fx = 0, fr = 0; + INT32 flipflag = 0; + + // pain and suffering defined below + if (r_splitscreen < 2) // don't change shit for THIS splitscreen. + { + fx = LAPS_X; + fy = LAPS_Y; + } + else + { + if (stplyr == &players[displayplayers[0]] || stplyr == &players[displayplayers[2]]) // If we are P1 or P3... + { + fx = LAPS_X; + fy = LAPS_Y; + splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; + } + else // else, that means we're P2 or P4. + { + fx = LAPS2_X; + fy = LAPS2_Y; + splitflags = V_SNAPTORIGHT|V_SNAPTOBOTTOM|V_SPLITSCREEN; + flipflag = V_FLIP; // make the string right aligned and other shit + } + } + + fr = fx; // Rings if (!uselives) @@ -2451,41 +2491,42 @@ static void K_drawKartLapsAndRings(void) } else { - // Laps - V_DrawScaledPatch(LAPS_X, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_lapsticker); - V_DrawKartString(LAPS_X+33, LAPS_Y+3, V_HUDTRANS|V_SLIDEIN|splitflags, va("%d/%d", min(stplyr->laps, numlaps), numlaps)); + fy = LAPS_Y-11; + + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) + fy -= 4; // Rings if (!uselives) - V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[1]); + V_DrawScaledPatch(LAPS_X, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[1]); else - V_DrawScaledPatch(LAPS_X, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[0]); + V_DrawScaledPatch(LAPS_X, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringsticker[0]); - V_DrawMappedPatch(LAPS_X+ringx+7, LAPS_Y-16, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_ring[ringanim_realframe], (colorring ? ringmap : NULL)); + V_DrawMappedPatch(LAPS_X+ringx+7, fy-5, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_ring[ringanim_realframe], (colorring ? ringmap : NULL)); if (stplyr->rings < 0) // Draw the minus for ring debt { - V_DrawMappedPatch(LAPS_X+23, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminus, ringmap); - V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); - V_DrawMappedPatch(LAPS_X+35, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); + V_DrawMappedPatch(LAPS_X+23, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminus, ringmap); + V_DrawMappedPatch(LAPS_X+29, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); + V_DrawMappedPatch(LAPS_X+35, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); } else { - V_DrawMappedPatch(LAPS_X+23, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); - V_DrawMappedPatch(LAPS_X+29, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); + V_DrawMappedPatch(LAPS_X+23, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[0]], ringmap); + V_DrawMappedPatch(LAPS_X+29, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[rn[1]], ringmap); } // SPB ring lock if (stplyr->pflags & PF_RINGLOCK) - V_DrawScaledPatch(LAPS_X-5, LAPS_Y-28, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); + V_DrawScaledPatch(LAPS_X-5, fy-17, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); // Lives if (uselives) { UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); - V_DrawMappedPatch(LAPS_X+46, LAPS_Y-16, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_RANK], colormap); + V_DrawMappedPatch(LAPS_X+46, fy-5, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_RANK], colormap); if (stplyr->lives >= 0) - V_DrawScaledPatch(LAPS_X+63, LAPS_Y-11, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow OR underflow + V_DrawScaledPatch(LAPS_X+63, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[(stplyr->lives % 10)]); // make sure this doesn't overflow OR underflow } } } @@ -2502,7 +2543,7 @@ static void K_drawKartAccessibilityIcons(INT32 fx) if (r_splitscreen < 2) // adjust to speedometer height { - if (gametyperules & (GTR_BUMPERS|GTR_SPHERES)) + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) fy -= 4; } else @@ -2592,7 +2633,7 @@ static void K_drawKartSpeedometer(void) numbers[1] = ((convSpeed / 10) % 10); numbers[2] = (convSpeed % 10); - if (gametyperules & (GTR_BUMPERS|GTR_SPHERES)) + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) battleoffset = -4; V_DrawScaledPatch(LAPS_X, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometersticker); @@ -5121,7 +5162,7 @@ void K_drawKartHUD(void) { if (gametyperules & GTR_CIRCUIT) { - K_drawKartLapsAndRings(); + K_drawKartLaps(); } else if (gametyperules & GTR_BUMPERS) { @@ -5143,6 +5184,10 @@ void K_drawKartHUD(void) { K_drawBlueSphereMeter(); } + else + { + K_drawRingCounter(); + } if (modeattacking && !bossinfo.valid) { From 8f78b37e57670b4f6f9d95b9fbe15f9c4791c72d Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 1 Jan 2023 16:02:55 +0000 Subject: [PATCH 065/128] Fix `yellowmap` not being included Was going to `#include "console.h"` but not worth it for literally one placeholder index --- src/y_inter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/y_inter.c b/src/y_inter.c index 509f40451..902966ca4 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1550,7 +1550,7 @@ void Y_StartVote(void) levelinfo[i].str[sizeof levelinfo[i].str - 1] = '\0'; // set up the gtc and gts - levelinfo[i].gtc = yellowmap[0]; // TODO rewrite vote screen + levelinfo[i].gtc = 73; // yellowmap[0] -- TODO rewrite vote screen if (i == 2 && votelevels[i][1] != votelevels[0][1]) levelinfo[i].gts = gametypes[votelevels[i][1]]->name; else From c10bd068de734d493a8666fbe44eadf51f7c8287 Mon Sep 17 00:00:00 2001 From: toaster Date: Sun, 1 Jan 2023 23:39:28 +0000 Subject: [PATCH 066/128] Fix loading a map in a seperate file before loading a file with the relevant header --- src/p_setup.c | 2 +- src/w_wad.c | 4 ++-- src/w_wad.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/p_setup.c b/src/p_setup.c index 61c2a9ec3..0aff1ef4e 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -7771,7 +7771,7 @@ UINT8 P_InitMapData(boolean existingmapheaders) for (i = 0; i < nummapheaders; ++i) { name = mapheaderinfo[i]->lumpname; - maplump = W_CheckNumForMap(name); + maplump = W_CheckNumForMap(name, (mapheaderinfo[i]->lumpnum == LUMPERROR)); // Always check for cup cache reassociations. // (The core assumption is that cups < headers.) diff --git a/src/w_wad.c b/src/w_wad.c index cef5615cc..ad630a6fb 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -1309,12 +1309,12 @@ lumpnum_t W_CheckNumForLongName(const char *name) // Look for valid map data through all added files in descendant order. // Get a map marker for WADs, and a standalone WAD file lump inside PK3s. -lumpnum_t W_CheckNumForMap(const char *name) +lumpnum_t W_CheckNumForMap(const char *name, boolean checktofirst) { lumpnum_t check = INT16_MAX; UINT32 uhash, hash = quickncasehash(name, LUMPNUMCACHENAME); INT32 i; - UINT16 firstfile = (partadd_earliestfile == UINT16_MAX) ? 0 : partadd_earliestfile; + UINT16 firstfile = (checktofirst || (partadd_earliestfile == UINT16_MAX)) ? 0 : partadd_earliestfile; // Check the lumpnumcache first. Loop backwards so that we check // most recent entries first diff --git a/src/w_wad.h b/src/w_wad.h index 24c0ac74b..31ca26e69 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -171,7 +171,7 @@ UINT16 W_CheckNumForFullNamePK3(const char *name, UINT16 wad, UINT16 startlump); UINT16 W_CheckNumForFolderStartPK3(const char *name, UINT16 wad, UINT16 startlump); UINT16 W_CheckNumForFolderEndPK3(const char *name, UINT16 wad, UINT16 startlump); -lumpnum_t W_CheckNumForMap(const char *name); +lumpnum_t W_CheckNumForMap(const char *name, boolean checktofirst); lumpnum_t W_CheckNumForName(const char *name); lumpnum_t W_CheckNumForLongName(const char *name); lumpnum_t W_GetNumForName(const char *name); // like W_CheckNumForName but I_Error on LUMPERROR From 151dccf32fa61e2ee7f9217c16e95431d1a65891 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 1 Jan 2023 15:39:42 -0800 Subject: [PATCH 067/128] Parity between title screen and menu version info --- src/f_finale.c | 71 ++++++++++++++++++++++++++---------------------- src/f_finale.h | 2 ++ src/k_menudraw.c | 7 +---- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/f_finale.c b/src/f_finale.c index e20deae1f..bbac13838 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1889,6 +1889,43 @@ void F_StartTitleScreen(void) F_CacheTitleScreen(); } +void F_VersionDrawer(void) +{ + // An adapted thing from old menus - most games have version info on the title screen now... + INT32 texty = vid.height - 10*vid.dupy; + +#define addtext(f, str) {\ + V_DrawThinString(vid.dupx, texty, V_NOSCALESTART|f, str);\ + texty -= 10*vid.dupy;\ +} + if (customversionstring[0] != '\0') + { + addtext(V_ALLOWLOWERCASE, customversionstring); + addtext(0, "Mod version:"); + } + else + { +// Development -- show revision / branch info +#if defined(TESTERS) + addtext(V_ALLOWLOWERCASE|V_SKYMAP, "Tester client"); + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); +#elif defined(HOSTTESTERS) + addtext(V_ALLOWLOWERCASE|V_REDMAP, "Netgame host for testers"); + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); +#elif defined(DEVELOP) + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, comprevision); + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, compbranch); +#else // Regular build + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", VERSIONSTRING)); +#endif + if (compuncommitted) + { + addtext(V_REDMAP|V_STRINGDANCE, "! UNCOMMITTED CHANGES !"); + } + } +#undef addtext +} + // (no longer) De-Demo'd Title Screen void F_TitleScreenDrawer(void) { @@ -1959,39 +1996,7 @@ void F_TitleScreenDrawer(void) V_DrawFixedPatch(0, 0, FRACUNIT, 0, kts_copyright, NULL); - // An adapted thing from old menus - most games have version info on the title screen now... - { - INT32 texty = vid.height - 10*vid.dupy; -#define addtext(f, str) {\ - V_DrawThinString(vid.dupx, texty, V_NOSCALESTART|f, str);\ - texty -= 10*vid.dupy;\ -} - if (customversionstring[0] != '\0') - { - addtext(V_ALLOWLOWERCASE, customversionstring); - addtext(0, "Mod version:"); - } - else - { -// Development -- show revision / branch info -#if defined(TESTERS) - addtext(V_ALLOWLOWERCASE|V_SKYMAP, "Tester client"); - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); -#elif defined(HOSTTESTERS) - addtext(V_ALLOWLOWERCASE|V_REDMAP, "Netgame host for testers"); - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", compdate)); -#elif defined(DEVELOP) - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, comprevision); - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, compbranch); -#else // Regular build - addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", VERSIONSTRING)); -#endif - if (compuncommitted) - addtext(V_REDMAP|V_STRINGDANCE, "! UNCOMMITTED CHANGES !"); - } -#undef addtext - } - + F_VersionDrawer(); break; } diff --git a/src/f_finale.h b/src/f_finale.h index a42228625..ca1108214 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -60,6 +60,8 @@ void F_EndingDrawer(void); void F_CreditTicker(void); void F_CreditDrawer(void); +void F_VersionDrawer(void); + void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer); void F_CutsceneDrawer(void); void F_EndCutScene(void); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 9071907bc..7c20a0f04 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -541,12 +541,7 @@ void M_Drawer(void) } else { -#ifdef DEVELOP // Development -- show revision / branch info - V_DrawThinString(vid.dupx, vid.height - 20*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, compbranch); - V_DrawThinString(vid.dupx, vid.height - 10*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, comprevision); -#else // Regular build - V_DrawThinString(vid.dupx, vid.height - 10*vid.dupy, V_NOSCALESTART|V_TRANSLUCENT|V_ALLOWLOWERCASE, va("%s", VERSIONSTRING)); -#endif + F_VersionDrawer(); } From 718eb8b11f848605c16ca67bf2cb8000c3c29b67 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 2 Jan 2023 00:54:24 +0000 Subject: [PATCH 068/128] Permit losing a life when exited if the player has PF_NOCONTEST --- src/k_grandprix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_grandprix.c b/src/k_grandprix.c index d26787100..6d879054b 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -675,7 +675,7 @@ void K_PlayerLoseLife(player_t *player) return; } - if (player->spectator || player->exiting || player->bot || player->lives <= 0 || (player->pflags & PF_LOSTLIFE)) + if (player->spectator || (player->exiting && !(player->pflags & PF_NOCONTEST)) || player->bot || player->lives <= 0 || (player->pflags & PF_LOSTLIFE)) { return; } From 2e4b5bd061657642cee3678ad8c0a4fa0ef4c9a2 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 29 Nov 2022 19:23:05 -0800 Subject: [PATCH 069/128] Clean up P_FuseThink flicker condition --- src/p_mobj.c | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 26b38d631..a1544b802 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9528,13 +9528,38 @@ for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) s P_RemoveMobj(mobj); // make sure they disappear } +static boolean P_CanFlickerFuse(mobj_t *mobj) +{ + switch (mobj->type) + { + case MT_SNAPPER_HEAD: + case MT_SNAPPER_LEG: + case MT_MINECARTSEG: + return true; + + case MT_RANDOMITEM: + case MT_EGGMANITEM: + case MT_FALLINGROCK: + if (mobj->fuse <= TICRATE) + { + return true; + } + break; + + default: + break; + } + + return false; + +} + static boolean P_FuseThink(mobj_t *mobj) { - if (mobj->type == MT_SNAPPER_HEAD || mobj->type == MT_SNAPPER_LEG || mobj->type == MT_MINECARTSEG) - mobj->renderflags ^= RF_DONTDRAW; - - if (mobj->fuse <= TICRATE && (mobj->type == MT_RANDOMITEM || mobj->type == MT_EGGMANITEM || mobj->type == MT_FALLINGROCK)) + if (P_CanFlickerFuse(mobj)) + { mobj->renderflags ^= RF_DONTDRAW; + } mobj->fuse--; From 43aa162fcba052fe491e4f07514d0358d531cdff Mon Sep 17 00:00:00 2001 From: James R Date: Sat, 10 Dec 2022 01:12:36 -0800 Subject: [PATCH 070/128] Add K_GetChaosEmeraldColor --- src/k_battle.c | 37 ++++++++++++++++++++++++------------- src/k_battle.h | 1 + 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/k_battle.c b/src/k_battle.c index 15b81f81c..7079bf3a4 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -221,6 +221,29 @@ void K_CheckEmeralds(player_t *player) K_CheckBumpers(); } +UINT16 K_GetChaosEmeraldColor(UINT32 emeraldType) +{ + switch (emeraldType) + { + case EMERALD_CHAOS1: + return SKINCOLOR_CHAOSEMERALD1; + case EMERALD_CHAOS2: + return SKINCOLOR_CHAOSEMERALD2; + case EMERALD_CHAOS3: + return SKINCOLOR_CHAOSEMERALD3; + case EMERALD_CHAOS4: + return SKINCOLOR_CHAOSEMERALD4; + case EMERALD_CHAOS5: + return SKINCOLOR_CHAOSEMERALD5; + case EMERALD_CHAOS6: + return SKINCOLOR_CHAOSEMERALD6; + case EMERALD_CHAOS7: + return SKINCOLOR_CHAOSEMERALD7; + default: + return SKINCOLOR_NONE; + } +} + mobj_t *K_SpawnChaosEmerald(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT32 emeraldType) { boolean validEmerald = true; @@ -240,25 +263,13 @@ mobj_t *K_SpawnChaosEmerald(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT switch (emeraldType) { case EMERALD_CHAOS1: - emerald->color = SKINCOLOR_CHAOSEMERALD1; - break; case EMERALD_CHAOS2: - emerald->color = SKINCOLOR_CHAOSEMERALD2; - break; case EMERALD_CHAOS3: - emerald->color = SKINCOLOR_CHAOSEMERALD3; - break; case EMERALD_CHAOS4: - emerald->color = SKINCOLOR_CHAOSEMERALD4; - break; case EMERALD_CHAOS5: - emerald->color = SKINCOLOR_CHAOSEMERALD5; - break; case EMERALD_CHAOS6: - emerald->color = SKINCOLOR_CHAOSEMERALD6; - break; case EMERALD_CHAOS7: - emerald->color = SKINCOLOR_CHAOSEMERALD7; + emerald->color = K_GetChaosEmeraldColor(emeraldType); break; default: CONS_Printf("Invalid emerald type %d\n", emeraldType); diff --git a/src/k_battle.h b/src/k_battle.h index 42d92cf33..12ad78004 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -25,6 +25,7 @@ boolean K_IsPlayerWanted(player_t *player); void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount); void K_CheckBumpers(void); void K_CheckEmeralds(player_t *player); +UINT16 K_GetChaosEmeraldColor(UINT32 emeraldType); mobj_t *K_SpawnChaosEmerald(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT32 emeraldType); mobj_t *K_SpawnSphereBox(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 amount); void K_DropEmeraldsFromPlayer(player_t *player, UINT32 emeraldType); From a94e18c277f324f8e6b2199d58ffa3e93927069e Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Dec 2022 22:32:57 -0800 Subject: [PATCH 071/128] Add K_UpdateMobjItemOverlay --- src/k_kart.c | 23 +++++++++++++++++++++++ src/k_kart.h | 2 ++ src/p_mobj.c | 20 +------------------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index f1c0b8be2..97f3361df 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -11164,4 +11164,27 @@ void K_HandleDirectionalInfluence(player_t *player) player->mo->momy = FixedMul(speed, finalY); } +void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount) +{ + switch (itemType) + { + case KITEM_ORBINAUT: + part->sprite = SPR_ITMO; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetOrbinautItemFrame(itemCount); + break; + case KITEM_INVINCIBILITY: + part->sprite = SPR_ITMI; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); + break; + case KITEM_SAD: + part->sprite = SPR_ITEM; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; + break; + default: + part->sprite = SPR_ITEM; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType); + break; + } +} + //} diff --git a/src/k_kart.h b/src/k_kart.h index f74f14e12..b297b2a57 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -197,6 +197,8 @@ fixed_t K_ItemScaleForPlayer(player_t *player); void K_SetItemOut(player_t *player); void K_UnsetItemOut(player_t *player); +void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/p_mobj.c b/src/p_mobj.c index a1544b802..8bdd50fd4 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -4346,25 +4346,7 @@ static void P_RefreshItemCapsuleParts(mobj_t *mobj) part->threshold = mobj->threshold; part->movecount = mobj->movecount; - switch (itemType) - { - case KITEM_ORBINAUT: - part->sprite = SPR_ITMO; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetOrbinautItemFrame(mobj->movecount); - break; - case KITEM_INVINCIBILITY: - part->sprite = SPR_ITMI; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); - break; - case KITEM_SAD: - part->sprite = SPR_ITEM; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; - break; - default: - part->sprite = SPR_ITEM; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType); - break; - } + K_UpdateMobjItemOverlay(part, itemType, mobj->movecount); // update number frame if (K_GetShieldFromItem(itemType) != KSHIELD_NONE) // shields don't stack, so don't show a number From 6eb8da1a567e579d5984f486047cf59faf5bd6d2 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Dec 2022 22:36:26 -0800 Subject: [PATCH 072/128] Add K_GetTotallyRandomResult, for battle item spawners --- src/k_kart.c | 100 +++++++++++++++++++++++++-------------------------- src/k_kart.h | 1 + 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 97f3361df..bb33f7493 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6183,17 +6183,48 @@ void K_DropHnextList(player_t *player, boolean keepshields) } } +SINT8 K_GetTotallyRandomResult(UINT8 useodds) +{ + itemroulette_t rouletteData = {0}; + INT32 spawnchance[NUMKARTRESULTS]; + INT32 totalspawnchance = 0; + INT32 i; + + memset(spawnchance, 0, sizeof (spawnchance)); + + K_FillItemRouletteData(NULL, &rouletteData); + + for (i = 1; i < NUMKARTRESULTS; i++) + { + spawnchance[i] = ( + totalspawnchance += K_KartGetItemOdds(NULL, &rouletteData, useodds, i) + ); + } + + if (totalspawnchance > 0) + { + totalspawnchance = P_RandomKey(PR_ITEM_ROULETTE, totalspawnchance); + for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); + } + else + { + i = KITEM_SAD; + } + + return i; +} + mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount) { mobj_t *drop = P_SpawnMobj(x, y, z, MT_FLOATINGITEM); mobj_t *backdrop = P_SpawnMobjFromMobj(drop, 0, 0, 0, MT_OVERLAY); - + P_SetTarget(&backdrop->target, drop); P_SetMobjState(backdrop, S_ITEMBACKDROP); P_SetScale(drop, drop->scale>>4); drop->destscale = (3*drop->destscale)/2; - + drop->angle = angle; P_Thrust(drop, FixedAngle(P_RandomFixed(PR_ITEM_ROULETTE) * 180) + angle, @@ -6205,62 +6236,31 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 if (type == 0) { - itemroulette_t rouletteData = {0}; - UINT8 useodds = 0; - INT32 spawnchance[NUMKARTRESULTS]; - INT32 totalspawnchance = 0; - INT32 i; + const SINT8 i = K_GetTotallyRandomResult(amount); - memset(spawnchance, 0, sizeof (spawnchance)); + // TODO: this is bad! + // K_KartGetItemResult requires a player + // but item roulette will need rewritten to change this - useodds = amount; + const SINT8 newType = K_ItemResultToType(i); + const UINT8 newAmount = K_ItemResultToAmount(i); - K_FillItemRouletteData(NULL, &rouletteData); - - for (i = 1; i < NUMKARTRESULTS; i++) + if (newAmount > 1) { - spawnchance[i] = ( - totalspawnchance += K_KartGetItemOdds(NULL, &rouletteData, useodds, i) - ); - } + UINT8 j; - if (totalspawnchance > 0) - { - UINT8 newType; - UINT8 newAmount; - - totalspawnchance = P_RandomKey(PR_ITEM_ROULETTE, totalspawnchance); - for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); - - // TODO: this is bad! - // K_KartGetItemResult requires a player - // but item roulette will need rewritten to change this - - newType = K_ItemResultToType(i); - newAmount = K_ItemResultToAmount(i); - - if (newAmount > 1) + for (j = 0; j < newAmount-1; j++) { - UINT8 j; - - for (j = 0; j < newAmount-1; j++) - { - K_CreatePaperItem( - x, y, z, - angle, flip, - newType, 1 - ); - } + K_CreatePaperItem( + x, y, z, + angle, flip, + newType, 1 + ); } + } - drop->threshold = newType; - drop->movecount = 1; - } - else - { - drop->threshold = 1; - drop->movecount = 1; - } + drop->threshold = newType; + drop->movecount = 1; } else { diff --git a/src/k_kart.h b/src/k_kart.h index b297b2a57..76043919b 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -138,6 +138,7 @@ INT32 K_GetKartDriftSparkValueForStage(player_t *player, UINT8 stage); void K_SpawnDriftBoostExplosion(player_t *player, int stage); void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave); void K_KartUpdatePosition(player_t *player); +SINT8 K_GetTotallyRandomResult(UINT8 useodds); mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount); void K_DropItems(player_t *player); void K_DropRocketSneaker(player_t *player); From 3965f7776300bde638cf99b519e285f5f75e9836 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Dec 2022 22:40:29 -0800 Subject: [PATCH 073/128] Add BATTLE_SPAWN_INTERVAL --- src/k_battle.c | 2 +- src/k_battle.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/k_battle.c b/src/k_battle.c index 7079bf3a4..6612b9548 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -350,7 +350,7 @@ UINT8 K_NumEmeralds(player_t *player) void K_RunPaperItemSpawners(void) { const boolean overtime = (battleovertime.enabled >= 10*TICRATE); - tic_t interval = 8*TICRATE; + tic_t interval = BATTLE_SPAWN_INTERVAL; const boolean canmakeemeralds = true; //(!(battlecapsules || bossinfo.boss)); diff --git a/src/k_battle.h b/src/k_battle.h index 12ad78004..d42d0ee09 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -8,6 +8,8 @@ extern "C" { #endif +#define BATTLE_SPAWN_INTERVAL (8*TICRATE) + extern struct battleovertime { UINT16 enabled; ///< Has this been initalized yet? From 71004d4b46d5596b91c372495d77da438be8c637 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Dec 2022 22:55:46 -0800 Subject: [PATCH 074/128] Halve spawn time for Battle items --- src/k_battle.c | 17 ++++++++--------- src/k_battle.h | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/k_battle.c b/src/k_battle.c index 6612b9548..d0bc69c97 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -347,10 +347,15 @@ UINT8 K_NumEmeralds(player_t *player) return num; } +static inline boolean IsOnInterval(tic_t interval) +{ + return ((leveltime - starttime) % interval) == 0; +} + void K_RunPaperItemSpawners(void) { const boolean overtime = (battleovertime.enabled >= 10*TICRATE); - tic_t interval = BATTLE_SPAWN_INTERVAL; + const tic_t interval = BATTLE_SPAWN_INTERVAL; const boolean canmakeemeralds = true; //(!(battlecapsules || bossinfo.boss)); @@ -375,13 +380,7 @@ void K_RunPaperItemSpawners(void) return; } - if (overtime == true) - { - // Double frequency of items - interval /= 2; - } - - if (((leveltime - starttime) % interval) != 0) + if (!IsOnInterval(interval)) { return; } @@ -557,7 +556,7 @@ void K_RunPaperItemSpawners(void) } else { - if (gametyperules & GTR_SPHERES) + if ((gametyperules & GTR_SPHERES) && IsOnInterval(2 * interval)) { drop = K_SpawnSphereBox( spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip), diff --git a/src/k_battle.h b/src/k_battle.h index d42d0ee09..68f3c1432 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -8,7 +8,7 @@ extern "C" { #endif -#define BATTLE_SPAWN_INTERVAL (8*TICRATE) +#define BATTLE_SPAWN_INTERVAL (4*TICRATE) extern struct battleovertime { From 8a92f03f7b969bef684acbff25ff37a99274c50e Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Dec 2022 22:57:01 -0800 Subject: [PATCH 075/128] Set fuse for Battle items --- src/k_battle.h | 1 + src/k_kart.c | 5 +++++ src/p_mobj.c | 1 + 3 files changed, 7 insertions(+) diff --git a/src/k_battle.h b/src/k_battle.h index 68f3c1432..f64cfa967 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -9,6 +9,7 @@ extern "C" { #endif #define BATTLE_SPAWN_INTERVAL (4*TICRATE) +#define BATTLE_DESPAWN_TIME (15*TICRATE) extern struct battleovertime { diff --git a/src/k_kart.c b/src/k_kart.c index bb33f7493..660878d4c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6273,6 +6273,11 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 P_SetTarget(&backdrop->tracer, drop); backdrop->flags2 |= MF2_LINKDRAW; + if (gametyperules & GTR_BUMPERS) + { + drop->fuse = BATTLE_DESPAWN_TIME; + } + return drop; } diff --git a/src/p_mobj.c b/src/p_mobj.c index 8bdd50fd4..f965d32c8 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -9522,6 +9522,7 @@ static boolean P_CanFlickerFuse(mobj_t *mobj) case MT_RANDOMITEM: case MT_EGGMANITEM: case MT_FALLINGROCK: + case MT_FLOATINGITEM: if (mobj->fuse <= TICRATE) { return true; From 5887c361118763eb84c0f443d1ea93a7acf09c7a Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 1 Jan 2023 15:55:47 -0800 Subject: [PATCH 076/128] Show CMAKE_BUILD_TYPE on title screen Includes a hint if optimizations may have been turned off. --- cmake/Comptime.cmake | 16 ++++++++++++++++ src/CMakeLists.txt | 2 +- src/comptime.c | 2 ++ src/config.h.in | 3 +++ src/d_netcmd.c | 7 ++----- src/doomdef.h | 3 ++- src/f_finale.c | 9 +++++++++ 7 files changed, 35 insertions(+), 7 deletions(-) diff --git a/cmake/Comptime.cmake b/cmake/Comptime.cmake index 3859c2aad..ddd79536e 100644 --- a/cmake/Comptime.cmake +++ b/cmake/Comptime.cmake @@ -1,3 +1,5 @@ +cmake_minimum_required(VERSION 3.3 FATAL_ERROR) + set(CMAKE_BINARY_DIR "${BINARY_DIR}") set(CMAKE_CURRENT_BINARY_DIR "${BINARY_DIR}") @@ -10,4 +12,18 @@ git_current_branch(SRB2_COMP_BRANCH) git_summary(SRB2_COMP_REVISION) git_working_tree_dirty(SRB2_COMP_UNCOMMITTED) +if("${CMAKE_BUILD_TYPE}" STREQUAL "") + set(CMAKE_BUILD_TYPE None) +endif() + +# These build types enable optimizations of some kind by default. +set(optimized_build_types "MINSIZEREL;RELEASE;RELWITHDEBINFO") + +string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) +if("${build_type}" IN_LIST optimized_build_types) + set(SRB2_COMP_OPTIMIZED TRUE) +else() + set(SRB2_COMP_OPTIMIZED FALSE) +endif() + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/src/config.h") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 148d12ff4..7293393cb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -154,7 +154,7 @@ add_custom_command( # build time for accurate git information and before anything # that needs it, obviously. add_custom_target(_SRB2_reconf ALL - COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/.. -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/Comptime.cmake + COMMAND ${CMAKE_COMMAND} -DGIT_EXECUTABLE=${GIT_EXECUTABLE} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}/.. -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/Comptime.cmake WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.." ) add_dependencies(SRB2SDL2 _SRB2_reconf) diff --git a/src/comptime.c b/src/comptime.c index 2baef79d6..74b810062 100644 --- a/src/comptime.c +++ b/src/comptime.c @@ -11,6 +11,8 @@ #include "config.h" const char *compbranch = SRB2_COMP_BRANCH; const char *comprevision = SRB2_COMP_REVISION; +const char *comptype = CMAKE_BUILD_TYPE; +const int compoptimized = SRB2_COMP_OPTIMIZED; #elif (defined(COMPVERSION)) #include "comptime.h" diff --git a/src/config.h.in b/src/config.h.in index d7b67cdce..77a205a74 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -20,6 +20,9 @@ #define COMPVERSION_UNCOMMITTED #endif +#define CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" +#cmakedefine01 SRB2_COMP_OPTIMIZED + #endif /* Manually defined asset hashes for non-CMake builds diff --git a/src/d_netcmd.c b/src/d_netcmd.c index df3b86bcf..5d3e00830 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -4774,16 +4774,13 @@ static void Command_Version_f(void) else // 16-bit? 128-bit? CONS_Printf("Bits Unknown "); + CONS_Printf("%s ", comptype); + // No ASM? #ifdef NOASM CONS_Printf("\x85" "NOASM " "\x80"); #endif - // Debug build -#ifdef _DEBUG - CONS_Printf("\x85" "DEBUG " "\x80"); -#endif - // DEVELOP build #if defined(TESTERS) CONS_Printf("\x88" "TESTERS " "\x80"); diff --git a/src/doomdef.h b/src/doomdef.h index 09a5efa4a..72ba17a84 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -664,7 +664,8 @@ UINT32 quickncasehash (const char *p, size_t n) // Compile date and time and revision. extern const char *compdate, *comptime, *comprevision, *compbranch; -extern int compuncommitted; +extern int compuncommitted, compoptimized; +extern const char *comptype; // Disabled code and code under testing // None of these that are disabled in the normal build are guaranteed to work perfectly diff --git a/src/f_finale.c b/src/f_finale.c index bbac13838..36ca452f9 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1918,6 +1918,15 @@ void F_VersionDrawer(void) #else // Regular build addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s", VERSIONSTRING)); #endif + if (compoptimized) + { + addtext(V_ALLOWLOWERCASE|V_TRANSLUCENT, va("%s build", comptype)); + } + else + { + addtext(V_ALLOWLOWERCASE|V_ORANGEMAP, va("%s build (no optimizations)", comptype)); + } + if (compuncommitted) { addtext(V_REDMAP|V_STRINGDANCE, "! UNCOMMITTED CHANGES !"); From 4b9797fe2631e2e1f04cb92f14f2d6bee9f705a7 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 2 Jan 2023 13:08:11 +0000 Subject: [PATCH 077/128] Correct for recursive iteration of multiple mines in succession clobbering filescope `minehitlag` --- src/k_collide.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/k_collide.c b/src/k_collide.c index 06d6900e6..e9e832b3d 100644 --- a/src/k_collide.c +++ b/src/k_collide.c @@ -324,6 +324,11 @@ tic_t K_MineExplodeAttack(mobj_t *actor, fixed_t size, boolean spin) if (!spin) { + if (minehitlag == 0) + { + minehitlag = actor->hitlag; + } + Obj_SpawnBrolyKi(actor, minehitlag); return minehitlag; From 26f3490ba85011164a89531a8c4e4ac5622ebb0a Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 2 Jan 2023 13:32:12 +0000 Subject: [PATCH 078/128] Increased memory safety with cupgrid memory allocation Also clearer variable name for length of cupgrid page in memory --- src/k_menufunc.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 73fab9aaa..191542ca7 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -3568,6 +3568,12 @@ static boolean M_LevelListFromGametype(INT16 gt) if (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES) { + if (first) + { + cupgrid.cappages = 0; + cupgrid.builtgrid = NULL; + } + levellist.newgametype = gt; levellist.levelsearch.typeoflevel = G_TOLFlag(gt); @@ -3595,7 +3601,7 @@ static boolean M_LevelListFromGametype(INT16 gt) { levelsearch_t templevelsearch = levellist.levelsearch; // full copy size_t currentid = 0, highestunlockedid = 0; - const size_t unitlen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); + const size_t pagelen = sizeof(cupheader_t*) * (CUPMENU_COLUMNS * CUPMENU_ROWS); boolean foundany = false; templevelsearch.cup = kartcupheaders; @@ -3606,20 +3612,20 @@ static boolean M_LevelListFromGametype(INT16 gt) I_Error("Can you really call this a racing game, I didn't recieve any Cups on my pillow or anything"); #endif - if (!cupgrid.builtgrid) + if (cupgrid.cappages == 0) { cupgrid.cappages = 2; cupgrid.builtgrid = Z_Calloc( - cupgrid.cappages * unitlen, + cupgrid.cappages * pagelen, PU_STATIC, - cupgrid.builtgrid); + NULL); if (!cupgrid.builtgrid) { I_Error("M_LevelListFromGametype: Not enough memory to allocate builtgrid"); } } - memset(cupgrid.builtgrid, 0, cupgrid.cappages * unitlen); + memset(cupgrid.builtgrid, 0, cupgrid.cappages * pagelen); while (templevelsearch.cup) { @@ -3633,10 +3639,10 @@ static boolean M_LevelListFromGametype(INT16 gt) foundany = true; - if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * unitlen) + if ((currentid * sizeof(cupheader_t*)) >= cupgrid.cappages * pagelen) { // Double the size of the buffer, and clear the other stuff. - const size_t firstlen = cupgrid.cappages * unitlen; + const size_t firstlen = cupgrid.cappages * pagelen; cupgrid.builtgrid = Z_Realloc(cupgrid.builtgrid, firstlen * 2, PU_STATIC, NULL); From e73e0bd835f08c752e8114da08eb5391c55388a9 Mon Sep 17 00:00:00 2001 From: toaster Date: Mon, 2 Jan 2023 20:20:47 +0000 Subject: [PATCH 079/128] Don't treat players with 0 lives that are exiting (definitely just completed a race) as irrelevant in P_CheckRacers counts Fixes special stages not booting you back to the title screen after running out of lives on them --- src/p_inter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p_inter.c b/src/p_inter.c index 9caf37531..9887b8bb7 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -861,7 +861,7 @@ boolean P_CheckRacers(void) // Check if all the players in the race have finished. If so, end the level. for (i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[i] || players[i].spectator || players[i].lives <= 0) + if (!playeringame[i] || players[i].spectator || (players[i].lives <= 0 && !players[i].exiting)) { // Y'all aren't even playing continue; From aa4fd8ab139777dca79e65176e2fe3c14761036b Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 2 Jan 2023 19:47:59 -0500 Subject: [PATCH 080/128] Add functions to initialize savebuffer_t g_demo.c is mostly unaltered because it is made of twigs. --- src/d_clisrv.c | 66 ++++++++++++++--------------------- src/d_netcmd.c | 14 +++++--- src/g_demo.c | 38 +++++++++------------ src/g_game.c | 66 ++++++++++++----------------------- src/k_profiles.c | 15 +++----- src/p_saveg.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ src/p_saveg.h | 7 ++++ src/p_setup.c | 9 ++--- 8 files changed, 174 insertions(+), 130 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 07c756d53..7efe8cf6f 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1141,29 +1141,26 @@ static boolean SV_ResendingSavegameToAnyone(void) static void SV_SendSaveGame(INT32 node, boolean resending) { size_t length, compressedlen; - savebuffer_t save; + savebuffer_t save = {0}; UINT8 *compressedsave; UINT8 *buffertosend; // first save it in a malloced buffer - save.size = NETSAVEGAMESIZE; - save.buffer = (UINT8 *)malloc(save.size); - if (!save.buffer) + if (P_SaveBufferAlloc(&save, NETSAVEGAMESIZE) == false) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); return; } // Leave room for the uncompressed length. - save.p = save.buffer + sizeof(UINT32); - save.end = save.buffer + save.size; + save.p += sizeof(UINT32); P_SaveNetGame(&save, resending); length = save.p - save.buffer; if (length > NETSAVEGAMESIZE) { - free(save.buffer); + P_SaveBufferFree(&save); I_Error("Savegame buffer overrun"); } @@ -1180,7 +1177,7 @@ static void SV_SendSaveGame(INT32 node, boolean resending) if ((compressedlen = lzf_compress(save.buffer + sizeof(UINT32), length - sizeof(UINT32), compressedsave + sizeof(UINT32), length - sizeof(UINT32) - 1))) { // Compressing succeeded; send compressed data - free(save.buffer); + P_SaveBufferFree(&save); // State that we're compressed. buffertosend = compressedsave; @@ -1211,7 +1208,7 @@ static consvar_t cv_dumpconsistency = CVAR_INIT ("dumpconsistency", "Off", CV_SA static void SV_SavedGame(void) { size_t length; - savebuffer_t save; + savebuffer_t save = {0}; char tmpsave[256]; if (!cv_dumpconsistency.value) @@ -1220,22 +1217,18 @@ static void SV_SavedGame(void) sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); // first save it in a malloced buffer - save.size = NETSAVEGAMESIZE; - save.p = save.buffer = (UINT8 *)malloc(save.size); - if (!save.p) + if (P_SaveBufferAlloc(&save, NETSAVEGAMESIZE) == false) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); return; } - save.end = save.buffer + save.size; - P_SaveNetGame(&save, false); length = save.p - save.buffer; if (length > NETSAVEGAMESIZE) { - free(save.buffer); + P_SaveBufferFree(&save); I_Error("Savegame buffer overrun"); } @@ -1243,7 +1236,7 @@ static void SV_SavedGame(void) if (!FIL_WriteFile(tmpsave, save.buffer, length)) CONS_Printf(M_GetText("Didn't save %s for netgame"), tmpsave); - free(save.buffer); + P_SaveBufferFree(&save); } #undef TMPSAVENAME @@ -1253,37 +1246,31 @@ static void SV_SavedGame(void) static void CL_LoadReceivedSavegame(boolean reloading) { - savebuffer_t save; + savebuffer_t save = {0}; size_t length, decompressedlen; char tmpsave[256]; sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); - length = FIL_ReadFile(tmpsave, &save.buffer); - - CONS_Printf(M_GetText("Loading savegame length %s\n"), sizeu1(length)); - if (!length) + if (P_SaveBufferFromFile(&save, tmpsave) == false) { I_Error("Can't read savegame sent"); return; } - save.p = save.buffer; - save.size = length; - save.end = save.buffer + save.size; + length = save.size; + CONS_Printf(M_GetText("Loading savegame length %s\n"), sizeu1(length)); // Decompress saved game if necessary. decompressedlen = READUINT32(save.p); - if(decompressedlen > 0) + if (decompressedlen > 0) { UINT8 *decompressedbuffer = Z_Malloc(decompressedlen, PU_STATIC, NULL); lzf_decompress(save.p, length - sizeof(UINT32), decompressedbuffer, decompressedlen); - Z_Free(save.buffer); - save.p = save.buffer = decompressedbuffer; - save.size = decompressedlen; - save.end = save.buffer + decompressedlen; + P_SaveBufferFree(&save); + P_SaveBufferFromExisting(&save, decompressedbuffer, decompressedlen); } paused = false; @@ -1315,10 +1302,13 @@ static void CL_LoadReceivedSavegame(boolean reloading) } // done - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); + if (unlink(tmpsave) == -1) + { CONS_Alert(CONS_ERROR, M_GetText("Can't delete %s\n"), tmpsave); + } + consistancy[gametic%BACKUPTICS] = Consistancy(); CON_ToggleOff(); @@ -6067,7 +6057,7 @@ void CL_ClearRewinds(void) rewind_t *CL_SaveRewindPoint(size_t demopos) { - savebuffer_t save; + savebuffer_t save = {0}; rewind_t *rewind; if (rewindhead && rewindhead->leveltime + REWIND_POINT_INTERVAL > leveltime) @@ -6077,10 +6067,7 @@ rewind_t *CL_SaveRewindPoint(size_t demopos) if (!rewind) return NULL; - save.buffer = save.p = rewind->savebuffer; - save.size = NETSAVEGAMESIZE; - save.end = save.buffer + save.size; - + P_SaveBufferFromExisting(&save, rewind->savebuffer, NETSAVEGAMESIZE); P_SaveNetGame(&save, false); rewind->leveltime = leveltime; @@ -6093,7 +6080,7 @@ rewind_t *CL_SaveRewindPoint(size_t demopos) rewind_t *CL_RewindToTime(tic_t time) { - savebuffer_t save; + savebuffer_t save = {0}; rewind_t *rewind; while (rewindhead && rewindhead->leveltime > time) @@ -6106,10 +6093,7 @@ rewind_t *CL_RewindToTime(tic_t time) if (!rewindhead) return NULL; - save.buffer = save.p = rewindhead->savebuffer; - save.size = NETSAVEGAMESIZE; - save.end = save.buffer + save.size; - + P_SaveBufferFromExisting(&save, rewindhead->savebuffer, NETSAVEGAMESIZE); P_LoadNetGame(&save, false); wipegamestate = gamestate; // No fading back in! diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 975c165ad..61b737ce9 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -5778,7 +5778,7 @@ static void Command_Togglemodified_f(void) static void Command_Archivetest_f(void) { - savebuffer_t save; + savebuffer_t save = {0}; UINT32 i, wrote; thinker_t *th; if (gamestate != GS_LEVEL) @@ -5794,9 +5794,11 @@ static void Command_Archivetest_f(void) ((mobj_t *)th)->mobjnum = i++; // allocate buffer - save.size = 1024; - save.buffer = save.p = ZZ_Alloc(save.size); - save.end = save.buffer + save.size; + if (P_SaveBufferAlloc(&save, 1024) == false) + { + CONS_Printf("Unable to allocate buffer.\n"); + return; + } // test archive CONS_Printf("LUA_Archive...\n"); @@ -5814,10 +5816,12 @@ static void Command_Archivetest_f(void) LUA_UnArchive(&save, true); i = READUINT8(save.p); if (i != 0x7F || wrote != (UINT32)(save.p - save.buffer)) + { CONS_Printf("Savegame corrupted. (write %u, read %u)\n", wrote, (UINT32)(save.p - save.buffer)); + } // free buffer - Z_Free(save.buffer); + P_SaveBufferFree(&save); CONS_Printf("Done. No crash.\n"); } #endif diff --git a/src/g_demo.c b/src/g_demo.c index 579d786f5..5e39d83fc 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -70,7 +70,7 @@ boolean noblit; // for comparative timing purposes tic_t demostarttime; // for comparative timing purposes static char demoname[MAX_WADPATH]; -static savebuffer_t demobuf; +static savebuffer_t demobuf = {0}; static UINT8 *demotime_p, *demoinfo_p; static UINT8 demoflags; boolean demosynced = true; // console warning message @@ -2019,10 +2019,8 @@ void G_RecordDemo(const char *name) // if (demobuf.buffer) // P_SaveBufferFree(&demobuf); - demobuf.size = maxsize; - demobuf.buffer = (UINT8 *)malloc(maxsize); + P_SaveBufferAlloc(&demobuf, maxsize); demobuf.p = NULL; - demobuf.end = demobuf.buffer + demobuf.size; demo.recording = true; } @@ -2034,10 +2032,8 @@ void G_RecordMetal(void) if (M_CheckParm("-maxdemo") && M_IsNextParm()) maxsize = atoi(M_GetNextParm()) * 1024; - demobuf.size = maxsize; - demobuf.buffer = (UINT8 *)malloc(maxsize); + P_SaveBufferAlloc(&demobuf, maxsize); demobuf.p = NULL; - demobuf.end = demobuf.buffer + demobuf.size; metalrecording = true; } @@ -2982,7 +2978,7 @@ void G_DoPlayDemo(char *defdemoname) if (FIL_CheckExtension(defdemoname)) { //FIL_DefaultExtension(defdemoname, ".lmp"); - if (!FIL_ReadFile(defdemoname, &demobuf.buffer)) + if (P_SaveBufferFromFile(&demobuf, defdemoname) == false) { snprintf(msg, 1024, M_GetText("Failed to read file '%s'.\n"), defdemoname); CONS_Alert(CONS_ERROR, "%s", msg); @@ -2990,20 +2986,20 @@ void G_DoPlayDemo(char *defdemoname) M_StartMessage(msg, NULL, MM_NOTHING); return; } - demobuf.p = demobuf.buffer; } // load demo resource from WAD - else if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR) + else { - snprintf(msg, 1024, M_GetText("Failed to read lump '%s'.\n"), defdemoname); - CONS_Alert(CONS_ERROR, "%s", msg); - gameaction = ga_nothing; - M_StartMessage(msg, NULL, MM_NOTHING); - return; - } - else // it's an internal demo - { - demobuf.buffer = demobuf.p = W_CacheLumpNum(l, PU_STATIC); + if ((l = W_CheckNumForName(defdemoname)) == LUMPERROR) + { + snprintf(msg, 1024, M_GetText("Failed to read lump '%s'.\n"), defdemoname); + CONS_Alert(CONS_ERROR, "%s", msg); + gameaction = ga_nothing; + M_StartMessage(msg, NULL, MM_NOTHING); + return; + } + + P_SaveBufferFromLump(&demobuf, l); #if defined(SKIPERRORS) && !defined(DEVELOP) skiperrors = true; // SRB2Kart: Don't print warnings for staff ghosts, since they'll inevitably happen when we make bugfixes/changes... #endif @@ -3945,7 +3941,7 @@ ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill) WriteDemoChecksum(); saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuf.buffer, demobuf.p - demobuf.buffer); // finally output the file. } - free(demobuf.buffer); + Z_Free(demobuf.buffer); metalrecording = false; if (saved) I_Error("Saved to %sMS.LMP", G_BuildMapName(gamemap)); @@ -4165,7 +4161,7 @@ void G_SaveDemo(void) if (FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer)) // finally output the file. demo.savemode = DSM_SAVED; - free(demobuf.buffer); + Z_Free(demobuf.buffer); demo.recording = false; if (!modeattacking) diff --git a/src/g_game.c b/src/g_game.c index da3a58986..3c11de495 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -4303,12 +4303,11 @@ void G_LoadGameSettings(void) // Loads the main data file, which stores information such as emblems found, etc. void G_LoadGameData(void) { - size_t length; UINT32 i, j; UINT32 versionID; UINT8 versionMinor; UINT8 rtemp; - savebuffer_t save; + savebuffer_t save = {0}; //For records UINT32 numgamedatamapheaders; @@ -4337,16 +4336,13 @@ void G_LoadGameData(void) return; } - length = FIL_ReadFile(va(pandf, srb2home, gamedatafilename), &save.buffer); - if (!length) + if (P_SaveBufferFromFile(&save, va(pandf, srb2home, gamedatafilename)) == false) { // No gamedata. We can save a new one. gamedata->loaded = true; return; } - save.p = save.buffer; - // Version check versionID = READUINT32(save.p); if (versionID != GD_VERSIONCHECK) @@ -4355,16 +4351,14 @@ void G_LoadGameData(void) if (strcmp(srb2home,".")) gdfolder = srb2home; - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); I_Error("Game data is not for Ring Racers v2.0.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); } versionMinor = READUINT8(save.p); if (versionMinor > GD_VERSIONMINOR) { - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); I_Error("Game data is from the future! (expected %d, got %d)", GD_VERSIONMINOR, versionMinor); } @@ -4475,8 +4469,7 @@ void G_LoadGameData(void) } // done - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); // Don't consider loaded until it's a success! // It used to do this much earlier, but this would cause the gamedata to @@ -4496,8 +4489,7 @@ void G_LoadGameData(void) if (strcmp(srb2home,".")) gdfolder = srb2home; - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); I_Error("Corrupt game data file.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); } @@ -4510,7 +4502,7 @@ void G_SaveGameData(void) size_t length; INT32 i, j; UINT8 btemp; - savebuffer_t save; + savebuffer_t save = {0}; if (!gamedata->loaded) return; // If never loaded (-nodata), don't save @@ -4530,14 +4522,11 @@ void G_SaveGameData(void) } length += nummapheaders * (MAXMAPLUMPNAME+1+4+4); - save.size = length; - save.p = save.buffer = (UINT8 *)malloc(save.size); - if (!save.p) + if (P_SaveBufferAlloc(&save, length) == false) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for saving game data\n")); return; } - save.end = save.buffer + save.size; // Version test @@ -4624,7 +4613,7 @@ void G_SaveGameData(void) length = save.p - save.buffer; FIL_WriteFile(va(pandf, srb2home, gamedatafilename), save.buffer, length); - free(save.buffer); + P_SaveBufferFree(&save); // Also save profiles here. PR_SaveProfiles(); @@ -4638,10 +4627,9 @@ void G_SaveGameData(void) // void G_LoadGame(UINT32 slot, INT16 mapoverride) { - size_t length; char vcheck[VERSIONSIZE]; char savename[255]; - savebuffer_t save; + savebuffer_t save = {0}; // memset savedata to all 0, fixes calling perfectly valid saves corrupt because of bots memset(&savedata, 0, sizeof(savedata)); @@ -4656,17 +4644,12 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) else sprintf(savename, savegamename, slot); - length = FIL_ReadFile(savename, &save.buffer); - if (!length) + if (P_SaveBufferFromFile(&save, savename) == false) { CONS_Printf(M_GetText("Couldn't read file %s\n"), savename); return; } - save.p = save.buffer; - save.size = length; - save.end = save.buffer + save.size; - memset(vcheck, 0, sizeof (vcheck)); sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); if (strcmp((const char *)save.p, (const char *)vcheck)) @@ -4679,7 +4662,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) M_ClearMenus(true); // so ESC backs out to title M_StartMessage(M_GetText("Save game from different version\n\nPress ESC\n"), NULL, MM_NOTHING); Command_ExitGame_f(); - Z_Free(save.buffer); + P_SaveBufferFree(&save); // no cheating! memset(&savedata, 0, sizeof(savedata)); @@ -4714,7 +4697,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride) } // done - Z_Free(save.buffer); + P_SaveBufferFree(&save); // gameaction = ga_nothing; // G_SetGamestate(GS_LEVEL); @@ -4740,7 +4723,7 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) boolean saved; char savename[256] = ""; const char *backup; - savebuffer_t save; + savebuffer_t save = {0}; if (marathonmode) strcpy(savename, liveeventbackup); @@ -4753,14 +4736,11 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) char name[VERSIONSIZE]; size_t length; - save.size = SAVEGAMESIZE; - save.p = save.buffer = (UINT8 *)malloc(save.size); - if (!save.p) + if (P_SaveBufferAlloc(&save, SAVEGAMESIZE) == false) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for saving game data\n")); return; } - save.end = save.buffer + save.size; memset(name, 0, sizeof (name)); sprintf(name, (marathonmode ? "back-up %d" : "version %d"), VERSION); @@ -4778,7 +4758,7 @@ void G_SaveGame(UINT32 slot, INT16 mapnum) length = save.p - save.buffer; saved = FIL_WriteFile(backup, save.buffer, length); - free(save.buffer); + P_SaveBufferFree(&save); } gameaction = ga_nothing; @@ -4798,7 +4778,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives) char vcheck[VERSIONSIZE]; char savename[255]; const char *backup; - savebuffer_t save; + savebuffer_t save = {0}; if (marathonmode) strcpy(savename, liveeventbackup); @@ -4806,22 +4786,19 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives) sprintf(savename, savegamename, slot); backup = va("%s",savename); - length = FIL_ReadFile(savename, &save.buffer); - if (!length) + if (P_SaveBufferFromFile(&save, savename) == false) { CONS_Printf(M_GetText("Couldn't read file %s\n"), savename); return; } + length = save.size; + { char temp[sizeof(timeattackfolder)]; UINT8 *lives_p; SINT8 pllives; - save.p = save.buffer; - save.size = length; - save.end = save.buffer + save.size; - // Version check memset(vcheck, 0, sizeof (vcheck)); sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION); @@ -4892,9 +4869,8 @@ cleanup: CONS_Printf(M_GetText("Game saved.\n")); else if (!saved) CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename)); - Z_Free(save.buffer); - save.p = save.buffer = NULL; + P_SaveBufferFree(&save); } #undef CHECKPOS #undef BADSAVE diff --git a/src/k_profiles.c b/src/k_profiles.c index 1163b1f8d..313dc84f8 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -217,16 +217,13 @@ void PR_SaveProfiles(void) size_t length = 0; const size_t headerlen = strlen(PROFILEHEADER); UINT8 i, j, k; - savebuffer_t save; + savebuffer_t save = {0}; - save.size = sizeof(UINT32) + (numprofiles * sizeof(profile_t)); - save.p = save.buffer = (UINT8 *)malloc(save.size); - if (!save.p) + if (P_SaveBufferAlloc(&save, sizeof(UINT32) + (numprofiles * sizeof(profile_t))) == false) { I_Error("No more free memory for saving profiles\n"); return; } - save.end = save.buffer + save.size; // Add header. WRITESTRINGN(save.p, PROFILEHEADER, headerlen); @@ -278,7 +275,6 @@ void PR_SaveProfiles(void) void PR_LoadProfiles(void) { - size_t length = 0; const size_t headerlen = strlen(PROFILEHEADER); UINT8 i, j, k, version; profile_t *dprofile = PR_MakeProfile( @@ -289,18 +285,15 @@ void PR_LoadProfiles(void) gamecontroldefault, true ); - savebuffer_t save; + savebuffer_t save = {0}; - length = FIL_ReadFile(va(pandf, srb2home, PROFILESFILE), &save.buffer); - if (!length) + if (P_SaveBufferFromFile(&save, va(pandf, srb2home, PROFILESFILE)) == false) { // No profiles. Add the default one. PR_AddProfile(dprofile); return; } - save.p = save.buffer; - if (strncmp(PROFILEHEADER, (const char *)save.buffer, headerlen)) { const char *gdfolder = "the Ring Racers folder"; diff --git a/src/p_saveg.c b/src/p_saveg.c index 562616b63..946e77faa 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -5226,3 +5226,92 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) return P_UnArchiveLuabanksAndConsistency(save); } + +boolean P_SaveBufferZAlloc(savebuffer_t *save, size_t alloc_size, INT32 tag, void *user) +{ + I_Assert(save->buffer == NULL); + save->buffer = (UINT8 *)Z_Malloc(alloc_size, tag, user); + + if (save->buffer == NULL) + { + return false; + } + + save->size = alloc_size; + save->p = save->buffer; + save->end = save->buffer + save->size; + + return true; +} + +boolean P_SaveBufferFromExisting(savebuffer_t *save, UINT8 *existing_buffer, size_t existing_size) +{ + I_Assert(save->buffer == NULL); + + if (existing_buffer == NULL || existing_size == 0) + { + return false; + } + + save->buffer = existing_buffer; + save->size = existing_size; + + save->p = save->buffer; + save->end = save->buffer + save->size; + + return true; +} + +boolean P_SaveBufferFromLump(savebuffer_t *save, lumpnum_t lump) +{ + I_Assert(save->buffer == NULL); + + if (lump == LUMPERROR) + { + return false; + } + + save->buffer = (UINT8 *)W_CacheLumpNum(lump, PU_STATIC); + + if (save->buffer == NULL) + { + return false; + } + + save->size = W_LumpLength(lump); + + save->p = save->buffer; + save->end = save->buffer + save->size; + + return true; +} + +boolean P_SaveBufferFromFile(savebuffer_t *save, char const *name) +{ + size_t len = 0; + + I_Assert(save->buffer == NULL); + len = FIL_ReadFile(name, &save->buffer); + + if (len != 0) + { + save->size = len; + + save->p = save->buffer; + save->end = save->buffer + save->size; + } + + return len; +} + +static void P_SaveBufferInvalidate(savebuffer_t *save) +{ + save->buffer = save->p = save->end = NULL; + save->size = 0; +} + +void P_SaveBufferFree(savebuffer_t *save) +{ + Z_Free(save->buffer); + P_SaveBufferInvalidate(save); +} diff --git a/src/p_saveg.h b/src/p_saveg.h index a0b0f8587..66ba58b00 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -57,6 +57,13 @@ struct savebuffer_t size_t size; }; +boolean P_SaveBufferZAlloc(savebuffer_t *save, size_t alloc_size, INT32 tag, void *user); +#define P_SaveBufferAlloc(a,b) P_SaveBufferZAlloc(a, b, PU_STATIC, NULL) +boolean P_SaveBufferFromExisting(savebuffer_t *save, UINT8 *existing_buffer, size_t existing_size); +boolean P_SaveBufferFromLump(savebuffer_t *save, lumpnum_t lump); +boolean P_SaveBufferFromFile(savebuffer_t *save, char const *name); +void P_SaveBufferFree(savebuffer_t *save); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/p_setup.c b/src/p_setup.c index e2e11a613..407f9f42f 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -722,20 +722,15 @@ void P_WriteThings(void) const char * filename; size_t i, length; mapthing_t *mt; - savebuffer_t save; + savebuffer_t save = {0}; INT16 temp; - save.size = nummapthings * sizeof (mapthing_t); - save.p = save.buffer = (UINT8 *)malloc(nummapthings * sizeof (mapthing_t)); - - if (!save.p) + if (P_SaveBufferAlloc(&save, nummapthings * sizeof (mapthing_t)) == false) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for thing writing!\n")); return; } - save.end = save.buffer + save.size; - mt = mapthings; for (i = 0; i < nummapthings; i++, mt++) { From 5bdbbd1695e55b231ab2f080ff9cc4b927d05ebf Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 2 Jan 2023 20:04:01 -0800 Subject: [PATCH 081/128] Comptime.cmake: escape \backslashes and "quotes" in git subject line --- cmake/Comptime.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmake/Comptime.cmake b/cmake/Comptime.cmake index ddd79536e..f50faf933 100644 --- a/cmake/Comptime.cmake +++ b/cmake/Comptime.cmake @@ -9,9 +9,11 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Mo include(GitUtilities) git_current_branch(SRB2_COMP_BRANCH) -git_summary(SRB2_COMP_REVISION) git_working_tree_dirty(SRB2_COMP_UNCOMMITTED) +git_summary(revision) +string(REGEX REPLACE "([\"\\])" "\\\\\\1" SRB2_COMP_REVISION "${revision}") + if("${CMAKE_BUILD_TYPE}" STREQUAL "") set(CMAKE_BUILD_TYPE None) endif() From aab661cad86045b998792fe89166b023c5325d08 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Jan 2023 04:11:42 -0800 Subject: [PATCH 082/128] Split shields and SPB special conditions from K_KartGetItemOdds --- src/k_roulette.c | 149 ++++++++++++++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 53 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index da12d5209..7ffb57461 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -417,6 +417,99 @@ static UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers return pdis; } +/*-------------------------------------------------- + static boolean K_DenyShieldOdds(kartitems_t item) + + Checks if this type of shield already exists in + another player's inventory. + + Input Arguments:- + item - The item type of the shield. + + Return:- + Whether this item is a shield and may not be awarded + at this time. +--------------------------------------------------*/ +static boolean K_DenyShieldOdds(kartitems_t item) +{ + INT32 shieldType = K_GetShieldFromItem(item); + + switch (shieldType) + { + case KSHIELD_NONE: + /* Marble Garden Top is not REALLY + a Sonic 3 shield */ + case KSHIELD_TOP: + { + break; + } + + default: + { + size_t i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + if (shieldType == K_GetShieldFromItem(players[i].itemtype)) + { + // Don't allow more than one of each shield type at a time + return true; + } + } + } + } + + return false; +} + +/*-------------------------------------------------- + static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position) + + Adjust odds of SPB according to distances of first and + second place players. + + Input Arguments:- + roulette - The roulette data that we intend to + insert this item into. + position - Position of player to consider for these + odds. + + Return:- + New item odds. +--------------------------------------------------*/ +static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position) +{ + if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done + || position == 1) // No SPB for 1st ever + { + return 0; + } + else + { + const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST); + const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; + const fixed_t maxOdds = 20 << FRACBITS; + fixed_t multiplier = FixedDiv(dist, distRange); + + if (multiplier < 0) + { + multiplier = 0; + } + + if (multiplier > FRACUNIT) + { + multiplier = FRACUNIT; + } + + return FixedMul(maxOdds, multiplier); + } +} + /*-------------------------------------------------- INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) @@ -428,14 +521,11 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, boolean rival = false; UINT8 position = 0; - INT32 shieldType = KSHIELD_NONE; - boolean powerItem = false; boolean cooldownOnStart = false; boolean notNearEnd = false; fixed_t newOdds = 0; - size_t i; I_Assert(roulette != NULL); @@ -478,33 +568,9 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, */ (void)bot; - shieldType = K_GetShieldFromItem(item); - switch (shieldType) + if (K_DenyShieldOdds(item)) { - case KSHIELD_NONE: - /* Marble Garden Top is not REALLY - a Sonic 3 shield */ - case KSHIELD_TOP: - { - break; - } - - default: - { - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] == false || players[i].spectator == true) - { - continue; - } - - if (shieldType == K_GetShieldFromItem(players[i].itemtype)) - { - // Don't allow more than one of each shield type at a time - return 0; - } - } - } + return 0; } if (gametype == GT_BATTLE) @@ -581,30 +647,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, if (specialstageinfo.valid == false) { - if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done - || position == 1) // No SPB for 1st ever - { - return 0; - } - else - { - const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST); - const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; - const fixed_t maxOdds = 20 << FRACBITS; - fixed_t multiplier = FixedDiv(dist, distRange); - - if (multiplier < 0) - { - multiplier = 0; - } - - if (multiplier > FRACUNIT) - { - multiplier = FRACUNIT; - } - - newOdds = FixedMul(maxOdds, multiplier); - } + newOdds = K_AdjustSPBOdds(roulette, position); } break; } From a37eccd7080fa1b715bdeb0917ee53a15167584b Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Jan 2023 04:32:21 -0800 Subject: [PATCH 083/128] Split powerItem etc conditions from K_KartGetItemOdds --- src/k_roulette.c | 124 +++++++++++++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 47 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 7ffb57461..d2a0066cb 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -510,6 +510,62 @@ static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position) } } +typedef struct { + boolean powerItem; + boolean cooldownOnStart; + boolean notNearEnd; + + // gameplay state + boolean rival; // player is a bot Rival +} itemconditions_t; + +/*-------------------------------------------------- + static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette) + + Adjust item odds to certain group conditions. + + Input Arguments:- + newOdds - The item odds to adjust. + conditions - The conditions state. + roulette - The roulette data that we intend to + insert this item into. + + Return:- + New item odds. +--------------------------------------------------*/ +static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette) +{ + if ((conditions->cooldownOnStart == true) && (leveltime < (30*TICRATE) + starttime)) + { + // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) + newOdds = 0; + } + else if ((conditions->notNearEnd == true) && (roulette->baseDist < ENDDIST)) + { + // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) + newOdds = 0; + } + else if (conditions->powerItem == true) + { + // This item is a "power item". This activates "frantic item" toggle related functionality. + if (franticitems == true) + { + // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. + newOdds *= 2; + } + + if (conditions->rival == true) + { + // The Rival bot gets frantic-like items, also :p + newOdds *= 2; + } + + newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(roulette->playing)); + } + + return newOdds; +} + /*-------------------------------------------------- INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) @@ -518,12 +574,14 @@ static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position) INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) { boolean bot = false; - boolean rival = false; UINT8 position = 0; - boolean powerItem = false; - boolean cooldownOnStart = false; - boolean notNearEnd = false; + itemconditions_t conditions = { + .powerItem = false, + .cooldownOnStart = false, + .notNearEnd = false, + .rival = false, + }; fixed_t newOdds = 0; @@ -535,7 +593,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, if (player != NULL) { bot = player->bot; - rival = (bot == true && player->botvars.rival == true); + conditions.rival = (bot == true && player->botvars.rival == true); position = player->position; } @@ -597,7 +655,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, case KITEM_EGGMAN: case KITEM_SUPERRING: { - notNearEnd = true; + conditions.notNearEnd = true; break; } @@ -611,15 +669,15 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, case KRITEM_QUADORBINAUT: case KRITEM_DUALJAWZ: { - powerItem = true; + conditions.powerItem = true; break; } case KITEM_HYUDORO: case KRITEM_TRIPLEBANANA: { - powerItem = true; - notNearEnd = true; + conditions.powerItem = true; + conditions.notNearEnd = true; break; } @@ -629,15 +687,15 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, case KITEM_BUBBLESHIELD: case KITEM_FLAMESHIELD: { - cooldownOnStart = true; - powerItem = true; + conditions.cooldownOnStart = true; + conditions.powerItem = true; break; } case KITEM_SPB: { - cooldownOnStart = true; - notNearEnd = true; + conditions.cooldownOnStart = true; + conditions.notNearEnd = true; if ((gametyperules & GTR_CIRCUIT) == 0) { @@ -654,9 +712,9 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, case KITEM_SHRINK: { - cooldownOnStart = true; - powerItem = true; - notNearEnd = true; + conditions.cooldownOnStart = true; + conditions.powerItem = true; + conditions.notNearEnd = true; if (roulette->playing - 1 <= roulette->exiting) { @@ -667,8 +725,8 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, case KITEM_LIGHTNINGSHIELD: { - cooldownOnStart = true; - powerItem = true; + conditions.cooldownOnStart = true; + conditions.powerItem = true; if (spbplace != -1) { @@ -689,35 +747,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, return newOdds; } - if ((cooldownOnStart == true) && (leveltime < (30*TICRATE)+starttime)) - { - // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) - newOdds = 0; - } - else if ((notNearEnd == true) && (roulette->baseDist < ENDDIST)) - { - // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) - newOdds = 0; - } - else if (powerItem == true) - { - // This item is a "power item". This activates "frantic item" toggle related functionality. - if (franticitems == true) - { - // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. - newOdds *= 2; - } - - if (rival == true) - { - // The Rival bot gets frantic-like items, also :p - newOdds *= 2; - } - - newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(roulette->playing)); - } - - newOdds = FixedInt(FixedRound(newOdds)); + newOdds = FixedInt(FixedRound(K_AdjustItemOddsToConditions(newOdds, &conditions, roulette))); return newOdds; } From 339cb1e8b81be084adfe0168b24bebaa09f70ace Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Jan 2023 04:44:49 -0800 Subject: [PATCH 084/128] K_KartGetItemOdds: apply GTR_CIRCUIT checks - No restriction on whether more than one player may receive the same type of shield outside of Race. - SPB is now technically allowed outside of Race, if item odds permit. - powerItems and assorted checks no longer apply outside of Race. --- src/k_roulette.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index d2a0066cb..da57abd0e 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -434,6 +434,11 @@ static boolean K_DenyShieldOdds(kartitems_t item) { INT32 shieldType = K_GetShieldFromItem(item); + if ((gametyperules & GTR_CIRCUIT) == 0) + { + return false; + } + switch (shieldType) { case KSHIELD_NONE: @@ -535,6 +540,12 @@ typedef struct { --------------------------------------------------*/ static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette) { + // None if this applies outside of Race modes (for now?) + if ((gametyperules & GTR_CIRCUIT) == 0) + { + return newOdds; + } + if ((conditions->cooldownOnStart == true) && (leveltime < (30*TICRATE) + starttime)) { // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) @@ -697,13 +708,8 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, conditions.cooldownOnStart = true; conditions.notNearEnd = true; - if ((gametyperules & GTR_CIRCUIT) == 0) - { - // Needs to be a race. - return 0; - } - - if (specialstageinfo.valid == false) + if ((gametyperules & GTR_CIRCUIT) && + specialstageinfo.valid == false) { newOdds = K_AdjustSPBOdds(roulette, position); } @@ -716,7 +722,8 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, conditions.powerItem = true; conditions.notNearEnd = true; - if (roulette->playing - 1 <= roulette->exiting) + if ((gametyperules & GTR_CIRCUIT) && + roulette->playing - 1 <= roulette->exiting) { return 0; } @@ -728,7 +735,7 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, conditions.cooldownOnStart = true; conditions.powerItem = true; - if (spbplace != -1) + if ((gametyperules & GTR_CIRCUIT) && spbplace != -1) { return 0; } From ae16f124dbe6432cc3e7354a0d2076b676998e7d Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Jan 2023 04:59:32 -0800 Subject: [PATCH 085/128] K_GetTotallyRandomResult: use NULL roulette data This fixes random paper items spawner RNG not advancing. --- src/k_kart.c | 7 +++---- src/k_roulette.c | 17 +++++++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 43675a539..e01d85db2 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6219,19 +6219,18 @@ void K_DropHnextList(player_t *player, boolean keepshields) SINT8 K_GetTotallyRandomResult(UINT8 useodds) { - itemroulette_t rouletteData = {0}; INT32 spawnchance[NUMKARTRESULTS]; INT32 totalspawnchance = 0; INT32 i; memset(spawnchance, 0, sizeof (spawnchance)); - K_FillItemRouletteData(NULL, &rouletteData); - for (i = 1; i < NUMKARTRESULTS; i++) { + // Avoid calling K_FillItemRouletteData since that + // function resets PR_ITEM_ROULETTE. spawnchance[i] = ( - totalspawnchance += K_KartGetItemOdds(NULL, &rouletteData, useodds, i) + totalspawnchance += K_KartGetItemOdds(NULL, NULL, useodds, i) ); } diff --git a/src/k_roulette.c b/src/k_roulette.c index da57abd0e..88cf2a3bb 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -489,6 +489,8 @@ static boolean K_DenyShieldOdds(kartitems_t item) --------------------------------------------------*/ static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position) { + I_Assert(roulette != NULL); + if (roulette->firstDist < ENDDIST*2 // No SPB when 1st is almost done || position == 1) // No SPB for 1st ever { @@ -551,7 +553,7 @@ static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemcondition // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) newOdds = 0; } - else if ((conditions->notNearEnd == true) && (roulette->baseDist < ENDDIST)) + else if ((conditions->notNearEnd == true) && (roulette != NULL && roulette->baseDist < ENDDIST)) { // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) newOdds = 0; @@ -571,7 +573,10 @@ static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemcondition newOdds *= 2; } - newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(roulette->playing)); + if (roulette != NULL) + { + newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(roulette->playing)); + } } return newOdds; @@ -596,8 +601,6 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, fixed_t newOdds = 0; - I_Assert(roulette != NULL); - I_Assert(item > KITEM_NONE); // too many off by one scenarioes. I_Assert(item < NUMKARTRESULTS); @@ -708,7 +711,8 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, conditions.cooldownOnStart = true; conditions.notNearEnd = true; - if ((gametyperules & GTR_CIRCUIT) && + if (roulette != NULL && + (gametyperules & GTR_CIRCUIT) && specialstageinfo.valid == false) { newOdds = K_AdjustSPBOdds(roulette, position); @@ -722,7 +726,8 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, conditions.powerItem = true; conditions.notNearEnd = true; - if ((gametyperules & GTR_CIRCUIT) && + if (roulette != NULL && + (gametyperules & GTR_CIRCUIT) && roulette->playing - 1 <= roulette->exiting) { return 0; From 3549625095a40a17bc09a6cc79c66e802b577fdd Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 1 Jan 2023 01:39:30 -0800 Subject: [PATCH 086/128] Add item monitor states SPR_MSHD SPR_IMDB SPR_MTWK S_MONITOR_DAMAGE S_MONITOR_DEATH S_MONITOR_SCREEN1A S_MONITOR_SCREEN1B S_MONITOR_SCREEN2A S_MONITOR_SCREEN2B S_MONITOR_SCREEN3A S_MONITOR_SCREEN3B S_MONITOR_SCREEN4A S_MONITOR_SCREEN4B S_MONITOR_STAND S_MONITOR_CRACKA S_MONITOR_CRACKB S_MONITOR_BIG_SHARD S_MONITOR_SMALL_SHARD S_MONITOR_TWINKLE MT_MONITOR MT_MONITOR_PART MT_MONITOR_SHARD --- src/deh_tables.c | 21 ++++++++++ src/info.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++ src/info.h | 24 +++++++++++ 3 files changed, 147 insertions(+) diff --git a/src/deh_tables.c b/src/deh_tables.c index 0c369b92e..474db3b43 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3283,6 +3283,24 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi //"S_ITEMCAPSULE_BOTTOM", //"S_ITEMCAPSULE_INSIDE", + "S_MONITOR_DAMAGE", + "S_MONITOR_DEATH", + "S_MONITOR_SCREEN1A", + "S_MONITOR_SCREEN1B", + "S_MONITOR_SCREEN2A", + "S_MONITOR_SCREEN2B", + "S_MONITOR_SCREEN3A", + "S_MONITOR_SCREEN3B", + "S_MONITOR_SCREEN4A", + "S_MONITOR_SCREEN4B", + "S_MONITOR_STAND", + "S_MONITOR_CRACKA", + "S_MONITOR_CRACKB", + + "S_MONITOR_BIG_SHARD", + "S_MONITOR_SMALL_SHARD", + "S_MONITOR_TWINKLE", + "S_MAGICIANBOX", "S_MAGICIANBOXTOP", "S_MAGICIANBOXBOTTOM", @@ -5298,6 +5316,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_FLOATINGITEM", "MT_ITEMCAPSULE", "MT_ITEMCAPSULE_PART", + "MT_MONITOR", + "MT_MONITOR_PART", + "MT_MONITOR_SHARD", "MT_MAGICIANBOX", "MT_SIGNSPARKLE", diff --git a/src/info.c b/src/info.c index 8e3757190..4b748f796 100644 --- a/src/info.c +++ b/src/info.c @@ -548,6 +548,9 @@ char sprnames[NUMSPRITES + 1][5] = "MGBX", // Heavy Magician transform box "MGBT", // Heavy Magician transform box top "MGBB", // Heavy Magician transform box bottom + "MSHD", // Item Monitor Big Shard + "IMDB", // Item Monitor Small Shard (Debris) + "MTWK", // Item Monitor Glass Twinkle "WIPD", // Wipeout dust trail "DRIF", // Drift Sparks @@ -3900,6 +3903,24 @@ state_t states[NUMSTATES] = //{SPR_ICAP, FF_FLOORSPRITE|4, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_BOTTOM //{SPR_ICAP, FF_FLOORSPRITE|5, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_INSIDE + {SPR_NULL, 0, 1, {NULL}, 6, 1, S_SPAWNSTATE}, // S_MONITOR_DAMAGE + {SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL}, // S_MONITOR_DEATH + {SPR_IMON, FF_PAPERSPRITE|1, 1, {NULL}, 3, 1, S_MONITOR_SCREEN1B}, // S_MONITOR_SCREEN1A + {SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN2A}, // S_MONITOR_SCREEN1B + {SPR_IMON, FF_PAPERSPRITE|2, 1, {NULL}, 3, 1, S_MONITOR_SCREEN2B}, // S_MONITOR_SCREEN2A + {SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN3A}, // S_MONITOR_SCREEN2B + {SPR_IMON, FF_PAPERSPRITE|3, 1, {NULL}, 3, 1, S_MONITOR_SCREEN3B}, // S_MONITOR_SCREEN3A + {SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN4A}, // S_MONITOR_SCREEN3B + {SPR_IMON, FF_PAPERSPRITE|4, 1, {NULL}, 3, 1, S_MONITOR_SCREEN4B}, // S_MONITOR_SCREEN4A + {SPR_IMON, FF_PAPERSPRITE|0, 1, {NULL}, 3, 1, S_MONITOR_SCREEN1A}, // S_MONITOR_SCREEN4B + {SPR_IMON, FF_PAPERSPRITE|5, -1, {NULL}, 3, 1, S_NULL}, // S_MONITOR_STAND + {SPR_NULL, FF_PAPERSPRITE|FF_TRANS50|FF_ADD|6, -1, {NULL}, 3, 35, S_NULL}, // S_MONITOR_CRACKA + {SPR_NULL, FF_PAPERSPRITE|FF_TRANS50|FF_SUBTRACT|10, -1, {NULL}, 3, 35, S_NULL}, // S_MONITOR_CRACKB + + {SPR_MSHD, FF_FULLBRIGHT|FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 7, 2, S_NULL}, // S_MONITOR_BIG_SHARD + {SPR_IMDB, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_MONITOR_SMALL_SHARD + {SPR_MTWK, FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_MONITOR_TWINKLE + {SPR_MGBX, FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX {SPR_MGBT, FF_FLOORSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_TOP {SPR_MGBB, FF_FLOORSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_BOTTOM @@ -22420,6 +22441,87 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_MONITOR + -1, // doomednum + S_INVISIBLE, // spawnstate + FRACUNIT, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_MONITOR_DAMAGE, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_MONITOR_DEATH, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 112*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SOLID|MF_SHOOTABLE|MF_SLIDEME|MF_DONTENCOREMAP|MF_NOHITLAGFORME, // flags + S_NULL // raisestate + }, + + { // MT_MONITOR_PART + -1, // doomednum + S_INVISIBLE, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 112*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOSQUISH, // flags + S_NULL // raisestate + }, + + { // MT_MONITOR_SHARD + -1, // doomednum + S_MONITOR_BIG_SHARD, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 32*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOSQUISH, // flags + S_NULL // raisestate + }, + { // MT_MAGICIANBOX -1, // doomednum S_MAGICIANBOX, // spawnstate diff --git a/src/info.h b/src/info.h index d0d5228e3..780b53320 100644 --- a/src/info.h +++ b/src/info.h @@ -1099,6 +1099,9 @@ typedef enum sprite SPR_MGBX, // Heavy Magician transform box SPR_MGBT, // Heavy Magician transform box top SPR_MGBB, // Heavy Magician transform box bottom + SPR_MSHD, // Item Monitor Big Shard + SPR_IMDB, // Item Monitor Small Shard (Debris) + SPR_MTWK, // Item Monitor Glass Twinkle SPR_WIPD, // Wipeout dust trail SPR_DRIF, // Drift Sparks @@ -4309,6 +4312,24 @@ typedef enum state //S_ITEMCAPSULE_BOTTOM, //S_ITEMCAPSULE_INSIDE, + S_MONITOR_DAMAGE, + S_MONITOR_DEATH, + S_MONITOR_SCREEN1A, + S_MONITOR_SCREEN1B, + S_MONITOR_SCREEN2A, + S_MONITOR_SCREEN2B, + S_MONITOR_SCREEN3A, + S_MONITOR_SCREEN3B, + S_MONITOR_SCREEN4A, + S_MONITOR_SCREEN4B, + S_MONITOR_STAND, + S_MONITOR_CRACKA, + S_MONITOR_CRACKB, + + S_MONITOR_BIG_SHARD, + S_MONITOR_SMALL_SHARD, + S_MONITOR_TWINKLE, + S_MAGICIANBOX, S_MAGICIANBOX_TOP, S_MAGICIANBOX_BOTTOM, @@ -6360,6 +6381,9 @@ typedef enum mobj_type MT_FLOATINGITEM, MT_ITEMCAPSULE, MT_ITEMCAPSULE_PART, + MT_MONITOR, + MT_MONITOR_PART, + MT_MONITOR_SHARD, MT_MAGICIANBOX, MT_SIGNSPARKLE, From 3491bd0b1d7ff96e3cbbff0e921966815ab56705 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Sep 2022 16:53:23 -0700 Subject: [PATCH 087/128] Add Battle monitors - Includes a struct definition for symmetrical objects made out of papersprite sides. - Dimensions of papersprite sides are looked up using sprite cache. - Monitors may contain multiple types of items. - Item RNG is deterministic from the time the monitor is spawned but the item types are not stored in memory. Instead the RNG seed is restored every time an item type needs to be determined. Item types need to be determined every time the icon on the monitor's screen changes and when the monitor is popped and drops all its items. - Monitors sparkle like emeralds if there is an emerald inside. - Monitors take damage from players simply bumping into them. The damage scales up with speed and weight. - Activating a lightning shield in proximity decimates the monitor into being able to be destroyed in one hit by anything thereafter. - All throwable / deployable items destroy a monitor in one hit. --- src/k_objects.h | 10 + src/objects/CMakeLists.txt | 1 + src/objects/monitor.c | 691 +++++++++++++++++++++++++++++++++++++ src/p_inter.c | 15 + src/p_map.c | 12 + src/p_mobj.c | 39 ++- 6 files changed, 765 insertions(+), 3 deletions(-) create mode 100644 src/objects/monitor.c diff --git a/src/k_objects.h b/src/k_objects.h index 0078aeae6..5ded33e2f 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -73,6 +73,16 @@ void Obj_UFOPieceRemoved(mobj_t *piece); mobj_t *Obj_CreateSpecialUFO(void); UINT32 K_GetSpecialUFODistance(void); +/* Monitors */ +mobj_t *Obj_SpawnMonitor(mobj_t *origin, UINT8 numItemTypes, UINT8 emerald); +void Obj_MonitorSpawnParts(mobj_t *monitor); +void Obj_MonitorPartThink(mobj_t *part); +fixed_t Obj_MonitorGetDamage(mobj_t *monitor, mobj_t *inflictor, UINT8 damagetype); +void Obj_MonitorOnDamage(mobj_t *monitor, mobj_t *inflictor, INT32 damage); +void Obj_MonitorOnDeath(mobj_t *monitor); +void Obj_MonitorShardThink(mobj_t *shard); +UINT32 Obj_MonitorGetEmerald(const mobj_t *monitor); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 00eadea36..aa1b91079 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -10,4 +10,5 @@ target_sources(SRB2SDL2 PRIVATE duel-bomb.c broly.c ufo.c + monitor.c ) diff --git a/src/objects/monitor.c b/src/objects/monitor.c new file mode 100644 index 000000000..16967c771 --- /dev/null +++ b/src/objects/monitor.c @@ -0,0 +1,691 @@ +#include "../doomdef.h" +#include "../doomstat.h" +#include "../info.h" +#include "../k_objects.h" +#include "../p_local.h" +#include "../r_state.h" +#include "../k_kart.h" +#include "../k_battle.h" +#include "../m_random.h" +#include "../r_main.h" + +#define FINE90 (FINEANGLES/4) +#define FINE180 (FINEANGLES/2) +#define TRUETAN(n) FINETANGENT(FINE90 + (n)) // bruh + +#define HEALTHFACTOR (FRACUNIT/4) // Always takes at most, 4 hits. + +#define MONITOR_PART_DEFINE(dispoffset, nsides, ...) \ +{dispoffset, nsides, (statenum_t[]){__VA_ARGS__, 0}} + +static const struct monitor_part_config { + INT32 dispoffset; + UINT8 nsides; + statenum_t * states; +} monitor_parts[] = { + MONITOR_PART_DEFINE (0, 3, + S_MONITOR_SCREEN1A, + S_ITEMICON, + S_MONITOR_CRACKB, + S_MONITOR_CRACKA), + + MONITOR_PART_DEFINE (-5, 5, S_MONITOR_STAND), +}; + +#define monitor_rngseed(o) ((o)->movedir) +#define monitor_itemcount(o) ((o)->movecount) +#define monitor_spawntic(o) ((o)->reactiontime) +#define monitor_emerald(o) ((o)->extravalue1) +#define monitor_damage(o) ((o)->extravalue2) +#define monitor_rammingspeed(o) ((o)->movefactor) + +static inline UINT8 +get_monitor_itemcount (const mobj_t *monitor) +{ + // protects against divide by zero + return max(monitor_itemcount(monitor), 1); +} + +#define part_monitor(o) ((o)->target) +#define part_type(o) ((o)->extravalue1) +#define part_index(o) ((o)->extravalue2) +#define part_theta(o) ((o)->movedir) + +#define shard_can_roll(o) ((o)->extravalue1) + +static const sprcache_t * get_state_sprcache (statenum_t); + +static const sprcache_t * +get_sprcache +( spritenum_t sprite, + UINT8 frame) +{ + const spritedef_t *sprdef = &sprites[sprite]; + + if (frame < sprdef->numframes) + { + size_t lump = sprdef->spriteframes[frame].lumpid[0]; + + return &spritecachedinfo[lump]; + } + else + { + return get_state_sprcache(S_UNKNOWN); + } +} + +static const sprcache_t * +get_state_sprcache (statenum_t statenum) +{ + return get_sprcache(states[statenum].sprite, + states[statenum].frame & FF_FRAMEMASK); +} + +static inline fixed_t +get_inradius +( fixed_t length, + INT32 nsides) +{ + return FixedDiv(length, 2 * TRUETAN(FINE180 / nsides)); +} + +static inline void +center_item_sprite +( mobj_t * part, + fixed_t scale) +{ + part->spriteyoffset = 25*FRACUNIT; + part->spritexscale = scale; + part->spriteyscale = scale; +} + +static mobj_t * +spawn_part +( mobj_t * monitor, + statenum_t state) +{ + mobj_t *part = P_SpawnMobjFromMobj( + monitor, 0, 0, 0, MT_MONITOR_PART); + + P_SetMobjState(part, state); + P_SetTarget(&part_monitor(part), monitor); + + part_type(part) = state; + + switch (state) + { + case S_ITEMICON: + // The first frame of the monitor is TV static so + // this should be invisible on the first frame. + part->renderflags |= RF_DONTDRAW; + break; + + default: + break; + } + + return part; +} + +static void +spawn_part_side +( mobj_t * monitor, + fixed_t rad, + fixed_t ang, + const struct monitor_part_config * p, + size_t side) +{ + INT32 i = 0; + + while (p->states[i]) + { + mobj_t *part = spawn_part(monitor, p->states[i]); + + part->radius = rad; + part_theta(part) = ang; + + // add one point for each layer (back to front order) + part->dispoffset = p->dispoffset + i; + + part_index(part) = side; + + i++; + } +} + +static void +spawn_monitor_parts +( mobj_t * monitor, + const struct monitor_part_config *p) +{ + const sprcache_t *info = get_state_sprcache(p->states[0]); + const fixed_t width = FixedMul(monitor->scale, info->width); + const fixed_t rad = get_inradius(width, p->nsides); + const fixed_t angle_factor = ANGLE_MAX / p->nsides; + + INT32 i; + angle_t ang = 0; + + for (i = 0; i < p->nsides; ++i) + { + spawn_part_side(monitor, rad, ang, p, i); + ang += angle_factor; + } +} + +static inline boolean +can_shard_state_roll (statenum_t state) +{ + switch (state) + { + case S_MONITOR_BIG_SHARD: + case S_MONITOR_SMALL_SHARD: + return true; + + default: + return false; + } +} + +static void +spawn_shard +( mobj_t * part, + statenum_t state) +{ + mobj_t *monitor = part_monitor(part); + + // These divisions and multiplications are done on the + // offsets to give bigger increments of randomness. + + const fixed_t half = FixedDiv( + monitor->height, monitor->scale) / 2; + + const UINT16 rad = (monitor->radius / monitor->scale) / 4; + const UINT16 tall = (half / FRACUNIT) / 4; + + mobj_t *p = P_SpawnMobjFromMobj(monitor, + P_RandomRange(PR_ITEM_DEBRIS, -(rad), rad) * 8 * FRACUNIT, + P_RandomRange(PR_ITEM_DEBRIS, -(rad), rad) * 8 * FRACUNIT, + (half / 4) + P_RandomKey(PR_ITEM_DEBRIS, tall + 1) * 4 * FRACUNIT, + MT_MONITOR_SHARD); + + angle_t th = (part->angle + ANGLE_90); + + th -= P_RandomKey(PR_ITEM_DEBRIS, ANGLE_45) - ANGLE_22h; + + p->hitlag = 0; + + P_Thrust(p, th, 6 * p->scale + monitor_rammingspeed(monitor)); + p->momz = P_RandomRange(PR_ITEM_DEBRIS, 3, 10) * p->scale; + + P_SetMobjState(p, state); + + shard_can_roll(p) = can_shard_state_roll(state); + + if (shard_can_roll(p)) + { + p->rollangle = P_Random(PR_ITEM_DEBRIS); + } + + if (P_RandomChance(PR_ITEM_DEBRIS, FRACUNIT/2)) + { + p->renderflags |= RF_DONTDRAW; + } +} + +static void +spawn_debris (mobj_t *part) +{ + const mobj_t *monitor = part_monitor(part); + + fixed_t i; + + for (i = monitor->health; + i <= FRACUNIT; i += HEALTHFACTOR/2) + { + spawn_shard(part, S_MONITOR_BIG_SHARD); + spawn_shard(part, S_MONITOR_SMALL_SHARD); + spawn_shard(part, S_MONITOR_TWINKLE); + } +} + +static void +spawn_monitor_explosion (mobj_t *monitor) +{ + mobj_t *smoldering = P_SpawnMobjFromMobj(monitor, 0, 0, 0, MT_SMOLDERING); + + UINT8 i; + + // Note that a Broly Ki is purposefully not spawned. This + // is to reduce visual clutter since these monitors would + // probably get popped a lot. + + K_MineFlashScreen(monitor); + + P_SetScale(smoldering, (smoldering->destscale /= 3)); + smoldering->tics = TICRATE*3; + + for (i = 0; i < 8; ++i) + { + mobj_t *x = P_SpawnMobjFromMobj(monitor, 0, 0, 0, MT_BOOMEXPLODE); + x->hitlag = 0; + x->color = SKINCOLOR_WHITE; + x->momx = P_RandomRange(PR_EXPLOSION, -5, 5) * monitor->scale, + x->momy = P_RandomRange(PR_EXPLOSION, -5, 5) * monitor->scale, + x->momz = P_RandomRange(PR_EXPLOSION, 0, 6) * monitor->scale * P_MobjFlip(monitor); + P_SetScale(x, (x->destscale *= 3)); + } +} + +static void +kill_monitor_part (mobj_t *part) +{ + const statenum_t statenum = part_type(part); + + switch (statenum) + { + case S_ITEMICON: + P_RemoveMobj(part); + return; + + case S_MONITOR_STAND: + part->momx = 0; + part->momy = 0; + break; + + case S_MONITOR_SCREEN1A: + spawn_debris(part); + P_SetMobjState(part, S_MONITOR_SCREEN1B); + /*FALLTHRU*/ + + default: + /* To be clear, momx/y do not need to set because + those fields are set every tic to offset each + part. */ + part->momz = (part->height / 8) * P_MobjFlip(part); + } + + part->fuse = TICRATE; + part->flags &= ~(MF_NOGRAVITY); +} + +static inline UINT32 +restore_item_rng (UINT32 seed) +{ + const UINT32 oldseed = P_GetRandSeed(PR_ITEM_ROULETTE); + + P_SetRandSeedNet(PR_ITEM_ROULETTE, + P_GetInitSeed(PR_ITEM_ROULETTE), seed); + + return oldseed; +} + +static inline SINT8 +get_item_result (void) +{ + return K_GetTotallyRandomResult(0); +} + +static SINT8 +get_cycle_result +( const mobj_t * monitor, + size_t cycle) +{ + const size_t rem = cycle % + get_monitor_itemcount(monitor); + + SINT8 result; + size_t i; + + const UINT32 oldseed = restore_item_rng( + monitor_rngseed(monitor)); + + for (i = 0; i <= rem; ++i) + { + result = get_item_result(); + } + + restore_item_rng(oldseed); + + return result; +} + +static inline tic_t +get_age (const mobj_t *monitor) +{ + return (leveltime - monitor_spawntic(monitor)); +} + +static inline boolean +is_flickering (const mobj_t *part) +{ + const mobj_t *monitor = part_monitor(part); + + return monitor->fuse > 0 && monitor->fuse <= TICRATE; +} + +static void +flicker +( mobj_t * part, + UINT8 interval) +{ + const tic_t age = get_age(part_monitor(part)); + + if (age % interval) + { + part->renderflags |= RF_DONTDRAW; + } + else + { + part->renderflags &= ~(RF_DONTDRAW); + } +} + +static void +project_icon (mobj_t *part) +{ + const mobj_t *monitor = part_monitor(part); + const tic_t age = get_age(monitor); + + // Item displayed on monitor cycles every N tics + if (age % 64 == 0) + { + const SINT8 result = get_cycle_result(monitor, + part_index(part) + (age / 64)); + + K_UpdateMobjItemOverlay(part, + K_ItemResultToType(result), + K_ItemResultToAmount(result)); + + center_item_sprite(part, 5*FRACUNIT/4); + } + + flicker(part, is_flickering(part) ? 4 : 2); +} + +static void +translate (mobj_t *part) +{ + const angle_t ang = part_theta(part) + + part_monitor(part)->angle; + + part->angle = (ang - ANGLE_90); + + // Because of MF_NOCLIPTHING, no friction is applied. + // This object is teleported back to the monitor every + // tic so its position is in total only ever translated + // by this much. + part->momx = P_ReturnThrustX(NULL, ang, part->radius); + part->momy = P_ReturnThrustY(NULL, ang, part->radius); +} + +static inline fixed_t +get_damage_multiplier (const mobj_t *monitor) +{ + return FixedDiv(monitor_damage(monitor), HEALTHFACTOR); +} + +static inline boolean +has_state +( const mobj_t * mobj, + statenum_t state) +{ + return mobj->hitlag == 0 && + (size_t)(mobj->state - states) == (size_t)state; +} + +static mobj_t * +adjust_monitor_drop +( mobj_t * monitor, + mobj_t * drop) +{ + P_InstaThrust(drop, drop->angle, 4*mapobjectscale); + + drop->momz *= 8; + + K_FlipFromObject(drop, monitor); + + return drop; +} + +void +Obj_MonitorSpawnParts (mobj_t *monitor) +{ + const size_t nparts = + sizeof monitor_parts / sizeof *monitor_parts; + + size_t i; + + P_SetScale(monitor, (monitor->destscale *= 2)); + + monitor_itemcount(monitor) = 0; + monitor_rngseed(monitor) = P_GetRandSeed(PR_ITEM_ROULETTE); + monitor_spawntic(monitor) = leveltime; + monitor_emerald(monitor) = 0; + + for (i = 0; i < nparts; ++i) + { + spawn_monitor_parts(monitor, &monitor_parts[i]); + } +} + +mobj_t * +Obj_SpawnMonitor +( mobj_t * origin, + UINT8 numItemTypes, + UINT8 emerald) +{ + mobj_t *monitor = P_SpawnMobj(origin->x, origin->y, + origin->z, MT_MONITOR); + + monitor->angle = P_Random(PR_DECORATION); + + monitor_itemcount(monitor) = numItemTypes; + monitor_emerald(monitor) = emerald; + + monitor->color = K_GetChaosEmeraldColor(emerald); + + return monitor; +} + +void +Obj_MonitorPartThink (mobj_t *part) +{ + const statenum_t statenum = part_type(part); + + mobj_t *monitor = part_monitor(part); + + if (part->fuse > 0) + { + return; + } + + if (P_MobjWasRemoved(monitor)) + { + P_RemoveMobj(part); + return; + } + + if (has_state(monitor, monitor->info->deathstate)) + { + kill_monitor_part(part); + return; + } + + if (is_flickering(part)) + { + flicker(part, 2); + } + + if (monitor->hitlag) + { + const fixed_t shake = FixedMul( + 2 * get_damage_multiplier(monitor), + monitor->radius / 8); + + part->sprxoff = P_AltFlip(shake, 2); + part->spryoff = P_AltFlip(shake, 4); + } + else + { + part->sprxoff = 0; + part->spryoff = 0; + } + + switch (statenum) + { + case S_MONITOR_SCREEN1A: + if (has_state(monitor, monitor->info->painstate)) + { + spawn_debris(part); + } + break; + + case S_MONITOR_CRACKA: + case S_MONITOR_CRACKB: + if (monitor->health < monitor->info->spawnhealth) + { + part->sprite = SPR_IMON; // initially SPR_NULL + part->frame = part->state->frame + + (monitor->health / HEALTHFACTOR); + } + break; + + case S_ITEMICON: + project_icon(part); + break; + + default: + break; + } + + P_MoveOrigin(part, monitor->x, monitor->y, monitor->z); + + translate(part); +} + +fixed_t +Obj_MonitorGetDamage +( mobj_t * monitor, + mobj_t * inflictor, + UINT8 damagetype) +{ + fixed_t damage; + + switch (damagetype & DMG_TYPEMASK) + { + case DMG_VOLTAGE: + if (monitor->health < HEALTHFACTOR) + { + return HEALTHFACTOR; + } + else + { + // always reduce to final damage state + return (monitor->health - HEALTHFACTOR) + 1; + } + } + + if (inflictor == NULL) + { + return HEALTHFACTOR; + } + + if (inflictor->player) + { + const fixed_t weight = + K_GetMobjWeight(inflictor, monitor); + + // HEALTHFACTOR is the minimum damage that can be + // dealt but player's weight (and speed) can buff the hit. + damage = HEALTHFACTOR + + (FixedMul(weight, HEALTHFACTOR) / 9); + + if (inflictor->scale > mapobjectscale) + { + damage = P_ScaleFromMap(damage, inflictor->scale); + } + } + else + { + damage = FRACUNIT; // kill instantly + } + + return damage; +} + +void +Obj_MonitorOnDamage +( mobj_t * monitor, + mobj_t * inflictor, + INT32 damage) +{ + monitor->fuse = BATTLE_DESPAWN_TIME; + monitor_damage(monitor) = damage; + monitor_rammingspeed(monitor) = inflictor + ? FixedDiv(FixedHypot(inflictor->momx, inflictor->momy), 4 * inflictor->radius) : 0; + monitor->hitlag = + 6 * get_damage_multiplier(monitor) / FRACUNIT; +} + +void +Obj_MonitorOnDeath (mobj_t *monitor) +{ + const UINT8 itemcount = get_monitor_itemcount(monitor); + const angle_t ang = ANGLE_MAX / itemcount; + const SINT8 flip = P_MobjFlip(monitor); + + INT32 i; + + UINT32 sharedseed = restore_item_rng( + monitor_rngseed(monitor)); + + for (i = 0; i < itemcount; ++i) + { + const SINT8 result = get_item_result(); + const UINT32 localseed = restore_item_rng(sharedseed); + + adjust_monitor_drop(monitor, + K_CreatePaperItem( + monitor->x, monitor->y, monitor->z + (128 * mapobjectscale * flip), + i * ang, flip, + K_ItemResultToType(result), + K_ItemResultToAmount(result))); + + // K_CreatePaperItem may advance RNG, so update our + // copy of the seed afterward + sharedseed = restore_item_rng(localseed); + } + + restore_item_rng(sharedseed); + + if (monitor_emerald(monitor) != 0) + { + adjust_monitor_drop(monitor, + K_SpawnChaosEmerald(monitor->x, monitor->y, monitor->z + (128 * mapobjectscale * flip), + ang, flip, monitor_emerald(monitor))); + } + + spawn_monitor_explosion(monitor); + + // There is hitlag from being damaged, so remove + // tangibility RIGHT NOW. + monitor->flags &= ~(MF_SOLID); +} + +void +Obj_MonitorShardThink (mobj_t *shard) +{ + if (shard_can_roll(shard)) + { + shard->rollangle += ANGLE_45; + } + + shard->renderflags ^= RF_DONTDRAW; +} + +UINT32 +Obj_MonitorGetEmerald (const mobj_t *monitor) +{ + return monitor_emerald(monitor); +} diff --git a/src/p_inter.c b/src/p_inter.c index 9887b8bb7..399d74918 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1666,6 +1666,10 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget break; } + case MT_MONITOR: + Obj_MonitorOnDeath(target); + break; + default: break; } @@ -2007,6 +2011,17 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da laglength = 0; // handled elsewhere } + switch (target->type) + { + case MT_MONITOR: + damage = Obj_MonitorGetDamage(target, inflictor, damagetype); + Obj_MonitorOnDamage(target, inflictor, damage); + break; + + default: + break; + } + // Everything above here can't be forced. if (!metalrecording) { diff --git a/src/p_map.c b/src/p_map.c index ef4819118..363b73997 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -1537,6 +1537,18 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) K_KartBouncing(tm.thing, thing); return BMIT_CONTINUE; } + else if (thing->type == MT_MONITOR) + { + // see if it went over / under + if (tm.thing->z > thing->z + thing->height) + return BMIT_CONTINUE; // overhead + if (tm.thing->z + tm.thing->height < thing->z) + return BMIT_CONTINUE; // underneath + + P_DamageMobj(thing, tm.thing, tm.thing, 1, DMG_NORMAL); + K_KartBouncing(tm.thing, thing); + return BMIT_CONTINUE; + } else if (thing->flags & MF_SOLID) { // see if it went over / under diff --git a/src/p_mobj.c b/src/p_mobj.c index 230454a6c..8d4009290 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -3077,6 +3077,17 @@ boolean P_SceneryZMovement(mobj_t *mo) P_RemoveMobj(mo); return false; } + break; + case MT_MONITOR_SHARD: + // Hits the ground + if ((mo->eflags & MFE_VERTICALFLIP) + ? (mo->ceilingz <= (mo->z + mo->height)) + : (mo->z <= mo->floorz)) + { + P_RemoveMobj(mo); + return false; + } + break; default: break; } @@ -6520,6 +6531,9 @@ static void P_MobjSceneryThink(mobj_t *mobj) return; } break; + case MT_MONITOR_SHARD: + Obj_MonitorShardThink(mobj); + break; case MT_VWREF: case MT_VWREB: { @@ -7382,6 +7396,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj) break; } case MT_EMERALD: + { + if (mobj->threshold > 0) + mobj->threshold--; + } + /*FALLTHRU*/ + case MT_MONITOR: { if (battleovertime.enabled >= 10*TICRATE) { @@ -7394,6 +7414,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } + // Don't spawn sparkles on a monitor with no + // emerald inside + if (mobj->type == MT_MONITOR && + Obj_MonitorGetEmerald(mobj) == 0) + { + break; + } + if (leveltime % 3 == 0) { mobj_t *sparkle = P_SpawnMobjFromMobj( @@ -7407,9 +7435,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj) sparkle->color = mobj->color; sparkle->momz += 8 * mobj->scale * P_MobjFlip(mobj); } - - if (mobj->threshold > 0) - mobj->threshold--; } break; case MT_DRIFTEXPLODE: @@ -9414,6 +9439,9 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->colorized = false; } break; + case MT_MONITOR_PART: + Obj_MonitorPartThink(mobj); + break; default: // check mobj against possible water content, before movement code P_MobjCheckWater(mobj); @@ -9519,6 +9547,7 @@ static boolean P_CanFlickerFuse(mobj_t *mobj) case MT_SNAPPER_HEAD: case MT_SNAPPER_LEG: case MT_MINECARTSEG: + case MT_MONITOR_PART: return true; case MT_RANDOMITEM: @@ -10620,6 +10649,10 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) break; } + case MT_MONITOR: { + Obj_MonitorSpawnParts(mobj); + break; + } case MT_KARMAHITBOX: { const fixed_t rad = FixedMul(mobjinfo[MT_PLAYER].radius, mobj->scale); From 7ce12f37dcaf37e9a0b15656fb0184568796ea61 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 1 Jan 2023 02:10:10 -0800 Subject: [PATCH 088/128] Spawn Battle monitors - Item count has always scaled up with player count. Items spawn every N tics. Previously, these items were evenly distributed across all item spots. Now, only one monitor is spawned at a time and the number of items inside scales up. - Emeralds spawn inside of monitors instead of loosely. - Sphere boxes should spawn in the same way as before. - Once a monitor has been spawned at an item spot, no more monitors can be spawned there until that one is destroyed. There's an additional delay of one spawn interval before a monitor can be spawned in that location again. This is so a monitor cannot ever instantly spawn back if one is destroyed at just the right time. --- src/k_battle.c | 135 +++++++++++++++++++------------------ src/k_objects.h | 6 ++ src/objects/CMakeLists.txt | 1 + src/objects/item-spot.c | 35 ++++++++++ src/objects/monitor.c | 14 ++++ 5 files changed, 126 insertions(+), 65 deletions(-) create mode 100644 src/objects/item-spot.c diff --git a/src/k_battle.c b/src/k_battle.c index e05b003a4..765df4966 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -18,6 +18,7 @@ #include "r_sky.h" // skyflatnum #include "k_grandprix.h" // K_CanChangeRules #include "p_spec.h" +#include "k_objects.h" // Battle overtime info struct battleovertime battleovertime; @@ -472,9 +473,7 @@ void K_RunPaperItemSpawners(void) #define MAXITEM 64 mobj_t *spotList[MAXITEM]; UINT8 spotMap[MAXITEM]; - UINT8 spotCount = 0, spotBackup = 0; - - INT16 starti = 0; + UINT8 spotCount = 0, spotBackup = 0, spotAvailable = 0; for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { @@ -488,14 +487,26 @@ void K_RunPaperItemSpawners(void) emeraldsSpawned |= mo->extravalue1; } + if (mo->type == MT_MONITOR) + { + emeraldsSpawned |= Obj_MonitorGetEmerald(mo); + } + if (mo->type != MT_PAPERITEMSPOT) continue; if (spotCount >= MAXITEM) continue; + if (Obj_ItemSpotIsAvailable(mo)) + { + // spotMap first only includes spots + // where a monitor doesn't exist + spotMap[spotAvailable] = spotCount; + spotAvailable++; + } + spotList[spotCount] = mo; - spotMap[spotCount] = spotCount; spotCount++; } @@ -513,7 +524,6 @@ void K_RunPaperItemSpawners(void) if (!(emeraldsSpawned & emeraldFlag)) { firstUnspawnedEmerald = emeraldFlag; - starti = -1; break; } } @@ -521,77 +531,72 @@ void K_RunPaperItemSpawners(void) //CONS_Printf("leveltime = %d ", leveltime); - spotBackup = spotCount; - for (i = starti; i < pcount; i++) + if (spotAvailable > 0) { - UINT8 r = 0, key = 0; - mobj_t *drop = NULL; - SINT8 flip = 1; + const UINT8 r = spotMap[P_RandomKey(PR_ITEM_ROULETTE, spotAvailable)]; - if (spotCount == 0) + Obj_ItemSpotAssignMonitor(spotList[r], Obj_SpawnMonitor( + spotList[r], 1 + pcount, firstUnspawnedEmerald)); + } + + for (i = 0; i < spotCount; ++i) + { + // now spotMap includes every spot + spotMap[i] = i; + } + + if ((gametyperules & GTR_SPHERES) && IsOnInterval(2 * interval)) + { + spotBackup = spotCount; + for (i = 0; i < pcount; i++) { - // all are accessible again - spotCount = spotBackup; - } + UINT8 r = 0, key = 0; + mobj_t *drop = NULL; + SINT8 flip = 1; - if (spotCount == 1) - { - key = 0; - } - else - { - key = P_RandomKey(PR_ITEM_ROULETTE, spotCount); - } - - r = spotMap[key]; - - //CONS_Printf("[%d %d %d] ", i, key, r); - - flip = P_MobjFlip(spotList[r]); - - // When -1, we're spawning a Chaos Emerald. - if (i == -1) - { - drop = K_SpawnChaosEmerald( - spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip), - FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip, - firstUnspawnedEmerald - ); - } - else - { - if ((gametyperules & GTR_SPHERES) && IsOnInterval(2 * interval)) + if (spotCount == 0) { - drop = K_SpawnSphereBox( - spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip), - FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip, - 10 - ); - K_FlipFromObject(drop, spotList[r]); + // all are accessible again + spotCount = spotBackup; } - drop = K_CreatePaperItem( + if (spotCount == 1) + { + key = 0; + } + else + { + key = P_RandomKey(PR_ITEM_ROULETTE, spotCount); + } + + r = spotMap[key]; + + //CONS_Printf("[%d %d %d] ", i, key, r); + + flip = P_MobjFlip(spotList[r]); + + drop = K_SpawnSphereBox( spotList[r]->x, spotList[r]->y, spotList[r]->z + (128 * mapobjectscale * flip), - FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip, - 0, 0 + FixedAngle(P_RandomRange(PR_ITEM_ROULETTE, 0, 359) * FRACUNIT), flip, + 10 ); - } - K_FlipFromObject(drop, spotList[r]); + K_FlipFromObject(drop, spotList[r]); - spotCount--; - if (key != spotCount) - { - // So the core theory of what's going on is that we keep every - // available option at the front of the array, so we don't have - // to skip over any gaps or do recursion to avoid doubles. - // But because spotCount can be reset in the case of a low - // quanitity of item spawnpoints in a map, we still need every - // entry in the array, even outside of the "visible" range. - // A series of swaps allows us to adhere to both constraints. - // -toast 22/03/22 (semipalindromic!) - spotMap[key] = spotMap[spotCount]; - spotMap[spotCount] = r; // was set to spotMap[key] previously + spotCount--; + if (key != spotCount) + { + // So the core theory of what's going on is that we keep every + // available option at the front of the array, so we don't have + // to skip over any gaps or do recursion to avoid doubles. + // But because spotCount can be reset in the case of a low + // quanitity of item spawnpoints in a map, we still need every + // entry in the array, even outside of the "visible" range. + // A series of swaps allows us to adhere to both constraints. + // -toast 22/03/22 (semipalindromic!) + spotMap[key] = spotMap[spotCount]; + spotMap[spotCount] = r; // was set to spotMap[key] previously + } } } //CONS_Printf("\n"); diff --git a/src/k_objects.h b/src/k_objects.h index 5ded33e2f..b1fe2014b 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -82,6 +82,12 @@ void Obj_MonitorOnDamage(mobj_t *monitor, mobj_t *inflictor, INT32 damage); void Obj_MonitorOnDeath(mobj_t *monitor); void Obj_MonitorShardThink(mobj_t *shard); UINT32 Obj_MonitorGetEmerald(const mobj_t *monitor); +void Obj_MonitorSetItemSpot(mobj_t *monitor, mobj_t *spot); + +/* Item Spot */ +boolean Obj_ItemSpotIsAvailable(const mobj_t *spot); +void Obj_ItemSpotAssignMonitor(mobj_t *spot, mobj_t *monitor); +void Obj_ItemSpotUpdate(mobj_t *spot); #ifdef __cplusplus } // extern "C" diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index aa1b91079..440f700de 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -11,4 +11,5 @@ target_sources(SRB2SDL2 PRIVATE broly.c ufo.c monitor.c + item-spot.c ) diff --git a/src/objects/item-spot.c b/src/objects/item-spot.c new file mode 100644 index 000000000..0fdff3056 --- /dev/null +++ b/src/objects/item-spot.c @@ -0,0 +1,35 @@ +#include "../doomdef.h" +#include "../m_fixed.h" +#include "../k_objects.h" +#include "../k_battle.h" +#include "../p_tick.h" +#include "../p_local.h" + +#define spot_monitor(o) ((o)->target) +#define spot_cool(o) ((o)->threshold) + +boolean +Obj_ItemSpotIsAvailable (const mobj_t *spot) +{ + return P_MobjWasRemoved(spot_monitor(spot)) && + (leveltime - spot_cool(spot)) > BATTLE_SPAWN_INTERVAL; +} + +void +Obj_ItemSpotAssignMonitor +( mobj_t * spot, + mobj_t * monitor) +{ + P_SetTarget(&spot_monitor(spot), monitor); + Obj_MonitorSetItemSpot(monitor, spot); +} + +void +Obj_ItemSpotUpdate (mobj_t *spot) +{ + if (P_MobjWasRemoved(spot_monitor(spot)) || + spot_monitor(spot)->health <= 0) + { + spot_cool(spot) = leveltime; + } +} diff --git a/src/objects/monitor.c b/src/objects/monitor.c index 16967c771..50d13ee3c 100644 --- a/src/objects/monitor.c +++ b/src/objects/monitor.c @@ -32,6 +32,7 @@ static const struct monitor_part_config { MONITOR_PART_DEFINE (-5, 5, S_MONITOR_STAND), }; +#define monitor_spot(o) ((o)->target) #define monitor_rngseed(o) ((o)->movedir) #define monitor_itemcount(o) ((o)->movecount) #define monitor_spawntic(o) ((o)->reactiontime) @@ -671,6 +672,11 @@ Obj_MonitorOnDeath (mobj_t *monitor) // There is hitlag from being damaged, so remove // tangibility RIGHT NOW. monitor->flags &= ~(MF_SOLID); + + if (!P_MobjWasRemoved(monitor_spot(monitor))) + { + Obj_ItemSpotUpdate(monitor_spot(monitor)); + } } void @@ -689,3 +695,11 @@ Obj_MonitorGetEmerald (const mobj_t *monitor) { return monitor_emerald(monitor); } + +void +Obj_MonitorSetItemSpot +( mobj_t * monitor, + mobj_t * spot) +{ + P_SetTarget(&monitor_spot(monitor), spot); +} From 50d9bf7b7d87c1778698276d16b29fdb30bce936 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 1 Jan 2023 02:07:09 -0800 Subject: [PATCH 089/128] Faster moving players hit monitors harder --- src/k_kart.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index e01d85db2..603266aed 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -624,13 +624,22 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against) // from causing super crazy bumps. fixed_t spd = K_GetKartSpeed(mobj->player, false, true); + fixed_t speedfactor = 8 * mapobjectscale; + weight = (mobj->player->kartweight) * FRACUNIT; - if (mobj->player->speed > spd) - weight += FixedDiv((mobj->player->speed - spd), 8 * mapobjectscale); + if (against && against->type == MT_MONITOR) + { + speedfactor /= 5; // speed matters more + } + else + { + if (mobj->player->itemtype == KITEM_BUBBLESHIELD) + weight += 9*FRACUNIT; + } - if (mobj->player->itemtype == KITEM_BUBBLESHIELD) - weight += 9*FRACUNIT; + if (mobj->player->speed > spd) + weight += FixedDiv((mobj->player->speed - spd), speedfactor); } return weight; From 691e84f4891493f08e087af37511a929d621f0cc Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 1 Jan 2023 01:56:29 -0800 Subject: [PATCH 090/128] Give item drops a trail upward, fall faster --- src/p_mobj.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/p_mobj.c b/src/p_mobj.c index 8d4009290..643dbaa91 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1225,6 +1225,21 @@ fixed_t P_GetMobjGravity(mobj_t *mo) case MT_ITEM_DEBRIS: gravityadd *= 6; break; + case MT_FLOATINGITEM: { + // Basically this accelerates gravity after + // the object reached its peak vertical + // momentum. It's a gradual acceleration up + // to 2x normal gravity. It's not instant to + // give it some 'weight'. + const fixed_t z = P_MobjFlip(mo) * mo->momz; + if (z < 0) + { + const fixed_t d = (z - (mo->height / 2)); + const fixed_t f = 2 * abs(FixedDiv(d, mo->height)); + gravityadd = FixedMul(gravityadd, FRACUNIT + min(f, 2*FRACUNIT)); + } + break; + } default: break; } @@ -5734,6 +5749,21 @@ static void P_MobjSceneryThink(mobj_t *mobj) P_AddOverlay(mobj); if (mobj->target->hitlag) // move to the correct position, update to the correct properties, but DON'T STATE-ANIMATE return; + switch (mobj->target->type) + { + case MT_FLOATINGITEM: + // Spawn trail for item drop as it flies upward. + // Done here so it applies to backdrop too. + if (mobj->target->momz * P_MobjFlip(mobj->target) > 0) + { + P_SpawnGhostMobj(mobj); + P_SpawnGhostMobj(mobj->target); + } + break; + + default: + break; + } break; case MT_WATERDROP: P_SceneryCheckWater(mobj); From 9336e39350ce0b2fcf3bc4cf0005923900b2fd7f Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 3 Jan 2023 16:03:47 +0000 Subject: [PATCH 091/128] Use Zone memory functions for sending compressed saves Fixes an issue where `SF_RAM` meant `free()` was being called on Zone memory produced by `P_SaveBufferAlloc` --- src/d_clisrv.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 7efe8cf6f..00cff178e 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1166,7 +1166,7 @@ static void SV_SendSaveGame(INT32 node, boolean resending) // Allocate space for compressed save: one byte fewer than for the // uncompressed data to ensure that the compression is worthwhile. - compressedsave = malloc(length - 1); + compressedsave = Z_Malloc(length - 1, PU_STATIC, NULL); if (!compressedsave) { CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); @@ -1187,14 +1187,14 @@ static void SV_SendSaveGame(INT32 node, boolean resending) else { // Compression failed to make it smaller; send original - free(compressedsave); + Z_Free(compressedsave); // State that we're not compressed buffertosend = save.buffer; WRITEUINT32(save.buffer, 0); } - AddRamToSendQueue(node, buffertosend, length, SF_RAM, 0); + AddRamToSendQueue(node, buffertosend, length, SF_Z_RAM, 0); // Remember when we started sending the savegame so we can handle timeouts sendingsavegame[node] = true; From b573b6efbc9ad639f031e5299511185c5951e303 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 3 Jan 2023 19:31:03 +0000 Subject: [PATCH 092/128] Don't tie restoring mobj/waypoint pointers on player struct to existence of player object --- src/p_saveg.c | 149 +++++++++++++++++++++++++------------------------- 1 file changed, 76 insertions(+), 73 deletions(-) diff --git a/src/p_saveg.c b/src/p_saveg.c index 946e77faa..7696ed964 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4094,7 +4094,6 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save) // remove all the current thinkers for (i = 0; i < NUM_THINKERLISTS; i++) { - currentthinker = thlist[i].next; for (currentthinker = thlist[i].next; currentthinker != &thlist[i]; currentthinker = next) { next = currentthinker->next; @@ -4438,7 +4437,7 @@ static void P_RelinkPointers(void) { thinker_t *currentthinker; mobj_t *mobj; - UINT32 temp; + UINT32 temp, i; // use info field (value = oldposition) to relink mobjs for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ]; @@ -4503,85 +4502,89 @@ static void P_RelinkPointers(void) if (!P_SetTarget(&mobj->terrainOverlay, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "terrainOverlay not found on %d\n", mobj->type); } - if (mobj->player) + } + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i]) + continue; + + if (players[i].skybox.viewpoint) { - if ( mobj->player->skybox.viewpoint) + temp = (UINT32)(size_t)players[i].skybox.viewpoint; + players[i].skybox.viewpoint = NULL; + if (!P_SetTarget(&players[i].skybox.viewpoint, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "skybox.viewpoint not found on player %d\n", i); + } + if (players[i].skybox.centerpoint) + { + temp = (UINT32)(size_t)players[i].skybox.centerpoint; + players[i].skybox.centerpoint = NULL; + if (!P_SetTarget(&players[i].skybox.centerpoint, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "skybox.centerpoint not found on player %d\n", i); + } + if (players[i].awayviewmobj) + { + temp = (UINT32)(size_t)players[i].awayviewmobj; + players[i].awayviewmobj = NULL; + if (!P_SetTarget(&players[i].awayviewmobj, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on player %d\n", i); + } + if (players[i].followmobj) + { + temp = (UINT32)(size_t)players[i].followmobj; + players[i].followmobj = NULL; + if (!P_SetTarget(&players[i].followmobj, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "followmobj not found on player %d\n", i); + } + if (players[i].follower) + { + temp = (UINT32)(size_t)players[i].follower; + players[i].follower = NULL; + if (!P_SetTarget(&players[i].follower, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "follower not found on player %d\n", i); + } + if (players[i].currentwaypoint) + { + temp = (UINT32)(size_t)players[i].currentwaypoint; + players[i].currentwaypoint = K_GetWaypointFromIndex(temp); + if (players[i].currentwaypoint == NULL) { - temp = (UINT32)(size_t)mobj->player->skybox.viewpoint; - mobj->player->skybox.viewpoint = NULL; - if (!P_SetTarget(&mobj->player->skybox.viewpoint, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "skybox.viewpoint not found on %d\n", mobj->type); + CONS_Debug(DBG_GAMELOGIC, "currentwaypoint not found on player %d\n", i); } - if ( mobj->player->skybox.centerpoint) + } + if (players[i].nextwaypoint) + { + temp = (UINT32)(size_t)players[i].nextwaypoint; + players[i].nextwaypoint = K_GetWaypointFromIndex(temp); + if (players[i].nextwaypoint == NULL) { - temp = (UINT32)(size_t)mobj->player->skybox.centerpoint; - mobj->player->skybox.centerpoint = NULL; - if (!P_SetTarget(&mobj->player->skybox.centerpoint, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "skybox.centerpoint not found on %d\n", mobj->type); + CONS_Debug(DBG_GAMELOGIC, "nextwaypoint not found on player %d\n", i); } - if ( mobj->player->awayviewmobj) + } + if (players[i].respawn.wp) + { + temp = (UINT32)(size_t)players[i].respawn.wp; + players[i].respawn.wp = K_GetWaypointFromIndex(temp); + if (players[i].respawn.wp == NULL) { - temp = (UINT32)(size_t)mobj->player->awayviewmobj; - mobj->player->awayviewmobj = NULL; - if (!P_SetTarget(&mobj->player->awayviewmobj, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "awayviewmobj not found on %d\n", mobj->type); - } - if (mobj->player->followmobj) - { - temp = (UINT32)(size_t)mobj->player->followmobj; - mobj->player->followmobj = NULL; - if (!P_SetTarget(&mobj->player->followmobj, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "followmobj not found on %d\n", mobj->type); - } - if (mobj->player->follower) - { - temp = (UINT32)(size_t)mobj->player->follower; - mobj->player->follower = NULL; - if (!P_SetTarget(&mobj->player->follower, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "follower not found on %d\n", mobj->type); - } - if (mobj->player->currentwaypoint) - { - temp = (UINT32)(size_t)mobj->player->currentwaypoint; - mobj->player->currentwaypoint = K_GetWaypointFromIndex(temp); - if (mobj->player->currentwaypoint == NULL) - { - CONS_Debug(DBG_GAMELOGIC, "currentwaypoint not found on %d\n", mobj->type); - } - } - if (mobj->player->nextwaypoint) - { - temp = (UINT32)(size_t)mobj->player->nextwaypoint; - mobj->player->nextwaypoint = K_GetWaypointFromIndex(temp); - if (mobj->player->nextwaypoint == NULL) - { - CONS_Debug(DBG_GAMELOGIC, "nextwaypoint not found on %d\n", mobj->type); - } - } - if (mobj->player->respawn.wp) - { - temp = (UINT32)(size_t)mobj->player->respawn.wp; - mobj->player->respawn.wp = K_GetWaypointFromIndex(temp); - if (mobj->player->respawn.wp == NULL) - { - CONS_Debug(DBG_GAMELOGIC, "respawn.wp not found on %d\n", mobj->type); - } - } - if (mobj->player->hoverhyudoro) - { - temp = (UINT32)(size_t)mobj->player->hoverhyudoro; - mobj->player->hoverhyudoro = NULL; - if (!P_SetTarget(&mobj->player->hoverhyudoro, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "hoverhyudoro not found on %d\n", mobj->type); - } - if (mobj->player->stumbleIndicator) - { - temp = (UINT32)(size_t)mobj->player->stumbleIndicator; - mobj->player->stumbleIndicator = NULL; - if (!P_SetTarget(&mobj->player->stumbleIndicator, P_FindNewPosition(temp))) - CONS_Debug(DBG_GAMELOGIC, "stumbleIndicator not found on %d\n", mobj->type); + CONS_Debug(DBG_GAMELOGIC, "respawn.wp not found on player %d\n", i); } } + if (players[i].hoverhyudoro) + { + temp = (UINT32)(size_t)players[i].hoverhyudoro; + players[i].hoverhyudoro = NULL; + if (!P_SetTarget(&players[i].hoverhyudoro, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "hoverhyudoro not found on player %d\n", i); + } + if (players[i].stumbleIndicator) + { + temp = (UINT32)(size_t)players[i].stumbleIndicator; + players[i].stumbleIndicator = NULL; + if (!P_SetTarget(&players[i].stumbleIndicator, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "stumbleIndicator not found on player %d\n", i); + } } } From 11b4fcdc1cda4fa4166cfb4cb5b55f6765a01588 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 3 Jan 2023 20:20:22 +0000 Subject: [PATCH 093/128] Remove heinous pointer-sent-over-the-network hack remnants This used to be used for `P_RelinkPointers()`, but was superseded by the far saner mobjnum. HOWEVER, its lingering effect was to leave `mobj->info` in an invalid state until a later `P_FinishMobjs()` was called. This is memory unsafety :D After removing the last remnants of this ancient hack, it is now once again possible to connect to a server without crashing immediately. However, I did get a crash after a few seconds, so there is definitely still *something* nasty going on under the hood. --- src/p_saveg.c | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/p_saveg.c b/src/p_saveg.c index 7696ed964..1fd515a1f 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -2106,9 +2106,6 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 if (diff & MD_MORE) WRITEUINT32(save->p, diff2); - // save pointer, at load time we will search this pointer to reinitilize pointers - WRITEUINT32(save->p, (size_t)mobj); - WRITEFIXED(save->p, mobj->z); // Force this so 3dfloor problems don't arise. WRITEFIXED(save->p, mobj->floorz); WRITEFIXED(save->p, mobj->ceilingz); @@ -3141,7 +3138,6 @@ static inline pslope_t *LoadSlope(UINT32 slopeid) static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) { - thinker_t *next; mobj_t *mobj; UINT32 diff; UINT32 diff2; @@ -3155,8 +3151,6 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) else diff2 = 0; - next = (void *)(size_t)READUINT32(save->p); - z = READFIXED(save->p); // Force this so 3dfloor problems don't arise. floorz = READFIXED(save->p); ceilingz = READFIXED(save->p); @@ -3478,8 +3472,6 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) if (diff2 & MD2_KITEMCAP) P_SetTarget(&kitemcap, mobj); - mobj->info = (mobjinfo_t *)next; // temporarily, set when leave this function - R_AddMobjInterpolator(mobj); return &mobj->thinker; @@ -4416,23 +4408,6 @@ static inline void P_UnArchivePolyObjects(savebuffer_t *save) P_UnArchivePolyObj(save, &PolyObjects[i]); } -static inline void P_FinishMobjs(void) -{ - thinker_t *currentthinker; - mobj_t *mobj; - - // put info field there real value - for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ]; - currentthinker = currentthinker->next) - { - if (currentthinker->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed) - continue; - - mobj = (mobj_t *)currentthinker; - mobj->info = &mobjinfo[mobj->type]; - } -} - static void P_RelinkPointers(void) { thinker_t *currentthinker; @@ -5214,7 +5189,6 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) P_NetUnArchiveTubeWaypoints(save); P_NetUnArchiveWaypoints(save); P_RelinkPointers(); - P_FinishMobjs(); } LUA_UnArchive(save, true); From 66e2f3a8e919b934935fd941f089ffc3f2747e65 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Jan 2023 02:27:03 -0800 Subject: [PATCH 094/128] Reduce stair janking loss of control if momentum angle drifts too far from facing --- src/p_user.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/p_user.c b/src/p_user.c index be70fd0e0..34c016d18 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1792,6 +1792,38 @@ static void P_DoBubbleBreath(player_t *player) } } +static inline boolean P_IsMomentumAngleLocked(player_t *player) +{ + // This timer is used for the animation too and the + // animation should continue for a bit after the physics + // stop. + + if (player->stairjank > 8) + { + const angle_t th = K_MomentumAngle(player->mo); + const angle_t d = AngleDelta(th, player->mo->angle); + + // A larger difference between momentum and facing + // angles awards back control. + // <45 deg: 3/4 tics + // >45 deg: 2/4 tics + // >90 deg: 1/4 tics + // >135 deg: 0/4 tics + + if ((leveltime & 3) > (d / ANGLE_45)) + { + return true; + } + } + + if (K_IsRidingFloatingTop(player)) + { + return true; + } + + return false; +} + //#define OLD_MOVEMENT_CODE 1 static void P_3dMovement(player_t *player) { @@ -1812,7 +1844,7 @@ static void P_3dMovement(player_t *player) // Get the old momentum; this will be needed at the end of the function! -SH oldMagnitude = R_PointToDist2(player->mo->momx - player->cmomx, player->mo->momy - player->cmomy, 0, 0); - if ((player->stairjank > 8 && leveltime & 3) || K_IsRidingFloatingTop(player)) + if (P_IsMomentumAngleLocked(player)) { movepushangle = K_MomentumAngle(player->mo); } From 5c51ad77f6cc060e37ebec7733d3c52eafaded72 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 3 Jan 2023 23:14:29 -0800 Subject: [PATCH 095/128] Use P_SaveBufferFree everywhere - Fixes PR_SaveProfiles calling stdlib free on zone memory. - Also touched g_demo.c --- src/g_demo.c | 26 +++++++++++++------------- src/k_profiles.c | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/g_demo.c b/src/g_demo.c index 6ef1a9ebb..a0d8fd2fb 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -3020,7 +3020,7 @@ void G_DoPlayDemo(char *defdemoname) CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demo.playback = false; demo.title = false; return; @@ -3040,7 +3040,7 @@ void G_DoPlayDemo(char *defdemoname) CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demo.playback = false; demo.title = false; return; @@ -3058,7 +3058,7 @@ void G_DoPlayDemo(char *defdemoname) CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demo.playback = false; demo.title = false; return; @@ -3078,7 +3078,7 @@ void G_DoPlayDemo(char *defdemoname) CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demo.playback = false; demo.title = false; return; @@ -3136,7 +3136,7 @@ void G_DoPlayDemo(char *defdemoname) if (!CON_Ready()) // In the console they'll just see the notice there! No point pulling them out. M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demo.playback = false; demo.title = false; return; @@ -3151,7 +3151,7 @@ void G_DoPlayDemo(char *defdemoname) CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demo.playback = false; demo.title = false; return; @@ -3187,7 +3187,7 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demo.playback = false; demo.title = false; return; @@ -3205,7 +3205,7 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demo.playback = false; demo.title = false; return; @@ -3260,7 +3260,7 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demo.playback = false; demo.title = false; return; @@ -3278,7 +3278,7 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demo.playback = false; demo.title = false; return; @@ -3934,7 +3934,7 @@ ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill) WriteDemoChecksum(); saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuf.buffer, demobuf.p - demobuf.buffer); // finally output the file. } - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); metalrecording = false; if (saved) I_Error("Saved to %sMS.LMP", G_BuildMapName(gamemap)); @@ -4006,7 +4006,7 @@ static void G_StopTimingDemo(void) // called from stopdemo command, map command, and g_checkdemoStatus. void G_StopDemo(void) { - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demobuf.buffer = NULL; demo.playback = false; if (demo.title) @@ -4154,7 +4154,7 @@ void G_SaveDemo(void) if (FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer)) // finally output the file. demo.savemode = DSM_SAVED; - Z_Free(demobuf.buffer); + P_SaveBufferFree(&demobuf); demo.recording = false; if (!modeattacking) diff --git a/src/k_profiles.c b/src/k_profiles.c index 313dc84f8..62f561587 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -267,10 +267,10 @@ void PR_SaveProfiles(void) if (!FIL_WriteFile(va(pandf, srb2home, PROFILESFILE), save.buffer, length)) { - free(save.buffer); + P_SaveBufferFree(&save); I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?"); } - free(save.buffer); + P_SaveBufferFree(&save); } void PR_LoadProfiles(void) From 8b96612e43dd7457e69d60b13376c563c0d7f66d Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 4 Jan 2023 00:08:30 -0800 Subject: [PATCH 096/128] Default addons menu to addons folder --- src/d_main.c | 2 +- src/d_main.h | 1 + src/filesrch.c | 4 ++-- src/k_menufunc.c | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 8384de07c..9891236e6 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -138,7 +138,7 @@ char srb2home[256] = "."; char srb2path[256] = "."; boolean usehome = true; const char *pandf = "%s" PATHSEP "%s"; -static char addonsdir[MAX_WADPATH]; +char addonsdir[MAX_WADPATH]; // // EVENT HANDLING diff --git a/src/d_main.h b/src/d_main.h index 75a8e4b25..0b4f6f19d 100644 --- a/src/d_main.h +++ b/src/d_main.h @@ -29,6 +29,7 @@ extern char srb2home[256]; //Alam: My Home extern boolean usehome; //Alam: which path? extern const char *pandf; //Alam: how to path? extern char srb2path[256]; //Alam: SRB2's Home +extern char addonsdir[MAX_WADPATH]; // Where addons are stored // the infinite loop of D_SRB2Loop() called from win_main for windows version void D_SRB2Loop(void) FUNCNORETURN; diff --git a/src/filesrch.c b/src/filesrch.c index 9db81ad37..5ec98babc 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -310,13 +310,13 @@ closedir (DIR * dirp) } #endif -static CV_PossibleValue_t addons_cons_t[] = {{0, "Default"}, +static CV_PossibleValue_t addons_cons_t[] = {{0, "Addons"}, #if 1 {1, "HOME"}, {2, "RINGRACERS"}, #endif {3, "CUSTOM"}, {0, NULL}}; -consvar_t cv_addons_option = CVAR_INIT ("addons_option", "Default", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange); +consvar_t cv_addons_option = CVAR_INIT ("addons_option", "Addons", CV_SAVE|CV_CALL, addons_cons_t, Addons_option_Onchange); consvar_t cv_addons_folder = CVAR_INIT ("addons_folder", "", CV_SAVE, NULL, NULL); static CV_PossibleValue_t addons_md5_cons_t[] = {{0, "Name"}, {1, "Contents"}, {0, NULL}}; diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 191542ca7..14ca1a179 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6842,7 +6842,7 @@ void M_Addons(INT32 choice) #if 1 if (cv_addons_option.value == 0) - pathname = usehome ? srb2home : srb2path; + pathname = addonsdir; else if (cv_addons_option.value == 1) pathname = srb2home; else if (cv_addons_option.value == 2) From 5ed30012ed758c16f00c3ac2dbbbbae31f7df475 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 4 Jan 2023 00:09:58 -0800 Subject: [PATCH 097/128] cv_addons_option: rename RINGRACERS to IWAD This maps to the srb2path folder, which is where the IWAD is located. --- src/filesrch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filesrch.c b/src/filesrch.c index 5ec98babc..6f18cdd83 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -312,7 +312,7 @@ closedir (DIR * dirp) static CV_PossibleValue_t addons_cons_t[] = {{0, "Addons"}, #if 1 - {1, "HOME"}, {2, "RINGRACERS"}, + {1, "HOME"}, {2, "IWAD"}, #endif {3, "CUSTOM"}, {0, NULL}}; From 2786c2095e5d19f718d51d4a6ed5d6b88707e45a Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 4 Jan 2023 14:21:10 +0000 Subject: [PATCH 098/128] More incorrect/inconsistent frees --- src/k_profiles.c | 6 ++---- src/p_setup.c | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/k_profiles.c b/src/k_profiles.c index 62f561587..5a9ddd75a 100644 --- a/src/k_profiles.c +++ b/src/k_profiles.c @@ -300,8 +300,7 @@ void PR_LoadProfiles(void) if (strcmp(srb2home,".")) gdfolder = srb2home; - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); I_Error("Not a valid Profile file.\nDelete %s (maybe in %s) and try again.", PROFILESFILE, gdfolder); } save.p += headerlen; @@ -309,8 +308,7 @@ void PR_LoadProfiles(void) version = READUINT8(save.p); if (version > PROFILEVER) { - Z_Free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); I_Error("Existing %s is from the future! (expected %d, got %d)", PROFILESFILE, PROFILEVER, version); } diff --git a/src/p_setup.c b/src/p_setup.c index 37ca56328..42ff2f33d 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -749,8 +749,7 @@ void P_WriteThings(void) filename = va("newthings-%s.lmp", G_BuildMapName(gamemap)); FIL_WriteFile(filename, save.buffer, length); - free(save.buffer); - save.p = NULL; + P_SaveBufferFree(&save); CONS_Printf(M_GetText("%s saved.\n"), filename); } From f9e0c0a4449b4b417003f3ad73a451dbc25754fe Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 4 Jan 2023 17:22:57 +0000 Subject: [PATCH 099/128] Challenges menu visual adjustment * Use the new background * Use clipped rectangles instead of black pixel overdraw, so pre-baked darkened areas in the new background can be visible instead --- src/k_menudraw.c | 44 ++++++++++++++++---------------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 0f1514cba..3a2a5d078 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1926,8 +1926,6 @@ static void M_DrawCupPreview(INT16 y, levelsearch_t *levelsearch) INT16 map, start = M_GetFirstLevelInList(&i, levelsearch); UINT8 starti = i; - V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); - if (levelsearch->cup && maxlevels > 0) { add = (cupgrid.previewanim / 82) % maxlevels; @@ -2060,7 +2058,9 @@ void M_DrawCupSelect(void) templevelsearch.cup = cupgrid.builtgrid[CUPMENU_CURSORID]; + V_DrawFill(0, 146 + (24*menutransition.tics), BASEVIDWIDTH, 54, 31); M_DrawCupPreview(146 + (24*menutransition.tics), &templevelsearch); + M_DrawCupTitle(120 - (24*menutransition.tics), &templevelsearch); } @@ -4518,7 +4518,8 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili patch_t *pat = missingpat; UINT8 *colormap = NULL; fixed_t siz; - UINT8 id, num, work; + UINT8 id, num; + UINT32 edgelength; id = (i * CHALLENGEGRIDHEIGHT) + j; num = gamedata->challengegrid[id]; @@ -4526,19 +4527,19 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili // Empty spots in the grid are always unconnected. if (num >= MAXUNLOCKABLES) { - V_DrawFill(x, y, 16, 16, challengesbordercolor); goto drawborder; } // Okay, this is what we want to draw. ref = &unlockables[num]; + edgelength = (ref->majorunlock ? 30 : 14); + // ...unless we simply aren't unlocked yet. if ((gamedata->unlocked[num] == false) || (challengesmenu.pending && num == challengesmenu.currentunlock && challengesmenu.unlockanim <= UNLOCKTIME)) { - work = (ref->majorunlock) ? 2 : 1; - V_DrawFill(x, y, 16*work, 16*work, + V_DrawFill(x+1, y+1, edgelength, edgelength, ((challengesmenu.extradata[id] == CHE_HINT) ? 132 : 11)); goto drawborder; } @@ -4589,6 +4590,12 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili siz = (SHORT(pat->width) << FRACBITS); siz = FixedDiv(((ref->majorunlock) ? 32 : 16) << FRACBITS, siz); + V_SetClipRect( + (x+1) << FRACBITS, (y+1) << FRACBITS, + edgelength << FRACBITS, edgelength << FRACBITS, + 0 + ); + V_DrawFixedPatch( x*FRACUNIT, y*FRACUNIT, siz, @@ -4596,19 +4603,11 @@ static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili colormap ); + V_ClearClipRect(); + drawborder: if (!hili) { - if (ref != NULL) - { - work = 16 * (ref->majorunlock ? 2 : 1); - // Horizontal - V_DrawFill(x, y , work, 1, challengesbordercolor); - V_DrawFill(x, y + work-1, work, 1, challengesbordercolor); - // Vertical - V_DrawFill(x , y+1, 1, work-2, challengesbordercolor); - V_DrawFill(x + work-1, y+1, 1, work-2, challengesbordercolor); - } return; } @@ -4628,7 +4627,6 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) if (challengesmenu.currentunlock >= MAXUNLOCKABLES) { - V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor); return; } @@ -4638,13 +4636,9 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) if (!gamedata->unlocked[challengesmenu.currentunlock]) { // todo draw some sort of question mark? - V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor); return; } - if (ref->type != SECRET_CUP) - V_DrawFill(0, 146, BASEVIDWIDTH, 54, challengesbordercolor); - switch (ref->type) { case SECRET_SKIN: @@ -4812,7 +4806,7 @@ void M_DrawChallenges(void) INT16 offset; { - patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); + patch_t *bg = W_CachePatchName("BGUNLCK2", PU_CACHE); V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); } @@ -4847,15 +4841,10 @@ void M_DrawChallenges(void) i = gamedata->challengegridwidth-1; explodex = x - (i*16)/2; x += (i*16)/2; - - V_DrawFill(0, currentMenu->y, explodex, (CHALLENGEGRIDHEIGHT*16), challengesbordercolor); - V_DrawFill((x+16), currentMenu->y, BASEVIDWIDTH - (x+16), (CHALLENGEGRIDHEIGHT*16), challengesbordercolor); } selectx = explodex + (challengesmenu.hilix*16); - V_DrawFill(0, (currentMenu->y)-1 , BASEVIDWIDTH, 1, challengesbordercolor); - V_DrawFill(0, (currentMenu->y) + (CHALLENGEGRIDHEIGHT*16), BASEVIDWIDTH, 1, challengesbordercolor); while (i >= 0 && x >= -32) { y = currentMenu->y-16; @@ -4910,7 +4899,6 @@ challengedesc: // Name bar { y = 120; - V_DrawScaledPatch(0, y, 0, W_CachePatchName("MENUHINT", PU_CACHE)); if (challengesmenu.currentunlock < MAXUNLOCKABLES) { From 06e7eb35671b349b497904afa065158af00aaec0 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 4 Jan 2023 17:23:18 +0000 Subject: [PATCH 100/128] Add a funny little rotating question mark ala SA1 character select --- src/info.c | 2 ++ src/info.h | 2 ++ src/k_menudraw.c | 24 +++++++++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/info.c b/src/info.c index 8e3757190..7cee3d58b 100644 --- a/src/info.c +++ b/src/info.c @@ -789,6 +789,8 @@ char sprnames[NUMSPRITES + 1][5] = "UFOA", "UFOS", + "UQMK", + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; diff --git a/src/info.h b/src/info.h index d0d5228e3..9cc7a3911 100644 --- a/src/info.h +++ b/src/info.h @@ -1340,6 +1340,8 @@ typedef enum sprite SPR_UFOA, SPR_UFOS, + SPR_UQMK, + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 3a2a5d078..af7c141a2 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4633,9 +4633,31 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) // Okay, this is what we want to draw. ref = &unlockables[challengesmenu.currentunlock]; + // Funny question mark? if (!gamedata->unlocked[challengesmenu.currentunlock]) { - // todo draw some sort of question mark? + spritedef_t *sprdef = &sprites[SPR_UQMK]; + spriteframe_t *sprframe; + patch_t *patch; + UINT32 useframe; + UINT32 addflags = 0; + + if (!sprdef->numframes) + { + return; + } + + useframe = (challengesmenu.ticker / 2) % sprdef->numframes; + + sprframe = &sprdef->spriteframes[useframe]; + patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE); + + if (sprframe->flip & 1) // Only for first sprite + { + addflags ^= V_FLIP; // This sprite is left/right flipped! + } + + V_DrawFixedPatch(x*FRACUNIT, (y+6)*FRACUNIT, FRACUNIT, addflags, patch, NULL); return; } From 20b6a8389b57ae4d474f1691f5cddcf63bf033df Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 4 Jan 2023 17:28:45 +0000 Subject: [PATCH 101/128] Challenge grid population improvement * Correctly identify that two columns with only one major unlock will have 6 empty tile slots, not two. This will result in less underutilised Challenges grid space. --- src/m_cond.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/m_cond.c b/src/m_cond.c index 14ad2b6b4..d93ff8d55 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -97,21 +97,27 @@ void M_PopulateChallengeGrid(void) { // Getting the number of 2-highs you can fit into two adjacent columns. UINT8 majorpad = (CHALLENGEGRIDHEIGHT/2); - majorpad = (nummajorunlocks+1)/majorpad; + numempty = nummajorunlocks%majorpad; + majorpad = (nummajorunlocks+(majorpad-1))/majorpad; gamedata->challengegridwidth = majorpad*2; + numempty *= 4; #if (CHALLENGEGRIDHEIGHT % 2) - // One empty per column. - numempty = gamedata->challengegridwidth; + // One extra empty per column. + numempty += gamedata->challengegridwidth; #endif + + //CONS_Printf("%d major unlocks means width of %d, numempty of %d\n", nummajorunlocks, gamedata->challengegridwidth, numempty); } if (numunlocks > numempty) { // Getting the number of extra columns to store normal unlocks - gamedata->challengegridwidth += ((numunlocks - numempty) + (CHALLENGEGRIDHEIGHT-1))/CHALLENGEGRIDHEIGHT; + UINT16 temp = ((numunlocks - numempty) + (CHALLENGEGRIDHEIGHT-1))/CHALLENGEGRIDHEIGHT; + gamedata->challengegridwidth += temp; majorcompact = 1; + //CONS_Printf("%d normal unlocks means %d extra entries, additional width of %d\n", numunlocks, (numunlocks - numempty), temp); } else if (challengegridloops) { From 6274b1f31f818c46893b7541101017604a83622a Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 4 Jan 2023 17:45:24 +0000 Subject: [PATCH 102/128] Fast forward through the unlock sequence by holding the Extra button. Based on VC complaints. --- src/k_menufunc.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 191542ca7..28fb0b50c 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -1219,18 +1219,33 @@ static boolean M_MenuConfirmPressed(UINT8 pid) return M_MenuButtonPressed(pid, MBT_A); } +/*static boolean M_MenuConfirmHeld(UINT8 pid) +{ + return M_MenuButtonHeld(pid, MBT_A); +}*/ + // Returns true if we press the Cancel button static boolean M_MenuBackPressed(UINT8 pid) { return (M_MenuButtonPressed(pid, MBT_B) || M_MenuButtonPressed(pid, MBT_X)); } +/*static boolean M_MenuBackHeld(UINT8 pid) +{ + return (M_MenuButtonHeld(pid, MBT_B) || M_MenuButtonHeld(pid, MBT_X)); +}*/ + // Retrurns true if we press the tertiary option button (C) static boolean M_MenuExtraPressed(UINT8 pid) { return M_MenuButtonPressed(pid, MBT_C); } +static boolean M_MenuExtraHeld(UINT8 pid) +{ + return M_MenuButtonHeld(pid, MBT_C); +} + // Updates the x coordinate of the keybord so prevent it from going in weird places static void M_UpdateKeyboardX(void) @@ -7369,6 +7384,7 @@ void M_Challenges(INT32 choice) void M_ChallengesTick(void) { + const UINT8 pid = 0; UINT8 i, newunlock = MAXUNLOCKABLES; boolean fresh = (challengesmenu.currentunlock >= MAXUNLOCKABLES); @@ -7412,8 +7428,9 @@ void M_ChallengesTick(void) else { // Unlock sequence. + tic_t nexttime = M_MenuExtraHeld(pid) ? (UNLOCKTIME*2) : MAXUNLOCKTIME; - if (++challengesmenu.unlockanim >= MAXUNLOCKTIME) + if (++challengesmenu.unlockanim >= nexttime) { challengesmenu.requestnew = true; } From c628014c472d469a0b09661b69dfca892ee27969 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 4 Jan 2023 17:52:28 +0000 Subject: [PATCH 103/128] Add an extra guard for unlock condition string drawing to prevent hypothetical out of bounds read --- src/k_menudraw.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index af7c141a2..6da10157a 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4950,6 +4950,7 @@ challengedesc: i = (challengesmenu.hilix * CHALLENGEGRIDHEIGHT) + challengesmenu.hiliy; if (challengesmenu.unlockcondition != NULL + && challengesmenu.currentunlock < MAXUNLOCKABLES && ((gamedata->unlocked[challengesmenu.currentunlock] == true) || ((challengesmenu.extradata != NULL) && (challengesmenu.extradata[i] & CHE_HINT)) From 91a40268711d958e06b7452764ea7494b2cce387 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 4 Jan 2023 18:04:38 +0000 Subject: [PATCH 104/128] Remove now-unused definition for challengesbordercolor --- src/k_menudraw.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 6da10157a..7cdbb91a7 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4510,8 +4510,6 @@ void M_DrawAddons(void) // Challenges Menu -#define challengesbordercolor 27 - static void M_DrawChallengeTile(INT16 i, INT16 j, INT32 x, INT32 y, boolean hili) { unlockable_t *ref = NULL; From 2a0e183340445319152eef10b58d31b21fcb4266 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Wed, 4 Jan 2023 14:51:09 -0600 Subject: [PATCH 105/128] legal: Add copyright notice to io/streams --- src/io/streams.cpp | 9 +++++++++ src/io/streams.hpp | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/io/streams.cpp b/src/io/streams.cpp index af134548b..6ba250a24 100644 --- a/src/io/streams.cpp +++ b/src/io/streams.cpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #include "streams.hpp" template class srb2::io::ZlibInputStream; diff --git a/src/io/streams.hpp b/src/io/streams.hpp index 60ad130b1..ca4d7cda8 100644 --- a/src/io/streams.hpp +++ b/src/io/streams.hpp @@ -1,3 +1,12 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + #ifndef __SRB2_IO_STREAMS_HPP__ #define __SRB2_IO_STREAMS_HPP__ From 25e3b4239c542a519b8e0b05eb0e7ac27cf4d75b Mon Sep 17 00:00:00 2001 From: Eidolon Date: Tue, 3 Jan 2023 19:24:48 -0600 Subject: [PATCH 106/128] io: Allow span and vec stream to seek past end --- src/io/streams.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/io/streams.hpp b/src/io/streams.hpp index ca4d7cda8..643cfd987 100644 --- a/src/io/streams.hpp +++ b/src/io/streams.hpp @@ -418,7 +418,7 @@ public: switch (seek_from) { case SeekFrom::kStart: - if (offset < 0 || offset >= static_cast(span_.size())) { + if (offset < 0) { throw std::logic_error("start offset is out of bounds"); } head = offset; @@ -430,7 +430,7 @@ public: head = span_.size() - offset; break; case SeekFrom::kCurrent: - if (head_ + offset < 0 || head_ + offset >= span_.size()) { + if (head_ + offset < 0) { throw std::logic_error("offset is out of bounds"); } head = head_ + offset; @@ -489,7 +489,7 @@ public: switch (seek_from) { case SeekFrom::kStart: - if (offset < 0 || offset >= static_cast(vec_.size())) { + if (offset < 0) { throw std::logic_error("start offset is out of bounds"); } head = offset; @@ -501,7 +501,7 @@ public: head = vec_.size() - offset; break; case SeekFrom::kCurrent: - if (head_ + offset < 0 || head_ + offset >= vec_.size()) { + if (head_ + offset < 0) { throw std::logic_error("offset is out of bounds"); } head = head_ + offset; From 210b513c8be2ddd7e22547597078a2cd5821a9b4 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sun, 1 Jan 2023 15:09:57 -0600 Subject: [PATCH 107/128] cmake: Add xmp-lite This is a very lightweight module playback engine which will replace OpenMPT. --- thirdparty/CMakeLists.txt | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 142bd2491..4aab20b9b 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -605,4 +605,64 @@ if(DiscordRPC_ADDED) endif() endif() +CPMAddPackage( + NAME xmp-lite + VERSION 4.5.0 + URL "https://github.com/libxmp/libxmp/releases/download/libxmp-4.5.0/libxmp-lite-4.5.0.tar.gz" + EXCLUDE_FROM_ALL ON + DOWNLOAD_ONLY ON +) +if(xmp-lite_ADDED) + set(xmp_sources + virtual.c + format.c + period.c + player.c + read_event.c + misc.c + dataio.c + lfo.c + scan.c + control.c + filter.c + effects.c + mixer.c + mix_all.c + load_helpers.c + load.c + hio.c + smix.c + memio.c + win32.c + + loaders/common.c + loaders/itsex.c + loaders/sample.c + loaders/xm_load.c + loaders/mod_load.c + loaders/s3m_load.c + loaders/it_load.c + ) + list(TRANSFORM xmp_sources PREPEND "${xmp-lite_SOURCE_DIR}/src/") + + add_library(xmp-lite "${SRB2_INTERNAL_LIBRARY_TYPE}" ${xmp_sources}) + + target_compile_definitions(xmp-lite PRIVATE -D_REENTRANT -DLIBXMP_CORE_PLAYER -DLIBXMP_NO_PROWIZARD -DLIBXMP_NO_DEPACKERS) + if("${SRB2_INTERNAL_LIBRARY_TYPE}" STREQUAL "STATIC") + if(WIN32) + # BUILDING_STATIC has to be public to work around a bug in xmp.h + # which adds __declspec(dllimport) even when statically linking + target_compile_definitions(xmp-lite PUBLIC -DBUILDING_STATIC) + else() + target_compile_definitions(xmp-lite PRIVATE -DBUILDING_STATIC) + endif() + else() + target_compile_definitions(xmp-lite PRIVATE -DBUILDING_DLL) + endif() + target_include_directories(xmp-lite PRIVATE "${xmp-lite_SOURCE_DIR}/src") + target_include_directories(xmp-lite PUBLIC "${xmp-lite_SOURCE_DIR}/include/libxmp-lite") + + add_library(xmp-lite::xmp-lite ALIAS xmp-lite) +endif() + add_subdirectory(tcbrindle_span) From 9806df5883ca77718e732efb6b7e3479cdeeb0c2 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sun, 1 Jan 2023 15:11:08 -0600 Subject: [PATCH 108/128] cmake: Add stb-vorbis This is a lightweight single-file Ogg Vorbis decoder which will be used for Ogg playback instead of libogg/libvorbis. --- thirdparty/CMakeLists.txt | 1 + thirdparty/stb_vorbis/CMakeLists.txt | 4 + thirdparty/stb_vorbis/include/stb_vorbis.h | 2 + thirdparty/stb_vorbis/stb_vorbis.c | 5584 ++++++++++++++++++++ 4 files changed, 5591 insertions(+) create mode 100644 thirdparty/stb_vorbis/CMakeLists.txt create mode 100644 thirdparty/stb_vorbis/include/stb_vorbis.h create mode 100644 thirdparty/stb_vorbis/stb_vorbis.c diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 4aab20b9b..8c828ff29 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -666,3 +666,4 @@ if(xmp-lite_ADDED) endif() add_subdirectory(tcbrindle_span) +add_subdirectory(stb_vorbis) diff --git a/thirdparty/stb_vorbis/CMakeLists.txt b/thirdparty/stb_vorbis/CMakeLists.txt new file mode 100644 index 000000000..3cc4bc5c3 --- /dev/null +++ b/thirdparty/stb_vorbis/CMakeLists.txt @@ -0,0 +1,4 @@ +# Update from https://github.com/nothings/stb +# This doesn't use CPM because stb_vorbis.c has a weird header setup +add_library(stb_vorbis STATIC stb_vorbis.c include/stb_vorbis.h) +target_include_directories(stb_vorbis PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") diff --git a/thirdparty/stb_vorbis/include/stb_vorbis.h b/thirdparty/stb_vorbis/include/stb_vorbis.h new file mode 100644 index 000000000..ef0b4d369 --- /dev/null +++ b/thirdparty/stb_vorbis/include/stb_vorbis.h @@ -0,0 +1,2 @@ +#define STB_VORBIS_HEADER_ONLY +#include "../stb_vorbis.c" diff --git a/thirdparty/stb_vorbis/stb_vorbis.c b/thirdparty/stb_vorbis/stb_vorbis.c new file mode 100644 index 000000000..3e5c2504c --- /dev/null +++ b/thirdparty/stb_vorbis/stb_vorbis.c @@ -0,0 +1,5584 @@ +// Ogg Vorbis audio decoder - v1.22 - public domain +// http://nothings.org/stb_vorbis/ +// +// Original version written by Sean Barrett in 2007. +// +// Originally sponsored by RAD Game Tools. Seeking implementation +// sponsored by Phillip Bennefall, Marc Andersen, Aaron Baker, +// Elias Software, Aras Pranckevicius, and Sean Barrett. +// +// LICENSE +// +// See end of file for license information. +// +// Limitations: +// +// - floor 0 not supported (used in old ogg vorbis files pre-2004) +// - lossless sample-truncation at beginning ignored +// - cannot concatenate multiple vorbis streams +// - sample positions are 32-bit, limiting seekable 192Khz +// files to around 6 hours (Ogg supports 64-bit) +// +// Feature contributors: +// Dougall Johnson (sample-exact seeking) +// +// Bugfix/warning contributors: +// Terje Mathisen Niklas Frykholm Andy Hill +// Casey Muratori John Bolton Gargaj +// Laurent Gomila Marc LeBlanc Ronny Chevalier +// Bernhard Wodo Evan Balster github:alxprd +// Tom Beaumont Ingo Leitgeb Nicolas Guillemot +// Phillip Bennefall Rohit Thiago Goulart +// github:manxorist Saga Musix github:infatum +// Timur Gagiev Maxwell Koo Peter Waller +// github:audinowho Dougall Johnson David Reid +// github:Clownacy Pedro J. Estebanez Remi Verschelde +// AnthoFoxo github:morlat Gabriel Ravier +// +// Partial history: +// 1.22 - 2021-07-11 - various small fixes +// 1.21 - 2021-07-02 - fix bug for files with no comments +// 1.20 - 2020-07-11 - several small fixes +// 1.19 - 2020-02-05 - warnings +// 1.18 - 2020-02-02 - fix seek bugs; parse header comments; misc warnings etc. +// 1.17 - 2019-07-08 - fix CVE-2019-13217..CVE-2019-13223 (by ForAllSecure) +// 1.16 - 2019-03-04 - fix warnings +// 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found +// 1.14 - 2018-02-11 - delete bogus dealloca usage +// 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) +// 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files +// 1.11 - 2017-07-23 - fix MinGW compilation +// 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory +// 1.09 - 2016-04-04 - back out 'truncation of last frame' fix from previous version +// 1.08 - 2016-04-02 - warnings; setup memory leaks; truncation of last frame +// 1.07 - 2015-01-16 - fixes for crashes on invalid files; warning fixes; const +// 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) +// some crash fixes when out of memory or with corrupt files +// fix some inappropriately signed shifts +// 1.05 - 2015-04-19 - don't define __forceinline if it's redundant +// 1.04 - 2014-08-27 - fix missing const-correct case in API +// 1.03 - 2014-08-07 - warning fixes +// 1.02 - 2014-07-09 - declare qsort comparison as explicitly _cdecl in Windows +// 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float (interleaved was correct) +// 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in >2-channel; +// (API change) report sample rate for decode-full-file funcs +// +// See end of file for full version history. + + +////////////////////////////////////////////////////////////////////////////// +// +// HEADER BEGINS HERE +// + +#ifndef STB_VORBIS_INCLUDE_STB_VORBIS_H +#define STB_VORBIS_INCLUDE_STB_VORBIS_H + +#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) +#define STB_VORBIS_NO_STDIO 1 +#endif + +#ifndef STB_VORBIS_NO_STDIO +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/////////// THREAD SAFETY + +// Individual stb_vorbis* handles are not thread-safe; you cannot decode from +// them from multiple threads at the same time. However, you can have multiple +// stb_vorbis* handles and decode from them independently in multiple thrads. + + +/////////// MEMORY ALLOCATION + +// normally stb_vorbis uses malloc() to allocate memory at startup, +// and alloca() to allocate temporary memory during a frame on the +// stack. (Memory consumption will depend on the amount of setup +// data in the file and how you set the compile flags for speed +// vs. size. In my test files the maximal-size usage is ~150KB.) +// +// You can modify the wrapper functions in the source (setup_malloc, +// setup_temp_malloc, temp_malloc) to change this behavior, or you +// can use a simpler allocation model: you pass in a buffer from +// which stb_vorbis will allocate _all_ its memory (including the +// temp memory). "open" may fail with a VORBIS_outofmem if you +// do not pass in enough data; there is no way to determine how +// much you do need except to succeed (at which point you can +// query get_info to find the exact amount required. yes I know +// this is lame). +// +// If you pass in a non-NULL buffer of the type below, allocation +// will occur from it as described above. Otherwise just pass NULL +// to use malloc()/alloca() + +typedef struct +{ + char *alloc_buffer; + int alloc_buffer_length_in_bytes; +} stb_vorbis_alloc; + + +/////////// FUNCTIONS USEABLE WITH ALL INPUT MODES + +typedef struct stb_vorbis stb_vorbis; + +typedef struct +{ + unsigned int sample_rate; + int channels; + + unsigned int setup_memory_required; + unsigned int setup_temp_memory_required; + unsigned int temp_memory_required; + + int max_frame_size; +} stb_vorbis_info; + +typedef struct +{ + char *vendor; + + int comment_list_length; + char **comment_list; +} stb_vorbis_comment; + +// get general information about the file +extern stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f); + +// get ogg comments +extern stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f); + +// get the last error detected (clears it, too) +extern int stb_vorbis_get_error(stb_vorbis *f); + +// close an ogg vorbis file and free all memory in use +extern void stb_vorbis_close(stb_vorbis *f); + +// this function returns the offset (in samples) from the beginning of the +// file that will be returned by the next decode, if it is known, or -1 +// otherwise. after a flush_pushdata() call, this may take a while before +// it becomes valid again. +// NOT WORKING YET after a seek with PULLDATA API +extern int stb_vorbis_get_sample_offset(stb_vorbis *f); + +// returns the current seek point within the file, or offset from the beginning +// of the memory buffer. In pushdata mode it returns 0. +extern unsigned int stb_vorbis_get_file_offset(stb_vorbis *f); + +/////////// PUSHDATA API + +#ifndef STB_VORBIS_NO_PUSHDATA_API + +// this API allows you to get blocks of data from any source and hand +// them to stb_vorbis. you have to buffer them; stb_vorbis will tell +// you how much it used, and you have to give it the rest next time; +// and stb_vorbis may not have enough data to work with and you will +// need to give it the same data again PLUS more. Note that the Vorbis +// specification does not bound the size of an individual frame. + +extern stb_vorbis *stb_vorbis_open_pushdata( + const unsigned char * datablock, int datablock_length_in_bytes, + int *datablock_memory_consumed_in_bytes, + int *error, + const stb_vorbis_alloc *alloc_buffer); +// create a vorbis decoder by passing in the initial data block containing +// the ogg&vorbis headers (you don't need to do parse them, just provide +// the first N bytes of the file--you're told if it's not enough, see below) +// on success, returns an stb_vorbis *, does not set error, returns the amount of +// data parsed/consumed on this call in *datablock_memory_consumed_in_bytes; +// on failure, returns NULL on error and sets *error, does not change *datablock_memory_consumed +// if returns NULL and *error is VORBIS_need_more_data, then the input block was +// incomplete and you need to pass in a larger block from the start of the file + +extern int stb_vorbis_decode_frame_pushdata( + stb_vorbis *f, + const unsigned char *datablock, int datablock_length_in_bytes, + int *channels, // place to write number of float * buffers + float ***output, // place to write float ** array of float * buffers + int *samples // place to write number of output samples + ); +// decode a frame of audio sample data if possible from the passed-in data block +// +// return value: number of bytes we used from datablock +// +// possible cases: +// 0 bytes used, 0 samples output (need more data) +// N bytes used, 0 samples output (resynching the stream, keep going) +// N bytes used, M samples output (one frame of data) +// note that after opening a file, you will ALWAYS get one N-bytes,0-sample +// frame, because Vorbis always "discards" the first frame. +// +// Note that on resynch, stb_vorbis will rarely consume all of the buffer, +// instead only datablock_length_in_bytes-3 or less. This is because it wants +// to avoid missing parts of a page header if they cross a datablock boundary, +// without writing state-machiney code to record a partial detection. +// +// The number of channels returned are stored in *channels (which can be +// NULL--it is always the same as the number of channels reported by +// get_info). *output will contain an array of float* buffers, one per +// channel. In other words, (*output)[0][0] contains the first sample from +// the first channel, and (*output)[1][0] contains the first sample from +// the second channel. +// +// *output points into stb_vorbis's internal output buffer storage; these +// buffers are owned by stb_vorbis and application code should not free +// them or modify their contents. They are transient and will be overwritten +// once you ask for more data to get decoded, so be sure to grab any data +// you need before then. + +extern void stb_vorbis_flush_pushdata(stb_vorbis *f); +// inform stb_vorbis that your next datablock will not be contiguous with +// previous ones (e.g. you've seeked in the data); future attempts to decode +// frames will cause stb_vorbis to resynchronize (as noted above), and +// once it sees a valid Ogg page (typically 4-8KB, as large as 64KB), it +// will begin decoding the _next_ frame. +// +// if you want to seek using pushdata, you need to seek in your file, then +// call stb_vorbis_flush_pushdata(), then start calling decoding, then once +// decoding is returning you data, call stb_vorbis_get_sample_offset, and +// if you don't like the result, seek your file again and repeat. +#endif + + +////////// PULLING INPUT API + +#ifndef STB_VORBIS_NO_PULLDATA_API +// This API assumes stb_vorbis is allowed to pull data from a source-- +// either a block of memory containing the _entire_ vorbis stream, or a +// FILE * that you or it create, or possibly some other reading mechanism +// if you go modify the source to replace the FILE * case with some kind +// of callback to your code. (But if you don't support seeking, you may +// just want to go ahead and use pushdata.) + +#if !defined(STB_VORBIS_NO_STDIO) && !defined(STB_VORBIS_NO_INTEGER_CONVERSION) +extern int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output); +#endif +#if !defined(STB_VORBIS_NO_INTEGER_CONVERSION) +extern int stb_vorbis_decode_memory(const unsigned char *mem, int len, int *channels, int *sample_rate, short **output); +#endif +// decode an entire file and output the data interleaved into a malloc()ed +// buffer stored in *output. The return value is the number of samples +// decoded, or -1 if the file could not be opened or was not an ogg vorbis file. +// When you're done with it, just free() the pointer returned in *output. + +extern stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from an ogg vorbis stream in memory (note +// this must be the entire stream!). on failure, returns NULL and sets *error + +#ifndef STB_VORBIS_NO_STDIO +extern stb_vorbis * stb_vorbis_open_filename(const char *filename, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from a filename via fopen(). on failure, +// returns NULL and sets *error (possibly to VORBIS_file_open_failure). + +extern stb_vorbis * stb_vorbis_open_file(FILE *f, int close_handle_on_close, + int *error, const stb_vorbis_alloc *alloc_buffer); +// create an ogg vorbis decoder from an open FILE *, looking for a stream at +// the _current_ seek point (ftell). on failure, returns NULL and sets *error. +// note that stb_vorbis must "own" this stream; if you seek it in between +// calls to stb_vorbis, it will become confused. Moreover, if you attempt to +// perform stb_vorbis_seek_*() operations on this file, it will assume it +// owns the _entire_ rest of the file after the start point. Use the next +// function, stb_vorbis_open_file_section(), to limit it. + +extern stb_vorbis * stb_vorbis_open_file_section(FILE *f, int close_handle_on_close, + int *error, const stb_vorbis_alloc *alloc_buffer, unsigned int len); +// create an ogg vorbis decoder from an open FILE *, looking for a stream at +// the _current_ seek point (ftell); the stream will be of length 'len' bytes. +// on failure, returns NULL and sets *error. note that stb_vorbis must "own" +// this stream; if you seek it in between calls to stb_vorbis, it will become +// confused. +#endif + +extern int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number); +extern int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number); +// these functions seek in the Vorbis file to (approximately) 'sample_number'. +// after calling seek_frame(), the next call to get_frame_*() will include +// the specified sample. after calling stb_vorbis_seek(), the next call to +// stb_vorbis_get_samples_* will start with the specified sample. If you +// do not need to seek to EXACTLY the target sample when using get_samples_*, +// you can also use seek_frame(). + +extern int stb_vorbis_seek_start(stb_vorbis *f); +// this function is equivalent to stb_vorbis_seek(f,0) + +extern unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f); +extern float stb_vorbis_stream_length_in_seconds(stb_vorbis *f); +// these functions return the total length of the vorbis stream + +extern int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output); +// decode the next frame and return the number of samples. the number of +// channels returned are stored in *channels (which can be NULL--it is always +// the same as the number of channels reported by get_info). *output will +// contain an array of float* buffers, one per channel. These outputs will +// be overwritten on the next call to stb_vorbis_get_frame_*. +// +// You generally should not intermix calls to stb_vorbis_get_frame_*() +// and stb_vorbis_get_samples_*(), since the latter calls the former. + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +extern int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts); +extern int stb_vorbis_get_frame_short (stb_vorbis *f, int num_c, short **buffer, int num_samples); +#endif +// decode the next frame and return the number of *samples* per channel. +// Note that for interleaved data, you pass in the number of shorts (the +// size of your array), but the return value is the number of samples per +// channel, not the total number of samples. +// +// The data is coerced to the number of channels you request according to the +// channel coercion rules (see below). You must pass in the size of your +// buffer(s) so that stb_vorbis will not overwrite the end of the buffer. +// The maximum buffer size needed can be gotten from get_info(); however, +// the Vorbis I specification implies an absolute maximum of 4096 samples +// per channel. + +// Channel coercion rules: +// Let M be the number of channels requested, and N the number of channels present, +// and Cn be the nth channel; let stereo L be the sum of all L and center channels, +// and stereo R be the sum of all R and center channels (channel assignment from the +// vorbis spec). +// M N output +// 1 k sum(Ck) for all k +// 2 * stereo L, stereo R +// k l k > l, the first l channels, then 0s +// k l k <= l, the first k channels +// Note that this is not _good_ surround etc. mixing at all! It's just so +// you get something useful. + +extern int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats); +extern int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples); +// gets num_samples samples, not necessarily on a frame boundary--this requires +// buffering so you have to supply the buffers. DOES NOT APPLY THE COERCION RULES. +// Returns the number of samples stored per channel; it may be less than requested +// at the end of the file. If there are no more samples in the file, returns 0. + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +extern int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts); +extern int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int num_samples); +#endif +// gets num_samples samples, not necessarily on a frame boundary--this requires +// buffering so you have to supply the buffers. Applies the coercion rules above +// to produce 'channels' channels. Returns the number of samples stored per channel; +// it may be less than requested at the end of the file. If there are no more +// samples in the file, returns 0. + +#endif + +//////// ERROR CODES + +enum STBVorbisError +{ + VORBIS__no_error, + + VORBIS_need_more_data=1, // not a real error + + VORBIS_invalid_api_mixing, // can't mix API modes + VORBIS_outofmem, // not enough memory + VORBIS_feature_not_supported, // uses floor 0 + VORBIS_too_many_channels, // STB_VORBIS_MAX_CHANNELS is too small + VORBIS_file_open_failure, // fopen() failed + VORBIS_seek_without_length, // can't seek in unknown-length file + + VORBIS_unexpected_eof=10, // file is truncated? + VORBIS_seek_invalid, // seek past EOF + + // decoding errors (corrupt/invalid stream) -- you probably + // don't care about the exact details of these + + // vorbis errors: + VORBIS_invalid_setup=20, + VORBIS_invalid_stream, + + // ogg errors: + VORBIS_missing_capture_pattern=30, + VORBIS_invalid_stream_structure_version, + VORBIS_continued_packet_flag_invalid, + VORBIS_incorrect_stream_serial_number, + VORBIS_invalid_first_page, + VORBIS_bad_packet_type, + VORBIS_cant_find_last_page, + VORBIS_seek_failed, + VORBIS_ogg_skeleton_not_supported +}; + + +#ifdef __cplusplus +} +#endif + +#endif // STB_VORBIS_INCLUDE_STB_VORBIS_H +// +// HEADER ENDS HERE +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef STB_VORBIS_HEADER_ONLY + +// global configuration settings (e.g. set these in the project/makefile), +// or just set them in this file at the top (although ideally the first few +// should be visible when the header file is compiled too, although it's not +// crucial) + +// STB_VORBIS_NO_PUSHDATA_API +// does not compile the code for the various stb_vorbis_*_pushdata() +// functions +// #define STB_VORBIS_NO_PUSHDATA_API + +// STB_VORBIS_NO_PULLDATA_API +// does not compile the code for the non-pushdata APIs +// #define STB_VORBIS_NO_PULLDATA_API + +// STB_VORBIS_NO_STDIO +// does not compile the code for the APIs that use FILE *s internally +// or externally (implied by STB_VORBIS_NO_PULLDATA_API) +// #define STB_VORBIS_NO_STDIO + +// STB_VORBIS_NO_INTEGER_CONVERSION +// does not compile the code for converting audio sample data from +// float to integer (implied by STB_VORBIS_NO_PULLDATA_API) +// #define STB_VORBIS_NO_INTEGER_CONVERSION + +// STB_VORBIS_NO_FAST_SCALED_FLOAT +// does not use a fast float-to-int trick to accelerate float-to-int on +// most platforms which requires endianness be defined correctly. +//#define STB_VORBIS_NO_FAST_SCALED_FLOAT + + +// STB_VORBIS_MAX_CHANNELS [number] +// globally define this to the maximum number of channels you need. +// The spec does not put a restriction on channels except that +// the count is stored in a byte, so 255 is the hard limit. +// Reducing this saves about 16 bytes per value, so using 16 saves +// (255-16)*16 or around 4KB. Plus anything other memory usage +// I forgot to account for. Can probably go as low as 8 (7.1 audio), +// 6 (5.1 audio), or 2 (stereo only). +#ifndef STB_VORBIS_MAX_CHANNELS +#define STB_VORBIS_MAX_CHANNELS 16 // enough for anyone? +#endif + +// STB_VORBIS_PUSHDATA_CRC_COUNT [number] +// after a flush_pushdata(), stb_vorbis begins scanning for the +// next valid page, without backtracking. when it finds something +// that looks like a page, it streams through it and verifies its +// CRC32. Should that validation fail, it keeps scanning. But it's +// possible that _while_ streaming through to check the CRC32 of +// one candidate page, it sees another candidate page. This #define +// determines how many "overlapping" candidate pages it can search +// at once. Note that "real" pages are typically ~4KB to ~8KB, whereas +// garbage pages could be as big as 64KB, but probably average ~16KB. +// So don't hose ourselves by scanning an apparent 64KB page and +// missing a ton of real ones in the interim; so minimum of 2 +#ifndef STB_VORBIS_PUSHDATA_CRC_COUNT +#define STB_VORBIS_PUSHDATA_CRC_COUNT 4 +#endif + +// STB_VORBIS_FAST_HUFFMAN_LENGTH [number] +// sets the log size of the huffman-acceleration table. Maximum +// supported value is 24. with larger numbers, more decodings are O(1), +// but the table size is larger so worse cache missing, so you'll have +// to probe (and try multiple ogg vorbis files) to find the sweet spot. +#ifndef STB_VORBIS_FAST_HUFFMAN_LENGTH +#define STB_VORBIS_FAST_HUFFMAN_LENGTH 10 +#endif + +// STB_VORBIS_FAST_BINARY_LENGTH [number] +// sets the log size of the binary-search acceleration table. this +// is used in similar fashion to the fast-huffman size to set initial +// parameters for the binary search + +// STB_VORBIS_FAST_HUFFMAN_INT +// The fast huffman tables are much more efficient if they can be +// stored as 16-bit results instead of 32-bit results. This restricts +// the codebooks to having only 65535 possible outcomes, though. +// (At least, accelerated by the huffman table.) +#ifndef STB_VORBIS_FAST_HUFFMAN_INT +#define STB_VORBIS_FAST_HUFFMAN_SHORT +#endif + +// STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH +// If the 'fast huffman' search doesn't succeed, then stb_vorbis falls +// back on binary searching for the correct one. This requires storing +// extra tables with the huffman codes in sorted order. Defining this +// symbol trades off space for speed by forcing a linear search in the +// non-fast case, except for "sparse" codebooks. +// #define STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH + +// STB_VORBIS_DIVIDES_IN_RESIDUE +// stb_vorbis precomputes the result of the scalar residue decoding +// that would otherwise require a divide per chunk. you can trade off +// space for time by defining this symbol. +// #define STB_VORBIS_DIVIDES_IN_RESIDUE + +// STB_VORBIS_DIVIDES_IN_CODEBOOK +// vorbis VQ codebooks can be encoded two ways: with every case explicitly +// stored, or with all elements being chosen from a small range of values, +// and all values possible in all elements. By default, stb_vorbis expands +// this latter kind out to look like the former kind for ease of decoding, +// because otherwise an integer divide-per-vector-element is required to +// unpack the index. If you define STB_VORBIS_DIVIDES_IN_CODEBOOK, you can +// trade off storage for speed. +//#define STB_VORBIS_DIVIDES_IN_CODEBOOK + +#ifdef STB_VORBIS_CODEBOOK_SHORTS +#error "STB_VORBIS_CODEBOOK_SHORTS is no longer supported as it produced incorrect results for some input formats" +#endif + +// STB_VORBIS_DIVIDE_TABLE +// this replaces small integer divides in the floor decode loop with +// table lookups. made less than 1% difference, so disabled by default. + +// STB_VORBIS_NO_INLINE_DECODE +// disables the inlining of the scalar codebook fast-huffman decode. +// might save a little codespace; useful for debugging +// #define STB_VORBIS_NO_INLINE_DECODE + +// STB_VORBIS_NO_DEFER_FLOOR +// Normally we only decode the floor without synthesizing the actual +// full curve. We can instead synthesize the curve immediately. This +// requires more memory and is very likely slower, so I don't think +// you'd ever want to do it except for debugging. +// #define STB_VORBIS_NO_DEFER_FLOOR + + + + +////////////////////////////////////////////////////////////////////////////// + +#ifdef STB_VORBIS_NO_PULLDATA_API + #define STB_VORBIS_NO_INTEGER_CONVERSION + #define STB_VORBIS_NO_STDIO +#endif + +#if defined(STB_VORBIS_NO_CRT) && !defined(STB_VORBIS_NO_STDIO) + #define STB_VORBIS_NO_STDIO 1 +#endif + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT + + // only need endianness for fast-float-to-int, which we don't + // use for pushdata + + #ifndef STB_VORBIS_BIG_ENDIAN + #define STB_VORBIS_ENDIAN 0 + #else + #define STB_VORBIS_ENDIAN 1 + #endif + +#endif +#endif + + +#ifndef STB_VORBIS_NO_STDIO +#include +#endif + +#ifndef STB_VORBIS_NO_CRT + #include + #include + #include + #include + + // find definition of alloca if it's not in stdlib.h: + #if defined(_MSC_VER) || defined(__MINGW32__) + #include + #endif + #if defined(__linux__) || defined(__linux) || defined(__sun__) || defined(__EMSCRIPTEN__) || defined(__NEWLIB__) + #include + #endif +#else // STB_VORBIS_NO_CRT + #define NULL 0 + #define malloc(s) 0 + #define free(s) ((void) 0) + #define realloc(s) 0 +#endif // STB_VORBIS_NO_CRT + +#include + +#ifdef __MINGW32__ + // eff you mingw: + // "fixed": + // http://sourceforge.net/p/mingw-w64/mailman/message/32882927/ + // "no that broke the build, reverted, who cares about C": + // http://sourceforge.net/p/mingw-w64/mailman/message/32890381/ + #ifdef __forceinline + #undef __forceinline + #endif + #define __forceinline + #ifndef alloca + #define alloca __builtin_alloca + #endif +#elif !defined(_MSC_VER) + #if __GNUC__ + #define __forceinline inline + #else + #define __forceinline + #endif +#endif + +#if STB_VORBIS_MAX_CHANNELS > 256 +#error "Value of STB_VORBIS_MAX_CHANNELS outside of allowed range" +#endif + +#if STB_VORBIS_FAST_HUFFMAN_LENGTH > 24 +#error "Value of STB_VORBIS_FAST_HUFFMAN_LENGTH outside of allowed range" +#endif + + +#if 0 +#include +#define CHECK(f) _CrtIsValidHeapPointer(f->channel_buffers[1]) +#else +#define CHECK(f) ((void) 0) +#endif + +#define MAX_BLOCKSIZE_LOG 13 // from specification +#define MAX_BLOCKSIZE (1 << MAX_BLOCKSIZE_LOG) + + +typedef unsigned char uint8; +typedef signed char int8; +typedef unsigned short uint16; +typedef signed short int16; +typedef unsigned int uint32; +typedef signed int int32; + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +typedef float codetype; + +#ifdef _MSC_VER +#define STBV_NOTUSED(v) (void)(v) +#else +#define STBV_NOTUSED(v) (void)sizeof(v) +#endif + +// @NOTE +// +// Some arrays below are tagged "//varies", which means it's actually +// a variable-sized piece of data, but rather than malloc I assume it's +// small enough it's better to just allocate it all together with the +// main thing +// +// Most of the variables are specified with the smallest size I could pack +// them into. It might give better performance to make them all full-sized +// integers. It should be safe to freely rearrange the structures or change +// the sizes larger--nothing relies on silently truncating etc., nor the +// order of variables. + +#define FAST_HUFFMAN_TABLE_SIZE (1 << STB_VORBIS_FAST_HUFFMAN_LENGTH) +#define FAST_HUFFMAN_TABLE_MASK (FAST_HUFFMAN_TABLE_SIZE - 1) + +typedef struct +{ + int dimensions, entries; + uint8 *codeword_lengths; + float minimum_value; + float delta_value; + uint8 value_bits; + uint8 lookup_type; + uint8 sequence_p; + uint8 sparse; + uint32 lookup_values; + codetype *multiplicands; + uint32 *codewords; + #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT + int16 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; + #else + int32 fast_huffman[FAST_HUFFMAN_TABLE_SIZE]; + #endif + uint32 *sorted_codewords; + int *sorted_values; + int sorted_entries; +} Codebook; + +typedef struct +{ + uint8 order; + uint16 rate; + uint16 bark_map_size; + uint8 amplitude_bits; + uint8 amplitude_offset; + uint8 number_of_books; + uint8 book_list[16]; // varies +} Floor0; + +typedef struct +{ + uint8 partitions; + uint8 partition_class_list[32]; // varies + uint8 class_dimensions[16]; // varies + uint8 class_subclasses[16]; // varies + uint8 class_masterbooks[16]; // varies + int16 subclass_books[16][8]; // varies + uint16 Xlist[31*8+2]; // varies + uint8 sorted_order[31*8+2]; + uint8 neighbors[31*8+2][2]; + uint8 floor1_multiplier; + uint8 rangebits; + int values; +} Floor1; + +typedef union +{ + Floor0 floor0; + Floor1 floor1; +} Floor; + +typedef struct +{ + uint32 begin, end; + uint32 part_size; + uint8 classifications; + uint8 classbook; + uint8 **classdata; + int16 (*residue_books)[8]; +} Residue; + +typedef struct +{ + uint8 magnitude; + uint8 angle; + uint8 mux; +} MappingChannel; + +typedef struct +{ + uint16 coupling_steps; + MappingChannel *chan; + uint8 submaps; + uint8 submap_floor[15]; // varies + uint8 submap_residue[15]; // varies +} Mapping; + +typedef struct +{ + uint8 blockflag; + uint8 mapping; + uint16 windowtype; + uint16 transformtype; +} Mode; + +typedef struct +{ + uint32 goal_crc; // expected crc if match + int bytes_left; // bytes left in packet + uint32 crc_so_far; // running crc + int bytes_done; // bytes processed in _current_ chunk + uint32 sample_loc; // granule pos encoded in page +} CRCscan; + +typedef struct +{ + uint32 page_start, page_end; + uint32 last_decoded_sample; +} ProbedPage; + +struct stb_vorbis +{ + // user-accessible info + unsigned int sample_rate; + int channels; + + unsigned int setup_memory_required; + unsigned int temp_memory_required; + unsigned int setup_temp_memory_required; + + char *vendor; + int comment_list_length; + char **comment_list; + + // input config +#ifndef STB_VORBIS_NO_STDIO + FILE *f; + uint32 f_start; + int close_on_free; +#endif + + uint8 *stream; + uint8 *stream_start; + uint8 *stream_end; + + uint32 stream_len; + + uint8 push_mode; + + // the page to seek to when seeking to start, may be zero + uint32 first_audio_page_offset; + + // p_first is the page on which the first audio packet ends + // (but not necessarily the page on which it starts) + ProbedPage p_first, p_last; + + // memory management + stb_vorbis_alloc alloc; + int setup_offset; + int temp_offset; + + // run-time results + int eof; + enum STBVorbisError error; + + // user-useful data + + // header info + int blocksize[2]; + int blocksize_0, blocksize_1; + int codebook_count; + Codebook *codebooks; + int floor_count; + uint16 floor_types[64]; // varies + Floor *floor_config; + int residue_count; + uint16 residue_types[64]; // varies + Residue *residue_config; + int mapping_count; + Mapping *mapping; + int mode_count; + Mode mode_config[64]; // varies + + uint32 total_samples; + + // decode buffer + float *channel_buffers[STB_VORBIS_MAX_CHANNELS]; + float *outputs [STB_VORBIS_MAX_CHANNELS]; + + float *previous_window[STB_VORBIS_MAX_CHANNELS]; + int previous_length; + + #ifndef STB_VORBIS_NO_DEFER_FLOOR + int16 *finalY[STB_VORBIS_MAX_CHANNELS]; + #else + float *floor_buffers[STB_VORBIS_MAX_CHANNELS]; + #endif + + uint32 current_loc; // sample location of next frame to decode + int current_loc_valid; + + // per-blocksize precomputed data + + // twiddle factors + float *A[2],*B[2],*C[2]; + float *window[2]; + uint16 *bit_reverse[2]; + + // current page/packet/segment streaming info + uint32 serial; // stream serial number for verification + int last_page; + int segment_count; + uint8 segments[255]; + uint8 page_flag; + uint8 bytes_in_seg; + uint8 first_decode; + int next_seg; + int last_seg; // flag that we're on the last segment + int last_seg_which; // what was the segment number of the last seg? + uint32 acc; + int valid_bits; + int packet_bytes; + int end_seg_with_known_loc; + uint32 known_loc_for_packet; + int discard_samples_deferred; + uint32 samples_output; + + // push mode scanning + int page_crc_tests; // only in push_mode: number of tests active; -1 if not searching +#ifndef STB_VORBIS_NO_PUSHDATA_API + CRCscan scan[STB_VORBIS_PUSHDATA_CRC_COUNT]; +#endif + + // sample-access + int channel_buffer_start; + int channel_buffer_end; +}; + +#if defined(STB_VORBIS_NO_PUSHDATA_API) + #define IS_PUSH_MODE(f) FALSE +#elif defined(STB_VORBIS_NO_PULLDATA_API) + #define IS_PUSH_MODE(f) TRUE +#else + #define IS_PUSH_MODE(f) ((f)->push_mode) +#endif + +typedef struct stb_vorbis vorb; + +static int error(vorb *f, enum STBVorbisError e) +{ + f->error = e; + if (!f->eof && e != VORBIS_need_more_data) { + f->error=e; // breakpoint for debugging + } + return 0; +} + + +// these functions are used for allocating temporary memory +// while decoding. if you can afford the stack space, use +// alloca(); otherwise, provide a temp buffer and it will +// allocate out of those. + +#define array_size_required(count,size) (count*(sizeof(void *)+(size))) + +#define temp_alloc(f,size) (f->alloc.alloc_buffer ? setup_temp_malloc(f,size) : alloca(size)) +#define temp_free(f,p) (void)0 +#define temp_alloc_save(f) ((f)->temp_offset) +#define temp_alloc_restore(f,p) ((f)->temp_offset = (p)) + +#define temp_block_array(f,count,size) make_block_array(temp_alloc(f,array_size_required(count,size)), count, size) + +// given a sufficiently large block of memory, make an array of pointers to subblocks of it +static void *make_block_array(void *mem, int count, int size) +{ + int i; + void ** p = (void **) mem; + char *q = (char *) (p + count); + for (i=0; i < count; ++i) { + p[i] = q; + q += size; + } + return p; +} + +static void *setup_malloc(vorb *f, int sz) +{ + sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. + f->setup_memory_required += sz; + if (f->alloc.alloc_buffer) { + void *p = (char *) f->alloc.alloc_buffer + f->setup_offset; + if (f->setup_offset + sz > f->temp_offset) return NULL; + f->setup_offset += sz; + return p; + } + return sz ? malloc(sz) : NULL; +} + +static void setup_free(vorb *f, void *p) +{ + if (f->alloc.alloc_buffer) return; // do nothing; setup mem is a stack + free(p); +} + +static void *setup_temp_malloc(vorb *f, int sz) +{ + sz = (sz+7) & ~7; // round up to nearest 8 for alignment of future allocs. + if (f->alloc.alloc_buffer) { + if (f->temp_offset - sz < f->setup_offset) return NULL; + f->temp_offset -= sz; + return (char *) f->alloc.alloc_buffer + f->temp_offset; + } + return malloc(sz); +} + +static void setup_temp_free(vorb *f, void *p, int sz) +{ + if (f->alloc.alloc_buffer) { + f->temp_offset += (sz+7)&~7; + return; + } + free(p); +} + +#define CRC32_POLY 0x04c11db7 // from spec + +static uint32 crc_table[256]; +static void crc32_init(void) +{ + int i,j; + uint32 s; + for(i=0; i < 256; i++) { + for (s=(uint32) i << 24, j=0; j < 8; ++j) + s = (s << 1) ^ (s >= (1U<<31) ? CRC32_POLY : 0); + crc_table[i] = s; + } +} + +static __forceinline uint32 crc32_update(uint32 crc, uint8 byte) +{ + return (crc << 8) ^ crc_table[byte ^ (crc >> 24)]; +} + + +// used in setup, and for huffman that doesn't go fast path +static unsigned int bit_reverse(unsigned int n) +{ + n = ((n & 0xAAAAAAAA) >> 1) | ((n & 0x55555555) << 1); + n = ((n & 0xCCCCCCCC) >> 2) | ((n & 0x33333333) << 2); + n = ((n & 0xF0F0F0F0) >> 4) | ((n & 0x0F0F0F0F) << 4); + n = ((n & 0xFF00FF00) >> 8) | ((n & 0x00FF00FF) << 8); + return (n >> 16) | (n << 16); +} + +static float square(float x) +{ + return x*x; +} + +// this is a weird definition of log2() for which log2(1) = 1, log2(2) = 2, log2(4) = 3 +// as required by the specification. fast(?) implementation from stb.h +// @OPTIMIZE: called multiple times per-packet with "constants"; move to setup +static int ilog(int32 n) +{ + static signed char log2_4[16] = { 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 }; + + if (n < 0) return 0; // signed n returns 0 + + // 2 compares if n < 16, 3 compares otherwise (4 if signed or n > 1<<29) + if (n < (1 << 14)) + if (n < (1 << 4)) return 0 + log2_4[n ]; + else if (n < (1 << 9)) return 5 + log2_4[n >> 5]; + else return 10 + log2_4[n >> 10]; + else if (n < (1 << 24)) + if (n < (1 << 19)) return 15 + log2_4[n >> 15]; + else return 20 + log2_4[n >> 20]; + else if (n < (1 << 29)) return 25 + log2_4[n >> 25]; + else return 30 + log2_4[n >> 30]; +} + +#ifndef M_PI + #define M_PI 3.14159265358979323846264f // from CRC +#endif + +// code length assigned to a value with no huffman encoding +#define NO_CODE 255 + +/////////////////////// LEAF SETUP FUNCTIONS ////////////////////////// +// +// these functions are only called at setup, and only a few times +// per file + +static float float32_unpack(uint32 x) +{ + // from the specification + uint32 mantissa = x & 0x1fffff; + uint32 sign = x & 0x80000000; + uint32 exp = (x & 0x7fe00000) >> 21; + double res = sign ? -(double)mantissa : (double)mantissa; + return (float) ldexp((float)res, (int)exp-788); +} + + +// zlib & jpeg huffman tables assume that the output symbols +// can either be arbitrarily arranged, or have monotonically +// increasing frequencies--they rely on the lengths being sorted; +// this makes for a very simple generation algorithm. +// vorbis allows a huffman table with non-sorted lengths. This +// requires a more sophisticated construction, since symbols in +// order do not map to huffman codes "in order". +static void add_entry(Codebook *c, uint32 huff_code, int symbol, int count, int len, uint32 *values) +{ + if (!c->sparse) { + c->codewords [symbol] = huff_code; + } else { + c->codewords [count] = huff_code; + c->codeword_lengths[count] = len; + values [count] = symbol; + } +} + +static int compute_codewords(Codebook *c, uint8 *len, int n, uint32 *values) +{ + int i,k,m=0; + uint32 available[32]; + + memset(available, 0, sizeof(available)); + // find the first entry + for (k=0; k < n; ++k) if (len[k] < NO_CODE) break; + if (k == n) { assert(c->sorted_entries == 0); return TRUE; } + assert(len[k] < 32); // no error return required, code reading lens checks this + // add to the list + add_entry(c, 0, k, m++, len[k], values); + // add all available leaves + for (i=1; i <= len[k]; ++i) + available[i] = 1U << (32-i); + // note that the above code treats the first case specially, + // but it's really the same as the following code, so they + // could probably be combined (except the initial code is 0, + // and I use 0 in available[] to mean 'empty') + for (i=k+1; i < n; ++i) { + uint32 res; + int z = len[i], y; + if (z == NO_CODE) continue; + assert(z < 32); // no error return required, code reading lens checks this + // find lowest available leaf (should always be earliest, + // which is what the specification calls for) + // note that this property, and the fact we can never have + // more than one free leaf at a given level, isn't totally + // trivial to prove, but it seems true and the assert never + // fires, so! + while (z > 0 && !available[z]) --z; + if (z == 0) { return FALSE; } + res = available[z]; + available[z] = 0; + add_entry(c, bit_reverse(res), i, m++, len[i], values); + // propagate availability up the tree + if (z != len[i]) { + for (y=len[i]; y > z; --y) { + assert(available[y] == 0); + available[y] = res + (1 << (32-y)); + } + } + } + return TRUE; +} + +// accelerated huffman table allows fast O(1) match of all symbols +// of length <= STB_VORBIS_FAST_HUFFMAN_LENGTH +static void compute_accelerated_huffman(Codebook *c) +{ + int i, len; + for (i=0; i < FAST_HUFFMAN_TABLE_SIZE; ++i) + c->fast_huffman[i] = -1; + + len = c->sparse ? c->sorted_entries : c->entries; + #ifdef STB_VORBIS_FAST_HUFFMAN_SHORT + if (len > 32767) len = 32767; // largest possible value we can encode! + #endif + for (i=0; i < len; ++i) { + if (c->codeword_lengths[i] <= STB_VORBIS_FAST_HUFFMAN_LENGTH) { + uint32 z = c->sparse ? bit_reverse(c->sorted_codewords[i]) : c->codewords[i]; + // set table entries for all bit combinations in the higher bits + while (z < FAST_HUFFMAN_TABLE_SIZE) { + c->fast_huffman[z] = i; + z += 1 << c->codeword_lengths[i]; + } + } + } +} + +#ifdef _MSC_VER +#define STBV_CDECL __cdecl +#else +#define STBV_CDECL +#endif + +static int STBV_CDECL uint32_compare(const void *p, const void *q) +{ + uint32 x = * (uint32 *) p; + uint32 y = * (uint32 *) q; + return x < y ? -1 : x > y; +} + +static int include_in_sort(Codebook *c, uint8 len) +{ + if (c->sparse) { assert(len != NO_CODE); return TRUE; } + if (len == NO_CODE) return FALSE; + if (len > STB_VORBIS_FAST_HUFFMAN_LENGTH) return TRUE; + return FALSE; +} + +// if the fast table above doesn't work, we want to binary +// search them... need to reverse the bits +static void compute_sorted_huffman(Codebook *c, uint8 *lengths, uint32 *values) +{ + int i, len; + // build a list of all the entries + // OPTIMIZATION: don't include the short ones, since they'll be caught by FAST_HUFFMAN. + // this is kind of a frivolous optimization--I don't see any performance improvement, + // but it's like 4 extra lines of code, so. + if (!c->sparse) { + int k = 0; + for (i=0; i < c->entries; ++i) + if (include_in_sort(c, lengths[i])) + c->sorted_codewords[k++] = bit_reverse(c->codewords[i]); + assert(k == c->sorted_entries); + } else { + for (i=0; i < c->sorted_entries; ++i) + c->sorted_codewords[i] = bit_reverse(c->codewords[i]); + } + + qsort(c->sorted_codewords, c->sorted_entries, sizeof(c->sorted_codewords[0]), uint32_compare); + c->sorted_codewords[c->sorted_entries] = 0xffffffff; + + len = c->sparse ? c->sorted_entries : c->entries; + // now we need to indicate how they correspond; we could either + // #1: sort a different data structure that says who they correspond to + // #2: for each sorted entry, search the original list to find who corresponds + // #3: for each original entry, find the sorted entry + // #1 requires extra storage, #2 is slow, #3 can use binary search! + for (i=0; i < len; ++i) { + int huff_len = c->sparse ? lengths[values[i]] : lengths[i]; + if (include_in_sort(c,huff_len)) { + uint32 code = bit_reverse(c->codewords[i]); + int x=0, n=c->sorted_entries; + while (n > 1) { + // invariant: sc[x] <= code < sc[x+n] + int m = x + (n >> 1); + if (c->sorted_codewords[m] <= code) { + x = m; + n -= (n>>1); + } else { + n >>= 1; + } + } + assert(c->sorted_codewords[x] == code); + if (c->sparse) { + c->sorted_values[x] = values[i]; + c->codeword_lengths[x] = huff_len; + } else { + c->sorted_values[x] = i; + } + } + } +} + +// only run while parsing the header (3 times) +static int vorbis_validate(uint8 *data) +{ + static uint8 vorbis[6] = { 'v', 'o', 'r', 'b', 'i', 's' }; + return memcmp(data, vorbis, 6) == 0; +} + +// called from setup only, once per code book +// (formula implied by specification) +static int lookup1_values(int entries, int dim) +{ + int r = (int) floor(exp((float) log((float) entries) / dim)); + if ((int) floor(pow((float) r+1, dim)) <= entries) // (int) cast for MinGW warning; + ++r; // floor() to avoid _ftol() when non-CRT + if (pow((float) r+1, dim) <= entries) + return -1; + if ((int) floor(pow((float) r, dim)) > entries) + return -1; + return r; +} + +// called twice per file +static void compute_twiddle_factors(int n, float *A, float *B, float *C) +{ + int n4 = n >> 2, n8 = n >> 3; + int k,k2; + + for (k=k2=0; k < n4; ++k,k2+=2) { + A[k2 ] = (float) cos(4*k*M_PI/n); + A[k2+1] = (float) -sin(4*k*M_PI/n); + B[k2 ] = (float) cos((k2+1)*M_PI/n/2) * 0.5f; + B[k2+1] = (float) sin((k2+1)*M_PI/n/2) * 0.5f; + } + for (k=k2=0; k < n8; ++k,k2+=2) { + C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); + C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); + } +} + +static void compute_window(int n, float *window) +{ + int n2 = n >> 1, i; + for (i=0; i < n2; ++i) + window[i] = (float) sin(0.5 * M_PI * square((float) sin((i - 0 + 0.5) / n2 * 0.5 * M_PI))); +} + +static void compute_bitreverse(int n, uint16 *rev) +{ + int ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + int i, n8 = n >> 3; + for (i=0; i < n8; ++i) + rev[i] = (bit_reverse(i) >> (32-ld+3)) << 2; +} + +static int init_blocksize(vorb *f, int b, int n) +{ + int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3; + f->A[b] = (float *) setup_malloc(f, sizeof(float) * n2); + f->B[b] = (float *) setup_malloc(f, sizeof(float) * n2); + f->C[b] = (float *) setup_malloc(f, sizeof(float) * n4); + if (!f->A[b] || !f->B[b] || !f->C[b]) return error(f, VORBIS_outofmem); + compute_twiddle_factors(n, f->A[b], f->B[b], f->C[b]); + f->window[b] = (float *) setup_malloc(f, sizeof(float) * n2); + if (!f->window[b]) return error(f, VORBIS_outofmem); + compute_window(n, f->window[b]); + f->bit_reverse[b] = (uint16 *) setup_malloc(f, sizeof(uint16) * n8); + if (!f->bit_reverse[b]) return error(f, VORBIS_outofmem); + compute_bitreverse(n, f->bit_reverse[b]); + return TRUE; +} + +static void neighbors(uint16 *x, int n, int *plow, int *phigh) +{ + int low = -1; + int high = 65536; + int i; + for (i=0; i < n; ++i) { + if (x[i] > low && x[i] < x[n]) { *plow = i; low = x[i]; } + if (x[i] < high && x[i] > x[n]) { *phigh = i; high = x[i]; } + } +} + +// this has been repurposed so y is now the original index instead of y +typedef struct +{ + uint16 x,id; +} stbv__floor_ordering; + +static int STBV_CDECL point_compare(const void *p, const void *q) +{ + stbv__floor_ordering *a = (stbv__floor_ordering *) p; + stbv__floor_ordering *b = (stbv__floor_ordering *) q; + return a->x < b->x ? -1 : a->x > b->x; +} + +// +/////////////////////// END LEAF SETUP FUNCTIONS ////////////////////////// + + +#if defined(STB_VORBIS_NO_STDIO) + #define USE_MEMORY(z) TRUE +#else + #define USE_MEMORY(z) ((z)->stream) +#endif + +static uint8 get8(vorb *z) +{ + if (USE_MEMORY(z)) { + if (z->stream >= z->stream_end) { z->eof = TRUE; return 0; } + return *z->stream++; + } + + #ifndef STB_VORBIS_NO_STDIO + { + int c = fgetc(z->f); + if (c == EOF) { z->eof = TRUE; return 0; } + return c; + } + #endif +} + +static uint32 get32(vorb *f) +{ + uint32 x; + x = get8(f); + x += get8(f) << 8; + x += get8(f) << 16; + x += (uint32) get8(f) << 24; + return x; +} + +static int getn(vorb *z, uint8 *data, int n) +{ + if (USE_MEMORY(z)) { + if (z->stream+n > z->stream_end) { z->eof = 1; return 0; } + memcpy(data, z->stream, n); + z->stream += n; + return 1; + } + + #ifndef STB_VORBIS_NO_STDIO + if (fread(data, n, 1, z->f) == 1) + return 1; + else { + z->eof = 1; + return 0; + } + #endif +} + +static void skip(vorb *z, int n) +{ + if (USE_MEMORY(z)) { + z->stream += n; + if (z->stream >= z->stream_end) z->eof = 1; + return; + } + #ifndef STB_VORBIS_NO_STDIO + { + long x = ftell(z->f); + fseek(z->f, x+n, SEEK_SET); + } + #endif +} + +static int set_file_offset(stb_vorbis *f, unsigned int loc) +{ + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (f->push_mode) return 0; + #endif + f->eof = 0; + if (USE_MEMORY(f)) { + if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) { + f->stream = f->stream_end; + f->eof = 1; + return 0; + } else { + f->stream = f->stream_start + loc; + return 1; + } + } + #ifndef STB_VORBIS_NO_STDIO + if (loc + f->f_start < loc || loc >= 0x80000000) { + loc = 0x7fffffff; + f->eof = 1; + } else { + loc += f->f_start; + } + if (!fseek(f->f, loc, SEEK_SET)) + return 1; + f->eof = 1; + fseek(f->f, f->f_start, SEEK_END); + return 0; + #endif +} + + +static uint8 ogg_page_header[4] = { 0x4f, 0x67, 0x67, 0x53 }; + +static int capture_pattern(vorb *f) +{ + if (0x4f != get8(f)) return FALSE; + if (0x67 != get8(f)) return FALSE; + if (0x67 != get8(f)) return FALSE; + if (0x53 != get8(f)) return FALSE; + return TRUE; +} + +#define PAGEFLAG_continued_packet 1 +#define PAGEFLAG_first_page 2 +#define PAGEFLAG_last_page 4 + +static int start_page_no_capturepattern(vorb *f) +{ + uint32 loc0,loc1,n; + if (f->first_decode && !IS_PUSH_MODE(f)) { + f->p_first.page_start = stb_vorbis_get_file_offset(f) - 4; + } + // stream structure version + if (0 != get8(f)) return error(f, VORBIS_invalid_stream_structure_version); + // header flag + f->page_flag = get8(f); + // absolute granule position + loc0 = get32(f); + loc1 = get32(f); + // @TODO: validate loc0,loc1 as valid positions? + // stream serial number -- vorbis doesn't interleave, so discard + get32(f); + //if (f->serial != get32(f)) return error(f, VORBIS_incorrect_stream_serial_number); + // page sequence number + n = get32(f); + f->last_page = n; + // CRC32 + get32(f); + // page_segments + f->segment_count = get8(f); + if (!getn(f, f->segments, f->segment_count)) + return error(f, VORBIS_unexpected_eof); + // assume we _don't_ know any the sample position of any segments + f->end_seg_with_known_loc = -2; + if (loc0 != ~0U || loc1 != ~0U) { + int i; + // determine which packet is the last one that will complete + for (i=f->segment_count-1; i >= 0; --i) + if (f->segments[i] < 255) + break; + // 'i' is now the index of the _last_ segment of a packet that ends + if (i >= 0) { + f->end_seg_with_known_loc = i; + f->known_loc_for_packet = loc0; + } + } + if (f->first_decode) { + int i,len; + len = 0; + for (i=0; i < f->segment_count; ++i) + len += f->segments[i]; + len += 27 + f->segment_count; + f->p_first.page_end = f->p_first.page_start + len; + f->p_first.last_decoded_sample = loc0; + } + f->next_seg = 0; + return TRUE; +} + +static int start_page(vorb *f) +{ + if (!capture_pattern(f)) return error(f, VORBIS_missing_capture_pattern); + return start_page_no_capturepattern(f); +} + +static int start_packet(vorb *f) +{ + while (f->next_seg == -1) { + if (!start_page(f)) return FALSE; + if (f->page_flag & PAGEFLAG_continued_packet) + return error(f, VORBIS_continued_packet_flag_invalid); + } + f->last_seg = FALSE; + f->valid_bits = 0; + f->packet_bytes = 0; + f->bytes_in_seg = 0; + // f->next_seg is now valid + return TRUE; +} + +static int maybe_start_packet(vorb *f) +{ + if (f->next_seg == -1) { + int x = get8(f); + if (f->eof) return FALSE; // EOF at page boundary is not an error! + if (0x4f != x ) return error(f, VORBIS_missing_capture_pattern); + if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (0x67 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (0x53 != get8(f)) return error(f, VORBIS_missing_capture_pattern); + if (!start_page_no_capturepattern(f)) return FALSE; + if (f->page_flag & PAGEFLAG_continued_packet) { + // set up enough state that we can read this packet if we want, + // e.g. during recovery + f->last_seg = FALSE; + f->bytes_in_seg = 0; + return error(f, VORBIS_continued_packet_flag_invalid); + } + } + return start_packet(f); +} + +static int next_segment(vorb *f) +{ + int len; + if (f->last_seg) return 0; + if (f->next_seg == -1) { + f->last_seg_which = f->segment_count-1; // in case start_page fails + if (!start_page(f)) { f->last_seg = 1; return 0; } + if (!(f->page_flag & PAGEFLAG_continued_packet)) return error(f, VORBIS_continued_packet_flag_invalid); + } + len = f->segments[f->next_seg++]; + if (len < 255) { + f->last_seg = TRUE; + f->last_seg_which = f->next_seg-1; + } + if (f->next_seg >= f->segment_count) + f->next_seg = -1; + assert(f->bytes_in_seg == 0); + f->bytes_in_seg = len; + return len; +} + +#define EOP (-1) +#define INVALID_BITS (-1) + +static int get8_packet_raw(vorb *f) +{ + if (!f->bytes_in_seg) { // CLANG! + if (f->last_seg) return EOP; + else if (!next_segment(f)) return EOP; + } + assert(f->bytes_in_seg > 0); + --f->bytes_in_seg; + ++f->packet_bytes; + return get8(f); +} + +static int get8_packet(vorb *f) +{ + int x = get8_packet_raw(f); + f->valid_bits = 0; + return x; +} + +static int get32_packet(vorb *f) +{ + uint32 x; + x = get8_packet(f); + x += get8_packet(f) << 8; + x += get8_packet(f) << 16; + x += (uint32) get8_packet(f) << 24; + return x; +} + +static void flush_packet(vorb *f) +{ + while (get8_packet_raw(f) != EOP); +} + +// @OPTIMIZE: this is the secondary bit decoder, so it's probably not as important +// as the huffman decoder? +static uint32 get_bits(vorb *f, int n) +{ + uint32 z; + + if (f->valid_bits < 0) return 0; + if (f->valid_bits < n) { + if (n > 24) { + // the accumulator technique below would not work correctly in this case + z = get_bits(f, 24); + z += get_bits(f, n-24) << 24; + return z; + } + if (f->valid_bits == 0) f->acc = 0; + while (f->valid_bits < n) { + int z = get8_packet_raw(f); + if (z == EOP) { + f->valid_bits = INVALID_BITS; + return 0; + } + f->acc += z << f->valid_bits; + f->valid_bits += 8; + } + } + + assert(f->valid_bits >= n); + z = f->acc & ((1 << n)-1); + f->acc >>= n; + f->valid_bits -= n; + return z; +} + +// @OPTIMIZE: primary accumulator for huffman +// expand the buffer to as many bits as possible without reading off end of packet +// it might be nice to allow f->valid_bits and f->acc to be stored in registers, +// e.g. cache them locally and decode locally +static __forceinline void prep_huffman(vorb *f) +{ + if (f->valid_bits <= 24) { + if (f->valid_bits == 0) f->acc = 0; + do { + int z; + if (f->last_seg && !f->bytes_in_seg) return; + z = get8_packet_raw(f); + if (z == EOP) return; + f->acc += (unsigned) z << f->valid_bits; + f->valid_bits += 8; + } while (f->valid_bits <= 24); + } +} + +enum +{ + VORBIS_packet_id = 1, + VORBIS_packet_comment = 3, + VORBIS_packet_setup = 5 +}; + +static int codebook_decode_scalar_raw(vorb *f, Codebook *c) +{ + int i; + prep_huffman(f); + + if (c->codewords == NULL && c->sorted_codewords == NULL) + return -1; + + // cases to use binary search: sorted_codewords && !c->codewords + // sorted_codewords && c->entries > 8 + if (c->entries > 8 ? c->sorted_codewords!=NULL : !c->codewords) { + // binary search + uint32 code = bit_reverse(f->acc); + int x=0, n=c->sorted_entries, len; + + while (n > 1) { + // invariant: sc[x] <= code < sc[x+n] + int m = x + (n >> 1); + if (c->sorted_codewords[m] <= code) { + x = m; + n -= (n>>1); + } else { + n >>= 1; + } + } + // x is now the sorted index + if (!c->sparse) x = c->sorted_values[x]; + // x is now sorted index if sparse, or symbol otherwise + len = c->codeword_lengths[x]; + if (f->valid_bits >= len) { + f->acc >>= len; + f->valid_bits -= len; + return x; + } + + f->valid_bits = 0; + return -1; + } + + // if small, linear search + assert(!c->sparse); + for (i=0; i < c->entries; ++i) { + if (c->codeword_lengths[i] == NO_CODE) continue; + if (c->codewords[i] == (f->acc & ((1 << c->codeword_lengths[i])-1))) { + if (f->valid_bits >= c->codeword_lengths[i]) { + f->acc >>= c->codeword_lengths[i]; + f->valid_bits -= c->codeword_lengths[i]; + return i; + } + f->valid_bits = 0; + return -1; + } + } + + error(f, VORBIS_invalid_stream); + f->valid_bits = 0; + return -1; +} + +#ifndef STB_VORBIS_NO_INLINE_DECODE + +#define DECODE_RAW(var, f,c) \ + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) \ + prep_huffman(f); \ + var = f->acc & FAST_HUFFMAN_TABLE_MASK; \ + var = c->fast_huffman[var]; \ + if (var >= 0) { \ + int n = c->codeword_lengths[var]; \ + f->acc >>= n; \ + f->valid_bits -= n; \ + if (f->valid_bits < 0) { f->valid_bits = 0; var = -1; } \ + } else { \ + var = codebook_decode_scalar_raw(f,c); \ + } + +#else + +static int codebook_decode_scalar(vorb *f, Codebook *c) +{ + int i; + if (f->valid_bits < STB_VORBIS_FAST_HUFFMAN_LENGTH) + prep_huffman(f); + // fast huffman table lookup + i = f->acc & FAST_HUFFMAN_TABLE_MASK; + i = c->fast_huffman[i]; + if (i >= 0) { + f->acc >>= c->codeword_lengths[i]; + f->valid_bits -= c->codeword_lengths[i]; + if (f->valid_bits < 0) { f->valid_bits = 0; return -1; } + return i; + } + return codebook_decode_scalar_raw(f,c); +} + +#define DECODE_RAW(var,f,c) var = codebook_decode_scalar(f,c); + +#endif + +#define DECODE(var,f,c) \ + DECODE_RAW(var,f,c) \ + if (c->sparse) var = c->sorted_values[var]; + +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + #define DECODE_VQ(var,f,c) DECODE_RAW(var,f,c) +#else + #define DECODE_VQ(var,f,c) DECODE(var,f,c) +#endif + + + + + + +// CODEBOOK_ELEMENT_FAST is an optimization for the CODEBOOK_FLOATS case +// where we avoid one addition +#define CODEBOOK_ELEMENT(c,off) (c->multiplicands[off]) +#define CODEBOOK_ELEMENT_FAST(c,off) (c->multiplicands[off]) +#define CODEBOOK_ELEMENT_BASE(c) (0) + +static int codebook_decode_start(vorb *f, Codebook *c) +{ + int z = -1; + + // type 0 is only legal in a scalar context + if (c->lookup_type == 0) + error(f, VORBIS_invalid_stream); + else { + DECODE_VQ(z,f,c); + if (c->sparse) assert(z < c->sorted_entries); + if (z < 0) { // check for EOP + if (!f->bytes_in_seg) + if (f->last_seg) + return z; + error(f, VORBIS_invalid_stream); + } + } + return z; +} + +static int codebook_decode(vorb *f, Codebook *c, float *output, int len) +{ + int i,z = codebook_decode_start(f,c); + if (z < 0) return FALSE; + if (len > c->dimensions) len = c->dimensions; + +#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + float last = CODEBOOK_ELEMENT_BASE(c); + int div = 1; + for (i=0; i < len; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + output[i] += val; + if (c->sequence_p) last = val + c->minimum_value; + div *= c->lookup_values; + } + return TRUE; + } +#endif + + z *= c->dimensions; + if (c->sequence_p) { + float last = CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i] += val; + last = val + c->minimum_value; + } + } else { + float last = CODEBOOK_ELEMENT_BASE(c); + for (i=0; i < len; ++i) { + output[i] += CODEBOOK_ELEMENT_FAST(c,z+i) + last; + } + } + + return TRUE; +} + +static int codebook_decode_step(vorb *f, Codebook *c, float *output, int len, int step) +{ + int i,z = codebook_decode_start(f,c); + float last = CODEBOOK_ELEMENT_BASE(c); + if (z < 0) return FALSE; + if (len > c->dimensions) len = c->dimensions; + +#ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int div = 1; + for (i=0; i < len; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + output[i*step] += val; + if (c->sequence_p) last = val; + div *= c->lookup_values; + } + return TRUE; + } +#endif + + z *= c->dimensions; + for (i=0; i < len; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + output[i*step] += val; + if (c->sequence_p) last = val; + } + + return TRUE; +} + +static int codebook_decode_deinterleave_repeat(vorb *f, Codebook *c, float **outputs, int ch, int *c_inter_p, int *p_inter_p, int len, int total_decode) +{ + int c_inter = *c_inter_p; + int p_inter = *p_inter_p; + int i,z, effective = c->dimensions; + + // type 0 is only legal in a scalar context + if (c->lookup_type == 0) return error(f, VORBIS_invalid_stream); + + while (total_decode > 0) { + float last = CODEBOOK_ELEMENT_BASE(c); + DECODE_VQ(z,f,c); + #ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + assert(!c->sparse || z < c->sorted_entries); + #endif + if (z < 0) { + if (!f->bytes_in_seg) + if (f->last_seg) return FALSE; + return error(f, VORBIS_invalid_stream); + } + + // if this will take us off the end of the buffers, stop short! + // we check by computing the length of the virtual interleaved + // buffer (len*ch), our current offset within it (p_inter*ch)+(c_inter), + // and the length we'll be using (effective) + if (c_inter + p_inter*ch + effective > len * ch) { + effective = len*ch - (p_inter*ch - c_inter); + } + + #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int div = 1; + for (i=0; i < effective; ++i) { + int off = (z / div) % c->lookup_values; + float val = CODEBOOK_ELEMENT_FAST(c,off) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + if (c->sequence_p) last = val; + div *= c->lookup_values; + } + } else + #endif + { + z *= c->dimensions; + if (c->sequence_p) { + for (i=0; i < effective; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + last = val; + } + } else { + for (i=0; i < effective; ++i) { + float val = CODEBOOK_ELEMENT_FAST(c,z+i) + last; + if (outputs[c_inter]) + outputs[c_inter][p_inter] += val; + if (++c_inter == ch) { c_inter = 0; ++p_inter; } + } + } + } + + total_decode -= effective; + } + *c_inter_p = c_inter; + *p_inter_p = p_inter; + return TRUE; +} + +static int predict_point(int x, int x0, int x1, int y0, int y1) +{ + int dy = y1 - y0; + int adx = x1 - x0; + // @OPTIMIZE: force int division to round in the right direction... is this necessary on x86? + int err = abs(dy) * (x - x0); + int off = err / adx; + return dy < 0 ? y0 - off : y0 + off; +} + +// the following table is block-copied from the specification +static float inverse_db_table[256] = +{ + 1.0649863e-07f, 1.1341951e-07f, 1.2079015e-07f, 1.2863978e-07f, + 1.3699951e-07f, 1.4590251e-07f, 1.5538408e-07f, 1.6548181e-07f, + 1.7623575e-07f, 1.8768855e-07f, 1.9988561e-07f, 2.1287530e-07f, + 2.2670913e-07f, 2.4144197e-07f, 2.5713223e-07f, 2.7384213e-07f, + 2.9163793e-07f, 3.1059021e-07f, 3.3077411e-07f, 3.5226968e-07f, + 3.7516214e-07f, 3.9954229e-07f, 4.2550680e-07f, 4.5315863e-07f, + 4.8260743e-07f, 5.1396998e-07f, 5.4737065e-07f, 5.8294187e-07f, + 6.2082472e-07f, 6.6116941e-07f, 7.0413592e-07f, 7.4989464e-07f, + 7.9862701e-07f, 8.5052630e-07f, 9.0579828e-07f, 9.6466216e-07f, + 1.0273513e-06f, 1.0941144e-06f, 1.1652161e-06f, 1.2409384e-06f, + 1.3215816e-06f, 1.4074654e-06f, 1.4989305e-06f, 1.5963394e-06f, + 1.7000785e-06f, 1.8105592e-06f, 1.9282195e-06f, 2.0535261e-06f, + 2.1869758e-06f, 2.3290978e-06f, 2.4804557e-06f, 2.6416497e-06f, + 2.8133190e-06f, 2.9961443e-06f, 3.1908506e-06f, 3.3982101e-06f, + 3.6190449e-06f, 3.8542308e-06f, 4.1047004e-06f, 4.3714470e-06f, + 4.6555282e-06f, 4.9580707e-06f, 5.2802740e-06f, 5.6234160e-06f, + 5.9888572e-06f, 6.3780469e-06f, 6.7925283e-06f, 7.2339451e-06f, + 7.7040476e-06f, 8.2047000e-06f, 8.7378876e-06f, 9.3057248e-06f, + 9.9104632e-06f, 1.0554501e-05f, 1.1240392e-05f, 1.1970856e-05f, + 1.2748789e-05f, 1.3577278e-05f, 1.4459606e-05f, 1.5399272e-05f, + 1.6400004e-05f, 1.7465768e-05f, 1.8600792e-05f, 1.9809576e-05f, + 2.1096914e-05f, 2.2467911e-05f, 2.3928002e-05f, 2.5482978e-05f, + 2.7139006e-05f, 2.8902651e-05f, 3.0780908e-05f, 3.2781225e-05f, + 3.4911534e-05f, 3.7180282e-05f, 3.9596466e-05f, 4.2169667e-05f, + 4.4910090e-05f, 4.7828601e-05f, 5.0936773e-05f, 5.4246931e-05f, + 5.7772202e-05f, 6.1526565e-05f, 6.5524908e-05f, 6.9783085e-05f, + 7.4317983e-05f, 7.9147585e-05f, 8.4291040e-05f, 8.9768747e-05f, + 9.5602426e-05f, 0.00010181521f, 0.00010843174f, 0.00011547824f, + 0.00012298267f, 0.00013097477f, 0.00013948625f, 0.00014855085f, + 0.00015820453f, 0.00016848555f, 0.00017943469f, 0.00019109536f, + 0.00020351382f, 0.00021673929f, 0.00023082423f, 0.00024582449f, + 0.00026179955f, 0.00027881276f, 0.00029693158f, 0.00031622787f, + 0.00033677814f, 0.00035866388f, 0.00038197188f, 0.00040679456f, + 0.00043323036f, 0.00046138411f, 0.00049136745f, 0.00052329927f, + 0.00055730621f, 0.00059352311f, 0.00063209358f, 0.00067317058f, + 0.00071691700f, 0.00076350630f, 0.00081312324f, 0.00086596457f, + 0.00092223983f, 0.00098217216f, 0.0010459992f, 0.0011139742f, + 0.0011863665f, 0.0012634633f, 0.0013455702f, 0.0014330129f, + 0.0015261382f, 0.0016253153f, 0.0017309374f, 0.0018434235f, + 0.0019632195f, 0.0020908006f, 0.0022266726f, 0.0023713743f, + 0.0025254795f, 0.0026895994f, 0.0028643847f, 0.0030505286f, + 0.0032487691f, 0.0034598925f, 0.0036847358f, 0.0039241906f, + 0.0041792066f, 0.0044507950f, 0.0047400328f, 0.0050480668f, + 0.0053761186f, 0.0057254891f, 0.0060975636f, 0.0064938176f, + 0.0069158225f, 0.0073652516f, 0.0078438871f, 0.0083536271f, + 0.0088964928f, 0.009474637f, 0.010090352f, 0.010746080f, + 0.011444421f, 0.012188144f, 0.012980198f, 0.013823725f, + 0.014722068f, 0.015678791f, 0.016697687f, 0.017782797f, + 0.018938423f, 0.020169149f, 0.021479854f, 0.022875735f, + 0.024362330f, 0.025945531f, 0.027631618f, 0.029427276f, + 0.031339626f, 0.033376252f, 0.035545228f, 0.037855157f, + 0.040315199f, 0.042935108f, 0.045725273f, 0.048696758f, + 0.051861348f, 0.055231591f, 0.058820850f, 0.062643361f, + 0.066714279f, 0.071049749f, 0.075666962f, 0.080584227f, + 0.085821044f, 0.091398179f, 0.097337747f, 0.10366330f, + 0.11039993f, 0.11757434f, 0.12521498f, 0.13335215f, + 0.14201813f, 0.15124727f, 0.16107617f, 0.17154380f, + 0.18269168f, 0.19456402f, 0.20720788f, 0.22067342f, + 0.23501402f, 0.25028656f, 0.26655159f, 0.28387361f, + 0.30232132f, 0.32196786f, 0.34289114f, 0.36517414f, + 0.38890521f, 0.41417847f, 0.44109412f, 0.46975890f, + 0.50028648f, 0.53279791f, 0.56742212f, 0.60429640f, + 0.64356699f, 0.68538959f, 0.72993007f, 0.77736504f, + 0.82788260f, 0.88168307f, 0.9389798f, 1.0f +}; + + +// @OPTIMIZE: if you want to replace this bresenham line-drawing routine, +// note that you must produce bit-identical output to decode correctly; +// this specific sequence of operations is specified in the spec (it's +// drawing integer-quantized frequency-space lines that the encoder +// expects to be exactly the same) +// ... also, isn't the whole point of Bresenham's algorithm to NOT +// have to divide in the setup? sigh. +#ifndef STB_VORBIS_NO_DEFER_FLOOR +#define LINE_OP(a,b) a *= b +#else +#define LINE_OP(a,b) a = b +#endif + +#ifdef STB_VORBIS_DIVIDE_TABLE +#define DIVTAB_NUMER 32 +#define DIVTAB_DENOM 64 +int8 integer_divide_table[DIVTAB_NUMER][DIVTAB_DENOM]; // 2KB +#endif + +static __forceinline void draw_line(float *output, int x0, int y0, int x1, int y1, int n) +{ + int dy = y1 - y0; + int adx = x1 - x0; + int ady = abs(dy); + int base; + int x=x0,y=y0; + int err = 0; + int sy; + +#ifdef STB_VORBIS_DIVIDE_TABLE + if (adx < DIVTAB_DENOM && ady < DIVTAB_NUMER) { + if (dy < 0) { + base = -integer_divide_table[ady][adx]; + sy = base-1; + } else { + base = integer_divide_table[ady][adx]; + sy = base+1; + } + } else { + base = dy / adx; + if (dy < 0) + sy = base - 1; + else + sy = base+1; + } +#else + base = dy / adx; + if (dy < 0) + sy = base - 1; + else + sy = base+1; +#endif + ady -= abs(base) * adx; + if (x1 > n) x1 = n; + if (x < x1) { + LINE_OP(output[x], inverse_db_table[y&255]); + for (++x; x < x1; ++x) { + err += ady; + if (err >= adx) { + err -= adx; + y += sy; + } else + y += base; + LINE_OP(output[x], inverse_db_table[y&255]); + } + } +} + +static int residue_decode(vorb *f, Codebook *book, float *target, int offset, int n, int rtype) +{ + int k; + if (rtype == 0) { + int step = n / book->dimensions; + for (k=0; k < step; ++k) + if (!codebook_decode_step(f, book, target+offset+k, n-offset-k, step)) + return FALSE; + } else { + for (k=0; k < n; ) { + if (!codebook_decode(f, book, target+offset, n-k)) + return FALSE; + k += book->dimensions; + offset += book->dimensions; + } + } + return TRUE; +} + +// n is 1/2 of the blocksize -- +// specification: "Correct per-vector decode length is [n]/2" +static void decode_residue(vorb *f, float *residue_buffers[], int ch, int n, int rn, uint8 *do_not_decode) +{ + int i,j,pass; + Residue *r = f->residue_config + rn; + int rtype = f->residue_types[rn]; + int c = r->classbook; + int classwords = f->codebooks[c].dimensions; + unsigned int actual_size = rtype == 2 ? n*2 : n; + unsigned int limit_r_begin = (r->begin < actual_size ? r->begin : actual_size); + unsigned int limit_r_end = (r->end < actual_size ? r->end : actual_size); + int n_read = limit_r_end - limit_r_begin; + int part_read = n_read / r->part_size; + int temp_alloc_point = temp_alloc_save(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + uint8 ***part_classdata = (uint8 ***) temp_block_array(f,f->channels, part_read * sizeof(**part_classdata)); + #else + int **classifications = (int **) temp_block_array(f,f->channels, part_read * sizeof(**classifications)); + #endif + + CHECK(f); + + for (i=0; i < ch; ++i) + if (!do_not_decode[i]) + memset(residue_buffers[i], 0, sizeof(float) * n); + + if (rtype == 2 && ch != 1) { + for (j=0; j < ch; ++j) + if (!do_not_decode[j]) + break; + if (j == ch) + goto done; + + for (pass=0; pass < 8; ++pass) { + int pcount = 0, class_set = 0; + if (ch == 2) { + while (pcount < part_read) { + int z = r->begin + pcount*r->part_size; + int c_inter = (z & 1), p_inter = z>>1; + if (pass == 0) { + Codebook *c = f->codebooks+r->classbook; + int q; + DECODE(q,f,c); + if (q == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[0][class_set] = r->classdata[q]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[0][i+pcount] = q % r->classifications; + q /= r->classifications; + } + #endif + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + int z = r->begin + pcount*r->part_size; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[0][class_set][i]; + #else + int c = classifications[0][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + Codebook *book = f->codebooks + b; + #ifdef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + #else + // saves 1% + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + #endif + } else { + z += r->part_size; + c_inter = z & 1; + p_inter = z >> 1; + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } else if (ch > 2) { + while (pcount < part_read) { + int z = r->begin + pcount*r->part_size; + int c_inter = z % ch, p_inter = z/ch; + if (pass == 0) { + Codebook *c = f->codebooks+r->classbook; + int q; + DECODE(q,f,c); + if (q == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[0][class_set] = r->classdata[q]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[0][i+pcount] = q % r->classifications; + q /= r->classifications; + } + #endif + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + int z = r->begin + pcount*r->part_size; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[0][class_set][i]; + #else + int c = classifications[0][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + Codebook *book = f->codebooks + b; + if (!codebook_decode_deinterleave_repeat(f, book, residue_buffers, ch, &c_inter, &p_inter, n, r->part_size)) + goto done; + } else { + z += r->part_size; + c_inter = z % ch; + p_inter = z / ch; + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } + } + goto done; + } + CHECK(f); + + for (pass=0; pass < 8; ++pass) { + int pcount = 0, class_set=0; + while (pcount < part_read) { + if (pass == 0) { + for (j=0; j < ch; ++j) { + if (!do_not_decode[j]) { + Codebook *c = f->codebooks+r->classbook; + int temp; + DECODE(temp,f,c); + if (temp == EOP) goto done; + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + part_classdata[j][class_set] = r->classdata[temp]; + #else + for (i=classwords-1; i >= 0; --i) { + classifications[j][i+pcount] = temp % r->classifications; + temp /= r->classifications; + } + #endif + } + } + } + for (i=0; i < classwords && pcount < part_read; ++i, ++pcount) { + for (j=0; j < ch; ++j) { + if (!do_not_decode[j]) { + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + int c = part_classdata[j][class_set][i]; + #else + int c = classifications[j][pcount]; + #endif + int b = r->residue_books[c][pass]; + if (b >= 0) { + float *target = residue_buffers[j]; + int offset = r->begin + pcount * r->part_size; + int n = r->part_size; + Codebook *book = f->codebooks + b; + if (!residue_decode(f, book, target, offset, n, rtype)) + goto done; + } + } + } + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + ++class_set; + #endif + } + } + done: + CHECK(f); + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + temp_free(f,part_classdata); + #else + temp_free(f,classifications); + #endif + temp_alloc_restore(f,temp_alloc_point); +} + + +#if 0 +// slow way for debugging +void inverse_mdct_slow(float *buffer, int n) +{ + int i,j; + int n2 = n >> 1; + float *x = (float *) malloc(sizeof(*x) * n2); + memcpy(x, buffer, sizeof(*x) * n2); + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n2; ++j) + // formula from paper: + //acc += n/4.0f * x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); + // formula from wikipedia + //acc += 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); + // these are equivalent, except the formula from the paper inverts the multiplier! + // however, what actually works is NO MULTIPLIER!?! + //acc += 64 * 2.0f / n2 * x[j] * (float) cos(M_PI/n2 * (i + 0.5 + n2/2)*(j + 0.5)); + acc += x[j] * (float) cos(M_PI / 2 / n * (2 * i + 1 + n/2.0)*(2*j+1)); + buffer[i] = acc; + } + free(x); +} +#elif 0 +// same as above, but just barely able to run in real time on modern machines +void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) +{ + float mcos[16384]; + int i,j; + int n2 = n >> 1, nmask = (n << 2) -1; + float *x = (float *) malloc(sizeof(*x) * n2); + memcpy(x, buffer, sizeof(*x) * n2); + for (i=0; i < 4*n; ++i) + mcos[i] = (float) cos(M_PI / 2 * i / n); + + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n2; ++j) + acc += x[j] * mcos[(2 * i + 1 + n2)*(2*j+1) & nmask]; + buffer[i] = acc; + } + free(x); +} +#elif 0 +// transform to use a slow dct-iv; this is STILL basically trivial, +// but only requires half as many ops +void dct_iv_slow(float *buffer, int n) +{ + float mcos[16384]; + float x[2048]; + int i,j; + int n2 = n >> 1, nmask = (n << 3) - 1; + memcpy(x, buffer, sizeof(*x) * n); + for (i=0; i < 8*n; ++i) + mcos[i] = (float) cos(M_PI / 4 * i / n); + for (i=0; i < n; ++i) { + float acc = 0; + for (j=0; j < n; ++j) + acc += x[j] * mcos[((2 * i + 1)*(2*j+1)) & nmask]; + buffer[i] = acc; + } +} + +void inverse_mdct_slow(float *buffer, int n, vorb *f, int blocktype) +{ + int i, n4 = n >> 2, n2 = n >> 1, n3_4 = n - n4; + float temp[4096]; + + memcpy(temp, buffer, n2 * sizeof(float)); + dct_iv_slow(temp, n2); // returns -c'-d, a-b' + + for (i=0; i < n4 ; ++i) buffer[i] = temp[i+n4]; // a-b' + for ( ; i < n3_4; ++i) buffer[i] = -temp[n3_4 - i - 1]; // b-a', c+d' + for ( ; i < n ; ++i) buffer[i] = -temp[i - n3_4]; // c'+d +} +#endif + +#ifndef LIBVORBIS_MDCT +#define LIBVORBIS_MDCT 0 +#endif + +#if LIBVORBIS_MDCT +// directly call the vorbis MDCT using an interface documented +// by Jeff Roberts... useful for performance comparison +typedef struct +{ + int n; + int log2n; + + float *trig; + int *bitrev; + + float scale; +} mdct_lookup; + +extern void mdct_init(mdct_lookup *lookup, int n); +extern void mdct_clear(mdct_lookup *l); +extern void mdct_backward(mdct_lookup *init, float *in, float *out); + +mdct_lookup M1,M2; + +void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) +{ + mdct_lookup *M; + if (M1.n == n) M = &M1; + else if (M2.n == n) M = &M2; + else if (M1.n == 0) { mdct_init(&M1, n); M = &M1; } + else { + if (M2.n) __asm int 3; + mdct_init(&M2, n); + M = &M2; + } + + mdct_backward(M, buffer, buffer); +} +#endif + + +// the following were split out into separate functions while optimizing; +// they could be pushed back up but eh. __forceinline showed no change; +// they're probably already being inlined. +static void imdct_step3_iter0_loop(int n, float *e, int i_off, int k_off, float *A) +{ + float *ee0 = e + i_off; + float *ee2 = ee0 + k_off; + int i; + + assert((n & 3) == 0); + for (i=(n>>2); i > 0; --i) { + float k00_20, k01_21; + k00_20 = ee0[ 0] - ee2[ 0]; + k01_21 = ee0[-1] - ee2[-1]; + ee0[ 0] += ee2[ 0];//ee0[ 0] = ee0[ 0] + ee2[ 0]; + ee0[-1] += ee2[-1];//ee0[-1] = ee0[-1] + ee2[-1]; + ee2[ 0] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-1] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-2] - ee2[-2]; + k01_21 = ee0[-3] - ee2[-3]; + ee0[-2] += ee2[-2];//ee0[-2] = ee0[-2] + ee2[-2]; + ee0[-3] += ee2[-3];//ee0[-3] = ee0[-3] + ee2[-3]; + ee2[-2] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-3] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-4] - ee2[-4]; + k01_21 = ee0[-5] - ee2[-5]; + ee0[-4] += ee2[-4];//ee0[-4] = ee0[-4] + ee2[-4]; + ee0[-5] += ee2[-5];//ee0[-5] = ee0[-5] + ee2[-5]; + ee2[-4] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-5] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + + k00_20 = ee0[-6] - ee2[-6]; + k01_21 = ee0[-7] - ee2[-7]; + ee0[-6] += ee2[-6];//ee0[-6] = ee0[-6] + ee2[-6]; + ee0[-7] += ee2[-7];//ee0[-7] = ee0[-7] + ee2[-7]; + ee2[-6] = k00_20 * A[0] - k01_21 * A[1]; + ee2[-7] = k01_21 * A[0] + k00_20 * A[1]; + A += 8; + ee0 -= 8; + ee2 -= 8; + } +} + +static void imdct_step3_inner_r_loop(int lim, float *e, int d0, int k_off, float *A, int k1) +{ + int i; + float k00_20, k01_21; + + float *e0 = e + d0; + float *e2 = e0 + k_off; + + for (i=lim >> 2; i > 0; --i) { + k00_20 = e0[-0] - e2[-0]; + k01_21 = e0[-1] - e2[-1]; + e0[-0] += e2[-0];//e0[-0] = e0[-0] + e2[-0]; + e0[-1] += e2[-1];//e0[-1] = e0[-1] + e2[-1]; + e2[-0] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-1] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-2] - e2[-2]; + k01_21 = e0[-3] - e2[-3]; + e0[-2] += e2[-2];//e0[-2] = e0[-2] + e2[-2]; + e0[-3] += e2[-3];//e0[-3] = e0[-3] + e2[-3]; + e2[-2] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-3] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-4] - e2[-4]; + k01_21 = e0[-5] - e2[-5]; + e0[-4] += e2[-4];//e0[-4] = e0[-4] + e2[-4]; + e0[-5] += e2[-5];//e0[-5] = e0[-5] + e2[-5]; + e2[-4] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-5] = (k01_21)*A[0] + (k00_20) * A[1]; + + A += k1; + + k00_20 = e0[-6] - e2[-6]; + k01_21 = e0[-7] - e2[-7]; + e0[-6] += e2[-6];//e0[-6] = e0[-6] + e2[-6]; + e0[-7] += e2[-7];//e0[-7] = e0[-7] + e2[-7]; + e2[-6] = (k00_20)*A[0] - (k01_21) * A[1]; + e2[-7] = (k01_21)*A[0] + (k00_20) * A[1]; + + e0 -= 8; + e2 -= 8; + + A += k1; + } +} + +static void imdct_step3_inner_s_loop(int n, float *e, int i_off, int k_off, float *A, int a_off, int k0) +{ + int i; + float A0 = A[0]; + float A1 = A[0+1]; + float A2 = A[0+a_off]; + float A3 = A[0+a_off+1]; + float A4 = A[0+a_off*2+0]; + float A5 = A[0+a_off*2+1]; + float A6 = A[0+a_off*3+0]; + float A7 = A[0+a_off*3+1]; + + float k00,k11; + + float *ee0 = e +i_off; + float *ee2 = ee0+k_off; + + for (i=n; i > 0; --i) { + k00 = ee0[ 0] - ee2[ 0]; + k11 = ee0[-1] - ee2[-1]; + ee0[ 0] = ee0[ 0] + ee2[ 0]; + ee0[-1] = ee0[-1] + ee2[-1]; + ee2[ 0] = (k00) * A0 - (k11) * A1; + ee2[-1] = (k11) * A0 + (k00) * A1; + + k00 = ee0[-2] - ee2[-2]; + k11 = ee0[-3] - ee2[-3]; + ee0[-2] = ee0[-2] + ee2[-2]; + ee0[-3] = ee0[-3] + ee2[-3]; + ee2[-2] = (k00) * A2 - (k11) * A3; + ee2[-3] = (k11) * A2 + (k00) * A3; + + k00 = ee0[-4] - ee2[-4]; + k11 = ee0[-5] - ee2[-5]; + ee0[-4] = ee0[-4] + ee2[-4]; + ee0[-5] = ee0[-5] + ee2[-5]; + ee2[-4] = (k00) * A4 - (k11) * A5; + ee2[-5] = (k11) * A4 + (k00) * A5; + + k00 = ee0[-6] - ee2[-6]; + k11 = ee0[-7] - ee2[-7]; + ee0[-6] = ee0[-6] + ee2[-6]; + ee0[-7] = ee0[-7] + ee2[-7]; + ee2[-6] = (k00) * A6 - (k11) * A7; + ee2[-7] = (k11) * A6 + (k00) * A7; + + ee0 -= k0; + ee2 -= k0; + } +} + +static __forceinline void iter_54(float *z) +{ + float k00,k11,k22,k33; + float y0,y1,y2,y3; + + k00 = z[ 0] - z[-4]; + y0 = z[ 0] + z[-4]; + y2 = z[-2] + z[-6]; + k22 = z[-2] - z[-6]; + + z[-0] = y0 + y2; // z0 + z4 + z2 + z6 + z[-2] = y0 - y2; // z0 + z4 - z2 - z6 + + // done with y0,y2 + + k33 = z[-3] - z[-7]; + + z[-4] = k00 + k33; // z0 - z4 + z3 - z7 + z[-6] = k00 - k33; // z0 - z4 - z3 + z7 + + // done with k33 + + k11 = z[-1] - z[-5]; + y1 = z[-1] + z[-5]; + y3 = z[-3] + z[-7]; + + z[-1] = y1 + y3; // z1 + z5 + z3 + z7 + z[-3] = y1 - y3; // z1 + z5 - z3 - z7 + z[-5] = k11 - k22; // z1 - z5 + z2 - z6 + z[-7] = k11 + k22; // z1 - z5 - z2 + z6 +} + +static void imdct_step3_inner_s_loop_ld654(int n, float *e, int i_off, float *A, int base_n) +{ + int a_off = base_n >> 3; + float A2 = A[0+a_off]; + float *z = e + i_off; + float *base = z - 16 * n; + + while (z > base) { + float k00,k11; + float l00,l11; + + k00 = z[-0] - z[ -8]; + k11 = z[-1] - z[ -9]; + l00 = z[-2] - z[-10]; + l11 = z[-3] - z[-11]; + z[ -0] = z[-0] + z[ -8]; + z[ -1] = z[-1] + z[ -9]; + z[ -2] = z[-2] + z[-10]; + z[ -3] = z[-3] + z[-11]; + z[ -8] = k00; + z[ -9] = k11; + z[-10] = (l00+l11) * A2; + z[-11] = (l11-l00) * A2; + + k00 = z[ -4] - z[-12]; + k11 = z[ -5] - z[-13]; + l00 = z[ -6] - z[-14]; + l11 = z[ -7] - z[-15]; + z[ -4] = z[ -4] + z[-12]; + z[ -5] = z[ -5] + z[-13]; + z[ -6] = z[ -6] + z[-14]; + z[ -7] = z[ -7] + z[-15]; + z[-12] = k11; + z[-13] = -k00; + z[-14] = (l11-l00) * A2; + z[-15] = (l00+l11) * -A2; + + iter_54(z); + iter_54(z-8); + z -= 16; + } +} + +static void inverse_mdct(float *buffer, int n, vorb *f, int blocktype) +{ + int n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; + int ld; + // @OPTIMIZE: reduce register pressure by using fewer variables? + int save_point = temp_alloc_save(f); + float *buf2 = (float *) temp_alloc(f, n2 * sizeof(*buf2)); + float *u=NULL,*v=NULL; + // twiddle factors + float *A = f->A[blocktype]; + + // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" + // See notes about bugs in that paper in less-optimal implementation 'inverse_mdct_old' after this function. + + // kernel from paper + + + // merged: + // copy and reflect spectral data + // step 0 + + // note that it turns out that the items added together during + // this step are, in fact, being added to themselves (as reflected + // by step 0). inexplicable inefficiency! this became obvious + // once I combined the passes. + + // so there's a missing 'times 2' here (for adding X to itself). + // this propagates through linearly to the end, where the numbers + // are 1/2 too small, and need to be compensated for. + + { + float *d,*e, *AA, *e_stop; + d = &buf2[n2-2]; + AA = A; + e = &buffer[0]; + e_stop = &buffer[n2]; + while (e != e_stop) { + d[1] = (e[0] * AA[0] - e[2]*AA[1]); + d[0] = (e[0] * AA[1] + e[2]*AA[0]); + d -= 2; + AA += 2; + e += 4; + } + + e = &buffer[n2-3]; + while (d >= buf2) { + d[1] = (-e[2] * AA[0] - -e[0]*AA[1]); + d[0] = (-e[2] * AA[1] + -e[0]*AA[0]); + d -= 2; + AA += 2; + e -= 4; + } + } + + // now we use symbolic names for these, so that we can + // possibly swap their meaning as we change which operations + // are in place + + u = buffer; + v = buf2; + + // step 2 (paper output is w, now u) + // this could be in place, but the data ends up in the wrong + // place... _somebody_'s got to swap it, so this is nominated + { + float *AA = &A[n2-8]; + float *d0,*d1, *e0, *e1; + + e0 = &v[n4]; + e1 = &v[0]; + + d0 = &u[n4]; + d1 = &u[0]; + + while (AA >= A) { + float v40_20, v41_21; + + v41_21 = e0[1] - e1[1]; + v40_20 = e0[0] - e1[0]; + d0[1] = e0[1] + e1[1]; + d0[0] = e0[0] + e1[0]; + d1[1] = v41_21*AA[4] - v40_20*AA[5]; + d1[0] = v40_20*AA[4] + v41_21*AA[5]; + + v41_21 = e0[3] - e1[3]; + v40_20 = e0[2] - e1[2]; + d0[3] = e0[3] + e1[3]; + d0[2] = e0[2] + e1[2]; + d1[3] = v41_21*AA[0] - v40_20*AA[1]; + d1[2] = v40_20*AA[0] + v41_21*AA[1]; + + AA -= 8; + + d0 += 4; + d1 += 4; + e0 += 4; + e1 += 4; + } + } + + // step 3 + ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + + // optimized step 3: + + // the original step3 loop can be nested r inside s or s inside r; + // it's written originally as s inside r, but this is dumb when r + // iterates many times, and s few. So I have two copies of it and + // switch between them halfway. + + // this is iteration 0 of step 3 + imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*0, -(n >> 3), A); + imdct_step3_iter0_loop(n >> 4, u, n2-1-n4*1, -(n >> 3), A); + + // this is iteration 1 of step 3 + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*0, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*1, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*2, -(n >> 4), A, 16); + imdct_step3_inner_r_loop(n >> 5, u, n2-1 - n8*3, -(n >> 4), A, 16); + + l=2; + for (; l < (ld-3)>>1; ++l) { + int k0 = n >> (l+2), k0_2 = k0>>1; + int lim = 1 << (l+1); + int i; + for (i=0; i < lim; ++i) + imdct_step3_inner_r_loop(n >> (l+4), u, n2-1 - k0*i, -k0_2, A, 1 << (l+3)); + } + + for (; l < ld-6; ++l) { + int k0 = n >> (l+2), k1 = 1 << (l+3), k0_2 = k0>>1; + int rlim = n >> (l+6), r; + int lim = 1 << (l+1); + int i_off; + float *A0 = A; + i_off = n2-1; + for (r=rlim; r > 0; --r) { + imdct_step3_inner_s_loop(lim, u, i_off, -k0_2, A0, k1, k0); + A0 += k1*4; + i_off -= 8; + } + } + + // iterations with count: + // ld-6,-5,-4 all interleaved together + // the big win comes from getting rid of needless flops + // due to the constants on pass 5 & 4 being all 1 and 0; + // combining them to be simultaneous to improve cache made little difference + imdct_step3_inner_s_loop_ld654(n >> 5, u, n2-1, A, n); + + // output is u + + // step 4, 5, and 6 + // cannot be in-place because of step 5 + { + uint16 *bitrev = f->bit_reverse[blocktype]; + // weirdly, I'd have thought reading sequentially and writing + // erratically would have been better than vice-versa, but in + // fact that's not what my testing showed. (That is, with + // j = bitreverse(i), do you read i and write j, or read j and write i.) + + float *d0 = &v[n4-4]; + float *d1 = &v[n2-4]; + while (d0 >= v) { + int k4; + + k4 = bitrev[0]; + d1[3] = u[k4+0]; + d1[2] = u[k4+1]; + d0[3] = u[k4+2]; + d0[2] = u[k4+3]; + + k4 = bitrev[1]; + d1[1] = u[k4+0]; + d1[0] = u[k4+1]; + d0[1] = u[k4+2]; + d0[0] = u[k4+3]; + + d0 -= 4; + d1 -= 4; + bitrev += 2; + } + } + // (paper output is u, now v) + + + // data must be in buf2 + assert(v == buf2); + + // step 7 (paper output is v, now v) + // this is now in place + { + float *C = f->C[blocktype]; + float *d, *e; + + d = v; + e = v + n2 - 4; + + while (d < e) { + float a02,a11,b0,b1,b2,b3; + + a02 = d[0] - e[2]; + a11 = d[1] + e[3]; + + b0 = C[1]*a02 + C[0]*a11; + b1 = C[1]*a11 - C[0]*a02; + + b2 = d[0] + e[ 2]; + b3 = d[1] - e[ 3]; + + d[0] = b2 + b0; + d[1] = b3 + b1; + e[2] = b2 - b0; + e[3] = b1 - b3; + + a02 = d[2] - e[0]; + a11 = d[3] + e[1]; + + b0 = C[3]*a02 + C[2]*a11; + b1 = C[3]*a11 - C[2]*a02; + + b2 = d[2] + e[ 0]; + b3 = d[3] - e[ 1]; + + d[2] = b2 + b0; + d[3] = b3 + b1; + e[0] = b2 - b0; + e[1] = b1 - b3; + + C += 4; + d += 4; + e -= 4; + } + } + + // data must be in buf2 + + + // step 8+decode (paper output is X, now buffer) + // this generates pairs of data a la 8 and pushes them directly through + // the decode kernel (pushing rather than pulling) to avoid having + // to make another pass later + + // this cannot POSSIBLY be in place, so we refer to the buffers directly + + { + float *d0,*d1,*d2,*d3; + + float *B = f->B[blocktype] + n2 - 8; + float *e = buf2 + n2 - 8; + d0 = &buffer[0]; + d1 = &buffer[n2-4]; + d2 = &buffer[n2]; + d3 = &buffer[n-4]; + while (e >= v) { + float p0,p1,p2,p3; + + p3 = e[6]*B[7] - e[7]*B[6]; + p2 = -e[6]*B[6] - e[7]*B[7]; + + d0[0] = p3; + d1[3] = - p3; + d2[0] = p2; + d3[3] = p2; + + p1 = e[4]*B[5] - e[5]*B[4]; + p0 = -e[4]*B[4] - e[5]*B[5]; + + d0[1] = p1; + d1[2] = - p1; + d2[1] = p0; + d3[2] = p0; + + p3 = e[2]*B[3] - e[3]*B[2]; + p2 = -e[2]*B[2] - e[3]*B[3]; + + d0[2] = p3; + d1[1] = - p3; + d2[2] = p2; + d3[1] = p2; + + p1 = e[0]*B[1] - e[1]*B[0]; + p0 = -e[0]*B[0] - e[1]*B[1]; + + d0[3] = p1; + d1[0] = - p1; + d2[3] = p0; + d3[0] = p0; + + B -= 8; + e -= 8; + d0 += 4; + d2 += 4; + d1 -= 4; + d3 -= 4; + } + } + + temp_free(f,buf2); + temp_alloc_restore(f,save_point); +} + +#if 0 +// this is the original version of the above code, if you want to optimize it from scratch +void inverse_mdct_naive(float *buffer, int n) +{ + float s; + float A[1 << 12], B[1 << 12], C[1 << 11]; + int i,k,k2,k4, n2 = n >> 1, n4 = n >> 2, n8 = n >> 3, l; + int n3_4 = n - n4, ld; + // how can they claim this only uses N words?! + // oh, because they're only used sparsely, whoops + float u[1 << 13], X[1 << 13], v[1 << 13], w[1 << 13]; + // set up twiddle factors + + for (k=k2=0; k < n4; ++k,k2+=2) { + A[k2 ] = (float) cos(4*k*M_PI/n); + A[k2+1] = (float) -sin(4*k*M_PI/n); + B[k2 ] = (float) cos((k2+1)*M_PI/n/2); + B[k2+1] = (float) sin((k2+1)*M_PI/n/2); + } + for (k=k2=0; k < n8; ++k,k2+=2) { + C[k2 ] = (float) cos(2*(k2+1)*M_PI/n); + C[k2+1] = (float) -sin(2*(k2+1)*M_PI/n); + } + + // IMDCT algorithm from "The use of multirate filter banks for coding of high quality digital audio" + // Note there are bugs in that pseudocode, presumably due to them attempting + // to rename the arrays nicely rather than representing the way their actual + // implementation bounces buffers back and forth. As a result, even in the + // "some formulars corrected" version, a direct implementation fails. These + // are noted below as "paper bug". + + // copy and reflect spectral data + for (k=0; k < n2; ++k) u[k] = buffer[k]; + for ( ; k < n ; ++k) u[k] = -buffer[n - k - 1]; + // kernel from paper + // step 1 + for (k=k2=k4=0; k < n4; k+=1, k2+=2, k4+=4) { + v[n-k4-1] = (u[k4] - u[n-k4-1]) * A[k2] - (u[k4+2] - u[n-k4-3])*A[k2+1]; + v[n-k4-3] = (u[k4] - u[n-k4-1]) * A[k2+1] + (u[k4+2] - u[n-k4-3])*A[k2]; + } + // step 2 + for (k=k4=0; k < n8; k+=1, k4+=4) { + w[n2+3+k4] = v[n2+3+k4] + v[k4+3]; + w[n2+1+k4] = v[n2+1+k4] + v[k4+1]; + w[k4+3] = (v[n2+3+k4] - v[k4+3])*A[n2-4-k4] - (v[n2+1+k4]-v[k4+1])*A[n2-3-k4]; + w[k4+1] = (v[n2+1+k4] - v[k4+1])*A[n2-4-k4] + (v[n2+3+k4]-v[k4+3])*A[n2-3-k4]; + } + // step 3 + ld = ilog(n) - 1; // ilog is off-by-one from normal definitions + for (l=0; l < ld-3; ++l) { + int k0 = n >> (l+2), k1 = 1 << (l+3); + int rlim = n >> (l+4), r4, r; + int s2lim = 1 << (l+2), s2; + for (r=r4=0; r < rlim; r4+=4,++r) { + for (s2=0; s2 < s2lim; s2+=2) { + u[n-1-k0*s2-r4] = w[n-1-k0*s2-r4] + w[n-1-k0*(s2+1)-r4]; + u[n-3-k0*s2-r4] = w[n-3-k0*s2-r4] + w[n-3-k0*(s2+1)-r4]; + u[n-1-k0*(s2+1)-r4] = (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1] + - (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1+1]; + u[n-3-k0*(s2+1)-r4] = (w[n-3-k0*s2-r4] - w[n-3-k0*(s2+1)-r4]) * A[r*k1] + + (w[n-1-k0*s2-r4] - w[n-1-k0*(s2+1)-r4]) * A[r*k1+1]; + } + } + if (l+1 < ld-3) { + // paper bug: ping-ponging of u&w here is omitted + memcpy(w, u, sizeof(u)); + } + } + + // step 4 + for (i=0; i < n8; ++i) { + int j = bit_reverse(i) >> (32-ld+3); + assert(j < n8); + if (i == j) { + // paper bug: original code probably swapped in place; if copying, + // need to directly copy in this case + int i8 = i << 3; + v[i8+1] = u[i8+1]; + v[i8+3] = u[i8+3]; + v[i8+5] = u[i8+5]; + v[i8+7] = u[i8+7]; + } else if (i < j) { + int i8 = i << 3, j8 = j << 3; + v[j8+1] = u[i8+1], v[i8+1] = u[j8 + 1]; + v[j8+3] = u[i8+3], v[i8+3] = u[j8 + 3]; + v[j8+5] = u[i8+5], v[i8+5] = u[j8 + 5]; + v[j8+7] = u[i8+7], v[i8+7] = u[j8 + 7]; + } + } + // step 5 + for (k=0; k < n2; ++k) { + w[k] = v[k*2+1]; + } + // step 6 + for (k=k2=k4=0; k < n8; ++k, k2 += 2, k4 += 4) { + u[n-1-k2] = w[k4]; + u[n-2-k2] = w[k4+1]; + u[n3_4 - 1 - k2] = w[k4+2]; + u[n3_4 - 2 - k2] = w[k4+3]; + } + // step 7 + for (k=k2=0; k < n8; ++k, k2 += 2) { + v[n2 + k2 ] = ( u[n2 + k2] + u[n-2-k2] + C[k2+1]*(u[n2+k2]-u[n-2-k2]) + C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; + v[n-2 - k2] = ( u[n2 + k2] + u[n-2-k2] - C[k2+1]*(u[n2+k2]-u[n-2-k2]) - C[k2]*(u[n2+k2+1]+u[n-2-k2+1]))/2; + v[n2+1+ k2] = ( u[n2+1+k2] - u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; + v[n-1 - k2] = (-u[n2+1+k2] + u[n-1-k2] + C[k2+1]*(u[n2+1+k2]+u[n-1-k2]) - C[k2]*(u[n2+k2]-u[n-2-k2]))/2; + } + // step 8 + for (k=k2=0; k < n4; ++k,k2 += 2) { + X[k] = v[k2+n2]*B[k2 ] + v[k2+1+n2]*B[k2+1]; + X[n2-1-k] = v[k2+n2]*B[k2+1] - v[k2+1+n2]*B[k2 ]; + } + + // decode kernel to output + // determined the following value experimentally + // (by first figuring out what made inverse_mdct_slow work); then matching that here + // (probably vorbis encoder premultiplies by n or n/2, to save it on the decoder?) + s = 0.5; // theoretically would be n4 + + // [[[ note! the s value of 0.5 is compensated for by the B[] in the current code, + // so it needs to use the "old" B values to behave correctly, or else + // set s to 1.0 ]]] + for (i=0; i < n4 ; ++i) buffer[i] = s * X[i+n4]; + for ( ; i < n3_4; ++i) buffer[i] = -s * X[n3_4 - i - 1]; + for ( ; i < n ; ++i) buffer[i] = -s * X[i - n3_4]; +} +#endif + +static float *get_window(vorb *f, int len) +{ + len <<= 1; + if (len == f->blocksize_0) return f->window[0]; + if (len == f->blocksize_1) return f->window[1]; + return NULL; +} + +#ifndef STB_VORBIS_NO_DEFER_FLOOR +typedef int16 YTYPE; +#else +typedef int YTYPE; +#endif +static int do_floor(vorb *f, Mapping *map, int i, int n, float *target, YTYPE *finalY, uint8 *step2_flag) +{ + int n2 = n >> 1; + int s = map->chan[i].mux, floor; + floor = map->submap_floor[s]; + if (f->floor_types[floor] == 0) { + return error(f, VORBIS_invalid_stream); + } else { + Floor1 *g = &f->floor_config[floor].floor1; + int j,q; + int lx = 0, ly = finalY[0] * g->floor1_multiplier; + for (q=1; q < g->values; ++q) { + j = g->sorted_order[q]; + #ifndef STB_VORBIS_NO_DEFER_FLOOR + STBV_NOTUSED(step2_flag); + if (finalY[j] >= 0) + #else + if (step2_flag[j]) + #endif + { + int hy = finalY[j] * g->floor1_multiplier; + int hx = g->Xlist[j]; + if (lx != hx) + draw_line(target, lx,ly, hx,hy, n2); + CHECK(f); + lx = hx, ly = hy; + } + } + if (lx < n2) { + // optimization of: draw_line(target, lx,ly, n,ly, n2); + for (j=lx; j < n2; ++j) + LINE_OP(target[j], inverse_db_table[ly]); + CHECK(f); + } + } + return TRUE; +} + +// The meaning of "left" and "right" +// +// For a given frame: +// we compute samples from 0..n +// window_center is n/2 +// we'll window and mix the samples from left_start to left_end with data from the previous frame +// all of the samples from left_end to right_start can be output without mixing; however, +// this interval is 0-length except when transitioning between short and long frames +// all of the samples from right_start to right_end need to be mixed with the next frame, +// which we don't have, so those get saved in a buffer +// frame N's right_end-right_start, the number of samples to mix with the next frame, +// has to be the same as frame N+1's left_end-left_start (which they are by +// construction) + +static int vorbis_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) +{ + Mode *m; + int i, n, prev, next, window_center; + f->channel_buffer_start = f->channel_buffer_end = 0; + + retry: + if (f->eof) return FALSE; + if (!maybe_start_packet(f)) + return FALSE; + // check packet type + if (get_bits(f,1) != 0) { + if (IS_PUSH_MODE(f)) + return error(f,VORBIS_bad_packet_type); + while (EOP != get8_packet(f)); + goto retry; + } + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + + i = get_bits(f, ilog(f->mode_count-1)); + if (i == EOP) return FALSE; + if (i >= f->mode_count) return FALSE; + *mode = i; + m = f->mode_config + i; + if (m->blockflag) { + n = f->blocksize_1; + prev = get_bits(f,1); + next = get_bits(f,1); + } else { + prev = next = 0; + n = f->blocksize_0; + } + +// WINDOWING + + window_center = n >> 1; + if (m->blockflag && !prev) { + *p_left_start = (n - f->blocksize_0) >> 2; + *p_left_end = (n + f->blocksize_0) >> 2; + } else { + *p_left_start = 0; + *p_left_end = window_center; + } + if (m->blockflag && !next) { + *p_right_start = (n*3 - f->blocksize_0) >> 2; + *p_right_end = (n*3 + f->blocksize_0) >> 2; + } else { + *p_right_start = window_center; + *p_right_end = n; + } + + return TRUE; +} + +static int vorbis_decode_packet_rest(vorb *f, int *len, Mode *m, int left_start, int left_end, int right_start, int right_end, int *p_left) +{ + Mapping *map; + int i,j,k,n,n2; + int zero_channel[256]; + int really_zero_channel[256]; + +// WINDOWING + + STBV_NOTUSED(left_end); + n = f->blocksize[m->blockflag]; + map = &f->mapping[m->mapping]; + +// FLOORS + n2 = n >> 1; + + CHECK(f); + + for (i=0; i < f->channels; ++i) { + int s = map->chan[i].mux, floor; + zero_channel[i] = FALSE; + floor = map->submap_floor[s]; + if (f->floor_types[floor] == 0) { + return error(f, VORBIS_invalid_stream); + } else { + Floor1 *g = &f->floor_config[floor].floor1; + if (get_bits(f, 1)) { + short *finalY; + uint8 step2_flag[256]; + static int range_list[4] = { 256, 128, 86, 64 }; + int range = range_list[g->floor1_multiplier-1]; + int offset = 2; + finalY = f->finalY[i]; + finalY[0] = get_bits(f, ilog(range)-1); + finalY[1] = get_bits(f, ilog(range)-1); + for (j=0; j < g->partitions; ++j) { + int pclass = g->partition_class_list[j]; + int cdim = g->class_dimensions[pclass]; + int cbits = g->class_subclasses[pclass]; + int csub = (1 << cbits)-1; + int cval = 0; + if (cbits) { + Codebook *c = f->codebooks + g->class_masterbooks[pclass]; + DECODE(cval,f,c); + } + for (k=0; k < cdim; ++k) { + int book = g->subclass_books[pclass][cval & csub]; + cval = cval >> cbits; + if (book >= 0) { + int temp; + Codebook *c = f->codebooks + book; + DECODE(temp,f,c); + finalY[offset++] = temp; + } else + finalY[offset++] = 0; + } + } + if (f->valid_bits == INVALID_BITS) goto error; // behavior according to spec + step2_flag[0] = step2_flag[1] = 1; + for (j=2; j < g->values; ++j) { + int low, high, pred, highroom, lowroom, room, val; + low = g->neighbors[j][0]; + high = g->neighbors[j][1]; + //neighbors(g->Xlist, j, &low, &high); + pred = predict_point(g->Xlist[j], g->Xlist[low], g->Xlist[high], finalY[low], finalY[high]); + val = finalY[j]; + highroom = range - pred; + lowroom = pred; + if (highroom < lowroom) + room = highroom * 2; + else + room = lowroom * 2; + if (val) { + step2_flag[low] = step2_flag[high] = 1; + step2_flag[j] = 1; + if (val >= room) + if (highroom > lowroom) + finalY[j] = val - lowroom + pred; + else + finalY[j] = pred - val + highroom - 1; + else + if (val & 1) + finalY[j] = pred - ((val+1)>>1); + else + finalY[j] = pred + (val>>1); + } else { + step2_flag[j] = 0; + finalY[j] = pred; + } + } + +#ifdef STB_VORBIS_NO_DEFER_FLOOR + do_floor(f, map, i, n, f->floor_buffers[i], finalY, step2_flag); +#else + // defer final floor computation until _after_ residue + for (j=0; j < g->values; ++j) { + if (!step2_flag[j]) + finalY[j] = -1; + } +#endif + } else { + error: + zero_channel[i] = TRUE; + } + // So we just defer everything else to later + + // at this point we've decoded the floor into buffer + } + } + CHECK(f); + // at this point we've decoded all floors + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + + // re-enable coupled channels if necessary + memcpy(really_zero_channel, zero_channel, sizeof(really_zero_channel[0]) * f->channels); + for (i=0; i < map->coupling_steps; ++i) + if (!zero_channel[map->chan[i].magnitude] || !zero_channel[map->chan[i].angle]) { + zero_channel[map->chan[i].magnitude] = zero_channel[map->chan[i].angle] = FALSE; + } + + CHECK(f); +// RESIDUE DECODE + for (i=0; i < map->submaps; ++i) { + float *residue_buffers[STB_VORBIS_MAX_CHANNELS]; + int r; + uint8 do_not_decode[256]; + int ch = 0; + for (j=0; j < f->channels; ++j) { + if (map->chan[j].mux == i) { + if (zero_channel[j]) { + do_not_decode[ch] = TRUE; + residue_buffers[ch] = NULL; + } else { + do_not_decode[ch] = FALSE; + residue_buffers[ch] = f->channel_buffers[j]; + } + ++ch; + } + } + r = map->submap_residue[i]; + decode_residue(f, residue_buffers, ch, n2, r, do_not_decode); + } + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + CHECK(f); + +// INVERSE COUPLING + for (i = map->coupling_steps-1; i >= 0; --i) { + int n2 = n >> 1; + float *m = f->channel_buffers[map->chan[i].magnitude]; + float *a = f->channel_buffers[map->chan[i].angle ]; + for (j=0; j < n2; ++j) { + float a2,m2; + if (m[j] > 0) + if (a[j] > 0) + m2 = m[j], a2 = m[j] - a[j]; + else + a2 = m[j], m2 = m[j] + a[j]; + else + if (a[j] > 0) + m2 = m[j], a2 = m[j] + a[j]; + else + a2 = m[j], m2 = m[j] - a[j]; + m[j] = m2; + a[j] = a2; + } + } + CHECK(f); + + // finish decoding the floors +#ifndef STB_VORBIS_NO_DEFER_FLOOR + for (i=0; i < f->channels; ++i) { + if (really_zero_channel[i]) { + memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); + } else { + do_floor(f, map, i, n, f->channel_buffers[i], f->finalY[i], NULL); + } + } +#else + for (i=0; i < f->channels; ++i) { + if (really_zero_channel[i]) { + memset(f->channel_buffers[i], 0, sizeof(*f->channel_buffers[i]) * n2); + } else { + for (j=0; j < n2; ++j) + f->channel_buffers[i][j] *= f->floor_buffers[i][j]; + } + } +#endif + +// INVERSE MDCT + CHECK(f); + for (i=0; i < f->channels; ++i) + inverse_mdct(f->channel_buffers[i], n, f, m->blockflag); + CHECK(f); + + // this shouldn't be necessary, unless we exited on an error + // and want to flush to get to the next packet + flush_packet(f); + + if (f->first_decode) { + // assume we start so first non-discarded sample is sample 0 + // this isn't to spec, but spec would require us to read ahead + // and decode the size of all current frames--could be done, + // but presumably it's not a commonly used feature + f->current_loc = 0u - n2; // start of first frame is positioned for discard (NB this is an intentional unsigned overflow/wrap-around) + // we might have to discard samples "from" the next frame too, + // if we're lapping a large block then a small at the start? + f->discard_samples_deferred = n - right_end; + f->current_loc_valid = TRUE; + f->first_decode = FALSE; + } else if (f->discard_samples_deferred) { + if (f->discard_samples_deferred >= right_start - left_start) { + f->discard_samples_deferred -= (right_start - left_start); + left_start = right_start; + *p_left = left_start; + } else { + left_start += f->discard_samples_deferred; + *p_left = left_start; + f->discard_samples_deferred = 0; + } + } else if (f->previous_length == 0 && f->current_loc_valid) { + // we're recovering from a seek... that means we're going to discard + // the samples from this packet even though we know our position from + // the last page header, so we need to update the position based on + // the discarded samples here + // but wait, the code below is going to add this in itself even + // on a discard, so we don't need to do it here... + } + + // check if we have ogg information about the sample # for this packet + if (f->last_seg_which == f->end_seg_with_known_loc) { + // if we have a valid current loc, and this is final: + if (f->current_loc_valid && (f->page_flag & PAGEFLAG_last_page)) { + uint32 current_end = f->known_loc_for_packet; + // then let's infer the size of the (probably) short final frame + if (current_end < f->current_loc + (right_end-left_start)) { + if (current_end < f->current_loc) { + // negative truncation, that's impossible! + *len = 0; + } else { + *len = current_end - f->current_loc; + } + *len += left_start; // this doesn't seem right, but has no ill effect on my test files + if (*len > right_end) *len = right_end; // this should never happen + f->current_loc += *len; + return TRUE; + } + } + // otherwise, just set our sample loc + // guess that the ogg granule pos refers to the _middle_ of the + // last frame? + // set f->current_loc to the position of left_start + f->current_loc = f->known_loc_for_packet - (n2-left_start); + f->current_loc_valid = TRUE; + } + if (f->current_loc_valid) + f->current_loc += (right_start - left_start); + + if (f->alloc.alloc_buffer) + assert(f->alloc.alloc_buffer_length_in_bytes == f->temp_offset); + *len = right_end; // ignore samples after the window goes to 0 + CHECK(f); + + return TRUE; +} + +static int vorbis_decode_packet(vorb *f, int *len, int *p_left, int *p_right) +{ + int mode, left_end, right_end; + if (!vorbis_decode_initial(f, p_left, &left_end, p_right, &right_end, &mode)) return 0; + return vorbis_decode_packet_rest(f, len, f->mode_config + mode, *p_left, left_end, *p_right, right_end, p_left); +} + +static int vorbis_finish_frame(stb_vorbis *f, int len, int left, int right) +{ + int prev,i,j; + // we use right&left (the start of the right- and left-window sin()-regions) + // to determine how much to return, rather than inferring from the rules + // (same result, clearer code); 'left' indicates where our sin() window + // starts, therefore where the previous window's right edge starts, and + // therefore where to start mixing from the previous buffer. 'right' + // indicates where our sin() ending-window starts, therefore that's where + // we start saving, and where our returned-data ends. + + // mixin from previous window + if (f->previous_length) { + int i,j, n = f->previous_length; + float *w = get_window(f, n); + if (w == NULL) return 0; + for (i=0; i < f->channels; ++i) { + for (j=0; j < n; ++j) + f->channel_buffers[i][left+j] = + f->channel_buffers[i][left+j]*w[ j] + + f->previous_window[i][ j]*w[n-1-j]; + } + } + + prev = f->previous_length; + + // last half of this data becomes previous window + f->previous_length = len - right; + + // @OPTIMIZE: could avoid this copy by double-buffering the + // output (flipping previous_window with channel_buffers), but + // then previous_window would have to be 2x as large, and + // channel_buffers couldn't be temp mem (although they're NOT + // currently temp mem, they could be (unless we want to level + // performance by spreading out the computation)) + for (i=0; i < f->channels; ++i) + for (j=0; right+j < len; ++j) + f->previous_window[i][j] = f->channel_buffers[i][right+j]; + + if (!prev) + // there was no previous packet, so this data isn't valid... + // this isn't entirely true, only the would-have-overlapped data + // isn't valid, but this seems to be what the spec requires + return 0; + + // truncate a short frame + if (len < right) right = len; + + f->samples_output += right-left; + + return right - left; +} + +static int vorbis_pump_first_frame(stb_vorbis *f) +{ + int len, right, left, res; + res = vorbis_decode_packet(f, &len, &left, &right); + if (res) + vorbis_finish_frame(f, len, left, right); + return res; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API +static int is_whole_packet_present(stb_vorbis *f) +{ + // make sure that we have the packet available before continuing... + // this requires a full ogg parse, but we know we can fetch from f->stream + + // instead of coding this out explicitly, we could save the current read state, + // read the next packet with get8() until end-of-packet, check f->eof, then + // reset the state? but that would be slower, esp. since we'd have over 256 bytes + // of state to restore (primarily the page segment table) + + int s = f->next_seg, first = TRUE; + uint8 *p = f->stream; + + if (s != -1) { // if we're not starting the packet with a 'continue on next page' flag + for (; s < f->segment_count; ++s) { + p += f->segments[s]; + if (f->segments[s] < 255) // stop at first short segment + break; + } + // either this continues, or it ends it... + if (s == f->segment_count) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + first = FALSE; + } + for (; s == -1;) { + uint8 *q; + int n; + + // check that we have the page header ready + if (p + 26 >= f->stream_end) return error(f, VORBIS_need_more_data); + // validate the page + if (memcmp(p, ogg_page_header, 4)) return error(f, VORBIS_invalid_stream); + if (p[4] != 0) return error(f, VORBIS_invalid_stream); + if (first) { // the first segment must NOT have 'continued_packet', later ones MUST + if (f->previous_length) + if ((p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); + // if no previous length, we're resynching, so we can come in on a continued-packet, + // which we'll just drop + } else { + if (!(p[5] & PAGEFLAG_continued_packet)) return error(f, VORBIS_invalid_stream); + } + n = p[26]; // segment counts + q = p+27; // q points to segment table + p = q + n; // advance past header + // make sure we've read the segment table + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + for (s=0; s < n; ++s) { + p += q[s]; + if (q[s] < 255) + break; + } + if (s == n) + s = -1; // set 'crosses page' flag + if (p > f->stream_end) return error(f, VORBIS_need_more_data); + first = FALSE; + } + return TRUE; +} +#endif // !STB_VORBIS_NO_PUSHDATA_API + +static int start_decoder(vorb *f) +{ + uint8 header[6], x,y; + int len,i,j,k, max_submaps = 0; + int longest_floorlist=0; + + // first page, first packet + f->first_decode = TRUE; + + if (!start_page(f)) return FALSE; + // validate page flag + if (!(f->page_flag & PAGEFLAG_first_page)) return error(f, VORBIS_invalid_first_page); + if (f->page_flag & PAGEFLAG_last_page) return error(f, VORBIS_invalid_first_page); + if (f->page_flag & PAGEFLAG_continued_packet) return error(f, VORBIS_invalid_first_page); + // check for expected packet length + if (f->segment_count != 1) return error(f, VORBIS_invalid_first_page); + if (f->segments[0] != 30) { + // check for the Ogg skeleton fishead identifying header to refine our error + if (f->segments[0] == 64 && + getn(f, header, 6) && + header[0] == 'f' && + header[1] == 'i' && + header[2] == 's' && + header[3] == 'h' && + header[4] == 'e' && + header[5] == 'a' && + get8(f) == 'd' && + get8(f) == '\0') return error(f, VORBIS_ogg_skeleton_not_supported); + else + return error(f, VORBIS_invalid_first_page); + } + + // read packet + // check packet header + if (get8(f) != VORBIS_packet_id) return error(f, VORBIS_invalid_first_page); + if (!getn(f, header, 6)) return error(f, VORBIS_unexpected_eof); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_first_page); + // vorbis_version + if (get32(f) != 0) return error(f, VORBIS_invalid_first_page); + f->channels = get8(f); if (!f->channels) return error(f, VORBIS_invalid_first_page); + if (f->channels > STB_VORBIS_MAX_CHANNELS) return error(f, VORBIS_too_many_channels); + f->sample_rate = get32(f); if (!f->sample_rate) return error(f, VORBIS_invalid_first_page); + get32(f); // bitrate_maximum + get32(f); // bitrate_nominal + get32(f); // bitrate_minimum + x = get8(f); + { + int log0,log1; + log0 = x & 15; + log1 = x >> 4; + f->blocksize_0 = 1 << log0; + f->blocksize_1 = 1 << log1; + if (log0 < 6 || log0 > 13) return error(f, VORBIS_invalid_setup); + if (log1 < 6 || log1 > 13) return error(f, VORBIS_invalid_setup); + if (log0 > log1) return error(f, VORBIS_invalid_setup); + } + + // framing_flag + x = get8(f); + if (!(x & 1)) return error(f, VORBIS_invalid_first_page); + + // second packet! + if (!start_page(f)) return FALSE; + + if (!start_packet(f)) return FALSE; + + if (!next_segment(f)) return FALSE; + + if (get8_packet(f) != VORBIS_packet_comment) return error(f, VORBIS_invalid_setup); + for (i=0; i < 6; ++i) header[i] = get8_packet(f); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); + //file vendor + len = get32_packet(f); + f->vendor = (char*)setup_malloc(f, sizeof(char) * (len+1)); + if (f->vendor == NULL) return error(f, VORBIS_outofmem); + for(i=0; i < len; ++i) { + f->vendor[i] = get8_packet(f); + } + f->vendor[len] = (char)'\0'; + //user comments + f->comment_list_length = get32_packet(f); + f->comment_list = NULL; + if (f->comment_list_length > 0) + { + f->comment_list = (char**) setup_malloc(f, sizeof(char*) * (f->comment_list_length)); + if (f->comment_list == NULL) return error(f, VORBIS_outofmem); + } + + for(i=0; i < f->comment_list_length; ++i) { + len = get32_packet(f); + f->comment_list[i] = (char*)setup_malloc(f, sizeof(char) * (len+1)); + if (f->comment_list[i] == NULL) return error(f, VORBIS_outofmem); + + for(j=0; j < len; ++j) { + f->comment_list[i][j] = get8_packet(f); + } + f->comment_list[i][len] = (char)'\0'; + } + + // framing_flag + x = get8_packet(f); + if (!(x & 1)) return error(f, VORBIS_invalid_setup); + + + skip(f, f->bytes_in_seg); + f->bytes_in_seg = 0; + + do { + len = next_segment(f); + skip(f, len); + f->bytes_in_seg = 0; + } while (len); + + // third packet! + if (!start_packet(f)) return FALSE; + + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (IS_PUSH_MODE(f)) { + if (!is_whole_packet_present(f)) { + // convert error in ogg header to write type + if (f->error == VORBIS_invalid_stream) + f->error = VORBIS_invalid_setup; + return FALSE; + } + } + #endif + + crc32_init(); // always init it, to avoid multithread race conditions + + if (get8_packet(f) != VORBIS_packet_setup) return error(f, VORBIS_invalid_setup); + for (i=0; i < 6; ++i) header[i] = get8_packet(f); + if (!vorbis_validate(header)) return error(f, VORBIS_invalid_setup); + + // codebooks + + f->codebook_count = get_bits(f,8) + 1; + f->codebooks = (Codebook *) setup_malloc(f, sizeof(*f->codebooks) * f->codebook_count); + if (f->codebooks == NULL) return error(f, VORBIS_outofmem); + memset(f->codebooks, 0, sizeof(*f->codebooks) * f->codebook_count); + for (i=0; i < f->codebook_count; ++i) { + uint32 *values; + int ordered, sorted_count; + int total=0; + uint8 *lengths; + Codebook *c = f->codebooks+i; + CHECK(f); + x = get_bits(f, 8); if (x != 0x42) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); if (x != 0x43) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); if (x != 0x56) return error(f, VORBIS_invalid_setup); + x = get_bits(f, 8); + c->dimensions = (get_bits(f, 8)<<8) + x; + x = get_bits(f, 8); + y = get_bits(f, 8); + c->entries = (get_bits(f, 8)<<16) + (y<<8) + x; + ordered = get_bits(f,1); + c->sparse = ordered ? 0 : get_bits(f,1); + + if (c->dimensions == 0 && c->entries != 0) return error(f, VORBIS_invalid_setup); + + if (c->sparse) + lengths = (uint8 *) setup_temp_malloc(f, c->entries); + else + lengths = c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); + + if (!lengths) return error(f, VORBIS_outofmem); + + if (ordered) { + int current_entry = 0; + int current_length = get_bits(f,5) + 1; + while (current_entry < c->entries) { + int limit = c->entries - current_entry; + int n = get_bits(f, ilog(limit)); + if (current_length >= 32) return error(f, VORBIS_invalid_setup); + if (current_entry + n > (int) c->entries) { return error(f, VORBIS_invalid_setup); } + memset(lengths + current_entry, current_length, n); + current_entry += n; + ++current_length; + } + } else { + for (j=0; j < c->entries; ++j) { + int present = c->sparse ? get_bits(f,1) : 1; + if (present) { + lengths[j] = get_bits(f, 5) + 1; + ++total; + if (lengths[j] == 32) + return error(f, VORBIS_invalid_setup); + } else { + lengths[j] = NO_CODE; + } + } + } + + if (c->sparse && total >= c->entries >> 2) { + // convert sparse items to non-sparse! + if (c->entries > (int) f->setup_temp_memory_required) + f->setup_temp_memory_required = c->entries; + + c->codeword_lengths = (uint8 *) setup_malloc(f, c->entries); + if (c->codeword_lengths == NULL) return error(f, VORBIS_outofmem); + memcpy(c->codeword_lengths, lengths, c->entries); + setup_temp_free(f, lengths, c->entries); // note this is only safe if there have been no intervening temp mallocs! + lengths = c->codeword_lengths; + c->sparse = 0; + } + + // compute the size of the sorted tables + if (c->sparse) { + sorted_count = total; + } else { + sorted_count = 0; + #ifndef STB_VORBIS_NO_HUFFMAN_BINARY_SEARCH + for (j=0; j < c->entries; ++j) + if (lengths[j] > STB_VORBIS_FAST_HUFFMAN_LENGTH && lengths[j] != NO_CODE) + ++sorted_count; + #endif + } + + c->sorted_entries = sorted_count; + values = NULL; + + CHECK(f); + if (!c->sparse) { + c->codewords = (uint32 *) setup_malloc(f, sizeof(c->codewords[0]) * c->entries); + if (!c->codewords) return error(f, VORBIS_outofmem); + } else { + unsigned int size; + if (c->sorted_entries) { + c->codeword_lengths = (uint8 *) setup_malloc(f, c->sorted_entries); + if (!c->codeword_lengths) return error(f, VORBIS_outofmem); + c->codewords = (uint32 *) setup_temp_malloc(f, sizeof(*c->codewords) * c->sorted_entries); + if (!c->codewords) return error(f, VORBIS_outofmem); + values = (uint32 *) setup_temp_malloc(f, sizeof(*values) * c->sorted_entries); + if (!values) return error(f, VORBIS_outofmem); + } + size = c->entries + (sizeof(*c->codewords) + sizeof(*values)) * c->sorted_entries; + if (size > f->setup_temp_memory_required) + f->setup_temp_memory_required = size; + } + + if (!compute_codewords(c, lengths, c->entries, values)) { + if (c->sparse) setup_temp_free(f, values, 0); + return error(f, VORBIS_invalid_setup); + } + + if (c->sorted_entries) { + // allocate an extra slot for sentinels + c->sorted_codewords = (uint32 *) setup_malloc(f, sizeof(*c->sorted_codewords) * (c->sorted_entries+1)); + if (c->sorted_codewords == NULL) return error(f, VORBIS_outofmem); + // allocate an extra slot at the front so that c->sorted_values[-1] is defined + // so that we can catch that case without an extra if + c->sorted_values = ( int *) setup_malloc(f, sizeof(*c->sorted_values ) * (c->sorted_entries+1)); + if (c->sorted_values == NULL) return error(f, VORBIS_outofmem); + ++c->sorted_values; + c->sorted_values[-1] = -1; + compute_sorted_huffman(c, lengths, values); + } + + if (c->sparse) { + setup_temp_free(f, values, sizeof(*values)*c->sorted_entries); + setup_temp_free(f, c->codewords, sizeof(*c->codewords)*c->sorted_entries); + setup_temp_free(f, lengths, c->entries); + c->codewords = NULL; + } + + compute_accelerated_huffman(c); + + CHECK(f); + c->lookup_type = get_bits(f, 4); + if (c->lookup_type > 2) return error(f, VORBIS_invalid_setup); + if (c->lookup_type > 0) { + uint16 *mults; + c->minimum_value = float32_unpack(get_bits(f, 32)); + c->delta_value = float32_unpack(get_bits(f, 32)); + c->value_bits = get_bits(f, 4)+1; + c->sequence_p = get_bits(f,1); + if (c->lookup_type == 1) { + int values = lookup1_values(c->entries, c->dimensions); + if (values < 0) return error(f, VORBIS_invalid_setup); + c->lookup_values = (uint32) values; + } else { + c->lookup_values = c->entries * c->dimensions; + } + if (c->lookup_values == 0) return error(f, VORBIS_invalid_setup); + mults = (uint16 *) setup_temp_malloc(f, sizeof(mults[0]) * c->lookup_values); + if (mults == NULL) return error(f, VORBIS_outofmem); + for (j=0; j < (int) c->lookup_values; ++j) { + int q = get_bits(f, c->value_bits); + if (q == EOP) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_invalid_setup); } + mults[j] = q; + } + +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + if (c->lookup_type == 1) { + int len, sparse = c->sparse; + float last=0; + // pre-expand the lookup1-style multiplicands, to avoid a divide in the inner loop + if (sparse) { + if (c->sorted_entries == 0) goto skip; + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->sorted_entries * c->dimensions); + } else + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->entries * c->dimensions); + if (c->multiplicands == NULL) { setup_temp_free(f,mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + len = sparse ? c->sorted_entries : c->entries; + for (j=0; j < len; ++j) { + unsigned int z = sparse ? c->sorted_values[j] : j; + unsigned int div=1; + for (k=0; k < c->dimensions; ++k) { + int off = (z / div) % c->lookup_values; + float val = mults[off]*c->delta_value + c->minimum_value + last; + c->multiplicands[j*c->dimensions + k] = val; + if (c->sequence_p) + last = val; + if (k+1 < c->dimensions) { + if (div > UINT_MAX / (unsigned int) c->lookup_values) { + setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); + return error(f, VORBIS_invalid_setup); + } + div *= c->lookup_values; + } + } + } + c->lookup_type = 2; + } + else +#endif + { + float last=0; + CHECK(f); + c->multiplicands = (codetype *) setup_malloc(f, sizeof(c->multiplicands[0]) * c->lookup_values); + if (c->multiplicands == NULL) { setup_temp_free(f, mults,sizeof(mults[0])*c->lookup_values); return error(f, VORBIS_outofmem); } + for (j=0; j < (int) c->lookup_values; ++j) { + float val = mults[j] * c->delta_value + c->minimum_value + last; + c->multiplicands[j] = val; + if (c->sequence_p) + last = val; + } + } +#ifndef STB_VORBIS_DIVIDES_IN_CODEBOOK + skip:; +#endif + setup_temp_free(f, mults, sizeof(mults[0])*c->lookup_values); + + CHECK(f); + } + CHECK(f); + } + + // time domain transfers (notused) + + x = get_bits(f, 6) + 1; + for (i=0; i < x; ++i) { + uint32 z = get_bits(f, 16); + if (z != 0) return error(f, VORBIS_invalid_setup); + } + + // Floors + f->floor_count = get_bits(f, 6)+1; + f->floor_config = (Floor *) setup_malloc(f, f->floor_count * sizeof(*f->floor_config)); + if (f->floor_config == NULL) return error(f, VORBIS_outofmem); + for (i=0; i < f->floor_count; ++i) { + f->floor_types[i] = get_bits(f, 16); + if (f->floor_types[i] > 1) return error(f, VORBIS_invalid_setup); + if (f->floor_types[i] == 0) { + Floor0 *g = &f->floor_config[i].floor0; + g->order = get_bits(f,8); + g->rate = get_bits(f,16); + g->bark_map_size = get_bits(f,16); + g->amplitude_bits = get_bits(f,6); + g->amplitude_offset = get_bits(f,8); + g->number_of_books = get_bits(f,4) + 1; + for (j=0; j < g->number_of_books; ++j) + g->book_list[j] = get_bits(f,8); + return error(f, VORBIS_feature_not_supported); + } else { + stbv__floor_ordering p[31*8+2]; + Floor1 *g = &f->floor_config[i].floor1; + int max_class = -1; + g->partitions = get_bits(f, 5); + for (j=0; j < g->partitions; ++j) { + g->partition_class_list[j] = get_bits(f, 4); + if (g->partition_class_list[j] > max_class) + max_class = g->partition_class_list[j]; + } + for (j=0; j <= max_class; ++j) { + g->class_dimensions[j] = get_bits(f, 3)+1; + g->class_subclasses[j] = get_bits(f, 2); + if (g->class_subclasses[j]) { + g->class_masterbooks[j] = get_bits(f, 8); + if (g->class_masterbooks[j] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } + for (k=0; k < 1 << g->class_subclasses[j]; ++k) { + g->subclass_books[j][k] = (int16)get_bits(f,8)-1; + if (g->subclass_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } + } + g->floor1_multiplier = get_bits(f,2)+1; + g->rangebits = get_bits(f,4); + g->Xlist[0] = 0; + g->Xlist[1] = 1 << g->rangebits; + g->values = 2; + for (j=0; j < g->partitions; ++j) { + int c = g->partition_class_list[j]; + for (k=0; k < g->class_dimensions[c]; ++k) { + g->Xlist[g->values] = get_bits(f, g->rangebits); + ++g->values; + } + } + // precompute the sorting + for (j=0; j < g->values; ++j) { + p[j].x = g->Xlist[j]; + p[j].id = j; + } + qsort(p, g->values, sizeof(p[0]), point_compare); + for (j=0; j < g->values-1; ++j) + if (p[j].x == p[j+1].x) + return error(f, VORBIS_invalid_setup); + for (j=0; j < g->values; ++j) + g->sorted_order[j] = (uint8) p[j].id; + // precompute the neighbors + for (j=2; j < g->values; ++j) { + int low = 0,hi = 0; + neighbors(g->Xlist, j, &low,&hi); + g->neighbors[j][0] = low; + g->neighbors[j][1] = hi; + } + + if (g->values > longest_floorlist) + longest_floorlist = g->values; + } + } + + // Residue + f->residue_count = get_bits(f, 6)+1; + f->residue_config = (Residue *) setup_malloc(f, f->residue_count * sizeof(f->residue_config[0])); + if (f->residue_config == NULL) return error(f, VORBIS_outofmem); + memset(f->residue_config, 0, f->residue_count * sizeof(f->residue_config[0])); + for (i=0; i < f->residue_count; ++i) { + uint8 residue_cascade[64]; + Residue *r = f->residue_config+i; + f->residue_types[i] = get_bits(f, 16); + if (f->residue_types[i] > 2) return error(f, VORBIS_invalid_setup); + r->begin = get_bits(f, 24); + r->end = get_bits(f, 24); + if (r->end < r->begin) return error(f, VORBIS_invalid_setup); + r->part_size = get_bits(f,24)+1; + r->classifications = get_bits(f,6)+1; + r->classbook = get_bits(f,8); + if (r->classbook >= f->codebook_count) return error(f, VORBIS_invalid_setup); + for (j=0; j < r->classifications; ++j) { + uint8 high_bits=0; + uint8 low_bits=get_bits(f,3); + if (get_bits(f,1)) + high_bits = get_bits(f,5); + residue_cascade[j] = high_bits*8 + low_bits; + } + r->residue_books = (short (*)[8]) setup_malloc(f, sizeof(r->residue_books[0]) * r->classifications); + if (r->residue_books == NULL) return error(f, VORBIS_outofmem); + for (j=0; j < r->classifications; ++j) { + for (k=0; k < 8; ++k) { + if (residue_cascade[j] & (1 << k)) { + r->residue_books[j][k] = get_bits(f, 8); + if (r->residue_books[j][k] >= f->codebook_count) return error(f, VORBIS_invalid_setup); + } else { + r->residue_books[j][k] = -1; + } + } + } + // precompute the classifications[] array to avoid inner-loop mod/divide + // call it 'classdata' since we already have r->classifications + r->classdata = (uint8 **) setup_malloc(f, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); + if (!r->classdata) return error(f, VORBIS_outofmem); + memset(r->classdata, 0, sizeof(*r->classdata) * f->codebooks[r->classbook].entries); + for (j=0; j < f->codebooks[r->classbook].entries; ++j) { + int classwords = f->codebooks[r->classbook].dimensions; + int temp = j; + r->classdata[j] = (uint8 *) setup_malloc(f, sizeof(r->classdata[j][0]) * classwords); + if (r->classdata[j] == NULL) return error(f, VORBIS_outofmem); + for (k=classwords-1; k >= 0; --k) { + r->classdata[j][k] = temp % r->classifications; + temp /= r->classifications; + } + } + } + + f->mapping_count = get_bits(f,6)+1; + f->mapping = (Mapping *) setup_malloc(f, f->mapping_count * sizeof(*f->mapping)); + if (f->mapping == NULL) return error(f, VORBIS_outofmem); + memset(f->mapping, 0, f->mapping_count * sizeof(*f->mapping)); + for (i=0; i < f->mapping_count; ++i) { + Mapping *m = f->mapping + i; + int mapping_type = get_bits(f,16); + if (mapping_type != 0) return error(f, VORBIS_invalid_setup); + m->chan = (MappingChannel *) setup_malloc(f, f->channels * sizeof(*m->chan)); + if (m->chan == NULL) return error(f, VORBIS_outofmem); + if (get_bits(f,1)) + m->submaps = get_bits(f,4)+1; + else + m->submaps = 1; + if (m->submaps > max_submaps) + max_submaps = m->submaps; + if (get_bits(f,1)) { + m->coupling_steps = get_bits(f,8)+1; + if (m->coupling_steps > f->channels) return error(f, VORBIS_invalid_setup); + for (k=0; k < m->coupling_steps; ++k) { + m->chan[k].magnitude = get_bits(f, ilog(f->channels-1)); + m->chan[k].angle = get_bits(f, ilog(f->channels-1)); + if (m->chan[k].magnitude >= f->channels) return error(f, VORBIS_invalid_setup); + if (m->chan[k].angle >= f->channels) return error(f, VORBIS_invalid_setup); + if (m->chan[k].magnitude == m->chan[k].angle) return error(f, VORBIS_invalid_setup); + } + } else + m->coupling_steps = 0; + + // reserved field + if (get_bits(f,2)) return error(f, VORBIS_invalid_setup); + if (m->submaps > 1) { + for (j=0; j < f->channels; ++j) { + m->chan[j].mux = get_bits(f, 4); + if (m->chan[j].mux >= m->submaps) return error(f, VORBIS_invalid_setup); + } + } else + // @SPECIFICATION: this case is missing from the spec + for (j=0; j < f->channels; ++j) + m->chan[j].mux = 0; + + for (j=0; j < m->submaps; ++j) { + get_bits(f,8); // discard + m->submap_floor[j] = get_bits(f,8); + m->submap_residue[j] = get_bits(f,8); + if (m->submap_floor[j] >= f->floor_count) return error(f, VORBIS_invalid_setup); + if (m->submap_residue[j] >= f->residue_count) return error(f, VORBIS_invalid_setup); + } + } + + // Modes + f->mode_count = get_bits(f, 6)+1; + for (i=0; i < f->mode_count; ++i) { + Mode *m = f->mode_config+i; + m->blockflag = get_bits(f,1); + m->windowtype = get_bits(f,16); + m->transformtype = get_bits(f,16); + m->mapping = get_bits(f,8); + if (m->windowtype != 0) return error(f, VORBIS_invalid_setup); + if (m->transformtype != 0) return error(f, VORBIS_invalid_setup); + if (m->mapping >= f->mapping_count) return error(f, VORBIS_invalid_setup); + } + + flush_packet(f); + + f->previous_length = 0; + + for (i=0; i < f->channels; ++i) { + f->channel_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1); + f->previous_window[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); + f->finalY[i] = (int16 *) setup_malloc(f, sizeof(int16) * longest_floorlist); + if (f->channel_buffers[i] == NULL || f->previous_window[i] == NULL || f->finalY[i] == NULL) return error(f, VORBIS_outofmem); + memset(f->channel_buffers[i], 0, sizeof(float) * f->blocksize_1); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + f->floor_buffers[i] = (float *) setup_malloc(f, sizeof(float) * f->blocksize_1/2); + if (f->floor_buffers[i] == NULL) return error(f, VORBIS_outofmem); + #endif + } + + if (!init_blocksize(f, 0, f->blocksize_0)) return FALSE; + if (!init_blocksize(f, 1, f->blocksize_1)) return FALSE; + f->blocksize[0] = f->blocksize_0; + f->blocksize[1] = f->blocksize_1; + +#ifdef STB_VORBIS_DIVIDE_TABLE + if (integer_divide_table[1][1]==0) + for (i=0; i < DIVTAB_NUMER; ++i) + for (j=1; j < DIVTAB_DENOM; ++j) + integer_divide_table[i][j] = i / j; +#endif + + // compute how much temporary memory is needed + + // 1. + { + uint32 imdct_mem = (f->blocksize_1 * sizeof(float) >> 1); + uint32 classify_mem; + int i,max_part_read=0; + for (i=0; i < f->residue_count; ++i) { + Residue *r = f->residue_config + i; + unsigned int actual_size = f->blocksize_1 / 2; + unsigned int limit_r_begin = r->begin < actual_size ? r->begin : actual_size; + unsigned int limit_r_end = r->end < actual_size ? r->end : actual_size; + int n_read = limit_r_end - limit_r_begin; + int part_read = n_read / r->part_size; + if (part_read > max_part_read) + max_part_read = part_read; + } + #ifndef STB_VORBIS_DIVIDES_IN_RESIDUE + classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(uint8 *)); + #else + classify_mem = f->channels * (sizeof(void*) + max_part_read * sizeof(int *)); + #endif + + // maximum reasonable partition size is f->blocksize_1 + + f->temp_memory_required = classify_mem; + if (imdct_mem > f->temp_memory_required) + f->temp_memory_required = imdct_mem; + } + + + if (f->alloc.alloc_buffer) { + assert(f->temp_offset == f->alloc.alloc_buffer_length_in_bytes); + // check if there's enough temp memory so we don't error later + if (f->setup_offset + sizeof(*f) + f->temp_memory_required > (unsigned) f->temp_offset) + return error(f, VORBIS_outofmem); + } + + // @TODO: stb_vorbis_seek_start expects first_audio_page_offset to point to a page + // without PAGEFLAG_continued_packet, so this either points to the first page, or + // the page after the end of the headers. It might be cleaner to point to a page + // in the middle of the headers, when that's the page where the first audio packet + // starts, but we'd have to also correctly skip the end of any continued packet in + // stb_vorbis_seek_start. + if (f->next_seg == -1) { + f->first_audio_page_offset = stb_vorbis_get_file_offset(f); + } else { + f->first_audio_page_offset = 0; + } + + return TRUE; +} + +static void vorbis_deinit(stb_vorbis *p) +{ + int i,j; + + setup_free(p, p->vendor); + for (i=0; i < p->comment_list_length; ++i) { + setup_free(p, p->comment_list[i]); + } + setup_free(p, p->comment_list); + + if (p->residue_config) { + for (i=0; i < p->residue_count; ++i) { + Residue *r = p->residue_config+i; + if (r->classdata) { + for (j=0; j < p->codebooks[r->classbook].entries; ++j) + setup_free(p, r->classdata[j]); + setup_free(p, r->classdata); + } + setup_free(p, r->residue_books); + } + } + + if (p->codebooks) { + CHECK(p); + for (i=0; i < p->codebook_count; ++i) { + Codebook *c = p->codebooks + i; + setup_free(p, c->codeword_lengths); + setup_free(p, c->multiplicands); + setup_free(p, c->codewords); + setup_free(p, c->sorted_codewords); + // c->sorted_values[-1] is the first entry in the array + setup_free(p, c->sorted_values ? c->sorted_values-1 : NULL); + } + setup_free(p, p->codebooks); + } + setup_free(p, p->floor_config); + setup_free(p, p->residue_config); + if (p->mapping) { + for (i=0; i < p->mapping_count; ++i) + setup_free(p, p->mapping[i].chan); + setup_free(p, p->mapping); + } + CHECK(p); + for (i=0; i < p->channels && i < STB_VORBIS_MAX_CHANNELS; ++i) { + setup_free(p, p->channel_buffers[i]); + setup_free(p, p->previous_window[i]); + #ifdef STB_VORBIS_NO_DEFER_FLOOR + setup_free(p, p->floor_buffers[i]); + #endif + setup_free(p, p->finalY[i]); + } + for (i=0; i < 2; ++i) { + setup_free(p, p->A[i]); + setup_free(p, p->B[i]); + setup_free(p, p->C[i]); + setup_free(p, p->window[i]); + setup_free(p, p->bit_reverse[i]); + } + #ifndef STB_VORBIS_NO_STDIO + if (p->close_on_free) fclose(p->f); + #endif +} + +void stb_vorbis_close(stb_vorbis *p) +{ + if (p == NULL) return; + vorbis_deinit(p); + setup_free(p,p); +} + +static void vorbis_init(stb_vorbis *p, const stb_vorbis_alloc *z) +{ + memset(p, 0, sizeof(*p)); // NULL out all malloc'd pointers to start + if (z) { + p->alloc = *z; + p->alloc.alloc_buffer_length_in_bytes &= ~7; + p->temp_offset = p->alloc.alloc_buffer_length_in_bytes; + } + p->eof = 0; + p->error = VORBIS__no_error; + p->stream = NULL; + p->codebooks = NULL; + p->page_crc_tests = -1; + #ifndef STB_VORBIS_NO_STDIO + p->close_on_free = FALSE; + p->f = NULL; + #endif +} + +int stb_vorbis_get_sample_offset(stb_vorbis *f) +{ + if (f->current_loc_valid) + return f->current_loc; + else + return -1; +} + +stb_vorbis_info stb_vorbis_get_info(stb_vorbis *f) +{ + stb_vorbis_info d; + d.channels = f->channels; + d.sample_rate = f->sample_rate; + d.setup_memory_required = f->setup_memory_required; + d.setup_temp_memory_required = f->setup_temp_memory_required; + d.temp_memory_required = f->temp_memory_required; + d.max_frame_size = f->blocksize_1 >> 1; + return d; +} + +stb_vorbis_comment stb_vorbis_get_comment(stb_vorbis *f) +{ + stb_vorbis_comment d; + d.vendor = f->vendor; + d.comment_list_length = f->comment_list_length; + d.comment_list = f->comment_list; + return d; +} + +int stb_vorbis_get_error(stb_vorbis *f) +{ + int e = f->error; + f->error = VORBIS__no_error; + return e; +} + +static stb_vorbis * vorbis_alloc(stb_vorbis *f) +{ + stb_vorbis *p = (stb_vorbis *) setup_malloc(f, sizeof(*p)); + return p; +} + +#ifndef STB_VORBIS_NO_PUSHDATA_API + +void stb_vorbis_flush_pushdata(stb_vorbis *f) +{ + f->previous_length = 0; + f->page_crc_tests = 0; + f->discard_samples_deferred = 0; + f->current_loc_valid = FALSE; + f->first_decode = FALSE; + f->samples_output = 0; + f->channel_buffer_start = 0; + f->channel_buffer_end = 0; +} + +static int vorbis_search_for_page_pushdata(vorb *f, uint8 *data, int data_len) +{ + int i,n; + for (i=0; i < f->page_crc_tests; ++i) + f->scan[i].bytes_done = 0; + + // if we have room for more scans, search for them first, because + // they may cause us to stop early if their header is incomplete + if (f->page_crc_tests < STB_VORBIS_PUSHDATA_CRC_COUNT) { + if (data_len < 4) return 0; + data_len -= 3; // need to look for 4-byte sequence, so don't miss + // one that straddles a boundary + for (i=0; i < data_len; ++i) { + if (data[i] == 0x4f) { + if (0==memcmp(data+i, ogg_page_header, 4)) { + int j,len; + uint32 crc; + // make sure we have the whole page header + if (i+26 >= data_len || i+27+data[i+26] >= data_len) { + // only read up to this page start, so hopefully we'll + // have the whole page header start next time + data_len = i; + break; + } + // ok, we have it all; compute the length of the page + len = 27 + data[i+26]; + for (j=0; j < data[i+26]; ++j) + len += data[i+27+j]; + // scan everything up to the embedded crc (which we must 0) + crc = 0; + for (j=0; j < 22; ++j) + crc = crc32_update(crc, data[i+j]); + // now process 4 0-bytes + for ( ; j < 26; ++j) + crc = crc32_update(crc, 0); + // len is the total number of bytes we need to scan + n = f->page_crc_tests++; + f->scan[n].bytes_left = len-j; + f->scan[n].crc_so_far = crc; + f->scan[n].goal_crc = data[i+22] + (data[i+23] << 8) + (data[i+24]<<16) + (data[i+25]<<24); + // if the last frame on a page is continued to the next, then + // we can't recover the sample_loc immediately + if (data[i+27+data[i+26]-1] == 255) + f->scan[n].sample_loc = ~0; + else + f->scan[n].sample_loc = data[i+6] + (data[i+7] << 8) + (data[i+ 8]<<16) + (data[i+ 9]<<24); + f->scan[n].bytes_done = i+j; + if (f->page_crc_tests == STB_VORBIS_PUSHDATA_CRC_COUNT) + break; + // keep going if we still have room for more + } + } + } + } + + for (i=0; i < f->page_crc_tests;) { + uint32 crc; + int j; + int n = f->scan[i].bytes_done; + int m = f->scan[i].bytes_left; + if (m > data_len - n) m = data_len - n; + // m is the bytes to scan in the current chunk + crc = f->scan[i].crc_so_far; + for (j=0; j < m; ++j) + crc = crc32_update(crc, data[n+j]); + f->scan[i].bytes_left -= m; + f->scan[i].crc_so_far = crc; + if (f->scan[i].bytes_left == 0) { + // does it match? + if (f->scan[i].crc_so_far == f->scan[i].goal_crc) { + // Houston, we have page + data_len = n+m; // consumption amount is wherever that scan ended + f->page_crc_tests = -1; // drop out of page scan mode + f->previous_length = 0; // decode-but-don't-output one frame + f->next_seg = -1; // start a new page + f->current_loc = f->scan[i].sample_loc; // set the current sample location + // to the amount we'd have decoded had we decoded this page + f->current_loc_valid = f->current_loc != ~0U; + return data_len; + } + // delete entry + f->scan[i] = f->scan[--f->page_crc_tests]; + } else { + ++i; + } + } + + return data_len; +} + +// return value: number of bytes we used +int stb_vorbis_decode_frame_pushdata( + stb_vorbis *f, // the file we're decoding + const uint8 *data, int data_len, // the memory available for decoding + int *channels, // place to write number of float * buffers + float ***output, // place to write float ** array of float * buffers + int *samples // place to write number of output samples + ) +{ + int i; + int len,right,left; + + if (!IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + if (f->page_crc_tests >= 0) { + *samples = 0; + return vorbis_search_for_page_pushdata(f, (uint8 *) data, data_len); + } + + f->stream = (uint8 *) data; + f->stream_end = (uint8 *) data + data_len; + f->error = VORBIS__no_error; + + // check that we have the entire packet in memory + if (!is_whole_packet_present(f)) { + *samples = 0; + return 0; + } + + if (!vorbis_decode_packet(f, &len, &left, &right)) { + // save the actual error we encountered + enum STBVorbisError error = f->error; + if (error == VORBIS_bad_packet_type) { + // flush and resynch + f->error = VORBIS__no_error; + while (get8_packet(f) != EOP) + if (f->eof) break; + *samples = 0; + return (int) (f->stream - data); + } + if (error == VORBIS_continued_packet_flag_invalid) { + if (f->previous_length == 0) { + // we may be resynching, in which case it's ok to hit one + // of these; just discard the packet + f->error = VORBIS__no_error; + while (get8_packet(f) != EOP) + if (f->eof) break; + *samples = 0; + return (int) (f->stream - data); + } + } + // if we get an error while parsing, what to do? + // well, it DEFINITELY won't work to continue from where we are! + stb_vorbis_flush_pushdata(f); + // restore the error that actually made us bail + f->error = error; + *samples = 0; + return 1; + } + + // success! + len = vorbis_finish_frame(f, len, left, right); + for (i=0; i < f->channels; ++i) + f->outputs[i] = f->channel_buffers[i] + left; + + if (channels) *channels = f->channels; + *samples = len; + *output = f->outputs; + return (int) (f->stream - data); +} + +stb_vorbis *stb_vorbis_open_pushdata( + const unsigned char *data, int data_len, // the memory available for decoding + int *data_used, // only defined if result is not NULL + int *error, const stb_vorbis_alloc *alloc) +{ + stb_vorbis *f, p; + vorbis_init(&p, alloc); + p.stream = (uint8 *) data; + p.stream_end = (uint8 *) data + data_len; + p.push_mode = TRUE; + if (!start_decoder(&p)) { + if (p.eof) + *error = VORBIS_need_more_data; + else + *error = p.error; + vorbis_deinit(&p); + return NULL; + } + f = vorbis_alloc(&p); + if (f) { + *f = p; + *data_used = (int) (f->stream - data); + *error = 0; + return f; + } else { + vorbis_deinit(&p); + return NULL; + } +} +#endif // STB_VORBIS_NO_PUSHDATA_API + +unsigned int stb_vorbis_get_file_offset(stb_vorbis *f) +{ + #ifndef STB_VORBIS_NO_PUSHDATA_API + if (f->push_mode) return 0; + #endif + if (USE_MEMORY(f)) return (unsigned int) (f->stream - f->stream_start); + #ifndef STB_VORBIS_NO_STDIO + return (unsigned int) (ftell(f->f) - f->f_start); + #endif +} + +#ifndef STB_VORBIS_NO_PULLDATA_API +// +// DATA-PULLING API +// + +static uint32 vorbis_find_page(stb_vorbis *f, uint32 *end, uint32 *last) +{ + for(;;) { + int n; + if (f->eof) return 0; + n = get8(f); + if (n == 0x4f) { // page header candidate + unsigned int retry_loc = stb_vorbis_get_file_offset(f); + int i; + // check if we're off the end of a file_section stream + if (retry_loc - 25 > f->stream_len) + return 0; + // check the rest of the header + for (i=1; i < 4; ++i) + if (get8(f) != ogg_page_header[i]) + break; + if (f->eof) return 0; + if (i == 4) { + uint8 header[27]; + uint32 i, crc, goal, len; + for (i=0; i < 4; ++i) + header[i] = ogg_page_header[i]; + for (; i < 27; ++i) + header[i] = get8(f); + if (f->eof) return 0; + if (header[4] != 0) goto invalid; + goal = header[22] + (header[23] << 8) + (header[24]<<16) + ((uint32)header[25]<<24); + for (i=22; i < 26; ++i) + header[i] = 0; + crc = 0; + for (i=0; i < 27; ++i) + crc = crc32_update(crc, header[i]); + len = 0; + for (i=0; i < header[26]; ++i) { + int s = get8(f); + crc = crc32_update(crc, s); + len += s; + } + if (len && f->eof) return 0; + for (i=0; i < len; ++i) + crc = crc32_update(crc, get8(f)); + // finished parsing probable page + if (crc == goal) { + // we could now check that it's either got the last + // page flag set, OR it's followed by the capture + // pattern, but I guess TECHNICALLY you could have + // a file with garbage between each ogg page and recover + // from it automatically? So even though that paranoia + // might decrease the chance of an invalid decode by + // another 2^32, not worth it since it would hose those + // invalid-but-useful files? + if (end) + *end = stb_vorbis_get_file_offset(f); + if (last) { + if (header[5] & 0x04) + *last = 1; + else + *last = 0; + } + set_file_offset(f, retry_loc-1); + return 1; + } + } + invalid: + // not a valid page, so rewind and look for next one + set_file_offset(f, retry_loc); + } + } +} + + +#define SAMPLE_unknown 0xffffffff + +// seeking is implemented with a binary search, which narrows down the range to +// 64K, before using a linear search (because finding the synchronization +// pattern can be expensive, and the chance we'd find the end page again is +// relatively high for small ranges) +// +// two initial interpolation-style probes are used at the start of the search +// to try to bound either side of the binary search sensibly, while still +// working in O(log n) time if they fail. + +static int get_seek_page_info(stb_vorbis *f, ProbedPage *z) +{ + uint8 header[27], lacing[255]; + int i,len; + + // record where the page starts + z->page_start = stb_vorbis_get_file_offset(f); + + // parse the header + getn(f, header, 27); + if (header[0] != 'O' || header[1] != 'g' || header[2] != 'g' || header[3] != 'S') + return 0; + getn(f, lacing, header[26]); + + // determine the length of the payload + len = 0; + for (i=0; i < header[26]; ++i) + len += lacing[i]; + + // this implies where the page ends + z->page_end = z->page_start + 27 + header[26] + len; + + // read the last-decoded sample out of the data + z->last_decoded_sample = header[6] + (header[7] << 8) + (header[8] << 16) + (header[9] << 24); + + // restore file state to where we were + set_file_offset(f, z->page_start); + return 1; +} + +// rarely used function to seek back to the preceding page while finding the +// start of a packet +static int go_to_page_before(stb_vorbis *f, unsigned int limit_offset) +{ + unsigned int previous_safe, end; + + // now we want to seek back 64K from the limit + if (limit_offset >= 65536 && limit_offset-65536 >= f->first_audio_page_offset) + previous_safe = limit_offset - 65536; + else + previous_safe = f->first_audio_page_offset; + + set_file_offset(f, previous_safe); + + while (vorbis_find_page(f, &end, NULL)) { + if (end >= limit_offset && stb_vorbis_get_file_offset(f) < limit_offset) + return 1; + set_file_offset(f, end); + } + + return 0; +} + +// implements the search logic for finding a page and starting decoding. if +// the function succeeds, current_loc_valid will be true and current_loc will +// be less than or equal to the provided sample number (the closer the +// better). +static int seek_to_sample_coarse(stb_vorbis *f, uint32 sample_number) +{ + ProbedPage left, right, mid; + int i, start_seg_with_known_loc, end_pos, page_start; + uint32 delta, stream_length, padding, last_sample_limit; + double offset = 0.0, bytes_per_sample = 0.0; + int probe = 0; + + // find the last page and validate the target sample + stream_length = stb_vorbis_stream_length_in_samples(f); + if (stream_length == 0) return error(f, VORBIS_seek_without_length); + if (sample_number > stream_length) return error(f, VORBIS_seek_invalid); + + // this is the maximum difference between the window-center (which is the + // actual granule position value), and the right-start (which the spec + // indicates should be the granule position (give or take one)). + padding = ((f->blocksize_1 - f->blocksize_0) >> 2); + if (sample_number < padding) + last_sample_limit = 0; + else + last_sample_limit = sample_number - padding; + + left = f->p_first; + while (left.last_decoded_sample == ~0U) { + // (untested) the first page does not have a 'last_decoded_sample' + set_file_offset(f, left.page_end); + if (!get_seek_page_info(f, &left)) goto error; + } + + right = f->p_last; + assert(right.last_decoded_sample != ~0U); + + // starting from the start is handled differently + if (last_sample_limit <= left.last_decoded_sample) { + if (stb_vorbis_seek_start(f)) { + if (f->current_loc > sample_number) + return error(f, VORBIS_seek_failed); + return 1; + } + return 0; + } + + while (left.page_end != right.page_start) { + assert(left.page_end < right.page_start); + // search range in bytes + delta = right.page_start - left.page_end; + if (delta <= 65536) { + // there's only 64K left to search - handle it linearly + set_file_offset(f, left.page_end); + } else { + if (probe < 2) { + if (probe == 0) { + // first probe (interpolate) + double data_bytes = right.page_end - left.page_start; + bytes_per_sample = data_bytes / right.last_decoded_sample; + offset = left.page_start + bytes_per_sample * (last_sample_limit - left.last_decoded_sample); + } else { + // second probe (try to bound the other side) + double error = ((double) last_sample_limit - mid.last_decoded_sample) * bytes_per_sample; + if (error >= 0 && error < 8000) error = 8000; + if (error < 0 && error > -8000) error = -8000; + offset += error * 2; + } + + // ensure the offset is valid + if (offset < left.page_end) + offset = left.page_end; + if (offset > right.page_start - 65536) + offset = right.page_start - 65536; + + set_file_offset(f, (unsigned int) offset); + } else { + // binary search for large ranges (offset by 32K to ensure + // we don't hit the right page) + set_file_offset(f, left.page_end + (delta / 2) - 32768); + } + + if (!vorbis_find_page(f, NULL, NULL)) goto error; + } + + for (;;) { + if (!get_seek_page_info(f, &mid)) goto error; + if (mid.last_decoded_sample != ~0U) break; + // (untested) no frames end on this page + set_file_offset(f, mid.page_end); + assert(mid.page_start < right.page_start); + } + + // if we've just found the last page again then we're in a tricky file, + // and we're close enough (if it wasn't an interpolation probe). + if (mid.page_start == right.page_start) { + if (probe >= 2 || delta <= 65536) + break; + } else { + if (last_sample_limit < mid.last_decoded_sample) + right = mid; + else + left = mid; + } + + ++probe; + } + + // seek back to start of the last packet + page_start = left.page_start; + set_file_offset(f, page_start); + if (!start_page(f)) return error(f, VORBIS_seek_failed); + end_pos = f->end_seg_with_known_loc; + assert(end_pos >= 0); + + for (;;) { + for (i = end_pos; i > 0; --i) + if (f->segments[i-1] != 255) + break; + + start_seg_with_known_loc = i; + + if (start_seg_with_known_loc > 0 || !(f->page_flag & PAGEFLAG_continued_packet)) + break; + + // (untested) the final packet begins on an earlier page + if (!go_to_page_before(f, page_start)) + goto error; + + page_start = stb_vorbis_get_file_offset(f); + if (!start_page(f)) goto error; + end_pos = f->segment_count - 1; + } + + // prepare to start decoding + f->current_loc_valid = FALSE; + f->last_seg = FALSE; + f->valid_bits = 0; + f->packet_bytes = 0; + f->bytes_in_seg = 0; + f->previous_length = 0; + f->next_seg = start_seg_with_known_loc; + + for (i = 0; i < start_seg_with_known_loc; i++) + skip(f, f->segments[i]); + + // start decoding (optimizable - this frame is generally discarded) + if (!vorbis_pump_first_frame(f)) + return 0; + if (f->current_loc > sample_number) + return error(f, VORBIS_seek_failed); + return 1; + +error: + // try to restore the file to a valid state + stb_vorbis_seek_start(f); + return error(f, VORBIS_seek_failed); +} + +// the same as vorbis_decode_initial, but without advancing +static int peek_decode_initial(vorb *f, int *p_left_start, int *p_left_end, int *p_right_start, int *p_right_end, int *mode) +{ + int bits_read, bytes_read; + + if (!vorbis_decode_initial(f, p_left_start, p_left_end, p_right_start, p_right_end, mode)) + return 0; + + // either 1 or 2 bytes were read, figure out which so we can rewind + bits_read = 1 + ilog(f->mode_count-1); + if (f->mode_config[*mode].blockflag) + bits_read += 2; + bytes_read = (bits_read + 7) / 8; + + f->bytes_in_seg += bytes_read; + f->packet_bytes -= bytes_read; + skip(f, -bytes_read); + if (f->next_seg == -1) + f->next_seg = f->segment_count - 1; + else + f->next_seg--; + f->valid_bits = 0; + + return 1; +} + +int stb_vorbis_seek_frame(stb_vorbis *f, unsigned int sample_number) +{ + uint32 max_frame_samples; + + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + // fast page-level search + if (!seek_to_sample_coarse(f, sample_number)) + return 0; + + assert(f->current_loc_valid); + assert(f->current_loc <= sample_number); + + // linear search for the relevant packet + max_frame_samples = (f->blocksize_1*3 - f->blocksize_0) >> 2; + while (f->current_loc < sample_number) { + int left_start, left_end, right_start, right_end, mode, frame_samples; + if (!peek_decode_initial(f, &left_start, &left_end, &right_start, &right_end, &mode)) + return error(f, VORBIS_seek_failed); + // calculate the number of samples returned by the next frame + frame_samples = right_start - left_start; + if (f->current_loc + frame_samples > sample_number) { + return 1; // the next frame will contain the sample + } else if (f->current_loc + frame_samples + max_frame_samples > sample_number) { + // there's a chance the frame after this could contain the sample + vorbis_pump_first_frame(f); + } else { + // this frame is too early to be relevant + f->current_loc += frame_samples; + f->previous_length = 0; + maybe_start_packet(f); + flush_packet(f); + } + } + // the next frame should start with the sample + if (f->current_loc != sample_number) return error(f, VORBIS_seek_failed); + return 1; +} + +int stb_vorbis_seek(stb_vorbis *f, unsigned int sample_number) +{ + if (!stb_vorbis_seek_frame(f, sample_number)) + return 0; + + if (sample_number != f->current_loc) { + int n; + uint32 frame_start = f->current_loc; + stb_vorbis_get_frame_float(f, &n, NULL); + assert(sample_number > frame_start); + assert(f->channel_buffer_start + (int) (sample_number-frame_start) <= f->channel_buffer_end); + f->channel_buffer_start += (sample_number - frame_start); + } + + return 1; +} + +int stb_vorbis_seek_start(stb_vorbis *f) +{ + if (IS_PUSH_MODE(f)) { return error(f, VORBIS_invalid_api_mixing); } + set_file_offset(f, f->first_audio_page_offset); + f->previous_length = 0; + f->first_decode = TRUE; + f->next_seg = -1; + return vorbis_pump_first_frame(f); +} + +unsigned int stb_vorbis_stream_length_in_samples(stb_vorbis *f) +{ + unsigned int restore_offset, previous_safe; + unsigned int end, last_page_loc; + + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + if (!f->total_samples) { + unsigned int last; + uint32 lo,hi; + char header[6]; + + // first, store the current decode position so we can restore it + restore_offset = stb_vorbis_get_file_offset(f); + + // now we want to seek back 64K from the end (the last page must + // be at most a little less than 64K, but let's allow a little slop) + if (f->stream_len >= 65536 && f->stream_len-65536 >= f->first_audio_page_offset) + previous_safe = f->stream_len - 65536; + else + previous_safe = f->first_audio_page_offset; + + set_file_offset(f, previous_safe); + // previous_safe is now our candidate 'earliest known place that seeking + // to will lead to the final page' + + if (!vorbis_find_page(f, &end, &last)) { + // if we can't find a page, we're hosed! + f->error = VORBIS_cant_find_last_page; + f->total_samples = 0xffffffff; + goto done; + } + + // check if there are more pages + last_page_loc = stb_vorbis_get_file_offset(f); + + // stop when the last_page flag is set, not when we reach eof; + // this allows us to stop short of a 'file_section' end without + // explicitly checking the length of the section + while (!last) { + set_file_offset(f, end); + if (!vorbis_find_page(f, &end, &last)) { + // the last page we found didn't have the 'last page' flag + // set. whoops! + break; + } + //previous_safe = last_page_loc+1; // NOTE: not used after this point, but note for debugging + last_page_loc = stb_vorbis_get_file_offset(f); + } + + set_file_offset(f, last_page_loc); + + // parse the header + getn(f, (unsigned char *)header, 6); + // extract the absolute granule position + lo = get32(f); + hi = get32(f); + if (lo == 0xffffffff && hi == 0xffffffff) { + f->error = VORBIS_cant_find_last_page; + f->total_samples = SAMPLE_unknown; + goto done; + } + if (hi) + lo = 0xfffffffe; // saturate + f->total_samples = lo; + + f->p_last.page_start = last_page_loc; + f->p_last.page_end = end; + f->p_last.last_decoded_sample = lo; + + done: + set_file_offset(f, restore_offset); + } + return f->total_samples == SAMPLE_unknown ? 0 : f->total_samples; +} + +float stb_vorbis_stream_length_in_seconds(stb_vorbis *f) +{ + return stb_vorbis_stream_length_in_samples(f) / (float) f->sample_rate; +} + + + +int stb_vorbis_get_frame_float(stb_vorbis *f, int *channels, float ***output) +{ + int len, right,left,i; + if (IS_PUSH_MODE(f)) return error(f, VORBIS_invalid_api_mixing); + + if (!vorbis_decode_packet(f, &len, &left, &right)) { + f->channel_buffer_start = f->channel_buffer_end = 0; + return 0; + } + + len = vorbis_finish_frame(f, len, left, right); + for (i=0; i < f->channels; ++i) + f->outputs[i] = f->channel_buffers[i] + left; + + f->channel_buffer_start = left; + f->channel_buffer_end = left+len; + + if (channels) *channels = f->channels; + if (output) *output = f->outputs; + return len; +} + +#ifndef STB_VORBIS_NO_STDIO + +stb_vorbis * stb_vorbis_open_file_section(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc, unsigned int length) +{ + stb_vorbis *f, p; + vorbis_init(&p, alloc); + p.f = file; + p.f_start = (uint32) ftell(file); + p.stream_len = length; + p.close_on_free = close_on_free; + if (start_decoder(&p)) { + f = vorbis_alloc(&p); + if (f) { + *f = p; + vorbis_pump_first_frame(f); + return f; + } + } + if (error) *error = p.error; + vorbis_deinit(&p); + return NULL; +} + +stb_vorbis * stb_vorbis_open_file(FILE *file, int close_on_free, int *error, const stb_vorbis_alloc *alloc) +{ + unsigned int len, start; + start = (unsigned int) ftell(file); + fseek(file, 0, SEEK_END); + len = (unsigned int) (ftell(file) - start); + fseek(file, start, SEEK_SET); + return stb_vorbis_open_file_section(file, close_on_free, error, alloc, len); +} + +stb_vorbis * stb_vorbis_open_filename(const char *filename, int *error, const stb_vorbis_alloc *alloc) +{ + FILE *f; +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + if (0 != fopen_s(&f, filename, "rb")) + f = NULL; +#else + f = fopen(filename, "rb"); +#endif + if (f) + return stb_vorbis_open_file(f, TRUE, error, alloc); + if (error) *error = VORBIS_file_open_failure; + return NULL; +} +#endif // STB_VORBIS_NO_STDIO + +stb_vorbis * stb_vorbis_open_memory(const unsigned char *data, int len, int *error, const stb_vorbis_alloc *alloc) +{ + stb_vorbis *f, p; + if (!data) { + if (error) *error = VORBIS_unexpected_eof; + return NULL; + } + vorbis_init(&p, alloc); + p.stream = (uint8 *) data; + p.stream_end = (uint8 *) data + len; + p.stream_start = (uint8 *) p.stream; + p.stream_len = len; + p.push_mode = FALSE; + if (start_decoder(&p)) { + f = vorbis_alloc(&p); + if (f) { + *f = p; + vorbis_pump_first_frame(f); + if (error) *error = VORBIS__no_error; + return f; + } + } + if (error) *error = p.error; + vorbis_deinit(&p); + return NULL; +} + +#ifndef STB_VORBIS_NO_INTEGER_CONVERSION +#define PLAYBACK_MONO 1 +#define PLAYBACK_LEFT 2 +#define PLAYBACK_RIGHT 4 + +#define L (PLAYBACK_LEFT | PLAYBACK_MONO) +#define C (PLAYBACK_LEFT | PLAYBACK_RIGHT | PLAYBACK_MONO) +#define R (PLAYBACK_RIGHT | PLAYBACK_MONO) + +static int8 channel_position[7][6] = +{ + { 0 }, + { C }, + { L, R }, + { L, C, R }, + { L, R, L, R }, + { L, C, R, L, R }, + { L, C, R, L, R, C }, +}; + + +#ifndef STB_VORBIS_NO_FAST_SCALED_FLOAT + typedef union { + float f; + int i; + } float_conv; + typedef char stb_vorbis_float_size_test[sizeof(float)==4 && sizeof(int) == 4]; + #define FASTDEF(x) float_conv x + // add (1<<23) to convert to int, then divide by 2^SHIFT, then add 0.5/2^SHIFT to round + #define MAGIC(SHIFT) (1.5f * (1 << (23-SHIFT)) + 0.5f/(1 << SHIFT)) + #define ADDEND(SHIFT) (((150-SHIFT) << 23) + (1 << 22)) + #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) (temp.f = (x) + MAGIC(s), temp.i - ADDEND(s)) + #define check_endianness() +#else + #define FAST_SCALED_FLOAT_TO_INT(temp,x,s) ((int) ((x) * (1 << (s)))) + #define check_endianness() + #define FASTDEF(x) +#endif + +static void copy_samples(short *dest, float *src, int len) +{ + int i; + check_endianness(); + for (i=0; i < len; ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp, src[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + dest[i] = v; + } +} + +static void compute_samples(int mask, short *output, int num_c, float **data, int d_offset, int len) +{ + #define STB_BUFFER_SIZE 32 + float buffer[STB_BUFFER_SIZE]; + int i,j,o,n = STB_BUFFER_SIZE; + check_endianness(); + for (o = 0; o < len; o += STB_BUFFER_SIZE) { + memset(buffer, 0, sizeof(buffer)); + if (o + n > len) n = len - o; + for (j=0; j < num_c; ++j) { + if (channel_position[num_c][j] & mask) { + for (i=0; i < n; ++i) + buffer[i] += data[j][d_offset+o+i]; + } + } + for (i=0; i < n; ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + output[o+i] = v; + } + } + #undef STB_BUFFER_SIZE +} + +static void compute_stereo_samples(short *output, int num_c, float **data, int d_offset, int len) +{ + #define STB_BUFFER_SIZE 32 + float buffer[STB_BUFFER_SIZE]; + int i,j,o,n = STB_BUFFER_SIZE >> 1; + // o is the offset in the source data + check_endianness(); + for (o = 0; o < len; o += STB_BUFFER_SIZE >> 1) { + // o2 is the offset in the output data + int o2 = o << 1; + memset(buffer, 0, sizeof(buffer)); + if (o + n > len) n = len - o; + for (j=0; j < num_c; ++j) { + int m = channel_position[num_c][j] & (PLAYBACK_LEFT | PLAYBACK_RIGHT); + if (m == (PLAYBACK_LEFT | PLAYBACK_RIGHT)) { + for (i=0; i < n; ++i) { + buffer[i*2+0] += data[j][d_offset+o+i]; + buffer[i*2+1] += data[j][d_offset+o+i]; + } + } else if (m == PLAYBACK_LEFT) { + for (i=0; i < n; ++i) { + buffer[i*2+0] += data[j][d_offset+o+i]; + } + } else if (m == PLAYBACK_RIGHT) { + for (i=0; i < n; ++i) { + buffer[i*2+1] += data[j][d_offset+o+i]; + } + } + } + for (i=0; i < (n<<1); ++i) { + FASTDEF(temp); + int v = FAST_SCALED_FLOAT_TO_INT(temp,buffer[i],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + output[o2+i] = v; + } + } + #undef STB_BUFFER_SIZE +} + +static void convert_samples_short(int buf_c, short **buffer, int b_offset, int data_c, float **data, int d_offset, int samples) +{ + int i; + if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { + static int channel_selector[3][2] = { {0}, {PLAYBACK_MONO}, {PLAYBACK_LEFT, PLAYBACK_RIGHT} }; + for (i=0; i < buf_c; ++i) + compute_samples(channel_selector[buf_c][i], buffer[i]+b_offset, data_c, data, d_offset, samples); + } else { + int limit = buf_c < data_c ? buf_c : data_c; + for (i=0; i < limit; ++i) + copy_samples(buffer[i]+b_offset, data[i]+d_offset, samples); + for ( ; i < buf_c; ++i) + memset(buffer[i]+b_offset, 0, sizeof(short) * samples); + } +} + +int stb_vorbis_get_frame_short(stb_vorbis *f, int num_c, short **buffer, int num_samples) +{ + float **output = NULL; + int len = stb_vorbis_get_frame_float(f, NULL, &output); + if (len > num_samples) len = num_samples; + if (len) + convert_samples_short(num_c, buffer, 0, f->channels, output, 0, len); + return len; +} + +static void convert_channels_short_interleaved(int buf_c, short *buffer, int data_c, float **data, int d_offset, int len) +{ + int i; + check_endianness(); + if (buf_c != data_c && buf_c <= 2 && data_c <= 6) { + assert(buf_c == 2); + for (i=0; i < buf_c; ++i) + compute_stereo_samples(buffer, data_c, data, d_offset, len); + } else { + int limit = buf_c < data_c ? buf_c : data_c; + int j; + for (j=0; j < len; ++j) { + for (i=0; i < limit; ++i) { + FASTDEF(temp); + float f = data[i][d_offset+j]; + int v = FAST_SCALED_FLOAT_TO_INT(temp, f,15);//data[i][d_offset+j],15); + if ((unsigned int) (v + 32768) > 65535) + v = v < 0 ? -32768 : 32767; + *buffer++ = v; + } + for ( ; i < buf_c; ++i) + *buffer++ = 0; + } + } +} + +int stb_vorbis_get_frame_short_interleaved(stb_vorbis *f, int num_c, short *buffer, int num_shorts) +{ + float **output; + int len; + if (num_c == 1) return stb_vorbis_get_frame_short(f,num_c,&buffer, num_shorts); + len = stb_vorbis_get_frame_float(f, NULL, &output); + if (len) { + if (len*num_c > num_shorts) len = num_shorts / num_c; + convert_channels_short_interleaved(num_c, buffer, f->channels, output, 0, len); + } + return len; +} + +int stb_vorbis_get_samples_short_interleaved(stb_vorbis *f, int channels, short *buffer, int num_shorts) +{ + float **outputs; + int len = num_shorts / channels; + int n=0; + while (n < len) { + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + if (k) + convert_channels_short_interleaved(channels, buffer, f->channels, f->channel_buffers, f->channel_buffer_start, k); + buffer += k*channels; + n += k; + f->channel_buffer_start += k; + if (n == len) break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; + } + return n; +} + +int stb_vorbis_get_samples_short(stb_vorbis *f, int channels, short **buffer, int len) +{ + float **outputs; + int n=0; + while (n < len) { + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + if (k) + convert_samples_short(channels, buffer, n, f->channels, f->channel_buffers, f->channel_buffer_start, k); + n += k; + f->channel_buffer_start += k; + if (n == len) break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) break; + } + return n; +} + +#ifndef STB_VORBIS_NO_STDIO +int stb_vorbis_decode_filename(const char *filename, int *channels, int *sample_rate, short **output) +{ + int data_len, offset, total, limit, error; + short *data; + stb_vorbis *v = stb_vorbis_open_filename(filename, &error, NULL); + if (v == NULL) return -1; + limit = v->channels * 4096; + *channels = v->channels; + if (sample_rate) + *sample_rate = v->sample_rate; + offset = data_len = 0; + total = limit; + data = (short *) malloc(total * sizeof(*data)); + if (data == NULL) { + stb_vorbis_close(v); + return -2; + } + for (;;) { + int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); + if (n == 0) break; + data_len += n; + offset += n * v->channels; + if (offset + limit > total) { + short *data2; + total *= 2; + data2 = (short *) realloc(data, total * sizeof(*data)); + if (data2 == NULL) { + free(data); + stb_vorbis_close(v); + return -2; + } + data = data2; + } + } + *output = data; + stb_vorbis_close(v); + return data_len; +} +#endif // NO_STDIO + +int stb_vorbis_decode_memory(const uint8 *mem, int len, int *channels, int *sample_rate, short **output) +{ + int data_len, offset, total, limit, error; + short *data; + stb_vorbis *v = stb_vorbis_open_memory(mem, len, &error, NULL); + if (v == NULL) return -1; + limit = v->channels * 4096; + *channels = v->channels; + if (sample_rate) + *sample_rate = v->sample_rate; + offset = data_len = 0; + total = limit; + data = (short *) malloc(total * sizeof(*data)); + if (data == NULL) { + stb_vorbis_close(v); + return -2; + } + for (;;) { + int n = stb_vorbis_get_frame_short_interleaved(v, v->channels, data+offset, total-offset); + if (n == 0) break; + data_len += n; + offset += n * v->channels; + if (offset + limit > total) { + short *data2; + total *= 2; + data2 = (short *) realloc(data, total * sizeof(*data)); + if (data2 == NULL) { + free(data); + stb_vorbis_close(v); + return -2; + } + data = data2; + } + } + *output = data; + stb_vorbis_close(v); + return data_len; +} +#endif // STB_VORBIS_NO_INTEGER_CONVERSION + +int stb_vorbis_get_samples_float_interleaved(stb_vorbis *f, int channels, float *buffer, int num_floats) +{ + float **outputs; + int len = num_floats / channels; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < len) { + int i,j; + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= len) k = len - n; + for (j=0; j < k; ++j) { + for (i=0; i < z; ++i) + *buffer++ = f->channel_buffers[i][f->channel_buffer_start+j]; + for ( ; i < channels; ++i) + *buffer++ = 0; + } + n += k; + f->channel_buffer_start += k; + if (n == len) + break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) + break; + } + return n; +} + +int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, int num_samples) +{ + float **outputs; + int n=0; + int z = f->channels; + if (z > channels) z = channels; + while (n < num_samples) { + int i; + int k = f->channel_buffer_end - f->channel_buffer_start; + if (n+k >= num_samples) k = num_samples - n; + if (k) { + for (i=0; i < z; ++i) + memcpy(buffer[i]+n, f->channel_buffers[i]+f->channel_buffer_start, sizeof(float)*k); + for ( ; i < channels; ++i) + memset(buffer[i]+n, 0, sizeof(float) * k); + } + n += k; + f->channel_buffer_start += k; + if (n == num_samples) + break; + if (!stb_vorbis_get_frame_float(f, NULL, &outputs)) + break; + } + return n; +} +#endif // STB_VORBIS_NO_PULLDATA_API + +/* Version history + 1.17 - 2019-07-08 - fix CVE-2019-13217, -13218, -13219, -13220, -13221, -13222, -13223 + found with Mayhem by ForAllSecure + 1.16 - 2019-03-04 - fix warnings + 1.15 - 2019-02-07 - explicit failure if Ogg Skeleton data is found + 1.14 - 2018-02-11 - delete bogus dealloca usage + 1.13 - 2018-01-29 - fix truncation of last frame (hopefully) + 1.12 - 2017-11-21 - limit residue begin/end to blocksize/2 to avoid large temp allocs in bad/corrupt files + 1.11 - 2017-07-23 - fix MinGW compilation + 1.10 - 2017-03-03 - more robust seeking; fix negative ilog(); clear error in open_memory + 1.09 - 2016-04-04 - back out 'avoid discarding last frame' fix from previous version + 1.08 - 2016-04-02 - fixed multiple warnings; fix setup memory leaks; + avoid discarding last frame of audio data + 1.07 - 2015-01-16 - fixed some warnings, fix mingw, const-correct API + some more crash fixes when out of memory or with corrupt files + 1.06 - 2015-08-31 - full, correct support for seeking API (Dougall Johnson) + some crash fixes when out of memory or with corrupt files + 1.05 - 2015-04-19 - don't define __forceinline if it's redundant + 1.04 - 2014-08-27 - fix missing const-correct case in API + 1.03 - 2014-08-07 - Warning fixes + 1.02 - 2014-07-09 - Declare qsort compare function _cdecl on windows + 1.01 - 2014-06-18 - fix stb_vorbis_get_samples_float + 1.0 - 2014-05-26 - fix memory leaks; fix warnings; fix bugs in multichannel + (API change) report sample rate for decode-full-file funcs + 0.99996 - bracket #include for macintosh compilation by Laurent Gomila + 0.99995 - use union instead of pointer-cast for fast-float-to-int to avoid alias-optimization problem + 0.99994 - change fast-float-to-int to work in single-precision FPU mode, remove endian-dependence + 0.99993 - remove assert that fired on legal files with empty tables + 0.99992 - rewind-to-start + 0.99991 - bugfix to stb_vorbis_get_samples_short by Bernhard Wodo + 0.9999 - (should have been 0.99990) fix no-CRT support, compiling as C++ + 0.9998 - add a full-decode function with a memory source + 0.9997 - fix a bug in the read-from-FILE case in 0.9996 addition + 0.9996 - query length of vorbis stream in samples/seconds + 0.9995 - bugfix to another optimization that only happened in certain files + 0.9994 - bugfix to one of the optimizations that caused significant (but inaudible?) errors + 0.9993 - performance improvements; runs in 99% to 104% of time of reference implementation + 0.9992 - performance improvement of IMDCT; now performs close to reference implementation + 0.9991 - performance improvement of IMDCT + 0.999 - (should have been 0.9990) performance improvement of IMDCT + 0.998 - no-CRT support from Casey Muratori + 0.997 - bugfixes for bugs found by Terje Mathisen + 0.996 - bugfix: fast-huffman decode initialized incorrectly for sparse codebooks; fixing gives 10% speedup - found by Terje Mathisen + 0.995 - bugfix: fix to 'effective' overrun detection - found by Terje Mathisen + 0.994 - bugfix: garbage decode on final VQ symbol of a non-multiple - found by Terje Mathisen + 0.993 - bugfix: pushdata API required 1 extra byte for empty page (failed to consume final page if empty) - found by Terje Mathisen + 0.992 - fixes for MinGW warning + 0.991 - turn fast-float-conversion on by default + 0.990 - fix push-mode seek recovery if you seek into the headers + 0.98b - fix to bad release of 0.98 + 0.98 - fix push-mode seek recovery; robustify float-to-int and support non-fast mode + 0.97 - builds under c++ (typecasting, don't use 'class' keyword) + 0.96 - somehow MY 0.95 was right, but the web one was wrong, so here's my 0.95 rereleased as 0.96, fixes a typo in the clamping code + 0.95 - clamping code for 16-bit functions + 0.94 - not publically released + 0.93 - fixed all-zero-floor case (was decoding garbage) + 0.92 - fixed a memory leak + 0.91 - conditional compiles to omit parts of the API and the infrastructure to support them: STB_VORBIS_NO_PULLDATA_API, STB_VORBIS_NO_PUSHDATA_API, STB_VORBIS_NO_STDIO, STB_VORBIS_NO_INTEGER_CONVERSION + 0.90 - first public release +*/ + +#endif // STB_VORBIS_HEADER_ONLY + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ From ba109d8981992e5611982fcdd375cd4f1d6c3fb6 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sun, 1 Jan 2023 15:13:01 -0600 Subject: [PATCH 109/128] core: Catch and I_Error uncaught exceptions in main --- src/sdl/CMakeLists.txt | 2 +- src/sdl/{i_main.c => i_main.cpp} | 53 ++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) rename src/sdl/{i_main.c => i_main.cpp} (84%) diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt index ee9c4cddc..61d2e1a8f 100644 --- a/src/sdl/CMakeLists.txt +++ b/src/sdl/CMakeLists.txt @@ -6,7 +6,7 @@ target_sources(SRB2SDL2 PRIVATE i_threads.c i_net.c i_system.c - i_main.c + i_main.cpp i_video.c dosstr.c endtxt.c diff --git a/src/sdl/i_main.c b/src/sdl/i_main.cpp similarity index 84% rename from src/sdl/i_main.c rename to src/sdl/i_main.cpp index bc1200788..105352e41 100644 --- a/src/sdl/i_main.c +++ b/src/sdl/i_main.cpp @@ -23,6 +23,10 @@ #include "../m_misc.h"/* path shit */ #include "../i_system.h" +#include +#include +#include + #if defined (__GNUC__) || defined (__unix__) #include #endif @@ -31,7 +35,9 @@ #include #endif +extern "C" { #include "time.h" // For log timestamps +} #ifdef HAVE_SDL @@ -70,7 +76,9 @@ char logfilename[1024]; #endif #if defined (_WIN32) +extern "C" { #include "../win32/win_dbg.h" +} typedef BOOL (WINAPI *p_IsDebuggerPresent)(VOID); #endif @@ -151,20 +159,20 @@ static void InitLogging(void) if (M_IsPathAbsolute(reldir)) { left = snprintf(logfilename, sizeof logfilename, - "%s"PATHSEP, reldir); + "%s" PATHSEP, reldir); } else #ifdef DEFAULTDIR if (logdir) { left = snprintf(logfilename, sizeof logfilename, - "%s"PATHSEP DEFAULTDIR PATHSEP"%s"PATHSEP, logdir, reldir); + "%s" PATHSEP DEFAULTDIR PATHSEP "%s" PATHSEP, logdir, reldir); } else #endif/*DEFAULTDIR*/ { left = snprintf(logfilename, sizeof logfilename, - "."PATHSEP"%s"PATHSEP, reldir); + "." PATHSEP "%s" PATHSEP, reldir); } strftime(&logfilename[left], sizeof logfilename - left, @@ -208,6 +216,33 @@ ChDirToExe (void) } #endif +static void walk_exception_stack(std::string& accum, bool nested) { + if (nested) + accum.append("\n Caused by: Unknown exception"); + else + accum.append("Uncaught exception: Unknown exception"); +} + +static void walk_exception_stack(std::string& accum, const std::exception& ex, bool nested) { + if (nested) + accum.append("\n Caused by: "); + else + accum.append("Uncaught exception: "); + + accum.append("("); + accum.append(typeid(ex).name()); + accum.append(") "); + accum.append(ex.what()); + + try { + std::rethrow_if_nested(ex); + } catch (const std::exception& ex) { + walk_exception_stack(accum, ex, true); + } catch (...) { + walk_exception_stack(accum, true); + } +} + /** \brief The main function @@ -268,6 +303,8 @@ int main(int argc, char **argv) MakeCodeWritable(); #endif + try { + // startup SRB2 CONS_Printf("Setting up Dr. Robotnik's Ring Racers...\n"); D_SRB2Main(); @@ -279,6 +316,16 @@ int main(int argc, char **argv) // never return D_SRB2Loop(); + } catch (const std::exception& ex) { + std::string exception; + walk_exception_stack(exception, ex, false); + I_Error("%s", exception.c_str()); + } catch (...) { + std::string exception; + walk_exception_stack(exception, false); + I_Error("%s", exception.c_str()); + } + #ifdef BUGTRAP // This is safe even if BT didn't start. ShutdownBugTrap(); From 8c259487b2f958000654922c0afc47a0c3b7f01f Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sun, 1 Jan 2023 15:15:28 -0600 Subject: [PATCH 110/128] audio: Add pure-ISO C++17 audio mixer and backend This replaces SDL2_mixer. --- src/CMakeLists.txt | 3 + src/audio/CMakeLists.txt | 37 ++ src/audio/chunk_load.cpp | 206 +++++++++ src/audio/chunk_load.hpp | 27 ++ src/audio/expand_mono.cpp | 26 ++ src/audio/expand_mono.hpp | 27 ++ src/audio/filter.cpp | 40 ++ src/audio/filter.hpp | 46 +++ src/audio/gain.cpp | 43 ++ src/audio/gain.hpp | 33 ++ src/audio/gme.cpp | 141 +++++++ src/audio/gme.hpp | 74 ++++ src/audio/gme_player.cpp | 73 ++++ src/audio/gme_player.hpp | 51 +++ src/audio/mixer.cpp | 62 +++ src/audio/mixer.hpp | 41 ++ src/audio/music_player.cpp | 421 +++++++++++++++++++ src/audio/music_player.hpp | 69 ++++ src/audio/ogg.cpp | 194 +++++++++ src/audio/ogg.hpp | 81 ++++ src/audio/ogg_player.cpp | 141 +++++++ src/audio/ogg_player.hpp | 72 ++++ src/audio/resample.cpp | 81 ++++ src/audio/resample.hpp | 63 +++ src/audio/sample.hpp | 78 ++++ src/audio/sound_chunk.hpp | 25 ++ src/audio/sound_effect_player.cpp | 72 ++++ src/audio/sound_effect_player.hpp | 46 +++ src/audio/source.hpp | 36 ++ src/audio/wav.cpp | 264 ++++++++++++ src/audio/wav.hpp | 51 +++ src/audio/wav_player.cpp | 45 ++ src/audio/wav_player.hpp | 49 +++ src/audio/xmp.cpp | 167 ++++++++ src/audio/xmp.hpp | 78 ++++ src/audio/xmp_player.cpp | 57 +++ src/audio/xmp_player.hpp | 48 +++ src/sdl/CMakeLists.txt | 2 +- src/sdl/new_sound.cpp | 665 ++++++++++++++++++++++++++++++ 39 files changed, 3734 insertions(+), 1 deletion(-) create mode 100644 src/audio/CMakeLists.txt create mode 100644 src/audio/chunk_load.cpp create mode 100644 src/audio/chunk_load.hpp create mode 100644 src/audio/expand_mono.cpp create mode 100644 src/audio/expand_mono.hpp create mode 100644 src/audio/filter.cpp create mode 100644 src/audio/filter.hpp create mode 100644 src/audio/gain.cpp create mode 100644 src/audio/gain.hpp create mode 100644 src/audio/gme.cpp create mode 100644 src/audio/gme.hpp create mode 100644 src/audio/gme_player.cpp create mode 100644 src/audio/gme_player.hpp create mode 100644 src/audio/mixer.cpp create mode 100644 src/audio/mixer.hpp create mode 100644 src/audio/music_player.cpp create mode 100644 src/audio/music_player.hpp create mode 100644 src/audio/ogg.cpp create mode 100644 src/audio/ogg.hpp create mode 100644 src/audio/ogg_player.cpp create mode 100644 src/audio/ogg_player.hpp create mode 100644 src/audio/resample.cpp create mode 100644 src/audio/resample.hpp create mode 100644 src/audio/sample.hpp create mode 100644 src/audio/sound_chunk.hpp create mode 100644 src/audio/sound_effect_player.cpp create mode 100644 src/audio/sound_effect_player.hpp create mode 100644 src/audio/source.hpp create mode 100644 src/audio/wav.cpp create mode 100644 src/audio/wav.hpp create mode 100644 src/audio/wav_player.cpp create mode 100644 src/audio/wav_player.hpp create mode 100644 src/audio/xmp.cpp create mode 100644 src/audio/xmp.hpp create mode 100644 src/audio/xmp_player.cpp create mode 100644 src/audio/xmp_player.hpp create mode 100644 src/sdl/new_sound.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7293393cb..57fa5993a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -226,6 +226,8 @@ target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_DISCORDRPC -DUSE_STUN) target_sources(SRB2SDL2 PRIVATE discord.c stun.c) target_link_libraries(SRB2SDL2 PRIVATE tcbrindle::span) +target_link_libraries(SRB2SDL2 PRIVATE stb_vorbis) +target_link_libraries(SRB2SDL2 PRIVATE xmp-lite::xmp-lite) set(SRB2_HAVE_THREADS ON) target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_THREADS) @@ -538,6 +540,7 @@ if(SRB2_CONFIG_PROFILEMODE AND "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") target_link_options(SRB2SDL2 PRIVATE -pg) endif() +add_subdirectory(audio) add_subdirectory(io) add_subdirectory(sdl) add_subdirectory(objects) diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt new file mode 100644 index 000000000..0f35daf91 --- /dev/null +++ b/src/audio/CMakeLists.txt @@ -0,0 +1,37 @@ +target_sources(SRB2SDL2 PRIVATE + chunk_load.cpp + chunk_load.hpp + expand_mono.cpp + expand_mono.hpp + filter.cpp + filter.hpp + gain.cpp + gain.hpp + gme_player.cpp + gme_player.hpp + gme.cpp + gme.hpp + mixer.cpp + mixer.hpp + music_player.cpp + music_player.hpp + ogg_player.cpp + ogg_player.hpp + ogg.cpp + ogg.hpp + resample.cpp + resample.hpp + sample.hpp + sound_chunk.hpp + sound_effect_player.cpp + sound_effect_player.hpp + source.hpp + wav_player.cpp + wav_player.hpp + wav.cpp + wav.hpp + xmp_player.cpp + xmp_player.hpp + xmp.cpp + xmp.hpp +) diff --git a/src/audio/chunk_load.cpp b/src/audio/chunk_load.cpp new file mode 100644 index 000000000..79900ec17 --- /dev/null +++ b/src/audio/chunk_load.cpp @@ -0,0 +1,206 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "chunk_load.hpp" + +#include + +#include "../cxxutil.hpp" +#include "../io/streams.hpp" +#include "gme.hpp" +#include "gme_player.hpp" +#include "ogg.hpp" +#include "ogg_player.hpp" +#include "resample.hpp" +#include "sound_chunk.hpp" +#include "sound_effect_player.hpp" +#include "wav.hpp" +#include "wav_player.hpp" + +using std::nullopt; +using std::optional; +using std::size_t; + +using namespace srb2::audio; +using namespace srb2; + +namespace { + +// Utility for leveraging Resampler... +class SoundChunkSource : public Source<1> { +public: + explicit SoundChunkSource(std::unique_ptr&& chunk) + : chunk_(std::forward>(chunk)) {} + + virtual size_t generate(tcb::span> buffer) override final { + if (!chunk_) + return 0; + + size_t written = 0; + for (; pos_ < chunk_->samples.size() && written < buffer.size(); pos_++) { + buffer[written] = chunk_->samples[pos_]; + written++; + } + return written; + } + +private: + std::unique_ptr chunk_; + size_t pos_ {0}; +}; + +template +std::vector> generate_to_vec(I& source, std::size_t estimate = 0) { + std::vector> generated; + + size_t total = 0; + size_t read = 0; + generated.reserve(estimate); + do { + generated.resize(total + 4096); + read = source.generate(tcb::span {generated.data() + total, 4096}); + total += read; + } while (read != 0); + generated.resize(total); + return generated; +} + +optional try_load_dmx(tcb::span data) { + io::SpanStream stream {data}; + + if (io::remaining(stream) < 8) + return nullopt; + + uint16_t version = io::read_uint16(stream); + if (version != 3) + return nullopt; + + uint16_t rate = io::read_uint16(stream); + uint32_t length = io::read_uint32(stream) - 32u; + + if (io::remaining(stream) < (length + 32u)) + return nullopt; + + stream.seek(io::SeekFrom::kCurrent, 16); + + std::vector> samples; + for (size_t i = 0; i < length; i++) { + uint8_t doom_sample = io::read_uint8(stream); + float float_sample = audio::sample_to_float(doom_sample); + samples.push_back(Sample<1> {float_sample}); + } + size_t samples_len = samples.size(); + + if (rate == 44100) { + return SoundChunk {samples}; + } + + std::unique_ptr chunk_source = + std::make_unique(std::make_unique(SoundChunk {std::move(samples)})); + Resampler<1> resampler(std::move(chunk_source), rate / static_cast(kSampleRate)); + + std::vector> resampled; + + size_t total = 0; + size_t read = 0; + resampled.reserve(samples_len * (static_cast(kSampleRate) / rate)); + do { + resampled.resize(total + 4096); + read = resampler.generate(tcb::span {resampled.data() + total, 4096}); + total += read; + } while (read != 0); + resampled.resize(total); + + return SoundChunk {std::move(resampled)}; +} + +optional try_load_wav(tcb::span data) { + io::SpanStream stream {data}; + + audio::Wav wav; + std::size_t sample_rate; + + try { + wav = audio::load_wav(stream); + } catch (const std::exception& ex) { + return nullopt; + } + + sample_rate = wav.sample_rate(); + + audio::Resampler<1> resampler(std::make_unique(std::move(wav)), + sample_rate / static_cast(kSampleRate)); + + SoundChunk chunk {generate_to_vec(resampler)}; + return chunk; +} + +optional try_load_ogg(tcb::span data) { + std::shared_ptr> player; + try { + io::SpanStream data_stream {data}; + audio::Ogg ogg = audio::load_ogg(data_stream); + player = std::make_shared>(std::move(ogg)); + } catch (...) { + return nullopt; + } + player->looping(false); + player->playing(true); + player->reset(); + std::size_t sample_rate = player->sample_rate(); + audio::Resampler<1> resampler(player, sample_rate / 44100.); + std::vector> resampled {generate_to_vec(resampler)}; + + SoundChunk chunk {std::move(resampled)}; + return chunk; +} + +optional try_load_gme(tcb::span data) { + std::shared_ptr> player; + try { + if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B}) { + io::SpanStream stream {data}; + audio::Gme gme = audio::load_gme(stream); + player = std::make_shared>(std::move(gme)); + } else { + io::ZlibInputStream stream {io::SpanStream(data)}; + audio::Gme gme = audio::load_gme(stream); + player = std::make_shared>(std::move(gme)); + } + } catch (...) { + return nullopt; + } + std::vector> samples {generate_to_vec(*player)}; + SoundChunk chunk {std::move(samples)}; + return chunk; +} + +} // namespace + +optional srb2::audio::try_load_chunk(tcb::span data) { + optional ret; + + ret = try_load_dmx(data); + if (ret) + return ret; + + ret = try_load_wav(data); + if (ret) + return ret; + + ret = try_load_ogg(data); + if (ret) + return ret; + + ret = try_load_gme(data); + if (ret) + return ret; + + return nullopt; +} diff --git a/src/audio/chunk_load.hpp b/src/audio/chunk_load.hpp new file mode 100644 index 000000000..c97d559d2 --- /dev/null +++ b/src/audio/chunk_load.hpp @@ -0,0 +1,27 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_CHUNK_LOAD_HPP__ +#define __SRB2_AUDIO_CHUNK_LOAD_HPP__ + +#include +#include + +#include + +#include "sound_chunk.hpp" + +namespace srb2::audio { + +/// @brief Try to load a chunk from the given byte span. +std::optional try_load_chunk(tcb::span data); + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_CHUNK_LOAD_HPP__ diff --git a/src/audio/expand_mono.cpp b/src/audio/expand_mono.cpp new file mode 100644 index 000000000..ce7cf0dc3 --- /dev/null +++ b/src/audio/expand_mono.cpp @@ -0,0 +1,26 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "expand_mono.hpp" + +#include + +using std::size_t; + +using namespace srb2::audio; + +ExpandMono::~ExpandMono() = default; + +size_t ExpandMono::filter(tcb::span> input_buffer, tcb::span> buffer) { + for (size_t i = 0; i < std::min(input_buffer.size(), buffer.size()); i++) { + buffer[i].amplitudes[0] = input_buffer[i].amplitudes[0]; + buffer[i].amplitudes[1] = input_buffer[i].amplitudes[0]; + } + return std::min(input_buffer.size(), buffer.size()); +} diff --git a/src/audio/expand_mono.hpp b/src/audio/expand_mono.hpp new file mode 100644 index 000000000..f3686704e --- /dev/null +++ b/src/audio/expand_mono.hpp @@ -0,0 +1,27 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_EXPAND_MONO_HPP__ +#define __SRB2_AUDIO_EXPAND_MONO_HPP__ + +#include + +#include "filter.hpp" + +namespace srb2::audio { + +class ExpandMono : public Filter<1, 2> { +public: + virtual ~ExpandMono(); + virtual std::size_t filter(tcb::span> input_buffer, tcb::span> buffer) override final; +}; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_EXPAND_MONO_HPP__ diff --git a/src/audio/filter.cpp b/src/audio/filter.cpp new file mode 100644 index 000000000..8bb09bdfb --- /dev/null +++ b/src/audio/filter.cpp @@ -0,0 +1,40 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "filter.hpp" + +using std::shared_ptr; +using std::size_t; + +using srb2::audio::Filter; +using srb2::audio::Sample; +using srb2::audio::Source; + +template +size_t Filter::generate(tcb::span> buffer) { + input_buffer_.clear(); + input_buffer_.resize(buffer.size()); + + input_->generate(input_buffer_); + + return filter(input_buffer_, buffer); +} + +template +void Filter::bind(const shared_ptr>& input) { + input_ = input; +} + +template +Filter::~Filter() = default; + +template class srb2::audio::Filter<1, 1>; +template class srb2::audio::Filter<1, 2>; +template class srb2::audio::Filter<2, 1>; +template class srb2::audio::Filter<2, 2>; diff --git a/src/audio/filter.hpp b/src/audio/filter.hpp new file mode 100644 index 000000000..59dce6e1e --- /dev/null +++ b/src/audio/filter.hpp @@ -0,0 +1,46 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_FILTER_HPP__ +#define __SRB2_AUDIO_FILTER_HPP__ + +#include +#include +#include + +#include + +#include "source.hpp" + +namespace srb2::audio { + +template +class Filter : public Source { +public: + virtual std::size_t generate(tcb::span> buffer) override; + + void bind(const std::shared_ptr>& input); + + virtual std::size_t filter(tcb::span> input_buffer, tcb::span> buffer) = 0; + + virtual ~Filter(); + +private: + std::shared_ptr> input_; + std::vector> input_buffer_; +}; + +extern template class Filter<1, 1>; +extern template class Filter<1, 2>; +extern template class Filter<2, 1>; +extern template class Filter<2, 2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_FILTER_HPP__ diff --git a/src/audio/gain.cpp b/src/audio/gain.cpp new file mode 100644 index 000000000..59839bb69 --- /dev/null +++ b/src/audio/gain.cpp @@ -0,0 +1,43 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "gain.hpp" + +#include + +using std::size_t; + +using srb2::audio::Filter; +using srb2::audio::Gain; +using srb2::audio::Sample; + +constexpr const float kGainInterpolationAlpha = 0.8f; + +template +size_t Gain::filter(tcb::span> input_buffer, tcb::span> buffer) { + size_t written = std::min(buffer.size(), input_buffer.size()); + for (size_t i = 0; i < written; i++) { + buffer[i] = input_buffer[i]; + buffer[i] *= gain_; + gain_ += (new_gain_ - gain_) * kGainInterpolationAlpha; + } + + return written; +} + +template +void Gain::gain(float new_gain) { + new_gain_ = std::clamp(new_gain, 0.0f, 1.0f); +} + +template +Gain::~Gain() = default; + +template class srb2::audio::Gain<1>; +template class srb2::audio::Gain<2>; diff --git a/src/audio/gain.hpp b/src/audio/gain.hpp new file mode 100644 index 000000000..ef4dd0d53 --- /dev/null +++ b/src/audio/gain.hpp @@ -0,0 +1,33 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_GAIN_HPP__ +#define __SRB2_AUDIO_GAIN_HPP__ + +#include + +#include "filter.hpp" + +namespace srb2::audio { + +template +class Gain : public Filter { +public: + virtual std::size_t filter(tcb::span> input_buffer, tcb::span> buffer) override final; + void gain(float new_gain); + + virtual ~Gain(); + +private: + float new_gain_ {1.f}; + float gain_ {1.f}; +}; +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_GAIN_HPP__ diff --git a/src/audio/gme.cpp b/src/audio/gme.cpp new file mode 100644 index 000000000..38279dd98 --- /dev/null +++ b/src/audio/gme.cpp @@ -0,0 +1,141 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "gme.hpp" + +#include +#include + +#include "../cxxutil.hpp" + +using namespace srb2; +using namespace srb2::audio; + +Gme::Gme() : memory_data_(), instance_(nullptr) { +} + +Gme::Gme(Gme&& rhs) noexcept : memory_data_(), instance_(nullptr) { + std::swap(memory_data_, rhs.memory_data_); + std::swap(instance_, rhs.instance_); +} + +Gme::Gme(std::vector&& data) : memory_data_(std::move(data)), instance_(nullptr) { + _init_with_data(); +} + +Gme::Gme(tcb::span data) : memory_data_(data.begin(), data.end()), instance_(nullptr) { + _init_with_data(); +} + +Gme& Gme::operator=(Gme&& rhs) noexcept { + std::swap(memory_data_, rhs.memory_data_); + std::swap(instance_, rhs.instance_); + + return *this; +} + +Gme::~Gme() { + if (instance_) { + gme_delete(instance_); + instance_ = nullptr; + } +} + +std::size_t Gme::get_samples(tcb::span buffer) { + SRB2_ASSERT(instance_ != nullptr); + + gme_err_t err = gme_play(instance_, buffer.size(), buffer.data()); + if (err) + throw GmeException(err); + + return buffer.size(); +} + +void Gme::seek(int sample) { + SRB2_ASSERT(instance_ != nullptr); + + gme_seek_samples(instance_, sample); +} + +std::optional Gme::duration_seconds() const { + SRB2_ASSERT(instance_ != nullptr); + + gme_info_t* info = nullptr; + gme_err_t res = gme_track_info(instance_, &info, 0); + if (res) + throw GmeException(res); + auto info_finally = srb2::finally([&info] { gme_free_info(info); }); + + if (info->length == -1) + return std::nullopt; + + // info lengths are in ms + return static_cast(info->length) / 1000.f; +} + +std::optional Gme::loop_point_seconds() const { + SRB2_ASSERT(instance_ != nullptr); + + gme_info_t* info = nullptr; + gme_err_t res = gme_track_info(instance_, &info, 0); + if (res) + throw GmeException(res); + auto info_finally = srb2::finally([&info] { gme_free_info(info); }); + + int loop_point_ms = info->intro_length; + if (loop_point_ms == -1) + return std::nullopt; + + return loop_point_ms / 44100.f; +} + +float Gme::position_seconds() const { + SRB2_ASSERT(instance_ != nullptr); + + gme_info_t* info = nullptr; + gme_err_t res = gme_track_info(instance_, &info, 0); + if (res) + throw GmeException(res); + auto info_finally = srb2::finally([&info] { gme_free_info(info); }); + + int position = gme_tell(instance_); + + // adjust position, since GME's counter keeps going past loop + if (info->length > 0) + position %= info->length; + else if (info->intro_length + info->loop_length > 0) + position = position >= (info->intro_length + info->loop_length) ? (position % info->loop_length) : position; + else + position %= 150 * 1000; // 2.5 minutes + + return position / 1000.f; +} + +void Gme::_init_with_data() { + if (instance_) { + return; + } + + if (memory_data_.size() >= std::numeric_limits::max()) + throw std::invalid_argument("Buffer is too large for gme"); + if (memory_data_.size() == 0) + throw std::invalid_argument("Insufficient data from stream"); + + gme_err_t result = + gme_open_data(reinterpret_cast(memory_data_.data()), memory_data_.size(), &instance_, 44100); + if (result) + throw GmeException(result); + + // we no longer need the data, so there's no reason to keep the allocation + memory_data_ = std::vector(); + + result = gme_start_track(instance_, 0); + if (result) + throw GmeException(result); +} diff --git a/src/audio/gme.hpp b/src/audio/gme.hpp new file mode 100644 index 000000000..34f2c2769 --- /dev/null +++ b/src/audio/gme.hpp @@ -0,0 +1,74 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_GME_HPP__ +#define __SRB2_AUDIO_GME_HPP__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#undef byte // BLARGG!! NO!! +#undef check // STOP IT!!!! + +#include "../io/streams.hpp" + +namespace srb2::audio { + +class GmeException : public std::exception { + std::string msg_; + +public: + explicit GmeException(gme_err_t msg) : msg_(msg == nullptr ? "" : msg) {} + + virtual const char* what() const noexcept override { return msg_.c_str(); } +}; + +class Gme { + std::vector memory_data_; + Music_Emu* instance_; + +public: + Gme(); + Gme(const Gme&) = delete; + Gme(Gme&& rhs) noexcept; + + Gme& operator=(const Gme&) = delete; + Gme& operator=(Gme&& rhs) noexcept; + + explicit Gme(std::vector&& data); + explicit Gme(tcb::span data); + + std::size_t get_samples(tcb::span buffer); + void seek(int sample); + + std::optional duration_seconds() const; + std::optional loop_point_seconds() const; + float position_seconds() const; + + ~Gme(); + +private: + void _init_with_data(); +}; + +template , int> = 0> +inline Gme load_gme(I& stream) { + std::vector data = srb2::io::read_to_vec(stream); + return Gme {std::move(data)}; +} + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_GME_HPP__ diff --git a/src/audio/gme_player.cpp b/src/audio/gme_player.cpp new file mode 100644 index 000000000..229c99676 --- /dev/null +++ b/src/audio/gme_player.cpp @@ -0,0 +1,73 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "gme_player.hpp" + +using namespace srb2; +using namespace srb2::audio; + +template +GmePlayer::GmePlayer(Gme&& gme) : gme_(std::forward(gme)), buf_() { +} + +template +GmePlayer::GmePlayer(GmePlayer&& rhs) noexcept = default; + +template +GmePlayer& GmePlayer::operator=(GmePlayer&& rhs) noexcept = default; + +template +GmePlayer::~GmePlayer() = default; + +template +std::size_t GmePlayer::generate(tcb::span> buffer) { + buf_.clear(); + buf_.resize(buffer.size() * 2); + + std::size_t read = gme_.get_samples(tcb::make_span(buf_)); + buf_.resize(read); + std::size_t new_samples = std::min((read / 2), buffer.size()); + for (std::size_t i = 0; i < new_samples; i++) { + if constexpr (C == 1) { + buffer[i].amplitudes[0] = (buf_[i * 2] / 32768.f + buf_[i * 2 + 1] / 32768.f) / 2.f; + } else if constexpr (C == 2) { + buffer[i].amplitudes[0] = buf_[i * 2] / 32768.f; + buffer[i].amplitudes[1] = buf_[i * 2 + 1] / 32768.f; + } + } + return new_samples; +} + +template +void GmePlayer::seek(float position_seconds) { + gme_.seek(static_cast(position_seconds * 44100.f)); +} + +template +void GmePlayer::reset() { + gme_.seek(0); +} + +template +std::optional GmePlayer::duration_seconds() const { + return gme_.duration_seconds(); +} + +template +std::optional GmePlayer::loop_point_seconds() const { + return gme_.loop_point_seconds(); +} + +template +float GmePlayer::position_seconds() const { + return gme_.position_seconds(); +} + +template class srb2::audio::GmePlayer<1>; +template class srb2::audio::GmePlayer<2>; diff --git a/src/audio/gme_player.hpp b/src/audio/gme_player.hpp new file mode 100644 index 000000000..a2f555c08 --- /dev/null +++ b/src/audio/gme_player.hpp @@ -0,0 +1,51 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_GME_PLAYER_HPP__ +#define __SRB2_AUDIO_GME_PLAYER_HPP__ + +#include + +#include "gme.hpp" +#include "source.hpp" + +namespace srb2::audio { + +template +class GmePlayer : public Source { + Gme gme_; + std::vector buf_; + +public: + GmePlayer(Gme&& gme); + GmePlayer(const GmePlayer&) = delete; + GmePlayer(GmePlayer&& gme) noexcept; + + ~GmePlayer(); + + GmePlayer& operator=(const GmePlayer&) = delete; + GmePlayer& operator=(GmePlayer&& rhs) noexcept; + + virtual std::size_t generate(tcb::span> buffer) override; + + void seek(float position_seconds); + + std::optional duration_seconds() const; + std::optional loop_point_seconds() const; + float position_seconds() const; + + void reset(); +}; + +extern template class GmePlayer<1>; +extern template class GmePlayer<2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_GME_PLAYER_HPP__ diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp new file mode 100644 index 000000000..d4b718ffa --- /dev/null +++ b/src/audio/mixer.cpp @@ -0,0 +1,62 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "mixer.hpp" + +#include + +using std::shared_ptr; +using std::size_t; + +using srb2::audio::Mixer; +using srb2::audio::Sample; +using srb2::audio::Source; + +namespace { + +template +void default_init_sample_buffer(Sample* buffer, size_t size) { + std::for_each(buffer, buffer + size, [](auto& i) { i = Sample {}; }); +} + +template +void mix_sample_buffers(Sample* dst, size_t size, Sample* src, size_t src_size) { + for (size_t i = 0; i < size && i < src_size; i++) { + dst[i] += src[i]; + } +} + +} // namespace + +template +size_t Mixer::generate(tcb::span> buffer) { + buffer_.resize(buffer.size()); + + default_init_sample_buffer(buffer.data(), buffer.size()); + + for (auto& source : sources_) { + size_t read = source->generate(buffer_); + + mix_sample_buffers(buffer.data(), buffer.size(), buffer_.data(), read); + } + + // because we initialized the out-buffer, we always generate size samples + return buffer.size(); +} + +template +void Mixer::add_source(const shared_ptr>& source) { + sources_.push_back(source); +} + +template +Mixer::~Mixer() = default; + +template class srb2::audio::Mixer<1>; +template class srb2::audio::Mixer<2>; diff --git a/src/audio/mixer.hpp b/src/audio/mixer.hpp new file mode 100644 index 000000000..75c8ba2f9 --- /dev/null +++ b/src/audio/mixer.hpp @@ -0,0 +1,41 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_MIXER_HPP__ +#define __SRB2_AUDIO_MIXER_HPP__ + +#include +#include + +#include + +#include "source.hpp" + +namespace srb2::audio { + +template +class Mixer : public Source { +public: + virtual std::size_t generate(tcb::span> buffer) override final; + + virtual ~Mixer(); + + void add_source(const std::shared_ptr>& source); + +private: + std::vector>> sources_; + std::vector> buffer_; +}; + +extern template class Mixer<1>; +extern template class Mixer<2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_MIXER_HPP__ diff --git a/src/audio/music_player.cpp b/src/audio/music_player.cpp new file mode 100644 index 000000000..1121518da --- /dev/null +++ b/src/audio/music_player.cpp @@ -0,0 +1,421 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "music_player.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#undef byte // BLARGG!! NO!! +#undef check // STOP IT!!!! + +#include "../cxxutil.hpp" +#include "../io/streams.hpp" +#include "gme_player.hpp" +#include "ogg_player.hpp" +#include "resample.hpp" +#include "xmp_player.hpp" + +using std::array; +using std::byte; +using std::make_unique; +using std::size_t; +using std::vector; + +using srb2::audio::MusicPlayer; +using srb2::audio::Resampler; +using srb2::audio::Sample; +using srb2::audio::Source; +using namespace srb2; + +class MusicPlayer::Impl { +public: + Impl() = default; + Impl(tcb::span data) : Impl() { _load(data); } + + size_t generate(tcb::span> buffer) { + if (!resampler_) + return 0; + + if (!playing_) + return 0; + + size_t total_written = 0; + + while (total_written < buffer.size()) { + const size_t generated = resampler_->generate(buffer.subspan(total_written)); + + // To avoid a branch preventing optimizations, we're always going to apply + // the fade gain, even if it would clamp anyway. + for (std::size_t i = 0; i < generated; i++) { + const float alpha = 1.0 - (gain_samples_target_ - std::min(gain_samples_ + i, gain_samples_target_)) / + static_cast(gain_samples_target_); + const float fade_gain = (gain_target_ - gain_) * std::clamp(alpha, 0.f, 1.f) + gain_; + buffer[total_written + i] *= fade_gain; + } + + gain_samples_ = std::min(gain_samples_ + generated, gain_samples_target_); + + if (gain_samples_ >= gain_samples_target_) { + fading_ = false; + gain_samples_ = gain_samples_target_; + gain_ = gain_target_; + } + + total_written += generated; + + if (generated == 0) { + playing_ = false; + break; + } + } + + return total_written; + } + + void _load(tcb::span data) { + ogg_inst_ = nullptr; + gme_inst_ = nullptr; + xmp_inst_ = nullptr; + resampler_ = std::nullopt; + + try { + io::SpanStream stream {data}; + audio::Ogg ogg = audio::load_ogg(stream); + ogg_inst_ = std::make_shared>(std::move(ogg)); + ogg_inst_->looping(looping_); + resampler_ = Resampler<2>(ogg_inst_, ogg_inst_->sample_rate() / 44100.f); + } catch (const std::exception& ex) { + // it's probably not ogg + ogg_inst_ = nullptr; + resampler_ = std::nullopt; + } + + if (!resampler_) { + try { + if (data[0] == std::byte {0x1F} && data[1] == std::byte {0x8B}) { + io::ZlibInputStream stream {io::SpanStream(data)}; + audio::Gme gme = audio::load_gme(stream); + gme_inst_ = std::make_shared>(std::move(gme)); + } else { + io::SpanStream stream {data}; + audio::Gme gme = audio::load_gme(stream); + gme_inst_ = std::make_shared>(std::move(gme)); + } + + resampler_ = Resampler<2>(gme_inst_, 1.f); + } catch (const std::exception& ex) { + // it's probably not gme + gme_inst_ = nullptr; + resampler_ = std::nullopt; + } + } + + if (!resampler_) { + try { + io::SpanStream stream {data}; + audio::Xmp<2> xmp = audio::load_xmp<2>(stream); + xmp_inst_ = std::make_shared>(std::move(xmp)); + xmp_inst_->looping(looping_); + + resampler_ = Resampler<2>(xmp_inst_, 1.f); + } catch (const std::exception& ex) { + // it's probably not xmp + xmp_inst_ = nullptr; + resampler_ = std::nullopt; + } + } + + playing_ = false; + + internal_gain(1.f); + } + + void play(bool looping) { + if (ogg_inst_) { + ogg_inst_->looping(looping); + ogg_inst_->playing(true); + playing_ = true; + ogg_inst_->reset(); + } else if (gme_inst_) { + playing_ = true; + gme_inst_->reset(); + } else if (xmp_inst_) { + xmp_inst_->looping(looping); + playing_ = true; + xmp_inst_->reset(); + } + } + + void unpause() { + if (ogg_inst_) { + ogg_inst_->playing(true); + playing_ = true; + } else if (gme_inst_) { + playing_ = true; + } else if (xmp_inst_) { + playing_ = true; + } + } + + void pause() { + if (ogg_inst_) { + ogg_inst_->playing(false); + playing_ = false; + } else if (gme_inst_) { + playing_ = false; + } else if (xmp_inst_) { + playing_ = false; + } + } + + void stop() { + if (ogg_inst_) { + ogg_inst_->reset(); + ogg_inst_->playing(false); + playing_ = false; + } else if (gme_inst_) { + gme_inst_->reset(); + playing_ = false; + } else if (xmp_inst_) { + xmp_inst_->reset(); + playing_ = false; + } + } + + void seek(float position_seconds) { + if (ogg_inst_) { + ogg_inst_->seek(position_seconds); + return; + } + if (gme_inst_) { + gme_inst_->seek(position_seconds); + return; + } + if (xmp_inst_) { + xmp_inst_->seek(position_seconds); + return; + } + } + + bool playing() const { + if (ogg_inst_) + return ogg_inst_->playing(); + else if (gme_inst_) + return playing_; + else if (xmp_inst_) + return playing_; + + return false; + } + + std::optional music_type() const { + if (ogg_inst_) + return audio::MusicType::kOgg; + else if (gme_inst_) + return audio::MusicType::kGme; + else if (xmp_inst_) + return audio::MusicType::kMod; + + return std::nullopt; + } + + std::optional duration_seconds() const { + if (ogg_inst_) + return ogg_inst_->duration_seconds(); + if (gme_inst_) + return gme_inst_->duration_seconds(); + if (xmp_inst_) + return xmp_inst_->duration_seconds(); + + return std::nullopt; + } + + std::optional loop_point_seconds() const { + if (ogg_inst_) + return ogg_inst_->loop_point_seconds(); + if (gme_inst_) + return gme_inst_->loop_point_seconds(); + + return std::nullopt; + } + + std::optional position_seconds() const { + if (ogg_inst_) + return ogg_inst_->position_seconds(); + if (gme_inst_) + return gme_inst_->position_seconds(); + + return std::nullopt; + } + + void fade_to(float gain, float seconds) { fade_from_to(gain_target_, gain, seconds); } + + void fade_from_to(float from, float to, float seconds) { + fading_ = true; + gain_ = from; + gain_target_ = to; + // Gain samples target must always be at least 1 to avoid a div-by-zero. + gain_samples_target_ = std::max(static_cast(seconds * 44100.f), 1ULL); + gain_samples_ = 0; + } + + bool fading() const { return fading_; } + + void stop_fade() { internal_gain(gain_target_); } + + void loop_point_seconds(float loop_point) { + if (ogg_inst_) + ogg_inst_->loop_point_seconds(loop_point); + } + + void internal_gain(float gain) { + fading_ = false; + gain_ = gain; + gain_target_ = gain; + gain_samples_target_ = 1; + gain_samples_ = 0; + } + +private: + std::shared_ptr> ogg_inst_; + std::shared_ptr> gme_inst_; + std::shared_ptr> xmp_inst_; + std::optional> resampler_; + bool playing_ {false}; + bool looping_ {false}; + + // fade control + float gain_target_ {1.f}; + float gain_ {1.f}; + bool fading_ {false}; + uint64_t gain_samples_ {0}; + uint64_t gain_samples_target_ {1}; +}; + +// The special member functions MUST be declared in this unit, where Impl is complete. +MusicPlayer::MusicPlayer() : impl_(make_unique()) { +} +MusicPlayer::MusicPlayer(tcb::span data) : impl_(make_unique(data)) { +} +MusicPlayer::MusicPlayer(MusicPlayer&& rhs) noexcept = default; +MusicPlayer& MusicPlayer::operator=(MusicPlayer&& rhs) noexcept = default; + +MusicPlayer::~MusicPlayer() = default; + +void MusicPlayer::play(bool looping) { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->play(looping); +} + +void MusicPlayer::unpause() { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->unpause(); +} + +void MusicPlayer::pause() { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->pause(); +} + +void MusicPlayer::stop() { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->stop(); +} + +void MusicPlayer::seek(float position_seconds) { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->seek(position_seconds); +} + +bool MusicPlayer::playing() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->playing(); +} + +size_t MusicPlayer::generate(tcb::span> buffer) { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->generate(buffer); +} + +std::optional MusicPlayer::music_type() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->music_type(); +} + +std::optional MusicPlayer::duration_seconds() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->duration_seconds(); +} + +std::optional MusicPlayer::loop_point_seconds() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->loop_point_seconds(); +} + +std::optional MusicPlayer::position_seconds() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->position_seconds(); +} + +void MusicPlayer::fade_to(float gain, float seconds) { + SRB2_ASSERT(impl_ != nullptr); + + impl_->fade_to(gain, seconds); +} + +void MusicPlayer::fade_from_to(float from, float to, float seconds) { + SRB2_ASSERT(impl_ != nullptr); + + impl_->fade_from_to(from, to, seconds); +} + +void MusicPlayer::internal_gain(float gain) { + SRB2_ASSERT(impl_ != nullptr); + + impl_->internal_gain(gain); +} + +bool MusicPlayer::fading() const { + SRB2_ASSERT(impl_ != nullptr); + + return impl_->fading(); +} + +void MusicPlayer::stop_fade() { + SRB2_ASSERT(impl_ != nullptr); + + impl_->stop_fade(); +} + +void MusicPlayer::loop_point_seconds(float loop_point) { + SRB2_ASSERT(impl_ != nullptr); + + impl_->loop_point_seconds(loop_point); +} diff --git a/src/audio/music_player.hpp b/src/audio/music_player.hpp new file mode 100644 index 000000000..668724fa0 --- /dev/null +++ b/src/audio/music_player.hpp @@ -0,0 +1,69 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_MUSIC_PLAYER_HPP__ +#define __SRB2_AUDIO_MUSIC_PLAYER_HPP__ + +#include +#include + +#include + +#include "source.hpp" + +struct stb_vorbis; + +namespace srb2::audio { + +enum class MusicType { + kOgg, + kGme, + kMod +}; + +class MusicPlayer : public Source<2> { +public: + MusicPlayer(); + MusicPlayer(tcb::span data); + MusicPlayer(const MusicPlayer& rhs) = delete; + MusicPlayer(MusicPlayer&& rhs) noexcept; + + MusicPlayer& operator=(const MusicPlayer& rhs) = delete; + MusicPlayer& operator=(MusicPlayer&& rhs) noexcept; + + virtual std::size_t generate(tcb::span> buffer) override final; + + void play(bool looping); + void unpause(); + void pause(); + void stop(); + void seek(float position_seconds); + void fade_to(float gain, float seconds); + void fade_from_to(float from, float to, float seconds); + void internal_gain(float gain); + void stop_fade(); + void loop_point_seconds(float loop_point); + bool playing() const; + std::optional music_type() const; + std::optional duration_seconds() const; + std::optional loop_point_seconds() const; + std::optional position_seconds() const; + bool fading() const; + + virtual ~MusicPlayer() final; + +private: + class Impl; + + std::unique_ptr impl_; +}; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_MUSIC_PLAYER_HPP__ diff --git a/src/audio/ogg.cpp b/src/audio/ogg.cpp new file mode 100644 index 000000000..388fd37fe --- /dev/null +++ b/src/audio/ogg.cpp @@ -0,0 +1,194 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "ogg.hpp" + +#include + +#include "../cxxutil.hpp" + +using namespace srb2; +using namespace srb2::audio; + +StbVorbisException::StbVorbisException(int code) noexcept : code_(code) { +} + +const char* StbVorbisException::what() const noexcept { + switch (code_) { + case VORBIS__no_error: + return "No error"; + case VORBIS_need_more_data: + return "Need more data"; + case VORBIS_invalid_api_mixing: + return "Invalid API mixing"; + case VORBIS_outofmem: + return "Out of memory"; + case VORBIS_feature_not_supported: + return "Feature not supported"; + case VORBIS_too_many_channels: + return "Too many channels"; + case VORBIS_file_open_failure: + return "File open failure"; + case VORBIS_seek_without_length: + return "Seek without length"; + case VORBIS_unexpected_eof: + return "Unexpected EOF"; + case VORBIS_seek_invalid: + return "Seek invalid"; + case VORBIS_invalid_setup: + return "Invalid setup"; + case VORBIS_invalid_stream: + return "Invalid stream"; + case VORBIS_missing_capture_pattern: + return "Missing capture pattern"; + case VORBIS_invalid_stream_structure_version: + return "Invalid stream structure version"; + case VORBIS_continued_packet_flag_invalid: + return "Continued packet flag invalid"; + case VORBIS_incorrect_stream_serial_number: + return "Incorrect stream serial number"; + case VORBIS_invalid_first_page: + return "Invalid first page"; + case VORBIS_bad_packet_type: + return "Bad packet type"; + case VORBIS_cant_find_last_page: + return "Can't find last page"; + case VORBIS_seek_failed: + return "Seek failed"; + case VORBIS_ogg_skeleton_not_supported: + return "OGG skeleton not supported"; + default: + return "Unrecognized error code"; + } +} + +Ogg::Ogg() noexcept : memory_data_(), instance_(nullptr) { +} + +Ogg::Ogg(std::vector data) : memory_data_(std::move(data)), instance_(nullptr) { + _init_with_data(); +} + +Ogg::Ogg(tcb::span data) : memory_data_(data.begin(), data.end()), instance_(nullptr) { + _init_with_data(); +} + +Ogg::Ogg(Ogg&& rhs) noexcept : memory_data_(), instance_(nullptr) { + std::swap(memory_data_, rhs.memory_data_); + std::swap(instance_, rhs.instance_); +} + +Ogg& Ogg::operator=(Ogg&& rhs) noexcept { + std::swap(memory_data_, rhs.memory_data_); + std::swap(instance_, rhs.instance_); + + return *this; +} + +Ogg::~Ogg() { + if (instance_) { + stb_vorbis_close(instance_); + instance_ = nullptr; + } +} + +std::size_t Ogg::get_samples(tcb::span> buffer) { + SRB2_ASSERT(instance_ != nullptr); + + size_t read = stb_vorbis_get_samples_float_interleaved( + instance_, 1, reinterpret_cast(buffer.data()), buffer.size() * 1); + + return read; +} + +std::size_t Ogg::get_samples(tcb::span> buffer) { + SRB2_ASSERT(instance_ != nullptr); + + size_t read = stb_vorbis_get_samples_float_interleaved( + instance_, 2, reinterpret_cast(buffer.data()), buffer.size() * 2); + + stb_vorbis_info info = stb_vorbis_get_info(instance_); + if (info.channels == 1) { + for (auto& sample : buffer.subspan(0, read)) { + sample.amplitudes[1] = sample.amplitudes[0]; + } + } + + return read; +} + +OggComment Ogg::comment() const { + SRB2_ASSERT(instance_ != nullptr); + + stb_vorbis_comment c_comment = stb_vorbis_get_comment(instance_); + + return OggComment { + std::string(c_comment.vendor), + std::vector(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)}; +} + +std::size_t Ogg::sample_rate() const { + SRB2_ASSERT(instance_ != nullptr); + + stb_vorbis_info info = stb_vorbis_get_info(instance_); + return info.sample_rate; +} + +void Ogg::seek(std::size_t sample) { + SRB2_ASSERT(instance_ != nullptr); + + stb_vorbis_seek(instance_, sample); +} + +std::size_t Ogg::position() const { + SRB2_ASSERT(instance_ != nullptr); + + return stb_vorbis_get_sample_offset(instance_); +} + +float Ogg::position_seconds() const { + return position() / static_cast(sample_rate()); +} + +std::size_t Ogg::duration_samples() const { + SRB2_ASSERT(instance_ != nullptr); + + return stb_vorbis_stream_length_in_samples(instance_); +} + +float Ogg::duration_seconds() const { + SRB2_ASSERT(instance_ != nullptr); + + return stb_vorbis_stream_length_in_seconds(instance_); +} + +std::size_t Ogg::channels() const { + SRB2_ASSERT(instance_ != nullptr); + + stb_vorbis_info info = stb_vorbis_get_info(instance_); + return info.channels; +} + +void Ogg::_init_with_data() { + if (instance_) { + return; + } + + if (memory_data_.size() >= std::numeric_limits::max()) + throw std::logic_error("Buffer is too large for stb_vorbis"); + if (memory_data_.size() == 0) + throw std::logic_error("Insufficient data from stream"); + + int vorbis_result; + instance_ = stb_vorbis_open_memory( + reinterpret_cast(memory_data_.data()), memory_data_.size(), &vorbis_result, NULL); + + if (vorbis_result != VORBIS__no_error) + throw StbVorbisException(vorbis_result); +} diff --git a/src/audio/ogg.hpp b/src/audio/ogg.hpp new file mode 100644 index 000000000..d4b8b9275 --- /dev/null +++ b/src/audio/ogg.hpp @@ -0,0 +1,81 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_OGG_HPP__ +#define __SRB2_AUDIO_OGG_HPP__ + +#include +#include +#include + +#include +#include + +#include "../io/streams.hpp" +#include "source.hpp" + +namespace srb2::audio { + +class StbVorbisException final : public std::exception { + int code_; + +public: + explicit StbVorbisException(int code) noexcept; + + virtual const char* what() const noexcept; +}; + +struct OggComment { + std::string vendor; + std::vector comments; +}; + +class Ogg final { + std::vector memory_data_; + stb_vorbis* instance_; + +public: + Ogg() noexcept; + + explicit Ogg(std::vector data); + explicit Ogg(tcb::span data); + + Ogg(const Ogg&) = delete; + Ogg(Ogg&& rhs) noexcept; + + Ogg& operator=(const Ogg&) = delete; + Ogg& operator=(Ogg&& rhs) noexcept; + + ~Ogg(); + + std::size_t get_samples(tcb::span> buffer); + std::size_t get_samples(tcb::span> buffer); + void seek(std::size_t sample); + std::size_t position() const; + float position_seconds() const; + + OggComment comment() const; + std::size_t sample_rate() const; + std::size_t channels() const; + std::size_t duration_samples() const; + float duration_seconds() const; + +private: + void _init_with_data(); +}; + +template , int> = 0> +inline Ogg load_ogg(I& stream) { + std::vector data = srb2::io::read_to_vec(stream); + return Ogg {std::move(data)}; +} + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_OGG_HPP__ diff --git a/src/audio/ogg_player.cpp b/src/audio/ogg_player.cpp new file mode 100644 index 000000000..d9028dedb --- /dev/null +++ b/src/audio/ogg_player.cpp @@ -0,0 +1,141 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "ogg_player.hpp" + +#include +#include +#include +#include +#include + +using namespace srb2; +using namespace srb2::audio; + +namespace { + +std::optional find_loop_point(const Ogg& ogg) { + OggComment comment = ogg.comment(); + std::size_t rate = ogg.sample_rate(); + for (auto& comment : comment.comments) { + if (comment.find("LOOPPOINT=") == 0) { + std::string_view comment_view(comment); + comment_view.remove_prefix(10); + std::string copied {comment_view}; + + try { + int loop_point = std::stoi(copied); + return loop_point; + } catch (...) { + } + } + + if (comment.find("LOOPMS=") == 0) { + std::string_view comment_view(comment); + comment_view.remove_prefix(7); + std::string copied {comment_view}; + + try { + int loop_ms = std::stoi(copied); + int loop_point = std::round(static_cast(loop_ms) / (rate / 1000.)); + + return loop_point; + } catch (...) { + } + } + } + + return std::nullopt; +} + +} // namespace + +template +OggPlayer::OggPlayer(Ogg&& ogg) noexcept + : playing_(false), looping_(false), loop_point_(std::nullopt), ogg_(std::forward(ogg)) { + loop_point_ = find_loop_point(ogg_); +} + +template +OggPlayer::OggPlayer(OggPlayer&& rhs) noexcept = default; + +template +OggPlayer& OggPlayer::operator=(OggPlayer&& rhs) noexcept = default; + +template +OggPlayer::~OggPlayer() = default; + +template +std::size_t OggPlayer::generate(tcb::span> buffer) { + if (!playing_) + return 0; + + std::size_t total = 0; + do { + std::size_t read = ogg_.get_samples(buffer.subspan(total)); + total += read; + + if (read == 0 && !looping_) { + playing_ = false; + break; + } + + if (read == 0 && loop_point_) { + ogg_.seek(*loop_point_); + } + + if (read == 0 && !loop_point_) { + ogg_.seek(0); + } + } while (total < buffer.size()); + + return total; +} + +template +void OggPlayer::seek(float position_seconds) { + ogg_.seek(static_cast(position_seconds * sample_rate())); +} + +template +void OggPlayer::loop_point_seconds(float loop_point) { + std::size_t rate = sample_rate(); + loop_point = static_cast(std::round(loop_point * rate)); +} + +template +void OggPlayer::reset() { + ogg_.seek(0); +} + +template +std::size_t OggPlayer::sample_rate() const { + return ogg_.sample_rate(); +} + +template +float OggPlayer::duration_seconds() const { + return ogg_.duration_seconds(); +} + +template +std::optional OggPlayer::loop_point_seconds() const { + if (!loop_point_) + return std::nullopt; + + return *loop_point_ / static_cast(sample_rate()); +} + +template +float OggPlayer::position_seconds() const { + return ogg_.position_seconds(); +} + +template class srb2::audio::OggPlayer<1>; +template class srb2::audio::OggPlayer<2>; diff --git a/src/audio/ogg_player.hpp b/src/audio/ogg_player.hpp new file mode 100644 index 000000000..049d39862 --- /dev/null +++ b/src/audio/ogg_player.hpp @@ -0,0 +1,72 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_OGG_SOURCE_HPP__ +#define __SRB2_AUDIO_OGG_SOURCE_HPP__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../io/streams.hpp" +#include "ogg.hpp" +#include "source.hpp" + +namespace srb2::audio { + +template +class OggPlayer final : public Source { + bool playing_; + bool looping_; + std::optional loop_point_; + Ogg ogg_; + +public: + OggPlayer(Ogg&& ogg) noexcept; + + OggPlayer(const OggPlayer&) = delete; + OggPlayer(OggPlayer&& rhs) noexcept; + + OggPlayer& operator=(const OggPlayer&) = delete; + OggPlayer& operator=(OggPlayer&& rhs) noexcept; + + virtual std::size_t generate(tcb::span> buffer) override final; + + bool looping() const { return looping_; } + + void looping(bool looping) { looping_ = looping; } + + bool playing() const { return playing_; } + void playing(bool playing) { playing_ = playing; } + void seek(float position_seconds); + void loop_point_seconds(float loop_point); + + void reset(); + std::size_t sample_rate() const; + + float duration_seconds() const; + std::optional loop_point_seconds() const; + float position_seconds() const; + + ~OggPlayer(); +}; + +extern template class OggPlayer<1>; +extern template class OggPlayer<2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_OGG_SOURCE_HPP__ diff --git a/src/audio/resample.cpp b/src/audio/resample.cpp new file mode 100644 index 000000000..a64d0af13 --- /dev/null +++ b/src/audio/resample.cpp @@ -0,0 +1,81 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "resample.hpp" + +#include +#include +#include +#include +#include + +using std::shared_ptr; +using std::size_t; +using std::vector; + +using namespace srb2::audio; + +template +Resampler::Resampler(std::shared_ptr>&& source, float ratio) + : source_(std::forward>>(source)), ratio_(ratio) { +} + +template +Resampler::Resampler(Resampler&& r) = default; + +template +Resampler::~Resampler() = default; + +template +Resampler& Resampler::operator=(Resampler&& r) = default; + +template +size_t Resampler::generate(tcb::span> buffer) { + if (!source_) + return 0; + + if (ratio_ == 1.f) { + // fast path - generate directly from source + size_t source_read = source_->generate(buffer); + return source_read; + } + + size_t written = 0; + + while (written < buffer.size()) { + // do we need a refill? + if (buf_.size() == 0 || pos_ >= static_cast(buf_.size() - 1)) { + pos_ -= buf_.size(); + last_ = buf_.size() == 0 ? Sample {} : buf_.back(); + buf_.clear(); + buf_.resize(512); + size_t source_read = source_->generate(buf_); + buf_.resize(source_read); + if (source_read == 0) { + break; + } + } + + if (pos_ < 0) { + buffer[written] = (buf_[0] - last_) * pos_frac_ + last_; + advance(ratio_); + written++; + continue; + } + + buffer[written] = (buf_[pos_ + 1] - buf_[pos_]) * pos_frac_ + buf_[pos_]; + advance(ratio_); + written++; + } + + return written; +} + +template class srb2::audio::Resampler<1>; +template class srb2::audio::Resampler<2>; diff --git a/src/audio/resample.hpp b/src/audio/resample.hpp new file mode 100644 index 000000000..7aab4f674 --- /dev/null +++ b/src/audio/resample.hpp @@ -0,0 +1,63 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_RESAMPLE_HPP__ +#define __SRB2_AUDIO_RESAMPLE_HPP__ + +#include +#include +#include +#include +#include + +#include + +#include "sound_chunk.hpp" +#include "source.hpp" + +namespace srb2::audio { + +template +class Resampler : public Source { +public: + Resampler(std::shared_ptr>&& source_, float ratio); + Resampler(const Resampler& r) = delete; + Resampler(Resampler&& r); + virtual ~Resampler(); + + virtual std::size_t generate(tcb::span> buffer); + + Resampler& operator=(const Resampler& r) = delete; + Resampler& operator=(Resampler&& r); + +private: + std::shared_ptr> source_; + float ratio_ {1.f}; + std::vector> buf_; + Sample last_; + int pos_ {0}; + float pos_frac_ {0.f}; + + void advance(float samples) { + pos_frac_ += samples; + float integer; + std::modf(pos_frac_, &integer); + pos_ += integer; + pos_frac_ -= integer; + } + + void refill(); +}; + +extern template class Resampler<1>; +extern template class Resampler<2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_RESAMPLE_HPP__ diff --git a/src/audio/sample.hpp b/src/audio/sample.hpp new file mode 100644 index 000000000..b1f0298b5 --- /dev/null +++ b/src/audio/sample.hpp @@ -0,0 +1,78 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_SAMPLE_HPP__ +#define __SRB2_AUDIO_SAMPLE_HPP__ + +#include + +namespace srb2::audio { + +template +struct Sample { + std::array amplitudes; + + constexpr Sample& operator+=(const Sample& rhs) noexcept { + for (std::size_t i = 0; i < C; i++) { + amplitudes[i] += rhs.amplitudes[i]; + } + return *this; + } + + constexpr Sample& operator*=(float rhs) noexcept { + for (std::size_t i = 0; i < C; i++) { + amplitudes[i] *= rhs; + } + return *this; + } +}; + +template +constexpr Sample operator+(const Sample& lhs, const Sample& rhs) noexcept { + Sample out; + for (std::size_t i = 0; i < C; i++) { + out.amplitudes[i] = lhs.amplitudes[i] + rhs.amplitudes[i]; + } + return out; +} + +template +constexpr Sample operator-(const Sample& lhs, const Sample& rhs) noexcept { + Sample out; + for (std::size_t i = 0; i < C; i++) { + out.amplitudes[i] = lhs.amplitudes[i] - rhs.amplitudes[i]; + } + return out; +} + +template +constexpr Sample operator*(const Sample& lhs, float rhs) noexcept { + Sample out; + for (std::size_t i = 0; i < C; i++) { + out.amplitudes[i] = lhs.amplitudes[i] * rhs; + } + return out; +} + +template +static constexpr float sample_to_float(T sample) noexcept; + +template <> +constexpr float sample_to_float(uint8_t sample) noexcept { + return (sample / 128.f) - 1.f; +} + +template <> +constexpr float sample_to_float(int16_t sample) noexcept { + return sample / 32768.f; +} + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_SAMPLE_HPP__ diff --git a/src/audio/sound_chunk.hpp b/src/audio/sound_chunk.hpp new file mode 100644 index 000000000..7fc0a45eb --- /dev/null +++ b/src/audio/sound_chunk.hpp @@ -0,0 +1,25 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_SOUND_CHUNK_HPP__ +#define __SRB2_AUDIO_SOUND_CHUNK_HPP__ + +#include + +#include "source.hpp" + +namespace srb2::audio { + +struct SoundChunk { + std::vector> samples; +}; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_SOUND_CHUNK_HPP__ diff --git a/src/audio/sound_effect_player.cpp b/src/audio/sound_effect_player.cpp new file mode 100644 index 000000000..a038ee3d8 --- /dev/null +++ b/src/audio/sound_effect_player.cpp @@ -0,0 +1,72 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "sound_effect_player.hpp" + +#include +#include +#include + +using std::shared_ptr; +using std::size_t; + +using srb2::audio::Sample; +using srb2::audio::SoundEffectPlayer; +using srb2::audio::Source; + +size_t SoundEffectPlayer::generate(tcb::span> buffer) { + if (!chunk_) + return 0; + if (position_ >= chunk_->samples.size()) { + return 0; + } + + size_t written = 0; + for (; position_ < chunk_->samples.size() && written < buffer.size(); position_++) { + float mono_sample = chunk_->samples[position_].amplitudes[0]; + + float sep_pan = ((sep_ + 1.f) / 2.f) * (3.14159 / 2.f); + + float left_scale = std::cos(sep_pan); + float right_scale = std::sin(sep_pan); + buffer[written] = {mono_sample * volume_ * left_scale, mono_sample * volume_ * right_scale}; + written += 1; + } + return written; +} + +void SoundEffectPlayer::start(const SoundChunk* chunk, float volume, float sep) { + this->update(volume, sep); + position_ = 0; + chunk_ = chunk; +} + +void SoundEffectPlayer::update(float volume, float sep) { + volume_ = volume; + sep_ = sep; +} + +void SoundEffectPlayer::reset() { + position_ = 0; + chunk_ = nullptr; +} + +bool SoundEffectPlayer::finished() const { + if (!chunk_) + return true; + if (position_ >= chunk_->samples.size()) + return true; + return false; +} + +bool SoundEffectPlayer::is_playing_chunk(const SoundChunk* chunk) const { + return chunk_ == chunk; +} + +SoundEffectPlayer::~SoundEffectPlayer() = default; diff --git a/src/audio/sound_effect_player.hpp b/src/audio/sound_effect_player.hpp new file mode 100644 index 000000000..99f5edb9e --- /dev/null +++ b/src/audio/sound_effect_player.hpp @@ -0,0 +1,46 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__ +#define __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__ + +#include + +#include + +#include "sound_chunk.hpp" +#include "source.hpp" + +namespace srb2::audio { + +class SoundEffectPlayer : public Source<2> { +public: + virtual std::size_t generate(tcb::span> buffer) override final; + + virtual ~SoundEffectPlayer() final; + + void start(const SoundChunk* chunk, float volume, float sep); + void update(float volume, float sep); + void reset(); + bool finished() const; + + bool is_playing_chunk(const SoundChunk* chunk) const; + +private: + float volume_; + float sep_; + + std::size_t position_; + + const SoundChunk* chunk_; +}; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_SOUND_EFFECT_PLAYER_HPP__ diff --git a/src/audio/source.hpp b/src/audio/source.hpp new file mode 100644 index 000000000..ea4be8761 --- /dev/null +++ b/src/audio/source.hpp @@ -0,0 +1,36 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_SOURCE_HPP__ +#define __SRB2_AUDIO_SOURCE_HPP__ + +#include + +#include + +#include "sample.hpp" + +namespace srb2::audio { + +template +class Source { +public: + virtual std::size_t generate(tcb::span> buffer) = 0; + + virtual ~Source() = default; +}; + +// This audio DSP is Stereo, FP32 system-endian, 44100 Hz internally. +// Conversions to other formats should be handled elsewhere. + +constexpr const std::size_t kSampleRate = 44100; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_SOURCE_HPP__ diff --git a/src/audio/wav.cpp b/src/audio/wav.cpp new file mode 100644 index 000000000..31f3a0468 --- /dev/null +++ b/src/audio/wav.cpp @@ -0,0 +1,264 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "wav.hpp" + +#include +#include +#include + +using namespace srb2; +using srb2::audio::Wav; + +namespace { + +constexpr const uint32_t kMagicRIFF = 0x46464952; +constexpr const uint32_t kMagicWAVE = 0x45564157; +constexpr const uint32_t kMagicFmt = 0x20746d66; +constexpr const uint32_t kMagicData = 0x61746164; + +constexpr const uint16_t kFormatPcm = 1; + +constexpr const std::size_t kRiffHeaderLength = 8; + +struct RiffHeader { + uint32_t magic; + std::size_t filesize; +}; + +struct TagHeader { + uint32_t type; + std::size_t length; +}; + +struct FmtTag { + uint16_t format; + uint16_t channels; + uint32_t rate; + uint32_t bytes_per_second; + uint32_t bytes_per_sample; + uint16_t bit_width; +}; + +struct DataTag {}; + +RiffHeader parse_riff_header(io::SpanStream& stream) { + if (io::remaining(stream) < kRiffHeaderLength) + throw std::runtime_error("insufficient bytes remaining in stream"); + + RiffHeader ret; + ret.magic = io::read_uint32(stream); + ret.filesize = io::read_uint32(stream); + return ret; +} + +TagHeader parse_tag_header(io::SpanStream& stream) { + if (io::remaining(stream) < 8) + throw std::runtime_error("insufficient bytes remaining in stream"); + + TagHeader header; + header.type = io::read_uint32(stream); + header.length = io::read_uint32(stream); + return header; +} + +FmtTag parse_fmt_tag(io::SpanStream& stream) { + if (io::remaining(stream) < 16) + throw std::runtime_error("insufficient bytes in stream"); + + FmtTag tag; + tag.format = io::read_uint16(stream); + tag.channels = io::read_uint16(stream); + tag.rate = io::read_uint32(stream); + tag.bytes_per_second = io::read_uint32(stream); + tag.bytes_per_sample = io::read_uint16(stream); + tag.bit_width = io::read_uint16(stream); + + return tag; +} + +template +void visit_tag(Visitor& visitor, io::SpanStream& stream, const TagHeader& header) { + if (io::remaining(stream) < header.length) + throw std::runtime_error("insufficient bytes in stream"); + + const io::StreamSize start = stream.seek(io::SeekFrom::kCurrent, 0); + const io::StreamSize dest = start + header.length; + + switch (header.type) { + case kMagicFmt: + { + FmtTag fmt_tag {parse_fmt_tag(stream)}; + visitor(fmt_tag); + break; + } + case kMagicData: + { + DataTag data_tag; + visitor(data_tag); + break; + } + default: + // Unrecognized tags are ignored. + break; + } + + stream.seek(io::SeekFrom::kStart, dest); +} + +std::vector read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count) { + std::vector samples; + samples.reserve(count); + for (std::size_t i = 0; i < count; i++) { + samples.push_back(io::read_uint8(stream)); + } + return samples; +} + +std::vector read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count) { + std::vector samples; + samples.reserve(count); + for (std::size_t i = 0; i < count; i++) { + samples.push_back(io::read_int16(stream)); + } + return samples; +} + +template +struct OverloadVisitor : Ts... { + using Ts::operator()...; +}; + +template +OverloadVisitor(Ts...) -> OverloadVisitor; + +} // namespace + +Wav::Wav() = default; + +Wav::Wav(tcb::span data) { + io::SpanStream stream {data}; + + auto [magic, filesize] = parse_riff_header(stream); + + if (magic != kMagicRIFF) { + throw std::runtime_error("invalid RIFF magic"); + } + + if (io::remaining(stream) < filesize) { + throw std::runtime_error("insufficient data in stream for RIFF's reported filesize"); + } + + const io::StreamSize riff_end = stream.seek(io::SeekFrom::kCurrent, 0) + filesize; + + uint32_t type = io::read_uint32(stream); + if (type != kMagicWAVE) { + throw std::runtime_error("RIFF in stream is not a WAVE"); + } + + std::optional read_fmt; + std::variant, std::vector> interleaved_samples; + + while (stream.seek(io::SeekFrom::kCurrent, 0) < riff_end) { + TagHeader tag_header {parse_tag_header(stream)}; + if (io::remaining(stream) < tag_header.length) { + throw std::runtime_error("WAVE tag length exceeds stream length"); + } + + auto tag_visitor = OverloadVisitor { + [&](const FmtTag& fmt) { + if (read_fmt) { + throw std::runtime_error("WAVE has multiple 'fmt' tags"); + } + if (fmt.format != kFormatPcm) { + throw std::runtime_error("Unsupported WAVE format (only PCM is supported)"); + } + read_fmt = fmt; + }, + [&](const DataTag& data) { + if (!read_fmt) { + throw std::runtime_error("unable to read data tag because no fmt tag was read"); + } + + if (tag_header.length % read_fmt->bytes_per_sample != 0) { + throw std::runtime_error("data tag length not divisible by bytes_per_sample"); + } + + const std::size_t sample_count = tag_header.length / read_fmt->bytes_per_sample; + + switch (read_fmt->bit_width) { + case 8: + interleaved_samples = std::move(read_uint8_samples_from_stream(stream, sample_count)); + break; + case 16: + interleaved_samples = std::move(read_int16_samples_from_stream(stream, sample_count)); + break; + default: + throw std::runtime_error("unsupported sample amplitude bit width"); + } + }}; + + visit_tag(tag_visitor, stream, tag_header); + } + + if (!read_fmt) { + throw std::runtime_error("WAVE did not have a fmt tag"); + } + + interleaved_samples_ = std::move(interleaved_samples); + channels_ = read_fmt->channels; + sample_rate_ = read_fmt->rate; +} + +namespace { + +template +std::size_t read_samples(std::size_t channels, + std::size_t offset, + const std::vector& samples, + tcb::span> buffer) noexcept { + const std::size_t offset_interleaved = offset * channels; + const std::size_t samples_size = samples.size(); + const std::size_t buffer_size = buffer.size(); + + if (offset_interleaved >= samples_size) { + return 0; + } + + const std::size_t remainder = (samples_size - offset_interleaved) / channels; + const std::size_t samples_to_read = std::min(buffer_size, remainder); + + for (std::size_t i = 0; i < samples_to_read; i++) { + buffer[i].amplitudes[0] = 0.f; + for (std::size_t j = 0; j < channels; j++) { + buffer[i].amplitudes[0] += audio::sample_to_float(samples[i * channels + j + offset_interleaved]); + } + buffer[i].amplitudes[0] /= static_cast(channels); + } + + return samples_to_read; +} + +} // namespace + +std::size_t Wav::get_samples(std::size_t offset, tcb::span> buffer) const noexcept { + auto samples_visitor = OverloadVisitor { + [&](const std::vector& samples) { return read_samples(channels(), offset, samples, buffer); }, + [&](const std::vector& samples) { + return read_samples(channels(), offset, samples, buffer); + }}; + + return std::visit(samples_visitor, interleaved_samples_); +} + +std::size_t Wav::interleaved_length() const noexcept { + auto samples_visitor = OverloadVisitor {[](const std::vector& samples) { return samples.size(); }, + [](const std::vector& samples) { return samples.size(); }}; + return std::visit(samples_visitor, interleaved_samples_); +} diff --git a/src/audio/wav.hpp b/src/audio/wav.hpp new file mode 100644 index 000000000..e571969e7 --- /dev/null +++ b/src/audio/wav.hpp @@ -0,0 +1,51 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_WAV_HPP__ +#define __SRB2_AUDIO_WAV_HPP__ + +#include +#include +#include +#include +#include + +#include + +#include "../io/streams.hpp" +#include "sample.hpp" + +namespace srb2::audio { + +class Wav final { + std::variant, std::vector> interleaved_samples_; + std::size_t channels_ = 1; + std::size_t sample_rate_ = 44100; + +public: + Wav(); + + explicit Wav(tcb::span data); + + std::size_t get_samples(std::size_t offset, tcb::span> buffer) const noexcept; + std::size_t interleaved_length() const noexcept; + std::size_t length() const noexcept { return interleaved_length() / channels(); }; + std::size_t channels() const noexcept { return channels_; }; + std::size_t sample_rate() const noexcept { return sample_rate_; }; +}; + +template , int> = 0> +inline Wav load_wav(I& stream) { + std::vector data = srb2::io::read_to_vec(stream); + return Wav {data}; +} + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_WAV_HPP__ diff --git a/src/audio/wav_player.cpp b/src/audio/wav_player.cpp new file mode 100644 index 000000000..64f9831f0 --- /dev/null +++ b/src/audio/wav_player.cpp @@ -0,0 +1,45 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "wav_player.hpp" + +using namespace srb2; + +using srb2::audio::WavPlayer; + +WavPlayer::WavPlayer() : WavPlayer(audio::Wav {}) { +} + +WavPlayer::WavPlayer(const WavPlayer& rhs) = default; + +WavPlayer::WavPlayer(WavPlayer&& rhs) noexcept = default; + +WavPlayer& WavPlayer::operator=(const WavPlayer& rhs) = default; + +WavPlayer& WavPlayer::operator=(WavPlayer&& rhs) noexcept = default; + +WavPlayer::WavPlayer(audio::Wav&& wav) noexcept : wav_(std::forward(wav)), position_(0), looping_(false) { +} + +std::size_t WavPlayer::generate(tcb::span> buffer) { + std::size_t samples_read = 0; + while (samples_read < buffer.size()) { + const std::size_t read_this_time = wav_.get_samples(position_, buffer.subspan(samples_read)); + position_ += read_this_time; + samples_read += read_this_time; + + if (position_ > wav_.length() && looping_) { + position_ = 0; + } + if (read_this_time == 0 && !looping_) { + break; + } + } + return samples_read; +} diff --git a/src/audio/wav_player.hpp b/src/audio/wav_player.hpp new file mode 100644 index 000000000..dc6a98864 --- /dev/null +++ b/src/audio/wav_player.hpp @@ -0,0 +1,49 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_WAV_PLAYER_HPP__ +#define __SRB2_AUDIO_WAV_PLAYER_HPP__ + +#include + +#include + +#include "source.hpp" +#include "wav.hpp" + +namespace srb2::audio { + +class WavPlayer final : public Source<1> { + Wav wav_; + std::size_t position_; + bool looping_; + +public: + WavPlayer(); + WavPlayer(const WavPlayer& rhs); + WavPlayer(WavPlayer&& rhs) noexcept; + + WavPlayer& operator=(const WavPlayer& rhs); + WavPlayer& operator=(WavPlayer&& rhs) noexcept; + + WavPlayer(Wav&& wav) noexcept; + + virtual std::size_t generate(tcb::span> buffer) override; + + bool looping() const { return looping_; } + void looping(bool looping) { looping_ = looping; } + + std::size_t sample_rate() const { return wav_.sample_rate(); } + float duration_seconds() const { return wav_.length() / static_cast(wav_.sample_rate()); } + void seek(float seconds) { position_ = seconds * wav_.sample_rate(); } +}; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_WAV_PLAYER_HPP__ diff --git a/src/audio/xmp.cpp b/src/audio/xmp.cpp new file mode 100644 index 000000000..9e88f2a7f --- /dev/null +++ b/src/audio/xmp.cpp @@ -0,0 +1,167 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "xmp.hpp" + +#include + +#include "../cxxutil.hpp" + +using namespace srb2; +using namespace srb2::audio; + +XmpException::XmpException(int code) : code_(code) { +} + +const char* XmpException::what() const noexcept { + switch (code_) { + case -XMP_ERROR_INTERNAL: + return "XMP_ERROR_INTERNAL"; + case -XMP_ERROR_FORMAT: + return "XMP_ERROR_FORMAT"; + case -XMP_ERROR_LOAD: + return "XMP_ERROR_LOAD"; + case -XMP_ERROR_DEPACK: + return "XMP_ERROR_DEPACK"; + case -XMP_ERROR_SYSTEM: + return "XMP_ERROR_SYSTEM"; + case -XMP_ERROR_INVALID: + return "XMP_ERROR_INVALID"; + case -XMP_ERROR_STATE: + return "XMP_ERROR_STATE"; + default: + return "unknown"; + } +} + +template +Xmp::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(false) { +} + +template +Xmp::Xmp(std::vector data) + : data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false) { + _init(); +} + +template +Xmp::Xmp(tcb::span data) + : data_(data.begin(), data.end()), instance_(nullptr), module_loaded_(false), looping_(false) { + _init(); +} + +template +Xmp::Xmp(Xmp&& rhs) noexcept : Xmp() { + std::swap(data_, rhs.data_); + std::swap(instance_, rhs.instance_); + std::swap(module_loaded_, rhs.module_loaded_); + std::swap(looping_, rhs.looping_); +} + +template +Xmp& Xmp::operator=(Xmp&& rhs) noexcept { + std::swap(data_, rhs.data_); + std::swap(instance_, rhs.instance_); + std::swap(module_loaded_, rhs.module_loaded_); + std::swap(looping_, rhs.looping_); + + return *this; +}; + +template +Xmp::~Xmp() { + if (instance_) { + xmp_free_context(instance_); + instance_ = nullptr; + } +} + +template +std::size_t Xmp::play_buffer(tcb::span> buffer) { + SRB2_ASSERT(instance_ != nullptr); + SRB2_ASSERT(module_loaded_ == true); + + int result = xmp_play_buffer(instance_, buffer.data(), buffer.size_bytes(), !looping_); + + if (result == -XMP_END) + return 0; + + if (result != 0) + throw XmpException(result); + + return buffer.size(); +} + +template +void Xmp::reset() { + SRB2_ASSERT(instance_ != nullptr); + SRB2_ASSERT(module_loaded_ == true); + + xmp_restart_module(instance_); +} + +template +float Xmp::duration_seconds() const { + SRB2_ASSERT(instance_ != nullptr); + SRB2_ASSERT(module_loaded_ == true); + + xmp_frame_info info; + xmp_get_frame_info(instance_, &info); + return static_cast(info.total_time) / 1000.f; +} + +template +void Xmp::seek(int position_ms) { + SRB2_ASSERT(instance_ != nullptr); + SRB2_ASSERT(module_loaded_ == true); + + int err = xmp_seek_time(instance_, position_ms); + if (err != 0) + throw XmpException(err); +} + +template +void Xmp::_init() { + if (instance_) + return; + + if (data_.size() >= std::numeric_limits::max()) + throw std::logic_error("Buffer is too large for xmp"); + if (data_.size() == 0) + throw std::logic_error("Insufficient data from stream"); + + instance_ = xmp_create_context(); + if (instance_ == nullptr) { + throw std::bad_alloc(); + } + + int result = xmp_load_module_from_memory(instance_, data_.data(), data_.size()); + if (result != 0) { + xmp_free_context(instance_); + instance_ = nullptr; + throw XmpException(result); + } + module_loaded_ = true; + + int flags = 0; + if constexpr (C == 1) { + flags |= XMP_FORMAT_MONO; + } + result = xmp_start_player(instance_, 44100, flags); + if (result != 0) { + xmp_release_module(instance_); + module_loaded_ = false; + xmp_free_context(instance_); + instance_ = nullptr; + throw XmpException(result); + } +} + +template class srb2::audio::Xmp<1>; +template class srb2::audio::Xmp<2>; diff --git a/src/audio/xmp.hpp b/src/audio/xmp.hpp new file mode 100644 index 000000000..a5b443bfa --- /dev/null +++ b/src/audio/xmp.hpp @@ -0,0 +1,78 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + + #ifndef __SRB2_AUDIO_XMP_HPP__ +#define __SRB2_AUDIO_XMP_HPP__ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../io/streams.hpp" + +namespace srb2::audio { + +class XmpException : public std::exception { + int code_; + +public: + XmpException(int code); + virtual const char* what() const noexcept override final; +}; + +template +class Xmp final { + std::vector data_; + xmp_context instance_; + bool module_loaded_; + bool looping_; + +public: + Xmp(); + + explicit Xmp(std::vector data); + explicit Xmp(tcb::span data); + + Xmp(const Xmp&) = delete; + Xmp(Xmp&& rhs) noexcept; + + Xmp& operator=(const Xmp&) = delete; + Xmp& operator=(Xmp&& rhs) noexcept; + + std::size_t play_buffer(tcb::span> buffer); + bool looping() const { return looping_; }; + void looping(bool looping) { looping_ = looping; }; + void reset(); + float duration_seconds() const; + void seek(int position_ms); + + ~Xmp(); + +private: + void _init(); +}; + +extern template class Xmp<1>; +extern template class Xmp<2>; + +template , int> = 0> +inline Xmp load_xmp(I& stream) { + std::vector data = srb2::io::read_to_vec(stream); + return Xmp {std::move(data)}; +} + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_XMP_HPP__ diff --git a/src/audio/xmp_player.cpp b/src/audio/xmp_player.cpp new file mode 100644 index 000000000..a08ee8bb5 --- /dev/null +++ b/src/audio/xmp_player.cpp @@ -0,0 +1,57 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "xmp_player.hpp" + +#include + +using namespace srb2; +using namespace srb2::audio; + +template +XmpPlayer::XmpPlayer(Xmp&& xmp) : xmp_(std::move(xmp)), buf_() { +} + +template +XmpPlayer::XmpPlayer(XmpPlayer&& rhs) noexcept = default; + +template +XmpPlayer& XmpPlayer::operator=(XmpPlayer&& rhs) noexcept = default; + +template +XmpPlayer::~XmpPlayer() = default; + +template +std::size_t XmpPlayer::generate(tcb::span> buffer) { + buf_.resize(buffer.size()); + std::size_t read = xmp_.play_buffer(tcb::make_span(buf_)); + buf_.resize(read); + std::size_t ret = std::min(buffer.size(), buf_.size()); + + for (std::size_t i = 0; i < ret; i++) { + for (std::size_t j = 0; j < C; j++) { + buffer[i].amplitudes[j] = buf_[i][j] / 32768.f; + } + } + + return ret; +} + +template +float XmpPlayer::duration_seconds() const { + return xmp_.duration_seconds(); +} + +template +void XmpPlayer::seek(float position_seconds) { + xmp_.seek(static_cast(std::round(position_seconds * 1000.f))); +} + +template class srb2::audio::XmpPlayer<1>; +template class srb2::audio::XmpPlayer<2>; diff --git a/src/audio/xmp_player.hpp b/src/audio/xmp_player.hpp new file mode 100644 index 000000000..5829dbd8a --- /dev/null +++ b/src/audio/xmp_player.hpp @@ -0,0 +1,48 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_AUDIO_XMP_PLAYER_HPP__ +#define __SRB2_AUDIO_XMP_PLAYER_HPP__ + +#include "source.hpp" +#include "xmp.hpp" + +namespace srb2::audio { + +template +class XmpPlayer final : public Source { + Xmp xmp_; + std::vector> buf_; + +public: + XmpPlayer(Xmp&& xmp); + + XmpPlayer(const XmpPlayer&) = delete; + XmpPlayer(XmpPlayer&& rhs) noexcept; + + XmpPlayer& operator=(const XmpPlayer&) = delete; + XmpPlayer& operator=(XmpPlayer&& rhs) noexcept; + + ~XmpPlayer(); + + virtual std::size_t generate(tcb::span> buffer) override final; + + bool looping() { return xmp_.looping(); }; + void looping(bool looping) { xmp_.looping(looping); } + void reset() { xmp_.reset(); } + float duration_seconds() const; + void seek(float position_seconds); +}; + +extern template class XmpPlayer<1>; +extern template class XmpPlayer<2>; + +} // namespace srb2::audio + +#endif // __SRB2_AUDIO_XMP_PLAYER_HPP__ diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt index 61d2e1a8f..669f4710f 100644 --- a/src/sdl/CMakeLists.txt +++ b/src/sdl/CMakeLists.txt @@ -1,7 +1,7 @@ # Declare SDL2 interface sources target_sources(SRB2SDL2 PRIVATE - mixer_sound.c + new_sound.cpp ogl_sdl.c i_threads.c i_net.c diff --git a/src/sdl/new_sound.cpp b/src/sdl/new_sound.cpp new file mode 100644 index 000000000..3052ee587 --- /dev/null +++ b/src/sdl/new_sound.cpp @@ -0,0 +1,665 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2022-2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include +#include +#include + +#include + +#include "../audio/chunk_load.hpp" +#include "../audio/gain.hpp" +#include "../audio/mixer.hpp" +#include "../audio/music_player.hpp" +#include "../audio/sound_chunk.hpp" +#include "../audio/sound_effect_player.hpp" +#include "../cxxutil.hpp" +#include "../io/streams.hpp" + +#include "../doomdef.h" +#include "../i_sound.h" +#include "../s_sound.h" +#include "../sounds.h" +#include "../w_wad.h" +#include "../z_zone.h" + +using std::make_shared; +using std::make_unique; +using std::shared_ptr; +using std::unique_ptr; +using std::vector; + +using srb2::audio::Gain; +using srb2::audio::Mixer; +using srb2::audio::MusicPlayer; +using srb2::audio::Sample; +using srb2::audio::SoundChunk; +using srb2::audio::SoundEffectPlayer; +using srb2::audio::Source; +using namespace srb2; +using namespace srb2::io; + +// extern in i_sound.h +UINT8 sound_started = false; + +static unique_ptr> master; +static shared_ptr> mixer_sound_effects; +static shared_ptr> mixer_music; +static shared_ptr music_player; +static shared_ptr> gain_sound_effects; +static shared_ptr> gain_music; + +static vector> sound_effect_channels; + +static void (*music_fade_callback)(); + +void* I_GetSfx(sfxinfo_t* sfx) { + if (sfx->lumpnum == LUMPERROR) + sfx->lumpnum = S_GetSfxLumpNum(sfx); + sfx->length = W_LumpLength(sfx->lumpnum); + + std::byte* lump = static_cast(W_CacheLumpNum(sfx->lumpnum, PU_SOUND)); + auto _ = srb2::finally([lump]() { Z_Free(lump); }); + + tcb::span data_span(lump, sfx->length); + std::optional chunk = srb2::audio::try_load_chunk(data_span); + + if (!chunk) + return nullptr; + + SoundChunk* heap_chunk = new SoundChunk {std::move(*chunk)}; + + return heap_chunk; +} + +void I_FreeSfx(sfxinfo_t* sfx) { + if (sfx->data) { + SoundChunk* chunk = static_cast(sfx->data); + auto _ = srb2::finally([chunk]() { delete chunk; }); + + // Stop any channels playing this chunk + for (auto& player : sound_effect_channels) { + if (player->is_playing_chunk(chunk)) { + player->reset(); + } + } + } + sfx->data = nullptr; + sfx->lumpnum = LUMPERROR; +} + +namespace { + +class SdlAudioLockHandle { +public: + SdlAudioLockHandle() { SDL_LockAudio(); } + ~SdlAudioLockHandle() { SDL_UnlockAudio(); } +}; + +void audio_callback(void* userdata, Uint8* buffer, int len) { + // The SDL Audio lock is implied to be held during callback. + + try { + Sample<2>* float_buffer = reinterpret_cast*>(buffer); + size_t float_len = len / 8; + + for (size_t i = 0; i < float_len; i++) { + float_buffer[i] = Sample<2> {0.f, 0.f}; + } + + if (!master) + return; + + master->generate(tcb::span {float_buffer, float_len}); + + for (size_t i = 0; i < float_len; i++) { + float_buffer[i] = { + std::clamp(float_buffer[i].amplitudes[0], -1.f, 1.f), + std::clamp(float_buffer[i].amplitudes[1], -1.f, 1.f), + }; + } + } catch (...) { + } + + return; +} + +void initialize_sound() { + if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { + CONS_Alert(CONS_ERROR, "Error initializing SDL Audio: %s\n", SDL_GetError()); + return; + } + + SDL_AudioSpec desired; + desired.format = AUDIO_F32SYS; + desired.channels = 2; + desired.samples = 1024; + desired.freq = 44100; + desired.callback = audio_callback; + + if (SDL_OpenAudio(&desired, NULL) < 0) { + CONS_Alert(CONS_ERROR, "Failed to open SDL Audio device: %s\n", SDL_GetError()); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return; + } + + SDL_PauseAudio(SDL_FALSE); + + { + SdlAudioLockHandle _; + + master = make_unique>(); + mixer_sound_effects = make_shared>(); + mixer_music = make_shared>(); + music_player = make_shared(); + gain_sound_effects = make_shared>(); + gain_music = make_shared>(); + gain_sound_effects->bind(mixer_sound_effects); + gain_music->bind(mixer_music); + master->add_source(gain_sound_effects); + master->add_source(gain_music); + mixer_music->add_source(music_player); + for (size_t i = 0; i < static_cast(cv_numChannels.value); i++) { + shared_ptr player = make_shared(); + sound_effect_channels.push_back(player); + mixer_sound_effects->add_source(player); + } + } + + sound_started = true; +} + +} // namespace + +void I_StartupSound(void) { + if (!sound_started) + initialize_sound(); +} + +void I_ShutdownSound(void) { + SdlAudioLockHandle _; + + for (auto& channel : sound_effect_channels) { + *channel = audio::SoundEffectPlayer(); + } +} + +void I_UpdateSound(void) { + // The SDL audio lock is re-entrant, so it is safe to lock twice + // for the "fade to stop music" callback later. + SdlAudioLockHandle _; + + if (music_fade_callback && !music_player->fading()) { + auto old_callback = music_fade_callback; + music_fade_callback = nullptr; + (old_callback()); + } + return; +} + +// +// SFX I/O +// + +INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel) { + (void) pitch; + (void) priority; + + SdlAudioLockHandle _; + + if (channel >= 0 && static_cast(channel) >= sound_effect_channels.size()) + return -1; + + shared_ptr player_channel; + if (channel < 0) { + // find a free sfx channel + for (size_t i = 0; i < sound_effect_channels.size(); i++) { + if (sound_effect_channels[i]->finished()) { + player_channel = sound_effect_channels[i]; + channel = i; + break; + } + } + } else { + player_channel = sound_effect_channels[channel]; + } + + if (!player_channel) + return -1; + + SoundChunk* chunk = static_cast(S_sfx[id].data); + if (chunk == nullptr) + return -1; + + float vol_float = static_cast(vol) / 255.f; + float sep_float = static_cast(sep) / 127.f - 1.f; + + player_channel->start(chunk, vol_float, sep_float); + + return channel; +} + +void I_StopSound(INT32 handle) { + SdlAudioLockHandle _; + + if (sound_effect_channels.empty()) + return; + + if (handle < 0) + return; + + size_t index = handle; + + if (index >= sound_effect_channels.size()) + return; + + sound_effect_channels[index]->reset(); +} + +boolean I_SoundIsPlaying(INT32 handle) { + SdlAudioLockHandle _; + + // Handle is channel index + if (sound_effect_channels.empty()) + return 0; + + if (handle < 0) + return 0; + + size_t index = handle; + + if (index >= sound_effect_channels.size()) + return 0; + + return sound_effect_channels[index]->finished() ? 0 : 1; +} + +void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch) { + (void) pitch; + + SdlAudioLockHandle _; + + if (sound_effect_channels.empty()) + return; + + if (handle < 0) + return; + + size_t index = handle; + + if (index >= sound_effect_channels.size()) + return; + + shared_ptr& channel = sound_effect_channels[index]; + if (!channel->finished()) { + float vol_float = static_cast(vol) / 255.f; + float sep_float = static_cast(sep) / 127.f - 1.f; + channel->update(vol_float, sep_float); + } +} + +void I_SetSfxVolume(int volume) { + SdlAudioLockHandle _; + float vol = static_cast(volume) / 100.f; + + if (gain_sound_effects) { + gain_sound_effects->gain(vol * vol * vol); + } +} + +/// ------------------------ +// MUSIC SYSTEM +/// ------------------------ + +void I_InitMusic(void) { + if (!sound_started) + initialize_sound(); + + SdlAudioLockHandle _; + + *music_player = audio::MusicPlayer(); +} + +void I_ShutdownMusic(void) { + SdlAudioLockHandle _; + + *music_player = audio::MusicPlayer(); +} + +/// ------------------------ +// MUSIC PROPERTIES +/// ------------------------ + +musictype_t I_SongType(void) { + if (!music_player) + return MU_NONE; + + SdlAudioLockHandle _; + + std::optional music_type = music_player->music_type(); + + if (music_type == std::nullopt) { + return MU_NONE; + } + + switch (*music_type) { + case audio::MusicType::kOgg: + return MU_OGG; + case audio::MusicType::kGme: + return MU_GME; + case audio::MusicType::kMod: + return MU_MOD; + default: + return MU_NONE; + } +} + +boolean I_SongPlaying(void) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + return music_player->music_type().has_value(); +} + +boolean I_SongPaused(void) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + return !music_player->playing(); +} + +/// ------------------------ +// MUSIC EFFECTS +/// ------------------------ + +boolean I_SetSongSpeed(float speed) { + (void) speed; + return false; +} + +/// ------------------------ +// MUSIC SEEKING +/// ------------------------ + +UINT32 I_GetSongLength(void) { + if (!music_player) + return 0; + + SdlAudioLockHandle _; + + std::optional duration = music_player->duration_seconds(); + + if (!duration) + return 0; + + return static_cast(std::round(*duration * 1000.f)); +} + +boolean I_SetSongLoopPoint(UINT32 looppoint) { + if (!music_player) + return 0; + + SdlAudioLockHandle _; + + if (music_player->music_type() == audio::MusicType::kOgg) { + music_player->loop_point_seconds(looppoint / 1000.f); + return true; + } + + return false; +} + +UINT32 I_GetSongLoopPoint(void) { + if (!music_player) + return 0; + + SdlAudioLockHandle _; + + std::optional loop_point_seconds = music_player->loop_point_seconds(); + + if (!loop_point_seconds) + return 0; + + return static_cast(std::round(*loop_point_seconds * 1000.f)); +} + +boolean I_SetSongPosition(UINT32 position) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + music_player->seek(position / 1000.f); + return true; +} + +UINT32 I_GetSongPosition(void) { + if (!music_player) + return 0; + + SdlAudioLockHandle _; + + std::optional position_seconds = music_player->position_seconds(); + + if (!position_seconds) + return 0; + + return static_cast(std::round(*position_seconds * 1000.f)); +} + +void I_UpdateSongLagThreshold(void) { +} + +void I_UpdateSongLagConditions(void) { +} + +/// ------------------------ +// MUSIC PLAYBACK +/// ------------------------ + +namespace { +void print_walk_ex_stack(const std::exception& ex) { + CONS_Alert(CONS_WARNING, " Caused by: %s\n", ex.what()); + try { + std::rethrow_if_nested(ex); + } catch (const std::exception& ex) { + print_walk_ex_stack(ex); + } +} + +void print_ex(const std::exception& ex) { + CONS_Alert(CONS_WARNING, "Exception loading music: %s\n", ex.what()); + try { + std::rethrow_if_nested(ex); + } catch (const std::exception& ex) { + print_walk_ex_stack(ex); + } +} +} // namespace + +boolean I_LoadSong(char* data, size_t len) { + if (!music_player) + return false; + + tcb::span data_span(reinterpret_cast(data), len); + audio::MusicPlayer new_player; + try { + new_player = audio::MusicPlayer {data_span}; + } catch (const std::exception& ex) { + print_ex(ex); + return false; + } + + if (music_fade_callback && music_player->fading()) { + auto old_callback = music_fade_callback; + music_fade_callback = nullptr; + (old_callback)(); + } + + SdlAudioLockHandle _; + + try { + *music_player = std::move(new_player); + } catch (const std::exception& ex) { + print_ex(ex); + return false; + } + + return true; +} + +void I_UnloadSong(void) { + if (!music_player) + return; + + if (music_fade_callback && music_player->fading()) { + auto old_callback = music_fade_callback; + music_fade_callback = nullptr; + (old_callback)(); + } + + SdlAudioLockHandle _; + + *music_player = audio::MusicPlayer(); +} + +boolean I_PlaySong(boolean looping) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + music_player->play(looping); + + return true; +} + +void I_StopSong(void) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + music_player->stop(); +} + +void I_PauseSong(void) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + music_player->pause(); +} + +void I_ResumeSong(void) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + music_player->unpause(); +} + +void I_SetMusicVolume(int volume) { + float vol = static_cast(volume) / 100.f; + + if (gain_music) { + gain_music->gain(vol * vol * vol); + } +} + +boolean I_SetSongTrack(int track) { + (void) track; + return false; +} + +/// ------------------------ +// MUSIC FADING +/// ------------------------ + +void I_SetInternalMusicVolume(UINT8 volume) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + float gain = volume / 100.f; + music_player->internal_gain(gain); +} + +void I_StopFadingSong(void) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + music_player->stop_fade(); +} + +boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void)) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + float source_gain = source_volume / 100.f; + float target_gain = target_volume / 100.f; + float seconds = ms / 1000.f; + + music_player->fade_from_to(source_gain, target_gain, seconds); + + if (music_fade_callback) + music_fade_callback(); + music_fade_callback = callback; + + return true; +} + +boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void)) { + if (!music_player) + return false; + + SdlAudioLockHandle _; + + float target_gain = target_volume / 100.f; + float seconds = ms / 1000.f; + + music_player->fade_to(target_gain, seconds); + + if (music_fade_callback) + music_fade_callback(); + music_fade_callback = callback; + + return true; +} + +static void stop_song_cb(void) { + if (!music_player) + return; + + SdlAudioLockHandle _; + + music_player->stop(); +} + +boolean I_FadeOutStopSong(UINT32 ms) { + return I_FadeSong(0.f, ms, stop_song_cb); +} + +boolean I_FadeInPlaySong(UINT32 ms, boolean looping) { + if (I_PlaySong(looping)) + return I_FadeSongFromVolume(100, 0, ms, nullptr); + else + return false; +} From b95fd459b9a7285dd1139197abe6f6a131dd7ef8 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sun, 1 Jan 2023 15:16:31 -0600 Subject: [PATCH 111/128] cmake: Remove SDL2_mixer and OpenMPT Libraries superceded by libxmp-lite and new mixer. --- CMakeLists.txt | 2 - cmake/Modules/FindOPENMPT.cmake | 33 --- cmake/Modules/FindSDL2_mixer.cmake | 44 ---- src/CMakeLists.txt | 3 - src/sdl/CMakeLists.txt | 4 +- thirdparty/CMakeLists.txt | 317 ----------------------------- thirdparty/openmpt_svn_version.h | 10 - 7 files changed, 2 insertions(+), 411 deletions(-) delete mode 100644 cmake/Modules/FindOPENMPT.cmake delete mode 100644 cmake/Modules/FindSDL2_mixer.cmake delete mode 100644 thirdparty/openmpt_svn_version.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dfd8e6f9..151a7a389 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,9 +130,7 @@ if("${SRB2_CONFIG_SYSTEM_LIBRARIES}") find_package(ZLIB REQUIRED) find_package(PNG REQUIRED) find_package(SDL2 REQUIRED) - find_package(SDL2_mixer REQUIRED) find_package(CURL REQUIRED) - find_package(OPENMPT REQUIRED) find_package(GME REQUIRED) endif() diff --git a/cmake/Modules/FindOPENMPT.cmake b/cmake/Modules/FindOPENMPT.cmake deleted file mode 100644 index 7e5b2d5a3..000000000 --- a/cmake/Modules/FindOPENMPT.cmake +++ /dev/null @@ -1,33 +0,0 @@ -include(LibFindMacros) - -libfind_pkg_check_modules(OPENMPT_PKGCONF OPENMPT) - -find_path(OPENMPT_INCLUDE_DIR - NAMES libopenmpt.h - PATHS - ${OPENMPT_PKGCONF_INCLUDE_DIRS} - "/usr/include/libopenmpt" - "/usr/local/include/libopenmpt" -) - -find_library(OPENMPT_LIBRARY - NAMES openmpt - PATHS - ${OPENMPT_PKGCONF_LIBRARY_DIRS} - "/usr/lib" - "/usr/local/lib" -) - -set(OPENMPT_PROCESS_INCLUDES OPENMPT_INCLUDE_DIR) -set(OPENMPT_PROCESS_LIBS OPENMPT_LIBRARY) -libfind_process(OPENMPT) - -if(OPENMPT_FOUND AND NOT TARGET openmpt) - add_library(openmpt UNKNOWN IMPORTED) - set_target_properties( - openmpt - PROPERTIES - IMPORTED_LOCATION "${OPENMPT_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${OPENMPT_INCLUDE_DIR}" - ) -endif() diff --git a/cmake/Modules/FindSDL2_mixer.cmake b/cmake/Modules/FindSDL2_mixer.cmake deleted file mode 100644 index 637498e53..000000000 --- a/cmake/Modules/FindSDL2_mixer.cmake +++ /dev/null @@ -1,44 +0,0 @@ -# Find SDL2 -# Once done, this will define -# -# SDL2_MIXER_FOUND - system has SDL2 -# SDL2_MIXER_INCLUDE_DIRS - SDL2 include directories -# SDL2_MIXER_LIBRARIES - link libraries - -include(LibFindMacros) - -libfind_pkg_check_modules(SDL2_MIXER_PKGCONF SDL2_mixer) - -# includes -find_path(SDL2_MIXER_INCLUDE_DIR - NAMES SDL_mixer.h - PATHS - ${SDL2_MIXER_PKGCONF_INCLUDE_DIRS} - "/usr/include/SDL2" - "/usr/local/include/SDL2" -) - -# library -find_library(SDL2_MIXER_LIBRARY - NAMES SDL2_mixer - PATHS - ${SDL2_MIXER_PKGCONF_LIBRARY_DIRS} - "/usr/lib" - "/usr/local/lib" -) - - -# set include dir variables -set(SDL2_MIXER_PROCESS_INCLUDES SDL2_MIXER_INCLUDE_DIR) -set(SDL2_MIXER_PROCESS_LIBS SDL2_MIXER_LIBRARY) -libfind_process(SDL2_MIXER) - -if(SDL2_MIXER_FOUND AND NOT TARGET SDL2_mixer::SDL2_mixer) - add_library(SDL2_mixer::SDL2_mixer UNKNOWN IMPORTED) - set_target_properties( - SDL2_mixer::SDL2_mixer - PROPERTIES - IMPORTED_LOCATION "${SDL2_MIXER_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${SDL2_MIXER_INCLUDE_DIR}" - ) -endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 57fa5993a..2d3a33b40 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -214,9 +214,6 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") target_include_directories(SRB2SDL2 PRIVATE "${libgme_SOURCE_DIR}") endif() -target_link_libraries(SRB2SDL2 PRIVATE openmpt) -target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_OPENMPT) - target_link_libraries(SRB2SDL2 PRIVATE ZLIB::ZLIB PNG::PNG CURL::libcurl) target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_ZLIB -DHAVE_PNG -DHAVE_CURL -D_LARGEFILE64_SOURCE) target_sources(SRB2SDL2 PRIVATE apng.c) diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt index 669f4710f..62e93a2d6 100644 --- a/src/sdl/CMakeLists.txt +++ b/src/sdl/CMakeLists.txt @@ -57,9 +57,9 @@ if("${CMAKE_SYSTEM_NAME}" MATCHES Darwin) endif() if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}") - target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2-static SDL2_mixer::SDL2_mixer-static) + target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2-static) else() - target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2 SDL2_mixer::SDL2_mixer) + target_link_libraries(SRB2SDL2 PRIVATE SDL2::SDL2) endif() if("${CMAKE_SYSTEM_NAME}" MATCHES Linux) diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 8c828ff29..850e4580c 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -26,31 +26,6 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") ) endif() -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME SDL2_mixer - VERSION 2.6.2 - URL "https://github.com/libsdl-org/SDL_mixer/archive/refs/tags/release-2.6.2.zip" - EXCLUDE_FROM_ALL ON - OPTIONS - "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "SDL2MIXER_INSTALL OFF" - "SDL2MIXER_DEPS_SHARED OFF" - "SDL2MIXER_SAMPLES OFF" - "SDL2MIXER_VENDORED ON" - "SDL2MIXER_FLAC ON" - "SDL2MIXER_FLAC_LIBFLAC OFF" - "SDL2MIXER_FLAC_DRFLAC ON" - "SDL2MIXER_MOD OFF" - "SDL2MIXER_MP3 ON" - "SDL2MIXER_MP3_DRMP3 ON" - "SDL2MIXER_MIDI ON" - "SDL2MIXER_OPUS OFF" - "SDL2MIXER_VORBIS STB" - "SDL2MIXER_WAVE ON" - ) -endif() - if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") CPMAddPackage( NAME ZLIB @@ -221,298 +196,6 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") ) endif() -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME openmpt - VERSION 0.4.30 - URL "https://github.com/OpenMPT/openmpt/archive/refs/tags/libopenmpt-0.4.30.zip" - DOWNLOAD_ONLY ON - ) - - if(openmpt_ADDED) - set( - openmpt_SOURCES - - # minimp3 - # -DMPT_WITH_MINIMP3 - include/minimp3/minimp3.c - - common/mptStringParse.cpp - common/mptLibrary.cpp - common/Logging.cpp - common/Profiler.cpp - common/version.cpp - common/mptCPU.cpp - common/ComponentManager.cpp - common/mptOS.cpp - common/serialization_utils.cpp - common/mptStringFormat.cpp - common/FileReader.cpp - common/mptWine.cpp - common/mptPathString.cpp - common/mptAlloc.cpp - common/mptUUID.cpp - common/mptTime.cpp - common/mptString.cpp - common/mptFileIO.cpp - common/mptStringBuffer.cpp - common/mptRandom.cpp - common/mptIO.cpp - common/misc_util.cpp - - common/mptCRC.h - common/mptLibrary.h - common/mptIO.h - common/version.h - common/stdafx.h - common/ComponentManager.h - common/Endianness.h - common/mptStringFormat.h - common/mptMutex.h - common/mptUUID.h - common/mptExceptionText.h - common/BuildSettings.h - common/mptAlloc.h - common/mptTime.h - common/FileReaderFwd.h - common/Logging.h - common/mptException.h - common/mptWine.h - common/mptStringBuffer.h - common/misc_util.h - common/mptBaseMacros.h - common/mptMemory.h - common/mptFileIO.h - common/serialization_utils.h - common/mptSpan.h - common/mptThread.h - common/FlagSet.h - common/mptString.h - common/mptStringParse.h - common/mptBaseUtils.h - common/mptRandom.h - common/CompilerDetect.h - common/FileReader.h - common/mptAssert.h - common/mptPathString.h - common/Profiler.h - common/mptOS.h - common/mptBaseTypes.h - common/mptCPU.h - common/mptBufferIO.h - common/versionNumber.h - - soundlib/WAVTools.cpp - soundlib/ITTools.cpp - soundlib/AudioCriticalSection.cpp - soundlib/Load_stm.cpp - soundlib/MixerLoops.cpp - soundlib/Load_dbm.cpp - soundlib/ModChannel.cpp - soundlib/Load_gdm.cpp - soundlib/Snd_fx.cpp - soundlib/Load_mid.cpp - soundlib/mod_specifications.cpp - soundlib/Snd_flt.cpp - soundlib/Load_psm.cpp - soundlib/Load_far.cpp - soundlib/patternContainer.cpp - soundlib/Load_med.cpp - soundlib/Load_dmf.cpp - soundlib/Paula.cpp - soundlib/modcommand.cpp - soundlib/Message.cpp - soundlib/SoundFilePlayConfig.cpp - soundlib/Load_uax.cpp - soundlib/plugins/PlugInterface.cpp - soundlib/plugins/LFOPlugin.cpp - soundlib/plugins/PluginManager.cpp - soundlib/plugins/DigiBoosterEcho.cpp - soundlib/plugins/dmo/DMOPlugin.cpp - soundlib/plugins/dmo/Flanger.cpp - soundlib/plugins/dmo/Distortion.cpp - soundlib/plugins/dmo/ParamEq.cpp - soundlib/plugins/dmo/Gargle.cpp - soundlib/plugins/dmo/I3DL2Reverb.cpp - soundlib/plugins/dmo/Compressor.cpp - soundlib/plugins/dmo/WavesReverb.cpp - soundlib/plugins/dmo/Echo.cpp - soundlib/plugins/dmo/Chorus.cpp - soundlib/Load_ams.cpp - soundlib/tuningbase.cpp - soundlib/ContainerUMX.cpp - soundlib/Load_ptm.cpp - soundlib/ContainerXPK.cpp - soundlib/SampleFormatMP3.cpp - soundlib/tuning.cpp - soundlib/Sndfile.cpp - soundlib/ContainerMMCMP.cpp - soundlib/Load_amf.cpp - soundlib/Load_669.cpp - soundlib/modsmp_ctrl.cpp - soundlib/Load_mtm.cpp - soundlib/OggStream.cpp - soundlib/Load_plm.cpp - soundlib/Tables.cpp - soundlib/Load_c67.cpp - soundlib/Load_mod.cpp - soundlib/Load_sfx.cpp - soundlib/Sndmix.cpp - soundlib/load_j2b.cpp - soundlib/ModSequence.cpp - soundlib/SampleFormatFLAC.cpp - soundlib/ModInstrument.cpp - soundlib/Load_mo3.cpp - soundlib/ModSample.cpp - soundlib/Dlsbank.cpp - soundlib/Load_itp.cpp - soundlib/UpgradeModule.cpp - soundlib/MIDIMacros.cpp - soundlib/ContainerPP20.cpp - soundlib/RowVisitor.cpp - soundlib/Load_imf.cpp - soundlib/SampleFormatVorbis.cpp - soundlib/Load_dsm.cpp - soundlib/Load_mt2.cpp - soundlib/MixerSettings.cpp - soundlib/S3MTools.cpp - soundlib/Load_xm.cpp - soundlib/MIDIEvents.cpp - soundlib/pattern.cpp - soundlib/Load_digi.cpp - soundlib/Load_s3m.cpp - soundlib/tuningCollection.cpp - soundlib/SampleIO.cpp - soundlib/Dither.cpp - soundlib/Load_mdl.cpp - soundlib/OPL.cpp - soundlib/WindowedFIR.cpp - soundlib/SampleFormats.cpp - soundlib/Load_wav.cpp - soundlib/Load_it.cpp - soundlib/UMXTools.cpp - soundlib/Load_stp.cpp - soundlib/Load_okt.cpp - soundlib/Load_ult.cpp - soundlib/MixFuncTable.cpp - soundlib/SampleFormatOpus.cpp - soundlib/Fastmix.cpp - soundlib/Tagging.cpp - soundlib/ITCompression.cpp - soundlib/Load_dtm.cpp - soundlib/MPEGFrame.cpp - soundlib/XMTools.cpp - soundlib/SampleFormatMediaFoundation.cpp - soundlib/InstrumentExtensions.cpp - - soundlib/MixerInterface.h - soundlib/SoundFilePlayConfig.h - soundlib/ModSample.h - soundlib/MIDIEvents.h - soundlib/ModSampleCopy.h - soundlib/patternContainer.h - soundlib/ChunkReader.h - soundlib/ITCompression.h - soundlib/Dither.h - soundlib/S3MTools.h - soundlib/MPEGFrame.h - soundlib/WAVTools.h - soundlib/mod_specifications.h - soundlib/ITTools.h - soundlib/RowVisitor.h - soundlib/plugins/PluginMixBuffer.h - soundlib/plugins/PluginStructs.h - soundlib/plugins/LFOPlugin.h - soundlib/plugins/PlugInterface.h - soundlib/plugins/DigiBoosterEcho.h - soundlib/plugins/OpCodes.h - soundlib/plugins/dmo/Echo.h - soundlib/plugins/dmo/I3DL2Reverb.h - soundlib/plugins/dmo/WavesReverb.h - soundlib/plugins/dmo/ParamEq.h - soundlib/plugins/dmo/Gargle.h - soundlib/plugins/dmo/DMOPlugin.h - soundlib/plugins/dmo/Chorus.h - soundlib/plugins/dmo/Compressor.h - soundlib/plugins/dmo/Distortion.h - soundlib/plugins/dmo/Flanger.h - soundlib/plugins/PluginManager.h - soundlib/SampleIO.h - soundlib/Container.h - soundlib/ModSequence.h - soundlib/UMXTools.h - soundlib/Message.h - soundlib/modcommand.h - soundlib/XMTools.h - soundlib/Snd_defs.h - soundlib/MixFuncTable.h - soundlib/pattern.h - soundlib/modsmp_ctrl.h - soundlib/Tagging.h - soundlib/tuningcollection.h - soundlib/Mixer.h - soundlib/FloatMixer.h - soundlib/AudioCriticalSection.h - soundlib/Tables.h - soundlib/tuningbase.h - soundlib/WindowedFIR.h - soundlib/Sndfile.h - soundlib/Paula.h - soundlib/ModInstrument.h - soundlib/Dlsbank.h - soundlib/IntMixer.h - soundlib/OPL.h - soundlib/Resampler.h - soundlib/ModChannel.h - soundlib/MixerSettings.h - soundlib/AudioReadTarget.h - soundlib/MixerLoops.h - soundlib/tuning.h - soundlib/MIDIMacros.h - soundlib/OggStream.h - soundlib/Loaders.h - soundlib/BitReader.h - soundlib/opal.h - - sounddsp/AGC.cpp - sounddsp/EQ.cpp - sounddsp/DSP.cpp - sounddsp/Reverb.cpp - sounddsp/Reverb.h - sounddsp/EQ.h - sounddsp/DSP.h - sounddsp/AGC.h - - libopenmpt/libopenmpt_c.cpp - libopenmpt/libopenmpt_cxx.cpp - libopenmpt/libopenmpt_impl.cpp - libopenmpt/libopenmpt_ext_impl.cpp - ) - list(TRANSFORM openmpt_SOURCES PREPEND "${openmpt_SOURCE_DIR}/") - - # -DLIBOPENMPT_BUILD - configure_file("openmpt_svn_version.h" "svn_version.h") - add_library(openmpt "${SRB2_INTERNAL_LIBRARY_TYPE}" ${openmpt_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/svn_version.h) - if("${CMAKE_C_COMPILER_ID}" STREQUAL GNU OR "${CMAKE_C_COMPILER_ID}" STREQUAL Clang OR "${CMAKE_C_COMPILER_ID}" STREQUAL AppleClang) - target_compile_options(openmpt PRIVATE "-g0") - endif() - if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND "${CMAKE_C_COMPILER_ID}" STREQUAL MSVC) - target_link_libraries(openmpt PRIVATE Rpcrt4) - endif() - target_compile_features(openmpt PRIVATE cxx_std_11) - target_compile_definitions(openmpt PRIVATE -DLIBOPENMPT_BUILD) - - target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/common") - target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/src") - target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}/include") - target_include_directories(openmpt PRIVATE "${openmpt_SOURCE_DIR}") - target_include_directories(openmpt PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") - - # I wish this wasn't necessary, but it is - target_include_directories(openmpt PUBLIC "${openmpt_SOURCE_DIR}") - endif() -endif() - if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") CPMAddPackage( NAME libgme diff --git a/thirdparty/openmpt_svn_version.h b/thirdparty/openmpt_svn_version.h deleted file mode 100644 index a45ed9f22..000000000 --- a/thirdparty/openmpt_svn_version.h +++ /dev/null @@ -1,10 +0,0 @@ - -#pragma once -#define OPENMPT_VERSION_SVNVERSION "17963" -#define OPENMPT_VERSION_REVISION 17963 -#define OPENMPT_VERSION_DIRTY 0 -#define OPENMPT_VERSION_MIXEDREVISIONS 0 -#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.4.32" -#define OPENMPT_VERSION_DATE "2022-09-25T14:19:05.052596Z" -#define OPENMPT_VERSION_IS_PACKAGE 1 - From ac7ca10061a3fbe165bac681381de7cff038d735 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Wed, 4 Jan 2023 17:28:11 -0600 Subject: [PATCH 112/128] cmake: Separate cpm xmp-lite setup --- thirdparty/CMakeLists.txt | 60 +---------------------------------- thirdparty/cpm-xmp-lite.cmake | 60 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 59 deletions(-) create mode 100644 thirdparty/cpm-xmp-lite.cmake diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 850e4580c..18397528c 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -288,65 +288,7 @@ if(DiscordRPC_ADDED) endif() endif() -CPMAddPackage( - NAME xmp-lite - VERSION 4.5.0 - URL "https://github.com/libxmp/libxmp/releases/download/libxmp-4.5.0/libxmp-lite-4.5.0.tar.gz" - EXCLUDE_FROM_ALL ON - DOWNLOAD_ONLY ON -) -if(xmp-lite_ADDED) - set(xmp_sources - virtual.c - format.c - period.c - player.c - read_event.c - misc.c - dataio.c - lfo.c - scan.c - control.c - filter.c - effects.c - mixer.c - mix_all.c - load_helpers.c - load.c - hio.c - smix.c - memio.c - win32.c - - loaders/common.c - loaders/itsex.c - loaders/sample.c - loaders/xm_load.c - loaders/mod_load.c - loaders/s3m_load.c - loaders/it_load.c - ) - list(TRANSFORM xmp_sources PREPEND "${xmp-lite_SOURCE_DIR}/src/") - - add_library(xmp-lite "${SRB2_INTERNAL_LIBRARY_TYPE}" ${xmp_sources}) - - target_compile_definitions(xmp-lite PRIVATE -D_REENTRANT -DLIBXMP_CORE_PLAYER -DLIBXMP_NO_PROWIZARD -DLIBXMP_NO_DEPACKERS) - if("${SRB2_INTERNAL_LIBRARY_TYPE}" STREQUAL "STATIC") - if(WIN32) - # BUILDING_STATIC has to be public to work around a bug in xmp.h - # which adds __declspec(dllimport) even when statically linking - target_compile_definitions(xmp-lite PUBLIC -DBUILDING_STATIC) - else() - target_compile_definitions(xmp-lite PRIVATE -DBUILDING_STATIC) - endif() - else() - target_compile_definitions(xmp-lite PRIVATE -DBUILDING_DLL) - endif() - target_include_directories(xmp-lite PRIVATE "${xmp-lite_SOURCE_DIR}/src") - target_include_directories(xmp-lite PUBLIC "${xmp-lite_SOURCE_DIR}/include/libxmp-lite") - - add_library(xmp-lite::xmp-lite ALIAS xmp-lite) -endif() +include("cpm-xmp-lite.cmake") add_subdirectory(tcbrindle_span) add_subdirectory(stb_vorbis) diff --git a/thirdparty/cpm-xmp-lite.cmake b/thirdparty/cpm-xmp-lite.cmake new file mode 100644 index 000000000..21a721e0a --- /dev/null +++ b/thirdparty/cpm-xmp-lite.cmake @@ -0,0 +1,60 @@ +CPMAddPackage( + NAME xmp-lite + VERSION 4.5.0 + URL "https://github.com/libxmp/libxmp/releases/download/libxmp-4.5.0/libxmp-lite-4.5.0.tar.gz" + EXCLUDE_FROM_ALL ON + DOWNLOAD_ONLY ON +) + +if(xmp-lite_ADDED) + set(xmp_sources + virtual.c + format.c + period.c + player.c + read_event.c + misc.c + dataio.c + lfo.c + scan.c + control.c + filter.c + effects.c + mixer.c + mix_all.c + load_helpers.c + load.c + hio.c + smix.c + memio.c + win32.c + + loaders/common.c + loaders/itsex.c + loaders/sample.c + loaders/xm_load.c + loaders/mod_load.c + loaders/s3m_load.c + loaders/it_load.c + ) + list(TRANSFORM xmp_sources PREPEND "${xmp-lite_SOURCE_DIR}/src/") + + add_library(xmp-lite "${SRB2_INTERNAL_LIBRARY_TYPE}" ${xmp_sources}) + + target_compile_definitions(xmp-lite PRIVATE -D_REENTRANT -DLIBXMP_CORE_PLAYER -DLIBXMP_NO_PROWIZARD -DLIBXMP_NO_DEPACKERS) + if("${SRB2_INTERNAL_LIBRARY_TYPE}" STREQUAL "STATIC") + if(WIN32) + # BUILDING_STATIC has to be public to work around a bug in xmp.h + # which adds __declspec(dllimport) even when statically linking + target_compile_definitions(xmp-lite PUBLIC -DBUILDING_STATIC) + else() + target_compile_definitions(xmp-lite PRIVATE -DBUILDING_STATIC) + endif() + else() + target_compile_definitions(xmp-lite PRIVATE -DBUILDING_DLL) + endif() + target_include_directories(xmp-lite PRIVATE "${xmp-lite_SOURCE_DIR}/src") + target_include_directories(xmp-lite PUBLIC "${xmp-lite_SOURCE_DIR}/include/libxmp-lite") + + add_library(xmp-lite::xmp-lite ALIAS xmp-lite) +endif() From dd182cd0a11f8a3f04127a1b9d82577bc5b22f11 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Wed, 4 Jan 2023 17:29:19 -0600 Subject: [PATCH 113/128] cmake: Separate cpm discordrpc setup --- thirdparty/CMakeLists.txt | 80 ++------------------------------- thirdparty/cpm-discordrpc.cmake | 63 ++++++++++++++++++++++++++ thirdparty/cpm-rapidjson.cmake | 13 ++++++ 3 files changed, 79 insertions(+), 77 deletions(-) create mode 100644 thirdparty/cpm-discordrpc.cmake create mode 100644 thirdparty/cpm-rapidjson.cmake diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 18397528c..7aa04fc32 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -209,85 +209,11 @@ if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") ) target_compile_features(gme PRIVATE cxx_std_11) target_link_libraries(gme PRIVATE ZLIB::ZLIB) + endif() -CPMAddPackage( - NAME RapidJSON - VERSION 1.1.0 - URL "https://github.com/Tencent/rapidjson/archive/v1.1.0.tar.gz" - EXCLUDE_FROM_ALL ON - DOWNLOAD_ONLY ON -) -if(RapidJSON_ADDED) - add_library(RapidJSON INTERFACE) - add_library(RapidJSON::RapidJSON ALIAS RapidJSON) - target_include_directories(RapidJSON INTERFACE "${RapidJSON_SOURCE_DIR}/include") -endif() - -CPMAddPackage( - NAME DiscordRPC - VERSION 3.4.0 - URL "https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.zip" - EXCLUDE_FROM_ALL ON - DOWNLOAD_ONLY ON -) - -if(DiscordRPC_ADDED) - set(DiscordRPC_SOURCES - include/discord_rpc.h - include/discord_register.h - - src/discord_rpc.cpp - src/rpc_connection.h - src/rpc_connection.cpp - src/serialization.h - src/serialization.cpp - src/connection.h - src/backoff.h - src/msg_queue.h - ) - list(TRANSFORM DiscordRPC_SOURCES PREPEND "${DiscordRPC_SOURCE_DIR}/") - - # Discord RPC is always statically linked because it's tiny. - add_library(discord-rpc STATIC ${DiscordRPC_SOURCES}) - add_library(DiscordRPC::DiscordRPC ALIAS discord-rpc) - - target_include_directories(discord-rpc PUBLIC "${DiscordRPC_SOURCE_DIR}/include") - target_compile_features(discord-rpc PUBLIC cxx_std_11) - target_link_libraries(discord-rpc PRIVATE RapidJSON::RapidJSON) - - # Platform-specific connection and register impls - if(WIN32) - target_compile_definitions(discord-rpc PUBLIC -DDISCORD_WINDOWS) - target_sources(discord-rpc PRIVATE - "${DiscordRPC_SOURCE_DIR}/src/connection_win.cpp" - "${DiscordRPC_SOURCE_DIR}/src/discord_register_win.cpp" - ) - target_link_libraries(discord-rpc PRIVATE psapi advapi32) - endif() - - if(UNIX) - target_sources(discord-rpc PRIVATE - "${DiscordRPC_SOURCE_DIR}/src/connection_unix.cpp" - ) - - if(APPLE) - target_compile_definitions(discord-rpc PUBLIC -DDISCORD_OSX) - target_sources(discord-rpc PRIVATE - "${DiscordRPC_SOURCE_DIR}/src/discord_register_osx.m" - ) - target_link_libraries(discord-rpc PUBLIC "-framework AppKit") - endif() - - if(UNIX AND NOT APPLE) - target_compile_definitions(discord-rpc PUBLIC -DDISCORD_LINUX) - target_sources(discord-rpc PRIVATE - "${DiscordRPC_SOURCE_DIR}/src/discord_register_linux.cpp" - ) - endif() - endif() -endif() - +include("cpm-rapidjson.cmake") +include("cpm-discordrpc.cmake") include("cpm-xmp-lite.cmake") add_subdirectory(tcbrindle_span) diff --git a/thirdparty/cpm-discordrpc.cmake b/thirdparty/cpm-discordrpc.cmake new file mode 100644 index 000000000..078ebaab5 --- /dev/null +++ b/thirdparty/cpm-discordrpc.cmake @@ -0,0 +1,63 @@ +CPMAddPackage( + NAME DiscordRPC + VERSION 3.4.0 + URL "https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.zip" + EXCLUDE_FROM_ALL ON + DOWNLOAD_ONLY ON +) + +if(DiscordRPC_ADDED) + set(DiscordRPC_SOURCES + include/discord_rpc.h + include/discord_register.h + + src/discord_rpc.cpp + src/rpc_connection.h + src/rpc_connection.cpp + src/serialization.h + src/serialization.cpp + src/connection.h + src/backoff.h + src/msg_queue.h + ) + list(TRANSFORM DiscordRPC_SOURCES PREPEND "${DiscordRPC_SOURCE_DIR}/") + + # Discord RPC is always statically linked because it's tiny. + add_library(discord-rpc STATIC ${DiscordRPC_SOURCES}) + add_library(DiscordRPC::DiscordRPC ALIAS discord-rpc) + + target_include_directories(discord-rpc PUBLIC "${DiscordRPC_SOURCE_DIR}/include") + target_compile_features(discord-rpc PUBLIC cxx_std_11) + target_link_libraries(discord-rpc PRIVATE RapidJSON::RapidJSON) + + # Platform-specific connection and register impls + if(WIN32) + target_compile_definitions(discord-rpc PUBLIC -DDISCORD_WINDOWS) + target_sources(discord-rpc PRIVATE + "${DiscordRPC_SOURCE_DIR}/src/connection_win.cpp" + "${DiscordRPC_SOURCE_DIR}/src/discord_register_win.cpp" + ) + target_link_libraries(discord-rpc PRIVATE psapi advapi32) + endif() + + if(UNIX) + target_sources(discord-rpc PRIVATE + "${DiscordRPC_SOURCE_DIR}/src/connection_unix.cpp" + ) + + if(APPLE) + target_compile_definitions(discord-rpc PUBLIC -DDISCORD_OSX) + target_sources(discord-rpc PRIVATE + "${DiscordRPC_SOURCE_DIR}/src/discord_register_osx.m" + ) + target_link_libraries(discord-rpc PUBLIC "-framework AppKit") + endif() + + if(UNIX AND NOT APPLE) + target_compile_definitions(discord-rpc PUBLIC -DDISCORD_LINUX) + target_sources(discord-rpc PRIVATE + "${DiscordRPC_SOURCE_DIR}/src/discord_register_linux.cpp" + ) + endif() + endif() +endif() diff --git a/thirdparty/cpm-rapidjson.cmake b/thirdparty/cpm-rapidjson.cmake new file mode 100644 index 000000000..2836625ed --- /dev/null +++ b/thirdparty/cpm-rapidjson.cmake @@ -0,0 +1,13 @@ +CPMAddPackage( + NAME RapidJSON + VERSION 1.1.0 + URL "https://github.com/Tencent/rapidjson/archive/v1.1.0.tar.gz" + EXCLUDE_FROM_ALL ON + DOWNLOAD_ONLY ON +) + +if(RapidJSON_ADDED) + add_library(RapidJSON INTERFACE) + add_library(RapidJSON::RapidJSON ALIAS RapidJSON) + target_include_directories(RapidJSON INTERFACE "${RapidJSON_SOURCE_DIR}/include") +endif() From 81d1aa79c21f9699d19e5ca191f95b583149967a Mon Sep 17 00:00:00 2001 From: Eidolon Date: Wed, 4 Jan 2023 17:30:37 -0600 Subject: [PATCH 114/128] cmake: Split common deps setup scripts --- thirdparty/CMakeLists.txt | 205 +----------------------------------- thirdparty/cpm-curl.cmake | 35 ++++++ thirdparty/cpm-libgme.cmake | 15 +++ thirdparty/cpm-png.cmake | 73 +++++++++++++ thirdparty/cpm-sdl2.cmake | 13 +++ thirdparty/cpm-zlib.cmake | 54 ++++++++++ 6 files changed, 195 insertions(+), 200 deletions(-) create mode 100644 thirdparty/cpm-curl.cmake create mode 100644 thirdparty/cpm-libgme.cmake create mode 100644 thirdparty/cpm-png.cmake create mode 100644 thirdparty/cpm-sdl2.cmake create mode 100644 thirdparty/cpm-zlib.cmake diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 7aa04fc32..86a090b07 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -9,207 +9,12 @@ else() set(NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES ON) endif() - if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME SDL2 - VERSION 2.24.2 - URL "https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.24.2.zip" - EXCLUDE_FROM_ALL ON - OPTIONS - "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "SDL_SHARED ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "SDL_STATIC ${NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "SDL_TEST OFF" - "SDL2_DISABLE_SDL2MAIN ON" - "SDL2_DISABLE_INSTALL ON" - ) -endif() - -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME ZLIB - VERSION 1.2.13 - URL "https://github.com/madler/zlib/archive/refs/tags/v1.2.13.zip" - EXCLUDE_FROM_ALL - DOWNLOAD_ONLY YES - ) - if(ZLIB_ADDED) - set(ZLIB_SRCS - crc32.h - deflate.h - gzguts.h - inffast.h - inffixed.h - inflate.h - inftrees.h - trees.h - zutil.h - - adler32.c - compress.c - crc32.c - deflate.c - gzclose.c - gzlib.c - gzread.c - gzwrite.c - inflate.c - infback.c - inftrees.c - inffast.c - trees.c - uncompr.c - zutil.c - ) - list(TRANSFORM ZLIB_SRCS PREPEND "${ZLIB_SOURCE_DIR}/") - - configure_file("${ZLIB_SOURCE_DIR}/zlib.pc.cmakein" "${ZLIB_BINARY_DIR}/zlib.pc" @ONLY) - configure_file("${ZLIB_SOURCE_DIR}/zconf.h.cmakein" "${ZLIB_BINARY_DIR}/include/zconf.h" @ONLY) - configure_file("${ZLIB_SOURCE_DIR}/zlib.h" "${ZLIB_BINARY_DIR}/include/zlib.h" @ONLY) - - add_library(ZLIB ${SRB2_INTERNAL_LIBRARY_TYPE} ${ZLIB_SRCS}) - set_target_properties(ZLIB PROPERTIES - VERSION 1.2.13 - OUTPUT_NAME "z" - ) - target_include_directories(ZLIB PRIVATE "${ZLIB_SOURCE_DIR}") - target_include_directories(ZLIB PUBLIC "${ZLIB_BINARY_DIR}/include") - if(MSVC) - target_compile_definitions(ZLIB PRIVATE -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) - endif() - add_library(ZLIB::ZLIB ALIAS ZLIB) - endif() -endif() - -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME png - VERSION 1.6.38 - URL "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.38.zip" - # png cmake build is broken on msys/mingw32 - DOWNLOAD_ONLY YES - ) - - if(png_ADDED) - # Since png's cmake build is broken, we're going to create a target manually - set( - PNG_SOURCES - - png.h - pngconf.h - - pngpriv.h - pngdebug.h - pnginfo.h - pngstruct.h - - png.c - pngerror.c - pngget.c - pngmem.c - pngpread.c - pngread.c - pngrio.c - pngrtran.c - pngrutil.c - pngset.c - pngtrans.c - pngwio.c - pngwrite.c - pngwtran.c - pngwutil.c - ) - list(TRANSFORM PNG_SOURCES PREPEND "${png_SOURCE_DIR}/") - - add_custom_command( - OUTPUT "${png_BINARY_DIR}/include/png.h" "${png_BINARY_DIR}/include/pngconf.h" - COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" "${png_BINARY_DIR}/include" - DEPENDS "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" - VERBATIM - ) - add_custom_command( - OUTPUT "${png_BINARY_DIR}/include/pnglibconf.h" - COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" "${png_BINARY_DIR}/include/pnglibconf.h" - DEPENDS "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" - VERBATIM - ) - list( - APPEND PNG_SOURCES - "${png_BINARY_DIR}/include/png.h" - "${png_BINARY_DIR}/include/pngconf.h" - "${png_BINARY_DIR}/include/pnglibconf.h" - ) - add_library(png "${SRB2_INTERNAL_LIBRARY_TYPE}" ${PNG_SOURCES}) - - # Disable ARM NEON since having it automatic breaks libpng external build on clang for some reason - target_compile_definitions(png PRIVATE -DPNG_ARM_NEON_OPT=0) - - # The png includes need to be available to consumers - target_include_directories(png PUBLIC "${png_BINARY_DIR}/include") - - # ... and these also need to be present only for png build - target_include_directories(png PRIVATE "${ZLIB_SOURCE_DIR}") - target_include_directories(png PRIVATE "${ZLIB_BINARY_DIR}") - target_include_directories(png PRIVATE "${png_BINARY_DIR}") - - target_link_libraries(png PRIVATE ZLIB::ZLIB) - add_library(PNG::PNG ALIAS png) - endif() -endif() - -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - set( - internal_curl_options - - "BUILD_CURL_EXE OFF" - "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "CURL_DISABLE_TESTS ON" - "HTTP_ONLY ON" - "CURL_DISABLE_CRYPTO_AUTH ON" - "CURL_DISABLE_NTLM ON" - "ENABLE_MANUAL OFF" - "ENABLE_THREADED_RESOLVER OFF" - "CURL_USE_LIBPSL OFF" - "CURL_USE_LIBSSH2 OFF" - "USE_LIBIDN2 OFF" - "CURL_ENABLE_EXPORT_TARGET OFF" - ) - if(${CMAKE_SYSTEM} MATCHES Windows) - list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF") - list(APPEND internal_curl_options "CURL_USE_SCHANNEL ON") - endif() - if(${CMAKE_SYSTEM} MATCHES Darwin) - list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF") - list(APPEND internal_curl_options "CURL_USE_SECTRANSP ON") - endif() - if(${CMAKE_SYSTEM} MATCHES Linux) - list(APPEND internal_curl_options "CURL_USE_OPENSSL ON") - endif() - - CPMAddPackage( - NAME curl - VERSION 7.86.0 - URL "https://github.com/curl/curl/archive/refs/tags/curl-7_86_0.zip" - EXCLUDE_FROM_ALL ON - OPTIONS ${internal_curl_options} - ) -endif() - -if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}") - CPMAddPackage( - NAME libgme - VERSION 0.6.3 - URL "https://bitbucket.org/mpyne/game-music-emu/get/e76bdc0cb916e79aa540290e6edd0c445879d3ba.zip" - EXCLUDE_FROM_ALL ON - OPTIONS - "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" - "ENABLE_UBSAN OFF" - "GME_YM2612_EMU MAME" - ) - target_compile_features(gme PRIVATE cxx_std_11) - target_link_libraries(gme PRIVATE ZLIB::ZLIB) - +include("cpm-sdl2.cmake") + include("cpm-zlib.cmake") + include("cpm-png.cmake") + include("cpm-curl.cmake") + include("cpm-libgme.cmake") endif() include("cpm-rapidjson.cmake") diff --git a/thirdparty/cpm-curl.cmake b/thirdparty/cpm-curl.cmake new file mode 100644 index 000000000..3d8c6e61d --- /dev/null +++ b/thirdparty/cpm-curl.cmake @@ -0,0 +1,35 @@ +set( + internal_curl_options + + "BUILD_CURL_EXE OFF" + "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" + "CURL_DISABLE_TESTS ON" + "HTTP_ONLY ON" + "CURL_DISABLE_CRYPTO_AUTH ON" + "CURL_DISABLE_NTLM ON" + "ENABLE_MANUAL OFF" + "ENABLE_THREADED_RESOLVER OFF" + "CURL_USE_LIBPSL OFF" + "CURL_USE_LIBSSH2 OFF" + "USE_LIBIDN2 OFF" + "CURL_ENABLE_EXPORT_TARGET OFF" +) +if(${CMAKE_SYSTEM} MATCHES Windows) + list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF") + list(APPEND internal_curl_options "CURL_USE_SCHANNEL ON") +endif() +if(${CMAKE_SYSTEM} MATCHES Darwin) + list(APPEND internal_curl_options "CURL_USE_OPENSSL OFF") + list(APPEND internal_curl_options "CURL_USE_SECTRANSP ON") +endif() +if(${CMAKE_SYSTEM} MATCHES Linux) + list(APPEND internal_curl_options "CURL_USE_OPENSSL ON") +endif() + +CPMAddPackage( + NAME curl + VERSION 7.86.0 + URL "https://github.com/curl/curl/archive/refs/tags/curl-7_86_0.zip" + EXCLUDE_FROM_ALL ON + OPTIONS ${internal_curl_options} +) diff --git a/thirdparty/cpm-libgme.cmake b/thirdparty/cpm-libgme.cmake new file mode 100644 index 000000000..1786ad0ec --- /dev/null +++ b/thirdparty/cpm-libgme.cmake @@ -0,0 +1,15 @@ +CPMAddPackage( + NAME libgme + VERSION 0.6.3 + URL "https://bitbucket.org/mpyne/game-music-emu/get/e76bdc0cb916e79aa540290e6edd0c445879d3ba.zip" + EXCLUDE_FROM_ALL ON + OPTIONS + "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" + "ENABLE_UBSAN OFF" + "GME_YM2612_EMU MAME" +) + +if(libgme_ADDED) + target_compile_features(gme PRIVATE cxx_std_11) + target_link_libraries(gme PRIVATE ZLIB::ZLIB) +endif() diff --git a/thirdparty/cpm-png.cmake b/thirdparty/cpm-png.cmake new file mode 100644 index 000000000..716ff27b8 --- /dev/null +++ b/thirdparty/cpm-png.cmake @@ -0,0 +1,73 @@ +CPMAddPackage( + NAME png + VERSION 1.6.38 + URL "https://github.com/glennrp/libpng/archive/refs/tags/v1.6.38.zip" + # png cmake build is broken on msys/mingw32 + DOWNLOAD_ONLY YES +) + +if(png_ADDED) + # Since png's cmake build is broken, we're going to create a target manually + set( + PNG_SOURCES + + png.h + pngconf.h + + pngpriv.h + pngdebug.h + pnginfo.h + pngstruct.h + + png.c + pngerror.c + pngget.c + pngmem.c + pngpread.c + pngread.c + pngrio.c + pngrtran.c + pngrutil.c + pngset.c + pngtrans.c + pngwio.c + pngwrite.c + pngwtran.c + pngwutil.c + ) + list(TRANSFORM PNG_SOURCES PREPEND "${png_SOURCE_DIR}/") + + add_custom_command( + OUTPUT "${png_BINARY_DIR}/include/png.h" "${png_BINARY_DIR}/include/pngconf.h" + COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" "${png_BINARY_DIR}/include" + DEPENDS "${png_SOURCE_DIR}/png.h" "${png_SOURCE_DIR}/pngconf.h" + VERBATIM + ) + add_custom_command( + OUTPUT "${png_BINARY_DIR}/include/pnglibconf.h" + COMMAND ${CMAKE_COMMAND} -E copy "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" "${png_BINARY_DIR}/include/pnglibconf.h" + DEPENDS "${png_SOURCE_DIR}/scripts/pnglibconf.h.prebuilt" + VERBATIM + ) + list( + APPEND PNG_SOURCES + "${png_BINARY_DIR}/include/png.h" + "${png_BINARY_DIR}/include/pngconf.h" + "${png_BINARY_DIR}/include/pnglibconf.h" + ) + add_library(png "${SRB2_INTERNAL_LIBRARY_TYPE}" ${PNG_SOURCES}) + + # Disable ARM NEON since having it automatic breaks libpng external build on clang for some reason + target_compile_definitions(png PRIVATE -DPNG_ARM_NEON_OPT=0) + + # The png includes need to be available to consumers + target_include_directories(png PUBLIC "${png_BINARY_DIR}/include") + + # ... and these also need to be present only for png build + target_include_directories(png PRIVATE "${ZLIB_SOURCE_DIR}") + target_include_directories(png PRIVATE "${ZLIB_BINARY_DIR}") + target_include_directories(png PRIVATE "${png_BINARY_DIR}") + + target_link_libraries(png PRIVATE ZLIB::ZLIB) + add_library(PNG::PNG ALIAS png) +endif() diff --git a/thirdparty/cpm-sdl2.cmake b/thirdparty/cpm-sdl2.cmake new file mode 100644 index 000000000..58cf9afc2 --- /dev/null +++ b/thirdparty/cpm-sdl2.cmake @@ -0,0 +1,13 @@ +CPMAddPackage( + NAME SDL2 + VERSION 2.24.2 + URL "https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.24.2.zip" + EXCLUDE_FROM_ALL ON + OPTIONS + "BUILD_SHARED_LIBS ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" + "SDL_SHARED ${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" + "SDL_STATIC ${NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}" + "SDL_TEST OFF" + "SDL2_DISABLE_SDL2MAIN ON" + "SDL2_DISABLE_INSTALL ON" +) diff --git a/thirdparty/cpm-zlib.cmake b/thirdparty/cpm-zlib.cmake new file mode 100644 index 000000000..aee090083 --- /dev/null +++ b/thirdparty/cpm-zlib.cmake @@ -0,0 +1,54 @@ +CPMAddPackage( + NAME ZLIB + VERSION 1.2.13 + URL "https://github.com/madler/zlib/archive/refs/tags/v1.2.13.zip" + EXCLUDE_FROM_ALL + DOWNLOAD_ONLY YES +) + +if(ZLIB_ADDED) + set(ZLIB_SRCS + crc32.h + deflate.h + gzguts.h + inffast.h + inffixed.h + inflate.h + inftrees.h + trees.h + zutil.h + + adler32.c + compress.c + crc32.c + deflate.c + gzclose.c + gzlib.c + gzread.c + gzwrite.c + inflate.c + infback.c + inftrees.c + inffast.c + trees.c + uncompr.c + zutil.c + ) + list(TRANSFORM ZLIB_SRCS PREPEND "${ZLIB_SOURCE_DIR}/") + + configure_file("${ZLIB_SOURCE_DIR}/zlib.pc.cmakein" "${ZLIB_BINARY_DIR}/zlib.pc" @ONLY) + configure_file("${ZLIB_SOURCE_DIR}/zconf.h.cmakein" "${ZLIB_BINARY_DIR}/include/zconf.h" @ONLY) + configure_file("${ZLIB_SOURCE_DIR}/zlib.h" "${ZLIB_BINARY_DIR}/include/zlib.h" @ONLY) + + add_library(ZLIB ${SRB2_INTERNAL_LIBRARY_TYPE} ${ZLIB_SRCS}) + set_target_properties(ZLIB PROPERTIES + VERSION 1.2.13 + OUTPUT_NAME "z" + ) + target_include_directories(ZLIB PRIVATE "${ZLIB_SOURCE_DIR}") + target_include_directories(ZLIB PUBLIC "${ZLIB_BINARY_DIR}/include") + if(MSVC) + target_compile_definitions(ZLIB PRIVATE -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) + endif() + add_library(ZLIB::ZLIB ALIAS ZLIB) +endif() From d53efbdace8b49374b498bf900f139ea24519e7f Mon Sep 17 00:00:00 2001 From: Eidolon Date: Wed, 4 Jan 2023 17:54:22 -0600 Subject: [PATCH 115/128] core: load exchndl.dll at runtime This allows the game to run without exchndl.dll present. --- src/CMakeLists.txt | 11 +---------- src/sdl/i_main.cpp | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2d3a33b40..fbf2d6f2a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -310,15 +310,6 @@ if("${SRB2_CONFIG_HWRENDER}") endif() endif() -# TODO: build this with the game -if(${CMAKE_SYSTEM} MATCHES Windows AND ${CMAKE_C_COMPILER_ID} MATCHES "GNU" AND ${SRB2_SYSTEM_BITS} EQUAL 32) - target_link_libraries(SRB2SDL2 PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/../libs/drmingw/lib/win32/libexchndl.a" - "${CMAKE_CURRENT_SOURCE_DIR}/../libs/drmingw/lib/win32/libmgwhelp.a" - ) - target_include_directories(SRB2SDL2 PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../libs/drmingw/include") -endif() - if(${SRB2_CONFIG_USEASM}) #SRB2_ASM_FLAGS can be used to pass flags to either nasm or yasm. if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") @@ -401,7 +392,7 @@ target_compile_options(SRB2SDL2 PRIVATE -Winline -Wformat-y2k -Wformat-security - + $<$,2.9.5>: -Wno-div-by-zero -Wendif-labels diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp index 105352e41..be3664ab4 100644 --- a/src/sdl/i_main.cpp +++ b/src/sdl/i_main.cpp @@ -71,10 +71,6 @@ char logfilename[1024]; #endif #endif -#if defined (_WIN32) -#include -#endif - #if defined (_WIN32) extern "C" { #include "../win32/win_dbg.h" @@ -202,6 +198,20 @@ static void InitLogging(void) } #endif +static void init_exchndl() +{ +#ifdef _WIN32 + HMODULE exchndl_module = LoadLibraryA("exchndl.dll"); + if (exchndl_module != NULL) + { + using PFN_ExcHndlInit = void(*)(void); + PFN_ExcHndlInit pfnExcHndlInit = reinterpret_cast( + GetProcAddress(exchndl_module, "ExcHndlInit")); + if (pfnExcHndlInit != NULL) + (pfnExcHndlInit)(); + } +#endif +} #ifdef _WIN32 static void @@ -294,7 +304,7 @@ int main(int argc, char **argv) ) #endif { - ExcHndlInit(); + init_exchndl(); } } #ifndef __MINGW32__ From 4522b4953ea1a11a5b29f604d0933663b1e67f81 Mon Sep 17 00:00:00 2001 From: Eidolon Date: Wed, 4 Jan 2023 19:36:33 -0600 Subject: [PATCH 116/128] core: change SRB2WADDIR var to RINGRACERSWADDIR --- src/sdl/SDL_main/SDL_macosx_main.m | 4 ++-- src/sdl/i_system.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/sdl/SDL_main/SDL_macosx_main.m b/src/sdl/SDL_main/SDL_macosx_main.m index 226afe13d..273dcfd39 100644 --- a/src/sdl/SDL_main/SDL_macosx_main.m +++ b/src/sdl/SDL_main/SDL_macosx_main.m @@ -287,8 +287,8 @@ static void CustomApplicationMain (int argc, char **argv) [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; #endif - if (!getenv("SRB2WADDIR")) - setenv("SRB2WADDIR", [[[NSBundle mainBundle] resourcePath] UTF8String], 1); + if (!getenv("RINGRACERSWADDIR")) + setenv("RINGRACERSWADDIR", [[[NSBundle mainBundle] resourcePath] UTF8String], 1); /* Hand off to main application code */ status = SDL_main (gArgc, gArgv); diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index b390b6d1e..515dc3e95 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -989,7 +989,7 @@ INT32 I_GetJoystickDeviceIndex(SDL_GameController *dev) SDL_Joystick *joystick = NULL; joystick = SDL_GameControllerGetJoystick(dev); - + if (joystick) { return SDL_JoystickInstanceID(joystick); @@ -2242,9 +2242,9 @@ static const char *locateWad(void) const char *envstr; const char *WadPath; - I_OutputMsg("SRB2WADDIR"); - // does SRB2WADDIR exist? - if (((envstr = I_GetEnv("SRB2WADDIR")) != NULL) && isWadPathOk(envstr)) + I_OutputMsg("RINGRACERSWADDIR"); + // does RINGRACERSWADDIR exist? + if (((envstr = I_GetEnv("RINGRACERSWADDIR")) != NULL) && isWadPathOk(envstr)) return envstr; #ifndef NOCWD From d2569dc3af811d5eaaab521935bd686f2f0af14c Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 4 Jan 2023 18:27:22 -0800 Subject: [PATCH 117/128] Pass uint64_t literal to std::max On 32-bit system, uint64_t is typedef to unsigned long long. On 64-bit system, uint64_t is typedef to unsigned long. unsigned long and unsigned long long are distinctly separate types, which causes std::max template to fail. --- src/audio/music_player.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/audio/music_player.cpp b/src/audio/music_player.cpp index 1121518da..bb85a01de 100644 --- a/src/audio/music_player.cpp +++ b/src/audio/music_player.cpp @@ -271,7 +271,8 @@ public: gain_ = from; gain_target_ = to; // Gain samples target must always be at least 1 to avoid a div-by-zero. - gain_samples_target_ = std::max(static_cast(seconds * 44100.f), 1ULL); + gain_samples_target_ = std::max( + static_cast(seconds * 44100.f), UINT64_C(1)); // UINT64_C generates a uint64_t literal gain_samples_ = 0; } From 78fa572df01e719a2af59859df6c74a2a741b0a0 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 4 Jan 2023 19:33:06 -0800 Subject: [PATCH 118/128] SDL: only react to window focus changes if state actually changes Because the state carries over for each call to Impl_HandleWindowEvent, once the window has been focused, events that do not change the focus state behave as if the window was just refocused, anyway. On linux i3wm, some such window events propogate constantly in fullscreen mode. --- src/sdl/i_video.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index 5cea8c65a..bdb522efb 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -572,10 +572,13 @@ static INT32 SDLJoyAxis(const Sint16 axis, UINT8 pid) static void Impl_HandleWindowEvent(SDL_WindowEvent evt) { +#define FOCUSUNION (mousefocus | (kbfocus << 1)) static SDL_bool firsttimeonmouse = SDL_TRUE; static SDL_bool mousefocus = SDL_TRUE; static SDL_bool kbfocus = SDL_TRUE; + const unsigned int oldfocus = FOCUSUNION; + switch (evt.event) { case SDL_WINDOWEVENT_ENTER: @@ -599,6 +602,11 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt) window_y = evt.data2; } + if (FOCUSUNION == oldfocus) // No state change + { + return; + } + if (mousefocus && kbfocus) { // Tell game we got focus back, resume music if necessary @@ -639,7 +647,7 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt) SDLdoUngrabMouse(); } } - +#undef FOCUSUNION } static void Impl_HandleKeyboardEvent(SDL_KeyboardEvent evt, Uint32 type) From 0bf821447e56b41e6ae8de0cd68974dc57ac85f6 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 4 Jan 2023 23:34:19 -0800 Subject: [PATCH 119/128] Add S_FindMusicDef, function to find musicdef by name Use this function wherever the musicdef list is walked. --- src/s_sound.c | 135 +++++++++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 67 deletions(-) diff --git a/src/s_sound.c b/src/s_sound.c index a721a9ff4..c9a63e7bc 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -1365,6 +1365,28 @@ musicdef_t *musicdefstart = NULL; struct cursongcredit cursongcredit; // Currently displayed song credit info int musicdef_volume; +// +// S_FindMusicDef +// +// Find music def by 6 char name +// +static musicdef_t *S_FindMusicDef(const char *name) +{ + musicdef_t *def = musicdefstart; + + while (def) + { + if (!stricmp(def->name, name)) + { + return def; + } + + def = def->next; + } + + return NULL; +} + static boolean MusicDefError ( @@ -1412,21 +1434,10 @@ ReadMusicDefFields } else { - musicdef_t **tail = &musicdefstart; - - // Search if this is a replacement - while (*tail) - { - if (!stricmp((*tail)->name, value)) - { - break; - } - - tail = &(*tail)->next; - } + def = S_FindMusicDef(value); // Nothing found, add to the end. - if (!(*tail)) + if (!def) { def = Z_Calloc(sizeof (musicdef_t), PU_STATIC, NULL); @@ -1434,10 +1445,11 @@ ReadMusicDefFields strlwr(def->name); def->volume = DEFAULT_MUSICDEF_VOLUME; - (*tail) = def; + def->next = musicdefstart; + musicdefstart = def; } - (*defp) = (*tail); + (*defp) = def; } } else @@ -1611,7 +1623,11 @@ void S_InitMusicDefs(void) // void S_ShowMusicCredit(void) { - musicdef_t *def = musicdefstart; + musicdef_t *def = S_FindMusicDef(music_name); + + char credittext[128] = ""; + char *work = NULL; + size_t len = 128, worklen; if (!cv_songcredits.value || demo.rewinding) return; @@ -1619,58 +1635,45 @@ void S_ShowMusicCredit(void) if (!def) // No definitions return; - while (def) + if (!def->title) { - if (!stricmp(def->name, music_name)) - { - char credittext[128] = ""; - char *work = NULL; - size_t len = 128, worklen; + return; + } - if (!def->title) - { - return; - } - - work = va("\x1F %s", def->title); - worklen = strlen(work); - if (worklen <= len) - { - strncat(credittext, work, len); - len -= worklen; + work = va("\x1F %s", def->title); + worklen = strlen(work); + if (worklen <= len) + { + strncat(credittext, work, len); + len -= worklen; #define MUSICCREDITAPPEND(field)\ - if (field)\ - {\ - work = va(" - %s", field);\ - worklen = strlen(work);\ - if (worklen <= len)\ - {\ - strncat(credittext, work, len);\ - len -= worklen;\ - }\ - } - - MUSICCREDITAPPEND(def->author); - MUSICCREDITAPPEND(def->source); - -#undef MUSICCREDITAPPEND - } - - if (credittext[0] == '\0') - return; - - cursongcredit.def = def; - Z_Free(cursongcredit.text); - cursongcredit.text = Z_StrDup(credittext); - cursongcredit.anim = 5*TICRATE; - cursongcredit.x = cursongcredit.old_x = 0; - cursongcredit.trans = NUMTRANSMAPS; - return; + if (field)\ + {\ + work = va(" - %s", field);\ + worklen = strlen(work);\ + if (worklen <= len)\ + {\ + strncat(credittext, work, len);\ + len -= worklen;\ + }\ } - def = def->next; + MUSICCREDITAPPEND(def->author); + MUSICCREDITAPPEND(def->source); + +#undef MUSICCREDITAPPEND } + + if (credittext[0] == '\0') + return; + + cursongcredit.def = def; + Z_Free(cursongcredit.text); + cursongcredit.text = Z_StrDup(credittext); + cursongcredit.anim = 5*TICRATE; + cursongcredit.x = cursongcredit.old_x = 0; + cursongcredit.trans = NUMTRANSMAPS; } /// ------------------------ @@ -2242,13 +2245,11 @@ void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 musicdef_volume = DEFAULT_MUSICDEF_VOLUME; { - musicdef_t *def; - for (def = musicdefstart; def; def = def->next) + musicdef_t *def = S_FindMusicDef(music_name); + + if (def) { - if (strcasecmp(def->name, music_name) == 0) - { - musicdef_volume = def->volume; - } + musicdef_volume = def->volume; } } From 24e7e4231d91d10e782bc2df3f66515368961048 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 4 Jan 2023 06:36:30 -0800 Subject: [PATCH 120/128] Print musicdef along with tunes -show --- src/s_sound.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/s_sound.c b/src/s_sound.c index c9a63e7bc..1fc80cf8e 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -2457,6 +2457,27 @@ void S_StartEx(boolean reset) music_stack_fadein = JINGLEPOSTFADE; } +static inline void PrintMusicDefField(const char *label, const char *field) +{ + if (field) + { + CONS_Printf("%s%s\n", label, field); + } +} + +static void PrintSongAuthors(const musicdef_t *def) +{ + CONS_Printf("Volume: %d/100\n\n", def->volume); + + PrintMusicDefField("Title: ", def->title); + PrintMusicDefField("Author: ", def->author); + + CONS_Printf("\n"); + + PrintMusicDefField("Original Source: ", def->source); + PrintMusicDefField("Original Composers: ", def->composers); +} + // TODO: fix this function, needs better support for map names static void Command_Tunes_f(void) { @@ -2481,8 +2502,15 @@ static void Command_Tunes_f(void) if (!strcasecmp(tunearg, "-show")) { + const musicdef_t *def = S_FindMusicDef(mapmusname); + CONS_Printf(M_GetText("The current tune is: %s [track %d]\n"), mapmusname, (mapmusflags & MUSIC_TRACKMASK)); + + if (def != NULL) + { + PrintSongAuthors(def); + } return; } if (!strcasecmp(tunearg, "-none")) From dfd8a36f8f8f6638e34c24c3ed2840a3672b5da0 Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 5 Jan 2023 03:51:04 -0800 Subject: [PATCH 121/128] sdl/i_main.cpp: fix -Wliteral-suffix --- src/sdl/i_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp index 105352e41..43fa95af9 100644 --- a/src/sdl/i_main.cpp +++ b/src/sdl/i_main.cpp @@ -187,7 +187,7 @@ static void InitLogging(void) logstream = fopen(logfilename, "w"); #ifdef DEFAULTDIR if (logdir) - link = va("%s/"DEFAULTDIR"/latest-log.txt", logdir); + link = va("%s/" DEFAULTDIR "/latest-log.txt", logdir); else #endif/*DEFAULTDIR*/ link = "latest-log.txt"; From e78625455e041c9dab82e5f312d9b5faecc09488 Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 5 Jan 2023 04:02:46 -0800 Subject: [PATCH 122/128] Resize downloaddir to fix -Wformat-truncation --- src/d_main.c | 3 ++- src/d_netfil.c | 1 - src/d_netfil.h | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/d_main.c b/src/d_main.c index 8384de07c..f7ea3b3ea 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -139,6 +139,7 @@ char srb2path[256] = "."; boolean usehome = true; const char *pandf = "%s" PATHSEP "%s"; static char addonsdir[MAX_WADPATH]; +char downloaddir[sizeof addonsdir + sizeof DOWNLOADDIR_PART] = "DOWNLOAD"; // // EVENT HANDLING @@ -1361,7 +1362,7 @@ void D_SRB2Main(void) /* and downloads in a subdirectory */ snprintf(downloaddir, sizeof downloaddir, "%s%s%s", - addonsdir, PATHSEP, "downloads"); + addonsdir, PATHSEP, DOWNLOADDIR_PART); // rand() needs seeded regardless of password srand((unsigned int)time(NULL)); diff --git a/src/d_netfil.c b/src/d_netfil.c index 64b605d12..5238f7028 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -99,7 +99,6 @@ static filetran_t transfer[MAXNETNODES]; INT32 fileneedednum; // Number of files needed to join the server fileneeded_t fileneeded[MAX_WADFILES]; // List of needed files static tic_t lasttimeackpacketsent = 0; -char downloaddir[512] = "DOWNLOAD"; // For resuming failed downloads typedef struct diff --git a/src/d_netfil.h b/src/d_netfil.h index 904c1862a..26bffbc18 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -62,7 +62,8 @@ struct fileneeded_t extern INT32 fileneedednum; extern fileneeded_t fileneeded[MAX_WADFILES]; -extern char downloaddir[512]; +#define DOWNLOADDIR_PART "downloads" +extern char downloaddir[]; extern INT32 lastfilenum; extern INT32 downloadcompletednum; From e7af4a5084846dd9ec2ddab7b22c46b38b83c862 Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 5 Jan 2023 04:24:18 -0800 Subject: [PATCH 123/128] Resize menudemo_t filepath to fix -Wformat-truncation --- src/g_demo.h | 2 +- src/k_menufunc.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/g_demo.h b/src/g_demo.h index 333d7620b..981396b89 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -82,7 +82,7 @@ typedef enum { } menudemotype_e; struct menudemo_t { - char filepath[256]; + char filepath[1023 + 256]; // see M_PrepReplayList and sizeof menupath menudemotype_e type; char title[65]; // Null-terminated for string prints diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 191542ca7..52390f4a4 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -6621,7 +6621,9 @@ void M_PrepReplayList(void) else { extrasmenu.demolist[i].type = MD_NOTLOADED; - snprintf(extrasmenu.demolist[i].filepath, 255, "%s%s", menupath, dirmenu[i] + DIR_STRING); + snprintf(extrasmenu.demolist[i].filepath, sizeof extrasmenu.demolist[i].filepath, + // 255 = UINT8 limit. dirmenu entries are restricted to this length (see DIR_LEN). + "%s%.255s", menupath, dirmenu[i] + DIR_STRING); sprintf(extrasmenu.demolist[i].title, "....."); } } From d56a5041aa57da58824192395b183a1a07e3aecb Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 5 Jan 2023 05:13:18 -0800 Subject: [PATCH 124/128] Use K_GetCachedItemPatch for distribution debugger Fixes crash from patch array mismatch. --- src/k_hud.c | 59 +++++++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 34 deletions(-) diff --git a/src/k_hud.c b/src/k_hud.c index 742d0506b..cc605df83 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -785,6 +785,29 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset) return NULL; } +static patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item) +{ + UINT8 offset; + + item = K_ItemResultToType(item); + + switch (item) + { + case KITEM_INVINCIBILITY: + offset = 7; + break; + + case KITEM_ORBINAUT: + offset = 4; + break; + + default: + offset = 1; + } + + return K_GetCachedItemPatch(item, offset); +} + //} INT32 ITEM_X, ITEM_Y; // Item Window @@ -4930,39 +4953,6 @@ K_drawMiniPing (void) static void K_drawDistributionDebugger(void) { - patch_t *patches[NUMKARTRESULTS] = { - kp_sadface[1], - kp_sneaker[1], - kp_rocketsneaker[1], - kp_invincibility[7], - kp_banana[1], - kp_eggman[1], - kp_orbinaut[4], - kp_jawz[1], - kp_mine[1], - kp_landmine[1], - kp_ballhog[1], - kp_selfpropelledbomb[1], - kp_grow[1], - kp_shrink[1], - kp_lightningshield[1], - kp_bubbleshield[1], - kp_flameshield[1], - kp_hyudoro[1], - kp_pogospring[1], - kp_superring[1], - kp_kitchensink[1], - kp_droptarget[1], - kp_gardentop[1], - - kp_sneaker[1], - kp_sneaker[1], - kp_banana[1], - kp_orbinaut[4], - kp_orbinaut[4], - kp_jawz[1] - }; - itemroulette_t rouletteData = {0}; const fixed_t scale = (FRACUNIT >> 1); @@ -4991,7 +4981,8 @@ static void K_drawDistributionDebugger(void) y = -pad; } - V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, patches[item], NULL); + V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, + K_GetSmallStaticCachedItemPatch(item), NULL); // Display amount for multi-items amount = K_ItemResultToAmount(item); From 724c9b774ec0c5ffa3756b15921f80106aed50d4 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 5 Jan 2023 23:32:12 +0000 Subject: [PATCH 125/128] Cleaner reference handling for the following extern-scope netsynced `mobj_t*`. - `skyboxcenterpnts` and `skyboxviewpnts` - P_InitSkyboxPoint`, which calls `P_SetTarget` - `waypoint->mobj` - Make sure NULL before using `P_SetTarget` on - `tubewaypoints` - Use `P_SetTarget` - Use `UINT32_MAX` for the invalid mobjnum, since 0 is a valid one. - `waypointcap` and `kitemcap` - NULL before reset Our international nightmare is over. --- src/k_waypoint.c | 1 + src/p_mobj.c | 27 ++++++++++++++++----------- src/p_saveg.c | 31 +++++++++++++++++++------------ src/p_setup.c | 2 +- src/p_spec.h | 2 ++ 5 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/k_waypoint.c b/src/k_waypoint.c index d53a4bbd3..cf70c1dcd 100644 --- a/src/k_waypoint.c +++ b/src/k_waypoint.c @@ -1949,6 +1949,7 @@ static waypoint_t *K_MakeWaypoint(mobj_t *const mobj) madewaypoint = &waypointheap[numwaypoints]; numwaypoints++; + madewaypoint->mobj = NULL; P_SetTarget(&madewaypoint->mobj, mobj); // Don't allow a waypoint that has its next ID set to itself to work diff --git a/src/p_mobj.c b/src/p_mobj.c index 230454a6c..e8fb3c30e 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12557,6 +12557,21 @@ static mobj_t *P_MakeSoftwareCorona(mobj_t *mo, INT32 height) return corona; } +void P_InitSkyboxPoint(mobj_t *mobj, mapthing_t *mthing) +{ + mtag_t tag = Tag_FGet(&mthing->tags); + if (tag < 0 || tag > 15) + { + CONS_Debug(DBG_GAMELOGIC, "P_InitSkyboxPoint: Skybox ID %d of mapthing %s is not between 0 and 15!\n", tag, sizeu1((size_t)(mthing - mapthings))); + return; + } + + if (mthing->args[0]) + P_SetTarget(&skyboxcenterpnts[tag], mobj); + else + P_SetTarget(&skyboxviewpnts[tag], mobj); +} + static boolean P_MapAlreadyHasStarPost(mobj_t *mobj) { thinker_t *th; @@ -12602,17 +12617,7 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean } case MT_SKYBOX: { - mtag_t tag = Tag_FGet(&mthing->tags); - if (tag < 0 || tag > 15) - { - CONS_Debug(DBG_GAMELOGIC, "P_SetupSpawnedMapThing: Skybox ID %d of mapthing %s is not between 0 and 15!\n", tag, sizeu1((size_t)(mthing - mapthings))); - break; - } - - if (mthing->args[0]) - skyboxcenterpnts[tag] = mobj; - else - skyboxviewpnts[tag] = mobj; + P_InitSkyboxPoint(mobj, mthing); break; } case MT_EGGSTATUE: diff --git a/src/p_saveg.c b/src/p_saveg.c index 1fd515a1f..8accfa309 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -1070,7 +1070,12 @@ static void P_NetArchiveTubeWaypoints(savebuffer_t *save) { WRITEUINT16(save->p, numtubewaypoints[i]); for (j = 0; j < numtubewaypoints[i]; j++) - WRITEUINT32(save->p, tubewaypoints[i][j] ? tubewaypoints[i][j]->mobjnum : 0); + { + if (tubewaypoints[i][j]) + WRITEUINT32(save->p, tubewaypoints[i][j]->mobjnum); + else + WRITEUINT32(save->p, UINT32_MAX); + } } } @@ -1085,7 +1090,9 @@ static void P_NetUnArchiveTubeWaypoints(savebuffer_t *save) for (j = 0; j < numtubewaypoints[i]; j++) { mobjnum = READUINT32(save->p); - tubewaypoints[i][j] = (mobjnum == 0) ? NULL : P_FindNewPosition(mobjnum); + tubewaypoints[i][j] = NULL; + if (mobjnum != UINT32_MAX) + P_SetTarget(&tubewaypoints[i][j], P_FindNewPosition(mobjnum)); } } } @@ -3068,6 +3075,7 @@ static void P_NetUnArchiveWaypoints(savebuffer_t *save) for (i = 0U; i < numArchiveWaypoints; i++) { waypoint = K_GetWaypointFromIndex(i); temp = READUINT32(save->p); + waypoint->mobj = NULL; if (!P_SetTarget(&waypoint->mobj, P_FindNewPosition(temp))) { CONS_Debug(DBG_GAMELOGIC, "waypoint mobj not found for %d\n", i); } @@ -3453,17 +3461,9 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) mobj->player->viewz = mobj->player->mo->z + mobj->player->viewheight; } - if (mobj->type == MT_SKYBOX) + if (mobj->type == MT_SKYBOX && mobj->spawnpoint) { - mtag_t tag = mobj->movedir; - if (tag < 0 || tag > 15) - { - CONS_Debug(DBG_GAMELOGIC, "LoadMobjThinker: Skybox ID %d of netloaded object is not between 0 and 15!\n", tag); - } - else if (mobj->flags2 & MF2_AMBUSH) - skyboxcenterpnts[tag] = mobj; - else - skyboxviewpnts[tag] = mobj; + P_InitSkyboxPoint(mobj, mobj->spawnpoint); } if (diff2 & MD2_WAYPOINTCAP) @@ -4105,6 +4105,13 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save) iquetail = iquehead = 0; P_InitThinkers(); + // Oh my god don't blast random memory with our reference counts. + waypointcap = kitemcap = NULL; + for (i = 0; i <= 15; i++) + { + skyboxcenterpnts[i] = skyboxviewpnts[i] = NULL; + } + // clear sector thinker pointers so they don't point to non-existant thinkers for all of eternity for (i = 0; i < numsectors; i++) { diff --git a/src/p_setup.c b/src/p_setup.c index 42ff2f33d..d77a27971 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -184,7 +184,7 @@ UINT16 numtubewaypoints[NUMTUBEWAYPOINTSEQUENCES]; void P_AddTubeWaypoint(UINT8 sequence, UINT8 id, mobj_t *waypoint) { - tubewaypoints[sequence][id] = waypoint; + P_SetTarget(&tubewaypoints[sequence][id], waypoint); if (id >= numtubewaypoints[sequence]) numtubewaypoints[sequence] = id + 1; } diff --git a/src/p_spec.h b/src/p_spec.h index 1982892d0..6f4d36f87 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -24,6 +24,8 @@ extern "C" { extern mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs extern mobj_t *skyboxcenterpnts[16]; // array of MT_SKYBOX centerpoint mobjs +void P_InitSkyboxPoint(mobj_t *mobj, mapthing_t *mthing); + // Amount (dx, dy) vector linedef is shifted right to get scroll amount #define SCROLL_SHIFT 5 From ede0f568191388a6a41a0c974d125772ac8b3e70 Mon Sep 17 00:00:00 2001 From: toaster Date: Thu, 5 Jan 2023 23:44:40 +0000 Subject: [PATCH 126/128] Revert all instances of P_SaveBufferFree(&demobuf) for now g_demo.c is officially No Man's Land --- src/g_demo.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/g_demo.c b/src/g_demo.c index a0d8fd2fb..a90771e48 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -2013,7 +2013,7 @@ void G_RecordDemo(const char *name) maxsize = atoi(M_GetNextParm()) * 1024; // if (demobuf.buffer) -// P_SaveBufferFree(&demobuf); +// Z_Free(demobuf.buffer); P_SaveBufferAlloc(&demobuf, maxsize); demobuf.p = NULL; @@ -3020,7 +3020,7 @@ void G_DoPlayDemo(char *defdemoname) CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3040,7 +3040,7 @@ void G_DoPlayDemo(char *defdemoname) CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3058,7 +3058,7 @@ void G_DoPlayDemo(char *defdemoname) CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3078,7 +3078,7 @@ void G_DoPlayDemo(char *defdemoname) CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3136,7 +3136,7 @@ void G_DoPlayDemo(char *defdemoname) if (!CON_Ready()) // In the console they'll just see the notice there! No point pulling them out. M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3151,7 +3151,7 @@ void G_DoPlayDemo(char *defdemoname) CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); Z_Free(pdemoname); - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3187,7 +3187,7 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3205,7 +3205,7 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3260,7 +3260,7 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3278,7 +3278,7 @@ void G_DoPlayDemo(char *defdemoname) Z_Free(demo.skinlist); demo.skinlist = NULL; Z_Free(pdemoname); - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demo.playback = false; demo.title = false; return; @@ -3934,7 +3934,7 @@ ATTRNORETURN void FUNCNORETURN G_StopMetalRecording(boolean kill) WriteDemoChecksum(); saved = FIL_WriteFile(va("%sMS.LMP", G_BuildMapName(gamemap)), demobuf.buffer, demobuf.p - demobuf.buffer); // finally output the file. } - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); metalrecording = false; if (saved) I_Error("Saved to %sMS.LMP", G_BuildMapName(gamemap)); @@ -4006,7 +4006,7 @@ static void G_StopTimingDemo(void) // called from stopdemo command, map command, and g_checkdemoStatus. void G_StopDemo(void) { - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demobuf.buffer = NULL; demo.playback = false; if (demo.title) @@ -4154,7 +4154,7 @@ void G_SaveDemo(void) if (FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer)) // finally output the file. demo.savemode = DSM_SAVED; - P_SaveBufferFree(&demobuf); + Z_Free(demobuf.buffer); demo.recording = false; if (!modeattacking) From 22b87e0f198f320feb8d0c96f51d7151782cc078 Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 6 Jan 2023 00:01:58 +0000 Subject: [PATCH 127/128] Consistency for mobjnum 0 meaning NULL Moves `P_Net(Un)ArchiveTubeWaypoints` down the file to take advantage of `SaveMobjnum` inline. --- src/p_saveg.c | 71 ++++++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/src/p_saveg.c b/src/p_saveg.c index 8accfa309..2fc6e3783 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -1062,41 +1062,6 @@ static void P_NetUnArchiveColormaps(savebuffer_t *save) net_colormaps = NULL; } -static void P_NetArchiveTubeWaypoints(savebuffer_t *save) -{ - INT32 i, j; - - for (i = 0; i < NUMTUBEWAYPOINTSEQUENCES; i++) - { - WRITEUINT16(save->p, numtubewaypoints[i]); - for (j = 0; j < numtubewaypoints[i]; j++) - { - if (tubewaypoints[i][j]) - WRITEUINT32(save->p, tubewaypoints[i][j]->mobjnum); - else - WRITEUINT32(save->p, UINT32_MAX); - } - } -} - -static void P_NetUnArchiveTubeWaypoints(savebuffer_t *save) -{ - INT32 i, j; - UINT32 mobjnum; - - for (i = 0; i < NUMTUBEWAYPOINTSEQUENCES; i++) - { - numtubewaypoints[i] = READUINT16(save->p); - for (j = 0; j < numtubewaypoints[i]; j++) - { - mobjnum = READUINT32(save->p); - tubewaypoints[i][j] = NULL; - if (mobjnum != UINT32_MAX) - P_SetTarget(&tubewaypoints[i][j], P_FindNewPosition(mobjnum)); - } - } -} - /// /// World Archiving /// @@ -3084,6 +3049,38 @@ static void P_NetUnArchiveWaypoints(savebuffer_t *save) } } +static void P_NetArchiveTubeWaypoints(savebuffer_t *save) +{ + INT32 i, j; + + for (i = 0; i < NUMTUBEWAYPOINTSEQUENCES; i++) + { + WRITEUINT16(save->p, numtubewaypoints[i]); + for (j = 0; j < numtubewaypoints[i]; j++) + { + WRITEUINT32(save->p, SaveMobjnum(tubewaypoints[i][j])); + } + } +} + +static void P_NetUnArchiveTubeWaypoints(savebuffer_t *save) +{ + INT32 i, j; + UINT32 mobjnum; + + for (i = 0; i < NUMTUBEWAYPOINTSEQUENCES; i++) + { + numtubewaypoints[i] = READUINT16(save->p); + for (j = 0; j < numtubewaypoints[i]; j++) + { + mobjnum = READUINT32(save->p); + tubewaypoints[i][j] = NULL; + if (mobjnum != 0) + P_SetTarget(&tubewaypoints[i][j], P_FindNewPosition(mobjnum)); + } + } +} + // Now save the pointers, tracer and target, but at load time we must // relink to this; the savegame contains the old position in the pointer // field copyed in the info field temporarily, but finally we just search @@ -3104,7 +3101,7 @@ mobj_t *P_FindNewPosition(UINT32 oldposition) return mobj; } - CONS_Debug(DBG_GAMELOGIC, "mobj not found\n"); + CONS_Debug(DBG_GAMELOGIC, "mobj %d not found\n", oldposition); return NULL; } @@ -5118,7 +5115,7 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) { thinker_t *th; mobj_t *mobj; - INT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise + UINT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise CV_SaveNetVars(&save->p); P_NetArchiveMisc(save, resending); From 16ac11d76e84942ffa8b180e19c30d7c1269d1d7 Mon Sep 17 00:00:00 2001 From: James R Date: Thu, 5 Jan 2023 18:06:50 -0800 Subject: [PATCH 128/128] sdl/i_main.cpp: guard entire init_exchndl behind _WIN32 Fixes -Wunused-function --- src/sdl/i_main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp index 44c685061..90b1a6a96 100644 --- a/src/sdl/i_main.cpp +++ b/src/sdl/i_main.cpp @@ -198,9 +198,9 @@ static void InitLogging(void) } #endif +#ifdef _WIN32 static void init_exchndl() { -#ifdef _WIN32 HMODULE exchndl_module = LoadLibraryA("exchndl.dll"); if (exchndl_module != NULL) { @@ -210,8 +210,8 @@ static void init_exchndl() if (pfnExcHndlInit != NULL) (pfnExcHndlInit)(); } -#endif } +#endif #ifdef _WIN32 static void