Merge branch 'master' into special-stages

This commit is contained in:
Sally Coolatta 2022-12-22 05:18:56 -05:00
commit 82df346417
64 changed files with 4554 additions and 1343 deletions

View file

@ -132,9 +132,12 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
k_roulette.c
)
if("${CMAKE_COMPILER_IS_GNUCC}" AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows" AND NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}")
# On MinGW with internal libraries, link the standard library statically
target_link_options(SRB2SDL2 PRIVATE "-static")
if("${CMAKE_COMPILER_IS_GNUCC}" AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows")
target_link_options(SRB2SDL2 PRIVATE "-Wl,--disable-dynamicbase")
if(NOT "${SRB2_CONFIG_SYSTEM_LIBRARIES}" AND NOT "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}")
# On MinGW with internal libraries, link the standard library statically
target_link_options(SRB2SDL2 PRIVATE "-static")
endif()
endif()
target_compile_features(SRB2SDL2 PRIVATE c_std_11 cxx_std_17)
@ -149,7 +152,11 @@ set(SRB2_CONFIG_USEASM OFF CACHE BOOL
set(SRB2_CONFIG_YASM OFF CACHE BOOL
"Use YASM in place of NASM.")
set(SRB2_CONFIG_DEV_BUILD OFF CACHE BOOL
"Compile a development build of SRB2Kart.")
"Compile a development build of Dr Robotnik's Ring Racers.")
set(SRB2_CONFIG_TESTERS OFF CACHE BOOL
"Compile a build for testers.")
set(SRB2_CONFIG_HOSTTESTERS OFF CACHE BOOL
"Compile a build to host netgames for testers builds.")
add_subdirectory(blua)
@ -233,7 +240,7 @@ if(${SRB2_CONFIG_HAVE_ZLIB})
set(SRB2_HAVE_ZLIB ON)
target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_ZLIB)
else()
message(WARNING "You have specified that ZLIB is available but it was not found. SRB2Kart may not compile correctly.")
message(WARNING "You have specified that ZLIB is available but it was not found. Dr Robotnik's Ring Racers may not compile correctly.")
endif()
endif()
@ -256,7 +263,7 @@ if(${SRB2_CONFIG_HAVE_PNG} AND ${SRB2_CONFIG_HAVE_ZLIB})
target_compile_definitions(SRB2SDL2 PRIVATE -D_LARGEFILE64_SOURCE)
target_sources(SRB2SDL2 PRIVATE apng.c)
else()
message(WARNING "You have specified that PNG is available but it was not found. SRB2Kart may not compile correctly.")
message(WARNING "You have specified that PNG is available but it was not found. Dr Robotnik's Ring Racers may not compile correctly.")
endif()
endif()
endif()
@ -482,6 +489,12 @@ target_compile_definitions(SRB2SDL2 PRIVATE -DCMAKECONFIG)
if(SRB2_CONFIG_DEBUGMODE)
target_compile_definitions(SRB2SDL2 PRIVATE -DZDEBUG -DPARANOIA -DRANGECHECK -DPACKETDROP)
endif()
if(SRB2_CONFIG_TESTERS)
target_compile_definitions(SRB2SDL2 PRIVATE -DTESTERS)
endif()
if(SRB2_CONFIG_HOSTTESTERS)
target_compile_definitions(SRB2SDL2 PRIVATE -DHOSTTESTERS)
endif()
if(SRB2_CONFIG_MOBJCONSISTANCY)
target_compile_definitions(SRB2SDL2 PRIVATE -DMOBJCONSISTANCY)
endif()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -154,7 +154,8 @@ Run this macro, then #undef FOREACH afterward
FOREACH (SUPERRING, 19),\
FOREACH (KITCHENSINK, 20),\
FOREACH (DROPTARGET, 21),\
FOREACH (GARDENTOP, 22)
FOREACH (GARDENTOP, 22),\
FOREACH (GACHABOM, 23)
typedef enum
{
@ -171,6 +172,7 @@ typedef enum
KRITEM_TRIPLEORBINAUT,
KRITEM_QUADORBINAUT,
KRITEM_DUALJAWZ,
KRITEM_TRIPLEGACHABOM,
NUMKARTRESULTS
} kartitems_t;
@ -421,7 +423,7 @@ struct player_t
UINT16 skincolor;
INT32 skin;
UINT32 availabilities;
UINT8 availabilities[MAXAVAILABILITY];
UINT8 fakeskin; // ironman
UINT8 lastfakeskin;
@ -464,6 +466,7 @@ struct player_t
UINT16 spinouttimer; // Spin-out from a banana peel or oil slick (was "pw_bananacam")
UINT8 spinouttype; // Determines the mode of spinout/wipeout, see kartspinoutflags_t
UINT8 instashield; // Instashield no-damage animation timer
INT32 invulnhitlag; // Numbers of tics of hitlag added this tic for "potential" damage -- not real damage
UINT8 wipeoutslow; // Timer before you slowdown when getting wiped out
UINT8 justbumped; // Prevent players from endlessly bumping into each other
UINT8 tumbleBounces;
@ -613,7 +616,12 @@ struct player_t
INT16 lastsidehit, lastlinehit;
//UINT8 timeshit; // That's TIMES HIT, not TIME SHIT, you doofus! -- in memoriam
// These track how many things tried to damage you, not
// whether you actually took damage.
UINT8 timeshit; // times hit this tic
UINT8 timeshitprev; // times hit before
// That's TIMES HIT, not TIME SHIT, you doofus! -- in memoriam
// No longer in memoriam =P -jart
INT32 onconveyor; // You are on a conveyor belt if nonzero

View file

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

View file

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

View file

@ -5384,6 +5384,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_SINK_SHIELD",
"MT_SINKTRAIL",
"MT_GACHABOM",
"MT_DUELBOMB", // Duel mode bombs
"MT_BATTLEBUMPER", // Battle Mode bumper
@ -6356,7 +6358,8 @@ struct int_const_s const INT_CONST[] = {
{"SF_X2AWAYSOUND",SF_X2AWAYSOUND},
// Global emblem var flags
// none in kart yet
{"GE_NOTMEDAL", GE_NOTMEDAL},
{"GE_TIMED", GE_TIMED},
// Map emblem var flags
{"ME_ENCORE",ME_ENCORE},
@ -6794,6 +6797,7 @@ struct int_const_s const INT_CONST[] = {
{"KRITEM_TRIPLEORBINAUT",KRITEM_TRIPLEORBINAUT},
{"KRITEM_QUADORBINAUT",KRITEM_QUADORBINAUT},
{"KRITEM_DUALJAWZ",KRITEM_DUALJAWZ},
{"KRITEM_TRIPLEGACHABOM",KRITEM_TRIPLEGACHABOM},
{"NUMKARTRESULTS",NUMKARTRESULTS},
// kartshields_t

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7999,7 +7999,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
1000, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
8, // reactiontime
0, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
@ -24202,6 +24202,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
S_NULL // raisestate
},
{ // MT_GACHABOM
-1, // doomednum
S_ORBINAUT1, // spawnstate
7, // spawnhealth
S_NULL, // seestate
sfx_tossed, // seesound
8, // reactiontime
sfx_s3k49, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_ORBINAUT_DEAD,// deathstate
S_NULL, // xdeathstate
sfx_s3k5d, // deathsound
64*FRACUNIT, // speed
24*FRACUNIT, // radius
32*FRACUNIT, // height
0, // display offset
100, // mass
1, // damage
sfx_s3k96, // activesound
MF_SHOOTABLE|MF_DONTENCOREMAP|MF_APPLYTERRAIN|MF_SLOPE, // flags
S_NULL // raisestate
},
{ // MT_DUELBOMB
2050, // doomednum
S_SPB1, // spawnstate

View file

@ -6442,6 +6442,8 @@ typedef enum mobj_type
MT_SINK_SHIELD,
MT_SINKTRAIL,
MT_GACHABOM,
MT_DUELBOMB, // Duel mode bombs
MT_BATTLEBUMPER, // Battle Mode bumpers

View file

@ -1558,6 +1558,11 @@ void K_BotItemUsage(player_t *player, ticcmd_t *cmd, INT16 turnamt)
case KITEM_FLAMESHIELD:
K_BotItemFlame(player, cmd);
break;
/*
case KITEM_GACHABOM:
K_BotItemGachabom(player, cmd);
break;
*/
}
}
}

