Merge branch 'monkey-input' of git.do.srb2.org:KartKrew/Kart into monkey-input

This commit is contained in:
AJ Martinez 2023-09-01 16:58:41 -07:00
commit 030d3d0656
57 changed files with 1369 additions and 511 deletions

View file

@ -338,6 +338,8 @@ static bool ACS_CountThing(mobj_t *mobj, mobjtype_t type)
return false;
}
// Unused, but it's here if you need it.
#if 0
/*--------------------------------------------------
static bool ACS_ActivatorIsLocal(ACSVM::Thread *thread)
@ -365,6 +367,7 @@ static bool ACS_ActivatorIsLocal(ACSVM::Thread *thread)
return false;
}
#endif
/*--------------------------------------------------
static UINT32 ACS_SectorThingCounter(sector_t *sec, mtag_t thingTag, bool (*filter)(mobj_t *))
@ -823,8 +826,10 @@ bool CallFunc_EndPrint(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Wo
(void)argV;
(void)argC;
if (ACS_ActivatorIsLocal(thread) == true)
HU_DoTitlecardCEcho(thread->printBuf.data());
auto& info = static_cast<Thread*>(thread)->info;
if (P_MobjWasRemoved(info.mo) == false && info.mo->player != nullptr)
HU_DoTitlecardCEcho(info.mo->player, thread->printBuf.data(), true);
thread->printBuf.drop();
return false;
@ -1203,7 +1208,7 @@ bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM
(void)argV;
(void)argC;
HU_DoTitlecardCEcho(thread->printBuf.data());
HU_DoTitlecardCEcho(nullptr, thread->printBuf.data(), true);
thread->printBuf.drop();
return false;

View file

@ -777,15 +777,17 @@ static void COM_CEcho_f(void)
size_t i;
char cechotext[1024] = "";
for (i = 1; i < COM_Argc(); i++)
strncpy(cechotext, COM_Argv(1), sizeof(cechotext)-1);
for (i = 2; i < COM_Argc(); i++)
{
strncat(cechotext, COM_Argv(i), sizeof(cechotext)-1);
strncat(cechotext, " ", sizeof(cechotext)-1);
strncat(cechotext, COM_Argv(i), sizeof(cechotext)-1);
}
cechotext[sizeof(cechotext) - 1] = '\0';
HU_DoCEcho(cechotext);
HU_DoTitlecardCEcho(NULL, cechotext, true);
}
/** Sets drawing flags for the CECHO command.

View file

@ -502,6 +502,7 @@ consvar_t cv_kicktime = Server("kicktime", "20").values(CV_Unsigned);
void MasterServer_OnChange(void);
consvar_t cv_masterserver = Server("masterserver", "https://ms.kartkrew.org/ms/api").onchange(MasterServer_OnChange);
consvar_t cv_masterserver_nagattempts = Server("masterserver_nagattempts", "5").values(CV_Unsigned);
void MasterServer_Debug_OnChange (void);
consvar_t cv_masterserver_debug = Server("masterserver_debug", "Off").on_off().onchange(MasterServer_Debug_OnChange);
@ -540,13 +541,14 @@ consvar_t cv_server_contact = Server("server_contact", "").onchange_noinit(Updat
consvar_t cv_servername = Server("servername", "Ring Racers server").onchange_noinit(Update_parameters);
void M_SortServerList(void);
consvar_t cv_serversort = Server("serversort", "Ping").dont_save().onchange(M_SortServerList).values({
{0,"Ping"},
{1,"AVG. Power Level"},
{2,"Most Players"},
{3,"Least Players"},
{4,"Max Player Slots"},
{5,"Gametype"},
consvar_t cv_serversort = Server("serversort", "Recommended").dont_save().onchange(M_SortServerList).values({
{-1, "Recommended"},
{ 0, "Ping"},
{ 1, "AVG. Power Level"},
{ 2, "Most Players"},
{ 3, "Least Players"},
{ 4, "Max Player Slots"},
{ 5, "Gametype"},
});
// show your ping on the HUD next to framerate. Defaults to warning only (shows up if your ping is > maxping)

View file

@ -1550,6 +1550,10 @@ static void SendAskInfo(INT32 node)
serverelem_t serverlist[MAXSERVERLIST];
UINT32 serverlistcount = 0;
UINT32 serverlistultimatecount = 0;
static boolean resendserverlistnode[MAXNETNODES];
static tic_t serverlistepoch;
static void SL_ClearServerList(INT32 connectedserver)
{
@ -1562,6 +1566,8 @@ static void SL_ClearServerList(INT32 connectedserver)
serverlist[i].node = 0;
}
serverlistcount = 0;
memset(resendserverlistnode, 0, sizeof resendserverlistnode);
}
static UINT32 SL_SearchServer(INT32 node)
@ -1574,32 +1580,37 @@ static UINT32 SL_SearchServer(INT32 node)
return UINT32_MAX;
}
static void SL_InsertServer(serverinfo_pak* info, SINT8 node)
static boolean SL_InsertServer(serverinfo_pak* info, SINT8 node)
{
UINT32 i;
resendserverlistnode[node] = false;
// search if not already on it
i = SL_SearchServer(node);
if (i == UINT32_MAX)
{
// not found add it
if (serverlistcount >= MAXSERVERLIST)
return; // list full
return false; // list full
if (info->_255 != 255)
return;/* old packet format */
return false;/* old packet format */
if (info->packetversion != PACKETVERSION)
return;/* old new packet format */
return false;/* old new packet format */
if (info->version != VERSION)
return; // Not same version.
return false; // Not same version.
if (info->subversion != SUBVERSION)
return; // Close, but no cigar.
return false; // Close, but no cigar.
if (strcmp(info->application, SRB2APPLICATION))
return;/* that's a different mod */
return false;/* that's a different mod */
if (info->modifiedgame != (mpmenu.room == 1))
return false;/* CORE vs MODDED! */
i = serverlistcount++;
}
@ -1609,6 +1620,8 @@ static void SL_InsertServer(serverinfo_pak* info, SINT8 node)
// resort server list
M_SortServerList();
return true;
}
void CL_QueryServerList (msg_server_t *server_list)
@ -1617,6 +1630,8 @@ void CL_QueryServerList (msg_server_t *server_list)
CL_UpdateServerList();
serverlistepoch = I_GetTime();
for (i = 0; server_list[i].header.buffer[0]; i++)
{
// Make sure MS version matches our own, to
@ -1629,19 +1644,43 @@ void CL_QueryServerList (msg_server_t *server_list)
if (node == -1)
break; // no more node free
SendAskInfo(node);
// Force close the connection so that servers can't eat
// up nodes forever if we never get a reply back from them
// (usually when they've not forwarded their ports).
//
// Don't worry, we'll get in contact with the working
// servers again when they send SERVERINFO to us later!
//
// (Note: as a side effect this probably means every
// server in the list will probably be using the same node (e.g. node 1),
// not that it matters which nodes they use when
// the connections are closed afterwards anyway)
// -- Monster Iestyn 12/11/18
Net_CloseConnection(node|FORCECLOSE);
resendserverlistnode[node] = true;
// Leave this node open. It'll be closed if the
// request times out (CL_TimeoutServerList).
}
}
serverlistultimatecount = i;
}
#define SERVERLISTRESENDRATE NEWTICRATE
void CL_TimeoutServerList(void)
{
if (netgame && serverlistultimatecount > serverlistcount)
{
const tic_t timediff = I_GetTime() - serverlistepoch;
const tic_t timetoresend = timediff % SERVERLISTRESENDRATE;
const boolean timedout = timediff > connectiontimeout;
if (timedout || (timediff > 0 && timetoresend == 0))
{
INT32 node;
for (node = 1; node < MAXNETNODES; ++node)
{
if (resendserverlistnode[node])
{
if (timedout)
Net_CloseConnection(node|FORCECLOSE);
else
SendAskInfo(node);
}
}
if (timedout)
serverlistultimatecount = serverlistcount;
}
}
}
@ -3875,22 +3914,14 @@ static void Got_AddPlayer(UINT8 **p, INT32 playernum)
CONS_Debug(DBG_NETPLAY, "addplayer: %d %d\n", node, newplayernum);
{
// Clear player before joining, lest some things get set incorrectly
CL_ClearPlayer(newplayernum);
G_DestroyParty(newplayernum);
G_AddPlayer(newplayernum);
//G_SpectatePlayerOnJoin(newplayernum); -- caused desyncs in this spot :(
playeringame[newplayernum] = true;
G_AddPlayer(newplayernum);
if (newplayernum+1 > doomcom->numslots)
doomcom->numslots = (INT16)(newplayernum+1);
}
if (newplayernum+1 > doomcom->numslots)
doomcom->numslots = (INT16)(newplayernum+1);
newplayer = &players[newplayernum];
newplayer->jointime = 0;
READSTRINGN(*p, player_names[newplayernum], MAXPLAYERNAME);
READMEM(*p, players[newplayernum].public_key, PUBKEYLENGTH);
READMEM(*p, clientpowerlevels[newplayernum], sizeof(((serverplayer_t *)0)->powerlevels));
@ -4195,6 +4226,11 @@ boolean SV_SpawnServer(void)
I_NetOpenSocket();
}
if (cv_advertise.value)
{
RegisterServer();
}
ourIP = 0;
STUN_bind(GotOurIP);
}
@ -4617,7 +4653,9 @@ static void HandleServerInfo(SINT8 node)
memcpy(servername, netbuffer->u.serverinfo.servername, MAXSERVERNAME);
CopyCaretColors(netbuffer->u.serverinfo.servername, servername, MAXSERVERNAME);
SL_InsertServer(&netbuffer->u.serverinfo, node);
// If we have cause to reject it, it's not worth observing.
if (SL_InsertServer(&netbuffer->u.serverinfo, node) == false)
serverlistultimatecount--;
}
static void PT_WillResendGamestate(void)

View file

@ -460,7 +460,7 @@ struct serverelem_t
};
extern serverelem_t serverlist[MAXSERVERLIST];
extern UINT32 serverlistcount;
extern UINT32 serverlistcount, serverlistultimatecount;
extern INT32 mapchangepending;
// Points inside doomcom
@ -595,6 +595,7 @@ void CL_ClearPlayer(INT32 playernum);
void CL_RemovePlayer(INT32 playernum, kickreason_t reason);
void CL_QueryServerList(msg_server_t *list);
void CL_UpdateServerList(void);
void CL_TimeoutServerList(void);
// Is there a game running
boolean Playing(void);

View file

@ -1193,14 +1193,13 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
&& LUA_HookTeamSwitch(&players[playernum], 0, false, false, false)) // fiiiine, lua can except it
{
P_DamageMobj(players[playernum].mo, NULL, NULL, 1, DMG_SPECTATOR);
players[playernum].playerstate = PST_REBORN;
players[playernum].pflags &= ~PF_WANTSTOJOIN;
players[playernum].spectator = true;
if (players[i].spectator)
{
HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false);
HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false);
FinalisePlaystateChange(playernum);
FinalisePlaystateChange(playernum);
}
}
}
}
@ -3452,6 +3451,22 @@ static void Command_ServerTeamChange_f(void)
SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
}
void P_SetPlayerSpectator(INT32 playernum)
{
//Make sure you're in the right gametype.
if (!G_GametypeHasTeams() && !G_GametypeHasSpectators())
return;
// Don't duplicate efforts.
if (players[playernum].spectator)
return;
players[playernum].spectator = true;
players[playernum].pflags &= ~PF_WANTSTOJOIN;
players[playernum].playerstate = PST_REBORN;
}
//todo: This and the other teamchange functions are getting too long and messy. Needs cleaning.
static void Got_Teamchange(UINT8 **cp, INT32 playernum)
{
@ -3523,55 +3538,38 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
{
CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]);
SendKick(playernum, KICK_MSG_CON_FAIL);
return;
}
//Safety first!
// (not respawning spectators here...)
wasspectator = (players[playernum].spectator == true);
if (!wasspectator && gamestate == GS_LEVEL)
{
if (players[playernum].mo)
{
P_DamageMobj(players[playernum].mo, NULL, NULL, 1,
(NetPacket.packet.newteam ? DMG_INSTAKILL : DMG_SPECTATOR));
}
//else
if (!NetPacket.packet.newteam)
{
players[playernum].playerstate = PST_REBORN;
}
}
players[playernum].pflags &= ~PF_WANTSTOJOIN;
if (!wasspectator)
{
if (gamestate == GS_LEVEL && players[playernum].mo)
{
// The following will call P_SetPlayerSpectator if successful
P_DamageMobj(players[playernum].mo, NULL, NULL, 1, DMG_SPECTATOR);
}
//...but because the above could return early under some contexts, we try again here
P_SetPlayerSpectator(playernum);
}
//Now that we've done our error checking and killed the player
//if necessary, put the player on the correct team/status.
boolean nochangeoccourred = false;
if (NetPacket.packet.newteam != 0)
{
// This serves us in both teamchange contexts.
players[playernum].pflags |= PF_WANTSTOJOIN;
}
if (G_GametypeHasTeams())
{
if (!NetPacket.packet.newteam)
{
players[playernum].ctfteam = 0;
players[playernum].spectator = true;
}
else
{
players[playernum].ctfteam = NetPacket.packet.newteam;
players[playernum].pflags |= PF_WANTSTOJOIN; //players[playernum].spectator = false;
nochangeoccourred = true;
}
}
else if (G_GametypeHasSpectators())
{
if (!NetPacket.packet.newteam)
players[playernum].spectator = true;
else
{
players[playernum].pflags |= PF_WANTSTOJOIN; //players[playernum].spectator = false;
nochangeoccourred = true;
}
// This one is, of course, specific.
players[playernum].ctfteam = NetPacket.packet.newteam;
}
if (NetPacket.packet.autobalance)
@ -3599,20 +3597,7 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
else if (NetPacket.packet.newteam == 0 && !wasspectator)
HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false); // "entered the game" text was moved to P_SpectatorJoinGame
/*if (G_GametypeHasTeams())
{
if (NetPacket.packet.newteam)
{
UINT8 i;
for (i = 0; i <= splitscreen; i++)
{
if (playernum == g_localplayers[i]) //CTF and Team Match colors.
CV_SetValue(&cv_playercolor[i], NetPacket.packet.newteam + 5); - -this calculation is totally wrong
}
}
}*/
if (gamestate != GS_LEVEL || nochangeoccourred == true)
if (gamestate != GS_LEVEL || wasspectator == true)
return;
FinalisePlaystateChange(playernum);

