Merge remote-tracking branch 'origin/master' into frey

This commit is contained in:
AJ Martinez 2024-03-03 20:55:40 -07:00
commit a11602869f
43 changed files with 655 additions and 292 deletions

View file

@ -1512,6 +1512,8 @@ void CONS_Printf(const char *fmt, ...)
vsprintf(txt, fmt, argptr);
va_end(argptr);
Lock_state();
// echo console prints to log file
DEBFILE(txt);
@ -1521,8 +1523,6 @@ void CONS_Printf(const char *fmt, ...)
CON_LogMessage(txt);
Lock_state();
// make sure new text is visible
con_scrollup = 0;

View file

@ -359,6 +359,7 @@ consvar_t cv_drawdist_precip = Player("drawdist_precip", "1024").values({
{0, "None"},
});
consvar_t cv_drawinput = Player("drawinput", "No").yes_no();
consvar_t cv_ffloorclip = Player("ffloorclip", "On").on_off();
consvar_t cv_fpscap = Player("fpscap", "Match refresh rate").values({
@ -445,7 +446,6 @@ consvar_t cv_showfocuslost = Player("showfocuslost", "Yes").yes_no();
void R_SetViewSize(void);
consvar_t cv_showhud = Player("showhud", "Yes").yes_no().onchange(R_SetViewSize).dont_save();
consvar_t cv_showinputjoy = Player("showinputjoy", "Off").on_off().dont_save();
consvar_t cv_skybox = Player("skybox", "On").on_off();
// Display song credits
@ -804,6 +804,7 @@ consvar_t cv_capsuletest = OnlineCheat("capsuletest", "Off").values(capsuletest_
consvar_t cv_debugcheese = OnlineCheat("debugcheese", "Off").on_off().description("Disable checks that prevent farming item boxes");
consvar_t cv_debugencorevote = OnlineCheat("debugencorevote", "Off").on_off().description("Force encore choice to appear on vote screen");
consvar_t cv_debuglapcheat = OnlineCheat("debuglapcheat", "Off").on_off().description("Permit far waypoint jumps and disable lap cheat prevention");
consvar_t cv_forcebots = OnlineCheat("forcebots", "No").yes_no().description("Force bots to appear, even in wrong game modes");
void ForceSkin_OnChange(void);
@ -816,6 +817,8 @@ consvar_t cv_kartdebugbots = OnlineCheat("debugbots", "Off").on_off().descriptio
consvar_t cv_kartdebugdistribution = OnlineCheat("debugitemodds", "Off").on_off().description("Show items that the roulette can roll");
consvar_t cv_kartdebughuddrop = OnlineCheat("debugitemdrop", "Off").on_off().description("Players drop paper items when damaged in any way");
consvar_t cv_kartdebugbotwhip = OnlineCheat("debugbotwhip", "Off").on_off().description("Disable bot ring and item pickups");
extern CV_PossibleValue_t kartdebugitem_cons_t[];
consvar_t cv_kartdebugitem = OnlineCheat("debugitem", "None").values(kartdebugitem_cons_t).description("Force item boxes to only roll one kind of item");
@ -892,7 +895,6 @@ consvar_t cv_debugrender_spriteclip = PlayerCheat("debugrender_spriteclip", "Off
consvar_t cv_debugrender_visplanes = PlayerCheat("debugrender_visplanes", "Off").on_off().description("Highlight the number of visplanes");
consvar_t cv_devmode_screen = PlayerCheat("devmode_screen", "1").min_max(1, 4).description("Choose which splitscreen player devmode applies to");
consvar_t cv_drawpickups = PlayerCheat("drawpickups", "Yes").yes_no().description("Hide rings, spheres, item capsules, prison capsules (visual only)");
consvar_t cv_drawinput = PlayerCheat("drawinput", "No").yes_no().description("Draw turn inputs outside of Record Attack (turn solver debugging)");
void lua_profile_OnChange(void);
consvar_t cv_lua_profile = PlayerCheat("lua_profile", "0").values(CV_Unsigned).onchange(lua_profile_OnChange).description("Show hook timings over an average of N tics");

View file

@ -278,12 +278,6 @@ void D_ProcessEvents(void)
HandleGamepadDeviceEvents(ev);
if (demo.savemode == demovars_s::DSM_TITLEENTRY)
{
if (G_DemoTitleResponder(ev))
continue;
}
// console input
#ifdef HAVE_THREADS
I_lock_mutex(&con_mutex);

View file

@ -2878,7 +2878,7 @@ static void Got_Mapcmd(const UINT8 **cp, INT32 playernum)
if (demo.playback && !demo.timing)
precache = false;
demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? DSM_WILLAUTOSAVE : DSM_NOTSAVING;
demo.willsave = (cv_recordmultiplayerdemos.value == 2);
demo.savebutton = 0;
G_InitNew(pencoremode, mapnumber, presetplayer, skipprecutscene);

View file

@ -94,6 +94,7 @@ extern consvar_t cv_kartdebugitem, cv_kartdebugamount, cv_kartdebugdistribution,
extern consvar_t cv_kartdebugnodes, cv_kartdebugcolorize, cv_kartdebugdirector;
extern consvar_t cv_spbtest, cv_reducevfx, cv_screenshake;
extern consvar_t cv_kartdebugwaypoints, cv_kartdebugbots;
extern consvar_t cv_kartdebugbotwhip;
extern consvar_t cv_kartdebugstart;
extern consvar_t cv_debugrank;
extern consvar_t cv_battletest;

View file

@ -1299,6 +1299,8 @@ 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, "LAPSPERSECTION"))
mapheaderinfo[num]->lapspersection = max((UINT8)i, 1u);
else if (fastcmp(word, "SKYBOXSCALE"))
mapheaderinfo[num]->skybox_scalex = mapheaderinfo[num]->skybox_scaley = mapheaderinfo[num]->skybox_scalez = (INT16)i;
else if (fastcmp(word, "SKYBOXSCALEX"))

View file

@ -509,6 +509,7 @@ struct mapheader_t
UINT16 levelflags; ///< LF_flags: merged booleans into one UINT16 for space, see below
UINT32 typeoflevel; ///< Combination of typeoflevel flags.
UINT8 numlaps; ///< Number of laps in circuit mode, unless overridden.
UINT8 lapspersection; ///< Number of laps per section in hybrid section-circuit maps.
fixed_t gravity; ///< Map-wide gravity.
char relevantskin[SKINNAMESIZE+1]; ///< Skin to use for tutorial (if not provided, uses Eggman.)
@ -927,7 +928,6 @@ extern tic_t g_fast_forward;
#include "d_clisrv.h"
extern consvar_t cv_showinputjoy; // display joystick in time attack
extern consvar_t cv_forceskin; // force clients to use the server's skin
extern consvar_t cv_downloading; // allow clients to downloading WADs.
extern consvar_t cv_nettimeout; // SRB2Kart: Advanced server options menu

View file

@ -65,6 +65,30 @@
#include "k_credits.h"
#include "k_grandprix.h"
static menuitem_t TitleEntry[] =
{
{IT_NOTHING | IT_SPACE, "Save Replay", NULL,
NULL, {NULL}, 0, 0},
};
static menu_t TitleEntryDef = {
sizeof (TitleEntry) / sizeof (menuitem_t),
NULL,
0,
TitleEntry,
0, 0,
0, 0,
MBF_SOUNDLESS,
NULL,
0, 0,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
boolean nodrawers; // for comparative timing purposes
boolean noblit; // for comparative timing purposes
tic_t demostarttime; // for comparative timing purposes
@ -1752,6 +1776,9 @@ void G_ConfirmRewind(tic_t rewindtime)
//
void G_RecordDemo(const char *name)
{
if (demo.recording)
G_CheckDemoStatus();
extern consvar_t cv_netdemosize;
INT32 maxsize;
@ -3968,7 +3995,7 @@ boolean G_CheckDemoStatus(void)
if (!demo.recording)
return false;
if (modeattacking || demo.savemode != demovars_s::DSM_NOTSAVING)
if (modeattacking || demo.willsave)
{
if (demobuf.p)
{
@ -3992,6 +4019,9 @@ void G_SaveDemo(void)
UINT8 i;
#endif
if (currentMenu == &TitleEntryDef)
M_ClearMenus(true);
// Ensure extrainfo pointer is always available, even if no info is present.
if (demoinfo_p && *(UINT32 *)demoinfo_p == 0)
{
@ -4054,14 +4084,13 @@ void G_SaveDemo(void)
md5_buffer((char *)p+16, (demobuf.buffer + length) - (p+16), p);
#endif
if (FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer)) // finally output the file.
demo.savemode = demovars_s::DSM_SAVED;
bool saved = FIL_WriteFile(demoname, demobuf.buffer, demobuf.p - demobuf.buffer); // finally output the file.
Z_Free(demobuf.buffer);
demo.recording = false;
if (!modeattacking)
{
if (demo.savemode == demovars_s::DSM_SAVED)
if (saved)
{
CONS_Printf(M_GetText("Demo %s recorded\n"), demoname);
if (gamedata->eversavedreplay == false)
@ -4076,55 +4105,6 @@ void G_SaveDemo(void)
}
}
boolean G_DemoTitleResponder(event_t *ev)
{
size_t len;
INT32 ch;
if (ev->type != ev_keydown)
return false;
ch = (INT32)ev->data1;
// Only ESC and non-keyboard keys abort connection
if (ch == KEY_ESCAPE)
{
demo.savemode = (cv_recordmultiplayerdemos.value == 2) ? demovars_s::DSM_WILLAUTOSAVE : demovars_s::DSM_NOTSAVING;
return true;
}
if (ch == KEY_ENTER || ch >= NUMKEYS)
{
demo.savemode = demovars_s::DSM_WILLSAVE;
return true;
}
if ((ch >= HU_FONTSTART && ch <= HU_FONTEND && fontv[HU_FONT].font[ch-HU_FONTSTART])
|| ch == ' ') // Allow spaces, of course
{
len = strlen(demo.titlename);
if (len < 64)
{
demo.titlename[len+1] = 0;
demo.titlename[len] = CON_ShiftChar(ch);
}
}
else if (ch == KEY_BACKSPACE)
{
if (shiftdown)
memset(demo.titlename, 0, sizeof(demo.titlename));
else
{
len = strlen(demo.titlename);
if (len > 0)
demo.titlename[len-1] = 0;
}
}
return true;
}
boolean G_CheckDemoTitleEntry(void)
{
if (menuactive || chat_on)
@ -4133,7 +4113,18 @@ boolean G_CheckDemoTitleEntry(void)
if (!G_PlayerInputDown(0, gc_b, 0) && !G_PlayerInputDown(0, gc_x, 0))
return false;
demo.savemode = demovars_s::DSM_TITLEENTRY;
demo.willsave = true;
M_OpenVirtualKeyboard(
false,
sizeof demo.titlename,
[](const char* replace) -> const char*
{
if (replace)
strlcpy(demo.titlename, replace, sizeof demo.titlename);
return demo.titlename;
},
&TitleEntryDef
);
return true;
}

View file

@ -90,13 +90,7 @@ struct demovars_s {
boolean netgame; // multiplayer netgame
tic_t savebutton; // Used to determine when the local player can choose to save the replay while the race is still going
enum {
DSM_NOTSAVING,
DSM_WILLAUTOSAVE,
DSM_TITLEENTRY,
DSM_WILLSAVE,
DSM_SAVED
} savemode;
boolean willsave;
boolean freecam;
@ -232,8 +226,6 @@ void G_DeferedPlayDemo(const char *demo);
void G_SaveDemo(void);
boolean G_DemoTitleResponder(event_t *ev);
boolean G_CheckDemoTitleEntry(void);
typedef enum

View file

@ -3933,15 +3933,68 @@ void G_GPCupIntoRoundQueue(cupheader_t *cup, UINT8 setgametype, boolean setencor
// At the end of the Cup is a Rank-restricted treat.
// So we append it to the end of the roundqueue.
// (as long as it exists, of course!)
cupLevelNum = cup->cachedlevels[CUPCACHE_SPECIAL];
if (cupLevelNum < nummapheaders)
{
G_MapIntoRoundQueue(
cupLevelNum,
G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel),
setencore, // if this isn't correct, Got_Mapcmd will fix it
true // Rank-restricted!
);
// Of course, this last minute game design tweak
// has to make things a little complicated. We
// basically just make sure they're dispensed
// at the intended difficulty sequence until
// you've got them all, at which point they
// become their intended order permanently.
// ~toast 010324
cupheader_t *emeraldcup = NULL;
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|| cup->id >= basenumkartcupheaders // custom content
|| M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order
{
// Standard order.
emeraldcup = cup;
}
else
{
// Determine order from sealedswaps.
for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++)
{
if (gamedata->sealedswaps[i] != grandprixinfo.cup)
continue;
// Repeat visit, grab the same ID.
break;
}
// If there's pending stars, get them from the associated cup order.
if (i < GDMAX_SEALEDSWAPS)
{
emeraldcup = kartcupheaders;
while (emeraldcup)
{
if (emeraldcup->id >= basenumkartcupheaders)
{
emeraldcup = NULL;
break;
}
if (emeraldcup->emeraldnum == i+1)
break;
emeraldcup = emeraldcup->next;
}
}
}
if (emeraldcup)
{
cupLevelNum = emeraldcup->cachedlevels[CUPCACHE_SPECIAL];
if (cupLevelNum < nummapheaders)
{
G_MapIntoRoundQueue(
cupLevelNum,
G_GuessGametypeByTOL(mapheaderinfo[cupLevelNum]->typeoflevel),
setencore, // if this isn't correct, Got_Mapcmd will fix it
true // Rank-restricted!
);
}
}
}
if (roundqueue.size == 0)
@ -4496,7 +4549,7 @@ void G_AfterIntermission(void)
M_PlaybackQuit(0);
return;
}
else if (demo.recording && (modeattacking || demo.savemode != DSM_NOTSAVING))
else if (demo.recording && (modeattacking || demo.willsave))
G_SaveDemo();
if (modeattacking) // End the run.

View file

@ -64,6 +64,7 @@ void srb2::save_ng_gamedata()
ng.milestones.majorkeyskipattempted = gamedata->majorkeyskipattempted;
ng.milestones.finishedtutorialchallenge = gamedata->finishedtutorialchallenge;
ng.milestones.enteredtutorialchallenge = gamedata->enteredtutorialchallenge;
ng.milestones.sealedswapalerted = gamedata->sealedswapalerted;
ng.milestones.gonerlevel = gamedata->gonerlevel;
ng.prisons.thisprisoneggpickup = gamedata->thisprisoneggpickup;
ng.prisons.prisoneggstothispickup = gamedata->prisoneggstothispickup;
@ -176,7 +177,7 @@ void srb2::save_ng_gamedata()
}
for (auto cup = kartcupheaders; cup; cup = cup->next)
{
if (cup->windata[0].best_placement == 0)
if (cup->windata[0].best_placement == 0 && cup->windata[1].got_emerald == false)
{
continue;
}
@ -229,6 +230,17 @@ void srb2::save_ng_gamedata()
ng.cups[cupdata.name] = std::move(cupdata);
}
for (int i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++)
{
srb2::GamedataSealedSwapJson sealedswap {};
cupheader_t* cup = gamedata->sealedswaps[i];
sealedswap.name = std::string(cup->name);
ng.sealedswaps.emplace_back(std::move(sealedswap));
}
std::string gamedataname_s {gamedatafilename};
fs::path savepath {fmt::format("{}/{}", srb2home, gamedataname_s)};
fs::path tmpsavepath {fmt::format("{}/{}.tmp", srb2home, gamedataname_s)};
@ -418,6 +430,7 @@ void srb2::load_ng_gamedata()
gamedata->majorkeyskipattempted = js.milestones.majorkeyskipattempted;
gamedata->finishedtutorialchallenge = js.milestones.finishedtutorialchallenge;
gamedata->enteredtutorialchallenge = js.milestones.enteredtutorialchallenge;
gamedata->sealedswapalerted = js.milestones.sealedswapalerted;
gamedata->gonerlevel = js.milestones.gonerlevel;
gamedata->thisprisoneggpickup = js.prisons.thisprisoneggpickup;
gamedata->prisoneggstothispickup = js.prisons.prisoneggstothispickup;
@ -720,6 +733,33 @@ void srb2::load_ng_gamedata()
}
}
size_t sealedswaps_size = js.sealedswaps.size();
for (size_t i = 0; i < std::min((size_t)GDMAX_SEALEDSWAPS, sealedswaps_size); i++)
{
cupheader_t* cup = nullptr;
// Find BASE cups only
for (cup = kartcupheaders; cup; cup = cup->next)
{
if (cup->id >= basenumkartcupheaders)
{
cup = NULL;
break;
}
std::string cupname = std::string(cup->name);
if (cupname == js.sealedswaps[i].name)
{
break;
}
}
if (cup)
{
gamedata->sealedswaps[i] = cup;
}
}
M_FinaliseGameData();
}

View file

@ -70,6 +70,7 @@ struct GamedataMilestonesJson final
bool majorkeyskipattempted;
bool finishedtutorialchallenge;
bool enteredtutorialchallenge;
bool sealedswapalerted;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
GamedataMilestonesJson,
@ -81,7 +82,8 @@ struct GamedataMilestonesJson final
chaokeytutorial,
majorkeyskipattempted,
finishedtutorialchallenge,
enteredtutorialchallenge
enteredtutorialchallenge,
sealedswapalerted
)
};
@ -184,6 +186,13 @@ struct GamedataCupJson final
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupJson, name, records)
};
struct GamedataSealedSwapJson final
{
std::string name;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSealedSwapJson, name)
};
struct GamedataJson final
{
GamedataPlaytimeJson playtime;
@ -203,6 +212,7 @@ struct GamedataJson final
std::unordered_map<std::string, GamedataMapJson> maps;
std::vector<GamedataSprayCanJson> spraycans;
std::unordered_map<std::string, GamedataCupJson> cups;
std::vector<GamedataSealedSwapJson> sealedswaps;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
GamedataJson,
@ -222,7 +232,8 @@ struct GamedataJson final
skins,
maps,
spraycans,
cups
cups,
sealedswaps
)
};