View file

@ -415,6 +415,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing)
case MT_SPB:
case MT_BUBBLESHIELDTRAP:
case MT_DUELBOMB:
case MT_GACHABOM:
K_AddDodgeObject(thing, side, 20);
break;
case MT_SHRINK_GUN:

View file

@ -87,7 +87,7 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2)
else if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD
|| t2->type == MT_ORBINAUT || t2->type == MT_ORBINAUT_SHIELD
|| t2->type == MT_JAWZ || t2->type == MT_JAWZ_SHIELD
|| t2->type == MT_BALLHOG)
|| t2->type == MT_BALLHOG || t2->type == MT_GACHABOM)
{
// Other Item Damage
angle_t bounceangle = K_GetCollideAngle(t1, t2);
@ -204,7 +204,7 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2)
static mobj_t *grenade;
static fixed_t explodedist;
static boolean explodespin;
static tic_t minehitlag;
static INT32 minehitlag;
static inline boolean PIT_SSMineChecks(mobj_t *thing)
{
@ -272,6 +272,9 @@ void K_DoMineSearch(mobj_t *actor, fixed_t size)
static inline BlockItReturn_t PIT_SSMineExplode(mobj_t *thing)
{
const INT32 oldhitlag = thing->hitlag;
INT32 lagadded;
if (grenade == NULL || P_MobjWasRemoved(grenade))
return BMIT_ABORT; // There's the possibility these can chain react onto themselves after they've already died if there are enough all in one spot
@ -283,9 +286,13 @@ static inline BlockItReturn_t PIT_SSMineExplode(mobj_t *thing)
if (PIT_SSMineChecks(thing) == true)
return BMIT_CONTINUE;
if (P_DamageMobj(thing, grenade, grenade->target, 1, (explodespin ? DMG_NORMAL : DMG_EXPLODE)))
P_DamageMobj(thing, grenade, grenade->target, 1, (explodespin ? DMG_NORMAL : DMG_EXPLODE));
lagadded = (thing->hitlag - oldhitlag);
if (lagadded > 0)
{
minehitlag = thing->hitlag;
minehitlag = lagadded;
}
return BMIT_CONTINUE;
@ -353,7 +360,8 @@ boolean K_MineCollide(mobj_t *t1, mobj_t *t2)
}
}
else if (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ
|| t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD)
|| t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD
|| t2->type == MT_GACHABOM)
{
// Bomb death
angle_t bounceangle = K_GetCollideAngle(t1, t2);
@ -391,6 +399,8 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2)
if (t2->player)
{
const INT32 oldhitlag = t2->hitlag;
if (t2->player->flashing)
return true;
@ -402,6 +412,7 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2)
{
// Melt item
S_StartSound(t2, sfx_s3k43);
K_SetHitLagForObjects(t2, t1, 3, false);
}
else
{
@ -409,13 +420,13 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2)
P_DamageMobj(t2, t1, t1->target, 1, DMG_TUMBLE);
}
t1->reactiontime = t2->hitlag;
t1->reactiontime = (t2->hitlag - oldhitlag);
P_KillMobj(t1, t2, t2, DMG_NORMAL);
}
else if (t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD
|| t2->type == MT_ORBINAUT || t2->type == MT_ORBINAUT_SHIELD
|| t2->type == MT_JAWZ || t2->type == MT_JAWZ_SHIELD
|| t2->type == MT_BALLHOG)
|| t2->type == MT_BALLHOG || t2->type == MT_GACHABOM)
{
// Other Item Damage
angle_t bounceangle = K_GetCollideAngle(t1, t2);

View file

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

View file

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

View file

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

View file

@ -70,7 +70,7 @@ static patch_t *kp_racefinish[6];
static patch_t *kp_positionnum[10][2][2]; // number, overlay or underlay, splitscreen
static patch_t *kp_facenum[MAXPLAYERS+1];
static patch_t *kp_facehighlight[8];
patch_t *kp_facehighlight[8];
static patch_t *kp_nocontestminimap;
static patch_t *kp_spbminimap;
@ -173,6 +173,11 @@ static patch_t *kp_bossret[4];
static patch_t *kp_trickcool[2];
static patch_t *kp_capsuletarget_arrow[2][2];
static patch_t *kp_capsuletarget_icon[2];
static patch_t *kp_capsuletarget_far[2];
static patch_t *kp_capsuletarget_near[8];
void K_LoadKartHUDGraphics(void)
{
INT32 i, j, k;
@ -644,6 +649,39 @@ void K_LoadKartHUDGraphics(void)
buffer[7] = '0'+((i+1)%10);
HU_UpdatePatch(&kp_bossret[i], "%s", buffer);
}
sprintf(buffer, "HCAPARxx");
for (i = 0; i < 2; i++)
{
buffer[6] = 'A'+i;
for (j = 0; j < 2; j++)
{
buffer[7] = '0'+j;
HU_UpdatePatch(&kp_capsuletarget_arrow[i][j], "%s", buffer);
}
}
sprintf(buffer, "HUDCAPCx");
for (i = 0; i < 2; i++)
{
buffer[7] = '0'+i;
HU_UpdatePatch(&kp_capsuletarget_icon[i], "%s", buffer);
}
sprintf(buffer, "HUDCAPBx");
for (i = 0; i < 2; i++)
{
buffer[7] = '0'+i;
HU_UpdatePatch(&kp_capsuletarget_far[i], "%s", buffer);
}
sprintf(buffer, "HUDCAPAx");
for (i = 0; i < 8; i++)
{
buffer[7] = '0'+i;
HU_UpdatePatch(&kp_capsuletarget_near[i], "%s", buffer);
}
}
// For the item toggle menu
@ -699,6 +737,9 @@ const char *K_GetItemPatch(UINT8 item, boolean tiny)
return (tiny ? "K_ISDTRG" : "K_ITDTRG");
case KITEM_GARDENTOP:
return (tiny ? "K_ISGTOP" : "K_ITGTOP");
case KITEM_GACHABOM: // temp
case KRITEM_TRIPLEGACHABOM: // temp
return (tiny ? "K_ISSINK" : "K_ITSINK");
case KRITEM_TRIPLEORBINAUT:
return (tiny ? "K_ISORBN" : "K_ITORB3");
case KRITEM_QUADORBINAUT:
@ -735,6 +776,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset)
kp_kitchensink,
kp_droptarget,
kp_gardentop,
kp_kitchensink, // temp
};
if (item == KITEM_SAD || (item > KITEM_NONE && item < NUMKARTITEMS))
@ -778,7 +820,7 @@ void K_ObjectTracking(trackingResult_t *result, vector3_t *point, boolean revers
fixed_t fovDiff, fov, fovTangent, fg;
fixed_t h;
INT32 da;
INT32 da, dp;
UINT8 cameraNum = R_GetViewNumber();
@ -839,6 +881,7 @@ void K_ObjectTracking(trackingResult_t *result, vector3_t *point, boolean revers
// Determine viewpoint factors.
h = R_PointToDist2(point->x, point->y, viewx, viewy);
da = AngleDeltaSigned(viewpointAngle, R_PointToAngle2(point->x, point->y, viewx, viewy));
dp = AngleDeltaSigned(viewpointAiming, R_PointToAngle2(0, 0, h, viewz));
if (reverse)
{
@ -849,6 +892,10 @@ void K_ObjectTracking(trackingResult_t *result, vector3_t *point, boolean revers
result->x = FixedMul(NEWTAN(da), fg);
result->y = FixedMul((NEWTAN(viewpointAiming) - FixedDiv((viewz - point->z), 1 + FixedMul(NEWCOS(da), h))), fg);
result->angle = da;
result->pitch = dp;
result->fov = fg;
// Rotate for screen roll...
if (viewpointRoll)
{
@ -1014,6 +1061,40 @@ static void K_initKartHUD(void)
}
}
void K_DrawMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, UINT16 map, UINT8 *colormap)
{
patch_t *PictureOfLevel = NULL;
if (map >= nummapheaders || !mapheaderinfo[map])
{
PictureOfLevel = nolvl;
}
else if (!mapheaderinfo[map]->thumbnailPic)
{
PictureOfLevel = blanklvl;
}
else
{
PictureOfLevel = mapheaderinfo[map]->thumbnailPic;
}
K_DrawLikeMapThumbnail(x, y, width, flags, PictureOfLevel, colormap);
}
void K_DrawLikeMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, patch_t *patch, UINT8 *colormap)
{
if (flags & V_FLIP)
x += width;
V_DrawFixedPatch(
x, y,
FixedDiv(width, (320 << FRACBITS)),
flags,
patch,
colormap
);
}
// see also MT_PLAYERARROW mobjthinker in p_mobj.c
static void K_drawKartItem(void)
{
@ -1465,7 +1546,7 @@ void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UI
static boolean canplaysound = true;
tic_t timetoreach = emblem->var;
if (emblem->collected)
if (gamedata->collected[(emblem-emblemlocations)])
{
emblempic[curemb] = W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE);
emblemcol[curemb] = R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_CACHE);
@ -3041,6 +3122,217 @@ static void K_DrawWeakSpot(weakspotdraw_t *ws)
V_DrawFixedPatch(ws->x, ws->y, FRACUNIT, 0, kp_bossret[j+1], colormap);
}
typedef struct capsuletracking_s
{
mobj_t *mobj;
vector3_t point;
fixed_t camDist;
} capsuletracking_t;
static void K_DrawCapsuleTracking(capsuletracking_t *caps)
{
trackingResult_t result = {0};
INT32 timer = 0;
K_ObjectTracking(&result, &caps->point, false);
if (result.onScreen == false)
{
// Off-screen, draw alongside the borders of the screen.
// Probably the most complicated thing.
INT32 scrVal = 240;
vector2_t screenSize = {0};
INT32 borderSize = 7;
vector2_t borderWin = {0};
vector2_t borderDir = {0};
fixed_t borderLen = FRACUNIT;
vector2_t arrowDir = {0};
vector2_t arrowPos = {0};
patch_t *arrowPatch = NULL;
INT32 arrowFlags = 0;
vector2_t capsulePos = {0};
patch_t *capsulePatch = NULL;
timer = (leveltime / 3);
screenSize.x = vid.width / vid.dupx;
screenSize.y = vid.height / vid.dupy;
if (r_splitscreen >= 2)
{
// Half-wide screens
screenSize.x >>= 1;
borderSize >>= 1;
}
if (r_splitscreen >= 1)
{
// Half-tall screens
screenSize.y >>= 1;
}
scrVal = max(screenSize.x, screenSize.y) - 80;
borderWin.x = screenSize.x - borderSize;
borderWin.y = screenSize.y - borderSize;
arrowDir.x = 0;
arrowDir.y = P_MobjFlip(caps->mobj) * FRACUNIT;
// Simply pointing towards the result doesn't work, so inaccurate hack...
borderDir.x = FixedMul(
FixedMul(
FINESINE((-result.angle >> ANGLETOFINESHIFT) & FINEMASK),
FINECOSINE((-result.pitch >> ANGLETOFINESHIFT) & FINEMASK)
),
result.fov
);
borderDir.y = FixedMul(
FINESINE((-result.pitch >> ANGLETOFINESHIFT) & FINEMASK),
result.fov
);
borderLen = R_PointToDist2(0, 0, borderDir.x, borderDir.y);
if (borderLen > 0)
{
borderDir.x = FixedDiv(borderDir.x, borderLen);
borderDir.y = FixedDiv(borderDir.y, borderLen);
}
else
{
// Eh just put it at the bottom.
borderDir.x = 0;
borderDir.y = FRACUNIT;
}
capsulePatch = kp_capsuletarget_icon[timer & 1];
if (abs(borderDir.x) > abs(borderDir.y))
{
// Horizontal arrow
arrowPatch = kp_capsuletarget_arrow[1][timer & 1];
arrowDir.y = 0;
if (borderDir.x < 0)
{
// LEFT
arrowDir.x = -FRACUNIT;
}
else
{
// RIGHT
arrowDir.x = FRACUNIT;
}
}
else
{
// Vertical arrow
arrowPatch = kp_capsuletarget_arrow[0][timer & 1];
arrowDir.x = 0;
if (borderDir.y < 0)
{
// UP
arrowDir.y = -FRACUNIT;
}
else
{
// DOWN
arrowDir.y = FRACUNIT;
}
}
arrowPos.x = (screenSize.x >> 1) + FixedMul(scrVal, borderDir.x);
arrowPos.y = (screenSize.y >> 1) + FixedMul(scrVal, borderDir.y);
arrowPos.x = min(max(arrowPos.x, borderSize), borderWin.x) * FRACUNIT;
arrowPos.y = min(max(arrowPos.y, borderSize), borderWin.y) * FRACUNIT;
capsulePos.x = arrowPos.x - (arrowDir.x * 12);
capsulePos.y = arrowPos.y - (arrowDir.y * 12);
arrowPos.x -= (arrowPatch->width << FRACBITS) >> 1;
arrowPos.y -= (arrowPatch->height << FRACBITS) >> 1;
capsulePos.x -= (capsulePatch->width << FRACBITS) >> 1;
capsulePos.y -= (capsulePatch->height << FRACBITS) >> 1;
if (arrowDir.x < 0)
{
arrowPos.x += arrowPatch->width << FRACBITS;
arrowFlags |= V_FLIP;
}
if (arrowDir.y < 0)
{
arrowPos.y += arrowPatch->height << FRACBITS;
arrowFlags |= V_VFLIP;
}
V_DrawFixedPatch(
capsulePos.x, capsulePos.y,
FRACUNIT,
V_SPLITSCREEN,
capsulePatch, NULL
);
V_DrawFixedPatch(
arrowPos.x, arrowPos.y,
FRACUNIT,
V_SPLITSCREEN | arrowFlags,
arrowPatch, NULL
);
}
else
{
// Draw simple overlay.
const fixed_t farDistance = 1280*mapobjectscale;
boolean useNear = (caps->camDist < farDistance);
patch_t *capsulePatch = NULL;
vector2_t capsulePos = {0};
boolean visible = P_CheckSight(stplyr->mo, caps->mobj);
if (visible == false && (leveltime & 1))
{
// Flicker when not visible.
return;
}
capsulePos.x = result.x;
capsulePos.y = result.y;
if (useNear == true)
{
timer = (leveltime / 2);
capsulePatch = kp_capsuletarget_near[timer % 8];
}
else
{
timer = (leveltime / 3);
capsulePatch = kp_capsuletarget_far[timer & 1];
}
capsulePos.x -= (capsulePatch->width << FRACBITS) >> 1;
capsulePos.y -= (capsulePatch->height << FRACBITS) >> 1;
V_DrawFixedPatch(
capsulePos.x, capsulePos.y,
FRACUNIT,
V_SPLITSCREEN,
capsulePatch, NULL
);
}
}
static void K_drawKartNameTags(void)
{
const fixed_t maxdistance = 8192*mapobjectscale;
@ -3049,7 +3341,7 @@ static void K_drawKartNameTags(void)
UINT8 tobesorted[MAXPLAYERS];
fixed_t sortdist[MAXPLAYERS];
UINT8 sortlen = 0;
UINT8 i, j;
size_t i, j;
if (stplyr == NULL || stplyr->mo == NULL || P_MobjWasRemoved(stplyr->mo))
{
@ -3140,6 +3432,85 @@ static void K_drawKartNameTags(void)
}
}
if (battlecapsules == true)
{
#define MAX_CAPSULE_HUD 32
capsuletracking_t capsuleList[MAX_CAPSULE_HUD];
size_t capsuleListLen = 0;
mobj_t *mobj = NULL;
mobj_t *next = NULL;
for (mobj = kitemcap; mobj; mobj = next)
{
capsuletracking_t *caps = NULL;
next = mobj->itnext;
if (mobj->health <= 0)
{
continue;
}
if (mobj->type != MT_BATTLECAPSULE)
{
continue;
}
caps = &capsuleList[capsuleListLen];
caps->mobj = mobj;
caps->point.x = R_InterpolateFixed(mobj->old_x, mobj->x);
caps->point.y = R_InterpolateFixed(mobj->old_y, mobj->y);
caps->point.z = R_InterpolateFixed(mobj->old_z, mobj->z);
caps->point.z += (mobj->height >> 1);
caps->camDist = R_PointToDist2(c.x, c.y, caps->point.x, caps->point.y);
capsuleListLen++;
if (capsuleListLen >= MAX_CAPSULE_HUD)
{
break;
}
}
if (capsuleListLen > 0)
{
// Sort by distance from camera.
if (capsuleListLen > 1)
{
for (i = 0; i < capsuleListLen-1; i++)
{
size_t swap = i;
for (j = i + 1; j < capsuleListLen; j++)
{
capsuletracking_t *cj = &capsuleList[j];
capsuletracking_t *cSwap = &capsuleList[swap];
if (cj->camDist > cSwap->camDist)
{
swap = j;
}
}
if (swap != i)
{
capsuletracking_t temp = capsuleList[swap];
capsuleList[swap] = capsuleList[i];
capsuleList[i] = temp;
}
}
}
for (i = 0; i < capsuleListLen; i++)
{
K_DrawCapsuleTracking(&capsuleList[i]);
}
}
#undef MAX_CAPSULE_HUD
}
for (i = 0; i < MAXPLAYERS; i++)
{
player_t *ntplayer = &players[i];
@ -3254,7 +3625,7 @@ static void K_drawKartNameTags(void)
{
if (!(demo.playback == true && demo.freecam == true))
{
for (j = 0; j <= r_splitscreen; j++)
for (j = 0; j <= (unsigned)r_splitscreen; j++)
{
if (ntplayer == &players[displayplayers[j]])
{
@ -3262,7 +3633,7 @@ static void K_drawKartNameTags(void)
}
}
if (j <= r_splitscreen && j != cnum)
if (j <= (unsigned)r_splitscreen && j != cnum)
{
localindicator = j;
}

View file

@ -26,6 +26,8 @@ struct trackingResult_t
fixed_t x, y;
fixed_t scale;
boolean onScreen;
INT32 angle, pitch;
fixed_t fov;
};
void K_ObjectTracking(trackingResult_t *result, vector3_t *point, boolean reverse);
@ -36,5 +38,9 @@ void K_drawKartHUD(void);
void K_drawKartFreePlay(void);
void K_drawKartTimestamp(tic_t drawtime, INT32 TX, INT32 TY, INT16 emblemmap, UINT8 mode);
void K_DrawTabRankings(INT32 x, INT32 y, playersort_t *tab, INT32 scorelines, INT32 whiteplayer, INT32 hilicol);
void K_DrawMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, UINT16 map, UINT8 *colormap);
void K_DrawLikeMapThumbnail(INT32 x, INT32 y, INT32 width, UINT32 flags, patch_t *patch, UINT8 *colormap);
extern patch_t *kp_facehighlight[8];
#endif

View file

@ -436,6 +436,9 @@ SINT8 K_ItemResultToType(SINT8 getitem)
case KRITEM_DUALJAWZ:
return KITEM_JAWZ;
case KRITEM_TRIPLEGACHABOM:
return KITEM_GACHABOM;
default:
I_Error("Bad item cooldown redirect for result %d\n", getitem);
break;
@ -456,6 +459,7 @@ UINT8 K_ItemResultToAmount(SINT8 getitem)
case KRITEM_TRIPLESNEAKER:
case KRITEM_TRIPLEBANANA:
case KRITEM_TRIPLEORBINAUT:
case KRITEM_TRIPLEGACHABOM:
return 3;
case KRITEM_QUADORBINAUT:
@ -629,6 +633,7 @@ fixed_t K_GetMobjWeight(mobj_t *mobj, mobj_t *against)
break;
case MT_ORBINAUT:
case MT_ORBINAUT_SHIELD:
case MT_GACHABOM:
case MT_DUELBOMB:
if (against->player)
weight = K_PlayerWeight(against, NULL);
@ -4428,7 +4433,7 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I
finalscale = source->scale;
}
if (dir == -1 && (type == MT_ORBINAUT || type == MT_BALLHOG))
if (dir == -1 && (type == MT_ORBINAUT || type == MT_GACHABOM || type == MT_BALLHOG))
{
// Backwards nerfs
finalspeed /= 8;
@ -4485,6 +4490,7 @@ static mobj_t *K_SpawnKartMissile(mobj_t *source, mobjtype_t type, angle_t an, I
switch (type)
{
case MT_ORBINAUT:
case MT_GACHABOM:
Obj_OrbinautThrown(th, finalspeed, dir);
break;
case MT_JAWZ:
@ -5332,6 +5338,12 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing,
dir = defaultDir;
}
if (mapthing == MT_GACHABOM && dir > 0)
{
// This item is both a missile and not!
missile = false;
}
if (missile) // Shootables
{
if (dir < 0 && mapthing != MT_SPB && mapthing != MT_GARDENTOP)
@ -5399,6 +5411,16 @@ mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing,
mo->rollangle = FixedAngle(P_RandomRange(PR_DECORATION, -180, 180) << FRACBITS);
}
if (mapthing == MT_GACHABOM)
{
// Set dropped flag
mo->flags2 |= MF2_AMBUSH;
mo->movecount = 2;
P_SetMobjState(mo, mo->info->deathstate);
mo->tics = -1;
mo->color = player->skincolor;
}
// this is the small graphic effect that plops in you when you throw an item:
throwmo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, MT_FIREDITEM);
P_SetTarget(&throwmo->target, player->mo);
@ -8103,6 +8125,32 @@ void K_KartPlayerAfterThink(player_t *player)
{
K_LookForRings(player->mo);
}
if (player->invulnhitlag > 0)
{
// Hitlag from what would normally be damage but the
// player was invulnerable.
//
// If we're constantly getting hit the same number of
// times, we're probably standing on a damage floor.
//
// Checking if we're hit more than before ensures
// that:
//
// 1) repeating damage doesn't count
// 2) new damage sources still count
if (player->timeshit <= player->timeshitprev)
{
if (!P_MobjWasRemoved(player->mo))
{
player->mo->hitlag -= player->invulnhitlag;
player->mo->eflags &= ~(MFE_DAMAGEHITLAG);
}
}
player->invulnhitlag = 0;
}
}
/*--------------------------------------------------
@ -10623,6 +10671,15 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
K_UpdateHnextList(player, true);
}
break;
case KITEM_GACHABOM:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
K_ThrowKartItem(player, true, MT_GACHABOM, 0, 0, 0);
K_PlayAttackTaunt(player->mo);
player->itemamount--;
K_UpdateHnextList(player, false);
}
break;
case KITEM_SAD:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO
&& !player->sadtimer)

View file

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

View file

@ -1496,14 +1496,14 @@ menuitem_t EXTRAS_Main[] =
{IT_STRING | IT_CALL, "Addons", "Add files to customize your experience.",
NULL, {.routine = M_Addons}, 0, 0},
{IT_STRING | IT_CALL, "Challenges", "View the requirements for some of the secret content you can unlock!",
NULL, {.routine = M_Challenges}, 0, 0},
{IT_STRING | IT_CALL, "Replay Hut", "Play the replays you've saved throughout your many races & battles!",
NULL, {.routine = M_ReplayHut}, 0, 0},
{IT_STRING | IT_CALL, "Statistics", "Look back on some of your greatest achievements such as your playtime and wins!",
NULL, {NULL}, 0, 0},
{IT_STRING | IT_TRANSTEXT, "Extras Checklist", "View the requirement for some of the secret content you can unlock!",
NULL, {NULL}, 0, 0},
NULL, {.routine = M_Statistics}, 0, 0},
};
// the extras menu essentially reuses the options menu stuff
@ -1744,3 +1744,39 @@ menu_t MISC_AddonsDef = {
NULL,
NULL
};
// Challenges.
menuitem_t MISC_ChallengesStatsDummyMenu[] =
{
{IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0},
};
menu_t MISC_ChallengesDef = {
sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t),
&MainDef,
0,
MISC_ChallengesStatsDummyMenu,
BASEVIDWIDTH/2, 32,
0, 0,
98, 0,
M_DrawChallenges,
M_ChallengesTick,
NULL,
NULL,
M_ChallengesInputs,
};
menu_t MISC_StatisticsDef = {
sizeof (MISC_ChallengesStatsDummyMenu)/sizeof (menuitem_t),
&MainDef,
0,
MISC_ChallengesStatsDummyMenu,
280, 185,
0, 0,
98, 0,
M_DrawStatistics,
NULL,
NULL,
NULL,
M_StatisticsInputs,
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

@ -98,12 +98,14 @@ static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] =
{ 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink
{ 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target
{ 0, 0, 0, 3, 5, 0, 0, 0 }, // Garden Top
{ 0, 0, 0, 0, 0, 0, 0, 0 }, // Gachabom
{ 0, 0, 2, 2, 2, 0, 0, 0 }, // Sneaker x2
{ 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3
{ 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3
{ 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3
{ 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4
{ 0, 0, 1, 2, 1, 0, 0, 0 } // Jawz x2
{ 0, 0, 1, 2, 1, 0, 0, 0 }, // Jawz x2
{ 0, 0, 0, 0, 0, 0, 0, 0 } // Gachabom x3
};
static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] =
@ -130,12 +132,14 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] =
{ 0, 0 }, // Kitchen Sink
{ 2, 0 }, // Drop Target
{ 4, 0 }, // Garden Top
{ 0, 0 }, // Gachabom
{ 0, 0 }, // Sneaker x2
{ 0, 1 }, // Sneaker x3
{ 0, 0 }, // Banana x3
{ 2, 0 }, // Orbinaut x3
{ 1, 1 }, // Orbinaut x4
{ 5, 1 } // Jawz x2
{ 5, 1 }, // Jawz x2
{ 0, 0 } // Gachabom x3
};
static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] =
@ -179,13 +183,14 @@ static kartitems_t K_KartItemReelTimeAttack[] =
static kartitems_t K_KartItemReelBreakTheCapsules[] =
{
KRITEM_TRIPLEORBINAUT,
KITEM_BANANA,
KITEM_GACHABOM,
KRITEM_TRIPLEGACHABOM,
KITEM_NONE
};
static kartitems_t K_KartItemReelBoss[] =
{
// FIXME: gachabom...?
KITEM_ORBINAUT,
KITEM_BANANA,
KITEM_NONE

View file

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

View file

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

View file

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

View file

@ -232,6 +232,8 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->spinouttimer);
else if (fastcmp(field,"instashield"))
lua_pushinteger(L, plr->instashield);
else if (fastcmp(field,"invulnhitlag"))
lua_pushinteger(L, plr->invulnhitlag);
else if (fastcmp(field,"wipeoutslow"))
lua_pushinteger(L, plr->wipeoutslow);
else if (fastcmp(field,"justbumped"))
@ -408,8 +410,6 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->skincolor);
else if (fastcmp(field,"skin"))
lua_pushinteger(L, plr->skin);
else if (fastcmp(field,"availabilities"))
lua_pushinteger(L, plr->availabilities);
else if (fastcmp(field,"fakeskin"))
lua_pushinteger(L, plr->fakeskin);
else if (fastcmp(field,"lastfakeskin"))
@ -474,6 +474,10 @@ static int player_get(lua_State *L)
lua_pushinteger(L, plr->lastsidehit);
else if (fastcmp(field,"lastlinehit"))
lua_pushinteger(L, plr->lastlinehit);
else if (fastcmp(field,"timeshit"))
lua_pushinteger(L, plr->timeshit);
else if (fastcmp(field,"timeshitprev"))
lua_pushinteger(L, plr->timeshitprev);
else if (fastcmp(field,"onconveyor"))
lua_pushinteger(L, plr->onconveyor);
else if (fastcmp(field,"awayviewmobj"))
@ -577,8 +581,6 @@ static int player_set(lua_State *L)
}
else if (fastcmp(field,"skin"))
return NOSET;
else if (fastcmp(field,"availabilities"))
return NOSET;
else if (fastcmp(field,"fakeskin"))
return NOSET;
else if (fastcmp(field,"lastfakeskin"))
@ -608,6 +610,8 @@ static int player_set(lua_State *L)
plr->spinouttimer = luaL_checkinteger(L, 3);
else if (fastcmp(field,"instashield"))
plr->instashield = luaL_checkinteger(L, 3);
else if (fastcmp(field,"invulnhitlag"))
plr->invulnhitlag = luaL_checkinteger(L, 3);
else if (fastcmp(field,"wipeoutslow"))
plr->wipeoutslow = luaL_checkinteger(L, 3);
else if (fastcmp(field,"justbumped"))
@ -834,6 +838,10 @@ static int player_set(lua_State *L)
plr->lastsidehit = (INT16)luaL_checkinteger(L, 3);
else if (fastcmp(field,"lastlinehit"))
plr->lastlinehit = (INT16)luaL_checkinteger(L, 3);
else if (fastcmp(field,"timeshit"))
plr->timeshit = (UINT8)luaL_checkinteger(L, 3);
else if (fastcmp(field,"timeshitprev"))
plr->timeshitprev = (UINT8)luaL_checkinteger(L, 3);
else if (fastcmp(field,"onconveyor"))
plr->onconveyor = (INT32)luaL_checkinteger(L, 3);
else if (fastcmp(field,"awayviewmobj"))

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -184,7 +184,7 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
if (t2->player)
{
if ((t2->player->flashing > 0 && t2->hitlag == 0)
&& !(t1->type == MT_ORBINAUT || t1->type == MT_JAWZ))
&& !(t1->type == MT_ORBINAUT || t1->type == MT_JAWZ || t1->type == MT_GACHABOM))
return true;
if (t2->player->hyudorotimer)
@ -209,7 +209,7 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2)
else if (t2->type == MT_ORBINAUT || t2->type == MT_JAWZ
|| t2->type == MT_ORBINAUT_SHIELD || t2->type == MT_JAWZ_SHIELD
|| t2->type == MT_BANANA || t2->type == MT_BANANA_SHIELD
|| t2->type == MT_BALLHOG)
|| t2->type == MT_BALLHOG || t2->type == MT_GACHABOM)
{
// Other Item Damage
angle_t bounceangle = K_GetCollideAngle(t1, t2);

View file

@ -157,6 +157,40 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon)
return true;
}
boolean P_CanPickupEmblem(player_t *player, INT32 emblemID)
{
if (emblemID < 0 || emblemID >= MAXEMBLEMS)
{
// Invalid emblem ID, can't pickup.
return false;
}
if (demo.playback)
{
// Never collect emblems in replays.
return false;
}
if (player->bot)
{
// Your nefarious opponent puppy can't grab these for you.
return false;
}
return true;
}
boolean P_EmblemWasCollected(INT32 emblemID)
{
if (emblemID < 0 || emblemID >= numemblems)
{
// Invalid emblem ID, can't pickup.
return true;
}
return gamedata->collected[emblemID];
}
/** Takes action based on a ::MF_SPECIAL thing touched by a player.
* Actually, this just checks a few things (heights, toucher->player, no
* objectplace, no dead or disappearing things)
@ -508,12 +542,24 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
// Secret emblem thingy
case MT_EMBLEM:
{
if (demo.playback || special->health > MAXEMBLEMS)
boolean gotcollected = false;
if (!P_CanPickupEmblem(player, special->health - 1))
return;
emblemlocations[special->health-1].collected = true;
M_UpdateUnlockablesAndExtraEmblems();
G_SaveGameData();
if (P_IsLocalPlayer(player) && !gamedata->collected[special->health-1])
{
gamedata->collected[special->health-1] = gotcollected = true;
M_UpdateUnlockablesAndExtraEmblems(true);
G_SaveGameData();
}
if (netgame)
{
// Don't delete the object in netgames, just fade it.
return;
}
break;
}
@ -962,7 +1008,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
|| target->type == MT_BANANA || target->type == MT_BANANA_SHIELD
|| target->type == MT_DROPTARGET || target->type == MT_DROPTARGET_SHIELD
|| target->type == MT_EGGMANITEM || target->type == MT_EGGMANITEM_SHIELD
|| target->type == MT_BALLHOG || target->type == MT_SPB)) // kart dead items
|| target->type == MT_BALLHOG || target->type == MT_SPB
|| target->type == MT_GACHABOM)) // kart dead items
target->flags |= MF_NOGRAVITY; // Don't drop Tails 03-08-2000
else
target->flags &= ~MF_NOGRAVITY; // lose it if you for whatever reason have it, I'm looking at you shields
@ -1975,7 +2022,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
if (!(target->flags & MF_SHOOTABLE))
return false; // shouldn't happen...
if (!(damagetype & DMG_DEATHMASK) && target->hitlag > 0 && inflictor == NULL)
if (!(damagetype & DMG_DEATHMASK) && (target->eflags & MFE_PAUSED))
return false;
}
@ -1998,6 +2045,18 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
if (player) // Player is the target
{
{
const INT32 oldtimeshit = player->timeshit;
player->timeshit++;
// overflow prevention
if (player->timeshit < oldtimeshit)
{
player->timeshit = oldtimeshit;
}
}
if (player->pflags & PF_GODMODE)
return false;
@ -2031,6 +2090,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
// If not, then spawn the instashield effect instead.
if (!force)
{
boolean invincible = true;
sfxenum_t sfx = sfx_None;
if (gametyperules & GTR_BUMPERS)
{
if (player->bumpers <= 0 && player->karmadelay)
@ -2050,8 +2112,35 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
}
}
if (player->invincibilitytimer > 0 || K_IsBigger(target, inflictor) == true || player->hyudorotimer > 0)
if (player->invincibilitytimer > 0)
{
sfx= sfx_invind;
}
else if (K_IsBigger(target, inflictor) == true)
{
sfx = sfx_grownd;
}
else if (player->hyudorotimer > 0)
;
else
{
invincible = false;
}
if (invincible)
{
const INT32 oldhitlag = target->hitlag;
laglength = max(laglength / 2, 1);
K_SetHitLagForObjects(target, inflictor, laglength, false);
player->invulnhitlag += (target->hitlag - oldhitlag);
if (player->timeshit > player->timeshitprev)
{
S_StartSound(target, sfx);
}
// Full invulnerability
K_DoInstashield(player);
return false;

View file

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

View file

@ -277,6 +277,12 @@ P_DoSpringEx
angle_t finalAngle,
UINT16 starcolor)
{
if (object->eflags & MFE_SPRUNG)
{
// Object was already sprung this tic
return;
}
if (horizspeed < 0)
{
horizspeed = -(horizspeed);
@ -402,7 +408,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
return false;
}
spring->flags &= ~(MF_SOLID|MF_SPECIAL); // De-solidify
spring->flags |= MF_NOCLIPTHING; // De-solidify
if (spring->eflags & MFE_VERTICALFLIP)
vertispeed *= -1;
@ -445,7 +451,7 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object)
spring->angle, starcolor);
// Re-solidify
spring->flags |= (spring->info->flags & (MF_SPRING|MF_SPECIAL));
spring->flags = (spring->flags & ~(MF_NOCLIPTHING)) | (spring->info->flags & (MF_NOCLIPTHING));
if (object->player)
{
@ -921,7 +927,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
// Bubble Shield reflect
if (((thing->type == MT_BUBBLESHIELD && thing->target->player && thing->target->player->bubbleblowup)
|| (thing->player && thing->player->bubbleblowup))
&& (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ
&& (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ || tm.thing->type == MT_GACHABOM
|| tm.thing->type == MT_BANANA || tm.thing->type == MT_EGGMANITEM || tm.thing->type == MT_BALLHOG
|| tm.thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || tm.thing->type == MT_SINK
|| tm.thing->type == MT_GARDENTOP
@ -937,7 +943,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
}
else if (((tm.thing->type == MT_BUBBLESHIELD && tm.thing->target->player && tm.thing->target->player->bubbleblowup)
|| (tm.thing->player && tm.thing->player->bubbleblowup))
&& (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ
&& (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_GACHABOM
|| thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG
|| thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || thing->type == MT_SINK
|| thing->type == MT_GARDENTOP
@ -958,7 +964,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
// Droptarget reflect
if ((thing->type == MT_DROPTARGET || thing->type == MT_DROPTARGET_SHIELD)
&& (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ
&& (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ || tm.thing->type == MT_GACHABOM
|| tm.thing->type == MT_BANANA || tm.thing->type == MT_EGGMANITEM || tm.thing->type == MT_BALLHOG
|| tm.thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || tm.thing->type == MT_SINK
|| tm.thing->type == MT_GARDENTOP
@ -973,7 +979,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
return K_DropTargetCollide(thing, tm.thing) ? BMIT_CONTINUE : BMIT_ABORT;
}
else if ((tm.thing->type == MT_DROPTARGET || tm.thing->type == MT_DROPTARGET_SHIELD)
&& (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ
&& (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_GACHABOM
|| thing->type == MT_BANANA || thing->type == MT_EGGMANITEM || thing->type == MT_BALLHOG
|| thing->type == MT_SSMINE || tm.thing->type == MT_LANDMINE || thing->type == MT_SINK
|| thing->type == MT_GARDENTOP
@ -993,7 +999,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
|| thing->type == MT_DROPTARGET_SHIELD || tm.thing->type == MT_DROPTARGET_SHIELD)
return BMIT_CONTINUE;
if (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ
if (tm.thing->type == MT_ORBINAUT || tm.thing->type == MT_JAWZ || tm.thing->type == MT_GACHABOM
|| tm.thing->type == MT_ORBINAUT_SHIELD || tm.thing->type == MT_JAWZ_SHIELD
|| tm.thing->type == MT_GARDENTOP)
{
@ -1005,7 +1011,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing)
return Obj_OrbinautJawzCollide(tm.thing, thing) ? BMIT_CONTINUE : BMIT_ABORT;
}
else if (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ
else if (thing->type == MT_ORBINAUT || thing->type == MT_JAWZ || thing->type == MT_GACHABOM
|| thing->type == MT_ORBINAUT_SHIELD || thing->type == MT_JAWZ_SHIELD
|| thing->type == MT_GARDENTOP)
{

View file

@ -1196,6 +1196,13 @@ fixed_t P_GetMobjGravity(mobj_t *mo)
case MT_BATTLEBUMPER:
gravityadd /= 2;
break;
case MT_GACHABOM:
if (!(mo->flags2 & MF2_AMBUSH))
{
// Use normal gravity, unless if it was tossed.
break;
}
/*FALLTHRU*/
case MT_BANANA:
case MT_EGGMANITEM:
case MT_SSMINE:
@ -1743,7 +1750,7 @@ void P_XYMovement(mobj_t *mo)
//{ SRB2kart - Orbinaut, Ballhog
// Bump sparks
if (mo->type == MT_ORBINAUT || mo->type == MT_BALLHOG)
if (mo->type == MT_ORBINAUT || mo->type == MT_BALLHOG || mo->type == MT_GACHABOM)
{
mobj_t *fx;
fx = P_SpawnMobj(mo->x, mo->y, mo->z, MT_BUMP);
@ -1757,6 +1764,7 @@ void P_XYMovement(mobj_t *mo)
switch (mo->type)
{
case MT_ORBINAUT: // Orbinaut speed decreasing
case MT_GACHABOM:
case MT_GARDENTOP:
if (mo->health > 1)
{
@ -5175,6 +5183,7 @@ boolean P_IsKartFieldItem(INT32 type)
case MT_SINK:
case MT_DROPTARGET:
case MT_DUELBOMB:
case MT_GACHABOM:
return true;
default:
@ -6669,6 +6678,7 @@ static boolean P_MobjDeadThink(mobj_t *mobj)
case MT_LANDMINE:
//case MT_DROPTARGET:
case MT_SPB:
case MT_GACHABOM:
if (P_IsObjectOnGround(mobj))
{
P_RemoveMobj(mobj);
@ -6959,9 +6969,38 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
}
break;
case MT_EMBLEM:
if (mobj->flags2 & MF2_NIGHTSPULL)
P_NightsItemChase(mobj);
{
INT32 trans = 0;
mobj->frame &= ~FF_TRANSMASK;
mobj->renderflags &= ~RF_TRANSMASK;
if (P_EmblemWasCollected(mobj->health - 1) || !P_CanPickupEmblem(&players[consoleplayer], mobj->health - 1))
{
trans = tr_trans50;
mobj->renderflags |= (tr_trans50 << RF_TRANSSHIFT);
}
if (mobj->reactiontime > 0
&& leveltime > starttime)
{
INT32 diff = mobj->reactiontime - (signed)(leveltime - starttime);
if (diff < 10)
{
if (diff <= 0)
{
P_RemoveMobj(mobj);
return false;
}
trans = max(trans, 10-diff);
}
}
mobj->renderflags |= (trans << RF_TRANSSHIFT);
break;
}
case MT_FLOATINGITEM:
{
mobj->pitch = mobj->roll = 0;
@ -7069,6 +7108,39 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
}
}
break;
case MT_GACHABOM:
{
if (mobj->flags2 & MF2_AMBUSH)
{
mobj->friction = ORIG_FRICTION/4;
if (mobj->momx || mobj->momy)
{
mobj_t *ghost = P_SpawnGhostMobj(mobj);
if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player)
{
ghost->color = mobj->target->player->skincolor;
ghost->colorized = true;
}
}
if (P_IsObjectOnGround(mobj))
{
if (mobj->movecount > 1)
{
S_StartSound(mobj, mobj->info->activesound);
mobj->momx = mobj->momy = 0;
mobj->movecount = 1;
}
}
if (mobj->threshold > 0)
mobj->threshold--;
break;
}
}
/* FALLTHRU */
case MT_ORBINAUT:
{
Obj_OrbinautThink(mobj);
@ -9624,6 +9696,7 @@ void P_MobjThinker(mobj_t *mobj)
// Don't run any thinker code while in hitlag
if (mobj->hitlag > 0)
{
mobj->eflags |= MFE_PAUSED;
mobj->hitlag--;
if (mobj->type == MT_DROPTARGET && mobj->reactiontime > 0 && mobj->hitlag == 2)
@ -9643,7 +9716,7 @@ void P_MobjThinker(mobj_t *mobj)
return;
}
mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG|MFE_JUSTBOUNCEDWALL|MFE_DAMAGEHITLAG|MFE_SLOPELAUNCHED);
mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG|MFE_JUSTBOUNCEDWALL|MFE_DAMAGEHITLAG|MFE_SLOPELAUNCHED|MFE_PAUSED);
// sal: what the hell? is there any reason this isn't done, like, literally ANYWHERE else?
P_SetTarget(&tm.floorthing, NULL);
@ -9824,6 +9897,7 @@ void P_MobjThinker(mobj_t *mobj)
|| mobj->type == MT_CANNONBALLDECOR
|| mobj->type == MT_FALLINGROCK
|| mobj->type == MT_ORBINAUT
|| mobj->type == MT_GACHABOM
|| mobj->type == MT_JAWZ
|| (mobj->type == MT_DROPTARGET && mobj->reactiontime))
{
@ -10103,6 +10177,7 @@ static void P_DefaultMobjShadowScale(mobj_t *thing)
case MT_ROCKETSNEAKER:
case MT_SPB:
case MT_DUELBOMB:
case MT_GACHABOM:
thing->shadowscale = 3*FRACUNIT/2;
break;
case MT_BANANA_SHIELD:
@ -11424,7 +11499,7 @@ void P_RespawnSpecials(void)
}
else if (pcount > 1)
{
time = (120 - ((pcount-2) * 20)) * TICRATE;
time = (120 * TICRATE) / (pcount - 1);
// If the map is longer or shorter than 3 laps, then adjust ring respawn to account for this.
// 5 lap courses would have more retreaded ground, while 2 lap courses would have less.
@ -12043,7 +12118,7 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
if (!emblem)
{
CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, Tag_FGet(&mthing->tags));
CONS_Alert(CONS_WARNING, "P_SetupEmblem: No map emblem for map %d with tag %d found!\n", gamemap, Tag_FGet(&mthing->tags));
return false;
}
@ -12056,24 +12131,13 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting
mobj->color = (UINT16)emcolor;
if (emblemlocations[j].collected)
{
P_UnsetThingPosition(mobj);
mobj->flags |= MF_NOCLIP;
mobj->flags &= ~MF_SPECIAL;
mobj->flags |= MF_NOBLOCKMAP;
mobj->frame |= (tr_trans50 << FF_TRANSSHIFT);
P_SetThingPosition(mobj);
}
else
{
mobj->frame &= ~FF_TRANSMASK;
mobj->frame &= ~FF_TRANSMASK;
if (emblemlocations[j].type == ET_GLOBAL)
{
mobj->reactiontime = emblemlocations[j].var;
}
if (emblemlocations[j].flags & GE_TIMED)
{
mobj->reactiontime = emblemlocations[j].var;
}
return true;
}
@ -12517,7 +12581,10 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean
case MT_EMBLEM:
{
if (!P_SetupEmblem(mthing, mobj))
{
P_RemoveMobj(mobj);
return false;
}
break;
}
case MT_SKYBOX:

View file

@ -255,7 +255,8 @@ typedef enum
MFE_DAMAGEHITLAG = 1<<13,
// Slope physics sent you airborne
MFE_SLOPELAUNCHED = 1<<14,
// free: to and including 1<<15
// Thinker is paused due to hitlag
MFE_PAUSED = 1<<15,
} mobjeflag_t;
//
@ -275,14 +276,17 @@ struct mobj_t
// List: thinker links.
thinker_t thinker;
mobjtype_t type;
const mobjinfo_t *info; // &mobjinfo[mobj->type]
// Info for drawing: position.
fixed_t x, y, z;
// --- Please make sure you keep the fields up to this
// --- point in sync with degenmobj_t.
fixed_t old_x, old_y, old_z; // position interpolation
fixed_t old_x2, old_y2, old_z2;
mobjtype_t type;
const mobjinfo_t *info; // &mobjinfo[mobj->type]
// More list: links in sector (if needed)
mobj_t *snext;
mobj_t **sprev; // killough 8/11/98: change to ptr-to-ptr
@ -428,14 +432,14 @@ struct precipmobj_t
// List: thinker links.
thinker_t thinker;
mobjtype_t type;
const mobjinfo_t *info; // &mobjinfo[mobj->type]
// Info for drawing: position.
fixed_t x, y, z;
fixed_t old_x, old_y, old_z; // position interpolation
fixed_t old_x2, old_y2, old_z2;
mobjtype_t type;
const mobjinfo_t *info; // &mobjinfo[mobj->type]
// More list: links in sector (if needed)
precipmobj_t *snext;
precipmobj_t **sprev; // killough 8/11/98: change to ptr-to-ptr