View file

@ -245,6 +245,7 @@ void D_SetupVote(void);
void D_ModifyClientVote(UINT8 player, SINT8 voted);
void D_PickVote(void);
void ObjectPlace_OnChange(void);
void P_SetPlayerSpectator(INT32 playernum);
boolean IsPlayerAdmin(INT32 playernum);
void SetAdminPlayer(INT32 playernum);
void ClearAdminPlayers(void);

View file

@ -5809,6 +5809,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_BATTLEUFO_BEAM",
"MT_POWERUP_AURA",
"MT_SCRIPT_THING",
};
const char *const MOBJFLAG_LIST[] = {

View file

@ -454,7 +454,7 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col
I_OsPolling();
I_UpdateNoBlit();
if (drawMenu)
if (drawMenu && rendermode != render_none)
{
#ifdef HAVE_THREADS
I_lock_mutex(&k_menu_mutex);

View file

@ -228,10 +228,7 @@ void G_ReadDemoExtraData(void)
{
if (!playeringame[p])
{
CL_ClearPlayer(p);
playeringame[p] = true;
G_AddPlayer(p);
players[p].spectator = true;
}
for (i = 0; i < MAXAVAILABILITY; i++)
@ -253,28 +250,34 @@ void G_ReadDemoExtraData(void)
switch (i) {
case DXD_PST_PLAYING:
if (players[p].bot)
if (players[p].spectator == true)
{
players[p].spectator = false;
}
else
{
players[p].pflags |= PF_WANTSTOJOIN;
if (players[p].bot)
{
players[p].spectator = false;
}
else
{
players[p].pflags |= PF_WANTSTOJOIN;
}
}
//CONS_Printf("player %s is despectating on tic %d\n", player_names[p], leveltime);
break;
case DXD_PST_SPECTATING:
players[p].pflags &= ~PF_WANTSTOJOIN; // double-fuck you
if (players[p].spectator != true)
if (players[p].spectator)
{
//CONS_Printf("player %s is spectating on tic %d\n", player_names[p], leveltime);
players[p].spectator = true;
if (players[p].mo)
P_DamageMobj(players[p].mo, NULL, NULL, 1, DMG_INSTAKILL);
else
players[p].playerstate = PST_REBORN;
players[p].pflags &= ~PF_WANTSTOJOIN;
}
else
{
if (players[p].mo)
{
P_DamageMobj(players[p].mo, NULL, NULL, 1, DMG_SPECTATOR);
}
P_SetPlayerSpectator(p);
}
break;
case DXD_PST_LEFT:
@ -3420,7 +3423,7 @@ void G_DoPlayDemo(const char *defdemoname)
if (!playeringame[displayplayers[0]] || players[displayplayers[0]].spectator)
displayplayers[0] = consoleplayer = serverplayer = p;
playeringame[p] = true;
G_AddPlayer(p);
players[p].spectator = spectator;
if (flags & DEMO_KICKSTART)

View file

@ -127,6 +127,8 @@ extern UINT8 demo_writerng;
#define DXD_COLOR 0x10 // color changed
#define DXD_FOLLOWER 0x20 // follower was changed
#define DXD_ADDPLAYER (DXD_JOINDATA|DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER)
#define DXD_WEAPONPREF 0x80 // netsynced playsim settings were changed
#define DXD_PST_PLAYING 0x01

View file

@ -1146,7 +1146,7 @@ void G_DoLoadLevelEx(boolean resetplayer, gamestate_t newstate)
else
{
// Podium: writetextmap is finished. Yay!
HU_DoTitlecardCEcho(va("Congratulations,\\%s!\\Check the console!", cv_playername[0].string));
HU_DoTitlecardCEcho(NULL, va("Congratulations,\\%s!\\Check the console!", cv_playername[0].string), true);
livestudioaudience_timer = 0;
LiveStudioAudience();
@ -1276,6 +1276,10 @@ boolean G_IsTitleCardAvailable(void)
if (gametyperules & GTR_SPECIALSTART)
return false;
// ALso.
if (K_PodiumSequence() == true)
return false;
// The title card is available.
return true;
}
@ -2827,9 +2831,6 @@ void G_DoReborn(INT32 playernum)
{
player_t *player = &players[playernum];
// Make sure objectplace is OFF when you first start the level!
OP_ResetObjectplace();
{
// respawn at the start
mobj_t *oldmo = NULL;
@ -2850,11 +2851,53 @@ void G_DoReborn(INT32 playernum)
}
}
// These are the barest esentials.
// This func probably doesn't even need to know if the player is a bot.
void G_AddPlayer(INT32 playernum)
{
player_t *p = &players[playernum];
p->playerstate = PST_REBORN;
demo_extradata[playernum] |= DXD_JOINDATA|DXD_PLAYSTATE|DXD_COLOR|DXD_NAME|DXD_SKIN|DXD_FOLLOWER; // Set everything
CL_ClearPlayer(playernum);
G_DestroyParty(playernum);
playeringame[playernum] = true;
player_t *newplayer = &players[playernum];
newplayer->playerstate = PST_REBORN;
newplayer->jointime = 0;
demo_extradata[playernum] |= DXD_ADDPLAYER;
}
void G_SpectatePlayerOnJoin(INT32 playernum)
{
// This is only ever called shortly after the above.
// That calls CL_ClearPlayer, so spectator is false by default
if (!netgame && !G_GametypeHasTeams() && !G_GametypeHasSpectators())
return;
// These are handled automatically elsewhere
if (demo.playback || players[playernum].bot)
return;
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
// Spectators are of no consequence
if (players[i].spectator)
continue;
// Prevent splitscreen hosters/joiners from only adding 1 player at a time in empty servers (this will also catch yourself)
if (!players[i].jointime)
continue;
// A ha! An established player! It's time to spectate
players[playernum].spectator = true;
break;
}
}
void G_BeginLevelExit(void)
@ -3273,7 +3316,7 @@ boolean G_GametypeHasSpectators(void)
#ifdef DEVELOP
return true;
#endif
return (netgame || (multiplayer && demo.netgame));
return (netgame || (demo.playback && demo.netgame));
}
//

View file

@ -240,6 +240,7 @@ void G_ResetView(UINT8 viewnum, INT32 playernum, boolean onlyactive);
void G_AdjustView(UINT8 viewnum, INT32 offset, boolean onlyactive);
void G_AddPlayer(INT32 playernum);
void G_SpectatePlayerOnJoin(INT32 playernum);
void G_SetExitGameFlag(void);
void G_ClearExitGameFlag(void);

View file

@ -11,7 +11,7 @@
/*
Documentation available here.
<https://ms.kartkrew.org/tools/api/2/>
<https://ms.kartkrew.org/tools/api/2.2/>
*/
#ifdef HAVE_CURL
@ -441,7 +441,12 @@ HMS_fetch_servers (msg_server_t *list, int query_id)
break;
#endif
//#define MSERVTESTALONE
#ifdef MSERVTESTALONE
strcpy(list[i].ip, "127.0.0.1"); // MS test without needing a second person to host
#else
strlcpy(list[i].ip, address, sizeof list[i].ip);
#endif
strlcpy(list[i].port, port, sizeof list[i].port);
if (contact)
@ -511,6 +516,40 @@ HMS_compare_mod_version (char *buffer, size_t buffer_size)
return ok;
}
const char *
HMS_fetch_rules (char *buffer, size_t buffer_size)
{
struct HMS_buffer *hms;
hms = HMS_connect("rules");
if (! hms)
return NULL;
boolean ok = HMS_do(hms);
if (ok)
{
char *p = strstr(hms->buffer, "\n\n");
if (p)
{
p[1] = '\0';
strlcpy(buffer, hms->buffer, buffer_size);
}
else
buffer = NULL;
}
HMS_end(hms);
if (!ok)
return NULL;
return buffer;
}
static char *
Strip_trailing_slashes (char *api)
{

View file

@ -62,6 +62,7 @@
#include "r_fps.h"
#include "d_clisrv.h"
#include "y_inter.h" // Y_PlayerStandingsDrawer
#include "g_party.h"
// coords are scaled
#define HU_INPUTX 0
@ -157,10 +158,15 @@ static tic_t cechotimer = 0;
static tic_t cechoduration = 5*TICRATE;
static INT32 cechoflags = 0;
static char tcechotext[1024]; // buffer for the titlecard text
static tic_t tcechotimer = 0; // goes up by 1 each frame this is active
static tic_t tcechoduration = 0; // Set automatically
struct tcecho_state
{
char text[1024]; // buffer for the titlecard text
tic_t start; // gametic that the message started
tic_t duration; // Set automatically
};
#define NUM_TCECHO_STATES (1 + MAXSPLITSCREENPLAYERS)
static struct tcecho_state g_tcecho[NUM_TCECHO_STATES];
static tic_t resynch_ticker = 0;
@ -283,6 +289,12 @@ void HU_Init(void)
PR ("GTFN");
REG;
PR ("4GTOL");
REG;
PR ("4GTFN");
REG;
DIG (1);
DIM (0, 10);
@ -960,13 +972,6 @@ void HU_Ticker(void)
if (cechotimer)
cechotimer--;
if (tcechotimer)
{
tcechotimer++;
if (tcechotimer > tcechoduration)
tcechotimer = 0;
}
if (gamestate != GS_LEVEL)
{
@ -1829,12 +1834,31 @@ static void HU_DrawCEcho(void)
}
}
static void HU_DrawTitlecardCEcho(void)
static tic_t HU_TitlecardCEchoElapsed(const struct tcecho_state *state)
{
if (tcechotimer)
return max(gametic, state->start) - state->start;
}
static void HU_DrawTitlecardCEcho(size_t num)
{
const struct tcecho_state *state = &g_tcecho[num];
tic_t elapsed = HU_TitlecardCEchoElapsed(state);
UINT8 viewnum = max(1, num) - 1;
boolean p4 = (num != 0 && r_splitscreen);
// If the splitscreens were somehow decreased in the
// middle of drawing this, don't draw it.
if (viewnum > r_splitscreen)
{
return;
}
if (elapsed < state->duration)
{
INT32 i = 0;
INT32 y = (BASEVIDHEIGHT/2)-16;
INT32 x = BASEVIDWIDTH/2;
INT32 y = BASEVIDHEIGHT/2;
INT32 pnumlines = 0;
INT32 timeroffset = 0;
@ -1842,11 +1866,28 @@ static void HU_DrawTitlecardCEcho(void)
char *echoptr;
char temp[1024];
for (i = 0; tcechotext[i] != '\0'; ++i)
if (tcechotext[i] == '\\')
for (i = 0; state->text[i] != '\0'; ++i)
if (state->text[i] == '\\')
pnumlines++;
y -= (pnumlines-1)*16;
if (p4)
{
if (r_splitscreen == 1) // 2P
{
y -= (1 - (viewnum * 2)) * (y / 2);
}
else // 3P / 4P
{
x -= (1 - ((viewnum % 2) * 2)) * (x / 2);
y -= (1 - ((viewnum / 2) * 2)) * (y / 2);
}
y -= 11 + ((pnumlines-1) * 9);
}
else
{
y -= 18 + ((pnumlines-1) * 16);
}
// Prevent crashing because I'm sick of this
if (y < 0)
@ -1856,13 +1897,13 @@ static void HU_DrawTitlecardCEcho(void)
return;
}
strcpy(temp, tcechotext);
strcpy(temp, state->text);
echoptr = &temp[0];
while (*echoptr != '\0')
{
INT32 w;
INT32 timer = (INT32)(tcechotimer - timeroffset);
INT32 ofs;
INT32 timer = (INT32)(elapsed - timeroffset);
if (timer <= 0)
return; // we don't care.
@ -1874,10 +1915,10 @@ static void HU_DrawTitlecardCEcho(void)
*line = '\0';
w = V_TitleCardStringWidth(echoptr);
V_DrawTitleCardString(BASEVIDWIDTH/2 -w/2, y, echoptr, 0, false, timer, TICRATE*4);
ofs = V_CenteredTitleCardStringOffset(echoptr, p4);
V_DrawTitleCardString(x - ofs, y, echoptr, 0, false, timer, TICRATE*4, p4);
y += 32;
y += p4 ? 18 : 32;
// offset the timer for the next line.
timeroffset += strlen(echoptr);
@ -2037,9 +2078,23 @@ drawontop:
if (cechotimer)
HU_DrawCEcho();
if (tcechotimer)
HU_DrawTitlecardCEcho();
const struct tcecho_state *firststate = &g_tcecho[0];
// Server messages overwrite player-specific messages
if (HU_TitlecardCEchoElapsed(firststate) < firststate->duration)
{
HU_DrawTitlecardCEcho(0);
}
else
{
size_t i;
for (i = 1; i < NUM_TCECHO_STATES; ++i)
{
HU_DrawTitlecardCEcho(i);
}
}
}
//======================================================================
@ -2576,17 +2631,41 @@ void HU_DoCEcho(const char *msg)
// No need to bother clearing the buffer or anything.
void HU_ClearTitlecardCEcho(void)
{
tcechotimer = 0;
size_t i;
for (i = 0; i < NUM_TCECHO_STATES; ++i)
{
g_tcecho[i].duration = 0;
}
}
// Similar but for titlecard CEcho and also way less convoluted because I have no clue whatever the fuck they were trying above.
void HU_DoTitlecardCEcho(const char *msg)
void HU_DoTitlecardCEcho(player_t *player, const char *msg, boolean interrupt)
{
if (player && !P_IsDisplayPlayer(player))
{
return;
}
struct tcecho_state *state = &g_tcecho[0];
if (player)
{
state = &g_tcecho[1 + G_PartyPosition(player - players)];
}
// If this message should not interrupt an existing
// message. Check if another message is already running.
if (!interrupt && HU_TitlecardCEchoElapsed(state) < state->duration)
{
return;
}
I_OutputMsg("%s\n", msg); // print to log
strncpy(tcechotext, msg, sizeof(tcechotext));
strncat(tcechotext, "\\", sizeof(tcechotext) - strlen(tcechotext) - 1);
tcechotext[sizeof(tcechotext) - 1] = '\0';
tcechotimer = 1;
tcechoduration = TICRATE*6 + strlen(tcechotext);
strncpy(state->text, msg, sizeof(state->text));
strncat(state->text, "\\", sizeof(state->text) - strlen(state->text) - 1);
state->text[sizeof(state->text) - 1] = '\0';
state->start = gametic;
state->duration = TICRATE*6 + strlen(state->text);
}

View file

@ -77,6 +77,9 @@ enum
X (GTOL),
X (GTFN),
X (GTOL4),
X (GTFN4),
X (TALLNUM),
X (NIGHTSNUM),
X (PINGNUM),
@ -163,7 +166,7 @@ void HU_SetCEchoFlags(INT32 flags);
void HU_DoCEcho(const char *msg);
// Titlecard CECHO shite
void HU_DoTitlecardCEcho(const char *msg);
void HU_DoTitlecardCEcho(player_t *player, const char *msg, boolean interrupt);
void HU_ClearTitlecardCEcho(void);
void DoSayCommand(char *message, SINT8 target, UINT8 flags, UINT8 source);

View file

@ -30312,6 +30312,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
S_NULL // raisestate
},
{ // MT_SCRIPT_THING
4096, // doomednum
S_INVISIBLE, // spawnstate
1000, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
0, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_NULL, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
16*FRACUNIT, // radius
16*FRACUNIT, // height
0, // display offset
0, // mass
0, // damage
sfx_None, // activesound
MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_SCENERY, // flags
S_NULL // raisestate
},
};
skincolor_t skincolors[MAXSKINCOLORS] = {

View file

@ -6999,6 +6999,8 @@ typedef enum mobj_type
MT_POWERUP_AURA,
MT_SCRIPT_THING,
MT_FIRSTFREESLOT,
MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
NUMMOBJTYPES

View file

@ -127,10 +127,7 @@ void K_CheckBumpers(void)
{
if (nobumpers > 0 && nobumpers >= numingame)
{
// TODO: this would make a great debug feature for release
#ifndef DEVELOP
P_DoAllPlayersExit(PF_NOCONTEST, false);
#endif
return;
}
}
@ -142,9 +139,9 @@ void K_CheckBumpers(void)
if (numingame <= 1)
{
if ((gametyperules & GTR_PRISONS) && (K_CanChangeRules(true) == true))
if ((gametyperules & GTR_PRISONS) && !battleprisons && (K_CanChangeRules(true) == true))
{
// Reset map to turn on battle capsules
// Reset map to turn on battle prisons
if (server)
D_MapChange(gamemap, gametype, encoremode, true, 0, false, false);
}

View file

@ -47,12 +47,8 @@ void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e st
{
CONS_Debug(DBG_NETPLAY, "addbot: %d\n", newplayernum);
// Clear player before joining, lest some things get set incorrectly
CL_ClearPlayer(newplayernum);
G_DestroyParty(newplayernum);
playeringame[newplayernum] = true;
G_AddPlayer(newplayernum);
if (newplayernum+1 > doomcom->numslots)
doomcom->numslots = (INT16)(newplayernum+1);

View file

@ -201,7 +201,7 @@ void K_InitGrandPrixBots(void)
}
else
{
players[i].spectator = true; // force spectate for all other players, if they happen to exist?
P_SetPlayerSpectator(i); // force spectate for all other players, if they happen to exist?
}
}
}
@ -391,6 +391,11 @@ void K_UpdateGrandPrixBots(void)
players[i].spectator = !(gametyperules & GTR_BOTS) || (grandprixinfo.eventmode != GPEVENT_NONE);
}
if (grandprixinfo.wonround == false)
{
return;
}
// Find the rival.
for (i = 0; i < MAXPLAYERS; i++)
{

View file

@ -4332,6 +4332,9 @@ static void K_drawKartFinish(boolean finish)
if (finish)
{
if (gametyperules & GTR_SPECIALSTART)
return;
timer = stplyr->karthud[khud_finish];
kptodraw = kp_racefinish;
minsplitstationary = 2;

View file

@ -3921,7 +3921,7 @@ void K_StumblePlayer(player_t *player)
P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
// Reset slope.
player->mo->pitch = player->mo->roll = 0;
P_ResetPitchRoll(player->mo);
}
boolean K_CheckStumble(player_t *player, angle_t oldPitch, angle_t oldRoll, boolean fromAir)
@ -3939,6 +3939,14 @@ boolean K_CheckStumble(player_t *player, angle_t oldPitch, angle_t oldRoll, bool
return false;
}
if (fromAir && player->airtime < STUMBLE_AIRTIME
&& player->airtime > 1) // ACHTUNG HACK, sorry. Ground-to-ground transitions sometimes have 1-tic airtime because collision blows
{
// Short airtime with no reaction window, probably a track traversal setpiece.
// Don't punish for these.
return false;
}
if ((player->mo->pitch == oldPitch)
&& (player->mo->roll == oldRoll))
{
@ -4102,6 +4110,9 @@ void K_UpdateStumbleIndicator(player_t *player)
mobj->renderflags &= ~RF_HORIZONTALFLIP;
}
if (air && player->airtime < STUMBLE_AIRTIME)
delta = 0;
steepRange = ANGLE_90 - steepVal;
delta = max(0, abs(delta) - ((signed)steepVal));
trans = ((FixedDiv(AngleFixed(delta), AngleFixed(steepRange)) * (NUMTRANSMAPS - 2)) + (FRACUNIT/2)) / FRACUNIT;
@ -4252,7 +4263,7 @@ static void K_HandleTumbleBounce(player_t *player)
player->tumbleHeight = 10;
player->pflags |= PF_TUMBLELASTBOUNCE;
player->mo->rollangle = 0; // p_user.c will stop rotating the player automatically
player->mo->pitch = player->mo->roll = 0; // Prevent Kodachrome Void infinite
P_ResetPitchRoll(player->mo); // Prevent Kodachrome Void infinite
}
}
@ -6155,8 +6166,7 @@ void K_DoPogoSpring(mobj_t *mo, fixed_t vertispeed, UINT8 sound)
mo->momz = FixedDiv(mo->momz, FixedSqrt(3*FRACUNIT));
}
mo->pitch = 0;
mo->roll = 0;
P_ResetPitchRoll(mo);
if (sound)
{
@ -8256,7 +8266,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd)
player->incontrol = 0;
player->incontrol++;
}
player->incontrol = min(player->incontrol, 5*TICRATE);
player->incontrol = max(player->incontrol, -5*TICRATE);
@ -10447,16 +10457,18 @@ static void K_KartSpindash(player_t *player)
{
if (player->pflags & PF_NOFASTFALL)
return;
// Update fastfall.
player->fastfall = player->mo->momz;
player->spindash = 0;
if (player->fastfallBase == 0)
if (player->fastfall == 0)
{
// Factors 3D momentum.
player->fastfallBase = FixedHypot(player->speed, player->mo->momz);
}
// Update fastfall.
player->fastfall = player->mo->momz;
player->spindash = 0;
P_ResetPitchRoll(player->mo);
return;
}
else if (player->fastfall != 0)
@ -10584,7 +10596,6 @@ boolean K_FastFallBounce(player_t *player)
player->mo->momz = bounce * P_MobjFlip(player->mo);
player->fastfall = 0;
player->fastfallBase = 0;
return true;
}

