Merge branch 'master' of https://git.do.srb2.org/KartKrew/Kart into tutorial-pass

This commit is contained in:
toaster 2023-12-08 13:08:43 +00:00
commit 6c7a1c14e5
58 changed files with 2392 additions and 342 deletions

View file

@ -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} $<TARGET_FILE:SRB2SDL2> $<TARGET_FILE:SRB2SDL2>.debug
COMMAND ${OBJCOPY} --strip-debug $<TARGET_FILE:SRB2SDL2>
# mold linker: .gnu_debuglink is present by default, so --add-gnu-debuglink would fail
COMMAND ${OBJCOPY} --strip-debug --remove-section=.gnu_debuglink $<TARGET_FILE:SRB2SDL2>
COMMAND ${OBJCOPY} --add-gnu-debuglink=$<TARGET_FILE:SRB2SDL2>.debug $<TARGET_FILE:SRB2SDL2>
)
endif()

View file

@ -214,7 +214,12 @@ optional<SoundChunk> try_load_gme(tcb::span<std::byte> data)
optional<SoundChunk> srb2::audio::try_load_chunk(tcb::span<std::byte> data)
{
optional<SoundChunk> ret;
optional<SoundChunk> ret = nullopt;
if (data.size() == 0)
{
return ret;
}
ret = try_load_dmx(data);
if (ret)
@ -232,5 +237,5 @@ optional<SoundChunk> srb2::audio::try_load_chunk(tcb::span<std::byte> data)
if (ret)
return ret;
return nullopt;
return ret;
}

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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[] = {

View file

@ -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;

View file

@ -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();
}

View file

@ -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;

View file

@ -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
},
};

View file

@ -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

View file

