Compare commits

...
27 Commits
Author SHA1 Message Date
Alug b59a54c61b change version string lul 2025-05-21 10:37:20 +02:00
Alug 42011f23b4 fix type mismatch for netUpdateFudge 2025-05-20 21:59:29 +02:00
Alug 7e6d31627e make savestate bit bigger lel 2025-05-20 21:58:11 +02:00
Alug a39ab80a77 no need to save those 2025-05-20 21:53:34 +02:00
Alug 18adf0ab32 oop 2025-05-20 21:36:57 +02:00
Alug 3ebfb353e1 more stuff 2025-05-20 21:34:22 +02:00
Alug 7769b0e0eb Revert "store last ticcmd in a buffer"
This reverts commit 9474142aca.
2025-05-20 21:18:48 +02:00
Alug b549aa6f53 this breaks horribly 2025-05-20 20:45:13 +02:00
Alug bd4103a2ed fix sound? 2025-05-20 20:42:32 +02:00
Alug 2e07f0be92 delet unused cvars 2025-05-20 20:41:26 +02:00
Alug 87eddd70af Merge remote-tracking branch 'origin/netshit3' into netshit3 2025-05-20 20:34:56 +02:00
Alug 7034bc6fb3 fix some hashtable warnings 2025-05-20 20:34:48 +02:00
indev 28cfdf8c23 Fix hud not ticking even outside of netplus simulations 2025-05-20 21:28:00 +03:00
Alug 00f24b8841 add P_SPMAngle hack 2025-05-20 20:25:08 +02:00
indev 8b706be4ba Port con_muted 2025-05-20 21:18:46 +03:00
Alug eb5d271dc4 this too 2025-05-20 20:20:37 +02:00
Alug fe107331fe delet this 2025-05-20 20:17:40 +02:00
Alug 51bac6418f clean up p_saveg 2025-05-20 20:13:25 +02:00
Alug 598344fb62 ok just dont touch the lua archive ig? 2025-05-20 19:05:43 +02:00
Alug c1690cc3a0 more shit 2025-05-20 19:02:40 +02:00
Alug d6ea4109ec make this actually do smth lel 2025-05-20 18:59:32 +02:00
Alug 9f634046e5 add hashtable shit 2025-05-20 18:53:45 +02:00
Alug 50af7630e9 smol fixes ig 2025-05-20 18:30:19 +02:00
Alug b30ff1db0e make this "work" 2025-05-20 18:00:56 +02:00
Alug a18662ebce we dont need to allocate a savebuffer, we just use the savestate one 2025-05-20 17:32:02 +02:00
Alug 3cd6fe4e34 tic_frequency 2025-05-20 16:14:50 +02:00
Alug 0b9ac05fbe netplus more like netminus
this is such a mess holy fuck
2025-05-20 15:52:05 +02:00
30 changed files with 1849 additions and 155 deletions

No files matched your search