View file

@ -40,8 +40,10 @@ Make sure this matches the actual number of states
#define RR_PROJECTILE_FUSE (8*TICRATE)
#define STUMBLE_STEEP_VAL ANG60
#define STUMBLE_STEEP_VAL_AIR (ANG30 + ANG10)
// 2023-08-26 +ang20 to Sal's OG values to make them friendlier - Tyron
#define STUMBLE_STEEP_VAL (ANG60 + ANG20)
#define STUMBLE_STEEP_VAL_AIR (ANG30 + ANG10 + ANG20)
#define STUMBLE_AIRTIME TICRATE
#define MAXRINGVOLUME 255
#define MINRINGVOLUME 100

View file

@ -29,8 +29,6 @@
extern "C" {
#endif
#define SERVERLISTDEBUG
// flags for items in the menu
// menu handle (what we do when key is pressed
#define IT_TYPE 14 // (2+4+8)
@ -831,7 +829,6 @@ extern struct mpmenu_s {
// See M_OptSelectTick, it'll make more sense there. Sorry if this is a bit of a mess!
UINT8 room;
boolean roomforced;
tic_t ticker;
UINT8 servernum;
@ -842,6 +839,9 @@ extern struct mpmenu_s {
} mpmenu;
void M_PleaseWait(void);
void M_PopupMasterServerRules(void);
// Time Attack
void M_PrepareTimeAttack(INT32 choice);
void M_StartTimeAttack(INT32 choice);
@ -895,11 +895,6 @@ void Fetch_servers_thread (int *id);
void M_RefreshServers(INT32 choice);
void M_ServersMenu(INT32 choice);
// for debugging purposes...
#ifdef SERVERLISTDEBUG
void M_ServerListFillDebug(void);
#endif
// Options menu:
// mode descriptions for video mode menu

View file

@ -3086,6 +3086,23 @@ void M_DrawTimeAttack(void)
// NOTE: This is pretty rigid and only intended for use with the multiplayer options menu which has *3* choices.
static void M_DrawMasterServerReminder(void)
{
// Did you change the Server Browser address? Have a little reminder.
INT32 mservflags = 0;
if (CV_IsSetToDefault(&cv_masterserver))
mservflags = highlightflags;
else
mservflags = warningflags;
INT32 y = BASEVIDHEIGHT - 24;
V_DrawFadeFill(0, y-1, BASEVIDWIDTH, 10+1, 0, 31, 5);
V_DrawCenteredThinString(BASEVIDWIDTH/2, y,
mservflags, va("List via \"%s\"", cv_masterserver.string));
}
static void M_MPOptDrawer(menu_t *m, INT16 extend[3][3])
{
// This is a copypaste of the generic gamemode menu code with a few changes.
@ -3149,6 +3166,7 @@ void M_DrawMPOptSelect(void)
M_DrawEggaChannel();
M_DrawMenuTooltips();
M_MPOptDrawer(&PLAY_MP_OptSelectDef, mpmenu.modewinextend);
M_DrawMasterServerReminder();
}
// Multiplayer mode option select: HOST GAME!
@ -3400,14 +3418,79 @@ void M_DrawMPRoomSelect(void)
// Draw buttons:
if (!mpmenu.roomforced || mpmenu.room == 0)
V_DrawFixedPatch(160<<FRACBITS, 100<<FRACBITS, FRACUNIT, mpmenu.room ? (5<<V_ALPHASHIFT) : 0, butt1[(mpmenu.room) ? 1 : 0], NULL);
V_DrawFixedPatch(160<<FRACBITS, 90<<FRACBITS, FRACUNIT, mpmenu.room ? (5<<V_ALPHASHIFT) : 0, butt1[(mpmenu.room) ? 1 : 0], NULL);
if (!mpmenu.roomforced || mpmenu.room == 1)
V_DrawFixedPatch(160<<FRACBITS, 100<<FRACBITS, FRACUNIT, (!mpmenu.room) ? (5<<V_ALPHASHIFT) : 0, butt2[(!mpmenu.room) ? 1 : 0], NULL);
V_DrawFixedPatch(160<<FRACBITS, 90<<FRACBITS, FRACUNIT, (!mpmenu.room) ? (5<<V_ALPHASHIFT) : 0, butt2[(!mpmenu.room) ? 1 : 0], NULL);
V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL);
V_DrawCenteredThinString(BASEVIDWIDTH/2, 12, 0, "Select today's type of play!");
M_DrawMasterServerReminder();
}
// SERVER BROWSER
static void M_DrawServerCountAndHorizontalBar(void)
{
const char *text;
INT32 y = currentMenu->y+STRINGHEIGHT;
const char throbber[4] = {'-', '\\', '|', '/'};
UINT8 throbindex = (mpmenu.ticker/4) % 4;
switch (M_GetWaitingMode())
{
case M_WAITING_VERSION:
text = "Checking for updates...";
break;
case M_WAITING_SERVERS:
text = "Loading server list...";
break;
default:
if (serverlistultimatecount > serverlistcount)
{
text = va("%d/%d server%s found...",
serverlistcount,
serverlistultimatecount,
serverlistultimatecount == 1 ? "" : "s"
);
}
else
{
throbindex = UINT8_MAX; // No throbber!
text = va("%d server%s found",
serverlistcount,
serverlistcount == 1 ? "" : "s"
);
}
}
if (throbindex == UINT8_MAX)
{
V_DrawRightAlignedString(
BASEVIDWIDTH - currentMenu->x,
y,
highlightflags,
text
);
}
else
{
V_DrawRightAlignedString(
BASEVIDWIDTH - currentMenu->x - 12, y,
highlightflags,
text
);
V_DrawCenteredString( // Only clean way to center the throbber without exposing character width
BASEVIDWIDTH - currentMenu->x - 4, y,
highlightflags,
va("%c", throbber[throbindex])
);
}
}
void M_DrawMPServerBrowser(void)
{
patch_t *text1 = W_CachePatchName("MENUBGT1", PU_CACHE);
@ -3503,11 +3586,19 @@ void M_DrawMPServerBrowser(void)
V_DrawFill(0, 53, 320, 1, 31);
V_DrawFill(0, 55, 320, 1, 31);
V_DrawCenteredGamemodeString(160, 2, 0, 0, "Server Browser");
const char *headertext;
if (M_SecretUnlocked(SECRET_ADDONS, true))
headertext = va("%s Servers", mpmenu.room ? "Modded" : "Core");
else
headertext = "Server Browser";
V_DrawCenteredGamemodeString(160, 2, 0, 0, headertext);
// normal menu options
M_DrawGenericMenu();
// And finally, the overlay bar!
M_DrawServerCountAndHorizontalBar();
M_DrawMasterServerReminder();
}
// OPTIONS MENU