@ -431,10 +431,10 @@ public:
head = offset;
break;
case SeekFrom::kEnd:
if (-offset >= static_cast<StreamOffset>(span_.size())) {
if (static_cast<StreamOffset>(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<StreamOffset>(vec_.size())) {
if (static_cast<StreamOffset>(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) {

View file

@ -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)

View file

@ -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;
}

View file

@ -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!

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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 <typename U>
Vec2(const Vec2<U>& b) : Vec2(b.x, b.y) {}

View file

@ -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,

View file

@ -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
};

View file

@ -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;
}

View file

@ -13,8 +13,12 @@
#include <optional>
#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<fixed>;
using vec2 = math::Vec2<fixed>;
// 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 <typename T>
PosArg(math::Vec2<T> p, fixed_t z) : PosArg(p.x, p.y, z) {}
PosArg(math::Vec2<T> 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<math::Fixed> 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<fixed_t> speed = {})
void scale_to(fixed stop, std::optional<fixed> speed = {})
{
mobj_t::destscale = stop;
@ -168,13 +183,68 @@ struct Mobj : mobj_t
}
}
void scale_between(fixed_t start, fixed_t stop, std::optional<fixed_t> speed = {})
void scale_between(fixed start, fixed stop, std::optional<fixed> 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

86
src/mobj_list.hpp Normal file
View file

@ -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 <type_traits>
#include "cxxutil.hpp"
#include "mobj.hpp"
#include "mobj_list_view.hpp"
namespace srb2
{
// Requires:
// void T::next(T*)
// T* T::next() const
template <typename T, mobj_t*& Head>
struct MobjList
{
static_assert(std::is_convertible_v<T, mobj_t>);
MobjList() {}
T* front() const { return static_cast<T*>(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*/

84
src/mobj_list_view.hpp Normal file
View file

@ -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 <cstddef>
#include <iterator>
#include <type_traits>
#include "p_mobj.h"
namespace srb2
{
// for (T* ptr : MobjList(hnext(), [](T* ptr) { return ptr->hnext(); }))
template <typename T, typename F>
struct MobjListView
{
static_assert(std::is_convertible_v<T, mobj_t>);
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*/

View file

@ -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;
}
}

View file

@ -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.

View file

@ -44,9 +44,9 @@ public:
return it != map_.end() ? const_cast<Tune*>(&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);

View file

@ -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).

View file

@ -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)

View file

@ -1,6 +1,10 @@
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <set>
#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<Spawner>(); }
void next(Spawner* n) { Mobj::hnext(n); }
};
struct UFO : mobj_t
struct UFO : Mobj
{
Spawner* spawner() const { return static_cast<Spawner*>(ufo_spawner(this)); }
void spawner(Spawner* n) { P_SetTarget(&ufo_spawner(this), n); }
void target() = delete;
Spawner* spawner() const { return Mobj::target<Spawner>(); }
void spawner(Spawner* n) { Mobj::target(n); }
void spawn_beam()
{
mobj_t *x;
Mobj *x = spawn_from<Mobj>({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<Spawner*, SpawnerCompare> set_;
MobjList<Spawner, svg_battleUfoSpawners> 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<UFO*>(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);

View file

@ -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;
}

38
src/objects/broly.cpp Normal file
View file

@ -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<Broly>(static_cast<Mobj*>(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<Broly*>(x)->think();
}

96
src/objects/broly.hpp Normal file
View file

@ -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 <type_traits>
#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<Fixed> size() const { return {mobj_t::threshold, mobj_t::extravalue2}; }
void size(const Vec2<Fixed>& 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 <typename T>
static T* spawn(Mobj* source, tic_t duration, const Vec2<Fixed>& size)
{
static_assert(std::is_base_of_v<Broly, T>);
if (duration == 0)
{
return nullptr;
}
T* x = Mobj::spawn<T>(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<Fixed> v = size();
scale(Easing_OutSine(linear(), v.y, v.x));
z = center - (height / 2);
return true;
}
};
}; // namespace srb2::objects
#endif/*objects_broly_hpp*/

View file

@ -8,10 +8,11 @@
//-----------------------------------------------------------------------------
#include <algorithm>
#include <vector>
#include <fmt/format.h>
#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<Arm*>(checkpoint_arm(this)); }
void arm(Arm* n) { P_SetTarget(&checkpoint_arm(this), n); }
Checkpoint* next() const { return static_cast<Checkpoint*>(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<Checkpoint*> vec_;
srb2::MobjList<Checkpoint, svg_checkpoints> list_;
};
CheckpointManager g_checkpoints;
}; // namespace
void Obj_ResetCheckpoints(void)
{
g_checkpoints = {};
}
void Obj_LinkCheckpoint(mobj_t* end)
{
auto chk = static_cast<Checkpoint*>(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<const Checkpoint*>(end);
auto chk = static_cast<Checkpoint*>(end);
g_checkpoints.erase(chk);

391
src/objects/crate.cpp Normal file
View file

@ -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 <algorithm>
#include <array>
#include <tuple>
#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<int, 6>; // -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<Graphic>(); }
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 <typename F>
bool visit(F&& visitor);
void update()
{
visit([](auto box) { box->mobj_t::eflags &= ~MFE_ONGROUND; });
}
};
template <class Config>
struct Box : AnyBox
{
static constexpr Fixed kIntendedSize = 128*FRACUNIT;
static constexpr Vec2<Fixed> kScrunch = {4*FRACUNIT/5, 6*FRACUNIT/5};
void extravalue1() = delete;
statenum_t debris_state() const { return static_cast<statenum_t>(mobj_t::extravalue1); }
void debris_state(statenum_t n) { mobj_t::extravalue1 = n; }
auto gfx() { return MobjListView(static_cast<Graphic*>(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<Side>({}, 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<Mobj>({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<Fixed> search = aabb();
Vec2<Fixed> org{bmaporgx, bmaporgy};
search.a -= org + MAXRADIUS;
search.b -= org - MAXRADIUS;
search.a.x = static_cast<UINT32>(search.a.x) >> MAPBLOCKSHIFT;
search.b.x = static_cast<UINT32>(search.b.x) >> MAPBLOCKSHIFT;
search.a.y = static_cast<UINT32>(search.a.y) >> MAPBLOCKSHIFT;
search.b.y = static_cast<UINT32>(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<AnyBox*>(thing)->update();
return BMIT_CONTINUE;
}
);
}
}
}
};
struct Crate : Box<SA2CrateConfig>
{
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<IceCapBlockConfig>
{
};
template <typename F>
bool AnyBox::visit(F&& visitor)
{
switch (type)
{
case MT_SA2_CRATE:
visitor(static_cast<Crate*>(this));
break;
case MT_ICECAPBLOCK:
visitor(static_cast<Ice*>(this));
break;
default:
return false;
}
return true;
}
}; // namespace
void Obj_BoxSideThink(mobj_t* mobj)
{
static_cast<Side*>(mobj)->think();
}
void Obj_TryCrateInit(mobj_t* mobj)
{
static_cast<AnyBox*>(mobj)->visit([&](auto box) { box->init(); });
}
boolean Obj_TryCrateThink(mobj_t* mobj)
{
bool c = false;
static_cast<AnyBox*>(mobj)->visit([&](auto box) { c = box->think(); });
return c;
}
void Obj_TryCrateTouch(mobj_t* special, mobj_t* toucher)
{
static_cast<AnyBox*>(special)->visit([&](auto box) { box->touch(static_cast<Toucher*>(toucher)); });
}
void Obj_TryCrateDamage(mobj_t* target, mobj_t* inflictor)
{
static_cast<AnyBox*>(target)->visit([&](auto box) { box->damage(static_cast<Toucher*>(inflictor)); });
}

View file

@ -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)))
{

236
src/objects/fuel.cpp Normal file
View file

@ -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<Explosion>(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<FuelCanister>({}, MT_BETA_PARTICLE_PHYSICAL);
caps->init();
return caps;
}
void init()
{
momz = 8 * scale();
z -= momz;
pieces<Wheel>();
pieces<Icon>();
}
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<Fixed> vector(angle_t angle) { return {FCOS(angle), FSIN(angle)}; }
template <class Config>
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<Vis>({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<FuelCanister::Emitter*>(mo)->init();
}
boolean Obj_FuelCanisterVisualThink(mobj_t *mo)
{
return static_cast<FuelCanister::Vis*>(mo)->think();
}
boolean Obj_FuelCanisterEmitterThink(mobj_t *mo)
{
return static_cast<FuelCanister::Emitter*>(mo)->think();
}
boolean Obj_FuelCanisterThink(mobj_t *mo)
{
return static_cast<FuelCanister*>(mo)->think();
}
void Obj_FuelCanisterTouch(mobj_t *special, mobj_t *toucher)
{
static_cast<FuelCanister*>(special)->touch(static_cast<Mobj*>(toucher));
}
void Obj_FuelCanisterExplosionTouch(mobj_t *special, mobj_t *toucher)
{
static_cast<FuelCanister::Explosion*>(special)->touch(static_cast<Mobj*>(toucher));
}
boolean Obj_FuelCanisterExplosionThink(mobj_t *mo)
{
return static_cast<FuelCanister::Explosion*>(mo)->think();
}

View file

@ -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);
}

19
src/objects/objects.hpp Normal file
View file

@ -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*/

179
src/objects/spear.cpp Normal file
View file

@ -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 <algorithm>
#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<Fixed> angle_vector(angle_t x)
{
return Vec2<Fixed> {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<Mobj>({}, MT_SPEARVISUAL);
vis->state(state);
return vis;
};
Vec2<Fixed> 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<Fixed> 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<Fixed> vector() const { return angle_vector(angle) * dist(); }
};
}; // namespace
void Obj_SpearInit(mobj_t* mobj)
{
static_cast<Spear*>(mobj)->init();
}
void Obj_SpearThink(mobj_t* mobj)
{
static_cast<Spear*>(mobj)->think();
}

View file

@ -563,6 +563,11 @@ void Controller::search()
continue;
}
if (player->spectator)
{
continue;
}
// Do not retarget existing target or owner.
if (mobj == chasing() || mobj == source())
{

View file

@ -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)

49
src/p_link.cpp Normal file
View file

@ -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 <functional>
#include <initializer_list>
#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<std::reference_wrapper<mobj_t*>>;
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);
}
}

27
src/p_link.h Normal file
View file

@ -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*/

View file

@ -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

View file

@ -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))
{

View file

@ -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);

View file

@ -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))

View file

@ -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.

View file

@ -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)
{

View file

@ -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);

View file

@ -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<spritecut_e>(box->cut | SC_LINKDRAW);
}
else
{

View file

@ -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;

View file

@ -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"},

View file

@ -1323,6 +1323,9 @@ typedef enum
// Ivo Ball
sfx_ivobal,
// Fuel Capsule
sfx_lcfuel,
// Damage sounds
sfx_dmga1,
sfx_dmga2,