View file

@ -35,6 +35,7 @@
#include "p_polyobj.h"
#include "lua_script.h"
#include "p_slopes.h"
#include "m_cond.h" // netUnlocked
// SRB2Kart
#include "k_battle.h"
@ -150,7 +151,12 @@ static void P_NetArchivePlayers(void)
WRITEUINT8(save_p, players[i].skincolor);
WRITEINT32(save_p, players[i].skin);
WRITEUINT32(save_p, players[i].availabilities);
for (j = 0; j < MAXAVAILABILITY; j++)
{
WRITEUINT8(save_p, players[i].availabilities[j]);
}
WRITEUINT8(save_p, players[i].fakeskin);
WRITEUINT8(save_p, players[i].lastfakeskin);
WRITEUINT32(save_p, players[i].score);
@ -184,6 +190,9 @@ static void P_NetArchivePlayers(void)
WRITEINT32(save_p, players[i].onconveyor);
WRITEUINT8(save_p, players[i].timeshit);
WRITEUINT8(save_p, players[i].timeshitprev);
WRITEUINT32(save_p, players[i].jointime);
WRITEUINT8(save_p, players[i].splitscreenindex);
@ -260,6 +269,7 @@ static void P_NetArchivePlayers(void)
WRITEUINT16(save_p, players[i].spinouttimer);
WRITEUINT8(save_p, players[i].spinouttype);
WRITEUINT8(save_p, players[i].instashield);
WRITEINT32(save_p, players[i].invulnhitlag);
WRITEUINT8(save_p, players[i].wipeoutslow);
WRITEUINT8(save_p, players[i].justbumped);
WRITEUINT8(save_p, players[i].tumbleBounces);
@ -514,7 +524,12 @@ static void P_NetUnArchivePlayers(void)
players[i].skincolor = READUINT8(save_p);
players[i].skin = READINT32(save_p);
players[i].availabilities = READUINT32(save_p);
for (j = 0; j < MAXAVAILABILITY; j++)
{
players[i].availabilities[j] = READUINT8(save_p);
}
players[i].fakeskin = READUINT8(save_p);
players[i].lastfakeskin = READUINT8(save_p);
players[i].score = READUINT32(save_p);
@ -546,6 +561,9 @@ static void P_NetUnArchivePlayers(void)
players[i].lastsidehit = READINT16(save_p);
players[i].lastlinehit = READINT16(save_p);
players[i].timeshit = READUINT8(save_p);
players[i].timeshitprev = READUINT8(save_p);
players[i].onconveyor = READINT32(save_p);
players[i].jointime = READUINT32(save_p);
@ -604,6 +622,7 @@ static void P_NetUnArchivePlayers(void)
players[i].spinouttimer = READUINT16(save_p);
players[i].spinouttype = READUINT8(save_p);
players[i].instashield = READUINT8(save_p);
players[i].invulnhitlag = READINT32(save_p);
players[i].wipeoutslow = READUINT8(save_p);
players[i].justbumped = READUINT8(save_p);
players[i].tumbleBounces = READUINT8(save_p);
@ -4684,9 +4703,6 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
//lastmapsaved = gamemap;
lastmaploaded = gamemap;
tokenlist = 0;
token = 0;
savedata.emeralds = READUINT16(save_p)-357;
READSTRINGN(save_p, testname, sizeof(testname));
@ -4705,7 +4721,7 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
static void P_NetArchiveMisc(boolean resending)
{
size_t i;
size_t i, j;
WRITEUINT32(save_p, ARCHIVEBLOCK_MISC);
@ -4726,7 +4742,14 @@ static void P_NetArchiveMisc(boolean resending)
WRITEUINT32(save_p, pig);
}
WRITEUINT32(save_p, tokenlist);
for (i = 0; i < MAXUNLOCKABLES;)
{
UINT8 btemp = 0;
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
btemp |= (netUnlocked[j+i] << j);
WRITEUINT8(save_p, btemp);
i += j;
}
WRITEUINT8(save_p, encoremode);
@ -4755,7 +4778,6 @@ static void P_NetArchiveMisc(boolean resending)
WRITEUINT8(save_p, globools);
}
WRITEUINT32(save_p, token);
WRITEUINT32(save_p, bluescore);
WRITEUINT32(save_p, redscore);
@ -4851,8 +4873,9 @@ static void P_NetArchiveMisc(boolean resending)
static inline boolean P_NetUnArchiveMisc(boolean reloading)
{
size_t i;
size_t i, j;
size_t numTasks;
UINT8 *old_save_p;
if (READUINT32(save_p) != ARCHIVEBLOCK_MISC)
I_Error("Bad $$$.sav at archive block Misc");
@ -4885,16 +4908,27 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
}
}
tokenlist = READUINT32(save_p);
for (i = 0; i < MAXUNLOCKABLES;)
{
UINT8 rtemp = READUINT8(save_p);
for (j = 0; j < 8 && j+i < MAXUNLOCKABLES; ++j)
netUnlocked[j+i] = ((rtemp >> j) & 1);
i += j;
}
encoremode = (boolean)READUINT8(save_p);
// FIXME: save_p should not be global!!!
old_save_p = save_p;
if (!P_LoadLevel(true, reloading))
{
CONS_Alert(CONS_ERROR, M_GetText("Can't load the level!\n"));
return false;
}
save_p = old_save_p;
// get the time
leveltime = READUINT32(save_p);
lastmap = READINT16(save_p);
@ -4918,7 +4952,6 @@ static inline boolean P_NetUnArchiveMisc(boolean reloading)
stoppedclock = !!(globools & (1<<1));
}
token = READUINT32(save_p);
bluescore = READUINT32(save_p);
redscore = READUINT32(save_p);

View file

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

View file

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

View file

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

View file

@ -4341,6 +4341,12 @@ void P_PlayerAfterThink(player_t *player)
// so a lag value of 1 is exactly attached to the player.
K_HandleFollower(player);
if (P_MobjWasRemoved(player->mo) || (player->mo->eflags & MFE_PAUSED) == 0)
{
player->timeshitprev = player->timeshit;
player->timeshit = 0;
}
if (K_PlayerUsesBotMovement(player))
{

View file

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

View file

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

View file

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

View file

@ -1148,6 +1148,10 @@ sfxinfo_t S_sfx[NUMSFX] =
{"pass15", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
{"pass16", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""},
// SRB2Kart - Blocked damage
{"grownd", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // SF_X8AWAYSOUND
{"invind", false, 64, 16, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // SF_X8AWAYSOUND
// SRB2Kart - Engine sounds
// Engine class A
{"krta00", false, 48, 65, -1, NULL, 0, -1, -1, LUMPERROR, ""},

View file

@ -1212,6 +1212,10 @@ typedef enum
sfx_pass15,
sfx_pass16,
// Blocked damage SFX
sfx_grownd,
sfx_invind,
// Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy...
// Engine class A - Low Speed, Low Weight
sfx_krta00,

View file

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

View file

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