View file

@ -94,6 +94,21 @@ gp_rank_e K_PodiumGrade(void)
return podiumData.grade;
}
/*--------------------------------------------------
boolean K_PodiumHasEmerald(void)
See header file for description.
--------------------------------------------------*/
boolean K_PodiumHasEmerald(void)
{
if (K_PodiumSequence() == false)
{
return false;
}
return podiumData.rank.specialWon;
}
/*--------------------------------------------------
UINT8 K_GetPodiumPosition(player_t *player)
@ -284,6 +299,7 @@ boolean K_StartCeremony(void)
G_SetGametype(GT_RACE);
G_DoLoadLevelEx(false, GS_CEREMONY);
wipegamestate = GS_CEREMONY; // I don't know what else to do here
r_splitscreen = 0; // Only one screen for the ceremony
R_ExecuteSetViewSize();

View file

@ -69,6 +69,20 @@ boolean K_PodiumRanking(void);
gp_rank_e K_PodiumGrade(void);
/*--------------------------------------------------
boolean K_PodiumHasEmerald(void)
Returns whether the Emerald or Prize was collected.
Input Arguments:-
N/A
Return:-
true if the Emerald/Prize was collected during the GP, otherwise false.
--------------------------------------------------*/
boolean K_PodiumHasEmerald(void);
/*--------------------------------------------------
UINT8 K_GetPodiumPosition(player_t *player);

View file

@ -3515,9 +3515,12 @@ static int lib_getTimeMicros(lua_State *L)
static int lib_startTitlecardCecho(lua_State *L)
{
const char *str = luaL_checkstring(L, 1);
HU_DoTitlecardCEcho(str);
player_t *player = lua_isnil(L, 1) ? NULL : *((player_t **)luaL_checkudata(L, 1, META_PLAYER));
const char *str = luaL_checkstring(L, 2);
boolean interrupt = lua_optboolean(L, 3);
HU_DoTitlecardCEcho(player, str, interrupt);
return 1;
}

View file

@ -948,6 +948,7 @@ static int libd_drawTitleCardString(lua_State *L)
boolean rightalign = lua_optboolean(L, 5);
INT32 timer = luaL_optinteger(L, 6, 0);
INT32 threshold = luaL_optinteger(L, 7, 0);
boolean p4 = lua_optboolean(L, 8);
huddrawlist_h list;
flags &= ~V_PARAMMASK; // Don't let crashes happen.
@ -958,9 +959,9 @@ static int libd_drawTitleCardString(lua_State *L)
lua_pop(L, 1);
if (LUA_HUD_IsDrawListValid(list))
LUA_HUD_AddDrawTitleCardString(list, x, y, flags, str, rightalign, timer, threshold);
LUA_HUD_AddDrawTitleCardString(list, x, y, flags, str, rightalign, timer, threshold, p4);
else
V_DrawTitleCardString(x, y, str, flags, rightalign, timer, threshold);
V_DrawTitleCardString(x, y, str, flags, rightalign, timer, threshold, p4);
return 0;
}
@ -989,9 +990,10 @@ static int libd_drawKartString(lua_State *L)
static int libd_titleCardStringWidth(lua_State *L)
{
const char *str = luaL_checkstring(L, 1);
boolean p4 = lua_optboolean(L, 2);
HUDONLY
lua_pushinteger(L, V_TitleCardStringWidth(str));
lua_pushinteger(L, V_TitleCardStringWidth(str, p4));
return 1;
}

View file

@ -61,6 +61,7 @@ typedef struct drawitem_s {
INT32 timer;
INT32 threshold;
boolean bossmode;
boolean p4;
} drawitem_t;
// The internal structure of a drawlist.
@ -358,7 +359,8 @@ void LUA_HUD_AddDrawTitleCardString(
const char *str,
boolean bossmode,
INT32 timer,
INT32 threshold
INT32 threshold,
boolean p4
)
{
size_t i = AllocateDrawItem(list);
@ -371,6 +373,7 @@ void LUA_HUD_AddDrawTitleCardString(
item->bossmode = bossmode;
item->timer = timer;
item->threshold = threshold;
item->p4 = p4;
}
void LUA_HUD_AddDrawKartString(
@ -465,7 +468,7 @@ void LUA_HUD_DrawList(huddrawlist_h list)
V_DrawFadeScreen(item->color, item->strength);
break;
case DI_DrawTitleCardString:
V_DrawTitleCardString(item->x, item->y, itemstr, item->flags, item->bossmode, item->timer, item->threshold);
V_DrawTitleCardString(item->x, item->y, itemstr, item->flags, item->bossmode, item->timer, item->threshold, item->p4);
break;
case DI_DrawKartString:
V_DrawTimerString(item->x, item->y, item->flags, itemstr);

View file

@ -111,7 +111,8 @@ void LUA_HUD_AddDrawTitleCardString(
const char *str,
boolean bossmode,
INT32 timer,
INT32 threshold
INT32 threshold,
boolean p4
);
void LUA_HUD_AddDrawKartString(
huddrawlist_h list,

View file

@ -28,8 +28,10 @@ menuitem_t OPTIONS_VideoOGL[] =
{IT_STRING | IT_CVAR, "Texture Quality", "Texture depth. Higher values are recommended.",
NULL, {.cvar = &cv_scr_depth}, 0, 0},
/*
{IT_STRING | IT_CVAR, "Texture Filter", "Texture Filter. Nearest is recommended.",
NULL, {.cvar = &cv_glfiltermode}, 0, 0},
*/
{IT_STRING | IT_CVAR, "Anisotropic", "Lower values will improve performance at a minor quality loss.",
NULL, {.cvar = &cv_glanisotropicmode}, 0, 0},

View file

@ -4,6 +4,7 @@
#include "../k_menu.h"
#include "../m_cond.h"
#include "../s_sound.h"
#include "../mserv.h" // cv_masterserver
#if defined (TESTERS)
#define IT_STRING_CALL_NOTESTERS IT_DISABLED
@ -11,13 +12,74 @@
#define IT_STRING_CALL_NOTESTERS (IT_STRING | IT_CALL)
#endif // TESTERS
static boolean firstDismissedNagThisBoot = true;
static void M_HandleMasterServerResetChoice(INT32 ch)
{
if (ch == MA_YES)
{
CV_Set(&cv_masterserver, cv_masterserver.defaultvalue);
CV_Set(&cv_masterserver_nagattempts, cv_masterserver_nagattempts.defaultvalue);
S_StartSound(NULL, sfx_s221);
}
else
{
if (firstDismissedNagThisBoot)
{
if (cv_masterserver_nagattempts.value > 0)
{
CV_SetValue(&cv_masterserver_nagattempts, cv_masterserver_nagattempts.value - 1);
}
firstDismissedNagThisBoot = false;
}
}
}
static void M_PreMPHostInitChoice(INT32 ch)
{
M_HandleMasterServerResetChoice(ch);
M_MPHostInit(0);
}
static void M_PreMPHostInit(INT32 choice)
{
(void)choice;
if (!CV_IsSetToDefault(&cv_masterserver) && cv_masterserver_nagattempts.value > 0)
{
M_StartMessage("Server Browser Alert", M_GetText("Hey! You've changed the game's\naddress for the Server Browser.\n\nYou won't be able to host games on\nthe official Server Browser.\n\nUnless you're from the future, this\nprobably isn't what you want.\n"), &M_PreMPHostInitChoice, MM_YESNO, "Fix and continue", "I changed the URL intentionally");
return;
}
M_MPHostInit(0);
}
static void M_PreMPRoomSelectInitChoice(INT32 ch)
{
M_HandleMasterServerResetChoice(ch);
M_MPRoomSelectInit(0);
}
static void M_PreMPRoomSelectInit(INT32 choice)
{
(void)choice;
if (!CV_IsSetToDefault(&cv_masterserver) && cv_masterserver_nagattempts.value > 0)
{
M_StartMessage("Server Browser Alert", M_GetText("Hey! You've changed the game's\naddress for the Server Browser.\n\nYou won't be able to see games from\nthe official Server Browser.\n\nUnless you're from the future, this\nprobably isn't what you want.\n"), &M_PreMPRoomSelectInitChoice, MM_YESNO, "Fix and continue", "I changed the URL intentionally");
return;
}
M_MPRoomSelectInit(0);
}
menuitem_t PLAY_MP_OptSelect[] =
{
{IT_STRING_CALL_NOTESTERS, "Host Game", "Start your own online game!",
NULL, {.routine = M_MPHostInit}, 0, 0},
NULL, {.routine = M_PreMPHostInit}, 0, 0},
{IT_STRING_CALL_NOTESTERS, "Server Browser", "Search for game servers to play in.",
NULL, {.routine = M_MPRoomSelectInit}, 0, 0},
NULL, {.routine = M_PreMPRoomSelectInit}, 0, 0},
{IT_STRING | IT_CALL, "Join by IP", "Join an online game by its IP address.",
NULL, {.routine = M_MPJoinIPInit}, 0, 0},

View file

@ -3,6 +3,8 @@
#include "../k_menu.h"
#include "../s_sound.h"
#include "../z_zone.h"
#include "../mserv.h"
// MULTIPLAYER HOST SCREEN -- see mhost_e
menuitem_t PLAY_MP_Host[] =
@ -43,13 +45,41 @@ menu_t PLAY_MP_HostDef = {
NULL
};
void M_PopupMasterServerRules(void)
{
#ifdef MASTERSERVER
if (cv_advertise.value && (serverrunning || currentMenu == &PLAY_MP_HostDef))
{
char *rules = GetMasterServerRules();
if (rules != NULL)
{
M_StartMessage("Server List Rules", rules, NULL, MM_NOTHING, NULL, NULL);
Z_Free(rules);
}
}
#endif
}
void M_MPHostInit(INT32 choice)
{
(void)choice;
mpmenu.modewinextend[0][0] = 1;
M_SetupNextMenu(&PLAY_MP_HostDef, true);
itemOn = mhost_go;
Get_rules();
// There's one downside to doing it this way:
// if you turn advertise on via the console,
// then access this menu for the first time,
// no rules will pop up because they haven't
// arrived yet.
M_PopupMasterServerRules();
// HOWEVER, this menu popup isn't for people
// who know how to use the Developer Console.
// People who CAN do that should already know
// what kind of service they're connecting to.
// (it'll still appear in the logs later, too!)
}
void M_HandleHostMenuGametype(INT32 choice)

View file

@ -6,6 +6,7 @@
#include "../i_system.h" // I_OsPolling
#include "../i_video.h" // I_UpdateNoBlit
#include "../m_misc.h" // NUMLOGIP
#include "../f_finale.h" // g_wipeskiprender
menuitem_t PLAY_MP_JoinIP[] =
{
@ -56,6 +57,21 @@ void M_MPJoinIPInit(INT32 choice)
M_SetupNextMenu(&PLAY_MP_JoinIPDef, true);
}
void M_PleaseWait(void)
{
if (rendermode == render_none)
return;
g_wipeskiprender = true;
M_DrawTextBox(56, BASEVIDHEIGHT/2-12, 24, 2);
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "PLEASE WAIT...");
I_OsPolling();
I_UpdateNoBlit();
if (rendermode == render_soft)
I_FinishUpdate(); // page flip or blit buffer
}
// Attempts to join a given IP from the menu.
void M_JoinIP(const char *ipa)
{
@ -67,13 +83,7 @@ void M_JoinIP(const char *ipa)
COM_BufAddText(va("connect \"%s\"\n", ipa));
// A little "please wait" message.
M_DrawTextBox(56, BASEVIDHEIGHT/2-12, 24, 2);
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Connecting to server...");
I_OsPolling();
I_UpdateNoBlit();
if (rendermode == render_soft)
I_FinishUpdate(); // page flip or blit buffer
M_PleaseWait();
}
boolean M_JoinIPInputs(INT32 ch)

View file