+2
View File
@@ -49,7 +49,9 @@ OBJS:=$(OBJS) \
$(OBJDIR)/lua_skinlib.o \
$(OBJDIR)/lua_thinkerlib.o \
$(OBJDIR)/lua_maplib.o \
$(OBJDIR)/hashtable.o \
$(OBJDIR)/lua_hudlib.o \
$(OBJDIR)/lua_hudlib_drawlist.o \
$(OBJDIR)/lua_udatalib.o \
$(OBJDIR)/lua_glib.o
+2
View File
@@ -400,7 +400,9 @@ OBJS:=$(OBJS) \
$(OBJDIR)/lua_skinlib.o \
$(OBJDIR)/lua_thinkerlib.o \
$(OBJDIR)/lua_maplib.o \
$(OBJDIR)/hashtable.o \
$(OBJDIR)/lua_hudlib.o \
$(OBJDIR)/lua_hudlib_drawlist.o \
$(OBJDIR)/lua_udatalib.o \
$(OBJDIR)/lua_glib.o
+6 -1
View File
@@ -59,6 +59,7 @@ static boolean con_started = false; // console has been initialised
boolean con_startup = false; // true at game startup, screen need refreshing
static boolean con_forcepic = true; // at startup toggle console translucency when first off
boolean con_recalc; // set true when screen size has changed
boolean con_muted = false;
static tic_t con_tick; // console ticker for anim or blinking prompt cursor
// con_scrollup should use time (currenttime - lasttime)..
@@ -1102,7 +1103,7 @@ static void CON_Print(char *msg)
INT32 controlchars = 0; // for color changing
char color = '\x80'; // keep color across lines
if (msg == NULL)
if (msg == NULL || con_muted)
return;
if (*msg == '\3') // chat text, makes ding sound
@@ -1241,6 +1242,10 @@ void CONS_Printf(const char *fmt, ...)
// echo console prints to log file
DEBFILE(txt);
//also mute the console in terminal/command line AND file, except for DEBFILE above
if (con_muted)
return;
if (con_started)
CON_Print(txt);
+3
View File
@@ -25,6 +25,9 @@ extern I_mutex con_mutex;
// set true when screen size has changed, to adapt console
extern boolean con_recalc;
// no messages will be printed while the console is muted
extern boolean con_muted;
extern boolean con_startup;
// top clip value for view render: do not draw part of view hidden by console
+706 -36
View File
@@ -11,6 +11,8 @@
/// \brief SRB2 Network game communication and protocol, all OS independent parts.
#include "doomdef.h"
#include <limits.h>
#include <time.h>
#ifdef __GNUC__
#include <unistd.h> //for unlink
@@ -187,6 +189,25 @@ boolean acceptnewnode = true;
boolean serverisfull = false; //lets us be aware if the server was full after we check files, but before downloading, so we can ask if the user still wants to download or not
tic_t firstconnectattempttime = 0;
// Net simulation stuff
savestate_t gameStateBuffer[MAXLOCALSAVESTATES]; //"gametic" Saved States
boolean gameStateBufferIsValid[MAXLOCALSAVESTATES]; //If states are valid to load?
ticcmd_t gameTicBuffer[MAXSIMULATIONS][MAXPLAYERS]; //recordings of players during simulations
ticcmd_t localTicBuffer[MAXSIMULATIONS]; //own recordings during simulations
boolean issimulation = false;
uint8_t simInaccuracy = 1; //depends on cv.siminaccuracy. if estimatedRTT < cv.siminaccuracy, then it equals estimatedRTT
steadyplayer_t steadyplayers[MAXPLAYERS];
void EncodeTiccmdTime(ticcmd_t *ticcmd, tic_t time);
tic_t DecodeTiccmdTime(const ticcmd_t *ticcmd);
static boolean CompareTiccmd(const ticcmd_t *a, const ticcmd_t *b);
static void RunSimulations(void);
// Net simulation stuff END
// engine
// Must be a power of two
@@ -218,7 +239,6 @@ static textcmdbuf_t *textcmdbuf[MAXSPLITSCREENPLAYERS] = {NULL};
ticcmd_t netcmds[BACKUPTICS][MAXPLAYERS];
static textcmdtic_t *textcmds[TEXTCMD_HASH_SIZE] = {NULL};
static ticcmd_t playercmds[MAXPLAYERS];
consvar_t cv_showjoinaddress = {"showjoinaddress", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -2010,7 +2030,7 @@ static void CL_LoadReceivedSavegame(boolean reloading)
automapactive = false;
// load a base level
if (P_LoadNetGame(&save, reloading))
if (P_LoadNetGame(&save, reloading, false))
{
if (!reloading)
{
@@ -2059,6 +2079,14 @@ static void CL_ReloadReceivedSavegame(void)
sprintf(player_names[i], "Player %d", i + 1);
}
if (simtic > gametic && !canSimulate)
{
simtic = gametic;
DEBFILE("Not simulating, clearing local savestates...\n");
// we don't load our local gamestate because we are gonna load the server's one
InvalidateSavestates();
}
CL_LoadReceivedSavegame(true);
neededtic = max(neededtic, gametic);
@@ -5080,7 +5108,7 @@ static void PT_ServerCFG(SINT8 node)
if (client)
{
maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
maketic = gametic = simtic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
if ((gametype = netbuffer->u.servercfg.gametype) >= NUMGAMETYPES)
I_Error("Bad gametype in cliserv!");
modifiedgame = netbuffer->u.servercfg.modifiedgame;
@@ -5383,6 +5411,12 @@ static void HandlePacketFromPlayer(SINT8 node)
/// \todo Use a separate cvar for that kind of timeout?
freezetimeout[node] = I_GetTime() + connectiontimeout;
// If we've alredy received a ticcmd for this tic, just submit it for the next one.
tic_t faketic = maketic;
if ((!!(netcmds[maketic % BACKUPTICS][netconsole].angleturn & TICCMD_RECEIVED))
&& (maketic - firstticstosend < BACKUPTICS - 1))
faketic++;
// Don't do anything for packets of type NODEKEEPALIVE?
// Sryder 2018/07/01: Update the freezetimeout still!
if (netbuffer->packettype == PT_NODEKEEPALIVE
@@ -5390,8 +5424,7 @@ static void HandlePacketFromPlayer(SINT8 node)
break;
// Copy ticcmd
// store it in an internal buffer so the last packet takes precedence, which minimizes input lag
G_MoveTiccmd(&playercmds[netconsole], &netbuffer->u.clientpak.cmd, 1);
G_MoveTiccmd(&netcmds[faketic%BACKUPTICS][netconsole], &netbuffer->u.clientpak.cmd, 1);
// Check ticcmd for "speed hacks"
if (CheckForSpeedHacks((UINT8)netconsole))
@@ -5403,7 +5436,8 @@ static void HandlePacketFromPlayer(SINT8 node)
|| (netbuffer->packettype == PT_CLIENT4CMD || netbuffer->packettype == PT_CLIENT4MIS))
&& (nodetoplayer2[node] >= 0))
{
G_MoveTiccmd(&playercmds[(UINT8)nodetoplayer2[node]], &netbuffer->u.client2pak.cmd2, 1);
G_MoveTiccmd(&netcmds[faketic%BACKUPTICS][(UINT8)nodetoplayer2[node]],
&netbuffer->u.client2pak.cmd2, 1);
if (CheckForSpeedHacks((UINT8)nodetoplayer2[node]))
break;
@@ -5413,7 +5447,8 @@ static void HandlePacketFromPlayer(SINT8 node)
|| (netbuffer->packettype == PT_CLIENT4CMD || netbuffer->packettype == PT_CLIENT4MIS))
&& (nodetoplayer3[node] >= 0))
{
G_MoveTiccmd(&playercmds[(UINT8)nodetoplayer3[node]], &netbuffer->u.client3pak.cmd3, 1);
G_MoveTiccmd(&netcmds[faketic%BACKUPTICS][(UINT8)nodetoplayer3[node]],
&netbuffer->u.client3pak.cmd3, 1);
if (CheckForSpeedHacks((UINT8)nodetoplayer3[node]))
break;
@@ -5422,7 +5457,8 @@ static void HandlePacketFromPlayer(SINT8 node)
if ((netbuffer->packettype == PT_CLIENT4CMD || netbuffer->packettype == PT_CLIENT4MIS)
&& (nodetoplayer4[node] >= 0))
{
G_MoveTiccmd(&playercmds[(UINT8)nodetoplayer4[node]], &netbuffer->u.client4pak.cmd4, 1);
G_MoveTiccmd(&netcmds[faketic%BACKUPTICS][(UINT8)nodetoplayer4[node]],
&netbuffer->u.client4pak.cmd4, 1);
if (CheckForSpeedHacks((UINT8)nodetoplayer4[node]))
break;
@@ -6186,6 +6222,7 @@ static void CL_SendClientCmd(void)
}
M_Memcpy(netbuffer->u.textcmd, localtextcmd[i], localtextcmd[i][0]+1);
// All extra data have been sent
if (HSendPacket(servernode, true, 0, localtextcmd[i][0]+1)) // Send can fail...
{
@@ -6308,6 +6345,23 @@ static void SV_SendTics(void)
supposedtics[0] = maketic;
}
void EncodeTiccmdTime(ticcmd_t *ticcmd, tic_t time)
{
#ifdef ENCODE_TICCMD_TIMES
ticcmd->aiming = (ticcmd->aiming & ~TICCMD_TIMEMASK_AIMING) | (time & TICCMD_TIMEMASK_AIMING);
ticcmd->angleturn = (ticcmd->angleturn & ~(TICCMD_TIMEMASK_ANGLE << 1)) | (((time >> TICCMD_TIMEBITS_AIMING) & TICCMD_TIMEMASK_ANGLE) << 1); // <<1 due to TICCMD_RECEIVED :/
#endif
}
tic_t DecodeTiccmdTime(const ticcmd_t *ticcmd)
{
#ifdef ENCODE_TICCMD_TIMES
return (ticcmd->aiming & TICCMD_TIMEMASK_AIMING) + (((ticcmd->angleturn & (TICCMD_TIMEMASK_ANGLE << 1)) >> 1) << TICCMD_TIMEBITS_AIMING);
#else
return 0;
#endif
}
//
// TryRunTics
//
@@ -6324,6 +6378,10 @@ static inline void CreateNewLocalCMD(UINT8 p, INT32 realtics)
}
G_BuildTiccmd(&localcmds[p][0], realtics, p+1);
//encode ticks in a special way so we can debug connetions!
EncodeTiccmdTime(&localcmds[p][0], I_GetTime());
localcmds[p][0].angleturn |= TICCMD_RECEIVED;
}
@@ -6331,10 +6389,14 @@ static void Local_Maketic(INT32 realtics)
{
INT32 i;
I_OsPolling(); // I_Getevent
D_ProcessEvents(); // menu responder, cons responder,
// game responder calls HU_Responder, AM_Responder, F_Responder,
// and G_MapEventsToControls
if (finaltargetsimtic + 1 == simtic || !canSimulate)
{
I_OsPolling(); // I_Getevent
D_ProcessEvents(); // menu responder, cons responder,
// game responder calls HU_Responder, AM_Responder, F_Responder,
// and G_MapEventsToControls
}
if (!dedicated)
rendergametic = gametic;
@@ -6378,11 +6440,73 @@ void SV_SpawnPlayer(INT32 playernum, INT32 x, INT32 y, angle_t angle)
// create missed tic
static void SV_Maketic(void)
{
G_MoveTiccmd(netcmds[maketic % BACKUPTICS], playercmds, MAXPLAYERS);
INT32 j;
for (j = 0; j < MAXNETNODES; j++)
{
if (!playerpernode[j])
continue;
INT32 player = nodetoplayer[j];
if ((netcmds[maketic%BACKUPTICS][player].angleturn & TICCMD_RECEIVED) == 0)
{ // we didn't receive this tic
INT32 i;
DEBFILE(va("MISS tic%4d for node %d\n", maketic, j));
#if defined(PARANOIA) && 0
CONS_Debug(DBG_NETPLAY, "Client Misstic %d\n", maketic);
#endif
// copy the old tic
for (i = 0; i < playerpernode[j]; i++)
{
if (i == 0) player = nodetoplayer[j];
else if (i == 1) player = nodetoplayer2[j];
else if (i == 2) player = nodetoplayer3[j];
else if (i == 3) player = nodetoplayer4[j];
netcmds[maketic%BACKUPTICS][player] = netcmds[(maketic-1)%BACKUPTICS][player];
netcmds[maketic%BACKUPTICS][player].angleturn &= ~TICCMD_RECEIVED;
}
}
}
// all tic are now proceed make the next
maketic++;
}
boolean TryRunTics(tic_t realtics)
// Overview for the new programmer and the programmer who probably isn't new, but doesn't have a clue what his code does becuase he didn't comment it ;)
// oh dayum did I just get called out by my old comment
/*
g ametic is always the real tic as received from the server, *plus one. the simulator does not change this!
smoothedTic is the time that we are using as a basis to cancel out jitter. this may be slightly <= gametic.
liveTic is the real time of the game from startup, used to record our commands
simTic is the gametic we've simulated to. if this == gametic, we haven't simulated any
gameStateBuffer[gametic] is the game state before 'gametic' executes
*/
tic_t liveTic;
int serverJitter, rttJitter;
int estimatedRTT = 0, minRTT, maxRTT;
int minLiveTicOffset, maxLiveTicOffset;
int recommendedSimulateTics = 0; // simulateTics recommendation based on the last known 'stable' RTT (range <= 2). Used for avoiding spike lag future-past-teleports.
int smoothingDelay;
double netUpdateFudge; // our last net update fudge
tic_t SavestatesClearedTic;
#define MAXOFFSETHISTORY 35
int ticTimeOffsetHistory[MAXOFFSETHISTORY];
static void DetermineNetConditions(void);
static boolean CompareTiccmd(const ticcmd_t* a, const ticcmd_t* b)
{
return a->aiming == b->aiming && (a->angleturn&~TICCMD_RECEIVED) == (b->angleturn&~TICCMD_RECEIVED) && a->buttons == b->buttons
&& a->forwardmove == b->forwardmove && a->sidemove == b->sidemove;
}
savebuffer_t statesave = {NULL, NULL};
boolean TryRunTics(tic_t realtics, tic_t entertic)
{
boolean ticking;
@@ -6405,6 +6529,10 @@ boolean TryRunTics(tic_t realtics)
D_MapChange(-1, 0, encoremode, false, 2, false, fromlevelselect); // finish the map change
}
precise_t now = I_GetPreciseTime();
double frame = ((double)now / tic_frequency);
netUpdateFudge = (((double)now / tic_frequency) - frame); // record the timefudge where the net update typically occurs
NetUpdate();
if (demo.playback)
@@ -6429,46 +6557,588 @@ boolean TryRunTics(tic_t realtics)
}
#endif
ticking = neededtic > gametic;
// detect if we can do simulation
canSimulate = (gamestate == GS_LEVEL)
&& leveltime >= NEWTICRATE
&& gametic >= NEWTICRATE
//&& !countdown2 //SRB2Kart: No simulating after everyone has finished ... it breaks stuff
&& (cv_simulate.value && !server) //SRB2Kart: make it impossible to sim before the start of the race
&& (!resynch_local_inprogress && !cl_redownloadinggamestate) && gametic >= SavestatesClearedTic + NEWTICRATE; //do not simulate for one second after clearing
if (simtic > gametic && !canSimulate)
{
// if we can't simulate anymore, we ought to reload a valid "server's"
// (in fact, our own) gamestate and then invalidate all of them.
// TODO 2.2.9 sends/receives savestates, we have to rewrite this later.
// maybe we should __specifically__ request the savestate from the server.
// we'll be requesting it anyway because if we desynched, we can't simulate.
if (gameStateBufferIsValid[gametic % MAXLOCALSAVESTATES])
{
if (!(gamestate == GS_INTERMISSION))
{
con_muted = true;
P_LoadGameState(&gameStateBuffer[gametic % MAXLOCALSAVESTATES], &statesave);
con_muted = false;
}
// Most of the time the RandSeed is correct (e.g. lua map voting)
// because we always load "real state" before making sims
// so setting it up explicitly for the intermission isn't needed.
// If RandSeed is not in synch with the server for whateva reason, it's
// an issue with vanilla SRB2(?) or we saved a bad gamestate
// TODO improve intermission handling later
// because we can get into intermission during sim with lua.
// it happened once with "battleroyale" lua mod
}
else
// CONS_Printf("Game state buffer is invalid! (simtic %d gametic %d)\n", simtic, gametic);
DEBFILE(va("NETPLUS: Game state buffer is invalid! (simtic %d gametic %d)\n", simtic, gametic));
simtic = gametic;
// CONS_Printf("Not simulating, clearing savestates...\n");
DEBFILE("NETPLUS: Not simulating, clearing savestates...\n");
InvalidateSavestates();
}
// see if we have enough latency to simulate lots of sims
//TODO Calculate possible CPU performance gains/loss from simulations
//TODO place netcmds in correct order!
if (!cl_redownloadinggamestate && canSimulate)
simInaccuracy = cv_siminaccuracy.value;
else
simInaccuracy = 1;
liveTic = entertic; //we get entertic from SRB2Loop()
// record actual local controls for this frame
// if realtics>=2, it copies input to several tics, means we lag
// we still need to buffer controls so they won't be lost if we don't process the real game with simInaccuracy enabled
for (tic_t i = 0; i < realtics; i++)
{
//localcmds are being calculated in NetUpdate()->Local_Maketic() function
localTicBuffer[(liveTic - i) % MAXSIMULATIONS] = localcmds[0][0];
}
ticking = (neededtic > gametic);
if (ticking)
{
hu_stopped = false;
// run the count * tics
while (neededtic > gametic)
if (!(gamestate == GS_LEVEL) // Not in a level
// In a level, in a netgame, it's an N livetic (but not gametic because the game can lag)
|| ((gamestate == GS_LEVEL) && ((liveTic % simInaccuracy == 0)) && netgame) || !netgame) // or singleplayer
{
boolean update_stats = !(paused || P_AutoPause());
DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic));
if (update_stats)
PS_START_TIMING(ps_tictime);
G_Ticker((gametic % NEWTICRATERATIO) == 0);
ExtraDataTicker();
gametic++;
consistancy[gametic%BACKUPTICS] = Consistancy();
if (update_stats)
// Load the real state if it exists before doing anything
// The server will resynch us anyway if things would go wrong
if (simtic > gametic && gameStateBufferIsValid[gametic % MAXLOCALSAVESTATES])
{
PS_STOP_TIMING(ps_tictime);
PS_UpdateTickStats();
con_muted = true;
P_LoadGameState(&gameStateBuffer[gametic % MAXLOCALSAVESTATES], &statesave);
con_muted = false;
if (Consistancy() != consistancy[gametic % BACKUPTICS])
// CONS_Alert(CONS_WARNING, "Saved state at %d isn't consistent with recorded checksum\n", gametic);
DEBFILE(va("NETPLUS: Saved state at %d isn't consistent with recorded checksum\n", gametic));
}
else if (simtic != gametic && !gameStateBufferIsValid[gametic % MAXLOCALSAVESTATES])
// CONS_Alert(CONS_WARNING, "Game state buffer is inaccessible but a simulation happened\n");
DEBFILE("NETPLUS: Game state buffer is inaccessible but a simulation happened\n");
// Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame.
if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value)
break;
// run the count * tics
while (neededtic > gametic)
{
boolean update_stats = !(paused || P_AutoPause());
DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic));
if (update_stats)
PS_START_TIMING(ps_tictime);
targetsimtic = gametic + 1;
G_Ticker((gametic % NEWTICRATERATIO) == 0);
ExtraDataTicker();
gametic++;
simtic = gametic;
consistancy[gametic % BACKUPTICS] = Consistancy();
if (update_stats)
{
PS_STOP_TIMING(ps_tictime);
PS_UpdateTickStats();
}
if (canSimulate)
{
if (neededtic == gametic)
{
// store this real state (hopefully accurate to the one from server)
P_SaveGameState(&gameStateBuffer[gametic % MAXLOCALSAVESTATES], &statesave);
gameStateBufferIsValid[gametic % MAXLOCALSAVESTATES] = true;
}
// store the ticcmds used during this game tic for simulations
// TODO optimize it in a way that they won't be saved when we finished chasing to server's gamestate
for (UINT8 i = 0; i < MAXPLAYERS; i++)
gameTicBuffer[gametic % MAXSIMULATIONS][i] = netcmds[(gametic - 1) % BACKUPTICS][i];
}
// Leave a certain amount of tics present in the net buffer as long as we've ran at least one tic this frame.
if (!canSimulate) //do not use the vanilla netbuffer or simulations will be doubled/tripled/so on
if (client && gamestate == GS_LEVEL && leveltime > 3 && neededtic <= gametic + cv_netticbuffer.value)
break;
}
}
else if (canSimulate)
targetsimtic = simtic + 1; //simulate the latest simulation, haha!
// collect net condition data based on encoded tics, it's needed for calculating correct netcmds
if (netgame)
DetermineNetConditions();
// And only then run simulations locally
if (canSimulate && !cl_redownloadinggamestate)
RunSimulations();
}
// The network/game is laggy, let's simulate one tic further and use previous controls
// hopefully the server won't miss our input
else
{
hu_stopped = true;
if (canSimulate && !cl_redownloadinggamestate && cv_simmisstics.value == 1)
{
// collect net condition data based on encoded tics, it's needed for calculating correct netcmds
DetermineNetConditions();
ticcmd_t temp;
for (int j = 0; j < MAXPLAYERS; j++)
{
if (players[j].ingame && j != consoleplayer && j != displayplayers[1] && j != displayplayers[2] && j != displayplayers[3])
netcmds[gametic % BACKUPTICS][j] = gameTicBuffer[(min(simtic + 1, gametic) + MAXSIMULATIONS) % MAXSIMULATIONS][j];
else
{
temp = netcmds[gametic % BACKUPTICS][consoleplayer];
netcmds[gametic % BACKUPTICS][consoleplayer] = localcmds[0][0];
}
}
DEBFILE(va("============ Running SIMMISS tic %d (local %d)\n", gametic, localgametic));
con_muted = true;
issimulation = true;
//G_Ticker(true); //tic one tic further as usual
con_muted = false;
netcmds[gametic % BACKUPTICS][consoleplayer] = temp;
issimulation = false;
}
}
return ticking;
}
//QUICKSORT junk from the internet
//algo by rathbhupendra
static void rathbhupendra_quicksort_swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
static int rathbhupendra_quicksort_partition(int arr[], int low, int high)
{
int pivot = arr[high]; // pivot
int i = (low - 1); // Index of smaller element and indicates the right position of pivot found so far
for (int j = low; j <= high - 1; j++)
{
// If current element is smaller than the pivot
if (arr[j] < pivot)
{
i++; // increment index of smaller element
rathbhupendra_quicksort_swap(&arr[i], &arr[j]);
}
}
rathbhupendra_quicksort_swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
static void rathbhupendra_quicksort(int arr[], int low, int high)
{
if (low < high)
{
/* pi is partitioning index, arr[p] is now
at right place */
int pi = rathbhupendra_quicksort_partition(arr, low, high);
// Separately sort elements before
// partition and after partition
rathbhupendra_quicksort(arr, low, pi - 1);
rathbhupendra_quicksort(arr, pi + 1, high);
}
}
//QUICKSORT junk from the internet
//MEDIAN junk from the internet
static float netplus_median(int n, int x[])
{
if (n % 2 == 0)
{
// if there is an even number of elements, return mean of the two elements in the middle
return ((x[n / 2] + x[n / 2 - 1]) / 2.0);
}
else
{
// else return the element in the middle
return x[n / 2];
}
}
int numToSimulateHistory[MAXSIMULATIONS] = {0}; //The remaining array elements will be automatically initialized to zero.
static int DetermineSimulationAmount(void)
{
UINT8 tastyFudge = 0;
// hack: don't treat duplicate tics as extra round-trip time
if (liveTic % simInaccuracy == 0) //each N game state while playing
{
for (UINT8 j = 1; j < 3; j++)
{
if (CompareTiccmd(&gameTicBuffer[gametic % MAXSIMULATIONS][consoleplayer], &gameTicBuffer[(gametic - j) % MAXSIMULATIONS][consoleplayer]))
tastyFudge++;
else
break;
}
}
int numDesiredSimulateTics = min(recommendedSimulateTics, cv_simulatetics.value);
// it 99% of the time always equals gametic+estimatedRTT-tastyFudge
int nextTargetSimTic = min(min(gametic + estimatedRTT - tastyFudge, gametic + numDesiredSimulateTics), simtic + MAXSIMULATIONS);
if ((nextTargetSimTic >= 0) && (liveTic % simInaccuracy == 0))
targetsimtic = nextTargetSimTic;
if (!cv_jittersmoothing.value)
return targetsimtic - simtic;
numToSimulateHistory[liveTic % MAXSIMULATIONS] = targetsimtic - simtic;
for (UINT8 i = 0; i < MAXSIMULATIONS; i++)
if (numToSimulateHistory[i] == 0)
return numToSimulateHistory[liveTic % MAXSIMULATIONS];
int numToSimulateHistorySorted[MAXSIMULATIONS] = {0};
M_Memcpy(numToSimulateHistorySorted, numToSimulateHistory, MAXSIMULATIONS * sizeof(int));
//quicksort
rathbhupendra_quicksort(numToSimulateHistorySorted, 0, MAXSIMULATIONS - 1);
//find the median
float numSimsMedian = netplus_median(MAXSIMULATIONS-1, numToSimulateHistorySorted);
if (numSimsMedian <= 1)
return 1;
if (numSimsMedian > numDesiredSimulateTics)
return numDesiredSimulateTics;
return (int)numSimsMedian;
}
UINT64 simStartTime = 0;
UINT64 simEndTime = 0;
static void RunSimulations(void)
{
if (!gameStateBufferIsValid[gametic % MAXLOCALSAVESTATES])
{
// CONS_Alert(CONS_WARNING, "Can't simulate, save on %d is invalid!\n", gametic);
DEBFILE(va("NETPLUS: Can't simulate, save on %d is invalid!\n", gametic));
return; // do not simulate if we cannot guarantee a recovery
}
int numToSimulate = DetermineSimulationAmount();
finaltargetsimtic = gametic + numToSimulate - 1; //-1 because we use this var to influence the gamestate
if (numToSimulate > 0) // only touch objects if we're definitely going to simulate and rewind!
{
// record steadyplayers' real game position and their simulated position
for (UINT8 i = 0; i < MAXPLAYERS; i++)
{
if (players[i].ingame && players[i].mo && i != consoleplayer)
{
if (simtic == gametic) // this is their real position
{
steadyplayers[i].histx[0] = players[i].mo->x;
steadyplayers[i].histy[0] = players[i].mo->y;
steadyplayers[i].histz[0] = players[i].mo->z;
}
else
{
P_UnsetThingPosition(players[i].mo);
// restore the players' simulated position before simulating more
players[i].mo->x = steadyplayers[i].histx[simtic - gametic];
players[i].mo->y = steadyplayers[i].histy[simtic - gametic];
players[i].mo->z = steadyplayers[i].histz[simtic - gametic];
P_SetThingPosition(players[i].mo);
}
}
}
// and cull distant thinkers if enabled
if (cv_simulateculldistance.value > 0)
{
thinker_t *current;
fixed_t minDistance = cv_simulateculldistance.value << FRACBITS;
mobj_t *playerMos[MAXPLAYERS];
UINT8 numPlayers = 0;
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (players[i].ingame && players[i].mo)
{
playerMos[numPlayers++] = players[i].mo;
}
}
for (current = thinkercap.next; current != &thinkercap; current = current->next)
{
if (current->function != (actionf_p1)P_MobjThinker)
continue;
if (P_IsProjectile(((mobj_t *)current)->type))
continue;
for (i = 0; i < numPlayers; i++)
{
if (P_AproxDistance(playerMos[i]->x - ((mobj_t *)current)->x, playerMos[i]->y - ((mobj_t *)current)->y) < minDistance)
break;
}
if (i == numPlayers)
{
// cull this mobj
((mobj_t *)current)->isculled = true;
}
}
}
}
// simulate the rest o da future
issimulation = true;
con_muted = true;
simStartTime = I_GetPreciseTime(); //for benchmarking
for (int i = 0; i < numToSimulate; i++)
{
// // control other players (just use their previous control for now)
// here you can do all sorts of player predictions.
for (int j = 0; j < MAXPLAYERS; j++)
{
if (players[j].ingame && j != consoleplayer)
//simtic+1 это для херни со smoothedTic
//use Memcpy or G_CopyTiccmd for that
netcmds[gametic % BACKUPTICS][j] = gameTicBuffer[(min(simtic + 1, gametic) + MAXSIMULATIONS) % MAXSIMULATIONS][j];
}
// control the local player
if (simtic + i < gametic) // game is smoothed, take tics from the _actual_ received state
{
netcmds[gametic % BACKUPTICS][consoleplayer] = gameTicBuffer[(simtic + 1) % MAXSIMULATIONS][consoleplayer];
}
else if (liveTic % simInaccuracy == 0)
{
netcmds[gametic % BACKUPTICS][consoleplayer] = localTicBuffer[(liveTic - estimatedRTT + i + 1 + MAXSIMULATIONS) % MAXSIMULATIONS];
}
else
netcmds[gametic % BACKUPTICS][consoleplayer] = localcmds[0][0]; //NO
DEBFILE(va("============ Running SIM tic %d (local %d) (sim %d)\n", gametic, localgametic, simtic));
//G_Ticker(true); // tic a bunch of times lol see what happens lolol
simtic++;
// record simulated players' positions
for (int j = 0; j < MAXPLAYERS; j++)
{
if (players[j].ingame && players[j].mo && j != consoleplayer)
{
// Update steadyplayers' simulated positions
steadyplayers[j].histx[simtic - gametic] = players[j].mo->x;
steadyplayers[j].histy[simtic - gametic] = players[j].mo->y;
steadyplayers[j].histz[simtic - gametic] = players[j].mo->z;
}
}
}
issimulation = false;
con_muted = false;
// lastsimtic = simtic;
// Finalise steadyplayers
if (numToSimulate > 0)
{
int histIndex = max((int)(simtic - gametic) - cv_netsteadyplayers.value, 0); // up to cv_netsteadyplayers.value behind the simulation
for (int i = 0; i < MAXPLAYERS; i++)
{
if (players[i].ingame && players[i].mo && i != consoleplayer && !players[i].spectator)
{
// Set the player's positions to the preferred simulation index (or perhaps just their original position)
// and store it in simx/simy/simz too for trails
P_UnsetThingPosition(players[i].mo);
players[i].mo->x = steadyplayers[i].histx[histIndex];
players[i].mo->y = steadyplayers[i].histy[histIndex];
players[i].mo->z = steadyplayers[i].histz[histIndex];
P_SetThingPosition(players[i].mo);
// fill any holes in the simulation history (due to frame skips)
for (int j = (int)max((int)finaltargetsimtic + 2, (int)simtic - 5); j <= (int)simtic; j++)
{
steadyplayers[i].simx[j % MAXSIMULATIONS] = steadyplayers[i].histx[histIndex];
steadyplayers[i].simy[j % MAXSIMULATIONS] = steadyplayers[i].histy[histIndex];
steadyplayers[i].simz[j % MAXSIMULATIONS] = steadyplayers[i].histz[histIndex];
}
// Generate trails
if (cv_nettrails.value)
{
int trailLifetime = cv_nettrails.value;
for (int s = 0; s < trailLifetime; s++)
{
// If there is a big discrepency between the player's current position and their last one, spawn a trail showing their movements
fixed_t prevx = steadyplayers[i].simx[(simtic - s - 1) % MAXSIMULATIONS], prevy = steadyplayers[i].simy[(simtic - s - 1) % MAXSIMULATIONS],
prevz = steadyplayers[i].simz[(simtic - s - 1) % MAXSIMULATIONS];
fixed_t curx = steadyplayers[i].simx[(simtic - s) % MAXSIMULATIONS], cury = steadyplayers[i].simy[(simtic - s) % MAXSIMULATIONS],
curz = steadyplayers[i].simz[(simtic - s) % MAXSIMULATIONS];
fixed_t distance = max(abs(curx - prevx), max(abs(cury - prevy), abs(curz - prevz)));
fixed_t stepDistance = 60 << FRACBITS; // distance between trail steps
fixed_t activationDistance = 60 << FRACBITS; // distance between trail steps
// If player is changing direction quickly in a net simulation, create a ghost trail
if (distance > activationDistance)
{
fixed_t numSteps = FixedDiv(distance, stepDistance) & ~FRACMASK;
fixed_t currentStep; // between 0 and 1
fixed_t step = FixedDiv(1 << FRACBITS, numSteps + 1);
for (currentStep = step; currentStep < (1 << FRACBITS); currentStep += step)
{
mobj_t *ghost = P_SpawnGhostMobj(players[i].mo);
ghost->x = prevx + FixedMul(curx - prevx, currentStep);
ghost->y = prevy + FixedMul(cury - prevy, currentStep);
ghost->z = prevz + FixedMul(curz - prevz, currentStep);
ghost->frame = (ghost->frame & FF_TRANSMASK) | ((6 - s) << FF_TRANSSHIFT);
ghost->fuse = 1;
}
}
}
}
}
}
}
simEndTime = I_GetPreciseTime();
rendergametic = gametic;
}
static void P_GameStateFreeMemory(savestate_t* savestate)
{
if (savestate->buffer)
Z_Free(savestate->buffer);
savestate->buffer = NULL; //a hacky way to invalidate the memory
}
void InvalidateSavestates(void)
{
if (simtic > gametic)
DEBFILE("NETPLUS: Savestates were invalidated during a simulation\n");
// CONS_Printf("Warning: Savestates were invalidated during a simulation!!\n");
for (int i = 0; i < MAXLOCALSAVESTATES; i++)
{
if (gameStateBufferIsValid[i] && gameStateBuffer[i].buffer != NULL)
{
//we don't want to store old states because a client might not want to simulate
//the game anymore or saved games are not valid anymore due to resynch
P_GameStateFreeMemory(&gameStateBuffer[i]);
}
gameStateBufferIsValid[i] = false;
}
SavestatesClearedTic = gametic;
}
int rttBuffer[20];
int rttBufferIndex = 0;
int rttBufferMax = 20;
static void DetermineNetConditions(void)
{
// Refresh the time offset between real time and server time
minLiveTicOffset = INT_MAX;
maxLiveTicOffset = INT_MIN;
if (simInaccuracy == 1)
ticTimeOffsetHistory[liveTic % MAXOFFSETHISTORY] = (int)liveTic - (int)gametic;
else
{
ticTimeOffsetHistory[liveTic % MAXOFFSETHISTORY] = (int)liveTic - (int)maketic;
}
// Find the net jitter based on the last 35 frames (one second)
for (int i = 0; i < MAXOFFSETHISTORY; i++)
{
minLiveTicOffset = min(minLiveTicOffset, ticTimeOffsetHistory[i]);
maxLiveTicOffset = max(maxLiveTicOffset, ticTimeOffsetHistory[i]);
}
serverJitter = min(5, maxLiveTicOffset - minLiveTicOffset); //we cannot skip more than 5 tics
// Estimate the RTT (once used awkward tic matching stuff, now uses sneaky encoded angles)
#ifndef ENCODE_TICCMD_TIMES
if (!(gametic % 35))
{
int matchingLiveTic, matchingGameTic;
if (FindMatchingTics(&matchingLiveTic, &matchingGameTic) && matchingLiveTic - matchingGameTic < BACKUPTICS - 2)
{
estimatedRTT = matchingLiveTic - matchingGameTic;
}
}
#else
for (int j = 1; j < TICCMD_TIME_SIZE - 1; j++)
{
if (CompareTiccmd(&gameTicBuffer[gametic % MAXSIMULATIONS][consoleplayer], &localTicBuffer[(liveTic - j + MAXSIMULATIONS) % MAXSIMULATIONS]))
{
estimatedRTT = j;
break;
}
}
#endif
// estimate RTT jitter
minRTT = INT_MAX;
maxRTT = INT_MIN;
rttBuffer[rttBufferIndex] = estimatedRTT;
rttBufferIndex = (rttBufferIndex + 1) % rttBufferMax;
for (int i = 0; i < rttBufferMax; i++)
{
minRTT = min(minRTT, rttBuffer[i]);
maxRTT = max(maxRTT, rttBuffer[i]);
}
rttJitter = maxRTT - minRTT;
// stable server conditions? (but not just a big lag spike?)
if (rttJitter <= 2 && maxRTT < MAXSIMULATIONS - 1) //TODO
{
// we know roughly how much we should simulate then!
recommendedSimulateTics = maxRTT + 1;
}
}
/* Ping Update except better:
We call this once per second and check for people's pings. If their ping happens to be too high, we increment some timer and kick them out.
@@ -6993,7 +7663,7 @@ rewind_t *CL_RewindToTime(tic_t time)
save.buffer = save.p = rewindhead->savebuffer;
P_LoadNetGame(&save, false);
P_LoadNetGame(&save, false, false);
wipegamestate = gamestate; // No fading back in!
timeinmap = leveltime;
+32 -1
View File
@@ -42,6 +42,19 @@ applications may follow different packet versions.
#define CLIENTBACKUPTICS 32
#define MAXTEXTCMD 256
// How many time bits to encode into ticcmds (aiming and angle components, respectively)
#define ENCODE_TICCMD_TIMES
#define TICCMD_TIMEBITS_AIMING 3
#define TICCMD_TIMEBITS_ANGLE 2
#define TICCMD_TIMEMASK_AIMING (~(0xFFFFFFFF<<TICCMD_TIMEBITS_AIMING))
#define TICCMD_TIMEMASK_ANGLE (~(0xFFFFFFFF<<TICCMD_TIMEBITS_ANGLE))
#define TICCMD_TIME_SIZE (1<<(TICCMD_TIMEBITS_AIMING+TICCMD_TIMEBITS_ANGLE))
// Maximum number of client-side simulations allowed. A simulation is a version of the game state extrapolated some frames ahead to cancel out network latency
#define MAXSIMULATIONS TICRATE //one second of simulations
#define MAXLOCALSAVESTATES 8
extern tic_t liveTic;
// No. of tics your controls can be delayed by.
// TODO: Instead of storing a ton of extra cmds for gentlemens' delay,
@@ -563,6 +576,17 @@ typedef enum
} kickreason_t;
// Player movement histories for simulated gamestates
typedef struct
{
// stores historical simulated positions where 0 is the real game position and simtic-gametic is the latest simulated position
fixed_t histx[MAXSIMULATIONS + 1], histy[MAXSIMULATIONS + 1], histz[MAXSIMULATIONS + 1];
// stores the final simulated position for each simulated gametic
fixed_t simx[MAXSIMULATIONS], simy[MAXSIMULATIONS], simz[MAXSIMULATIONS];
} steadyplayer_t;
/* the max number of name changes in some time period */
#define MAXNAMECHANGES (5)
#define NAMECHANGERATE (60*TICRATE)
@@ -576,6 +600,10 @@ extern boolean acceptnewnode;
extern SINT8 servernode;
extern char connectedservername[MAXSERVERNAME+1];
extern boolean issimulation; // whether the currently executed tic is part of a simulated gamestate
extern steadyplayer_t steadyplayers[MAXPLAYERS]; // Player movement histories for simulated gamestates
extern int rttJitter; //Round Trip Time jitter
void Command_Ping_f(void);
extern tic_t connectiontimeout;
extern tic_t jointimeout;
@@ -642,7 +670,10 @@ boolean Playing(void);
void D_QuitNetGame(void);
//? How many ticks to run?
boolean TryRunTics(tic_t realtic);
boolean TryRunTics(tic_t realtic, tic_t entertic);
// Invalidates save states used in simulations
void InvalidateSavestates(void);
// extra data for lmps
// these functions scare me. they contain magic.
+1 -1
View File
@@ -859,7 +859,7 @@ void D_SRB2Loop(void)
realtics = 1;
// process tics (but maybe not if realtic == 0)
TryRunTics(realtics);
TryRunTics(realtics, entertic);
if (lastdraw || singletics || (gametic > rendergametic))
{
+77
View File
@@ -1208,6 +1208,83 @@ boolean HGetPacket(void)
return true;
}
#define gettime (I_GetPreciseTime() / tic_frequency)
void TimeFudge(void)
{
// you can use SDL_GetPerformanceFrequency() instead of tic_frequency to get more precise timings
double startTime = gettime;
double packetTimeFudge[64];
int numReceivedPackets = 0;
const double numSampleTics = 10;
int i;
double lastTimeReceivedPacket;
if (server && netgame)
{
CONS_Printf("Servers do not need time fudging!\n");
return;
}
lastTimeReceivedPacket = gettime;
while ((fabs(gettime - startTime) < numSampleTics) || numReceivedPackets < 10)
{
I_NetGet();
if ((doomcom->remotenode != -1)) // Packet received
{
packetTimeFudge[numReceivedPackets] = 100 * fabs((gettime - lastTimeReceivedPacket) - netUpdateFudge); // gets the time fudge offset (0-100)
CONS_Printf("%i: %f\n", numReceivedPackets, packetTimeFudge[numReceivedPackets]);
numReceivedPackets++;
lastTimeReceivedPacket = gettime;
}
}
if (numReceivedPackets > 0)
{
UINT8 minOffset = 100, maxOffset = 0, averageOffset = 0;
UINT8 newTimeFudge;
UINT8 estimatedRange;
for (i = 0; i < numReceivedPackets; i++)
{
if (packetTimeFudge[i] < 0)
continue;
minOffset = min(minOffset, packetTimeFudge[i]);
maxOffset = max(maxOffset, packetTimeFudge[i]);
}
if (maxOffset - minOffset > 50)
{
// say maxOffset = 90 and minOffset = 20, we want the average to be 30...
maxOffset = maxOffset - 100;
if (minOffset > maxOffset)
{
int swap = maxOffset;
maxOffset = minOffset;
minOffset = swap;
}
}
estimatedRange = maxOffset - minOffset;
averageOffset = (maxOffset + minOffset) / 2;
CONS_Printf("%i packets received\n Timer fluctuations:\nmin: %d max: %d avg: %d est. range: %d (network update time: %lf)\n", numReceivedPackets, minOffset, maxOffset, averageOffset,
estimatedRange, netUpdateFudge);
if (averageOffset <= 0)
{
CONS_Printf("Timers are OK, no time fudging required\n");
return;
}
newTimeFudge = (cv_timefudge.value + averageOffset + 50) % 100;
CONS_Printf("New time fudge: %i%%\n", newTimeFudge);
CV_SetValue(&cv_timefudge, newTimeFudge);
}
}
#undef gettime
static boolean Internal_Get(void)
{
doomcom->remotenode = -1;
+3
View File
@@ -27,6 +27,9 @@
#define STATLENGTH (TICRATE*2)
void TimeFudge(void);
extern double netUpdateFudge;
// stat of net
extern INT32 ticruned, ticmiss;
extern INT32 getbps, sendbps;
+90 -11
View File
@@ -123,6 +123,9 @@ static void KartEncore_OnChange(void);
static void KartComeback_OnChange(void);
static void KartEliminateLast_OnChange(void);
static void Command_Autotimefudge(void);
static void TimeFudge_OnChange(void);
#ifdef NETGAME_DEVMODE
static void Fishcake_OnChange(void);
#endif
@@ -527,6 +530,38 @@ consvar_t cv_skinselectgridsort ={ "skinselectgridsort", "Real name", CV_SAVE|CV
static CV_PossibleValue_t betainterscreen_t[] = {{0, "Off"}, {1, "On"}, {2, "NoBG"}, {0, NULL}};
consvar_t cv_betainterscreen = {"betaintermissionscreen", "Off", CV_SAVE, betainterscreen_t, NULL, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_simulate = { "sim", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL };
consvar_t cv_powerupmusic = { "powerupmusic", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL };
consvar_t cv_playerfullbright = { "playerfullbright", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL };
static CV_PossibleValue_t simulateTics_cons_t[] = { {0, "MIN"}, {MAXSIMULATIONS - 1, "MAX"}, {0, NULL} };
consvar_t cv_simulatetics = { "simtics", "MAX", 0, simulateTics_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL };
consvar_t cv_simmisstics = { "simmisstics", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL };
consvar_t cv_jittersmoothing = { "jittersmoothing", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL };
static CV_PossibleValue_t simulateculldistance_cons_t[] = { {0, "MIN"}, {10000, "MAX"}, {0, NULL} };
consvar_t cv_simulateculldistance = { "simcull", "MIN", 0, simulateculldistance_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL };
static CV_PossibleValue_t siminaccuracy_cons_t[] = { {1, "MIN"}, {10, "MAX"}, {0, NULL} };
consvar_t cv_siminaccuracy = { "siminaccuracy", "MIN", 0, siminaccuracy_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL };
static CV_PossibleValue_t netsteadyplayers_cons_t[] = { {0, "MIN"}, {MAXSIMULATIONS - 1, "MAX"}, {0, NULL} };
consvar_t cv_netsteadyplayers = { "simsteadyplayers", "0", 0, netsteadyplayers_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL };
static CV_PossibleValue_t nettrails_cons_t[] = { {0, "MIN"}, {10, "MAX"}, {0, NULL} };
consvar_t cv_nettrails = { "simtrails", "5", 0, nettrails_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL };
consvar_t cv_netslingdelay = { "simslingdelay", "No", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL };
static CV_PossibleValue_t debugsimulaterewind_cons_t[] = { {0, "MIN"}, {BACKUPTICS - 1, "MAX"}, {0, NULL} };
consvar_t cv_debugsimulaterewind = { "debugsimulaterewind", "0", 0, debugsimulaterewind_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL };
static CV_PossibleValue_t timefudge_cons_t[] = { {0, "MIN"}, {100, "MAX"}, {0, NULL} };
consvar_t cv_timefudge = { "timefudge", "0", CV_CALL, timefudge_cons_t, TimeFudge_OnChange, 0, NULL, NULL, 0, 0, NULL };
INT16 gametype = GT_RACE; // SRB2kart
boolean forceresetplayers = false;
boolean deferencoremode = false;
@@ -665,6 +700,20 @@ void D_RegisterServerCommands(void)
COM_AddCommand("archivetest", Command_Archivetest_f);
#endif
COM_AddCommand("autotimefudge", Command_Autotimefudge);
CV_RegisterVar(&cv_simulate);
CV_RegisterVar(&cv_simulatetics);
CV_RegisterVar(&cv_simmisstics);
CV_RegisterVar(&cv_jittersmoothing);
CV_RegisterVar(&cv_simulateculldistance);
CV_RegisterVar(&cv_siminaccuracy);
CV_RegisterVar(&cv_netsteadyplayers);
CV_RegisterVar(&cv_debugsimulaterewind);
CV_RegisterVar(&cv_timefudge);
CV_RegisterVar(&cv_nettrails);
CV_RegisterVar(&cv_netslingdelay);
CV_RegisterVar(&cv_playerfullbright);
COM_AddCommand("replaymarker", Command_ReplayMarker);
// for master server connection
@@ -789,6 +838,11 @@ void D_RegisterServerCommands(void)
#endif
}
void Command_Autotimefudge(void)
{
TimeFudge();
}
// =========================================================================
// CLIENT STARTUP
// =========================================================================
@@ -2863,6 +2917,8 @@ static void Got_Mapcmd(UINT8 **cp, INT32 playernum)
return;
}
InvalidateSavestates();
if (chmappending)
chmappending--;
@@ -4981,6 +5037,9 @@ void ItemFinder_OnChange(void)
* \sa cv_pointlimit, TimeLimit_OnChange
* \author Graue <graue@oceanbase.org>
*/
static int lastpointlimit;
static void PointLimit_OnChange(void)
{
// Don't allow pointlimit in Single Player/Co-Op/Race!
@@ -4991,15 +5050,20 @@ static void PointLimit_OnChange(void)
return;
}
if (cv_pointlimit.value)
if (cv_pointlimit.value != lastpointlimit)
{
CONS_Printf(M_GetText("Levels will end after %s scores %d point%s.\n"),
G_GametypeHasTeams() ? M_GetText("a team") : M_GetText("someone"),
cv_pointlimit.value,
cv_pointlimit.value > 1 ? "s" : "");
if (cv_pointlimit.value)
{
CONS_Printf(M_GetText("Levels will end after %s scores %d point%s.\n"),
G_GametypeHasTeams() ? M_GetText("a team") : M_GetText("someone"),
cv_pointlimit.value,
cv_pointlimit.value > 1 ? "s" : "");
}
else if (netgame || multiplayer)
CONS_Printf(M_GetText("Point limit disabled\n"));
}
else if (netgame || multiplayer)
CONS_Printf(M_GetText("Point limit disabled\n"));
lastpointlimit = cv_pointlimit.value;
}
static void NumLaps_OnChange(void)
@@ -5037,6 +5101,7 @@ UINT32 timelimitintics = 0;
*
* \sa cv_timelimit, PointLimit_OnChange
*/
static void TimeLimit_OnChange(void)
{
// Don't allow timelimit in Single Player/Co-Op/Race!
@@ -5048,19 +5113,28 @@ static void TimeLimit_OnChange(void)
if (cv_timelimit.value != 0)
{
CONS_Printf(M_GetText("Levels will end after %d minute%s.\n"),cv_timelimit.value,cv_timelimit.value == 1 ? "" : "s"); // Graue 11-17-2003
timelimitintics = cv_timelimit.value * 60 * TICRATE;
UINT32 newtimelimit = cv_timelimit.value * 60 * TICRATE;
//add hidetime for tag too!
if (G_TagGametype())
timelimitintics += hidetime * TICRATE;
newtimelimit += hidetime * TICRATE;
if (newtimelimit != timelimitintics)
{
CONS_Printf(M_GetText("Levels will end after %d minute%s.\n"), cv_timelimit.value, cv_timelimit.value == 1 ? "" : "s"); // Graue 11-17-2003
timelimitintics = newtimelimit; // ...and slightly modified by LX 12-11-2019 <3
}
// Note the deliberate absence of any code preventing
// pointlimit and timelimit from being set simultaneously.
// Some people might like to use them together. It works.
}
else if (netgame || multiplayer)
else if ((netgame || multiplayer) && timelimitintics > 0)
{
timelimitintics = 0;
CONS_Printf(M_GetText("Time limit disabled\n"));
}
#ifdef HAVE_DISCORDRPC
DRPC_UpdatePresence();
@@ -5228,6 +5302,11 @@ static void SoundTest_OnChange(void)
S_StartSound(NULL, cv_soundtest.value);
}
static void TimeFudge_OnChange(void)
{
I_SetTime(I_GetTime(), cv_timefudge.value, true);
}
static void AutoBalance_OnChange(void)
{
autobalance = (INT16)cv_autobalance.value;
+12
View File
@@ -146,6 +146,18 @@ extern consvar_t cv_pingmeasurement;
extern consvar_t cv_pingicon;
extern consvar_t cv_pingstyle;
extern consvar_t cv_simulate;
extern consvar_t cv_simulatetics;
extern consvar_t cv_simmisstics;
extern consvar_t cv_jittersmoothing;
extern consvar_t cv_simulateculldistance;
extern consvar_t cv_siminaccuracy;
extern consvar_t cv_netsteadyplayers;
extern consvar_t cv_debugsimulaterewind;
extern consvar_t cv_timefudge;
extern consvar_t cv_nettrails;
extern consvar_t cv_netslingdelay;
//extern consvar_t cv_smallpos;
extern consvar_t cv_showminimapnames;
extern consvar_t cv_minihead;
+3
View File
@@ -89,6 +89,9 @@ typedef struct
UINT16 sidenum[2];
} ATTRPACK maplinedef_t;
//#define INT_MIN (-2147483647 - 1)
//#define INT_MAX 2147483647
//
// LineDef attributes.
//
+2 -2
View File
@@ -113,8 +113,8 @@ extern char logfilename[1024];
#else
#define VERSION 1 // Game version
#define SUBVERSION 6 // more precise version number
#define VERSIONSTRING "Saturn lugdev Zzz..."
#define VERSIONSTRINGW L"Saturn lugdev Zzz..."
#define VERSIONSTRING "Saturn - NetPlus"
#define VERSIONSTRINGW L"Saturn - NetPlus lel"
// Hey! If you change this, add 1 to the MODVERSION below! Otherwise we can't force updates!
// And change CMakeLists.txt (not src/, but in root), for CMake users!
// AND appveyor.yml, for the build bots!
+5
View File
@@ -530,6 +530,11 @@ extern UINT32 timesBeatenWithEmeralds;
// WAD, partly set at startup time.
extern tic_t gametic;
extern tic_t simtic; // current tic at simulation, if applicable (if no simulation happened, this=gametic)
extern tic_t targetsimtic; // the target sim tic, if applicable
extern tic_t smoothedTic; // current game tic when smoothing is enabled (\todo: could cause problems if game objects care about gametic)
extern boolean canSimulate;
extern tic_t finaltargetsimtic;
#define localgametic leveltime
// Player spawn spots.
+13 -3
View File
@@ -126,6 +126,11 @@ INT32 consoleplayer; // player taking events and displaying
INT32 displayplayers[MAXSPLITSCREENPLAYERS]; // view being displayed
tic_t gametic;
tic_t simtic; // simulated tic
tic_t targetsimtic; // target simulated tic
tic_t smoothedTic;
boolean canSimulate;
tic_t finaltargetsimtic;
tic_t levelstarttic; // gametic at level start
UINT32 totalrings; // for intermission
INT16 lastmap; // last level you were at (returning from special stages)
@@ -1891,9 +1896,14 @@ void G_Ticker(boolean run)
if (demo.title)
F_TitleDemoTicker();
P_Ticker(run); // tic the game
ST_Ticker();
AM_Ticker();
HU_Ticker();
//do not draw any GUI during sims
if (!issimulation || finaltargetsimtic == simtic)
{
ST_Ticker();
AM_Ticker();
HU_Ticker();
}
}
break;
+380
View File
@@ -0,0 +1,380 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// John FrostFox (john.frostfox@gmail.com)
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file hashtable.c
/// \brief Hashtable abstraction implementation
#include "hashtable.h"
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define HT_NUMLISTS UINT8_MAX
typedef struct mobjnum_linkedList_s{
thinker_t* thinker;
struct mobjnum_linkedList_s* prev; //the first element must always be null
struct mobjnum_linkedList_s* next; //the last element must always be null
}
mobjnum_linkedList;
mobjnum_linkedList mobjnum_Hashtable[HT_NUMLISTS]; //dumb and idiotic, there are up to UINT8_MAX linked lists.
//Wish I could just simply make a mobj_t array with UINT64_MAX entries, LOL
void mobjnum_ht_linkedList_Wipe();
/** Initializes simple mobjnum/thinker hashtable linked list.*/
void mobjnum_ht_linkedList_Init()
{
mobjnum_ht_linkedList_Wipe();
for (uint8_t i = 0; i < HT_NUMLISTS; i++)
{
mobjnum_Hashtable[i].thinker = NULL;
mobjnum_Hashtable[i].prev = NULL;
mobjnum_Hashtable[i].next = NULL;
}
}
/** Adds a thinker's address, determines in which list to add automatically
*
* \param mobj Which mobj's memory address to add
*/
void mobjnum_ht_linkedList_AddEntry (thinker_t* thinker)
{
mobjnum_linkedList* currentEntry; // = &mobjnum_Hashtable[(UINT8)(mobj->mobjnum % HT_NUMLISTS)];
mobjnum_linkedList* next;
currentEntry = &mobjnum_Hashtable[(UINT8)(((mobj_t*)thinker)->mobjnum % HT_NUMLISTS)];
if (!currentEntry->next) // check for the first entry
{
currentEntry->thinker = thinker;
// CONS_Printf("next not exists, create\n");
currentEntry->next = malloc(sizeof(mobjnum_linkedList));
currentEntry->next->thinker = NULL;
currentEntry->next->next = NULL;
currentEntry->next->prev = currentEntry;
return;
}
for (currentEntry = &mobjnum_Hashtable[(UINT8)(((mobj_t*)thinker)->mobjnum % HT_NUMLISTS)]; currentEntry != NULL; currentEntry = next)
{
if (!currentEntry->thinker)
{
currentEntry->thinker = thinker;
break;
}
else
{
if (!currentEntry->next)
{
currentEntry->next = malloc(sizeof(mobjnum_linkedList));
currentEntry->next->thinker = thinker;
currentEntry->next->next = NULL;
currentEntry->next->prev = currentEntry;
next = NULL;
}
else
next = currentEntry->next;
}
}
}
/** Removes a thinker's address by its mobjnum, determines from which list to remove automatically
*
* \param mobjnum Which mobj's memory address to remove by itsmobjnum
*/
// void mobjnum_ht_linkedList_RemoveEntry (uint8_t mobjnumber)
// {
// mobjnum_linkedList* currentEntry = &mobjnum_Hashtable[(UINT8)(mobjnumber % HT_NUMLISTS)];
// if (currentEntry->next == &currentEntry)
// currentEntry->mobj = &mobj;
// else
// {
// currentEntry->next = malloc(sizeof(mobjnum_linkedList));
// }
// }
/** Looks for a thinker by its mobjnum.
*
* \param mobjnumber mobj's mobjnum
* \return thinker_t* if an object is found, otherwise NULL. Cast it to mobj_t* to get the mobj.
*/
thinker_t* mobjnum_ht_linkedList_Find (uint32_t mobjnumber)
{
// mobjnum_linkedList* currentEntry = &mobjnum_Hashtable[(UINT8)(mobjnumber % HT_NUMLISTS)];
mobjnum_linkedList* currentEntry; // = &mobjnum_Hashtable[(UINT8)(mobj->mobjnum % HT_NUMLISTS)];
mobjnum_linkedList* next;
if (!mobjnumber)
return NULL;
currentEntry = &mobjnum_Hashtable[(UINT8)(mobjnumber % HT_NUMLISTS)];
if (!currentEntry->next) // check for the first entry
{
if (!currentEntry->thinker)
return NULL;
if (((mobj_t *)currentEntry->thinker)->mobjnum == mobjnumber)
return currentEntry->thinker;
}
for (currentEntry = &mobjnum_Hashtable[(UINT8)(mobjnumber % HT_NUMLISTS)]; currentEntry != NULL; currentEntry = next)
{
if (!currentEntry->thinker)
return NULL;
if (((mobj_t *)currentEntry->thinker)->mobjnum == mobjnumber)
return currentEntry->thinker;
if (currentEntry->next)
next = currentEntry->next;
else
next = NULL;
}
return NULL;
}
/** Wipes the entire list by iterating everything it has*/
void mobjnum_ht_linkedList_Wipe()
{
mobjnum_linkedList* currentEntry; // = &mobjnum_Hashtable[(UINT8)(mobj->mobjnum % HT_NUMLISTS)];
mobjnum_linkedList* next;
for (UINT8 i = 0; i < HT_NUMLISTS; i++)
{
if (!mobjnum_Hashtable[i].next)
continue;
for (currentEntry = &mobjnum_Hashtable[i]; currentEntry != NULL; currentEntry = next)
{
// bye!
if (currentEntry->next)
next = currentEntry->next;
else
next = NULL;
if (currentEntry->next)
{
currentEntry->next = NULL;
}
if (currentEntry->thinker)
{
currentEntry->thinker = NULL;
}
if (currentEntry->prev) //prevents freeing the first element
{
currentEntry->prev = NULL;
free(currentEntry);
}
}
}
return;
}
// Simple hash table implemented in C.
// Hash table entry (slot may be filled or empty).
typedef struct {
const char* key; // key is NULL if this slot is empty
void* value;
} ht_entry;
// Hash table structure: create with ht_create, free with ht_destroy.
struct hashtable {
ht_entry* entries; // hash slots
size_t capacity; // size of _entries array
size_t length; // number of items in hash table
};
#define INITIAL_CAPACITY 16 // must not be zero
hashtable* hashtable_Create(void) {
// Allocate space for hash table struct.
hashtable* table = malloc(sizeof(hashtable));
if (table == NULL) {
return NULL;
}
table->length = 0;
table->capacity = INITIAL_CAPACITY;
// Allocate (zero'd) space for entry buckets.
table->entries = calloc(table->capacity, sizeof(ht_entry));
if (table->entries == NULL) {
free(table); // error, free table before we return!
return NULL;
}
return table;
}
void hashtable_Destroy(hashtable* table)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
// First free allocated keys.
for (size_t i = 0; i < table->capacity; i++)
{
if (table->entries[i].key != NULL)
{
free((void*)table->entries[i].key);
}
}
#pragma GCC diagnostic pop
// Then free entries array and table itself.
free(table->entries);
free(table);
}
#define FNV_OFFSET 14695981039346656037UL
#define FNV_PRIME 1099511628211UL
// Return 64-bit FNV-1a hash for key (NUL-terminated). See description:
// https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
static uint64_t hash_key(const char* key) {
uint64_t hash = FNV_OFFSET;
for (const char* p = key; *p; p++) {
hash ^= (uint64_t)(unsigned char)(*p);
hash *= FNV_PRIME;
}
return hash;
}
void* hashtable_Get(hashtable* table, const UINT32* key) {
// AND hash with capacity-1 to ensure it's within entries array.
uint64_t hash = hash_key((const char*)key);
size_t index = (size_t)(hash & (uint64_t)(table->capacity - 1));
// Loop till we find an empty entry.
while (table->entries[index].key != NULL) {
if (strcmp((const char*)key, table->entries[index].key) == 0) {
// Found key, return value.
return table->entries[index].value;
}
// Key wasn't in this slot, move to next (linear probing).
index++;
if (index >= table->capacity) {
// At end of entries array, wrap around.
index = 0;
}
}
return NULL;
}
// Internal function to set an entry (without expanding table).
static const char* ht_set_entry(ht_entry* entries, size_t capacity,
const char* key, void* value, size_t* plength) {
// AND hash with capacity-1 to ensure it's within entries array.
uint64_t hash = hash_key(key);
size_t index = (size_t)(hash & (uint64_t)(capacity - 1));
// Loop till we find an empty entry.
while (entries[index].key != NULL) {
if (strcmp(key, entries[index].key) == 0) {
// Found key (it already exists), update value.
entries[index].value = value;
return entries[index].key;
}
// Key wasn't in this slot, move to next (linear probing).
index++;
if (index >= capacity) {
// At end of entries array, wrap around.
index = 0;
}
}
// Didn't find key, allocate+copy if needed, then insert it.
if (plength != NULL) {
key = strdup(key);
if (key == NULL) {
return NULL;
}
(*plength)++;
}
entries[index].key = (const char*)key;
entries[index].value = value;
return key;
}
// Expand hash table to twice its current size. Return true on success,
// false if out of memory.
static boolean ht_expand(hashtable* table) {
// Allocate new entries array.
size_t new_capacity = table->capacity * 2;
if (new_capacity < table->capacity) {
return false; // overflow (capacity would be too big)
}
ht_entry* new_entries = calloc(new_capacity, sizeof(ht_entry));
if (new_entries == NULL) {
return false;
}
// Iterate entries, move all non-empty ones to new table's entries.
for (size_t i = 0; i < table->capacity; i++) {
ht_entry entry = table->entries[i];
if (entry.key != NULL) {
ht_set_entry(new_entries, new_capacity, entry.key,
entry.value, NULL);
}
}
// Free old entries array and update this table's details.
free(table->entries);
table->entries = new_entries;
table->capacity = new_capacity;
return true;
}
const char* hashtable_Set(hashtable* table, const UINT32* key, void* value) {
assert(value != NULL);
if (value == NULL) {
return NULL;
}
// If length will exceed half of current capacity, expand it.
if (table->length >= table->capacity / 2) {
if (!ht_expand(table)) {
return NULL;
}
}
// Set entry and update length.
return ht_set_entry(table->entries, table->capacity, (const char*)key, value,
&table->length);
}
size_t hashtable_Length(hashtable* table) {
return table->length;
}
/*static hashtable_iterator ht_iterator(hashtable* table)
{
hashtable_iterator it;
it._table = table;
it._index = 0;
return it;
}*/
boolean hashtable_Next(hashtable_iterator* it) {
// Loop till we've hit end of entries array.
hashtable* table = it->_table;
while (it->_index < table->capacity) {
size_t i = it->_index;
it->_index++;
if (table->entries[i].key != NULL) {
// Found next non-empty item, update iterator key and value.
ht_entry entry = table->entries[i];
it->key = (const UINT32*)entry.key;
it->value = entry.value;
return true;
}
}
return false;
}
+56
View File
@@ -0,0 +1,56 @@
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// John FrostFox (john.frostfox@gmail.com)
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file hashtable.h
/// \brief Hashtable abstraction
#include "doomdef.h"
#include "p_mobj.h"
#include "d_think.h"
// extern uint16_t hashHits;
// extern uint16_t hashMiss;
void mobjnum_ht_linkedList_Init(void);
void mobjnum_ht_linkedList_AddEntry (thinker_t* thinker);
thinker_t* mobjnum_ht_linkedList_Find (uint32_t mobjnumber);
// void mobjnum_ht_linkedList_Wipe();
// Hash table iterator: create with ht_iterator, iterate with ht_next.
typedef struct hashtable hashtable;
typedef struct {
const UINT32* key;
void* value;
hashtable* _table; // reference to hash table being iterated
size_t _index; // current index into ht._entries
} hashtable_iterator;
hashtable* hashtable_Create(void); //creates a hashtable and returns the pointer
void* hashtable_Get(hashtable* table, const UINT32* key);
// Set item with given key (NUL-terminated) to value (which must not
// be NULL). If not already present in table, key is copied to newly
// allocated memory (keys are freed automatically when ht_destroy is
// called). Return address of copied key, or NULL if out of memory.
// const char* hashtable_Set(hashtable* table, UINT32* key, void* value);
const char* hashtable_Set(hashtable* table, const UINT32* key, void* value);
size_t hashtable_Length(hashtable* table);
// Free memory allocated for hash table, including allocated keys.
void hashtable_Destroy(hashtable* table);
// Return new hash table iterator (for use with ht_next).
hashtable_iterator hashtable_Iterator(hashtable_iterator* table);
// Move iterator to next item in hash table, update iterator's key
// and value to current item, and return true. If there are no more
// items, return false. Don't call ht_set during iteration.
boolean hashtable_Next(hashtable_iterator* it);
+6
View File
@@ -2772,6 +2772,12 @@ void HU_SetCEchoFlags(INT32 flags)
void HU_DoCEcho(const char *msg)
{
if (con_muted)
{
DEBFILE(va("%s\n", msg));
return;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation" // This is fine, we set null byte later
strncpy(cechotext, msg, sizeof(cechotext));
+5
View File
@@ -21,6 +21,9 @@
#pragma interface
#endif
extern double tic_frequency;
extern double elapsed_frames;
/** \brief max quit functions
*/
#define MAX_QUIT_FUNCS 16
@@ -61,6 +64,8 @@ UINT64 I_GetPrecisePrecision(void);
*/
double I_GetFrameTime(void);
void I_SetTime(tic_t tic, int fudge, boolean useAbsoluteFudge);
/** \brief Sleeps for the given duration in milliseconds. Depending on the
operating system's scheduler, the calling thread may give up its
time slice for a longer duration. The implementation should give a
+28 -3
View File
@@ -35,6 +35,8 @@
#include "doomstat.h"
#include "hashtable.h"
#ifndef NOBLUAJIT
#include "d_main.h"
#include "i_system.h"
@@ -1128,7 +1130,7 @@ static UINT8 UnArchiveValue(UINT8 **p, int TABLESINDEX, boolean network)
return 3; // Don't set the field
}
LUA_PushUserdata(gL, P_FindNewPosition(READUINT32(*p)), META_MOBJ);
LUA_PushUserdata(gL, P_FindNewPosition_Hashtable(READUINT32(*p)), META_MOBJ);
break;
case ARCH_PLAYER:
LUA_PushUserdata(gL, &players[READUINT8(*p)], META_PLAYER);
@@ -1350,7 +1352,30 @@ void LUA_UnArchive(savebuffer_t *save, boolean network)
if (network == true)
{
do {
mobjnum = READUINT32(save->p); // read a mobjnum
while(mobjnum != UINT32_MAX) // repeat until end of mobjs marker.
{
th = mobjnum_ht_linkedList_Find(mobjnum);
if (th && ((mobj_t *)th)->mobjnum == mobjnum)
UnArchiveExtVars(&save->p, th, network);
else
{
for (th = thinkercap.next; th != &thinkercap; th = th->next)
{
if (th->function != (actionf_p1)P_MobjThinker)
continue;
if (((mobj_t *)th)->mobjnum != mobjnum) // find matching mobj
continue;
UnArchiveExtVars(&save->p, th, network); // apply variables
}
}
mobjnum = READUINT32(save->p); // read a mobjnum
}
/*do {
mobjnum = READUINT32(save->p); // read a mobjnum
for (th = thinkercap.next; th != &thinkercap; th = th->next)
{
@@ -1360,7 +1385,7 @@ void LUA_UnArchive(savebuffer_t *save, boolean network)
if (((mobj_t *)th)->mobjnum == mobjnum) // find matching mobj
UnArchiveExtVars(&save->p, th, network); // apply variables
}
} while(mobjnum != UINT32_MAX); // repeat until end of mobjs marker.
} while(mobjnum != UINT32_MAX); // repeat until end of mobjs marker.*/
LUA_HookNetArchive(NetUnArchive, save); // call the NetArchive hook in unarchive mode
}
+2 -1
View File
@@ -7745,12 +7745,13 @@ static void M_PlaybackFastForward(INT32 choice)
CV_SetValue(&cv_playbackspeed, cv_playbackspeed.value == 1 ? 4 : 1);
}
static void M_PlaybackAdvance(INT32 choice)
{
(void)choice;
paused = false;
TryRunTics(1);
TryRunTics(1, 0);
paused = true;
}
+2 -1
View File
@@ -262,7 +262,7 @@ void P_PrecipitationEffects(void);
void P_RemoveMobj(mobj_t *th);
void P_RemoveSavegameMobj(mobj_t *th);
void P_RemoveSavegameMobj(mobj_t *th, boolean preserveLevel);
boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state);
boolean P_SetMobjState(mobj_t *mobj, statenum_t state);
//void P_RunShields(void);
@@ -320,6 +320,7 @@ FUNCINLINE static ATTRINLINE SINT8 P_MobjFlip(const mobj_t *mobj)
fixed_t P_GetMobjGravity(mobj_t *mo);
FUNCMATH boolean P_WeaponOrPanel(mobjtype_t type);
FUNCMATH boolean P_IsProjectile(mobjtype_t type);
boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled);
+20 -2
View File
@@ -50,6 +50,8 @@ static mobj_t *shadowcap = NULL;
mobj_t *waypointcap = NULL;
mobj_t *mobjcache = NULL;
UINT32 globalmobjnum = 0; // this should never overflow, but 4 billion would be an impressive number to reach. \todo ensure this never happens
void P_InitCachedActions(void)
{
actioncachehead.prev = actioncachehead.next = &actioncachehead;
@@ -376,6 +378,12 @@ boolean P_WeaponOrPanel(mobjtype_t type)
return false;
}
boolean P_IsProjectile(mobjtype_t type)
{
return type == MT_THROWNBOUNCE || type == MT_THROWNINFINITY || type == MT_THROWNAUTOMATIC || type == MT_THROWNSCATTER
|| type == MT_THROWNEXPLOSION || type == MT_THROWNGRENADE || type == MT_REDRING;
}
//
// P_ExplodeMissile
//
@@ -9477,6 +9485,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
mobj->thinker.function = (actionf_p1)P_MobjThinker;
mobj->type = type;
mobj->info = info;
mobj->localmobjnum = globalmobjnum++;
mobj->x = x;
mobj->y = y;
@@ -10241,7 +10250,7 @@ void P_FreePrecipMobj(precipmobj_t *mobj)
}
// Clearing out stuff for savegames
void P_RemoveSavegameMobj(mobj_t *mobj)
void P_RemoveSavegameMobj(mobj_t *mobj, boolean preserveLevel)
{
thinker_t *th = (thinker_t*)mobj;
@@ -10256,7 +10265,9 @@ void P_RemoveSavegameMobj(mobj_t *mobj)
}
// stop any playing sound
S_StopSound(mobj);
if (!preserveLevel)
S_StopSound(mobj);
R_RemoveMobjInterpolator(mobj);
// just set its reference count to 0 to not trigger the assert in P_UnlinkThinker
@@ -12472,6 +12483,13 @@ mobj_t *P_SPMAngle(mobj_t *source, mobjtype_t type, angle_t angle, UINT8 allowai
th = P_SpawnMobj(x, y, z, type);
if (cv_netslingdelay.value && issimulation && (tic_t)cv_netsteadyplayers.value >= targetsimtic - simtic && source == players[consoleplayer].mo)
{
z = 0x80000000; // don't make rings appear when using netslingdelay, teleport them somewhere else
th->flags |= MF_NOTHINK;
th->sprite = SPR_NULL;
}
if (source->eflags & MFE_VERTICALFLIP)
th->flags2 |= MF2_OBJECTFLIP;
+6
View File
@@ -386,6 +386,7 @@ typedef struct mobj_s
fixed_t waterbottom; // bottom of the water FOF the mobj is in
UINT32 mobjnum; // A unique number for this mobj. Used for restoring pointers on save games.
UINT32 localmobjnum; // A unique number for this mobj stored locally for association when restoring savestates
fixed_t scale;
fixed_t old_scale; // interpolation
@@ -408,6 +409,8 @@ typedef struct mobj_s
boolean colorized; // Whether the mobj uses the rainbow colormap
boolean mirrored; // The object's rotations will be mirrored left to right, e.g., see frame AL from the right and AR from the left
boolean isculled; // Whether the mobj is being culled during simulations
tic_t slamsoundtimer; // Funni slam sound when landing
// saltyhop! hardcode edition
@@ -541,6 +544,9 @@ void P_SetScale(mobj_t *mobj, fixed_t newscale);
void P_XYMovement(mobj_t *mo);
void P_EmeraldManager(void);
extern INT32 modulothing;
extern UINT32 globalmobjnum;
#define MAXHUNTEMERALDS 64
extern mapthing_t *huntemeralds[MAXHUNTEMERALDS];
extern INT32 numhuntemeralds;
+311 -90
View File
@@ -44,6 +44,8 @@ savedata_t savedata;
#define ARCHIVEBLOCK_THINKERS 0x7F37037C
#define ARCHIVEBLOCK_SPECIALS 0x7F228378
boolean memleak = false;
// Note: This cannot be bigger
// than an UINT16
typedef enum
@@ -56,6 +58,38 @@ typedef enum
SECONDAXIS = 0x20,
} player_saveflags;
#include "hashtable.h"
// Now save the pointers, tracer and target, but at load time we must
// relink to this; the savegame contains the old position in the pointer
// field copyed in the info field temporarily, but finally we just search
// for the old position and relink to it.
mobj_t *P_FindNewPosition_Hashtable(UINT32 oldposition)
{
thinker_t *th;
mobj_t *mobj;
th = mobjnum_ht_linkedList_Find(oldposition);
if (th && ((mobj_t *)th)->mobjnum == oldposition && !(th->function == (actionf_p1)P_RemoveThinkerDelayed))
return (mobj_t *)th;
for (th = thinkercap.next; th != &thinkercap; th = th->next)
{
if (th->function != (actionf_p1)P_MobjThinker)
continue;
mobj = (mobj_t *)th;
if (mobj->mobjnum != oldposition)
continue;
return mobj;
}
CONS_Debug(DBG_GAMELOGIC, "mobj not found\n");
return NULL;
}
//
// P_ArchivePlayer
//
@@ -513,6 +547,67 @@ static void P_NetUnArchivePlayers(savebuffer_t *save, boolean reloading)
#define LD_S2BOTTEX 0x04
#define LD_S2MIDTEX 0x08
//
// P_LocalArchiveWorld
// Archives the world's state locally, regardless of differences from original state
// Faster than NetArchiveWorld
//
static void P_LocalArchiveWorld(savebuffer_t *save)
{
UINT8* put;
// paranoia something something
//ClearNetColormaps();
WRITEUINT32(save->p, ARCHIVEBLOCK_WORLD);
put = save->p;
// dump all sector memory into the data
WRITEMEM(put, sectors, numsectors * sizeof(sectors[0]));
WRITEMEM(put, lines, numlines * sizeof(lines[0]));
save->p = put;
}
static void P_LocalUnArchiveWorld(savebuffer_t *save)
{
UINT8* get = save->p;
if (READUINT32(get) != ARCHIVEBLOCK_WORLD)
{
I_Error("Bad SaveState at archive block World");
}
// preserve certain local variables
sector_t* preservedSectors = Z_Malloc(numsectors * sizeof(sector_t), PU_CACHE, NULL);
memcpy(preservedSectors, sectors, numsectors * sizeof(sector_t));
READMEM(get, sectors, numsectors * sizeof(sectors[0]));
READMEM(get, lines, numlines * sizeof(lines[0]));
for (size_t i = 0; i < numsectors; i++)
{
sectors[i].thinglist = preservedSectors[i].thinglist;
sectors[i].touching_thinglist = preservedSectors[i].touching_thinglist;
if (sectors[i].thinglist == (mobj_t*)0xDDDDDDDD)
{
continue;
}
// restore preserved local stuff (tbh I don't really know what this is lol)
sectors[i].lightlist = preservedSectors[i].lightlist;
sectors[i].numlights = preservedSectors[i].numlights;
sectors[i].attached = preservedSectors[i].attached;
sectors[i].attachedsolid = preservedSectors[i].attachedsolid;
}
Z_Free(preservedSectors);
save->p = get;
}
//
// P_NetArchiveWorld
//
@@ -1003,6 +1098,14 @@ typedef enum
tc_end
} specials_e;
typedef enum
{
ST_FLOORDATA = 1,
ST_CEILDATA = 2,
ST_LIGHTDATA = 4,
ST_FADEMAPDATA = 8
} sectortarget_e;
static inline UINT32 SaveMobjnum(const mobj_t *mobj)
{
if (mobj) return mobj->mobjnum;
@@ -1943,7 +2046,7 @@ FUNCINLINE static ATTRINLINE mobj_t *AllocMobj(void)
return mobj;
}
static void LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker)
static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker)
{
thinker_t *next;
mobj_t *mobj;
@@ -1971,7 +2074,7 @@ static void LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker)
if (mapthings[spawnpointnum].type == 1705 || mapthings[spawnpointnum].type == 1713) // NiGHTS Hoop special case
{
P_SpawnHoopsAndRings(&mapthings[spawnpointnum]);
return;
return NULL;
}
mobj = AllocMobj();
@@ -2190,7 +2293,7 @@ static void LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker)
skyboxmo[0] = mobj;
}
P_AddThinker(&mobj->thinker);
//P_AddThinker(&mobj->thinker);
if (diff2 & MD2_WAYPOINTCAP)
P_SetTarget(&waypointcap, mobj);
@@ -2198,6 +2301,8 @@ static void LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker)
mobj->info = (mobjinfo_t *)next; // temporarily, set when leave this function
R_AddMobjInterpolator(mobj);
return &mobj->thinker;
}
//
@@ -2211,7 +2316,7 @@ static void LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker)
// 2 - Ceiling Only
// 3 - Both
//
static void LoadSpecialLevelThinker(savebuffer_t *save, actionf_p1 thinker, UINT8 floorOrCeiling)
static thinker_t *LoadSpecialLevelThinker(savebuffer_t *save, actionf_p1 thinker, UINT8 floorOrCeiling)
{
levelspecthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
size_t i;
@@ -2232,7 +2337,7 @@ static void LoadSpecialLevelThinker(savebuffer_t *save, actionf_p1 thinker, UINT
ht->sector->floordata = ht;
}
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2240,7 +2345,7 @@ static void LoadSpecialLevelThinker(savebuffer_t *save, actionf_p1 thinker, UINT
//
// Loads a ceiling_t from a save game
//
static void LoadCeilingThinker(savebuffer_t *save, actionf_p1 thinker)
static thinker_t *LoadCeilingThinker(savebuffer_t *save, actionf_p1 thinker)
{
ceiling_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2261,7 +2366,7 @@ static void LoadCeilingThinker(savebuffer_t *save, actionf_p1 thinker)
ht->sourceline = READFIXED(save->p);
if (ht->sector)
ht->sector->ceilingdata = ht;
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2269,7 +2374,7 @@ static void LoadCeilingThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a floormove_t from a save game
//
static void LoadFloormoveThinker(savebuffer_t *save, actionf_p1 thinker)
static thinker_t *LoadFloormoveThinker(savebuffer_t *save, actionf_p1 thinker)
{
floormove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2285,7 +2390,7 @@ static void LoadFloormoveThinker(savebuffer_t *save, actionf_p1 thinker)
ht->delaytimer = READFIXED(save->p);
if (ht->sector)
ht->sector->floordata = ht;
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2293,7 +2398,7 @@ static void LoadFloormoveThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a lightflash_t from a save game
//
static void LoadLightflashThinker(savebuffer_t *save, actionf_p1 thinker)
static thinker_t *LoadLightflashThinker(savebuffer_t *save, actionf_p1 thinker)
{
lightflash_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2302,7 +2407,7 @@ static void LoadLightflashThinker(savebuffer_t *save, actionf_p1 thinker)
ht->minlight = READINT32(save->p);
if (ht->sector)
ht->sector->lightingdata = ht;
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2310,7 +2415,7 @@ static void LoadLightflashThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a strobe_t from a save game
//
static void LoadStrobeThinker(savebuffer_t *save, actionf_p1 thinker)
static thinker_t *LoadStrobeThinker(savebuffer_t *save, actionf_p1 thinker)
{
strobe_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2322,7 +2427,7 @@ static void LoadStrobeThinker(savebuffer_t *save, actionf_p1 thinker)
ht->brighttime = READINT32(save->p);
if (ht->sector)
ht->sector->lightingdata = ht;
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2330,7 +2435,7 @@ static void LoadStrobeThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a glow_t from a save game
//
static void LoadGlowThinker(savebuffer_t *save, actionf_p1 thinker)
static thinker_t *LoadGlowThinker(savebuffer_t *save, actionf_p1 thinker)
{
glow_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2341,7 +2446,7 @@ static void LoadGlowThinker(savebuffer_t *save, actionf_p1 thinker)
ht->speed = READINT32(save->p);
if (ht->sector)
ht->sector->lightingdata = ht;
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2349,7 +2454,7 @@ static void LoadGlowThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a fireflicker_t from a save game
//
static void LoadFireflickerThinker(savebuffer_t *save, actionf_p1 thinker)
static thinker_t *LoadFireflickerThinker(savebuffer_t *save, actionf_p1 thinker)
{
fireflicker_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2360,7 +2465,7 @@ static void LoadFireflickerThinker(savebuffer_t *save, actionf_p1 thinker)
ht->minlight = READINT32(save->p);
if (ht->sector)
ht->sector->lightingdata = ht;
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2368,7 +2473,7 @@ static void LoadFireflickerThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a elevator_t from a save game
//
static void LoadElevatorThinker(savebuffer_t *save, actionf_p1 thinker, UINT8 floorOrCeiling)
static thinker_t *LoadElevatorThinker(savebuffer_t *save, actionf_p1 thinker, UINT8 floorOrCeiling)
{
elevator_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2398,7 +2503,7 @@ static void LoadElevatorThinker(savebuffer_t *save, actionf_p1 thinker, UINT8 fl
ht->sector->floordata = ht;
}
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2406,7 +2511,7 @@ static void LoadElevatorThinker(savebuffer_t *save, actionf_p1 thinker, UINT8 fl
//
// Loads a scroll_t from a save game
//
static void LoadScrollThinker(savebuffer_t *save, actionf_p1 thinker)
static thinker_t *LoadScrollThinker(savebuffer_t *save, actionf_p1 thinker)
{
scroll_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2420,7 +2525,7 @@ static void LoadScrollThinker(savebuffer_t *save, actionf_p1 thinker)
ht->accel = READINT32(save->p);
ht->exclusive = READINT32(save->p);
ht->type = READUINT8(save->p);
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2428,7 +2533,7 @@ static void LoadScrollThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a friction_t from a save game
//
static inline void LoadFrictionThinker(savebuffer_t *save, actionf_p1 thinker)
static inline thinker_t *LoadFrictionThinker(savebuffer_t *save, actionf_p1 thinker)
{
friction_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2437,7 +2542,7 @@ static inline void LoadFrictionThinker(savebuffer_t *save, actionf_p1 thinker)
ht->affectee = READINT32(save->p);
ht->referrer = READINT32(save->p);
ht->roverfriction = READUINT8(save->p);
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2445,7 +2550,7 @@ static inline void LoadFrictionThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a pusher_t from a save game
//
static void LoadPusherThinker(savebuffer_t *save, actionf_p1 thinker)
static thinker_t *LoadPusherThinker(savebuffer_t *save, actionf_p1 thinker)
{
pusher_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2463,7 +2568,7 @@ static void LoadPusherThinker(savebuffer_t *save, actionf_p1 thinker)
ht->exclusive = READINT32(save->p);
ht->slider = READINT32(save->p);
ht->source = P_GetPushThing(ht->affectee);
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2471,7 +2576,7 @@ static void LoadPusherThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a laserthink_t from a save game
//
static inline void LoadLaserThinker(savebuffer_t *save, actionf_p1 thinker)
static inline thinker_t *LoadLaserThinker(savebuffer_t *save, actionf_p1 thinker)
{
laserthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ffloor_t *rover = NULL;
@@ -2483,7 +2588,7 @@ static inline void LoadLaserThinker(savebuffer_t *save, actionf_p1 thinker)
if (rover->secnum == (size_t)(ht->sec - sectors)
&& rover->master == ht->sourceline)
ht->ffloor = rover;
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2491,7 +2596,7 @@ static inline void LoadLaserThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a lightlevel_t from a save game
//
FUNCINLINE static ATTRINLINE void LoadLightlevelThinker(savebuffer_t *save, actionf_p1 thinker)
FUNCINLINE static ATTRINLINE thinker_t *LoadLightlevelThinker(savebuffer_t *save, actionf_p1 thinker)
{
lightlevel_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2500,7 +2605,7 @@ FUNCINLINE static ATTRINLINE void LoadLightlevelThinker(savebuffer_t *save, acti
ht->speed = READINT32(save->p);
if (ht->sector)
ht->sector->lightingdata = ht;
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2508,7 +2613,7 @@ FUNCINLINE static ATTRINLINE void LoadLightlevelThinker(savebuffer_t *save, acti
//
// Loads a executor_t from a save game
//
static inline void LoadExecutorThinker(savebuffer_t *save, actionf_p1 thinker)
static inline thinker_t *LoadExecutorThinker(savebuffer_t *save, actionf_p1 thinker)
{
executor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2516,7 +2621,7 @@ static inline void LoadExecutorThinker(savebuffer_t *save, actionf_p1 thinker)
ht->caller = LoadMobj(READUINT32(save->p));
ht->sector = LoadSector(READUINT32(save->p));
ht->timer = READINT32(save->p);
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2524,7 +2629,7 @@ static inline void LoadExecutorThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a disappear_t thinker
//
static inline void LoadDisappearThinker(savebuffer_t *save, actionf_p1 thinker)
static inline thinker_t *LoadDisappearThinker(savebuffer_t *save, actionf_p1 thinker)
{
disappear_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2535,7 +2640,7 @@ static inline void LoadDisappearThinker(savebuffer_t *save, actionf_p1 thinker)
ht->affectee = READINT32(save->p);
ht->sourceline = READINT32(save->p);
ht->exists = READINT32(save->p);
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2543,7 +2648,7 @@ static inline void LoadDisappearThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a polyrotate_t thinker
//
static inline void LoadPolyrotatetThinker(savebuffer_t *save, actionf_p1 thinker)
static inline thinker_t *LoadPolyrotatetThinker(savebuffer_t *save, actionf_p1 thinker)
{
polyrotate_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2551,7 +2656,7 @@ static inline void LoadPolyrotatetThinker(savebuffer_t *save, actionf_p1 thinker
ht->speed = READINT32(save->p);
ht->distance = READINT32(save->p);
ht->turnobjs = READUINT8(save->p);
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2559,7 +2664,7 @@ static inline void LoadPolyrotatetThinker(savebuffer_t *save, actionf_p1 thinker
//
// Loads a polymovet_t thinker
//
static void LoadPolymoveThinker(savebuffer_t *save, actionf_p1 thinker)
static thinker_t *LoadPolymoveThinker(savebuffer_t *save, actionf_p1 thinker)
{
polymove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2569,7 +2674,7 @@ static void LoadPolymoveThinker(savebuffer_t *save, actionf_p1 thinker)
ht->momy = READFIXED(save->p);
ht->distance = READINT32(save->p);
ht->angle = READANGLE(save->p);
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2577,7 +2682,7 @@ static void LoadPolymoveThinker(savebuffer_t *save, actionf_p1 thinker)
//
// Loads a polywaypoint_t thinker
//
static inline void LoadPolywaypointThinker(savebuffer_t *save, actionf_p1 thinker)
static inline thinker_t *LoadPolywaypointThinker(savebuffer_t *save, actionf_p1 thinker)
{
polywaypoint_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2593,7 +2698,7 @@ static inline void LoadPolywaypointThinker(savebuffer_t *save, actionf_p1 thinke
ht->diffx = READFIXED(save->p);
ht->diffy = READFIXED(save->p);
ht->diffz = READFIXED(save->p);
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2601,7 +2706,7 @@ static inline void LoadPolywaypointThinker(savebuffer_t *save, actionf_p1 thinke
//
// loads a polyslidedoor_t thinker
//
static inline void LoadPolyslidedoorThinker(savebuffer_t *save, actionf_p1 thinker)
static inline thinker_t *LoadPolyslidedoorThinker(savebuffer_t *save, actionf_p1 thinker)
{
polyslidedoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2618,7 +2723,7 @@ static inline void LoadPolyslidedoorThinker(savebuffer_t *save, actionf_p1 think
ht->momx = READFIXED(save->p);
ht->momy = READFIXED(save->p);
ht->closing = READUINT8(save->p);
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2626,7 +2731,7 @@ static inline void LoadPolyslidedoorThinker(savebuffer_t *save, actionf_p1 think
//
// Loads a polyswingdoor_t thinker
//
static inline void LoadPolyswingdoorThinker(savebuffer_t *save, actionf_p1 thinker)
static inline thinker_t *LoadPolyswingdoorThinker(savebuffer_t *save, actionf_p1 thinker)
{
polyswingdoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2638,7 +2743,7 @@ static inline void LoadPolyswingdoorThinker(savebuffer_t *save, actionf_p1 think
ht->initDistance = READINT32(save->p);
ht->distance = READINT32(save->p);
ht->closing = READUINT8(save->p);
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
@@ -2646,7 +2751,7 @@ static inline void LoadPolyswingdoorThinker(savebuffer_t *save, actionf_p1 think
//
// Loads a polydisplace_t thinker
//
static inline void LoadPolydisplaceThinker(savebuffer_t *save, actionf_p1 thinker)
static inline thinker_t *LoadPolydisplaceThinker(savebuffer_t *save, actionf_p1 thinker)
{
polydisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL);
ht->thinker.function = thinker;
@@ -2655,13 +2760,13 @@ static inline void LoadPolydisplaceThinker(savebuffer_t *save, actionf_p1 thinke
ht->dx = READFIXED(save->p);
ht->dy = READFIXED(save->p);
ht->oldHeights = READFIXED(save->p);
P_AddThinker(&ht->thinker);
return &ht->thinker;
}
//
// P_NetUnArchiveThinkers
//
static void P_NetUnArchiveThinkers(savebuffer_t *save)
static void P_NetUnArchiveThinkers(savebuffer_t *save, boolean preserveLevel, boolean savestate)
{
thinker_t *currentthinker;
thinker_t *next;
@@ -2680,7 +2785,7 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save)
next = currentthinker->next;
if (currentthinker->function == (actionf_p1)P_MobjThinker)
P_RemoveSavegameMobj((mobj_t *)currentthinker); // item isn't saved, don't remove it
P_RemoveSavegameMobj((mobj_t *)currentthinker, preserveLevel); // item isn't saved, don't remove it
else
{
(next->prev = currentthinker->prev)->next = next;
@@ -2694,7 +2799,8 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save)
// we don't want the removed mobjs to come back
iquetail = iquehead = 0;
P_InitThinkers();
if (!savestate)
P_InitThinkers();
// clear sector thinker pointers so they don't point to non-existant thinkers for all of eternity
for (i = 0; i < numsectors; i++)
@@ -2705,6 +2811,7 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save)
// read in saved thinkers
for (;;)
{
thinker_t* th = NULL;
tclass = READUINT8(save->p);
if (tclass == tc_end)
@@ -2714,154 +2821,161 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save)
switch (tclass)
{
case tc_mobj:
LoadMobjThinker(save, (actionf_p1)P_MobjThinker);
th = LoadMobjThinker(save, (actionf_p1)P_MobjThinker);
break;
case tc_ceiling:
LoadCeilingThinker(save, (actionf_p1)T_MoveCeiling);
th = LoadCeilingThinker(save, (actionf_p1)T_MoveCeiling);
break;
case tc_crushceiling:
LoadCeilingThinker(save, (actionf_p1)T_CrushCeiling);
th = LoadCeilingThinker(save, (actionf_p1)T_CrushCeiling);
break;
case tc_floor:
LoadFloormoveThinker(save, (actionf_p1)T_MoveFloor);
th = LoadFloormoveThinker(save, (actionf_p1)T_MoveFloor);
break;
case tc_flash:
LoadLightflashThinker(save, (actionf_p1)T_LightningFlash);
th = LoadLightflashThinker(save, (actionf_p1)T_LightningFlash);
break;
case tc_strobe:
LoadStrobeThinker(save, (actionf_p1)T_StrobeFlash);
th = LoadStrobeThinker(save, (actionf_p1)T_StrobeFlash);
break;
case tc_glow:
LoadGlowThinker(save, (actionf_p1)T_Glow);
th = LoadGlowThinker(save, (actionf_p1)T_Glow);
break;
case tc_fireflicker:
LoadFireflickerThinker(save, (actionf_p1)T_FireFlicker);
th = LoadFireflickerThinker(save, (actionf_p1)T_FireFlicker);
break;
case tc_elevator:
LoadElevatorThinker(save, (actionf_p1)T_MoveElevator, 3);
th = LoadElevatorThinker(save, (actionf_p1)T_MoveElevator, 3);
break;
case tc_continuousfalling:
LoadSpecialLevelThinker(save, (actionf_p1)T_ContinuousFalling, 3);
th = LoadSpecialLevelThinker(save, (actionf_p1)T_ContinuousFalling, 3);
break;
case tc_thwomp:
LoadSpecialLevelThinker(save, (actionf_p1)T_ThwompSector, 3);
th = LoadSpecialLevelThinker(save, (actionf_p1)T_ThwompSector, 3);
break;
case tc_noenemies:
LoadSpecialLevelThinker(save, (actionf_p1)T_NoEnemiesSector, 0);
th = LoadSpecialLevelThinker(save, (actionf_p1)T_NoEnemiesSector, 0);
break;
case tc_eachtime:
LoadSpecialLevelThinker(save, (actionf_p1)T_EachTimeThinker, 0);
th = LoadSpecialLevelThinker(save, (actionf_p1)T_EachTimeThinker, 0);
break;
case tc_raisesector:
LoadSpecialLevelThinker(save, (actionf_p1)T_RaiseSector, 0);
th = LoadSpecialLevelThinker(save, (actionf_p1)T_RaiseSector, 0);
break;
/// \todo rewrite all the code that uses an elevator_t but isn't an elevator
/// \note working on it!
case tc_camerascanner:
LoadElevatorThinker(save, (actionf_p1)T_CameraScanner, 0);
th = LoadElevatorThinker(save, (actionf_p1)T_CameraScanner, 0);
break;
case tc_bouncecheese:
LoadSpecialLevelThinker(save, (actionf_p1)T_BounceCheese, 2);
th = LoadSpecialLevelThinker(save, (actionf_p1)T_BounceCheese, 2);
break;
case tc_startcrumble:
LoadElevatorThinker(save, (actionf_p1)T_StartCrumble, 1);
th = LoadElevatorThinker(save, (actionf_p1)T_StartCrumble, 1);
break;
case tc_marioblock:
LoadSpecialLevelThinker(save, (actionf_p1)T_MarioBlock, 3);
th = LoadSpecialLevelThinker(save, (actionf_p1)T_MarioBlock, 3);
break;
case tc_marioblockchecker:
LoadSpecialLevelThinker(save, (actionf_p1)T_MarioBlockChecker, 0);
th = LoadSpecialLevelThinker(save, (actionf_p1)T_MarioBlockChecker, 0);
break;
case tc_spikesector:
LoadSpecialLevelThinker(save, (actionf_p1)T_SpikeSector, 0);
th = LoadSpecialLevelThinker(save, (actionf_p1)T_SpikeSector, 0);
break;
case tc_floatsector:
LoadSpecialLevelThinker(save, (actionf_p1)T_FloatSector, 0);
th = LoadSpecialLevelThinker(save, (actionf_p1)T_FloatSector, 0);
break;
case tc_bridgethinker:
LoadSpecialLevelThinker(save, (actionf_p1)T_BridgeThinker, 3);
th = LoadSpecialLevelThinker(save, (actionf_p1)T_BridgeThinker, 3);
break;
case tc_laserflash:
LoadLaserThinker(save, (actionf_p1)T_LaserFlash);
th = LoadLaserThinker(save, (actionf_p1)T_LaserFlash);
break;
case tc_lightfade:
LoadLightlevelThinker(save, (actionf_p1)T_LightFade);
th = LoadLightlevelThinker(save, (actionf_p1)T_LightFade);
break;
case tc_executor:
LoadExecutorThinker(save, (actionf_p1)T_ExecutorDelay);
th = LoadExecutorThinker(save, (actionf_p1)T_ExecutorDelay);
restoreNum = true;
break;
case tc_disappear:
LoadDisappearThinker(save, (actionf_p1)T_Disappear);
th = LoadDisappearThinker(save, (actionf_p1)T_Disappear);
break;
case tc_polyrotate:
LoadPolyrotatetThinker(save, (actionf_p1)T_PolyObjRotate);
th = LoadPolyrotatetThinker(save, (actionf_p1)T_PolyObjRotate);
break;
case tc_polymove:
LoadPolymoveThinker(save, (actionf_p1)T_PolyObjMove);
th = LoadPolymoveThinker(save, (actionf_p1)T_PolyObjMove);
break;
case tc_polywaypoint:
LoadPolywaypointThinker(save, (actionf_p1)T_PolyObjWaypoint);
th = LoadPolywaypointThinker(save, (actionf_p1)T_PolyObjWaypoint);
break;
case tc_polyslidedoor:
LoadPolyslidedoorThinker(save, (actionf_p1)T_PolyDoorSlide);
th = LoadPolyslidedoorThinker(save, (actionf_p1)T_PolyDoorSlide);
break;
case tc_polyswingdoor:
LoadPolyswingdoorThinker(save, (actionf_p1)T_PolyDoorSwing);
th = LoadPolyswingdoorThinker(save, (actionf_p1)T_PolyDoorSwing);
break;
case tc_polyflag:
LoadPolymoveThinker(save, (actionf_p1)T_PolyObjFlag);
th = LoadPolymoveThinker(save, (actionf_p1)T_PolyObjFlag);
break;
case tc_polydisplace:
LoadPolydisplaceThinker(save, (actionf_p1)T_PolyObjDisplace);
th = LoadPolydisplaceThinker(save, (actionf_p1)T_PolyObjDisplace);
break;
case tc_scroll:
LoadScrollThinker(save, (actionf_p1)T_Scroll);
th = LoadScrollThinker(save, (actionf_p1)T_Scroll);
break;
case tc_friction:
LoadFrictionThinker(save, (actionf_p1)T_Friction);
th = LoadFrictionThinker(save, (actionf_p1)T_Friction);
break;
case tc_pusher:
LoadPusherThinker(save, (actionf_p1)T_Pusher);
th = LoadPusherThinker(save, (actionf_p1)T_Pusher);
break;
default:
I_Error("P_UnarchiveSpecials: Unknown tclass %d in savegame", tclass);
}
if (th)
{
P_AddThinker(th);
if (savestate && th->function == (actionf_p1)P_MobjThinker && tclass == tc_mobj)
mobjnum_ht_linkedList_AddEntry(th);
}
}
CONS_Debug(DBG_NETPLAY, "%u thinkers loaded\n", numloaded);
@@ -2879,7 +2993,7 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save)
delay = (void *)currentthinker;
if ((mobjnum = (UINT32)(size_t)delay->caller))
delay->caller = P_FindNewPosition(mobjnum);
delay->caller = P_FindNewPosition_Hashtable(mobjnum);
}
}
}
@@ -2976,6 +3090,7 @@ static inline void P_UnArchivePolyObjects(savebuffer_t *save)
for (i = 0; i < numSavedPolys; ++i)
P_UnArchivePolyObj(save, &PolyObjects[i]);
}
//
// P_FinishMobjs
//
@@ -3289,10 +3404,11 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending)
WRITEUINT8(save->p, 0x2e);
}
FUNCINLINE static ATTRINLINE boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading)
FUNCINLINE static ATTRINLINE boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading, boolean savestate)
{
UINT32 pig;
INT32 i;
INT16 oldMap = gamemap;
if (READUINT32(save->p) != ARCHIVEBLOCK_MISC)
I_Error("Bad $$$.sav at archive block Misc");
@@ -3330,12 +3446,17 @@ FUNCINLINE static ATTRINLINE boolean P_NetUnArchiveMisc(savebuffer_t *save, bool
encoremode = (boolean)READUINT8(save->p);
if (!P_SetupLevel(true, reloading))
if (!savestate && !P_SetupLevel(true, reloading))
{
CONS_Alert(CONS_ERROR, M_GetText("Can't load the level!\n"));
return false;
}
if (savestate && (!reloading || (gamemap != oldMap)))
{
return false;
}
// get the time
leveltime = READUINT32(save->p);
totalrings = READUINT32(save->p);
@@ -3481,11 +3602,15 @@ boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride)
return true;
}
boolean P_LoadNetGame(savebuffer_t *save, boolean reloading)
boolean P_LoadNetGame(savebuffer_t *save, boolean reloading, boolean preserveLevel)
{
mobjnum_ht_linkedList_Init(); //clean up hashtables to avoid lua stuff using them
//this is temporary and will be rewritten to use in vanilla code
//once rollback netcode will be stable
CV_LoadNetVars(&save->p);
if (!P_NetUnArchiveMisc(save, reloading))
if (!P_NetUnArchiveMisc(save, reloading, false))
return false;
P_NetUnArchivePlayers(save, reloading);
@@ -3494,7 +3619,7 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading)
{
P_NetUnArchiveWorld(save);
P_UnArchivePolyObjects(save);
P_NetUnArchiveThinkers(save);
P_NetUnArchiveThinkers(save, preserveLevel, false);
P_NetUnArchiveSpecials(save);
P_RelinkPointers();
P_FinishMobjs();
@@ -3513,3 +3638,99 @@ boolean P_LoadNetGame(savebuffer_t *save, boolean reloading)
return READUINT8(save->p) == 0x1d;
}
// P_SaveGameState is a within-level-only mechanism for saving the game state. It must not be used cross level. Used for simulation backtracking.
// It uses a mixture of existing NetArchive functions and faster LocalArchive functions to do the job
void P_SaveGameState(savestate_t* savestate, savebuffer_t *save)
{
mobj_t* mobj;
thinker_t* th;
UINT32 i = 1; // don't start from 0, it'd be confused with a blank pointer otherwise
if (savestate->buffer == NULL)
{
savestate->buffer = Z_Malloc(15 * 1024 * 1024, PU_STATIC, NULL); // fifteen? megabytes?
}
save->p = savestate->buffer;
WRITEINT16(save->p, gamemap);
WRITEUINT32(save->p, globalmobjnum);
CV_SaveNetVars(&save->p, false);
P_NetArchiveMisc(save, true);
// assign mobj nums for pointer relinking
for (th = thinkercap.next; th != &thinkercap; th = th->next)
{
if (th->function != (actionf_p1)P_MobjThinker)
continue;
mobj = (mobj_t *)th;
if (mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER)
continue;
mobj->mobjnum = i++;
}
P_NetArchivePlayers(save, true);
if (gamestate == GS_LEVEL)
{
P_LocalArchiveWorld(save);
P_ArchivePolyObjects(save);
P_NetArchiveThinkers(save);
P_NetArchiveSpecials(save);
}
//LUA_Archive(save, true);
}
// P_LoadGameState is a within-level-only mechanism for loading the game state. It must not be used cross level. Used for simulation backtracking.
// It uses a mixture of existing NetUnArchive functions and faster LocalUnArchive functions to do the job
boolean P_LoadGameState(const savestate_t* savestate, savebuffer_t *save)
{
INT16 savedGameMap;
mobjnum_ht_linkedList_Init();
if (savestate->buffer == NULL)
{
return false;
}
save->p = ((unsigned char*)savestate->buffer);
savedGameMap = READINT16(save->p);
if (savedGameMap != gamemap)
{
// savestates do not work cross-level
return false;
}
globalmobjnum = READUINT32(save->p);
CV_LoadNetVars(&save->p);
if (P_NetUnArchiveMisc(save, true, true))
{
return false;
}
P_NetUnArchivePlayers(save, true);
if (gamestate == GS_LEVEL)
{
P_LocalUnArchiveWorld(save);
P_UnArchivePolyObjects(save);
P_NetUnArchiveThinkers(save, false, true);
P_NetUnArchiveSpecials(save);
}
//LUA_UnArchive(save, true);
return true;
}
+11 -1
View File
@@ -47,8 +47,18 @@ typedef struct
void P_SaveGame(savebuffer_t *save);
void P_SaveNetGame(savebuffer_t *save, boolean resending);
boolean P_LoadGame(savebuffer_t *save, INT16 mapoverride);
boolean P_LoadNetGame(savebuffer_t *save, boolean reloading);
boolean P_LoadNetGame(savebuffer_t *save, boolean reloading, boolean preserveLevel);
mobj_t *P_FindNewPosition(UINT32 oldposition);
typedef struct
{
UINT8 *buffer;
} savestate_t;
void P_SaveGameState(savestate_t* savestate, savebuffer_t *save);
boolean P_LoadGameState(const savestate_t* savestate, savebuffer_t *save);
mobj_t *P_FindNewPosition_Hashtable(UINT32 oldposition);
#endif
+9
View File
@@ -2806,6 +2806,14 @@ boolean P_SetupLevel(boolean fromnetsave, boolean reloadinggamestate)
CON_Drawer(); // let the user know what we are going to do
I_FinishUpdate(); // page flip or blit buffer
// Invalidate simulation save states
if (fromnetsave)
{
// CONS_Alert(CONS_WARNING, "Flushing savestates due to netsave loading\n");
DEBFILE("Flushing savestates due to netsave loading\n");
InvalidateSavestates();
}
// Initialize sector node list.
P_Initsecnode();
@@ -2920,6 +2928,7 @@ boolean P_SetupLevel(boolean fromnetsave, boolean reloadinggamestate)
Patch_FreeTag(PU_PATCH_LOWPRIORITY);
//Patch_FreeTag(PU_PATCH_ROTATED); // we keep those ty!
Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1);
InvalidateSavestates();
#if defined (WALLSPLATS) || defined (FLOORSPLATS)
// clear the splats from previous level
+4
View File
@@ -457,6 +457,10 @@ static inline void P_RunThinkers(void)
#ifdef PARANOIA
I_Assert(currentthinker->function != NULL);
#endif
if (currentthinker->function == (actionf_p1)P_MobjThinker && issimulation && ((mobj_t*)currentthinker)->isculled)
continue; // apply distance culling
currentthinker->function(currentthinker);
}
}
+6
View File
@@ -472,6 +472,12 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume)
}
}
// local player sounds play immediately during simulations, this is controlled
// in TryRunTics and RunSimulations in d_clisrv.c
//if (gamestate == GS_LEVEL) // and when we are actually playing
//if (((simtic != targetsimtic - 1 && origin == listenmobj[0]) || (origin != listenmobj[0] && issimulation)))
//return;
for (i = 0; i <= splitscreen; i++)
{
player_t *player = &players[displayplayers[i]];
+46 -2
View File
@@ -1706,9 +1706,11 @@ UINT64 I_GetPrecisePrecision(void)
static UINT32 frame_rate;
static double frame_frequency;
double frame_frequency;
static UINT64 frame_epoch;
static double elapsed_frames;
double elapsed_frames;
double tic_frequency;
static void I_InitFrameTime(const UINT64 now, const UINT32 cap)
{
@@ -1727,6 +1729,8 @@ static void I_InitFrameTime(const UINT64 now, const UINT32 cap)
frame_frequency = timer_frequency / (double)frame_rate;
}
SINT8 lastTimeFudge = 0;
double I_GetFrameTime(void)
{
const UINT64 now = SDL_GetPerformanceCounter();
@@ -1738,6 +1742,32 @@ double I_GetFrameTime(void)
I_InitFrameTime(now, cap);
}
//LXShadow's comment:
/* If the server and client are using different timer types, this will cause jutter.
*
*
I *t also messes with SRB2netplus's timer fudge, meaning that for a truly accurate timerfudge it needs to know which timer the server is using...
Fudge the timer to sync better with online games. Uses multiply-first approach (more accurate)*/
if (cv_timefudge.value != lastTimeFudge)
{
if (elapsed_frames > 0.00000000001)
elapsed_frames = now;
if (cv_timefudge.value > lastTimeFudge)
{
elapsed_frames -= 1.0; // do not allow the same tic to play twice
}
elapsed_frames = (double)(elapsed_frames + (double)cv_timefudge.value / 100);
// 100 is just to get a float number from 0 to 1 by dividing timefudge/100
// this probably allows to move the time in slight steps
// knowing that "elapsed" is a FP value, this makes sense.
// jitters happen when we are not "even" with timers within 0..1 (and also depending on latency)
lastTimeFudge = cv_timefudge.value;
}
if (frame_rate == 0)
{
// Always advance a frame.
@@ -1752,6 +1782,18 @@ double I_GetFrameTime(void)
return elapsed_frames;
}
//
// I_SetTime
// Sets the time, used to fudge timers for better network synching
//
void I_SetTime(tic_t tic, int fudge, boolean useAbsoluteFudge)
{
tic = max(tic, SDL_GetTicks());
if (useAbsoluteFudge)
elapsed_frames = elapsed_frames + ((double)fudge / 100) * tic_frequency;
}
//
// I_StartupTimer
//
@@ -1761,6 +1803,8 @@ void I_StartupTimer(void)
I_InitFrameTime(0, R_GetFramerateCap());
elapsed_frames = 0.0;
tic_frequency = timer_frequency / (double)NEWTICRATE;
}
void I_Sleep(UINT32 ms)