diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 55c445453..59b594a04 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -50,6 +50,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 p_floor.c p_inter.c p_lights.c + p_link.cpp p_loop.c p_map.c p_mapthing.cpp @@ -600,7 +601,8 @@ if((CMAKE_COMPILER_IS_GNUCC) AND NOT ("${CMAKE_SYSTEM_NAME}" MATCHES Darwin)) message(STATUS "Will make separate debug symbols in *.debug") add_custom_command(TARGET SRB2SDL2 POST_BUILD COMMAND ${OBJCOPY} ${OBJCOPY_ONLY_KEEP_DEBUG} $ $.debug - COMMAND ${OBJCOPY} --strip-debug $ + # mold linker: .gnu_debuglink is present by default, so --add-gnu-debuglink would fail + COMMAND ${OBJCOPY} --strip-debug --remove-section=.gnu_debuglink $ COMMAND ${OBJCOPY} --add-gnu-debuglink=$.debug $ ) endif() diff --git a/src/audio/chunk_load.cpp b/src/audio/chunk_load.cpp index c076c1bdb..782b70ca3 100644 --- a/src/audio/chunk_load.cpp +++ b/src/audio/chunk_load.cpp @@ -214,7 +214,12 @@ optional try_load_gme(tcb::span data) optional srb2::audio::try_load_chunk(tcb::span data) { - optional ret; + optional ret = nullopt; + + if (data.size() == 0) + { + return ret; + } ret = try_load_dmx(data); if (ret) @@ -232,5 +237,5 @@ optional srb2::audio::try_load_chunk(tcb::span data) if (ret) return ret; - return nullopt; + return ret; } diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 8edba171b..c2b7756a6 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -2190,9 +2190,9 @@ void D_MapChange(UINT16 mapnum, INT32 newgametype, boolean pencoremode, boolean } } -void D_SetupVote(void) +void D_SetupVote(INT16 newgametype) { - UINT8 buf[(VOTE_NUM_LEVELS * 2) + 2]; // four UINT16 maps (at twice the width of a UINT8), and two gametypes + UINT8 buf[(VOTE_NUM_LEVELS * 2) + 4]; UINT8 *p = buf; INT32 i; @@ -2200,13 +2200,14 @@ void D_SetupVote(void) UINT16 votebuffer[VOTE_NUM_LEVELS + 1]; memset(votebuffer, UINT16_MAX, sizeof(votebuffer)); + WRITEINT16(p, newgametype); WRITEUINT8(p, ((cv_kartencore.value == 1) && (gametyperules & GTR_ENCORE))); WRITEUINT8(p, G_SometimesGetDifferentEncore()); for (i = 0; i < VOTE_NUM_LEVELS; i++) { UINT16 m = G_RandMap( - G_TOLFlag(gametype), + G_TOLFlag(newgametype), prevmap, false, (i < VOTE_NUM_LEVELS-1), votebuffer @@ -5305,6 +5306,7 @@ static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum) static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) { + INT16 newGametype = 0; boolean baseEncore = false; boolean optionalEncore = false; INT16 tempVoteLevels[VOTE_NUM_LEVELS][2]; @@ -5320,6 +5322,7 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) return; } + newGametype = READINT16(*cp); baseEncore = (boolean)READUINT8(*cp); optionalEncore = (boolean)READUINT8(*cp); @@ -5329,6 +5332,17 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) baseEncore = optionalEncore = false; } + if (newGametype < 0 || newGametype >= numgametypes) + { + if (server) + { + I_Error("Got_SetupVotecmd: Gametype %d out of range (numgametypes = %d)", newGametype, numgametypes); + } + + CONS_Alert(CONS_WARNING, M_GetText("Vote setup with bad gametype %d received from %s\n"), newGametype, player_names[playernum]); + return; + } + for (i = 0; i < VOTE_NUM_LEVELS; i++) { tempVoteLevels[i][0] = (UINT16)READUINT16(*cp); @@ -5348,6 +5362,12 @@ static void Got_SetupVotecmd(UINT8 **cp, INT32 playernum) return; } + { + INT16 oldGametype = gametype; + G_SetGametype(newGametype); + D_GameTypeChanged(oldGametype); + } + if (optionalEncore == true) { tempVoteLevels[VOTE_NUM_LEVELS - 1][1] ^= VOTE_MOD_ENCORE; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index a2129195f..2a8b3bed7 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -241,7 +241,7 @@ void Command_Retry_f(void); boolean G_GamestateUsesExitLevel(void); void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore void D_MapChange(UINT16 pmapnum, INT32 pgametype, boolean pencoremode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pforcespecialstage); -void D_SetupVote(void); +void D_SetupVote(INT16 newgametype); void D_ModifyClientVote(UINT8 player, SINT8 voted); void D_PickVote(void); void ObjectPlace_OnChange(void); diff --git a/src/deh_lua.c b/src/deh_lua.c index db3d4cbc5..d1c7d4fea 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -431,6 +431,11 @@ static inline int lib_getenum(lua_State *L) } else if (fastncmp("S_",word,2)) { p = word+2; + if (fastcmp(p, "FIRSTFREESLOT")) + { + lua_pushinteger(L, S_FIRSTFREESLOT); + return 1; + } for (i = 0; i < NUMSTATEFREESLOTS; i++) { if (!FREE_STATES[i]) break; @@ -448,6 +453,11 @@ static inline int lib_getenum(lua_State *L) } else if (fastncmp("MT_",word,3)) { p = word+3; + if (fastcmp(p, "FIRSTFREESLOT")) + { + lua_pushinteger(L, MT_FIRSTFREESLOT); + return 1; + } for (i = 0; i < NUMMOBJFREESLOTS; i++) { if (!FREE_MOBJS[i]) break; @@ -465,6 +475,11 @@ static inline int lib_getenum(lua_State *L) } else if (fastncmp("SPR_",word,4)) { p = word+4; + if (fastcmp(p, "FIRSTFREESLOT")) + { + lua_pushinteger(L, SPR_FIRSTFREESLOT); + return 1; + } for (i = 0; i < NUMSPRITES; i++) if (!sprnames[i][4] && fastncmp(p,sprnames[i],4)) { lua_pushinteger(L, i); diff --git a/src/deh_tables.c b/src/deh_tables.c index 8d767ddf1..4e3e668ed 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4831,6 +4831,39 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi // MT_IVOBALL "S_IVOBALL", + + "S_SA2_CRATE_DEBRIS", + "S_SA2_CRATE_DEBRIS_E", + "S_SA2_CRATE_DEBRIS_F", + "S_SA2_CRATE_DEBRIS_G", + "S_SA2_CRATE_DEBRIS_H", + "S_SA2_CRATE_DEBRIS_METAL", + + "S_ICECAPBLOCK_DEBRIS", + "S_ICECAPBLOCK_DEBRIS_C", + "S_ICECAPBLOCK_DEBRIS_D", + "S_ICECAPBLOCK_DEBRIS_E", + "S_ICECAPBLOCK_DEBRIS_F", + + // MT_SPEAR + "S_SPEAR_ROD", + "S_SPEAR_TIP", + "S_SPEAR_HILT_FRONT", + "S_SPEAR_HILT_BACK", + "S_SPEAR_WALL", + + // MT_BSZLAMP_S + "S_BLMS", + "S_BLMM", + "S_BLML", + + // MT_BSZSLAMP + "S_BSWL", + "S_BSWC", + + "S_BETA_PARTICLE_WHEEL", + "S_BETA_PARTICLE_ICON", + "S_BETA_PARTICLE_EXPLOSION", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -6057,6 +6090,24 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_IVOBALL", "MT_PATROLIVOBALL", "MT_AIRIVOBALL", + + "MT_BOX_SIDE", + "MT_BOX_DEBRIS", + "MT_SA2_CRATE", + "MT_ICECAPBLOCK", + + "MT_SPEAR", + "MT_SPEARVISUAL", + "MT_BSZLAMP_S", + "MT_BSZLAMP_M", + "MT_BSZLAMP_L", + "MT_BSZSLAMP", + "MT_BSZSLCHA", + + "MT_BETA_EMITTER", + "MT_BETA_PARTICLE_PHYSICAL", + "MT_BETA_PARTICLE_VISUAL", + "MT_BETA_PARTICLE_EXPLOSION", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/doomstat.h b/src/doomstat.h index 300558131..06a38d724 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -42,8 +42,8 @@ extern "C" { // Selected by user. extern INT16 gamemap; +extern boolean g_reloadingMap; extern char mapmusname[7]; -extern UINT16 mapmusflags; extern UINT32 mapmusposition; extern UINT32 mapmusresume; extern UINT8 mapmusrng; diff --git a/src/f_finale.c b/src/f_finale.c index 2db2dd90c..66eea452d 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1536,6 +1536,7 @@ void F_StartTitleScreen(void) gamestate_t prevwipegamestate = wipegamestate; titlemapinaction = true; gamemap = titleMapNum+1; + g_reloadingMap = false; G_DoLoadLevelEx(true, GS_TITLESCREEN); if (!titlemap) @@ -1576,6 +1577,7 @@ void F_StartTitleScreen(void) G_SetGamestate(GS_TITLESCREEN); titlemapinaction = false; gamemap = 1; // g_game.c + g_reloadingMap = false; CON_ClearHUD(); } diff --git a/src/g_game.c b/src/g_game.c index 36b7a56e6..055e9b225 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -105,12 +105,12 @@ static void G_DoWorldDone(void); static void G_DoStartVote(void); char mapmusname[7]; // Music name -UINT16 mapmusflags; // Track and reset bit UINT32 mapmusposition; // Position to jump to UINT32 mapmusresume; UINT8 mapmusrng; // Random selection result -INT16 gamemap = 1; +INT16 gamemap = 0; +boolean g_reloadingMap; UINT32 maptol; preciptype_t globalweather = PRECIP_NONE; @@ -3062,7 +3062,7 @@ static gametype_t defaultgametypes[] = { "Special", "GT_SPECIAL", - GTR_CATCHER|GTR_SPECIALSTART|GTR_ROLLINGSTART|GTR_CIRCUIT, + GTR_CATCHER|GTR_SPECIALSTART|GTR_ROLLINGSTART|GTR_CIRCUIT|GTR_NOPOSITION, TOL_SPECIAL, int_time, 0, @@ -3152,7 +3152,7 @@ INT32 G_GuessGametypeByTOL(UINT32 tol) // void G_SetGametype(INT16 gtype) { - if (gtype < 0 || gtype > numgametypes) + if (gtype < 0 || gtype >= numgametypes) { I_Error("G_SetGametype: Bad gametype change %d (was %d/\"%s\")", gtype, gametype, gametypes[gametype]->name); } @@ -4385,7 +4385,7 @@ static void G_DoStartVote(void) { if (gamestate == GS_VOTING) I_Error("G_DoStartVote: NEXTMAP_VOTING causes recursive vote!"); - D_SetupVote(); + D_SetupVote(gametype); } gameaction = ga_nothing; } @@ -5930,11 +5930,9 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr } } + g_reloadingMap = (map == gamemap); gamemap = map; - // Don't carry over custom music change to another map. - mapmusflags |= MUSIC_RELOADRESET; - automapactive = false; imcontinuing = false; diff --git a/src/info.c b/src/info.c index e66a1e06b..7dfefaade 100644 --- a/src/info.c +++ b/src/info.c @@ -986,6 +986,21 @@ char sprnames[NUMSPRITES + 1][5] = "FBTN", "SFTR", + "SABX", + "ICBL", + + "BSSP", + "BSPB", + "BSPR", + "BSSR", + "BLMS", + "BLMM", + "BLML", + "BSWL", + "BSWC", + + "LCLA", + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -5677,6 +5692,39 @@ state_t states[NUMSTATES] = // MT_IVOBALL {SPR_BSPH, 2|FF_SEMIBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_IVOBALL + + {SPR_UNKN, FF_FULLBRIGHT, -1, {A_RandomStateRange}, S_SA2_CRATE_DEBRIS_E, S_SA2_CRATE_DEBRIS_H, S_NULL}, // S_SA2_CRATE_DEBRIS + {SPR_SABX, 4, 70, {NULL}, 0, 0, S_NULL}, // S_SA2_CRATE_DEBRIS_E + {SPR_SABX, 5, 70, {NULL}, 0, 0, S_NULL}, // S_SA2_CRATE_DEBRIS_F + {SPR_SABX, 6, 70, {NULL}, 0, 0, S_NULL}, // S_SA2_CRATE_DEBRIS_G + {SPR_SABX, 7, 70, {NULL}, 0, 0, S_NULL}, // S_SA2_CRATE_DEBRIS_H + {SPR_SABX, 12, 70, {NULL}, 0, 0, S_NULL}, // S_SA2_CRATE_DEBRIS_METAL + + {SPR_UNKN, FF_FULLBRIGHT, -1, {A_RandomStateRange}, S_ICECAPBLOCK_DEBRIS_C, S_ICECAPBLOCK_DEBRIS_F, S_NULL}, // S_ICECAPBLOCK_DEBRIS + {SPR_ICBL, 2, 70, {NULL}, 0, 0, S_NULL}, // S_ICECAPBLOCK_DEBRIS_C + {SPR_ICBL, 3, 70, {NULL}, 0, 0, S_NULL}, // S_ICECAPBLOCK_DEBRIS_D + {SPR_ICBL, 4, 70, {NULL}, 0, 0, S_NULL}, // S_ICECAPBLOCK_DEBRIS_E + {SPR_ICBL, 5, 70, {NULL}, 0, 0, S_NULL}, // S_ICECAPBLOCK_DEBRIS_F + + // MT_SPEAR + {SPR_BSSP, 1|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_SPEAR_ROD + {SPR_BSSP, 2|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_SPEAR_TIP + {SPR_BSPR, 0|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_SPEAR_HILT_FRONT + {SPR_BSPB, 0|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_SPEAR_HILT_BACK + {SPR_BSSR, 0|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_SPEAR_WALL + + // MT_BSZLAMP_S + {SPR_BLMS, 0|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BLMS + {SPR_BLMM, 0|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BLMM + {SPR_BLML, 0|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BLML + + // MT_BSZSLAMP + {SPR_BSWL, 0|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BSWL + {SPR_BSWC, 0|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BSWC + + {SPR_LCLA, 0|FF_FULLBRIGHT|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BETA_PARTICLE_WHEEL + {SPR_LCLA, 1|FF_FULLBRIGHT|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_BETA_PARTICLE_ICON + {SPR_LCLA, 2|FF_FULLBRIGHT, -1, {NULL}, 0, 0, S_NULL}, // S_BETA_PARTICLE_EXPLOSION }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -24855,7 +24903,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 16, // mass 0, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags S_NULL // raisestate }, @@ -24882,7 +24930,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 16, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_NOCLIP|MF_NOCLIPTHING|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + MF_SOLID|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags S_NULL // raisestate }, @@ -24909,7 +24957,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 16, // mass 0, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags S_NULL // raisestate }, @@ -24936,7 +24984,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 0, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags S_NULL // raisestate }, @@ -24963,7 +25011,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 0, // damage sfx_None, // activesound - MF_NOBLOCKMAP|MF_NOCLIP|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags S_NULL // raisestate }, @@ -30257,7 +30305,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = 100, // mass 0, // damage sfx_None, // activesound - MF_SOLID|MF_SHOOTABLE|MF_DONTENCOREMAP, // flags + MF_SOLID|MF_SHOOTABLE|MF_DONTENCOREMAP|MF_DONTPUNT, // flags S_NULL // raisestate }, @@ -30632,8 +30680,8 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_None, // deathsound 0, // speed - 0, // radius - 0, // height + 40*FRACUNIT, // radius + 80*FRACUNIT, // height 0, // display offset 100, // mass 1, // damage @@ -32200,6 +32248,397 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_SCENERY|MF_SPECIAL|MF_NOGRAVITY, // flags S_NULL // raisestate }, + { // MT_BOX_SIDE + -1, // doomednum + S_INVISIBLE, // spawnstate + 1000, // 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 + 40*FRACUNIT, // radius + 80*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOCLIPTHING, // flags + S_NULL // raisestate + }, + { // MT_BOX_DEBRIS + -1, // doomednum + S_INVISIBLE, // spawnstate + 1000, // 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 + 0*FRACUNIT, // radius + 0*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOCLIPTHING, // flags + S_NULL // raisestate + }, + { // MT_SA2_CRATE + 2529, // 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 + 40*FRACUNIT, // radius + 80*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_SOLID|MF_SHOOTABLE|MF_SCENERY|MF_DONTPUNT, // flags + S_NULL // raisestate + }, + { // MT_ICECAPBLOCK + 3750, // 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 + 40*FRACUNIT, // radius + 80*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_SOLID|MF_SHOOTABLE|MF_SCENERY|MF_DONTPUNT, // flags + S_NULL // raisestate + }, + + { // MT_SPEAR + 3450, // doomednum + S_SPEAR_ROD, // spawnstate + 1000, // 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 + 64*FRACUNIT, // radius + 80*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_PAIN|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_PAPERCOLLISION|MF_SCENERY|MF_NOHITLAGFORME, // flags + S_NULL // raisestate + }, + { // MT_SPEARVISUAL + -1, // doomednum + S_UNKNOWN, // spawnstate + 1000, // 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 + 1*FRACUNIT, // radius + 1*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIPHEIGHT|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY|MF_NOBLOCKMAP, // flags + S_NULL // raisestate + }, + { // MT_BSZLAMP_S + 3452, // doomednum + S_BLMS, // 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, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + { // MT_BSZLAMP_M + 3453, // doomednum + S_BLMM, // 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, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + { // MT_BSZLAMP_L + 3454, // doomednum + S_BLML, // 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, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + { // MT_BSZSLAMP + 3469, // doomednum + S_BSWL, // 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 + 96*FRACUNIT, // radius + 128*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + { // MT_BSZSLCHA + 3470, // doomednum + S_BSWC, // 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 + 128*FRACUNIT, // radius + 128*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + 0, // flags + S_NULL // raisestate + }, + { // MT_BETA_EMITTER + 2699, // 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 + 0, // radius + 0, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOSECTOR|MF_SCENERY, // flags + S_NULL // raisestate + }, + { // MT_BETA_PARTICLE_PHYSICAL + -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 + 24*FRACUNIT, // radius + 128*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + { // MT_BETA_PARTICLE_VISUAL + -1, // doomednum + S_NULL, // 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 + 0, // radius + 0, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_NOCLIP|MF_SCENERY|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + { // MT_BETA_PARTICLE_EXPLOSION + -1, // doomednum + S_BETA_PARTICLE_EXPLOSION, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 8, // 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 + 40*FRACUNIT, // radius + 80*FRACUNIT, // height + 0, // display offset + 100, // mass + 1, // damage + sfx_None, // activesound + MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY|MF_DONTENCOREMAP|MF_NOHITLAGFORME|MF_SPECIAL|MF_DONTPUNT, // flags + S_NULL // raisestate + }, }; diff --git a/src/info.h b/src/info.h index 9e6d02d8d..339b261e0 100644 --- a/src/info.h +++ b/src/info.h @@ -1540,6 +1540,21 @@ typedef enum sprite SPR_FBTN, SPR_SFTR, + SPR_SABX, + SPR_ICBL, + + SPR_BSSP, + SPR_BSPB, + SPR_BSPR, + SPR_BSSR, + SPR_BLMS, + SPR_BLMM, + SPR_BLML, + SPR_BSWL, + SPR_BSWC, + + SPR_LCLA, + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -6102,6 +6117,39 @@ typedef enum state // MT_IVOBALL S_IVOBALL, + S_SA2_CRATE_DEBRIS, + S_SA2_CRATE_DEBRIS_E, + S_SA2_CRATE_DEBRIS_F, + S_SA2_CRATE_DEBRIS_G, + S_SA2_CRATE_DEBRIS_H, + S_SA2_CRATE_DEBRIS_METAL, + + S_ICECAPBLOCK_DEBRIS, + S_ICECAPBLOCK_DEBRIS_C, + S_ICECAPBLOCK_DEBRIS_D, + S_ICECAPBLOCK_DEBRIS_E, + S_ICECAPBLOCK_DEBRIS_F, + + // MT_SPEAR + S_SPEAR_ROD, + S_SPEAR_TIP, + S_SPEAR_HILT_FRONT, + S_SPEAR_HILT_BACK, + S_SPEAR_WALL, + + // MT_BSZLAMP_S + S_BLMS, + S_BLMM, + S_BLML, + + // MT_BSZSLAMP + S_BSWL, + S_BSWC, + + S_BETA_PARTICLE_WHEEL, + S_BETA_PARTICLE_ICON, + S_BETA_PARTICLE_EXPLOSION, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -7348,6 +7396,24 @@ typedef enum mobj_type MT_PATROLIVOBALL, MT_AIRIVOBALL, + MT_BOX_SIDE, + MT_BOX_DEBRIS, + MT_SA2_CRATE, + MT_ICECAPBLOCK, + + MT_SPEAR, + MT_SPEARVISUAL, + MT_BSZLAMP_S, + MT_BSZLAMP_M, + MT_BSZLAMP_L, + MT_BSZSLAMP, + MT_BSZSLCHA, + + MT_BETA_EMITTER, + MT_BETA_PARTICLE_PHYSICAL, + MT_BETA_PARTICLE_VISUAL, + MT_BETA_PARTICLE_EXPLOSION, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/io/streams.hpp b/src/io/streams.hpp index ede48f0a9..39f0b6299 100644 --- a/src/io/streams.hpp +++ b/src/io/streams.hpp @@ -431,10 +431,10 @@ public: head = offset; break; case SeekFrom::kEnd: - if (-offset >= static_cast(span_.size())) { + if (static_cast(span_.size()) + offset < 0) { throw std::logic_error("end offset is out of bounds"); } - head = span_.size() - offset; + head = span_.size() + offset; break; case SeekFrom::kCurrent: if (head_ + offset < 0) { @@ -526,10 +526,10 @@ public: head = offset; break; case SeekFrom::kEnd: - if (-offset >= static_cast(vec_.size())) { + if (static_cast(vec_.size()) + offset < 0) { throw std::logic_error("end offset is out of bounds"); } - head = vec_.size() - offset; + head = vec_.size() + offset; break; case SeekFrom::kCurrent: if (head_ + offset < 0) { diff --git a/src/k_battle.c b/src/k_battle.c index ee6bb180a..5fc1dc9e5 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -821,7 +821,7 @@ void K_BattleInit(boolean singleplayercontext) } g_battleufo.due = starttime; - g_battleufo.previousId = Obj_GetFirstBattleUFOSpawnerID(); + g_battleufo.previousId = Obj_RandomBattleUFOSpawnerID() - 1; } UINT8 K_Bumpers(player_t *player) diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 568f87338..8c4a0c078 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -1233,7 +1233,8 @@ void K_PuntHazard(mobj_t *t1, mobj_t *t2) boolean K_PuntCollide(mobj_t *t1, mobj_t *t2) { - if (t1->flags & MF_DONTPUNT) + // MF_SHOOTABLE will get damaged directly, instead + if (t1->flags & (MF_DONTPUNT | MF_SHOOTABLE)) { return false; } diff --git a/src/k_follower.c b/src/k_follower.c index f288d63f1..78e233e4c 100644 --- a/src/k_follower.c +++ b/src/k_follower.c @@ -419,6 +419,7 @@ void K_HandleFollower(player_t *player) K_UpdateFollowerState(player->follower, fl->idlestate, FOLLOWERSTATE_IDLE); P_SetTarget(&player->follower->target, player->mo); // we need that to know when we need to disappear + P_SetTarget(&player->follower->punt_ref, player->mo); player->follower->angle = player->follower->old_angle = player->mo->angle; // This is safe to only spawn it here, the follower is removed then respawned when switched. @@ -427,10 +428,12 @@ void K_HandleFollower(player_t *player) bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_FRONT); P_SetTarget(&player->follower->hnext, bmobj); P_SetTarget(&bmobj->target, player->follower); // Used to know if we have to despawn at some point. + P_SetTarget(&bmobj->punt_ref, player->mo); bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_BACK); P_SetTarget(&player->follower->hnext->hnext, bmobj); // this seems absolutely stupid, I know, but this will make updating the momentums/flags of these a bit easier. P_SetTarget(&bmobj->target, player->follower); // Ditto + P_SetTarget(&bmobj->punt_ref, player->mo); } } else // follower exists, woo! diff --git a/src/k_kart.c b/src/k_kart.c index 119619bd0..6f57ffe2b 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -3914,9 +3914,22 @@ void K_RemoveGrowShrink(player_t *player) player->roundconditions.consecutive_grow_lasers = 0; } +// Should this object actually scale check? +// Scale-to-scale comparisons only make sense for objects that expect to have dynamic scale. +static boolean K_IsScaledItem(mobj_t *mobj) +{ + return mobj && !P_MobjWasRemoved(mobj) && + (mobj->type == MT_ORBINAUT || mobj->type == MT_JAWZ || mobj->type == MT_GACHABOM + || mobj->type == MT_BANANA || mobj->type == MT_EGGMANITEM || mobj->type == MT_BALLHOG + || mobj->type == MT_SSMINE || mobj->type == MT_LANDMINE || mobj->type == MT_SINK + || mobj->type == MT_GARDENTOP || mobj->type == MT_DROPTARGET || mobj->type == MT_PLAYER); +} + + boolean K_IsBigger(mobj_t *compare, mobj_t *other) { fixed_t compareScale, otherScale; + const fixed_t requiredDifference = (mapobjectscale/4); if ((compare == NULL || P_MobjWasRemoved(compare) == true) || (other == NULL || P_MobjWasRemoved(other) == true)) @@ -3924,6 +3937,18 @@ boolean K_IsBigger(mobj_t *compare, mobj_t *other) return false; } + // If a player is colliding with a non-kartitem object, we don't care about what scale that object is: + // mappers are liable to fuck with the scale for their own reasons, and we need to compare against the + // player's base scale instead to match expectations. + if (K_IsScaledItem(compare) != K_IsScaledItem(other)) + { + if (compare->type == MT_PLAYER) + return (compare->scale > requiredDifference + FixedMul(mapobjectscale, P_GetMobjDefaultScale(compare))); + else if (other->type == MT_PLAYER) + return false; // Haha what the fuck are you doing + // fallthrough + } + if ((compareScale = P_GetMobjDefaultScale(compare)) != FRACUNIT) { compareScale = FixedDiv(compare->scale, compareScale); @@ -3942,7 +3967,7 @@ boolean K_IsBigger(mobj_t *compare, mobj_t *other) otherScale = other->scale; } - return (compareScale > otherScale + (mapobjectscale / 4)); + return (compareScale > otherScale + requiredDifference); } static fixed_t K_TumbleZ(mobj_t *mo, fixed_t input) @@ -8057,6 +8082,9 @@ static void K_UpdateTripwire(player_t *player) P_SetTarget(&front->target, player->mo); P_SetTarget(&back->target, player->mo); + P_SetTarget(&front->punt_ref, player->mo); + P_SetTarget(&back->punt_ref, player->mo); + front->dispoffset = 1; front->old_angle = back->old_angle = K_MomentumAngle(player->mo); back->dispoffset = -1; @@ -13160,6 +13188,11 @@ boolean K_IsPlayingDisplayPlayer(player_t *player) boolean K_PlayerCanPunt(player_t *player) { + if (player->trickpanel > TRICKSTATE_READY) + { + return true; + } + if (player->invincibilitytimer > 0) { return true; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 70ce57187..c24c752fb 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4548,12 +4548,14 @@ void M_DrawVideoModes(void) (SCR_IsAspectCorrect(cv_scr_width.value, cv_scr_height.value)) ? 0x83 : 0x80, cv_scr_width.value, cv_scr_height.value)); - V_DrawCenteredString(BASEVIDWIDTH/2 + menutransition.tics*64, currentMenu->y + 75+16, - recommendedflags, "Marked modes are recommended."); V_DrawCenteredString(BASEVIDWIDTH/2 + menutransition.tics*64, currentMenu->y + 75+24, - highlightflags, "Other modes may have visual errors."); - V_DrawCenteredString(BASEVIDWIDTH/2 + menutransition.tics*64, currentMenu->y + 75+32, - highlightflags, "Larger modes may have performance issues."); + recommendedflags, "Modes marked in GREEN are recommended."); + /* + V_DrawCenteredString(BASEVIDWIDTH/2 + menutransition.tics*64, currentMenu->y + 75+16, + highlightflags, "High resolutions stress your PC more, but will"); + V_DrawCenteredString(BASEVIDWIDTH/2 + menutransition.tics*64, currentMenu->y + 75+24, + highlightflags, "look sharper. Balance visual quality and FPS!"); + */ } // Draw the cursor for the VidMode menu diff --git a/src/k_objects.h b/src/k_objects.h index 51e7eae96..c564cc2a9 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -184,8 +184,7 @@ void Obj_BattleUFODeath(mobj_t *ufo); void Obj_LinkBattleUFOSpawner(mobj_t *spawner); void Obj_UnlinkBattleUFOSpawner(mobj_t *spawner); void Obj_SpawnBattleUFOFromSpawner(void); -INT32 Obj_GetFirstBattleUFOSpawnerID(void); -void Obj_ResetUFOSpawners(void); +INT32 Obj_RandomBattleUFOSpawnerID(void); void Obj_BattleUFOBeamThink(mobj_t *beam); /* Power-Up Aura */ @@ -230,7 +229,6 @@ void Obj_FakeShadowThink(mobj_t *shadow); boolean Obj_FakeShadowZ(const mobj_t *shadow, fixed_t *return_z, pslope_t **return_slope); /* Checkpoints */ -void Obj_ResetCheckpoints(void); void Obj_LinkCheckpoint(mobj_t *end); void Obj_UnlinkCheckpoint(mobj_t *end); void Obj_CheckpointThink(mobj_t *end); @@ -321,6 +319,26 @@ void Obj_PatrolIvoBallInit(mobj_t *mo); void Obj_PatrolIvoBallThink(mobj_t *mo); void Obj_PatrolIvoBallTouch(mobj_t *special, mobj_t *toucher); +/* SA2 Crates / Ice Cap Blocks */ +void Obj_BoxSideThink(mobj_t *mo); +void Obj_TryCrateInit(mobj_t *mo); +boolean Obj_TryCrateThink(mobj_t *mo); +void Obj_TryCrateTouch(mobj_t *special, mobj_t *toucher); +void Obj_TryCrateDamage(mobj_t *target, mobj_t *inflictor); + +/* Lavender Shrine Spears */ +void Obj_SpearInit(mobj_t *mo); +void Obj_SpearThink(mobj_t *mo); + +/* Lost Colony Fuel Canister */ +void Obj_FuelCanisterEmitterInit(mobj_t *mo); +boolean Obj_FuelCanisterVisualThink(mobj_t *mo); +boolean Obj_FuelCanisterEmitterThink(mobj_t *mo); +boolean Obj_FuelCanisterThink(mobj_t *mo); +void Obj_FuelCanisterTouch(mobj_t *special, mobj_t *toucher); +void Obj_FuelCanisterExplosionTouch(mobj_t *special, mobj_t *toucher); +boolean Obj_FuelCanisterExplosionThink(mobj_t *mo); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_podium.cpp b/src/k_podium.cpp index 6635071a3..072a4e075 100644 --- a/src/k_podium.cpp +++ b/src/k_podium.cpp @@ -940,6 +940,7 @@ boolean K_StartCeremony(void) && mapheaderinfo[podiumMapNum]->lumpnum != LUMPERROR) { gamemap = podiumMapNum+1; + g_reloadingMap = false; encoremode = grandprixinfo.encore; @@ -1034,8 +1035,6 @@ void K_ResetCeremony(void) { mapmusrng--; } - - mapmusflags |= MUSIC_RELOADRESET; } if (!grandprixinfo.cup) diff --git a/src/lua_script.c b/src/lua_script.c index bd54fac61..d47df24dc 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -299,9 +299,6 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"mapmusname")) { lua_pushstring(L, mapmusname); return 1; - } else if (fastcmp(word,"mapmusflags")) { - lua_pushinteger(L, mapmusflags); - return 1; } else if (fastcmp(word,"mapmusposition")) { lua_pushinteger(L, mapmusposition); return 1; @@ -424,8 +421,6 @@ int LUA_WriteGlobals(lua_State *L, const char *word) strncpy(mapmusname, str, strlength); } - else if (fastcmp(word, "mapmusflags")) - mapmusflags = (UINT16)luaL_checkinteger(L, 2); else if (fastcmp(word, "mapmusrng")) mapmusrng = (UINT8)luaL_checkinteger(L, 2); // SRB2Kart diff --git a/src/math/vec.hpp b/src/math/vec.hpp index 670a3677c..935725b3c 100644 --- a/src/math/vec.hpp +++ b/src/math/vec.hpp @@ -22,9 +22,9 @@ struct Vec2 { T x, y; - Vec2() : x{}, y{} {} - Vec2(T x_, T y_) : x(x_), y(y_) {} - Vec2(T z) : x(z), y(z) {} + constexpr Vec2() : x{}, y{} {} + constexpr Vec2(T x_, T y_) : x(x_), y(y_) {} + constexpr Vec2(T z) : x(z), y(z) {} template Vec2(const Vec2& b) : Vec2(b.x, b.y) {} diff --git a/src/menus/options-gameplay-1.c b/src/menus/options-gameplay-1.c index c1eaf7cb4..74217467e 100644 --- a/src/menus/options-gameplay-1.c +++ b/src/menus/options-gameplay-1.c @@ -33,7 +33,7 @@ menuitem_t OPTIONS_Gameplay[] = {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, - {IT_STRING | IT_CVAR, "Minimum Input Delay", "Practice for online play! Higher = more delay.", + {IT_STRING | IT_CVAR, "Minimum Input Delay", "Practice for online play! Higher = more delay, 0 = instant response.", NULL, {.cvar = &cv_mindelay}, 0, 0}, {IT_SPACE | IT_NOTHING, NULL, NULL, diff --git a/src/menus/options-video-modes.c b/src/menus/options-video-modes.c index 37c53e5b6..a7648b7d6 100644 --- a/src/menus/options-video-modes.c +++ b/src/menus/options-video-modes.c @@ -7,7 +7,7 @@ menuitem_t OPTIONS_VideoModes[] = { - {IT_KEYHANDLER | IT_NOTHING, NULL, "Select a resolution.", + {IT_KEYHANDLER | IT_NOTHING, NULL, "Select a resolution. Higher = sharper game, lower = higher FPS.", NULL, {.routine = M_HandleVideoModes}, 0, 0}, // dummy menuitem for the control func }; diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 15bb8d162..cdd0b1cf8 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -1,6 +1,7 @@ /// \file menus/transient/pause-game.c /// \brief In-game/pause menus +#include "../../d_netcmd.h" #include "../../k_menu.h" #include "../../k_grandprix.h" // K_CanChangeRules #include "../../m_cond.h" @@ -332,7 +333,10 @@ void M_HandlePauseMenuGametype(INT32 choice) if (menugametype != gametype) { M_ClearMenus(true); - COM_ImmedExecute(va("randommap -gt %s", gametypes[menugametype]->name)); + if (server || IsPlayerAdmin(consoleplayer)) + { + D_SetupVote(menugametype); + } return; } diff --git a/src/mobj.hpp b/src/mobj.hpp index fc78f480a..ce947b851 100644 --- a/src/mobj.hpp +++ b/src/mobj.hpp @@ -13,8 +13,12 @@ #include #include "math/fixed.hpp" +#include "math/line_segment.hpp" #include "math/vec.hpp" +#include "doomtype.h" +#include "k_hitlag.h" +#include "k_kart.h" #include "info.h" #include "p_local.h" #include "p_mobj.h" @@ -27,16 +31,20 @@ namespace srb2 struct Mobj : mobj_t { + using fixed = math::Fixed; + using line_segment = math::LineSegment; + using vec2 = math::Vec2; + // TODO: Vec3 would be nice struct PosArg { - math::Fixed x, y, z; + fixed x, y, z; PosArg() : PosArg(0, 0, 0) {} - PosArg(fixed_t x_, fixed_t y_, fixed_t z_) : x(x_), y(y_), z(z_) {} + PosArg(fixed x_, fixed y_, fixed z_) : x(x_), y(y_), z(z_) {} template - PosArg(math::Vec2 p, fixed_t z) : PosArg(p.x, p.y, z) {} + PosArg(math::Vec2 p, fixed z) : PosArg(p.x, p.y, z) {} PosArg(const mobj_t* mobj) : PosArg(mobj->x, mobj->y, mobj->z) {} }; @@ -97,19 +105,26 @@ struct Mobj : mobj_t PosArg center() const { return {x, y, z + (height / 2)}; } PosArg pos() const { return {x, y, z}; } - math::Vec2 pos2d() const { return {x, y}; } - math::Fixed top() const { return z + height; } + vec2 pos2d() const { return {x, y}; } + fixed top() const { return z + height; } bool is_flipped() const { return eflags & MFE_VERTICALFLIP; } - math::Fixed flip(fixed_t x) const { return x * P_MobjFlip(this); } + fixed flip(fixed x) const { return x * P_MobjFlip(this); } // Collision helper bool z_overlaps(const Mobj* b) const { return z < b->top() && b->z < top(); } void move_origin(const PosArg& p) { P_MoveOrigin(this, p.x, p.y, p.z); } void set_origin(const PosArg& p) { P_SetOrigin(this, p.x, p.y, p.z); } - void instathrust(angle_t angle, fixed_t speed) { P_InstaThrust(this, angle, speed); } - void thrust(angle_t angle, fixed_t speed) { P_Thrust(this, angle, speed); } + void instathrust(angle_t angle, fixed speed) { P_InstaThrust(this, angle, speed); } + void thrust(angle_t angle, fixed speed) { P_Thrust(this, angle, speed); } + + static void bounce(Mobj* t1, Mobj* t2) { K_KartBouncing(t1, t2); } + void solid_bounce(Mobj* solid) { K_KartSolidBounce(this, solid); } + + // A = bottom left corner + // this->aabb; the standard bounding box. This is inapproporiate for paper collision! + line_segment aabb() const { return {{x - radius, y - radius}, {x + radius, y + radius}}; } // @@ -150,15 +165,15 @@ struct Mobj : mobj_t // Scale // - math::Fixed scale() const { return mobj_t::scale; } + fixed scale() const { return mobj_t::scale; } - void scale(fixed_t n) + void scale(fixed n) { - mobj_t::scale = n; + P_SetScale(this, n); mobj_t::destscale = n; } - void scale_to(fixed_t stop, std::optional speed = {}) + void scale_to(fixed stop, std::optional speed = {}) { mobj_t::destscale = stop; @@ -168,13 +183,68 @@ struct Mobj : mobj_t } } - void scale_between(fixed_t start, fixed_t stop, std::optional speed = {}) + void scale_between(fixed start, fixed stop, std::optional speed = {}) { - mobj_t::scale = start; + P_SetScale(this, start); scale_to(stop, speed); } + // + // Sprite offsets + // + +#define FIXED_METHOD(member) \ + fixed member() const { return mobj_t::member; } \ + void member(fixed n) { mobj_t::member = n; } + + FIXED_METHOD(spritexscale) + FIXED_METHOD(spriteyscale) + FIXED_METHOD(spritexoffset) + FIXED_METHOD(spriteyoffset) + FIXED_METHOD(sprxoff) + FIXED_METHOD(spryoff) + FIXED_METHOD(sprzoff) + + vec2 spritescale() const { return {spritexscale(), spriteyscale()}; } + void spritescale(const vec2& v) + { + spritexscale(v.x); + spriteyscale(v.y); + } + + vec2 spriteoffset() const { return {spritexoffset(), spriteyoffset()}; } + void spriteoffset(const vec2& v) + { + spritexoffset(v.x); + spriteyoffset(v.y); + } + + vec2 sproff2d() const { return {sprxoff(), spryoff()}; } + void sproff2d(const vec2& v) + { + sprxoff(v.x); + spryoff(v.y); + } + + void linkdraw(bool n) { flags2 = n ? flags2 | MF2_LINKDRAW : flags2 & ~MF2_LINKDRAW; } + + // WARNING: sets tracer! + void linkdraw(Mobj* parent) + { + tracer(parent); + linkdraw(true); + } + + void linkdraw(Mobj* parent, INT32 offset) + { + linkdraw(parent); + dispoffset = offset; + } + + // TODO: Vec3 + + // // Sound // @@ -194,6 +264,18 @@ struct Mobj : mobj_t voice(sfx, volume); } } + + + // + // Hitlag + // + + INT32 hitlag() const { return mobj_t::hitlag; } + void hitlag(INT32 tics, bool damage = false) { K_AddHitLag(this, tics, damage); } + void hitlag(Mobj* inflictor, Mobj* source, INT32 tics, bool damage) + { + K_SetHitLagForObjects(this, inflictor, source, tics, damage); + } }; }; // namespace srb2 diff --git a/src/mobj_list.hpp b/src/mobj_list.hpp new file mode 100644 index 000000000..ad9ac8964 --- /dev/null +++ b/src/mobj_list.hpp @@ -0,0 +1,86 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// 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 mobj_list_hpp +#define mobj_list_hpp + +#include + +#include "cxxutil.hpp" +#include "mobj.hpp" +#include "mobj_list_view.hpp" + +namespace srb2 +{ + +// Requires: +// void T::next(T*) +// T* T::next() const +template +struct MobjList +{ + static_assert(std::is_convertible_v); + + MobjList() {} + + T* front() const { return static_cast(Head); } + bool empty() const { return !front(); } + + void push_front(T* ptr) + { + ptr->next(front()); + front(ptr); + } + + void erase(T* node) + { + if (front() == node) + { + front(node->next()); + node->next(nullptr); + return; + } + + auto view = this->view(); + auto end = view.end(); + auto it = view.begin(); + + SRB2_ASSERT(it != end); + + for (;;) + { + T* prev = *it; + + it++; + + if (it == end) + { + break; + } + + if (*it == node) + { + prev->next(node->next()); + node->next(nullptr); + break; + } + } + } + + auto begin() const { return view().begin(); } + auto end() const { return view().end(); } + +private: + void front(T* ptr) { Mobj::ManagedPtr {Head} = ptr; } + auto view() const { return MobjListView(front(), [](T* node) { return node->next(); }); } +}; + +}; // namespace srb2 + +#endif/*mobj_list_hpp*/ diff --git a/src/mobj_list_view.hpp b/src/mobj_list_view.hpp new file mode 100644 index 000000000..122ec6684 --- /dev/null +++ b/src/mobj_list_view.hpp @@ -0,0 +1,84 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// 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 mobj_list_view_hpp +#define mobj_list_view_hpp + +#include +#include +#include + +#include "p_mobj.h" + +namespace srb2 +{ + +// for (T* ptr : MobjList(hnext(), [](T* ptr) { return ptr->hnext(); })) +template +struct MobjListView +{ + static_assert(std::is_convertible_v); + + struct Iterator + { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = T*; + using pointer = value_type; + using reference = value_type; + + Iterator(pointer ptr, F adv) : ptr_(deref(ptr)), adv_(adv) {} + Iterator& operator=(const Iterator& b) + { + // adv_ may be a lambda. However, lambdas are not + // copy assignable. Therefore, perform copy + // construction instead! + this->~Iterator(); + return *new(this) Iterator {b}; + } + + bool operator==(const Iterator& b) const { return ptr_ == b.ptr_; }; + bool operator!=(const Iterator& b) const { return ptr_ != b.ptr_; }; + + reference operator*() const { return ptr_; } + pointer operator->() { return ptr_; } + + Iterator& operator++() + { + ptr_ = deref(adv_(ptr_)); + return *this; + } + + Iterator operator++(int) + { + Iterator prev = *this; + ++(*this); + return prev; + } + + private: + pointer ptr_; + F adv_; + + static T* deref(T* ptr) { return !P_MobjWasRemoved(ptr) ? ptr : nullptr; } + }; + + MobjListView(T* ptr, F adv) : ptr_(ptr), adv_(adv) {} + + Iterator begin() const { return {ptr_, adv_}; } + Iterator end() const { return {nullptr, adv_}; } + +private: + T* ptr_; + F adv_; +}; + +}; // namespace srb2 + +#endif/*mobj_list_view_hpp*/ diff --git a/src/music.cpp b/src/music.cpp index b95d74b4a..00b200a21 100644 --- a/src/music.cpp +++ b/src/music.cpp @@ -35,6 +35,12 @@ void Music_Init(void) tune.nightcoreable = true; } + { + Tune& tune = g_tunes.insert("level_nosync", g_tunes.find("level")); + + tune.sync = false; + } + { Tune& tune = g_tunes.insert("position"); @@ -133,9 +139,9 @@ void Music_Init(void) } { - Tune& tune = g_tunes.insert("menu_nocred"); + Tune& tune = g_tunes.insert("menu_nocred", g_tunes.find("menu")); - tune.priority = 100; + tune.credit = false; } { @@ -165,14 +171,10 @@ void Music_Init(void) } { - Tune& tune = g_tunes.insert("stereo_fade"); + Tune& tune = g_tunes.insert("stereo_fade", g_tunes.find("stereo")); - tune.priority = 1000; tune.fade_out = 5000; tune.fade_out_inclusive = false; - tune.resist = true; - tune.keep_open = true; - tune.credit = true; } } @@ -350,3 +352,13 @@ const char* Music_CurrentId(void) { return g_tunes.current_id(); } + +void Music_BatchExempt(const char* id) +{ + Tune* tune = g_tunes.find(id); + + if (tune) + { + tune->resist_once = true; + } +} diff --git a/src/music.h b/src/music.h index c7712a472..a85b1913f 100644 --- a/src/music.h +++ b/src/music.h @@ -92,6 +92,10 @@ void Music_Remap(const char *id, const char *song); // Set whether a tune should loop. void Music_Loop(const char *id, boolean loop); +// Temporarily exemplify a tune from batch operations, such +// as Music_StopAll. +void Music_BatchExempt(const char *id); + // // Query properties. diff --git a/src/music_manager.hpp b/src/music_manager.hpp index 5ac215878..38110d46c 100644 --- a/src/music_manager.hpp +++ b/src/music_manager.hpp @@ -44,9 +44,9 @@ public: return it != map_.end() ? const_cast(&it->second) : nullptr; } - Tune& insert(const char* id) + Tune& insert(const char* id, const Tune* original = nullptr) { - auto res = map_.emplace(id, Tune{}); + auto res = map_.emplace(id, original ? *original : Tune{}); SRB2_ASSERT(res.second); @@ -73,6 +73,12 @@ public: { for (auto& [_, tune] : map_) { + if (tune.resist_once) + { + tune.resist_once = false; + continue; + } + if (!tune.resist) { f(tune); diff --git a/src/music_tune.hpp b/src/music_tune.hpp index b22efb23d..ec9aac6dd 100644 --- a/src/music_tune.hpp +++ b/src/music_tune.hpp @@ -57,6 +57,7 @@ public: // from TuneManager::stop_all etc. It must be // stopped/paused individually. bool resist = false; + bool resist_once = false; // set at runtime // This tune shows a credit when first played (not // resumed). diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 6a420e1cf..3048568ea 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -8,7 +8,7 @@ target_sources(SRB2SDL2 PRIVATE orbinaut.c jawz.c duel-bomb.c - broly.c + broly.cpp ufo.c monitor.c item-spot.c @@ -44,6 +44,9 @@ target_sources(SRB2SDL2 PRIVATE mega-barrier.cpp frost-thrower.cpp ivoball.cpp + crate.cpp + spear.cpp + fuel.cpp ) add_subdirectory(versus) diff --git a/src/objects/battle-ufo.cpp b/src/objects/battle-ufo.cpp index 2ccbb78da..bbefa9672 100644 --- a/src/objects/battle-ufo.cpp +++ b/src/objects/battle-ufo.cpp @@ -1,6 +1,10 @@ #include +#include #include -#include + +#include "../math/fixed.hpp" +#include "../mobj.hpp" +#include "../mobj_list.hpp" #include "../doomdef.h" #include "../m_random.h" @@ -9,101 +13,96 @@ #include "../k_objects.h" #include "../k_kart.h" +using srb2::math::Fixed; +using srb2::Mobj; +using srb2::MobjList; + +extern mobj_t* svg_battleUfoSpawners; + #define BATTLEUFO_LEG_ZOFFS (3*FRACUNIT) // Spawn height offset from the body #define BATTLEUFO_LEGS (3) // Number of UFO legs to spawn #define BATTLEUFO_BOB_AMP (4) // UFO bob strength #define BATTLEUFO_BOB_SPEED (TICRATE*2) // UFO bob speed -#define spawner_id(o) ((o)->thing_args[0]) - -#define ufo_spawner(o) ((o)->target) - namespace { -struct Spawner : mobj_t +struct Spawner : Mobj { - INT32 id() const { return spawner_id(this); } + void thing_args() = delete; + INT32 id() const { return this->mobj_t::thing_args[0]; } + + void hnext() = delete; + Spawner* next() const { return Mobj::hnext(); } + void next(Spawner* n) { Mobj::hnext(n); } }; -struct UFO : mobj_t +struct UFO : Mobj { - Spawner* spawner() const { return static_cast(ufo_spawner(this)); } - void spawner(Spawner* n) { P_SetTarget(&ufo_spawner(this), n); } + void target() = delete; + Spawner* spawner() const { return Mobj::target(); } + void spawner(Spawner* n) { Mobj::target(n); } + void spawn_beam() { - mobj_t *x; + Mobj *x = spawn_from({0, 0, height / 4}, MT_BATTLEUFO_BEAM); - x = P_SpawnMobjFromMobj(this, 0, 0, FixedDiv(this->height / 4, this->scale), MT_BATTLEUFO_BEAM); x->renderflags |= RF_FLOORSPRITE|RF_NOSPLATBILLBOARD|RF_SLOPESPLAT|RF_NOSPLATROLLANGLE; x->colorized = true; x->color = SKINCOLOR_SAPPHIRE; } -}; -struct SpawnerCompare -{ - bool operator()(const Spawner* a, const Spawner* b) const + void bob() { - return a->id() < b->id(); + // Copied and slightly modified from k_kart.c + Fixed sine = (BATTLEUFO_BOB_AMP * Fixed {FSIN(M_TAU_FIXED * BATTLEUFO_BOB_SPEED * leveltime)}) / 4; + + momz = flip(sine * scale()); } }; class SpawnerList { private: - std::set set_; + MobjList list_; public: - void insert(Spawner* spawner) - { - auto [it, inserted] = set_.insert(spawner); - - if (inserted) - { - mobj_t* dummy = nullptr; - P_SetTarget(&dummy, spawner); - } - } - - void erase(Spawner* spawner) - { - if (set_.erase(spawner)) - { - mobj_t* dummy = spawner; - P_SetTarget(&dummy, nullptr); - } - } + void insert(Spawner* spawner) { list_.push_front(spawner); } + void erase(Spawner* spawner) { list_.erase(spawner); } Spawner* next(INT32 order) const { - auto it = std::upper_bound( - set_.begin(), - set_.end(), - order, - [](INT32 a, const Spawner* b) { return a < b->id(); } - ); + using T = const Spawner*; - return it != set_.end() ? *it : *set_.begin(); + auto it = std::find_if(list_.begin(), list_.end(), [order](T p) { return order < p->id(); }); + auto min = [&](auto cmp) { return std::min_element(list_.begin(), list_.end(), cmp); }; + + return *(it != list_.end() + ? min([order](T a, T b) { return order < a->id() && a->id() < b->id(); }) + : min([](T a, T b) { return a->id() < b->id(); })); } INT32 random_id() const { - if (set_.empty()) + if (list_.empty()) { return 0; } - auto it = set_.begin(); + auto it = list_.begin(); + std::size_t count = std::distance(it, list_.end()); - std::advance(it, P_RandomKey(PR_BATTLEUFO, set_.size())); + if (count > 1u) + { + std::advance(it, P_RandomKey(PR_BATTLEUFO, count - 1u)); + } - return (*std::prev(it == set_.begin() ? set_.end() : it))->id(); + return it->id(); } void spawn_ufo() const { - if (set_.empty()) + if (list_.empty()) { return; } @@ -123,10 +122,7 @@ void Obj_BattleUFOThink(mobj_t *mobj) { UFO* ufo = static_cast(mobj); - // Copied and slightly modified from k_kart.c - fixed_t sine = FixedMul(ufo->scale, BATTLEUFO_BOB_AMP * FINESINE((((M_TAU_FIXED * BATTLEUFO_BOB_SPEED) * leveltime) >> ANGLETOFINESHIFT) & FINEMASK)); - fixed_t targz = FixedMul(ufo->scale, sine) * P_MobjFlip(ufo); - ufo->momz = targz; + ufo->bob(); if ((leveltime/2) & 1) { @@ -227,16 +223,11 @@ void Obj_SpawnBattleUFOFromSpawner(void) g_spawners.spawn_ufo(); } -INT32 Obj_GetFirstBattleUFOSpawnerID(void) +INT32 Obj_RandomBattleUFOSpawnerID(void) { return g_spawners.random_id(); } -void Obj_ResetUFOSpawners(void) -{ - g_spawners = {}; -} - void Obj_BattleUFOBeamThink(mobj_t *beam) { P_SetObjectMomZ(beam, beam->info->speed, true); diff --git a/src/objects/broly.c b/src/objects/broly.c deleted file mode 100644 index 2419fac10..000000000 --- a/src/objects/broly.c +++ /dev/null @@ -1,77 +0,0 @@ -#include "../doomdef.h" -#include "../info.h" -#include "../k_kart.h" -#include "../k_objects.h" -#include "../m_easing.h" -#include "../p_local.h" -#include "../s_sound.h" - -/* An object may not be visible on the same tic: - 1) that it spawned - 2) that it cycles to the next state */ -#define BUFFER_TICS (2) - -#define broly_duration(o) ((o)->extravalue1) -#define broly_maxscale(o) ((o)->extravalue2) - -static inline fixed_t -get_unit_linear (const mobj_t *x) -{ - const tic_t t = (x->tics - BUFFER_TICS); - - return t * FRACUNIT / broly_duration(x); -} - -mobj_t * -Obj_SpawnBrolyKi -( mobj_t * source, - tic_t duration) -{ - mobj_t *x; - - if (duration <= 0) - { - return NULL; - } - - x = P_SpawnMobjFromMobj( - source, 0, 0, 0, MT_BROLY); - - P_SetTarget(&x->target, source); - - // Shrink into center of source object. - x->z = (source->z + source->height / 2); - - x->colorized = true; - x->color = source->color; - x->hitlag = 0; // do not copy source hitlag - - broly_maxscale(x) = 64 * mapobjectscale; - broly_duration(x) = duration; - - x->tics = (duration + BUFFER_TICS); - - K_ReduceVFXForEveryone(x); - - S_StartSound(x, sfx_cdfm74); - - return x; -} - -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; -} diff --git a/src/objects/broly.cpp b/src/objects/broly.cpp new file mode 100644 index 000000000..fe2f43726 --- /dev/null +++ b/src/objects/broly.cpp @@ -0,0 +1,38 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// 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 "broly.hpp" + +#include "../doomstat.h" +#include "../k_kart.h" +#include "../sounds.h" + +using namespace srb2::objects; + +mobj_t * +Obj_SpawnBrolyKi +( mobj_t * source, + tic_t duration) +{ + Broly* x = Broly::spawn(static_cast(source), duration, {64 * mapobjectscale, 0}); + + x->colorized = true; + x->color = source->color; + + K_ReduceVFXForEveryone(x); + x->voice(sfx_cdfm74); + + return x; +} + +boolean +Obj_BrolyKiThink (mobj_t *x) +{ + return static_cast(x)->think(); +} diff --git a/src/objects/broly.hpp b/src/objects/broly.hpp new file mode 100644 index 000000000..d75a7fc4b --- /dev/null +++ b/src/objects/broly.hpp @@ -0,0 +1,96 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// 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 objects_broly_hpp +#define objects_broly_hpp + +#include + +#include "objects.hpp" + +#include "../info.h" +#include "../m_easing.h" + +namespace srb2::objects +{ + +struct Broly : Mobj +{ + static constexpr mobjtype_t kMobjType = MT_BROLY; + + /* An object may not be visible on the same tic: + 1) that it spawned + 2) that it cycles to the next state */ + static constexpr int kBufferTics = 2; + + void extravalue1() = delete; + tic_t duration() const { return mobj_t::extravalue1; } + void duration(tic_t n) { mobj_t::extravalue1 = n; } + + void threshold() = delete; + void extravalue2() = delete; + Vec2 size() const { return {mobj_t::threshold, mobj_t::extravalue2}; } + void size(const Vec2& n) + { + mobj_t::threshold = n.x; + mobj_t::extravalue2 = n.y; + } + + bool valid() const { return duration(); } + + tic_t remaining() const { return tics - kBufferTics; } + + Fixed linear() const { return (remaining() * FRACUNIT) / duration(); } + + template + static T* spawn(Mobj* source, tic_t duration, const Vec2& size) + { + static_assert(std::is_base_of_v); + + if (duration == 0) + { + return nullptr; + } + + T* x = Mobj::spawn(source->center(), T::kMobjType); + + x->target(source); + + // Shrink into center of source object. + x->z -= x->height / 2; + + x->size(size); + x->duration(duration); + + x->tics = (duration + kBufferTics); + + return x; + } + + bool think() + { + if (!valid()) + { + remove(); + return false; + } + + const Fixed center = z + (height / 2); + const Vec2 v = size(); + + scale(Easing_OutSine(linear(), v.y, v.x)); + z = center - (height / 2); + + return true; + } +}; + +}; // namespace srb2::objects + +#endif/*objects_broly_hpp*/ diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index 903dcd349..361f22cc2 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -8,10 +8,11 @@ //----------------------------------------------------------------------------- #include -#include #include +#include "../mobj_list.hpp" + #include "../doomdef.h" #include "../doomtype.h" #include "../info.h" @@ -32,10 +33,13 @@ #include "../sounds.h" #include "../tables.h" +extern mobj_t* svg_checkpoints; + #define checkpoint_id(o) ((o)->thing_args[0]) #define checkpoint_other(o) ((o)->target) #define checkpoint_orb(o) ((o)->tracer) #define checkpoint_arm(o) ((o)->hnext) +#define checkpoint_next(o) ((o)->hprev) #define checkpoint_var(o) ((o)->movedir) #define checkpoint_speed(o) ((o)->movecount) #define checkpoint_speed_multiplier(o) ((o)->movefactor) @@ -120,6 +124,9 @@ struct Checkpoint : mobj_t Arm* arm() const { return static_cast(checkpoint_arm(this)); } void arm(Arm* n) { P_SetTarget(&checkpoint_arm(this), n); } + Checkpoint* next() const { return static_cast(checkpoint_next(this)); } + void next(Checkpoint* n) { P_SetTarget(&checkpoint_next(this), n); } + fixed_t var() const { return checkpoint_var(this); } void var(fixed_t n) { checkpoint_var(this) = n; } @@ -391,34 +398,23 @@ private: struct CheckpointManager { - auto begin() { return vec_.begin(); } - auto end() { return vec_.end(); } + auto begin() { return list_.begin(); } + auto end() { return list_.end(); } auto find(INT32 id) { return std::find_if(begin(), end(), [id](Checkpoint* chk) { return chk->id() == id; }); } - void push_back(Checkpoint* chk) { vec_.push_back(chk); } + void push_front(Checkpoint* chk) { list_.push_front(chk); } - void erase(const Checkpoint* chk) - { - if (auto it = std::find(vec_.begin(), vec_.end(), chk); it != end()) - { - vec_.erase(it); - } - } + void erase(Checkpoint* chk) { list_.erase(chk); } private: - std::vector vec_; + srb2::MobjList list_; }; CheckpointManager g_checkpoints; }; // namespace -void Obj_ResetCheckpoints(void) -{ - g_checkpoints = {}; -} - void Obj_LinkCheckpoint(mobj_t* end) { auto chk = static_cast(end); @@ -456,7 +452,7 @@ void Obj_LinkCheckpoint(mobj_t* end) } else { - g_checkpoints.push_back(chk); + g_checkpoints.push_front(chk); } chk->gingerbread(); @@ -464,7 +460,7 @@ void Obj_LinkCheckpoint(mobj_t* end) void Obj_UnlinkCheckpoint(mobj_t* end) { - auto chk = static_cast(end); + auto chk = static_cast(end); g_checkpoints.erase(chk); diff --git a/src/objects/crate.cpp b/src/objects/crate.cpp new file mode 100644 index 000000000..dafa06b72 --- /dev/null +++ b/src/objects/crate.cpp @@ -0,0 +1,391 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Kart Krew. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +// Original Lua script by Lat +// Hardcoded by jartha + +#include +#include +#include + +#include "../math/fixed.hpp" +#include "../math/line_segment.hpp" +#include "../math/vec.hpp" +#include "../mobj.hpp" +#include "../mobj_list_view.hpp" + +#include "../d_player.h" +#include "../doomtype.h" +#include "../info.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../m_random.h" +#include "../p_local.h" +#include "../p_maputl.h" +#include "../p_pspr.h" +#include "../r_defs.h" + +using srb2::math::Fixed; +using srb2::math::LineSegment; +using srb2::math::Vec2; +using srb2::Mobj; +using srb2::MobjListView; + +namespace +{ + +using frame_layout = std::array; // -z, +z, -x, -y, +x, +y + +struct SA2CrateConfig +{ + static constexpr spritenum_t kSprite = SPR_SABX; + static constexpr frame_layout kFrames = {3, 2, 0, 0, 0, 0}; + static constexpr statenum_t kDefaultDebris = S_SA2_CRATE_DEBRIS; +}; + +struct IceCapBlockConfig +{ + static constexpr spritenum_t kSprite = SPR_ICBL; + static constexpr frame_layout kFrames = {6, 6, 0, 0, 0, 0}; + static constexpr statenum_t kDefaultDebris = S_ICECAPBLOCK_DEBRIS; +}; + +struct Graphic : Mobj +{ + void hnext() = delete; + Graphic* next() const { return Mobj::hnext(); } + void next(Graphic* n) { Mobj::hnext(n); } + + Graphic* dress(spritenum_t sprite, UINT32 frame) + { + this->sprite = sprite; + this->frame = frame; + this->renderflags |= RF_NOSPLATBILLBOARD; + return this; + } + + Graphic* xy(fixed_t x, fixed_t y) + { + this->sproff2d({x, y}); + return this; + } + + Graphic* z(fixed_t z) + { + this->sprzoff(z); + return this; + } + + Graphic* turn(angle_t angle) + { + this->angle = angle; + return this; + } +}; + +struct Side : Graphic +{ + bool valid() const { return Mobj::valid() && Mobj::valid(owner()); } + + void think() + { + if (!valid()) + { + remove(); + return; + } + + move_origin(owner()); + renderflags = owner()->renderflags; + } +}; + +struct Toucher : Mobj +{ + bool boosting() const { return player && (player->sneakertimer || K_PlayerCanPunt(player)); } +}; + +struct AnyBox : Graphic +{ + template + bool visit(F&& visitor); + + void update() + { + visit([](auto box) { box->mobj_t::eflags &= ~MFE_ONGROUND; }); + } +}; + +template +struct Box : AnyBox +{ + static constexpr Fixed kIntendedSize = 128*FRACUNIT; + static constexpr Vec2 kScrunch = {4*FRACUNIT/5, 6*FRACUNIT/5}; + + void extravalue1() = delete; + statenum_t debris_state() const { return static_cast(mobj_t::extravalue1); } + void debris_state(statenum_t n) { mobj_t::extravalue1 = n; } + + auto gfx() { return MobjListView(static_cast(this), [](Graphic* g) { return g->next(); }); } + + void init() + { + scale(scale() * (kIntendedSize / Fixed {info->height})); + + Graphic* node = this; + int i = 0; + auto dress = [&](Graphic* g, UINT32 ff) { return g->dress(Config::kSprite, Config::kFrames[i++] | ff); }; + auto side = [&](UINT32 ff) + { + Side* side = spawn_from({}, MT_BOX_SIDE); + side->owner(this); + node->next(side); // link + node = side; + return dress(side, ff); + }; + + dress(this, FF_FLOORSPRITE); // bottom (me) + side(FF_FLOORSPRITE)->z(height); // top + + // sides + side(FF_PAPERSPRITE)->xy(-radius, 0)->turn(ANGLE_270); + side(FF_PAPERSPRITE)->xy(0, -radius); + side(FF_PAPERSPRITE)->xy(+radius, 0)->turn(ANGLE_90); + side(FF_PAPERSPRITE)->xy(0, +radius)->turn(ANGLE_180); + + debris_state(Config::kDefaultDebris); + } + + bool think() + { + if (fuse) + { + fuse--; + renderflags ^= RF_DONTDRAW; + + if (!fuse) + { + update_nearby(); + remove(); + return false; + } + } + + return true; + } + + void touch(Toucher* toucher) + { + if (fuse) + { + return; + } + + P_DamageMobj(this, toucher, nullptr, 1, DMG_NORMAL); + + if (!toucher->boosting()) + { + toucher->solid_bounce(this); + } + } + + bool damage_valid(const Mobj* inflictor) const { return !fuse && Mobj::valid(inflictor); } + + void damage(Mobj* inflictor) + { + if (!damage_valid(inflictor)) + { + return; + } + + inflictor->hitlag(3); + fuse = 10; + + // scrunch crate sides + for (Graphic* g : gfx()) + { + if (g->frame & FF_PAPERSPRITE) + { + g->frame++; + g->spritescale(kScrunch); + } + else + { + g->spritescale(kScrunch.x); + } + + // reset interp + g->mobj_t::old_spritexscale = g->spritexscale(); + g->mobj_t::old_spriteyscale = g->spriteyscale(); + + g->sproff2d(g->sproff2d() * kScrunch.x); + g->sprzoff(g->sprzoff() * kScrunch.y); + } + + debris(inflictor); + update_nearby(); + } + +private: + void debris(Mobj* inflictor) + { + if (debris_state() >= NUMSTATES) + { + return; + } + + auto rng = [&](int x, int y) { return P_RandomRange(PR_DECORATION, x, y) * scale(); }; + auto rng_xyz = [&](int x) { return std::tuple(rng(-x, x), rng(-x, x), rng(0, x)); }; + + auto spawn = [&] + { + auto [x, y, z] = rng_xyz(info->height / FRACUNIT); + Mobj* p = spawn_from({x, y, z}, MT_BOX_DEBRIS); + + p->scale_between(scale() / 2, scale()); + p->state(debris_state()); + + std::tie(x, y, z) = rng_xyz(4); + + p->momx = (inflictor->momx / 8) + x; + p->momy = (inflictor->momy / 8) + y; + p->momz = (Fixed::hypot(inflictor->momx, inflictor->momy) / 4) + z; + }; + + spawn(); + spawn(); + spawn(); + spawn(); + spawn(); + spawn(); + } + + void update_nearby() const + { + LineSegment search = aabb(); + Vec2 org{bmaporgx, bmaporgy}; + + search.a -= org + MAXRADIUS; + search.b -= org - MAXRADIUS; + + search.a.x = static_cast(search.a.x) >> MAPBLOCKSHIFT; + search.b.x = static_cast(search.b.x) >> MAPBLOCKSHIFT; + search.a.y = static_cast(search.a.y) >> MAPBLOCKSHIFT; + search.b.y = static_cast(search.b.y) >> MAPBLOCKSHIFT; + + BMBOUNDFIX(search.a.x, search.b.x, search.b.x, search.b.y); + + for (INT32 bx = search.a.x; bx <= search.b.x; ++bx) + { + for (INT32 by = search.a.y; by <= search.b.y; ++by) + { + P_BlockThingsIterator( + bx, + by, + [](mobj_t* thing) + { + static_cast(thing)->update(); + return BMIT_CONTINUE; + } + ); + } + } + } +}; + +struct Crate : Box +{ + static constexpr int kMetalFrameStart = 8; + + void thing_args() = delete; + bool metal() const { return mobj_t::thing_args[0]; } + + void init() + { + Box::init(); + + if (metal()) + { + for (Graphic* g : gfx()) + { + g->frame += kMetalFrameStart; + } + + debris_state(S_SA2_CRATE_DEBRIS_METAL); + } + } + + void damage(Toucher* inflictor) + { + if (!Box::damage_valid(inflictor)) + { + return; + } + + if (metal() && !inflictor->boosting()) + { + return; + } + + Box::damage(inflictor); + } +}; + +struct Ice : Box +{ +}; + +template +bool AnyBox::visit(F&& visitor) +{ + switch (type) + { + case MT_SA2_CRATE: + visitor(static_cast(this)); + break; + + case MT_ICECAPBLOCK: + visitor(static_cast(this)); + break; + + default: + return false; + } + + return true; +} + +}; // namespace + +void Obj_BoxSideThink(mobj_t* mobj) +{ + static_cast(mobj)->think(); +} + +void Obj_TryCrateInit(mobj_t* mobj) +{ + static_cast(mobj)->visit([&](auto box) { box->init(); }); +} + +boolean Obj_TryCrateThink(mobj_t* mobj) +{ + bool c = false; + static_cast(mobj)->visit([&](auto box) { c = box->think(); }); + return c; +} + +void Obj_TryCrateTouch(mobj_t* special, mobj_t* toucher) +{ + static_cast(special)->visit([&](auto box) { box->touch(static_cast(toucher)); }); +} + +void Obj_TryCrateDamage(mobj_t* target, mobj_t* inflictor) +{ + static_cast(target)->visit([&](auto box) { box->damage(static_cast(inflictor)); }); +} diff --git a/src/objects/emerald.c b/src/objects/emerald.c index 65b23edb6..e749efb92 100644 --- a/src/objects/emerald.c +++ b/src/objects/emerald.c @@ -276,6 +276,7 @@ static void spawn_lens_flare(mobj_t *emerald) void Obj_BeginEmeraldOrbit(mobj_t *emerald, mobj_t *target, fixed_t radius, INT32 revolution_time, tic_t fuse) { P_SetTarget(&emerald_orbit(emerald), target); + P_SetTarget(&emerald->punt_ref, target); if (P_MobjWasRemoved(emerald_award(emerald))) { diff --git a/src/objects/fuel.cpp b/src/objects/fuel.cpp new file mode 100644 index 000000000..53e4ccfcd --- /dev/null +++ b/src/objects/fuel.cpp @@ -0,0 +1,236 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// 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 "broly.hpp" +#include "objects.hpp" + +#include "../doomdef.h" +#include "../doomstat.h" +#include "../info.h" +#include "../k_objects.h" +#include "../sounds.h" +#include "../tables.h" + +using namespace srb2::objects; + +namespace +{ + +struct FuelCanister : Mobj +{ + struct Emitter : Mobj + { + void thing_args() = delete; + tic_t frequency() const { return mobj_t::thing_args[0]; } + tic_t initial_timer() const { return mobj_t::thing_args[1]; } + + void extravalue1() = delete; + tic_t timer() const { return mobj_t::extravalue1; } + void timer(tic_t n) { mobj_t::extravalue1 = n; } + + void init() + { + timer(initial_timer()); + } + + bool think() + { + if (timer() > 0) + { + timer(timer() - 1); + return true; + } + + timer(frequency()); + + FuelCanister::spawn(this); + + return true; + } + }; + + struct Vis : Mobj + { + void extravalue1() = delete; + angle_t phys_angle_ofs() const { return mobj_t::extravalue1; } + void phys_angle_ofs(angle_t n) { mobj_t::extravalue1 = n; } + + void extravalue2() = delete; + angle_t vis_angle_ofs() const { return mobj_t::extravalue2; } + void vis_angle_ofs(angle_t n) { mobj_t::extravalue2 = n; } + + bool valid() const { return Mobj::valid() && Mobj::valid(target()); } + + bool think() + { + if (!valid()) + { + remove(); + return false; + } + + const angle_t angleOutward = target()->angle + phys_angle_ofs(); + + move_origin({target()->pos2d() + (vector(angleOutward) * Fixed {radius}), target()->z}); + angle = angleOutward + vis_angle_ofs(); + + return true; + } + }; + + struct Explosion : Broly + { + static constexpr mobjtype_t kMobjType = MT_BETA_PARTICLE_EXPLOSION; + + static Explosion* spawn(Mobj* source) + { + Explosion* x = Broly::spawn(source, 3*TICRATE, {1, 8 * mapobjectscale}); + x->voice(sfx_lcfuel); + return x; + } + + void touch(Mobj* toucher) + { + if (!P_DamageMobj(toucher, this, this, 1, DMG_NORMAL)) + { + auto& hitlag = toucher->mobj_t::hitlag; + + // Hitlag = remaining duration of explosion + if (hitlag >= 0 && hitlag + 0u < remaining()) + { + hitlag = remaining(); + } + } + } + + bool think() { return Broly::think(); } + }; + + bool valid() const { return Mobj::valid() && momz; } + + static FuelCanister* spawn(Mobj* source) + { + FuelCanister* caps = source->spawn_from({}, MT_BETA_PARTICLE_PHYSICAL); + caps->init(); + return caps; + } + + void init() + { + momz = 8 * scale(); + z -= momz; + + pieces(); + pieces(); + } + + bool think() + { + if (!valid()) + { + remove(); + return false; + } + + angle += 8 * ANG1; + + return true; + } + + void touch(Mobj* toucher) + { + Explosion::spawn(toucher); + } + +private: + struct Wheel + { + static constexpr int kSides = 6; + static constexpr statenum_t kState = S_BETA_PARTICLE_WHEEL; + static constexpr int kRadius = 8; + static constexpr Fixed kScale = FRACUNIT; + static constexpr angle_t kAngleOffset = 0; + static constexpr int kZOffset = 0; + }; + + struct Icon + { + static constexpr int kSides = 2; + static constexpr statenum_t kState = S_BETA_PARTICLE_ICON; + static constexpr int kRadius = 8; + static constexpr Fixed kScale = 3*FRACUNIT/4; + static constexpr angle_t kAngleOffset = ANGLE_90; + static constexpr int kZOffset = 64; + }; + + static Vec2 vector(angle_t angle) { return {FCOS(angle), FSIN(angle)}; } + + template + void pieces() + { + constexpr angle_t kAngleBetween = ANGLE_MAX / Config::kSides; + + const Fixed zOfs = Config::kZOffset * (Fixed {FRACUNIT} / Config::kScale); + const Fixed radius = Config::kRadius * scale(); + const Fixed scale = Config::kScale * this->scale(); + + for (int i = 1; i <= Config::kSides; ++i) + { + angle_t angleOutward = i * kAngleBetween; + + Vis* vis = spawn_from({vector(angle + angleOutward) * radius, 0}, MT_BETA_PARTICLE_VISUAL); + + vis->state(Config::kState); + vis->target(this); + vis->scale(scale); + vis->radius = radius; + vis->spriteyoffset(zOfs); + + vis->phys_angle_ofs(angleOutward); + vis->vis_angle_ofs(Config::kAngleOffset); + } + } +}; + +}; // namespace + +void Obj_FuelCanisterEmitterInit(mobj_t *mo) +{ + static_cast(mo)->init(); +} + +boolean Obj_FuelCanisterVisualThink(mobj_t *mo) +{ + return static_cast(mo)->think(); +} + +boolean Obj_FuelCanisterEmitterThink(mobj_t *mo) +{ + return static_cast(mo)->think(); +} + +boolean Obj_FuelCanisterThink(mobj_t *mo) +{ + return static_cast(mo)->think(); +} + +void Obj_FuelCanisterTouch(mobj_t *special, mobj_t *toucher) +{ + static_cast(special)->touch(static_cast(toucher)); +} + +void Obj_FuelCanisterExplosionTouch(mobj_t *special, mobj_t *toucher) +{ + static_cast(special)->touch(static_cast(toucher)); +} + +boolean Obj_FuelCanisterExplosionThink(mobj_t *mo) +{ + return static_cast(mo)->think(); +} diff --git a/src/objects/ivoball.cpp b/src/objects/ivoball.cpp index fbcecede9..d3fd3e8ce 100644 --- a/src/objects/ivoball.cpp +++ b/src/objects/ivoball.cpp @@ -64,7 +64,7 @@ struct IvoBall : Mobj Fixed wave{(x / mapobjectscale) + (y / mapobjectscale)}; offset(wave / kRippleFactor); color = SKINCOLOR_TANGERINE; - sprzoff = kFloat * mapobjectscale; + sprzoff(kFloat * mapobjectscale); } void think() @@ -81,7 +81,7 @@ struct IvoBall : Mobj fixed_t ballTimer = leveltime + offset(); Fixed bob = kBobHeight * Fixed {FSIN((M_TAU_FIXED * kBobTime) * ballTimer)}; - spriteyoffset = bob; + spriteyoffset(bob); colorized = !((ballTimer / kFlashTime) & 1); } diff --git a/src/objects/objects.hpp b/src/objects/objects.hpp new file mode 100644 index 000000000..dd8869ccc --- /dev/null +++ b/src/objects/objects.hpp @@ -0,0 +1,19 @@ +#ifndef objects_objects_hpp +#define objects_objects_hpp + +#include "../math/fixed.hpp" +#include "../math/vec.hpp" +#include "../mobj.hpp" + +#include "../k_objects.h" + +namespace srb2::objects +{ + +using srb2::Mobj; +using srb2::math::Fixed; +using srb2::math::Vec2; + +}; // namespace srb2::objects + +#endif/*objects_objects_hpp*/ diff --git a/src/objects/spear.cpp b/src/objects/spear.cpp new file mode 100644 index 000000000..4998c1cdc --- /dev/null +++ b/src/objects/spear.cpp @@ -0,0 +1,179 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Kart Krew. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +// Original Lua script by Lat +// Hardcoded by jartha + +#include + +#include "../math/fixed.hpp" +#include "../math/vec.hpp" +#include "../mobj.hpp" +#include "../mobj_list_view.hpp" + +#include "../doomstat.h" +#include "../doomtype.h" +#include "../info.h" +#include "../k_objects.h" +#include "../tables.h" + +using srb2::math::Fixed; +using srb2::math::Vec2; +using srb2::Mobj; +using srb2::MobjListView; + +namespace +{ + +Vec2 angle_vector(angle_t x) +{ + return Vec2 {FCOS(x), FSIN(x)}; +} + +struct Spear : Mobj +{ + enum Mode + { + kWait, + kShake, + kPush, + kPull, + kNumModes, + }; + + static constexpr tic_t kWaitTimes[kNumModes] = { + TICRATE, + TICRATE/2, + TICRATE/2, + TICRATE, + }; + + void extravalue1() = delete; + int mode() const { return mobj_t::extravalue1; } + void mode(int n) + { + mobj_t::extravalue1 = n; + timer(kWaitTimes[n]); + } + + void extravalue2() = delete; + tic_t timer() const { return mobj_t::extravalue2; } + void timer(tic_t n) { mobj_t::extravalue2 = n; } + + void threshold() = delete; + Fixed dist() const { return mobj_t::threshold; } + void dist(Fixed n) { mobj_t::threshold = n; } + + void thing_args() = delete; + bool delayed_start() const { return mobj_t::thing_args[0]; } + + void init() + { + mode(kWait); + + if (delayed_start()) + { + timer(timer() + TICRATE*3/2); + } + + auto piece = [&](statenum_t state) + { + Mobj* vis = spawn_from({}, MT_SPEARVISUAL); + vis->state(state); + return vis; + }; + + Vec2 v = angle_vector(angle) * scale(); + auto divider = [&](statenum_t state, int offset) + { + Mobj* vis = piece(state); + vis->angle = angle - ANGLE_90; + vis->sproff2d(v * offset); + return vis; + }; + + Mobj* head = this; + auto link = [&](Mobj* vis) + { + vis->punt_ref(this); + head->hnext(vis); + head = vis; + return vis; + }; + + Mobj* wall = divider(S_SPEAR_WALL, 0); // never moves + + set_origin({pos2d() + (angle_vector(angle) * Fixed {radius}), z}); + + link(divider(S_SPEAR_HILT_BACK, 26)); + Mobj* front = link(divider(S_SPEAR_HILT_FRONT, 34)); + + Mobj* tip = piece(S_SPEAR_TIP); + tip->angle = angle; + link(tip); + + // Whether you use a positive or negative offset + // depends on how the sprite would originally be + // sorted... + this->linkdraw(wall, -5); // this sorts the rod behind the wall plate + tip->linkdraw(front, -5); // this sorts the tip IN FRONT of the rod + } + + void think() + { + Vec2 p = pos2d() - vector(); + dist(new_dist()); + Mobj::PosArg mpos{p + vector(), z}; + + move_origin(mpos); + for (Mobj* vis : MobjListView(hnext(), [](Mobj* vis) { return vis->hnext(); })) + { + vis->move_origin(mpos); + } + + timer(timer() - 1); + if (!timer()) + { + mode((mode() + 1) % kNumModes); + } + } + +private: + Fixed new_dist() const + { + static constexpr int kMinDist = -96; + static constexpr int kMaxDist = 0; + + switch (mode()) + { + default: + return kMinDist * scale(); + case kShake: + return (leveltime & 1 ? kMinDist : kMinDist + 4) * scale(); + case kPush: + return std::min(scale() * kMaxDist, dist() + (16 * scale())); + case kPull: + return std::max(scale() * kMinDist, dist() - (4 * scale())); + } + } + + Vec2 vector() const { return angle_vector(angle) * dist(); } +}; + +}; // namespace + +void Obj_SpearInit(mobj_t* mobj) +{ + static_cast(mobj)->init(); +} + +void Obj_SpearThink(mobj_t* mobj) +{ + static_cast(mobj)->think(); +} diff --git a/src/objects/super-flicky.cpp b/src/objects/super-flicky.cpp index 344cbd46c..2578b0b7c 100644 --- a/src/objects/super-flicky.cpp +++ b/src/objects/super-flicky.cpp @@ -563,6 +563,11 @@ void Controller::search() continue; } + if (player->spectator) + { + continue; + } + // Do not retarget existing target or owner. if (mobj == chasing() || mobj == source()) { diff --git a/src/p_inter.c b/src/p_inter.c index 35e158178..4c600ef0b 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -988,6 +988,25 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; } + case MT_SA2_CRATE: + case MT_ICECAPBLOCK: + { + Obj_TryCrateTouch(special, toucher); + return; + } + + case MT_BETA_PARTICLE_PHYSICAL: + { + Obj_FuelCanisterTouch(special, toucher); + break; + } + + case MT_BETA_PARTICLE_EXPLOSION: + { + Obj_FuelCanisterExplosionTouch(special, toucher); + return; + } + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; @@ -2842,10 +2861,19 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da return false; } - if (target->type == MT_BALLSWITCH_BALL) + switch (target->type) { - Obj_BallSwitchDamaged(target, inflictor, source); - return false; + case MT_BALLSWITCH_BALL: + Obj_BallSwitchDamaged(target, inflictor, source); + return false; + + case MT_SA2_CRATE: + case MT_ICECAPBLOCK: + Obj_TryCrateDamage(target, inflictor); + return true; + + default: + break; } if (!force) diff --git a/src/p_link.cpp b/src/p_link.cpp new file mode 100644 index 000000000..1bef20bc4 --- /dev/null +++ b/src/p_link.cpp @@ -0,0 +1,49 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// 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 "p_link.h" +#include "p_mobj.h" + + +#define LINK_PACK \ + svg_battleUfoSpawners,\ + svg_checkpoints + + +using link = mobj_t*; +using each_ref = std::initializer_list>; + +link LINK_PACK; + +void P_InitMobjPointers(void) +{ + for (mobj_t*& head : each_ref {LINK_PACK}) + { + head = nullptr; + } +} + +void P_SaveMobjPointers(void(*fn)(mobj_t*)) +{ + for (mobj_t* head : {LINK_PACK}) + { + fn(head); + } +} + +void P_LoadMobjPointers(void(*fn)(mobj_t**)) +{ + for (mobj_t*& head : each_ref {LINK_PACK}) + { + fn(&head); + } +} diff --git a/src/p_link.h b/src/p_link.h new file mode 100644 index 000000000..5baa82556 --- /dev/null +++ b/src/p_link.h @@ -0,0 +1,27 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// 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 p_link_h +#define p_link_h + +#include "typedef.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void P_InitMobjPointers(void); +void P_SaveMobjPointers(void(*callback)(mobj_t*)); +void P_LoadMobjPointers(void(*callback)(mobj_t**)); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif/*p_link_h*/ diff --git a/src/p_map.c b/src/p_map.c index 216d7c169..924ccd53c 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -1200,8 +1200,6 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return BMIT_CONTINUE; } - //} - if ((thing->type == MT_SPRINGSHELL || thing->type == MT_YELLOWSHELL) && thing->health > 0 && (tm.thing->player || (tm.thing->flags & MF_PUSHABLE)) && tm.thing->health > 0) { @@ -1474,11 +1472,11 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) // The bump has to happen last if (P_IsObjectOnGround(thing) && tm.thing->momz < 0 && tm.thing->player->trickpanel) { - P_DamageMobj(thing, tm.thing, tm.thing, 1, DMG_WIPEOUT|DMG_STEAL); + P_DamageMobj(thing, tm.thing, tm.thing, 1, DMG_TUMBLE); } else if (P_IsObjectOnGround(tm.thing) && thing->momz < 0 && thing->player->trickpanel) { - P_DamageMobj(tm.thing, thing, thing, 1, DMG_WIPEOUT|DMG_STEAL); + P_DamageMobj(tm.thing, thing, thing, 1, DMG_TUMBLE); } if (K_KartBouncing(tm.thing, thing) == true) @@ -1618,6 +1616,17 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) K_KartBouncing(tm.thing, thing); return BMIT_CONTINUE; } + else if ((thing->flags & MF_SHOOTABLE) && K_PlayerCanPunt(tm.thing->player)) + { + // 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); + return BMIT_CONTINUE; + } else if (thing->flags & MF_SOLID) { // see if it went over / under @@ -1634,6 +1643,31 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) } } + switch (tm.thing->type) + { + case MT_SA2_CRATE: + case MT_ICECAPBLOCK: + // Let crates stack on top of solid objects (this + // includes other crates). + if (thing->flags & MF_SOLID) + { + fixed_t thingtop = thing->z + thing->height; + if (tm.thing->z > thing->z && thingtop > tm.floorz) + { + tm.floorz = thingtop; + tm.floorrover = NULL; + tm.floorslope = NULL; + tm.floorpic = -1; + tm.floorstep = 0; + return BMIT_CONTINUE; + } + } + break; + + default: + break; + } + // This code is causing conflicts for Ring Racers, // as solid objects cause bumping. If you need to // bring back this code for a moving platform-style diff --git a/src/p_mobj.c b/src/p_mobj.c index 2578f13af..76d2daa6d 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -5434,6 +5434,9 @@ void P_RunOverlays(void) mo->scale = mo->destscale = FixedMul(mo->target->scale, mo->movefactor); mo->angle = (mo->target->player ? mo->target->player->drawangle : mo->target->angle) + mo->movedir; + P_SetTarget(&mo->punt_ref, mo->target->punt_ref); + mo->reappear = mo->target->reappear; + if (!(mo->threshold & OV_DONTSCREENOFFSET)) { mo->spritexoffset = mo->target->spritexoffset; @@ -6836,6 +6839,47 @@ static void P_MobjSceneryThink(mobj_t *mobj) Obj_PatrolIvoBallThink(mobj); return; } + case MT_SA2_CRATE: + case MT_ICECAPBLOCK: + { + if (!Obj_TryCrateThink(mobj)) + { + return; + } + break; + } + case MT_BOX_SIDE: + { + Obj_BoxSideThink(mobj); + return; + } + case MT_SPEAR: + { + Obj_SpearThink(mobj); + return; + } + case MT_SPEARVISUAL: + { + return; + } + case MT_BETA_PARTICLE_VISUAL: + { + Obj_FuelCanisterVisualThink(mobj); + return; + } + case MT_BETA_EMITTER: + { + Obj_FuelCanisterEmitterThink(mobj); + return; + } + case MT_BETA_PARTICLE_EXPLOSION: + { + if (Obj_FuelCanisterExplosionThink(mobj) == false) + { + return; + } + break; + } case MT_VWREF: case MT_VWREB: { @@ -10233,6 +10277,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj) Obj_SidewaysFreezeThrusterThink(mobj); break; } + case MT_BETA_PARTICLE_PHYSICAL: + { + if (!Obj_FuelCanisterThink(mobj)) + { + return false; + } + break; + } default: // check mobj against possible water content, before movement code @@ -11006,6 +11058,10 @@ fixed_t P_GetMobjDefaultScale(mobj_t *mobj) case MT_HANAGUMIHALL_STEAM: case MT_HANAGUMIHALL_NPC: return 2*FRACUNIT; + case MT_SPEAR: + return 2*FRACUNIT; + case MT_BETA_EMITTER: + return 4*FRACUNIT; default: break; } @@ -14476,6 +14532,22 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) Obj_PatrolIvoBallInit(mobj); break; } + case MT_SA2_CRATE: + case MT_ICECAPBLOCK: + { + Obj_TryCrateInit(mobj); + break; + } + case MT_SPEAR: + { + Obj_SpearInit(mobj); + break; + } + case MT_BETA_EMITTER: + { + Obj_FuelCanisterEmitterInit(mobj); + break; + } default: break; } @@ -14502,24 +14574,10 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) return true; } -static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) +void P_CopyMapThingSpecialFieldsToMobj(const mapthing_t *mthing, mobj_t *mobj) { - mobj_t *mobj = NULL; size_t arg = SIZE_MAX; - mobj = P_SpawnMobj(x, y, z, type); - mobj->spawnpoint = mthing; - - mobj->angle = FixedAngle(mthing->angle << FRACBITS); - mobj->pitch = FixedAngle(mthing->pitch << FRACBITS); - mobj->roll = FixedAngle(mthing->roll << FRACBITS); - - P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale)); - mobj->destscale = FixedMul(mobj->destscale, mthing->scale); - - mobj->spritexscale = mthing->spritexscale; - mobj->spriteyscale = mthing->spriteyscale; - P_SetThingTID(mobj, mthing->tid); mobj->special = mthing->special; @@ -14573,6 +14631,26 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, mobj->script_stringargs[arg] = Z_Realloc(mobj->script_stringargs[arg], len + 1, PU_LEVEL, NULL); M_Memcpy(mobj->script_stringargs[arg], mthing->script_stringargs[arg], len + 1); } +} + +static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) +{ + mobj_t *mobj = NULL; + + mobj = P_SpawnMobj(x, y, z, type); + mobj->spawnpoint = mthing; + + mobj->angle = FixedAngle(mthing->angle << FRACBITS); + mobj->pitch = FixedAngle(mthing->pitch << FRACBITS); + mobj->roll = FixedAngle(mthing->roll << FRACBITS); + + P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale)); + mobj->destscale = FixedMul(mobj->destscale, mthing->scale); + + mobj->spritexscale = mthing->spritexscale; + mobj->spriteyscale = mthing->spriteyscale; + + P_CopyMapThingSpecialFieldsToMobj(mthing, mobj); if (!P_SetupSpawnedMapThing(mthing, mobj)) { diff --git a/src/p_mobj.h b/src/p_mobj.h index 2570acda9..dabdafe51 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -557,6 +557,7 @@ fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const f fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y); mobj_t *P_SpawnMapThing(mapthing_t *mthing); +void P_CopyMapThingSpecialFieldsToMobj(const mapthing_t *mthing, mobj_t *mobj); void P_SpawnHoop(mapthing_t *mthing); void P_SpawnItemPattern(mapthing_t *mthing); void P_SpawnItemLine(mapthing_t *mt1, mapthing_t *mt2); diff --git a/src/p_saveg.c b/src/p_saveg.c index 9d727cdcc..30b0a8df2 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -36,6 +36,7 @@ #include "lua_script.h" #include "p_slopes.h" #include "m_cond.h" // netUnlocked +#include "p_link.h" // SRB2Kart #include "k_grandprix.h" @@ -50,6 +51,8 @@ savedata_t savedata; savedata_cup_t cupsavedata; +static savebuffer_t *current_savebuffer; + // Block UINT32s to attempt to ensure that the correct data is // being sent and received #define ARCHIVEBLOCK_MISC 0x7FEEDEED @@ -3839,6 +3842,11 @@ static void SavePolyfadeThinker(savebuffer_t *save, const thinker_t *th, const U WRITEINT32(save->p, ht->timer); } +static void WriteMobjPointer(mobj_t *mobj) +{ + WRITEUINT32(current_savebuffer->p, SaveMobjnum(mobj)); +} + static void P_NetArchiveThinkers(savebuffer_t *save) { const thinker_t *th; @@ -3846,6 +3854,8 @@ static void P_NetArchiveThinkers(savebuffer_t *save) WRITEUINT32(save->p, ARCHIVEBLOCK_THINKERS); + P_SaveMobjPointers(WriteMobjPointer); + for (i = 0; i < NUM_THINKERLISTS; i++) { UINT32 numsaved = 0; @@ -4538,6 +4548,10 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) mobj->script_stringargs[j][len] = '\0'; } } + else if (mobj->spawnpoint) + { + P_CopyMapThingSpecialFieldsToMobj(mobj->spawnpoint, mobj); + } if (diff2 & MD2_FLOORSPRITESLOPE) { pslope_t *slope = (pslope_t *)P_CreateFloorSpriteSlope(mobj); @@ -5234,6 +5248,11 @@ static thinker_t* LoadPolyfadeThinker(savebuffer_t *save, actionf_p1 thinker) return &ht->thinker; } +static void ReadMobjPointer(mobj_t **mobj_p) +{ + *mobj_p = LoadMobj(READUINT32(current_savebuffer->p)); +} + static void P_NetUnArchiveThinkers(savebuffer_t *save) { thinker_t *currentthinker; @@ -5269,6 +5288,8 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save) // we don't want the removed mobjs to come back P_InitThinkers(); + P_LoadMobjPointers(ReadMobjPointer); + // clear sector thinker pointers so they don't point to non-existant thinkers for all of eternity for (i = 0; i < numsectors; i++) { @@ -5572,12 +5593,26 @@ static inline void P_UnArchivePolyObjects(savebuffer_t *save) P_UnArchivePolyObj(save, &PolyObjects[i]); } +static mobj_t *RelinkMobj(mobj_t **ptr) +{ + UINT32 temp = (UINT32)(size_t)*ptr; + *ptr = NULL; + return P_SetTarget(ptr, P_FindNewPosition(temp)); +} + +static void RelinkMobjVoid(mobj_t **ptr) +{ + RelinkMobj(ptr); +} + static void P_RelinkPointers(void) { thinker_t *currentthinker; mobj_t *mobj; UINT32 temp, i; + P_LoadMobjPointers(RelinkMobjVoid); + // use info field (value = oldposition) to relink mobjs for (currentthinker = thlist[THINK_MOBJ].next; currentthinker != &thlist[THINK_MOBJ]; currentthinker = currentthinker->next) @@ -5592,37 +5627,27 @@ static void P_RelinkPointers(void) if (mobj->tracer) { - temp = (UINT32)(size_t)mobj->tracer; - mobj->tracer = NULL; - if (!P_SetTarget(&mobj->tracer, P_FindNewPosition(temp))) + if (!RelinkMobj(&mobj->tracer)) CONS_Debug(DBG_GAMELOGIC, "tracer not found on %d\n", mobj->type); } if (mobj->target) { - temp = (UINT32)(size_t)mobj->target; - mobj->target = NULL; - if (!P_SetTarget(&mobj->target, P_FindNewPosition(temp))) + if (!RelinkMobj(&mobj->target)) CONS_Debug(DBG_GAMELOGIC, "target not found on %d\n", mobj->type); } if (mobj->hnext) { - temp = (UINT32)(size_t)mobj->hnext; - mobj->hnext = NULL; - if (!P_SetTarget(&mobj->hnext, P_FindNewPosition(temp))) + if (!RelinkMobj(&mobj->hnext)) CONS_Debug(DBG_GAMELOGIC, "hnext not found on %d\n", mobj->type); } if (mobj->hprev) { - temp = (UINT32)(size_t)mobj->hprev; - mobj->hprev = NULL; - if (!P_SetTarget(&mobj->hprev, P_FindNewPosition(temp))) + if (!RelinkMobj(&mobj->hprev)) CONS_Debug(DBG_GAMELOGIC, "hprev not found on %d\n", mobj->type); } if (mobj->itnext) { - temp = (UINT32)(size_t)mobj->itnext; - mobj->itnext = NULL; - if (!P_SetTarget(&mobj->itnext, P_FindNewPosition(temp))) + if (!RelinkMobj(&mobj->itnext)) CONS_Debug(DBG_GAMELOGIC, "itnext not found on %d\n", mobj->type); } if (mobj->terrain) @@ -5636,23 +5661,17 @@ static void P_RelinkPointers(void) } if (mobj->terrainOverlay) { - temp = (UINT32)(size_t)mobj->terrainOverlay; - mobj->terrainOverlay = NULL; - if (!P_SetTarget(&mobj->terrainOverlay, P_FindNewPosition(temp))) + if (!RelinkMobj(&mobj->terrainOverlay)) CONS_Debug(DBG_GAMELOGIC, "terrainOverlay not found on %d\n", mobj->type); } if (mobj->punt_ref) { - temp = (UINT32)(size_t)mobj->punt_ref; - mobj->punt_ref = NULL; - if (!P_SetTarget(&mobj->punt_ref, P_FindNewPosition(temp))) + if (!RelinkMobj(&mobj->punt_ref)) CONS_Debug(DBG_GAMELOGIC, "punt_ref not found on %d\n", mobj->type); } if (mobj->owner) { - temp = (UINT32)(size_t)mobj->owner; - mobj->owner = NULL; - if (!P_SetTarget(&mobj->owner, P_FindNewPosition(temp))) + if (!RelinkMobj(&mobj->owner)) CONS_Debug(DBG_GAMELOGIC, "owner not found on %d\n", mobj->type); } } @@ -5664,37 +5683,27 @@ static void P_RelinkPointers(void) if (players[i].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))) + if (!RelinkMobj(&players[i].skybox.viewpoint)) 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))) + if (!RelinkMobj(&players[i].skybox.centerpoint)) CONS_Debug(DBG_GAMELOGIC, "skybox.centerpoint not found on player %d\n", i); } if (players[i].awayview.mobj) { - temp = (UINT32)(size_t)players[i].awayview.mobj; - players[i].awayview.mobj = NULL; - if (!P_SetTarget(&players[i].awayview.mobj, P_FindNewPosition(temp))) + if (!RelinkMobj(&players[i].awayview.mobj)) CONS_Debug(DBG_GAMELOGIC, "awayview.mobj 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))) + if (!RelinkMobj(&players[i].followmobj)) 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))) + if (!RelinkMobj(&players[i].follower)) CONS_Debug(DBG_GAMELOGIC, "follower not found on player %d\n", i); } if (players[i].currentwaypoint) @@ -5726,72 +5735,52 @@ static void P_RelinkPointers(void) } if (players[i].hoverhyudoro) { - temp = (UINT32)(size_t)players[i].hoverhyudoro; - players[i].hoverhyudoro = NULL; - if (!P_SetTarget(&players[i].hoverhyudoro, P_FindNewPosition(temp))) + if (!RelinkMobj(&players[i].hoverhyudoro)) 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))) + if (!RelinkMobj(&players[i].stumbleIndicator)) CONS_Debug(DBG_GAMELOGIC, "stumbleIndicator not found on player %d\n", i); } if (players[i].wavedashIndicator) { - temp = (UINT32)(size_t)players[i].wavedashIndicator; - players[i].wavedashIndicator = NULL; - if (!P_SetTarget(&players[i].wavedashIndicator, P_FindNewPosition(temp))) + if (!RelinkMobj(&players[i].wavedashIndicator)) CONS_Debug(DBG_GAMELOGIC, "wavedashIndicator not found on player %d\n", i); } if (players[i].trickIndicator) { - temp = (UINT32)(size_t)players[i].trickIndicator; - players[i].trickIndicator = NULL; - if (!P_SetTarget(&players[i].trickIndicator, P_FindNewPosition(temp))) + if (!RelinkMobj(&players[i].trickIndicator)) CONS_Debug(DBG_GAMELOGIC, "trickIndicator not found on player %d\n", i); } if (players[i].whip) { - temp = (UINT32)(size_t)players[i].whip; - players[i].whip = NULL; - if (!P_SetTarget(&players[i].whip, P_FindNewPosition(temp))) + if (!RelinkMobj(&players[i].whip)) CONS_Debug(DBG_GAMELOGIC, "whip not found on player %d\n", i); } if (players[i].hand) { - temp = (UINT32)(size_t)players[i].hand; - players[i].hand = NULL; - if (!P_SetTarget(&players[i].hand, P_FindNewPosition(temp))) + if (!RelinkMobj(&players[i].hand)) CONS_Debug(DBG_GAMELOGIC, "hand not found on player %d\n", i); } if (players[i].ringShooter) { - temp = (UINT32)(size_t)players[i].ringShooter; - players[i].ringShooter = NULL; - if (!P_SetTarget(&players[i].ringShooter, P_FindNewPosition(temp))) + if (!RelinkMobj(&players[i].ringShooter)) CONS_Debug(DBG_GAMELOGIC, "ringShooter not found on player %d\n", i); } if (players[i].flickyAttacker) { - temp = (UINT32)(size_t)players[i].flickyAttacker; - players[i].flickyAttacker = NULL; - if (!P_SetTarget(&players[i].flickyAttacker, P_FindNewPosition(temp))) + if (!RelinkMobj(&players[i].flickyAttacker)) CONS_Debug(DBG_GAMELOGIC, "flickyAttacker not found on player %d\n", i); } if (players[i].powerup.flickyController) { - temp = (UINT32)(size_t)players[i].powerup.flickyController; - players[i].powerup.flickyController = NULL; - if (!P_SetTarget(&players[i].powerup.flickyController, P_FindNewPosition(temp))) + if (!RelinkMobj(&players[i].powerup.flickyController)) CONS_Debug(DBG_GAMELOGIC, "powerup.flickyController not found on player %d\n", i); } if (players[i].powerup.barrier) { - temp = (UINT32)(size_t)players[i].powerup.barrier; - players[i].powerup.barrier = NULL; - if (!P_SetTarget(&players[i].powerup.barrier, P_FindNewPosition(temp))) + if (!RelinkMobj(&players[i].powerup.barrier)) CONS_Debug(DBG_GAMELOGIC, "powerup.barrier not found on player %d\n", i); } } @@ -6422,17 +6411,13 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) gametic = READUINT32(save->p); gamemap = READINT16(save->p); + g_reloadingMap = false; // gamemap changed; we assume that its map header is always valid, // so make it so if (!gamemap || gamemap > nummapheaders || !mapheaderinfo[gamemap-1]) I_Error("P_NetUnArchiveMisc: Internal map ID %d not found (nummapheaders = %d)", gamemap-1, nummapheaders); - // tell the sound code to reset the music since we're skipping what - // normally sets this flag - if (!reloading) - mapmusflags |= MUSIC_RELOADRESET; - G_SetGamestate(READINT16(save->p)); gametype = READINT16(save->p); @@ -6687,6 +6672,8 @@ void P_SaveGame(savebuffer_t *save) void P_SaveNetGame(savebuffer_t *save, boolean resending) { + current_savebuffer = save; + thinker_t *th; mobj_t *mobj; UINT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise @@ -6763,6 +6750,8 @@ badloadgame: boolean P_LoadNetGame(savebuffer_t *save, boolean reloading) { + current_savebuffer = save; + CV_LoadNetVars(&save->p); if (!P_NetUnArchiveMisc(save, reloading)) diff --git a/src/p_setup.c b/src/p_setup.c index 913158baf..a3fc5f9f2 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8069,7 +8069,7 @@ static void P_InitMinimapInfo(void) void P_ResetLevelMusic(void) { - mapmusrng = 0; + UINT8 idx = 0; if (mapheaderinfo[gamemap-1]->musname_size > 1) { @@ -8090,20 +8090,46 @@ void P_ResetLevelMusic(void) if (tempmapmus_size > 1) { - mapmusrng = P_RandomKey(PR_MUSICSELECT, tempmapmus_size); + if (g_reloadingMap) + { + // If restarting the map, simply cycle + // through available alt music. + idx = (mapmusrng + 1) % tempmapmus_size; + } + else + { + idx = P_RandomKey(PR_MUSICSELECT, tempmapmus_size); + } //CONS_Printf("Rolled position %u, maps to %u\n", mapmusrng, tempmapmus[mapmusrng]); - mapmusrng = tempmapmus[mapmusrng]; + idx = tempmapmus[idx]; } } + + mapmusrng = idx; } void P_LoadLevelMusic(void) { - tic_t level_music_start = starttime + (TICRATE/2); + const char *music = mapheaderinfo[gamemap-1]->musname[mapmusrng]; - Music_StopAll(); - Music_Remap("level", mapheaderinfo[gamemap-1]->musname[mapmusrng]); - Music_Seek("level", max(leveltime, level_music_start) - level_music_start); + if (gametyperules & GTR_NOPOSITION) + { + if (!stricmp(Music_Song("level_nosync"), music)) + { + // Do not reset music if it is the same + Music_BatchExempt("level_nosync"); + } + Music_StopAll(); + Music_Remap("level_nosync", music); + } + else + { + Music_StopAll(); + Music_Remap("level", music); + + tic_t level_music_start = starttime + (TICRATE/2); + Music_Seek("level", max(leveltime, level_music_start) - level_music_start); + } } /** Loads a level from a lump or external wad. diff --git a/src/p_tick.c b/src/p_tick.c index c61fed92f..b12f2f361 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -29,6 +29,7 @@ #include "r_main.h" #include "r_fps.h" #include "d_clisrv.h" // UpdateChallenges +#include "p_link.h" // Object place #include "m_cheat.h" @@ -299,8 +300,7 @@ void P_InitThinkers(void) skyboxcenterpnts[i] = skyboxviewpnts[i] = NULL; } - Obj_ResetUFOSpawners(); - Obj_ResetCheckpoints(); + P_InitMobjPointers(); } // @@ -975,7 +975,11 @@ void P_Ticker(boolean run) if (leveltime == (starttime + (TICRATE/2))) { // Plays the music after the starting countdown. - Music_Play("level"); + if (!Music_Playing("level_nosync")) + { + // Do not stop level_nosync + Music_Play(Music_Song("level_nosync")[0] ? "level_nosync" : "level"); + } } else if (starttime != introtime) { diff --git a/src/p_user.c b/src/p_user.c index b1a0fd3cd..d5e781e19 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1332,6 +1332,11 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) { G_BeginLevelExit(); } + + if (specialstageinfo.valid == true && losing == false && P_MobjWasRemoved(player->mo) == false) + { + K_MakeObjectReappear(player->mo); + } } K_InitPlayerTally(player); diff --git a/src/r_things.cpp b/src/r_things.cpp index a1187b803..9cb033f05 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1656,8 +1656,6 @@ static void R_ProjectBoundingBox(mobj_t *thing, vissprite_t *vis) box->sortscale = vis->sortscale; // link sorting to sprite box->dispoffset = vis->dispoffset + 5; - - box->cut = static_cast(box->cut | SC_LINKDRAW); } else { diff --git a/src/s_sound.c b/src/s_sound.c index dfd3bb4fa..8a88cf3b0 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -2086,7 +2086,7 @@ void S_ShowMusicCredit(void) char credittext[128] = ""; char *work = NULL; size_t len = 128, worklen; - INT32 widthused = BASEVIDWIDTH, workwidth; + INT32 widthused = (3*BASEVIDWIDTH/4) - 7, workwidth; if (!cv_songcredits.value) return; diff --git a/src/sounds.c b/src/sounds.c index 1c9a5b841..9d0722e2a 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1251,6 +1251,8 @@ sfxinfo_t S_sfx[NUMSFX] = {"ivobal", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Ivo Ball + {"lcfuel", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Fuel Capsule explodes"}, + // Damage sounds {"dmga1", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, "Damaged"}, {"dmga2", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, "Damaged"}, diff --git a/src/sounds.h b/src/sounds.h index 88ba458e4..9b31bb293 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1323,6 +1323,9 @@ typedef enum // Ivo Ball sfx_ivobal, + // Fuel Capsule + sfx_lcfuel, + // Damage sounds sfx_dmga1, sfx_dmga2,