@ -42,8 +42,7 @@ void M_MPRoomSelect(INT32 choice)
M_ServersMenu(0);
M_SetMenuDelay(pid);
}
else if (mpmenu.roomforced == false
&& menucmd[pid].dpad_lr != 0)
else if (menucmd[pid].dpad_lr != 0)
{
mpmenu.room ^= 1;
S_StartSound(NULL, sfx_s3k5b);
@ -59,12 +58,28 @@ void M_MPRoomSelectTick(void)
void M_MPRoomSelectInit(INT32 choice)
{
(void)choice;
if (modifiedgame)
{
M_StartMessage("Server Browser & Add-Ons", M_GetText("You have add-ons loaded.\nYou won't be able to join netgames!\n\nTo play online, restart the game\nand don't load any addons.\n\n\"Dr. Robotnik's Ring Racers\" will\nautomatically add everything\nyou need when you join.\n"), NULL, MM_NOTHING, NULL, NULL);
return;
}
// The following behaviour is affected by modifiedgame despite the above restriction.
// It's a sanity check were that to be removed, wheither by us or by a modified client.
// "wheither"? That typo rules, I'm keeping that ~toast 280823
mpmenu.room = (modifiedgame == true) ? 1 : 0;
mpmenu.roomforced = ((modifiedgame == true) || (!M_SecretUnlocked(SECRET_ADDONS, true)));
mpmenu.ticker = 0;
mpmenu.servernum = 0;
mpmenu.scrolln = 0;
mpmenu.slide = 0;
if ((modifiedgame == true) || (M_SecretUnlocked(SECRET_ADDONS, true) == false))
{
M_ServersMenu(0);
return;
}
M_SetupNextMenu(&PLAY_MP_RoomSelectDef, false);
}

View file

@ -3,11 +3,14 @@
#include "../k_menu.h"
#include "../v_video.h"
#include "../i_system.h" // I_OsPolling
#include "../i_video.h" // I_UpdateNoBlit
#include "../s_sound.h"
//#define SERVERLISTDEBUG
#ifdef SERVERLISTDEBUG
#include "../m_random.h"
void M_ServerListFillDebug(void);
#endif
menuitem_t PLAY_MP_ServerBrowser[] =
@ -16,8 +19,8 @@ menuitem_t PLAY_MP_ServerBrowser[] =
{IT_STRING | IT_CVAR, "SORT BY", NULL, // tooltip MUST be null.
NULL, {.cvar = &cv_serversort}, 0, 0},
{IT_STRING, "REFRESH", NULL,
NULL, {NULL}, 0, 0},
{IT_STRING | IT_CALL, "REFRESH", NULL,
NULL, {.routine = &M_RefreshServers}, 0, 0},
{IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0},
};
@ -42,28 +45,6 @@ menu_t PLAY_MP_ServerBrowserDef = {
// for server fetch threads...
M_waiting_mode_t m_waiting_mode = M_NOT_WAITING;
// depending on mpmenu.room, either allows only unmodded servers or modded ones. Remove others from the list.
// we do this by iterating backwards.
static void M_CleanServerList(void)
{
UINT8 i = serverlistcount;
while (i)
{
if (serverlist[i].info.modifiedgame != mpmenu.room)
{
// move everything after this index 1 slot down...
if (i != serverlistcount)
memcpy(&serverlist[i], &serverlist[i+1], sizeof(serverelem_t)*(serverlistcount-i));
serverlistcount--;
}
i--;
}
}
void
M_SetWaitingMode (int mode)
{
@ -180,31 +161,19 @@ void M_RefreshServers(INT32 choice)
{
(void)choice;
// Display a little "please wait" message.
M_DrawTextBox(52, BASEVIDHEIGHT/2-10, 25, 3);
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, "Searching for servers...");
V_DrawCenteredString(BASEVIDWIDTH/2, (BASEVIDHEIGHT/2)+12, 0, "Please wait.");
I_OsPolling();
I_UpdateNoBlit();
if (rendermode == render_soft)
I_FinishUpdate(); // page flip or blit buffer
CL_UpdateServerList();
#ifdef SERVERLISTDEBUG
M_ServerListFillDebug();
#else /*SERVERLISTDEBUG*/
#ifdef MASTERSERVER
#ifdef HAVE_THREADS
Spawn_masterserver_thread("fetch-servers", Fetch_servers_thread);
#else/*HAVE_THREADS*/
Fetch_servers_thread(NULL);
#endif/*HAVE_THREADS*/
#else/*MASTERSERVER*/
CL_UpdateServerList();
#endif/*MASTERSERVER*/
#ifdef SERVERLISTDEBUG
M_ServerListFillDebug();
#endif
M_CleanServerList();
M_SortServerList();
#endif /*SERVERLISTDEBUG*/
}
#ifdef UPDATE_ALERT
@ -253,13 +222,21 @@ void M_ServersMenu(INT32 choice)
// modified game check: no longer handled
// we don't request a restart unless the filelist differs
CL_UpdateServerList();
mpmenu.ticker = 0;
mpmenu.servernum = 0;
mpmenu.scrolln = 0;
mpmenu.slide = 0;
PLAY_MP_ServerBrowserDef.prevMenu = currentMenu;
M_SetupNextMenu(&PLAY_MP_ServerBrowserDef, false);
itemOn = 0;
#ifdef SERVERLISTDEBUG
M_ServerListFillDebug();
#else /*SERVERLISTDEBUG*/
#if defined (MASTERSERVER) && defined (HAVE_THREADS)
I_lock_mutex(&ms_QueryId_mutex);
{
@ -289,12 +266,7 @@ void M_ServersMenu(INT32 choice)
M_RefreshServers(0);
#endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/
#ifdef SERVERLISTDEBUG
M_ServerListFillDebug();
#endif
M_CleanServerList();
M_SortServerList();
#endif /*SERVERLISTDEBUG*/
}
#ifdef SERVERLISTDEBUG
@ -304,29 +276,31 @@ void M_ServerListFillDebug(void)
{
UINT8 i = 0;
serverlistcount = 10;
serverlistcount = 40;
memset(serverlist, 0, sizeof(serverlist)); // zero out the array for convenience...
for (i = 0; i < serverlistcount; i++)
{
// We don't really care about the server node for this, let's just fill in the info so that we have a visual...
serverlist[i].info.numberofplayer = min(i, 8);
serverlist[i].info.maxplayer = 8;
serverlist[i].info.maxplayer = M_RandomRange(8, 16);
UINT8 val = i % 16;
serverlist[i].info.numberofplayer = min(val, serverlist[i].info.maxplayer);
serverlist[i].info.avgpwrlv = P_RandomRange(PR_UNDEFINED, 500, 1500);
serverlist[i].info.time = P_RandomRange(PR_UNDEFINED, 1, 8); // ping
serverlist[i].info.avgpwrlv = M_RandomRange(500, 1500);
serverlist[i].info.time = M_RandomRange(1, 8); // ping
strcpy(serverlist[i].info.servername, va("Serv %d", i+1));
strcpy(serverlist[i].info.gametypename, i & 1 ? "Race" : "Battle");
P_RandomRange(PR_UNDEFINED, 0, 5); // change results...
serverlist[i].info.kartvars = P_RandomRange(PR_UNDEFINED, 0, 3) & SV_SPEEDMASK;
serverlist[i].info.kartvars = M_RandomRange(0, 3) & SV_SPEEDMASK;
serverlist[i].info.modifiedgame = P_RandomRange(PR_UNDEFINED, 0, 1);
serverlist[i].info.modifiedgame = M_RandomRange(0, 1);
CONS_Printf("Serv %d | %d...\n", i, serverlist[i].info.modifiedgame);
}
M_SortServerList();
}
#endif // SERVERLISTDEBUG
@ -339,7 +313,7 @@ static int ServerListEntryComparator_##key(const void *entry1, const void *entry
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \
if (sa->info.key != sb->info.key) \
return sa->info.key - sb->info.key; \
return strcmp(sa->info.servername, sb->info.servername); \
return sa->info.time - sb->info.time; \
}
// This does descending instead of ascending.
@ -349,15 +323,22 @@ static int ServerListEntryComparator_##key##_reverse(const void *entry1, const v
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \
if (sb->info.key != sa->info.key) \
return sb->info.key - sa->info.key; \
return strcmp(sb->info.servername, sa->info.servername); \
return sa->info.time - sb->info.time; \
}
SERVER_LIST_ENTRY_COMPARATOR(time)
//SERVER_LIST_ENTRY_COMPARATOR(time) -- done seperately due to the usual tiebreaker being time
SERVER_LIST_ENTRY_COMPARATOR(numberofplayer)
SERVER_LIST_ENTRY_COMPARATOR_REVERSE(numberofplayer)
SERVER_LIST_ENTRY_COMPARATOR_REVERSE(maxplayer)
SERVER_LIST_ENTRY_COMPARATOR(avgpwrlv)
static int ServerListEntryComparator_time(const void *entry1, const void *entry2)
{
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2;
if (sa->info.time != sb->info.time)
return sa->info.time - sb->info.time;
return strcmp(sa->info.servername, sb->info.servername);
}
static int ServerListEntryComparator_gametypename(const void *entry1, const void *entry2)
{
@ -365,13 +346,51 @@ static int ServerListEntryComparator_gametypename(const void *entry1, const void
int c;
if (( c = strcasecmp(sa->info.gametypename, sb->info.gametypename) ))
return c;
return strcmp(sa->info.servername, sb->info.servername); \
return sa->info.time - sb->info.time;
}
static int ServerListEntryComparator_recommended(const void *entry1, const void *entry2)
{
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2;
INT32 saseedval = sa->info.numberofplayer;
INT32 sbseedval = sb->info.numberofplayer;
// Tyron wrote the following on 25072022:
// "sort should be two parts
// top part of the list is "all non-empty servers sorted by reverse playercount", with servers above a certain reported ping marked as bad connection or whatever
// bottom part of the list is all empty servers sorted by ping"
// toast is implementing on 27082023, over a year later, because
// "fixing server join flow" is saner to do near the end
{
const UINT8 SERVER_EMPTY = 1;
// The intent with this nudge is to show you
// good games you could get a memorable Duel in,
// with the possibility to really katamari into
// something more substantial.
// By comparison, empty games are not nearly as
// fun to get going, so let's lower their SEO.
if (!saseedval)
saseedval = MAXPLAYERS + SERVER_EMPTY;
if (!sbseedval)
sbseedval = MAXPLAYERS + SERVER_EMPTY;
}
if (saseedval != sbseedval)
return saseedval - sbseedval;
return sa->info.time - sb->info.time;
}
void M_SortServerList(void)
{
switch(cv_serversort.value)
{
case -1:
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_recommended);
break;
case 0: // Ping.
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_time);
break;
@ -397,89 +416,115 @@ void M_SortServerList(void)
// Server browser inputs & ticker
void M_MPServerBrowserTick(void)
{
mpmenu.ticker++;
mpmenu.slide /= 2;
#if defined (MASTERSERVER) && defined (HAVE_THREADS)
I_lock_mutex(&ms_ServerList_mutex);
{
if (ms_ServerList)
{
CL_QueryServerList(ms_ServerList);
free(ms_ServerList);
ms_ServerList = NULL;
}
}
I_unlock_mutex(ms_ServerList_mutex);
#endif
CL_TimeoutServerList();
}
// Input handler for server browser.
boolean M_ServerBrowserInputs(INT32 ch)
{
UINT8 pid = 0;
UINT8 maxscroll = serverlistcount-(SERVERSPERPAGE/2);
INT16 maxscroll = serverlistcount - (SERVERSPERPAGE/2) - 2; // Why? Because
if (maxscroll < 0)
maxscroll = 0;
const INT16 serverbrowserOn = (currentMenu->numitems - 1);
(void) ch;
if (!itemOn && menucmd[pid].dpad_ud < 0)
{
M_PrevOpt(); // go to itemOn 2
if (serverlistcount)
{
UINT8 prevscroll = mpmenu.scrolln;
// Return the MS listing to the bottom.
INT32 prevscroll = mpmenu.scrolln;
mpmenu.servernum = serverlistcount;
mpmenu.servernum = serverlistcount-1;
mpmenu.scrolln = maxscroll;
mpmenu.slide = SERVERSPACE * (prevscroll - mpmenu.scrolln);
mpmenu.slide = SERVERSPACE * (prevscroll - (INT32)mpmenu.scrolln);
}
else
{
itemOn = 1; // Sike! If there are no servers, go to refresh instead.
M_PrevOpt(); // Double apply
}
return true; // overwrite behaviour.
}
else if (itemOn == 2) // server browser itself...
else if (itemOn == (serverbrowserOn - 1) && menucmd[pid].dpad_ud > 0 && !serverlistcount)
{
// we have to manually do that here.
if (M_MenuBackPressed(pid))
M_NextOpt(); // Double apply
}
else if (itemOn == serverbrowserOn) // server browser itself...
{
#ifndef SERVERLISTDEBUG
if (M_MenuConfirmPressed(pid))
{
M_GoBack(0);
M_SetMenuDelay(pid);
COM_BufAddText(va("connect node %d\n", serverlist[mpmenu.servernum].node));
M_PleaseWait();
return true;
}
#endif
else if (menucmd[pid].dpad_ud > 0) // down
if (menucmd[pid].dpad_ud > 0) // down
{
if (mpmenu.servernum >= serverlistcount-1)
{
UINT8 prevscroll = mpmenu.scrolln;
mpmenu.servernum = 0;
mpmenu.scrolln = 0;
mpmenu.slide = SERVERSPACE * (prevscroll - mpmenu.scrolln);
M_NextOpt(); // Go back to the top of the real menu.
}
else
if ((UINT32)(mpmenu.servernum+1) < serverlistcount)
{
// Listing scroll down
mpmenu.servernum++;
if (mpmenu.scrolln < maxscroll && mpmenu.servernum > SERVERSPERPAGE/2)
{
mpmenu.scrolln++;
mpmenu.slide += SERVERSPACE;
}
}
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
return true;
}
// Return the MS listing to the top.
INT32 prevscroll = mpmenu.scrolln;
mpmenu.servernum = 0;
mpmenu.scrolln = 0;
mpmenu.slide = SERVERSPACE * (prevscroll - (INT32)mpmenu.scrolln);
}
else if (menucmd[pid].dpad_ud < 0)
{
if (!mpmenu.servernum)
if (mpmenu.servernum)
{
M_PrevOpt();
}
else
{
if (mpmenu.servernum <= serverlistcount-(SERVERSPERPAGE/2) && mpmenu.scrolln)
// Listing scroll up
if (mpmenu.servernum <= (INT16)maxscroll && mpmenu.scrolln)
{
mpmenu.scrolln--;
mpmenu.slide -= SERVERSPACE;
}
mpmenu.servernum--;
}
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
S_StartSound(NULL, sfx_s3k5b);
M_SetMenuDelay(pid);
return true;
}
}
return true; // Overwrite behaviour.
}
return false; // use normal behaviour.
}