View file

@ -7,6 +7,7 @@
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include <algorithm>
#include <string>
#include <fmt/format.h>
@ -18,6 +19,8 @@
#include "../i_joy.h"
#include "../k_hud.h"
#include "../k_kart.h"
#include "../m_easing.h"
#include "../p_tick.h"
#include "../v_draw.hpp"
using srb2::Draw;
@ -59,8 +62,21 @@ const char* dpad_suffix(const Vec2<float>& v)
}; // namespace
void K_DrawInputDisplay(INT32 x, INT32 y, INT32 flags, char mode, UINT8 pid, boolean local, boolean transparent)
void K_DrawInputDisplay(float x, float y, INT32 flags, char mode, UINT8 pid, boolean local, boolean transparent)
{
auto fade_in = []
{
constexpr tic_t kStart = TICRATE;
constexpr tic_t kDuration = TICRATE/2;
fixed_t f = std::min(std::max(leveltime, kStart) - kStart, kDuration) * FRACUNIT / kDuration;
return Easing_Linear(f, 0, 9);
};
auto alpha_to_flag = [](int alpha) { return (9 - alpha) << V_ALPHASHIFT; };
int alpha = fade_in();
if (alpha == 0)
return;
const ticcmd_t& cmd = players[displayplayers[pid]].cmd;
const boolean analog = (mode == '4' || mode == '5') ? players[displayplayers[pid]].analoginput : false;
const std::string prefix = fmt::format("PR{}", mode);
@ -73,7 +89,8 @@ void K_DrawInputDisplay(INT32 x, INT32 y, INT32 flags, char mode, UINT8 pid, boo
Draw box = Draw(x, y).flags(flags);
box.flags(transparent ? V_TRANSLUCENT : 0).patch(gfx("CONT"));
box.flags(alpha_to_flag(alpha / (transparent ? 2 : 1))).patch(gfx("CONT"));
box = box.flags(alpha_to_flag(alpha));
Vec2<float> dpad = local ?
Vec2<float> {

View file

@ -1479,13 +1479,19 @@ static void K_BotItemInstashield(const player_t *player, ticcmd_t *cmd)
const fixed_t radius = FixedMul(mobjinfo[MT_INSTAWHIP].radius, player->mo->scale);
size_t i = SIZE_MAX;
if (K_ItemButtonWasDown(player) == true)
{
// Release the button, dude.
return;
}
boolean nearbyThreat = false; // Someone's near enough to worry about, start charging.
boolean attackOpportunity = false; // Someone's close enough to hit!
boolean coastIsClear = true; // Nobody is nearby, let any pending charge go.
if (player->instaWhipCharge || leveltime < starttime || player->spindash)
UINT8 stupidRating = MAXBOTDIFFICULTY - player->botvars.difficulty;
// Weak bots take a second to react on offense.
UINT8 reactiontime = stupidRating;
// Weak bots misjudge their attack range. Purely accurate at Lv.MAX, 250% overestimate at Lv.1
fixed_t radiusWithError = radius + 3*(radius * stupidRating / MAXBOTDIFFICULTY)/2;
// Future work: Expand threat range versus fast pursuers.
if (leveltime < starttime || player->spindash || player->defenseLockout)
{
// Instashield is on cooldown.
return;
@ -1517,18 +1523,41 @@ static void K_BotItemInstashield(const player_t *player, ticcmd_t *cmd)
(player->mo->z - target->mo->z) / 4
);
if (dist <= radius)
if (dist <= 8 * radius)
{
K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * 2);
coastIsClear = false;
}
if (dist <= 5 * radius)
{
nearbyThreat = true;
}
if (dist <= (radiusWithError + target->mo->radius))
{
attackOpportunity = true;
K_ItemConfirmForTarget(player, cmd, target, 1);
}
}
if (player->botvars.itemconfirm > 10*TICRATE)
if (player->instaWhipCharge) // Already charging, do we stay committed?
{
// Use it!!
cmd->buttons |= BT_ATTACK;
//player->botvars.itemconfirm = 0;
cmd->buttons |= BT_ATTACK; // Keep holding, unless...
// ...there are no attackers that are even distantly threatening...
if (coastIsClear)
cmd->buttons &= ~BT_ATTACK;
// ...or we're ready to rock.
if (attackOpportunity && player->instaWhipCharge >= (INSTAWHIP_CHARGETIME + reactiontime) && player->botvars.itemconfirm >= reactiontime)
cmd->buttons &= ~BT_ATTACK;
}
else // When should we get spooked and start a charge?
{
if (nearbyThreat)
cmd->buttons |= BT_ATTACK;
}
}
/*--------------------------------------------------
@ -1800,7 +1829,7 @@ static void K_UpdateBotGameplayVarsItemUsageMash(player_t *player)
--------------------------------------------------*/
void K_UpdateBotGameplayVarsItemUsage(player_t *player)
{
if (player->itemflags & IF_USERINGS)
if (player->itemflags & IF_USERINGS && !player->instaWhipCharge)
{
return;
}

View file

@ -20,6 +20,7 @@
#include "p_local.h"
#include "r_main.h"
#include "s_sound.h"
#include "m_easing.h"
/*--------------------------------------------------
void K_AddHitLag(mobj_t *mo, INT32 tics, boolean fromDamage)
@ -152,7 +153,11 @@ static void K_PlayHitLagSFX(mobj_t *victim, UINT8 tics)
soundID = sfx_dmgb1;
}
soundID += ((tics * (NUM_HITLAG_SOUNDS - 1)) + (MAXHITLAGTICS >> 1)) / MAXHITLAGTICS;
soundID += Easing_Linear(
min(FRACUNIT, FRACUNIT*tics/MAXHITLAGTICS),
0,
NUM_HITLAG_SOUNDS-1
);
S_StartSound(victim, soundID);
}

View file

@ -51,6 +51,7 @@
#include "g_party.h"
#include "k_hitlag.h"
#include "g_input.h"
#include "k_dialogue.h"
//{ Patch Definitions
static patch_t *kp_nodraw;
@ -5163,19 +5164,28 @@ static void K_drawKartFirstPerson(void)
static void K_drawInput(void)
{
UINT8 viewnum = R_GetViewNumber();
boolean freecam = camera[viewnum].freecam; //disable some hud elements w/ freecam
if (!cv_drawinput.value && !modeattacking && gametype != GT_TUTORIAL)
return;
if (stplyr->spectator || freecam || demo.attract)
return;
INT32 def[4][3] = {
{247, 156, V_SNAPTOBOTTOM | V_SNAPTORIGHT}, // 1p
{247, 56, V_SNAPTOBOTTOM | V_SNAPTORIGHT}, // 2p
{6, 52, V_SNAPTOBOTTOM | V_SNAPTOLEFT}, // 4p left
{282 - BASEVIDWIDTH/2, 52, V_SNAPTOBOTTOM | V_SNAPTORIGHT}, // 4p right
};
INT32 k = r_splitscreen <= 1 ? r_splitscreen : 2 + (R_GetViewNumber() & 1);
INT32 flags = def[k][2] | V_SPLITSCREEN | V_SLIDEIN;
INT32 k = r_splitscreen <= 1 ? r_splitscreen : 2 + (viewnum & 1);
INT32 flags = def[k][2] | V_SPLITSCREEN;
char mode = ((stplyr->pflags & PF_ANALOGSTICK) ? '4' : '2') + (r_splitscreen > 1);
bool local = !demo.playback && P_IsMachineLocalPlayer(stplyr);
K_DrawInputDisplay(
def[k][0],
def[k][1],
def[k][0] - FixedToFloat(K_GetDialogueSlide(34 * FRACUNIT)),
def[k][1] - FixedToFloat(K_GetDialogueSlide(51 * FRACUNIT)),
flags,
mode,
(local ? G_LocalSplitscreenPartyPosition : G_PartyPosition)(stplyr - players),
@ -6062,13 +6072,6 @@ void K_drawKartHUD(void)
K_drawRingCounter(gametypeinfoshown);
}
if ((modeattacking && !bossinfo.valid) || cv_drawinput.value)
{
// Draw the input UI
if (LUA_HudEnabled(hud_position))
K_drawInput();
}
// Draw the item window
if (LUA_HudEnabled(hud_item) && !freecam)
{
@ -6128,7 +6131,10 @@ void K_drawKartHUD(void)
K_drawEmeraldWin(true);
if (modeattacking || freecam) // everything after here is MP and debug only
{
K_drawInput();
return;
}
if ((gametyperules & GTR_KARMA) && !r_splitscreen && (stplyr->karthud[khud_yougotem] % 2)) // * YOU GOT EM *
V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem);
@ -6147,6 +6153,10 @@ void K_drawKartHUD(void)
{
K_drawSpectatorHUD(true);
}
else
{
K_drawInput();
}
if (cv_kartdebugdistribution.value)
K_drawDistributionDebugger();

View file

@ -63,7 +63,7 @@ void K_DrawKartPositionNumXY(
boolean exit, boolean lastLap, boolean losing
);
void K_DrawInputDisplay(INT32 x, INT32 y, INT32 flags, char mode, UINT8 pid, boolean local, boolean transparent);
void K_DrawInputDisplay(float x, float y, INT32 flags, char mode, UINT8 pid, boolean local, boolean transparent);
extern patch_t *kp_capsuletarget_arrow[2][2];
extern patch_t *kp_capsuletarget_icon[2];

View file

@ -8534,6 +8534,17 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
else if (player->rings < -20)
player->rings = -20;
if (cv_kartdebugbotwhip.value)
{
if (player->bot)
{
player->rings = 0;
player->itemtype = 0;
player->itemamount = 0;
player->itemRoulette.active = false;
}
}
if (player->spheres > 40)
player->spheres = 40;
// where's the < 0 check? see below the following block!
@ -9909,9 +9920,10 @@ static void K_UpdateDistanceFromFinishLine(player_t *const player)
// correct we need to add to it the length of the entire circuit multiplied by the number of laps
// left after this one. This will give us the total distance to the finish line, and allow item
// distance calculation to work easily
if ((mapheaderinfo[gamemap - 1]->levelflags & LF_SECTIONRACE) == 0U)
const mapheader_t *mapheader = mapheaderinfo[gamemap - 1];
if ((mapheader->levelflags & LF_SECTIONRACE) == 0U)
{
const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps);
const UINT8 numfulllapsleft = ((UINT8)numlaps - player->laps) / mapheader->lapspersection;
player->distancetofinish += numfulllapsleft * K_GetCircuitLength();
}
}
@ -9953,18 +9965,27 @@ static void K_UpdatePlayerWaypoints(player_t *const player)
UINT32 delta = u32_delta(player->distancetofinish, player->distancetofinishprev);
if (player->respawn.state == RESPAWNST_NONE && delta > distance_threshold && old_currentwaypoint != NULL)
{
CONS_Debug(DBG_GAMELOGIC, "Player %s: waypoint ID %d too far away (%u > %u)\n",
sizeu1(player - players), K_GetWaypointID(player->nextwaypoint), delta, distance_threshold);
extern consvar_t cv_debuglapcheat;
#define debug_args "Player %s: waypoint ID %d too far away (%u > %u)\n", \
sizeu1(player - players), K_GetWaypointID(player->nextwaypoint), delta, distance_threshold
if (cv_debuglapcheat.value)
CONS_Printf(debug_args);
else
CONS_Debug(DBG_GAMELOGIC, debug_args);
#undef debug_args
// Distance jump is too great, keep the old waypoints and old distance.
player->currentwaypoint = old_currentwaypoint;
player->nextwaypoint = old_nextwaypoint;
player->distancetofinish = player->distancetofinishprev;
// Start the auto respawn timer when the distance jumps.
if (!player->bigwaypointgap)
if (!cv_debuglapcheat.value)
{
player->bigwaypointgap = 35;
// Distance jump is too great, keep the old waypoints and old distance.
player->currentwaypoint = old_currentwaypoint;
player->nextwaypoint = old_nextwaypoint;
player->distancetofinish = player->distancetofinishprev;
// Start the auto respawn timer when the distance jumps.
if (!player->bigwaypointgap)
{
player->bigwaypointgap = 35;
}
}
}
else
@ -11382,7 +11403,7 @@ static void K_KartSpindash(player_t *player)
if (player->spindash > 0 && (buttons & (BT_DRIFT|BT_BRAKE|BT_ACCELERATE)) != (BT_DRIFT|BT_BRAKE|BT_ACCELERATE))
{
player->spindashspeed = (player->spindash * FRACUNIT) / MAXCHARGETIME;
player->spindashspeed = (min(player->spindash, MAXCHARGETIME) * FRACUNIT) / MAXCHARGETIME;
player->spindashboost = TICRATE;
// if spindash was charged enough, give a small thrust.
@ -12003,6 +12024,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
S_StartSound(player->mo, sfx_kc50);
player->instaWhipCharge = 0;
player->botvars.itemconfirm = 0;
}
else
{
@ -12436,6 +12458,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
{
INT32 numhogs = min((player->ballhogcharge / BALLHOGINCREMENT), player->itemamount);
K_SetItemOut(player); // need this to set itemscale
if (numhogs <= 0)
{
// no tapfire scams
@ -12464,6 +12488,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
K_PlayAttackTaunt(player->mo);
}
K_UnsetItemOut(player);
player->ballhogcharge = 0;
player->itemflags &= ~IF_HOLDREADY;
player->botvars.itemconfirm = 0;
@ -12475,7 +12500,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
player->itemamount--;
K_SetItemOut(player);
K_ThrowKartItem(player, true, MT_SPB, 1, 0, 0);
K_UnsetItemOut(player);
K_PlayAttackTaunt(player->mo);
player->botvars.itemconfirm = 0;
}
@ -12802,7 +12829,9 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
case KITEM_GACHABOM:
if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO)
{
K_SetItemOut(player); // need this to set itemscale
K_ThrowKartItem(player, true, MT_GACHABOM, 0, 0, 0);
K_UnsetItemOut(player);
K_PlayAttackTaunt(player->mo);
player->itemamount--;
player->roundconditions.gachabom_miser = (

View file

@ -542,6 +542,7 @@ extern INT32 menuKey; // keyboard key pressed for menu
extern INT16 virtualKeyboard[5][NUMVIRTUALKEYSINROW];
extern INT16 shift_virtualKeyboard[5][NUMVIRTUALKEYSINROW];
typedef const char *(*vkb_query_fn_t)(const char *replace);
extern struct menutyping_s
{
boolean active; // Active
@ -554,7 +555,10 @@ extern struct menutyping_s
boolean keyboardcapslock;
boolean keyboardshift;
char cache[MAXSTRINGLENGTH]; // cached string
vkb_query_fn_t queryfn; // callback on open and close
menu_t *dummymenu;
size_t cachelen;
char *cache; // cached string
} menutyping;
// While typing, we'll have a fade strongly darken the screen to overlay the typing menu instead
@ -680,7 +684,10 @@ void M_Init(void);
void M_PlayMenuJam(void);
void M_OpenVirtualKeyboard(boolean gamepad);
boolean M_ConsiderSealedSwapAlert(void);
void M_OpenVirtualKeyboard(boolean gamepad, size_t cachelen, vkb_query_fn_t queryfn, menu_t *dummymenu);
void M_AbortVirtualKeyboard(void);
void M_MenuTypingInput(INT32 key);
void M_QuitResponse(INT32 ch);
@ -1347,6 +1354,8 @@ extern struct challengesmenu_s {
boolean chaokeyadd, keywasadded;
UINT8 chaokeyhold;
boolean considersealedswapalert;
boolean requestflip;
UINT16 unlockcount[CMC_MAX];

View file

@ -495,6 +495,21 @@ static void M_DrawMenuTooltips(void)
}
}
static const char *M_MenuTypingCroppedString(void)
{
static char buf[36];
const char *p = menutyping.cache;
size_t n = strlen(p);
if (n > sizeof buf)
{
p += n - sizeof buf;
n = sizeof buf;
}
memcpy(buf, p, n);
buf[n] = '\0';
return buf;
}
// Draws the typing submenu
static void M_DrawMenuTyping(void)
{
@ -534,7 +549,7 @@ static void M_DrawMenuTyping(void)
V_DrawFill(x + 4, y + 4 + 5, 1, 8+6, 121);
V_DrawFill(x + 5 + boxwidth - 8, y + 4 + 5, 1, 8+6, 121);
INT32 textwidth = M_DrawCaretString(x + 8, y + 12, menutyping.cache, true);
INT32 textwidth = M_DrawCaretString(x + 8, y + 12, M_MenuTypingCroppedString(), true);
if (skullAnimCounter < 4
&& menutyping.menutypingclose == false
&& menutyping.menutypingfade == (menutyping.keyboardtyping ? 9 : 18))
@ -2823,6 +2838,57 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi
rankx += 19 - (rankw / 2);
cupwindata_t *windata = &(cup->windata[difficulty]);
UINT8 emeraldnum = UINT8_MAX;
if (!noemerald)
{
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|| cup->id >= basenumkartcupheaders // custom content
|| M_SecretUnlocked(SECRET_SPECIALATTACK, true)) // true order
{
// Standard order.
if (windata->got_emerald == true)
{
emeraldnum = cup->emeraldnum;
}
}
else
{
// Determine order from sealedswaps.
UINT8 i;
for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++)
{
if (gamedata->sealedswaps[i] != cup)
continue;
break;
}
// If there's pending stars, get them from the associated cup order.
if (i < GDMAX_SEALEDSWAPS)
{
cupheader_t *emeraldcup = kartcupheaders;
while (emeraldcup)
{
if (emeraldcup->id >= basenumkartcupheaders)
{
emeraldcup = NULL;
break;
}
if (emeraldcup->emeraldnum == i+1)
{
if (emeraldcup->windata[difficulty].got_emerald == true)
emeraldnum = i+1;
break;
}
emeraldcup = emeraldcup->next;
}
}
}
}
if (windata->best_placement == 0)
{
if (statsmode)
@ -2832,14 +2898,13 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi
V_DrawCharacter((14-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false);
rankx += 14 + 1;
V_DrawCharacter((12-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false);
if (!noemerald)
{
rankx += 12 + 1;
V_DrawCharacter((12-4)/2 + rankx, ranky, '.' | V_GRAYMAP, false);
}
}
return rankw;
else
{
rankx += 14 + 1;
}
goto windataemeraldmaybe;
}
UINT8 *colormap = NULL;
@ -2934,13 +2999,15 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi
if (charPat)
V_DrawFixedPatch((rankx)*FRACUNIT, (ranky)*FRACUNIT, FRACUNIT, 0, charPat, colormap);
windataemeraldmaybe:
rankx += 12 + 1;
if (noemerald)
;
else if (windata->got_emerald == true)
else if (emeraldnum != UINT8_MAX)
{
if (cup->emeraldnum == 0)
if (emeraldnum == 0)
V_DrawCharacter(rankx+2, ranky+2, '+', false);
else
{
@ -2948,14 +3015,14 @@ fixed_t M_DrawCupWinData(INT32 rankx, INT32 ranky, cupheader_t *cup, UINT8 diffi
if (!flash)
{
UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (cup->emeraldnum-1) % 7;
UINT16 col = SKINCOLOR_CHAOSEMERALD1 + (emeraldnum-1) % 7;
colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE);
}
const char *emname = va(
"%sMAP%c",
(cup->emeraldnum > 7) ? "SUP" : "EME",
(emeraldnum > 7) ? "SUP" : "EME",
colormap ? '\0' : 'B'
);
@ -3116,7 +3183,7 @@ void M_DrawCupSelect(void)
y += 44; //(8 + 100) - (20 + 44)
}
if (windata && windata->best_placement != 0)
if (windata)
{
M_DrawCupWinData(
x,

View file

@ -188,6 +188,14 @@ static void M_ChangeCvar(INT32 choice)
M_ChangeCvarDirect(choice, currentMenu->menuitems[itemOn].itemaction.cvar);
}
static const char *M_QueryCvarAction(const char *replace)
{
consvar_t *cvar = currentMenu->menuitems[itemOn].itemaction.cvar;
if (replace)
CV_Set(cvar, replace);
return cvar->string;
}
boolean M_NextOpt(void)
{
INT16 oldItemOn = itemOn; // prevent infinite loop
@ -560,6 +568,35 @@ void M_PlayMenuJam(void)
#undef IsCurrentlyPlaying
boolean M_ConsiderSealedSwapAlert(void)
{
if (gamedata->sealedswapalerted == true)
return false;
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|| M_SecretUnlocked(SECRET_SPECIALATTACK, true)) // true order
{
gamedata->sealedswapalerted = true;
// Don't make a message if no Sealed Stars have yet been found.
if (gamedata->everseenspecial == false)
return false;
M_StartMessage(
"Message from the Stars",
"As if called by fate, the Emeralds you've\n"
"collected return to their rightful places...\n"
"\n"
"The Sealed Stars are now ordered via Cups!\n",
NULL, MM_NOTHING, NULL, NULL
);
return true;
}
return false;
}
void M_ValidateRestoreMenu(void)
{
if (restoreMenu == NULL || restoreMenu == &MAIN_GonerDef)
@ -629,6 +666,11 @@ menu_t *M_SpecificMenuRestore(menu_t *torestore)
M_SetupPlayMenu(-1);
PLAY_CharSelectDef.prevMenu = &MainDef;
if (torestore != &MISC_ChallengesDef)
{
M_ConsiderSealedSwapAlert();
}
return torestore;
}
@ -797,7 +839,7 @@ void M_ClearMenus(boolean callexitmenufunc)
D_StartTitle();
}
menutyping.active = false;
M_AbortVirtualKeyboard();
menumessage.active = false;
menuactive = false;
@ -884,6 +926,8 @@ void M_SetupNextMenu(menu_t *menudef, boolean notransition)
void M_GoBack(INT32 choice)
{
const INT16 behaviourflags = currentMenu->behaviourflags;
(void)choice;
noFurtherInput = true;
@ -906,7 +950,8 @@ void M_GoBack(INT32 choice)
else // No returning to the title screen.
M_QuitSRB2(-1);
S_StartSound(NULL, sfx_s3k5b);
if (!(behaviourflags & MBF_SOUNDLESS))
S_StartSound(NULL, sfx_s3k5b);
}
//
@ -1090,7 +1135,8 @@ static void M_HandleMenuInput(void)
// If we're hovering over a IT_CV_STRING option, pressing A/X opens the typing submenu
if (M_MenuConfirmPressed(pid))
{
M_OpenVirtualKeyboard(thisMenuKey == -1); // If we entered this menu by pressing a menu Key, default to keyboard typing, otherwise use controller.
// If we entered this menu by pressing a menu Key, default to keyboard typing, otherwise use controller.
M_OpenVirtualKeyboard(thisMenuKey == -1, MAXSTRINGLENGTH, M_QueryCvarAction, NULL);
return;
}
else if (M_MenuExtraPressed(pid))

View file

@ -83,6 +83,7 @@ static struct podiumData_s
sfxenum_t gradeVoice;
cupheader_t *cup;
UINT8 emeraldnum;
boolean fastForward;
@ -102,6 +103,7 @@ void podiumData_s::Init(void)
{
rank = grandprixinfo.rank;
cup = grandprixinfo.cup;
emeraldnum = cup->emeraldnum;
}
else
{
@ -119,6 +121,7 @@ void podiumData_s::Init(void)
cup = cup->next;
}
emeraldnum = 0;
memset(&rank, 0, sizeof(gpRank_t));
rank.skin = players[consoleplayer].skin;
@ -628,12 +631,7 @@ void podiumData_s::Draw(void)
case GPEVENT_SPECIAL:
{
srb2::Draw drawer_emerald = drawer_gametype;
UINT8 emeraldNum = 0;
if (cup != nullptr)
{
emeraldNum = cup->emeraldnum;
}
UINT8 emeraldNum = g_podiumData.emeraldnum;
boolean useWhiteFrame = ((leveltime & 1) || !dta->gotSpecialPrize);
patch_t *emeraldPatch = nullptr;
@ -844,12 +842,7 @@ void podiumData_s::Draw(void)
if (rank.specialWon == true)
{
UINT8 emeraldNum = 0;
if (cup != nullptr)
{
emeraldNum = cup->emeraldnum;
}
UINT8 emeraldNum = g_podiumData.emeraldnum;
const boolean emeraldBlink = (leveltime & 1);
patch_t *emeraldOverlay = nullptr;
@ -1263,6 +1256,60 @@ void K_ResetCeremony(void)
return;
}
cupheader_t *emeraldcup = NULL;
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|| grandprixinfo.cup->id >= basenumkartcupheaders // custom content
|| M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order
{
// Standard order.
emeraldcup = grandprixinfo.cup;
}
else
{
// Determine order from sealedswaps.
for (i = 0; i < GDMAX_SEALEDSWAPS; i++)
{
if (gamedata->sealedswaps[i] == NULL)
{
if (g_podiumData.rank.specialWon == true)
{
// First visit! Mark it off.
gamedata->sealedswaps[i] = grandprixinfo.cup;
}
break;
}
if (gamedata->sealedswaps[i] != grandprixinfo.cup)
continue;
// Repeat visit, grab the same ID.
break;
}
// If there's pending stars, apply them to the new cup order.
if (i < GDMAX_SEALEDSWAPS)
{
emeraldcup = kartcupheaders;
while (emeraldcup)
{
if (emeraldcup->id >= basenumkartcupheaders)
{
emeraldcup = NULL;
break;
}
if (emeraldcup->emeraldnum == i+1)
break;
emeraldcup = emeraldcup->next;
}
g_podiumData.emeraldnum = i+1;
}
}
// Write grade, position, and emerald-having-ness for later sessions!
i = (grandprixinfo.masterbots) ? KARTGP_MASTER : grandprixinfo.gamespeed;
@ -1292,9 +1339,9 @@ void K_ResetCeremony(void)
anymerit = true;
}
if (g_podiumData.rank.specialWon == true)
if (g_podiumData.rank.specialWon == true && emeraldcup)
{
grandprixinfo.cup->windata[i].got_emerald = true;
emeraldcup->windata[i].got_emerald = true;
anymerit = true;
}

View file

@ -2535,6 +2535,8 @@ 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,"lapspersection"))
lua_pushinteger(L, header->lapspersection);
else if (fastcmp(field,"levelselect"))
lua_pushinteger(L, header->levelselect);
else if (fastcmp(field,"levelflags"))

View file

@ -96,6 +96,7 @@ static UINT8 cheatf_warp(void)
if (success)
{
gamedata->gonerlevel = GDGONER_DONE;
gamedata->sealedswapalerted = true;
G_SetUsedCheats();
}
@ -231,6 +232,7 @@ static UINT8 cheatf_devmode(void)
}
gamedata->gonerlevel = GDGONER_DONE;
gamedata->sealedswapalerted = true;
M_ClearMenus(true);

View file

@ -666,6 +666,7 @@ void M_ClearStats(void)
gamedata->majorkeyskipattempted = false;
gamedata->enteredtutorialchallenge = false;
gamedata->finishedtutorialchallenge = false;
gamedata->sealedswapalerted = false;
gamedata->musicstate = GDMUSIC_NONE;
gamedata->importprofilewins = false;
@ -720,6 +721,8 @@ void M_ClearSecrets(void)
skincolors[i].cache_spraycan = UINT16_MAX;
}
memset(gamedata->sealedswaps, 0, sizeof(gamedata->sealedswaps));
Z_Free(gamedata->challengegrid);
gamedata->challengegrid = NULL;
gamedata->challengegridwidth = 0;

View file

@ -291,6 +291,7 @@ typedef enum {
// This is the largest number of 9s that will fit in UINT32 and UINT16 respectively.
#define GDMAX_RINGS 999999999
#define GDMAX_CHAOKEYS 9999
#define GDMAX_SEALEDSWAPS 7
#define GDCONVERT_ROUNDSTOKEY 14
@ -371,12 +372,15 @@ struct gamedata_t
UINT32 totalrings;
UINT32 totaltumbletime;
// Chao Key condition bypass
// CHAO KEYS AND THEIR GENERATION
UINT32 pendingkeyrounds;
UINT8 pendingkeyroundoffset;
UINT16 keyspending;
UINT16 chaokeys;
// EMERALD REMAPPING
cupheader_t *sealedswaps[GDMAX_SEALEDSWAPS];
// SPECIFIC SPECIAL EVENTS
boolean everloadedaddon;
boolean everfinishedcredits;
@ -387,6 +391,7 @@ struct gamedata_t
boolean majorkeyskipattempted;
boolean enteredtutorialchallenge;
boolean finishedtutorialchallenge;
boolean sealedswapalerted;
gdmusic_t musicstate;
UINT8 gonerlevel;

View file

@ -335,6 +335,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
challengesmenu.requestnew = false;
challengesmenu.chaokeyadd = false;
challengesmenu.keywasadded = false;
challengesmenu.considersealedswapalert = false;
challengesmenu.chaokeyhold = 0;
challengesmenu.currentunlock = MAXUNLOCKABLES;
challengesmenu.unlockcondition = NULL;
@ -668,10 +669,15 @@ void M_ChallengesTick(void)
if (challengesmenu.currentunlock < MAXUNLOCKABLES
&& challengesmenu.unlockanim == UNLOCKTIME)
{
unlockable_t *ref = &unlockables[challengesmenu.currentunlock];
// Unlock animation... also tied directly to the actual unlock!
gamedata->unlocked[challengesmenu.currentunlock] = true;
M_UpdateUnlockablesAndExtraEmblems(true, true);
if (ref->type == SECRET_SPECIALATTACK)
challengesmenu.considersealedswapalert = true;
// Update shown description just in case..?
if (challengesmenu.unlockcondition)
Z_Free(challengesmenu.unlockcondition);
@ -682,12 +688,10 @@ void M_ChallengesTick(void)
if (challengesmenu.extradata)
{
unlockable_t *ref;
UINT16 bombcolor;
M_UpdateChallengeGridExtraData(challengesmenu.extradata);
ref = &unlockables[challengesmenu.currentunlock];
bombcolor = SKINCOLOR_NONE;
if (ref->color != SKINCOLOR_NONE && ref->color < numskincolors)
@ -747,7 +751,14 @@ void M_ChallengesTick(void)
// Play music the moment control returns.
M_PlayMenuJam();
if (gamedata->chaokeytutorial == false
if (challengesmenu.considersealedswapalert == true
&& M_ConsiderSealedSwapAlert() == true)
{
// No keygen tutorial in this case...
// not ideal but at least unlikely to
// get at same time?? :V
}
else if (gamedata->chaokeytutorial == false
&& challengesmenu.keywasadded == true)
{
M_ChallengesTutorial(CCTUTORIAL_KEYGEN);

View file

@ -8,7 +8,7 @@
#include "../m_easing.h"
#include "../p_local.h" // cv_tilting
extern "C" consvar_t cv_mindelay;
extern "C" consvar_t cv_mindelay, cv_drawinput;
using srb2::Draw;
@ -119,6 +119,9 @@ menuitem_t OPTIONS_ProfileAccessibility[] = {
{IT_STRING | IT_CVAR, "Screenshake", "Adjust shake intensity from hazards and offroad.",
NULL, {.cvar = &cv_screenshake}, 0, 0},
{IT_STRING | IT_CVAR, "Input Display", "Show virtual controller on the HUD.",
NULL, {.cvar = &cv_drawinput}, 0, 0},
};
menu_t OPTIONS_ProfileAccessibilityDef = {

View file

@ -116,6 +116,9 @@ void M_StopMessage(INT32 choice)
boolean M_MenuMessageTick(void)
{
if (menuwipe)
return false;
if (menumessage.closing)
{
if (menumessage.closing > MENUMESSAGECLOSE)

View file

@ -5,6 +5,7 @@
#include "../../s_sound.h"
#include "../../console.h" // CON_ShiftChar
#include "../../i_system.h" // I_Clipboard funcs
#include "../../z_zone.h"
// Typing "sub"-menu
struct menutyping_s menutyping;
@ -91,9 +92,9 @@ boolean M_ChangeStringCvar(INT32 choice)
const char *paste = I_ClipboardPaste();
if (paste == NULL || paste[0] == '\0')
;
else if (len < MAXSTRINGLENGTH - 1)
else if (len < menutyping.cachelen)
{
strlcat(menutyping.cache, paste, MAXSTRINGLENGTH);
strlcat(menutyping.cache, paste, menutyping.cachelen + 1);
S_StartSound(NULL, sfx_tmxbdn); // Tails
}
@ -146,7 +147,7 @@ boolean M_ChangeStringCvar(INT32 choice)
if (choice >= 32 && choice <= 127)
{
len = strlen(menutyping.cache);
if (len < MAXSTRINGLENGTH - 1)
if (len < menutyping.cachelen)
{
menutyping.cache[len++] = (char)choice;
menutyping.cache[len] = 0;
@ -180,7 +181,19 @@ static void M_ToggleVirtualShift(void)
static void M_CloseVirtualKeyboard(void)
{
menutyping.menutypingclose = true; // close menu.
CV_Set(currentMenu->menuitems[itemOn].itemaction.cvar, menutyping.cache);
menutyping.queryfn(menutyping.cache);
}
void M_AbortVirtualKeyboard(void)
{
if (!menutyping.active)
return;
menutyping.active = false;
Z_Free(menutyping.cache);
if (currentMenu == menutyping.dummymenu)
M_GoBack(0);
}
static boolean M_IsTypingKey(INT32 key)
@ -202,7 +215,7 @@ void M_MenuTypingInput(INT32 key)
// Closing
menutyping.menutypingfade--;
if (!menutyping.menutypingfade)
menutyping.active = false;
M_AbortVirtualKeyboard();
return; // prevent inputs while closing the menu.
}
@ -405,11 +418,28 @@ void M_MenuTypingInput(INT32 key)
}
}
void M_OpenVirtualKeyboard(boolean gamepad)
void M_OpenVirtualKeyboard(boolean gamepad, size_t cachelen, vkb_query_fn_t queryfn, menu_t *dummymenu)
{
menutyping.keyboardtyping = !gamepad;
menutyping.active = true;
menutyping.menutypingclose = false;
strlcpy(menutyping.cache, currentMenu->menuitems[itemOn].itemaction.cvar->string, MAXSTRINGLENGTH);
menutyping.queryfn = queryfn;
menutyping.dummymenu = dummymenu;
menutyping.cachelen = cachelen;
Z_Malloc(cachelen + 1, PU_STATIC, &menutyping.cache);
strlcpy(menutyping.cache, queryfn(NULL), cachelen + 1);
if (dummymenu)
{
if (!menuactive)
{
M_StartControlPanel();
dummymenu->prevMenu = NULL;
}
else
dummymenu->prevMenu = currentMenu;
M_SetupNextMenu(dummymenu, true);
}
}

View file

@ -228,7 +228,7 @@ void Obj_SpawnGachaBomRebound(mobj_t* source, mobj_t* target)
{
mobj_t *x = P_SpawnMobjFromMobjUnscaled(source, 0, 0, target->height / 2, MT_GACHABOM_REBOUND);
x->color = target->color;
x->color = target->player ? target->player->skincolor : target->color;
x->angle = angle;
if (!(gametyperules & GTR_BUMPERS) || battleprisons)

View file

@ -2935,7 +2935,9 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
{
sfx = sfx_invind;
}
else if (K_IsBigger(target, inflictor) == true)
else if (K_IsBigger(target, inflictor) == true &&
// SPB bypasses grow (K_IsBigger handles NULL check)
(type != DMG_EXPLODE || inflictor->type != MT_SPBEXPLOSION || !inflictor->movefactor))
{
sfx = sfx_grownd;
}

View file

@ -11290,7 +11290,7 @@ tic_t itemrespawntime[ITEMQUESIZE];
size_t iquehead, iquetail;
#ifdef PARANOIA
#define SCRAMBLE_REMOVED // Force debug build to crash when Removed mobj is accessed
//#define SCRAMBLE_REMOVED // Force debug build to crash when Removed mobj is accessed
#endif
void P_RemoveMobj(mobj_t *mobj)
{

View file

@ -6286,6 +6286,14 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save)
{
UINT32 val = READUINT32(save->p);
if (roundqueue.entries[i].rankrestricted && roundqueue.position != i+1)
{
// If this is a Sealed Star that hasn't yet been
// reached, don't be picky about divergance. Just
// use the base game without question. ~toast 010324
continue;
}
mapnum = roundqueue.entries[i].mapnum;
if (mapnum < nummapheaders && mapheaderinfo[mapnum] != NULL)
{

View file

@ -458,6 +458,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 num)
mapheaderinfo[num]->palette = UINT16_MAX;
mapheaderinfo[num]->encorepal = UINT16_MAX;
mapheaderinfo[num]->numlaps = NUMLAPS_DEFAULT;
mapheaderinfo[num]->lapspersection = 1;
mapheaderinfo[num]->levelselect = 0;
mapheaderinfo[num]->levelflags = 0;
mapheaderinfo[num]->menuflags = 0;

View file

@ -1178,9 +1178,8 @@ void P_Ticker(boolean run)
{
G_WriteAllGhostTics();
if (cv_recordmultiplayerdemos.value && (demo.savemode == DSM_NOTSAVING || demo.savemode == DSM_WILLAUTOSAVE))
if (demo.savebutton && demo.savebutton + 3*TICRATE < leveltime)
G_CheckDemoTitleEntry();
if (cv_recordmultiplayerdemos.value && demo.savebutton && demo.savebutton + 3*TICRATE < leveltime)
G_CheckDemoTitleEntry();
}
else if (demo.playback) // Use Ghost data for consistency checks.
{

View file

@ -296,9 +296,29 @@ UINT8 P_GetNextEmerald(void)
{
cupheader_t *cup = NULL;
if (grandprixinfo.gp == true)
if (grandprixinfo.gp == true && grandprixinfo.cup)
{
cup = grandprixinfo.cup;
if (gamedata->sealedswaps[GDMAX_SEALEDSWAPS-1] != NULL // all found
|| grandprixinfo.cup->id >= basenumkartcupheaders // custom content
|| M_SecretUnlocked(SECRET_SPECIALATTACK, false)) // true order
{
cup = grandprixinfo.cup;
}
else
{
// Determine order from sealedswaps.
UINT8 i;
for (i = 0; (i < GDMAX_SEALEDSWAPS && gamedata->sealedswaps[i]); i++)
{
if (gamedata->sealedswaps[i] != grandprixinfo.cup)
continue;
// Repeat visit, grab the same ID.
break;
}
return i+1;
}
}
if (cup == NULL)

View file

@ -1617,7 +1617,11 @@ void R_RenderPlayerView(void)
{
if (top > bot)
std::swap(top, bot);
UINT8* p = &screens[0][x + top * vid.width];
if (top < 0)
top = 0;
if (bot > viewheight-1)
bot = viewheight-1;
UINT8* p = &topleft[x + top * vid.width];
while (top <= bot)
{
*p = 35;
@ -1634,7 +1638,7 @@ void R_RenderPlayerView(void)
INT32 bottom = pl->bottom[pl->minx];
span(pl->minx, top, bottom);
span(pl->maxx, pl->top[pl->maxx], pl->bottom[pl->maxx]);
for (INT32 x = pl->minx + 1; x < pl->maxx; ++x)
for (INT32 x = pl->minx + 1; x < std::min(pl->maxx, viewwidth); ++x)
{
INT32 new_top = pl->top[x];
INT32 new_bottom = pl->bottom[x];
@ -1668,14 +1672,14 @@ void R_RenderPlayerView(void)
INT32 width = (portal->end - portal->start);
INT32 i;
for (i = 0; i < width; ++i)
for (i = 0; i < std::min(width, viewwidth); ++i)
{
INT32 yl = std::max(portal->ceilingclip[i] + 1, 0);
INT32 yh = std::min(static_cast<INT32>(portal->floorclip[i]), viewheight);
for (; yl < yh; ++yl)
{
screens[0][portal->start + i + (yl * vid.width)] = pal;
topleft[portal->start + i + (yl * vid.width)] = pal;
}
}

View file

@ -126,6 +126,18 @@ static void R_UpdatePlaneRipple(drawspandata_t* ds)
static void R_SetSlopePlaneVectors(drawspandata_t* ds, visplane_t *pl, INT32 y, fixed_t xoff, fixed_t yoff);
static bool R_CheckMapPlane(const char* funcname, INT32 y, INT32 x1, INT32 x2)
{
if (x1 == x2)
return true;
if (x1 < x2 && x1 >= 0 && x2 < viewwidth && y >= 0 && y < viewheight)
return true;
CONS_Debug(DBG_RENDER, "%s: x1=%d, x2=%d at y=%d\n", funcname, x1, x2, y);
return false;
}
static void R_MapPlane(drawspandata_t *ds, spandrawfunc_t *spanfunc, INT32 y, INT32 x1, INT32 x2, boolean allow_parallel)
{
ZoneScoped;
@ -133,13 +145,8 @@ static void R_MapPlane(drawspandata_t *ds, spandrawfunc_t *spanfunc, INT32 y, IN
fixed_t distance = 0, span;
size_t pindex;
#ifdef RANGECHECK
if (x2 < x1 || x1 < 0 || x2 >= viewwidth || y > viewheight)
I_Error("R_MapPlane: %d, %d at %d", x1, x2, y);
#endif
if (x1 >= vid.width)
x1 = vid.width - 1;
if (!R_CheckMapPlane(__func__, y, x1, x2))
return;
angle = (ds->currentplane->viewangle + ds->currentplane->plangle)>>ANGLETOFINESHIFT;
planecos = FINECOSINE(angle);
@ -216,13 +223,9 @@ static void R_MapPlane(drawspandata_t *ds, spandrawfunc_t *spanfunc, INT32 y, IN
static void R_MapTiltedPlane(drawspandata_t *ds, void(*spanfunc)(drawspandata_t*), INT32 y, INT32 x1, INT32 x2, boolean allow_parallel)
{
ZoneScoped;
#ifdef RANGECHECK
if (x2 < x1 || x1 < 0 || x2 >= viewwidth || y >= viewheight || y < 0)
I_Error("R_MapTiltedPlane: %d, %d at %d", x1, x2, y);
#endif
if (x1 >= vid.width)
x1 = vid.width - 1;
if (!R_CheckMapPlane(__func__, y, x1, x2))
return;
// Water ripple effect
if (ds->planeripple.active)

View file

@ -1303,36 +1303,6 @@ static void ST_overlayDrawer(void)
K_DrawMidVote();
}
void ST_DrawDemoTitleEntry(void)
{
static UINT8 anim = 0;
char *nametodraw;
anim++;
anim %= 8;
nametodraw = demo.titlename;
while (V_StringWidth(nametodraw, 0) > MAXSTRINGLENGTH*8 - 8)
nametodraw++;
#define x (BASEVIDWIDTH/2 - 139)
#define y (BASEVIDHEIGHT/2)
M_DrawTextBox(x, y + 4, MAXSTRINGLENGTH, 1);
V_DrawString(x + 8, y + 12, 0, nametodraw);
if (anim < 4)
V_DrawCharacter(x + 8 + V_StringWidth(nametodraw, 0), y + 12,
'_' | 0x80, false);
M_DrawTextBox(x + 30, y - 24, 26, 1);
V_DrawString(x + 38, y - 16, 0, "Enter the name of the replay.");
M_DrawTextBox(x + 50, y + 20, 20, 1);
V_DrawThinString(x + 58, y + 28, 0, "Escape - Cancel");
V_DrawRightAlignedThinString(x + 220, y + 28, 0, "Enter - Confirm");
#undef x
#undef y
}
// MayonakaStatic: draw Midnight Channel's TV-like borders
static void ST_MayonakaStatic(void)
{
@ -1487,6 +1457,15 @@ void ST_DrawServerSplash(boolean timelimited)
}
}
void ST_DrawSaveReplayHint(INT32 flags)
{
V_DrawRightAlignedThinString(
BASEVIDWIDTH - 2, 2,
flags|V_YELLOWMAP,
demo.willsave ? "Replay will be saved. \xAB" "Change title" : "\xAB" "or " "\xAE" "Save replay"
);
}
static fixed_t ST_CalculateFadeIn(player_t *player)
{
const tic_t length = TICRATE/4;
@ -1626,26 +1605,6 @@ void ST_Drawer(void)
INT32 flags = V_SNAPTOTOP | V_SNAPTORIGHT |
(Easing_Linear(min(t, fadeLength) * FRACUNIT / fadeLength, 9, 0) << V_ALPHASHIFT);
switch (demo.savemode)
{
case DSM_NOTSAVING:
V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, "\xAB" "or " "\xAE" "Save replay");
break;
case DSM_WILLAUTOSAVE:
V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, "Replay will be saved. \xAB" "Change title");
break;
case DSM_WILLSAVE:
V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, flags|V_YELLOWMAP, "Replay will be saved.");
break;
case DSM_TITLEENTRY:
ST_DrawDemoTitleEntry();
break;
default: // Don't render anything
break;
}
ST_DrawSaveReplayHint(flags);
}
}

View file

@ -33,9 +33,6 @@ extern "C" {
// Called by main loop.
void ST_Ticker(boolean run);
// Called when naming a replay.
void ST_DrawDemoTitleEntry(void);
#ifdef HAVE_DISCORDRPC
// Called when you have Discord asks
void ST_AskToJoinEnvelope(void);
@ -79,6 +76,7 @@ extern tic_t lt_exitticker, lt_endtime;
extern tic_t lt_fade;
void ST_DrawServerSplash(boolean timelimited);
void ST_DrawSaveReplayHint(INT32 flags);
// return if player a is in the same team as player b
boolean ST_SameTeam(player_t *a, player_t *b);

View file

@ -2702,9 +2702,11 @@ void V_DrawStringScaled(
c -= font->start;
if (V_CharacterValid(font, c) == true)
{
// Remove offsets from patch
fixed_t patchxofs = SHORT (font->font[c]->leftoffset) * dupx * FRACUNIT;
cw = SHORT (font->font[c]->width) * dupx;
cxoff = (*fontspec.dim_fn)(scale, fontspec.chw, hchw, dupx, &cw);
V_DrawFixedPatch(cx + cxoff, cy + cyoff, scale,
V_DrawFixedPatch(cx + cxoff + patchxofs, cy + cyoff, scale,
flags, font->font[c], colormap);
cx += cw;
}

View file

@ -80,7 +80,6 @@ static INT32 powertype = PWRLV_DISABLED;
static INT32 intertic;
static INT32 endtic = -1;
static INT32 sorttic = -1;
static INT32 replayprompttic;
static fixed_t mqscroll = 0;
static fixed_t chkscroll = 0;
@ -1690,35 +1689,8 @@ skiptallydrawer:
}
finalcounter:
{
if ((modeattacking == ATTACKING_NONE) && (demo.recording || demo.savemode == demovars_s::DSM_SAVED) && !demo.playback)
{
switch (demo.savemode)
{
case demovars_s::DSM_NOTSAVING:
{
INT32 buttonx = BASEVIDWIDTH;
INT32 buttony = 2;
K_drawButtonAnim(buttonx - 76, buttony, 0, kp_button_b[1], replayprompttic);
V_DrawRightAlignedThinString(buttonx - 55, buttony, highlightflags, "or");
K_drawButtonAnim(buttonx - 55, buttony, 0, kp_button_x[1], replayprompttic);
V_DrawRightAlignedThinString(buttonx - 2, buttony, highlightflags, "Save replay");
break;
}
case demovars_s::DSM_SAVED:
V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, highlightflags, "Replay saved!");
break;
case demovars_s::DSM_TITLEENTRY:
ST_DrawDemoTitleEntry();
break;
default: // Don't render any text here
break;
}
}
}
if ((modeattacking == ATTACKING_NONE) && demo.recording)
ST_DrawSaveReplayHint(0);
if (Y_CanSkipIntermission())
{
@ -1754,16 +1726,7 @@ void Y_Ticker(void)
return;
if (demo.recording)
{
if (demo.savemode == demovars_s::DSM_NOTSAVING)
{
replayprompttic++;
G_CheckDemoTitleEntry();
}
if (demo.savemode == demovars_s::DSM_WILLSAVE || demo.savemode == demovars_s::DSM_WILLAUTOSAVE)
G_SaveDemo();
}
G_CheckDemoTitleEntry();
// Check for pause or menu up in single player
if (paused || P_AutoPause())