View file

@ -31,19 +31,20 @@ static inline size_t M_StringHeight(const char *string)
void M_StartMessage(const char *header, const char *string, void (*routine)(INT32), menumessagetype_t itemtype, const char *confirmstr, const char *defaultstr)
{
const UINT8 pid = 0;
static char *message = NULL;
Z_Free(message);
DEBFILE(string);
message = V_ScaledWordWrap(
BASEVIDWIDTH << FRACBITS,
char *message = V_ScaledWordWrap(
(BASEVIDWIDTH - 8) << FRACBITS,
FRACUNIT, FRACUNIT, FRACUNIT,
0,
HU_FONT,
string
);
strncpy(menumessage.message, string, MAXMENUMESSAGE);
strncpy(menumessage.message, message, MAXMENUMESSAGE);
Z_Free(message);
menumessage.header = header;
menumessage.flags = itemtype;
menumessage.routine = routine;
@ -84,7 +85,7 @@ void M_StartMessage(const char *header, const char *string, void (*routine)(INT3
// oogh my god this was replaced in 2023
menumessage.x = (8 * MAXSTRINGLENGTH) - 1;
menumessage.y = M_StringHeight(message);
menumessage.y = M_StringHeight(menumessage.message);
M_SetMenuDelay(pid); // Set menu delay to avoid setting off any of the handlers.
}

View file

@ -17,6 +17,7 @@
#include "doomstat.h"
#include "doomdef.h"
#include "console.h" // con_startup
#include "command.h"
#include "i_threads.h"
#include "mserv.h"
@ -40,6 +41,8 @@ static boolean MSUpdateAgain;
static time_t MSLastPing;
static char *MSRules;
#ifdef HAVE_THREADS
static I_mutex MSMutex;
static I_cond MSCond;
@ -157,6 +160,43 @@ static void Command_Listserv_f(void)
}
}
static boolean firstmsrules = false;
static void
Get_masterserver_rules (boolean checkfirst)
{
char rules[256];
if (checkfirst)
{
boolean MSRulesExist;
Lock_state();
MSRulesExist = (MSRules != NULL);
Unlock_state();
if (MSRulesExist)
return;
}
if (HMS_fetch_rules(rules, sizeof rules))
{
Lock_state();
Z_Free(MSRules);
MSRules = Z_StrDup(rules);
if (MSRegistered == true)
{
CONS_Printf("\n");
CONS_Alert(CONS_NOTICE, "%s\n", rules);
}
firstmsrules = true;
Unlock_state();
}
}
static void
Finish_registration (void)
{
@ -175,6 +215,17 @@ Finish_registration (void)
}
Unlock_state();
char *rules = GetMasterServerRules();
if (rules == NULL)
{
Get_masterserver_rules(true);
}
else
{
CONS_Printf("\n");
CONS_Alert(CONS_NOTICE, "%s\n", rules);
}
if (registered)
CONS_Printf("Master server registration successful.\n");
}
@ -257,6 +308,15 @@ Finish_unlist (void)
}
}
static void
Finish_masterserver_change (char *api)
{
HMS_set_api(api);
if (!con_startup)
Get_masterserver_rules(false);
}
#ifdef HAVE_THREADS
static int *
Server_id (void)
@ -350,7 +410,14 @@ Change_masterserver_thread (char *api)
}
Unlock_state();
HMS_set_api(api);
Finish_masterserver_change(api);
}
static void
Get_masterserver_rules_thread (void)
{
// THIS FUNC has its own lock check in it
Get_masterserver_rules(true);
}
#endif/*HAVE_THREADS*/
@ -397,6 +464,17 @@ void UnregisterServer(void)
#endif/*MASTERSERVER*/
}
char *GetMasterServerRules(void)
{
char *rules;
Lock_state();
rules = MSRules ? Z_StrDup(MSRules) : NULL;
Unlock_state();
return rules;
}
static boolean
Online (void)
{
@ -447,7 +525,21 @@ Set_api (const char *api)
strdup(api)
);
#else
HMS_set_api(strdup(api));
Finish_masterserver_change(strdup(api));
#endif
}
void
Get_rules (void)
{
#ifdef HAVE_THREADS
I_spawn_thread(
"get-masterserver-rules",
(I_thread_fn)Get_masterserver_rules_thread,
NULL
);
#else
Get_masterserver_rules(true);
#endif
}
@ -521,6 +613,8 @@ void Advertise_OnChange(void)
#ifdef HAVE_DISCORDRPC
DRPC_UpdatePresence();
#endif
M_PopupMasterServerRules();
}
#ifdef DEVELOP

View file

@ -57,6 +57,7 @@ struct msg_ban_t
// ================================ GLOBALS ===============================
extern consvar_t cv_masterserver, cv_servername;
extern consvar_t cv_masterserver_nagattempts;
extern consvar_t cv_server_contact;
extern consvar_t cv_masterserver_update_rate;
extern consvar_t cv_masterserver_timeout;
@ -77,6 +78,8 @@ extern I_mutex ms_ServerList_mutex;
void RegisterServer(void);
void UnregisterServer(void);
void Get_rules(void);
void MasterClient_Ticker(void);
msg_server_t *GetShortServersList(int id);
@ -84,6 +87,8 @@ msg_server_t *GetShortServersList(int id);
char *GetMODVersion(int id);
#endif
char *GetMasterServerRules(void);
void AddMServCommands(void);
/* HTTP */
@ -94,6 +99,7 @@ int HMS_update (void);
void HMS_list_servers (void);
msg_server_t * HMS_fetch_servers (msg_server_t *list, int id);
int HMS_compare_mod_version (char *buffer, size_t size_of_buffer);
const char * HMS_fetch_rules (char *buffer, size_t size_of_buffer);
#ifdef __cplusplus
} // extern "C"

View file

@ -161,6 +161,7 @@ static void DashRingLaunch(player_t *player, mobj_t *ring)
player->dashRingPushTics = DASHRING_PUSH_TICS;
player->mo->rollangle = 0;
P_ResetPitchRoll(player->mo);
player->flashing = 0;
player->fastfall = 0;
K_TumbleInterrupt(player);

View file

@ -26,6 +26,7 @@
#include "../r_skins.h"
#include "../k_hitlag.h"
#include "../acs/interface.h"
#include "../hu_stuff.h"
#define UFO_BASE_SPEED (42 * FRACUNIT) // UFO's slowest speed.
#define UFO_SPEEDUP (FRACUNIT >> 1) // Acceleration
@ -453,6 +454,8 @@ static void UFOMove(mobj_t *ufo)
// Disable player
P_DoAllPlayersExit(PF_NOCONTEST, false);
HU_DoTitlecardCEcho(NULL, "TOO LATE...", false);
}
if (pathfindsuccess == true)

View file

@ -1556,7 +1556,11 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
P_PlayDeathSound(target);
}
if (K_Cooperative())
// Prisons Free Play: don't eliminate P1 for
// spectating. Because in Free Play, this player
// can enter the game again, and these flags would
// make them intangible.
if (K_Cooperative() && !target->player->spectator)
{
target->player->pflags |= PF_ELIMINATED;
@ -2052,30 +2056,34 @@ static boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *sou
static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 type)
{
if (player->respawn.state != RESPAWNST_NONE)
if (type == DMG_SPECTATOR && (G_GametypeHasTeams() || G_GametypeHasSpectators()))
{
K_DoInstashield(player);
return false;
P_SetPlayerSpectator(player-players);
}
if (!player->exiting && (specialstageinfo.valid == true || modeattacking & ATTACKING_SPB))
else
{
// TODO: this would make a great debug feature for release
#ifdef DEVELOP
if (type != DMG_SPECTATOR)
if (player->respawn.state != RESPAWNST_NONE)
{
K_DoInstashield(player);
return false;
}
if (player->exiting)
{
player->mo->destscale = 1;
player->mo->flags |= MF_NOCLIPTHING;
return false;
}
if (specialstageinfo.valid == true)
{
HU_DoTitlecardCEcho(player, "FALL OUT!", false);
P_DoPlayerExit(player, PF_NOCONTEST);
}
else if (modeattacking & ATTACKING_SPB)
{
P_DoPlayerExit(player, PF_NOCONTEST);
}
#else
P_DoPlayerExit(player, PF_NOCONTEST);
#endif
}
if (player->exiting)
{
player->mo->destscale = 1;
player->mo->flags |= MF_NOCLIPTHING;
return false;
}
switch (type)
@ -2578,7 +2586,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
player->driftboost = player->strongdriftboost = 0;
player->gateBoost = 0;
player->fastfall = 0;
player->fastfallBase = 0;
player->ringboost = 0;
player->glanceDir = 0;
player->pflags &= ~PF_GAINAX;

View file

@ -569,6 +569,7 @@ void P_ExplodeMissile(mobj_t *mo);
void P_CheckGravity(mobj_t *mo, boolean affect);
void P_SetPitchRollFromSlope(mobj_t *mo, pslope_t *slope);
void P_SetPitchRoll(mobj_t *mo, angle_t pitch, angle_t yaw);
void P_ResetPitchRoll(mobj_t *mo);
fixed_t P_ScaleFromMap(fixed_t n, fixed_t scale);
fixed_t P_GetMobjHead(const mobj_t *);
fixed_t P_GetMobjFeet(const mobj_t *);

View file

@ -1333,7 +1333,7 @@ void P_SetPitchRollFromSlope(mobj_t *mo, pslope_t *slope)
}
else
{
mo->pitch = mo->roll = 0;
P_ResetPitchRoll(mo);
}
}
@ -1348,6 +1348,15 @@ void P_SetPitchRoll(mobj_t *mo, angle_t pitch, angle_t yaw)
mo->pitch = FixedMul(pitch, FINECOSINE (yaw));
}
//
// P_ResetPitchRoll
//
void P_ResetPitchRoll(mobj_t *mo)
{
mo->pitch = 0;
mo->roll = 0;
}
#define STOPSPEED (FRACUNIT)
//
@ -6716,6 +6725,49 @@ static void P_MobjSceneryThink(mobj_t *mobj)
case MT_ARKARROW:
Obj_ArkArrowThink(mobj);
break;
case MT_SCRIPT_THING:
{
if (mobj->thing_args[2] != 0)
{
// turned off
break;
}
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] == false)
{
continue;
}
player_t *player = &players[i];
if (P_MobjWasRemoved(player->mo) == true)
{
continue;
}
fixed_t dist = R_PointToDist2(
mobj->x, mobj->y,
player->mo->x, player->mo->y
);
if (dist < mobj->thing_args[0] * FRACUNIT)
{
P_ActivateThingSpecial(mobj, player->mo);
if (mobj->thing_args[1] == 0)
{
P_RemoveMobj(mobj);
return;
}
break;
}
}
break;
}
case MT_VWREF:
case MT_VWREB:
{
@ -7191,7 +7243,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
}
case MT_FLOATINGITEM:
{
mobj->pitch = mobj->roll = 0;
P_ResetPitchRoll(mobj);
if (mobj->flags & MF_NOCLIPTHING)
{
if (P_CheckDeathPitCollide(mobj))
@ -11778,61 +11830,24 @@ void P_RespawnSpecials(void)
//
void P_SpawnPlayer(INT32 playernum)
{
UINT8 i, pcount = 0; // MAXPLAYERS if exiting
UINT8 i;
player_t *p = &players[playernum];
mobj_t *mobj;
boolean justjoined = (p->jointime <= 1);
if (p->playerstate == PST_REBORN)
{
G_PlayerReborn(playernum, (p->jointime <= 1));
G_PlayerReborn(playernum, justjoined);
}
for (i = 0; i < MAXPLAYERS; i++)
{
if (i == playernum)
continue;
if (!playeringame[i] || players[i].spectator)
continue;
if (players[i].exiting)
{
pcount = MAXPLAYERS;
break;
}
if (players[i].jointime <= 1) // Prevent splitscreen hosters/joiners from only adding 1 player at a time in empty servers
continue;
pcount++;
}
if (justjoined)
G_SpectatePlayerOnJoin(playernum);
// spawn as spectator determination
if (multiplayer && demo.playback)
{
; // Don't mess with spectator values since the demo setup handles them already.
}
else if (p->bot)
{
if (K_PodiumSequence() == true)
; // This is too late to correct spectator status. Whatever state we're in at this point, our (dog) bed is made.
else if (!(gametyperules & GTR_BOTS)
|| (grandprixinfo.gp == true
&& grandprixinfo.eventmode != GPEVENT_NONE))
{
// Bots aren't supposed to be here.
p->spectator = true;
}
else
{
// No point in a spectating bot!
p->spectator = false;
}
}
else if (netgame && p->jointime <= 1 && pcount)
{
p->spectator = true;
}
else if (multiplayer && !netgame)
if (G_GametypeHasTeams())
{
// If you're in a team game and you don't have a team assigned yet...
if (G_GametypeHasTeams() && p->ctfteam == 0)
if (!p->spectator && p->ctfteam == 0)
{
changeteam_union NetPacket;
UINT16 usvalue;
@ -11842,9 +11857,6 @@ void P_SpawnPlayer(INT32 playernum)
// yes even in splitscreen mode
p->spectator = true;
if (playernum&1) p->skincolor = skincolor_redteam;
else p->skincolor = skincolor_blueteam;
// but immediately send a team change packet.
NetPacket.packet.playernum = playernum;
NetPacket.packet.verification = true;
@ -11853,22 +11865,6 @@ void P_SpawnPlayer(INT32 playernum)
usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
}
else // Otherwise, never spectator.
{
// TODO: this would make a great debug feature for release
#ifndef DEVELOP
p->spectator = false;
#endif
}
}
if (G_GametypeHasTeams())
{
// Fix stupid non spectator spectators.
if (!p->spectator && !p->ctfteam)
{
p->spectator = true;
}
// Fix team colors.
// This code isn't being done right somewhere else. Oh well.
@ -11967,8 +11963,7 @@ void P_SpawnPlayer(INT32 playernum)
S_StartSound(body, sfx_s1af);
}
// I'm not refactoring the loop at the top of this file.
pcount = 0;
UINT8 pcount = 0;
for (i = 0; i < MAXPLAYERS; ++i)
{

View file

@ -7835,6 +7835,9 @@ static void P_InitPlayers(void)
UINT8 i;
INT32 skin = -1;
// Make sure objectplace is OFF when you first start the level!
OP_ResetObjectplace();
// Are we forcing a character?
if (gametype == GT_TUTORIAL)
{
@ -7871,14 +7874,7 @@ static void P_InitPlayers(void)
// followercolor can be left alone for hopefully obvious reasons
}
if (!(gametyperules & GTR_CIRCUIT) && K_PodiumSequence() == false)
{
G_DoReborn(i);
}
else // gametype is race
{
G_SpawnPlayer(i);
}
G_SpawnPlayer(i);
players[i].xtralife = 0; // extra lives do not ever carry over from the previous round
}
@ -8245,8 +8241,17 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
}
}
if (gametyperules & GTR_SPECIALSTART)
if (K_PodiumHasEmerald())
{
// Special Stage out
if (ranspecialwipe != 2)
S_StartSound(NULL, sfx_s3k6a);
levelfadecol = 0;
wipetype = wipe_encore_towhite;
}
else if (gametyperules & GTR_SPECIALSTART)
{
// Special Stage in
if (ranspecialwipe != 2)
S_StartSound(NULL, sfx_s3kaf);
levelfadecol = 0;
@ -8254,6 +8259,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
}
else if (skipstats == 1)
{
// MapWarp
if (ranspecialwipe != 2)
S_StartSound(NULL, sfx_s3k73);
levelfadecol = 0;
@ -8261,11 +8267,13 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
}
else if (encoremode)
{
// Encore
levelfadecol = 0;
wipetype = wipe_encore_towhite;
}
else
{
// Default
levelfadecol = 31;
}
@ -8547,7 +8555,7 @@ void P_PostLoadLevel(void)
K_InitGrandPrixBots();
grandprixinfo.initalize = false;
}
else if (grandprixinfo.wonround == true)
else
{
K_UpdateGrandPrixBots();
grandprixinfo.wonround = false;

View file

@ -1987,7 +1987,15 @@ static void K_HandleLapIncrement(player_t *player)
player->starpostnum = 0;
if (P_IsDisplayPlayer(player))
if (gametyperules & GTR_SPECIALSTART)
{
if (player->laps > numlaps)
{
// Warp out
S_StartSound(NULL, sfx_s3kb3);
}
}
else if (P_IsDisplayPlayer(player))
{
if (numlaps > 1 && player->laps == numlaps) // final lap
S_StartSound(NULL, sfx_s3k68);
@ -2053,6 +2061,8 @@ static void K_HandleLapIncrement(player_t *player)
if (!(specialstageinfo.ufo == NULL || P_MobjWasRemoved(specialstageinfo.ufo)))
{
applyflags |= PF_NOCONTEST;
HU_DoTitlecardCEcho(player, "EMPTY\\HANDED?", false);
}
}

View file

@ -484,12 +484,10 @@ void P_ResetPlayer(player_t *player)
player->trickpanel = 0;
player->glanceDir = 0;
player->fastfall = 0;
player->fastfallBase = 0;
if (player->mo != NULL && P_MobjWasRemoved(player->mo) == false)
{
player->mo->pitch = 0;
player->mo->roll = 0;
P_ResetPitchRoll(player->mo);
}
}
@ -1296,7 +1294,7 @@ void P_DoPlayerExit(player_t *player, pflags_t flags)
{
K_UpdateAllPlayerPositions();
if (cv_kartvoices.value)
if (cv_kartvoices.value && !(gametyperules & GTR_SPECIALSTART))
{
if (P_IsDisplayPlayer(player))
{
@ -1326,7 +1324,9 @@ void P_DoPlayerExit(player_t *player, pflags_t flags)
G_BeginLevelExit();
}
if (grandprixinfo.gp == true && player->bot == false && losing == false)
if (grandprixinfo.gp == true
&& (roundqueue.size && roundqueue.position < roundqueue.size) // Not the last map of GP
&& player->bot == false && losing == false)
{
const UINT8 lifethreshold = 20;
@ -1407,6 +1407,11 @@ void P_DoAllPlayersExit(pflags_t flags, boolean trygivelife)
// You've already finished, don't play again
;
}
else if (gametyperules & GTR_SPECIALSTART)
{
// Warp out
S_StartSound(NULL, sfx_s3kb3);
}
else if (musiccountdown == 0)
{
// Other people finish
@ -3504,7 +3509,7 @@ boolean P_SpectatorJoinGame(player_t *player)
// Pressing fire assigns you to a team that needs players if allowed.
// Partial code reproduction from p_tick.c autobalance code.
// a surprise tool that will help us later...
if (G_GametypeHasTeams())
if (G_GametypeHasTeams() && player->ctfteam == 0)
{
INT32 z, numplayersred = 0, numplayersblue = 0;

View file

@ -200,7 +200,13 @@ CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1];
#define TRANSTAB_AMTMUL10 (255.0f / 10.0f)
static void R_GenerateBlendTables(void);
struct GenerateBlendTables_State
{
RGBA_t *masterPalette;
RGBA_t *gammaCorrectedPalette;
};
static void R_GenerateBlendTables_Core(struct GenerateBlendTables_State *state);
static void R_GenerateTranslucencyTable(UINT8 *table, RGBA_t* sourcepal, int style, UINT8 blendamt);
static void R_AllocateBlendTables(void)
@ -221,8 +227,13 @@ static void R_AllocateBlendTables(void)
#ifdef HAVE_THREADS
static void R_GenerateBlendTables_Thread(void *userdata)
{
(void)userdata;
R_GenerateBlendTables();
struct GenerateBlendTables_State *state = userdata;
R_GenerateBlendTables_Core(state);
free(state->masterPalette);
free(state->gammaCorrectedPalette);
free(state);
}
#endif
@ -247,16 +258,29 @@ void R_InitTranslucencyTables(void)
W_ReadLump(W_GetNumForName("TRANS90"), transtables+0x80000);
R_AllocateBlendTables();
#ifdef HAVE_THREADS
I_spawn_thread("blend-tables",
R_GenerateBlendTables_Thread, NULL);
#else
R_GenerateBlendTables();
#endif
}
void R_GenerateBlendTables(void)
{
#ifdef HAVE_THREADS
// Allocate copies for the worker thread since the originals can be freed in the main thread.
struct GenerateBlendTables_State *state = malloc(sizeof *state);
size_t palsize = 256 * sizeof(RGBA_t);
state->masterPalette = memcpy(malloc(palsize), pMasterPalette, palsize);
state->gammaCorrectedPalette = memcpy(malloc(palsize), pGammaCorrectedPalette, palsize);
I_spawn_thread("blend-tables",
R_GenerateBlendTables_Thread, state);
#else
struct GenerateBlendTables_State state = {pMasterPalette, pGammaCorrectedPalette};
R_GenerateBlendTables_Core(&state);
#endif
}
static void R_GenerateBlendTables_Core(struct GenerateBlendTables_State *state)
{
INT32 i;
@ -265,12 +289,12 @@ void R_GenerateBlendTables(void)
const size_t offs = (0x10000 * i);
const UINT8 alpha = (TRANSTAB_AMTMUL10 * ((float)(10-i)));
R_GenerateTranslucencyTable(blendtables[blendtab_add] + offs, pGammaCorrectedPalette, AST_ADD, alpha);
R_GenerateTranslucencyTable(blendtables[blendtab_subtract] + offs, pMasterPalette, AST_SUBTRACT, alpha); // intentionally uses pMasterPalette
R_GenerateTranslucencyTable(blendtables[blendtab_reversesubtract] + offs, pGammaCorrectedPalette, AST_REVERSESUBTRACT, alpha);
R_GenerateTranslucencyTable(blendtables[blendtab_add] + offs, state->gammaCorrectedPalette, AST_ADD, alpha);
R_GenerateTranslucencyTable(blendtables[blendtab_subtract] + offs, state->masterPalette, AST_SUBTRACT, alpha); // intentionally uses pMasterPalette
R_GenerateTranslucencyTable(blendtables[blendtab_reversesubtract] + offs, state->gammaCorrectedPalette, AST_REVERSESUBTRACT, alpha);
}
R_GenerateTranslucencyTable(blendtables[blendtab_modulate], pGammaCorrectedPalette, AST_MODULATE, 0);
R_GenerateTranslucencyTable(blendtables[blendtab_modulate], state->gammaCorrectedPalette, AST_MODULATE, 0);
}
void R_GenerateTranslucencyTable(UINT8 *table, RGBA_t* sourcepal, int style, UINT8 blendamt)

View file

@ -157,6 +157,7 @@ enum
extern UINT8 *blendtables[NUMBLENDMAPS];
void R_InitTranslucencyTables(void);
void R_GenerateBlendTables(void);
UINT8 *R_GetTranslucencyTable(INT32 alphalevel);
UINT8 *R_GetBlendTable(int style, INT32 alphalevel);

View file

@ -1466,7 +1466,7 @@ static void copy_to_skin (struct ParseSpriteInfoState *parser, INT32 skinnum)
}
}
static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean all)
static boolean R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean all)
{
char *sprinfoToken;
size_t sprinfoTokenLength;
@ -1487,12 +1487,15 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite frame should be");
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where sprite frame should be\n");
return false;
}
sprinfoTokenLength = strlen(sprinfoToken);
if (sprinfoTokenLength != 1)
{
I_Error("Error parsing SPRTINFO lump: Invalid frame \"%s\"",sprinfoToken);
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Invalid frame \"%s\"\n",sprinfoToken);
Z_Free(sprinfoToken);
return false;
}
else
frameChar = sprinfoToken;
@ -1504,7 +1507,10 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean
// Left Curly Brace
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
I_Error("Error parsing SPRTINFO lump: Missing sprite info");
{
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Missing sprite info\n");
return false;
}
else
{
if (strcmp(sprinfoToken,"{")==0)
@ -1513,7 +1519,8 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info should be");
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where sprite info should be\n");
return false;
}
while (strcmp(sprinfoToken,"}")!=0)
{
@ -1550,7 +1557,8 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace should be");
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace should be\n");
return false;
}
}
}
@ -1574,7 +1582,11 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean
INT32 i;
if (!parser->foundskins)
I_Error("Error parsing SPRTINFO lump: No skins specified in this sprite2 definition");
{
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: No skins specified in this sprite2 definition\n");
Z_Free(bright);
return false;
}
if (parser->foundskins < 0)
{
@ -1607,6 +1619,8 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean
M_Memcpy(&spriteinfo[parser->sprnum], parser->info, sizeof(spriteinfo_t));
}
}
return true;
}
//
@ -1614,7 +1628,7 @@ static void R_ParseSpriteInfoFrame(struct ParseSpriteInfoState *parser, boolean
//
// Parse a SPRTINFO lump.
//
static void R_ParseSpriteInfo(boolean spr2)
static boolean R_ParseSpriteInfo(boolean spr2)
{
char *sprinfoToken;
size_t sprinfoTokenLength;
@ -1634,7 +1648,8 @@ static void R_ParseSpriteInfo(boolean spr2)
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite name should be");
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where sprite name should be\n");
return false;
}
if (!strcmp(sprinfoToken, "*")) // All sprites
@ -1646,7 +1661,9 @@ static void R_ParseSpriteInfo(boolean spr2)
sprinfoTokenLength = strlen(sprinfoToken);
if (sprinfoTokenLength != 4)
{
I_Error("Error parsing SPRTINFO lump: Sprite name \"%s\" isn't 4 characters long",sprinfoToken);
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Sprite name \"%s\" isn't 4 characters long\n",sprinfoToken);
Z_Free(sprinfoToken);
return false;
}
else
{
@ -1666,7 +1683,10 @@ static void R_ParseSpriteInfo(boolean spr2)
for (i = 0; i <= NUMSPRITES; i++)
{
if (i == NUMSPRITES)
I_Error("Error parsing SPRTINFO lump: Unknown sprite name \"%s\"", newSpriteName);
{
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unknown sprite name \"%s\"\n", newSpriteName);
return false;
}
if (!memcmp(newSpriteName,sprnames[i],4))
{
parser.sprnum = i;
@ -1679,7 +1699,10 @@ static void R_ParseSpriteInfo(boolean spr2)
for (i = 0; i <= NUMPLAYERSPRITES; i++)
{
if (i == NUMPLAYERSPRITES)
I_Error("Error parsing SPRTINFO lump: Unknown sprite2 name \"%s\"", newSpriteName);
{
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unknown sprite2 name \"%s\"\n", newSpriteName);
return false;
}
if (!memcmp(newSpriteName,spr2names[i],4))
{
parser.spr2num = i;
@ -1695,22 +1718,33 @@ static void R_ParseSpriteInfo(boolean spr2)
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where open curly brace for sprite \"%s\" should be",newSpriteName);
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where open curly brace for sprite \"%s\" should be\n",newSpriteName);
Z_Free(parser.info);
return false;
}
boolean error = false;
if (strcmp(sprinfoToken,"{")==0)
{
Z_Free(sprinfoToken);
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where definition for sprite \"%s\" should be",newSpriteName);
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where definition for sprite \"%s\" should be\n",newSpriteName);
Z_Free(parser.info);
return false;
}
while (strcmp(sprinfoToken,"}")!=0)
{
if (stricmp(sprinfoToken, "SKIN")==0)
{
if (!spr2)
I_Error("Error parsing SPRTINFO lump: \"SKIN\" token found outside of a sprite2 definition");
{
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: \"SKIN\" token found outside of a sprite2 definition\n");
error = true;
break;
}
Z_Free(sprinfoToken);
R_ParseSpriteInfoSkin(&parser);
@ -1718,31 +1752,46 @@ static void R_ParseSpriteInfo(boolean spr2)
else if (stricmp(sprinfoToken, "FRAME")==0)
{
Z_Free(sprinfoToken);
R_ParseSpriteInfoFrame(&parser, PARSER_FRAME);
if (!R_ParseSpriteInfoFrame(&parser, PARSER_FRAME))
{
error = true;
break;
}
}
else if (stricmp(sprinfoToken, "DEFAULT")==0)
{
Z_Free(sprinfoToken);
R_ParseSpriteInfoFrame(&parser, PARSER_DEFAULT);
if (!R_ParseSpriteInfoFrame(&parser, PARSER_DEFAULT))
{
error = true;
break;
}
}
else
{
I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\" in sprite %s",sprinfoToken,newSpriteName);
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unknown keyword \"%s\" in sprite %s\n",sprinfoToken,newSpriteName);
error = true;
break;
}
sprinfoToken = M_GetToken(NULL);
if (sprinfoToken == NULL)
{
I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace for sprite \"%s\" should be",newSpriteName);
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace for sprite \"%s\" should be\n",newSpriteName);
error = true;
break;
}
}
}
else
{
I_Error("Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"",newSpriteName,sprinfoToken);
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"\n",newSpriteName,sprinfoToken);
error = true;
}
Z_Free(sprinfoToken);
Z_Free(parser.info);
return !error;
}
//
@ -1777,13 +1826,20 @@ void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum)
sprinfoToken = M_GetToken(sprinfoText);
while (sprinfoToken != NULL)
{
boolean error = true;
if (!stricmp(sprinfoToken, "SPRITE"))
R_ParseSpriteInfo(false);
error = !R_ParseSpriteInfo(false);
else if (!stricmp(sprinfoToken, "SPRITE2"))
R_ParseSpriteInfo(true);
error = !R_ParseSpriteInfo(true);
else
I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\"", sprinfoToken);
CONS_Alert(CONS_WARNING, "Error parsing SPRTINFO lump: Unknown keyword \"%s\"\n", sprinfoToken);
Z_Free(sprinfoToken);
if (error)
break;
sprinfoToken = M_GetToken(NULL);
}
Z_Free((void *)sprinfoText);

View file

@ -943,10 +943,10 @@ void ST_drawTitleCard(void)
// Everything else...
if (bossinfo.enemyname)
{
bx = V_TitleCardStringWidth(bossinfo.enemyname);
bx = V_TitleCardStringWidth(bossinfo.enemyname, false);
// Name.
V_DrawTitleCardString((BASEVIDWIDTH - bx)/2, 75, bossinfo.enemyname, 0, true, bossinfo.titleshow, lt_exitticker);
V_DrawTitleCardString((BASEVIDWIDTH - bx)/2, 75, bossinfo.enemyname, 0, true, bossinfo.titleshow, lt_exitticker, false);
// Under-bar.
{
@ -1067,10 +1067,10 @@ void ST_drawTitleCard(void)
V_DrawFixedPatch(eggx2*FRACUNIT, eggy2*FRACUNIT, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, tccirclebottom, NULL);
// Now the level name.
V_DrawTitleCardString((actnum) ? 265 : 280, 60, lvlttl, V_SNAPTORIGHT, false, lt_ticker, TTANIMENDTHRESHOLD);
V_DrawTitleCardString((actnum) ? 265 : 280, 60, lvlttl, V_SNAPTORIGHT, false, lt_ticker, TTANIMENDTHRESHOLD, false);
if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE))
V_DrawTitleCardString((actnum) ? 265 : 280, 60+32, strlen(zonttl) ? zonttl : "ZONE", V_SNAPTORIGHT, false, lt_ticker - strlen(lvlttl), TTANIMENDTHRESHOLD);
V_DrawTitleCardString((actnum) ? 265 : 280, 60+32, strlen(zonttl) ? zonttl : "ZONE", V_SNAPTORIGHT, false, lt_ticker - strlen(lvlttl), TTANIMENDTHRESHOLD, false);
// the act has a similar graphic animation, but we'll handle it here since it's only like 2 graphics lmfao.
if (actnum && actnum < 10)

View file

@ -1852,50 +1852,155 @@ void V_DrawChatCharacter(INT32 x, INT32 y, INT32 c, boolean lowercase, UINT8 *co
);
}
// V_TitleCardStringWidth
// Get the string's width using the titlecard font.
INT32 V_TitleCardStringWidth(const char *str)
template <bool Centered>
static INT32 Internal_TitleCardStringOffset(const char *str, boolean p4)
{
int bg_font = GTOL_FONT;
int fg_font = GTFN_FONT;
if (p4)
{
bg_font = GTOL4_FONT;
fg_font = GTFN4_FONT;
}
INT32 xoffs = 0;
const char *ch = str;
char c;
patch_t *pp;
for (;;ch++)
// Returns true if it reached the end, false if interrupted.
auto scan = [&](auto keep_going)
{
if (!*ch)
break;
if (*ch == '\n')
for (;;ch++)
{
xoffs = 0;
continue;
if (*ch == '\n')
{
xoffs = 0;
return false;
}
if (!keep_going(*ch))
{
break;
}
c = *ch;
c = toupper(c);
c -= LT_FONTSTART;
// check if character exists, if not, it's a space.
if (c < 0 || c >= LT_FONTSIZE || !fontv[bg_font].font[(INT32)c])
{
xoffs += p4 ? 5 : 10;
continue;
}
pp = fontv[fg_font].font[(INT32)c];
xoffs += pp->width - (p4 ? 3 : 5);
}
c = *ch;
c = toupper(c);
c -= LT_FONTSTART;
return true;
};
// check if character exists, if not, it's a space.
if (c < 0 || c >= LT_FONTSIZE || !fontv[GTOL_FONT].font[(INT32)c])
do
{
// For the sake of centering, don't count spaces or
// punctuation at each end of a line.
// TODO: This should ideally be more sophisticated:
// - Check patch width directly for monospace or
// punctuation that isn't necessarily thin.
// - Apply to all centered string drawing.
if constexpr (Centered)
{
xoffs += 10;
continue;
// Count leading fluff
if (!scan([](int c) { return c && !isalnum(c); }))
{
continue;
}
if (!*ch)
{
// ALL fluff, so center it normally.
break;
}
// xoffs gets halved later, which centers the
// string. If we don't want leading fluff to push
// everything to the right, its full width needs
// to be subtracted, so it's doubled here to
// cancel out the division.
xoffs *= 2;
INT32 trim = -1;
bool reached_end = scan(
[&trim, &xoffs](int c)
{
if (isalnum(c))
{
trim = -1;
}
else if (trim < 0)
{
trim = xoffs;
}
return c;
}
);
// Discount trailing fluff
if (reached_end && trim >= 0)
{
xoffs = trim;
}
}
else
{
scan([](int c) { return c; });
}
pp = fontv[GTFN_FONT].font[(INT32)c];
xoffs += pp->width-5;
}
while (*(ch++));
return xoffs;
if constexpr (Centered)
{
return xoffs / 2;
}
else
{
return xoffs;
}
}
// V_TitleCardStringWidth
// Get the string's width using the titlecard font.
INT32 V_TitleCardStringWidth(const char *str, boolean p4)
{
return Internal_TitleCardStringOffset<false>(str, p4);
}
// V_CenteredTitleCardStringOffset
// Subtract this offset from an X coordinate to center the string around that point.
INT32 V_CenteredTitleCardStringOffset(const char *str, boolean p4)
{
return Internal_TitleCardStringOffset<true>(str, p4);
}
// V_DrawTitleCardScreen.
// see v_video.h's prototype for more information.
//
void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold)
void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold, boolean p4)
{
int bg_font = GTOL_FONT;
int fg_font = GTFN_FONT;
if (p4)
{
bg_font = GTOL4_FONT;
fg_font = GTFN4_FONT;
}
INT32 xoffs = 0;
INT32 yoffs = 0;
@ -1916,7 +2021,7 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole
x -= 2; // Account for patch width...
if (flags & V_SNAPTORIGHT)
x -= V_TitleCardStringWidth(str);
x -= V_TitleCardStringWidth(str, p4);
for (;;ch++, i++)
@ -1933,7 +2038,7 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole
if (*ch == '\n')
{
xoffs = x;
yoffs += 32;
yoffs += p4 ? 18 : 32;
continue;
}
@ -1944,14 +2049,14 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole
c -= LT_FONTSTART;
// check if character exists, if not, it's a space.
if (c < 0 || c >= LT_FONTSIZE || !fontv[GTFN_FONT].font[(INT32)c])
if (c < 0 || c >= LT_FONTSIZE || !fontv[fg_font].font[(INT32)c])
{
xoffs += 10;
xoffs += p4 ? 5 : 10;
continue;
}
ol = fontv[GTOL_FONT].font[(INT32)c];
pp = fontv[GTFN_FONT].font[(INT32)c];
ol = fontv[bg_font].font[(INT32)c];
pp = fontv[fg_font].font[(INT32)c];
if (bossmode)
{
@ -2004,7 +2109,7 @@ void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boole
V_DrawStretchyFixedPatch((x + xoffs)*FRACUNIT + offs, (y+yoffs)*FRACUNIT, abs(scalex), FRACUNIT, flags|flipflag, pp, NULL);
}
xoffs += pp->width -5;
xoffs += pp->width - (p4 ? 3 : 5);
}
}

View file

@ -347,10 +347,13 @@ void V_DrawRightAlignedThinStringAtFixed(fixed_t x, fixed_t y, INT32 option, con
// threshold: when the letters start disappearing (leave to 0 to disable) (both are INT32 in case you supply negative values...)
// NOTE: This function ignores most conventional string flags (V_RETURN8, V_FORCEUPPERCASE ...)
// NOTE: This font only works with uppercase letters.
void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold);
void V_DrawTitleCardString(INT32 x, INT32 y, const char *str, INT32 flags, boolean bossmode, INT32 timer, INT32 threshold, boolean p4);
// returns thr width of a string drawn using the above function.
INT32 V_TitleCardStringWidth(const char *str);
INT32 V_TitleCardStringWidth(const char *str, boolean p4);
// offset that can be subtracted to center align.
INT32 V_CenteredTitleCardStringOffset(const char *str, boolean p4);
// Draw tall nums, used for menu, HUD, intermission
void V_DrawTallNum(INT32 x, INT32 y, INT32 flags, INT32 num);

View file

@ -1,4 +1,4 @@
#define SRB2VERSION "2.0"/* this must be the first line, for cmake !! */
#define SRB2VERSION "1.0"/* this must be the first line, for cmake !! */
// The Modification ID; must be obtained from a Master Server Admin ( https://mb.srb2.org/showgroups.php ).
// DO NOT try to set this otherwise, or your modification will be unplayable through the Master Server.

View file

@ -1462,7 +1462,7 @@ void Y_IntermissionDrawer(void)
}
else
{
headerwidth = V_TitleCardStringWidth(data.headerstring);
headerwidth = V_TitleCardStringWidth(data.headerstring, false);
headerx = (BASEVIDWIDTH - headerwidth)/2;
headery = 17;
@ -1490,7 +1490,7 @@ void Y_IntermissionDrawer(void)
V_DrawMappedPatch(x + roundx, 39, 0, roundpatch, NULL);
}
V_DrawTitleCardString(x + headerx, headery, data.headerstring, 0, false, 0, 0);
V_DrawTitleCardString(x + headerx, headery, data.headerstring, 0, false, 0, 0, false);
}
// Returns early if there's no players to draw