Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b59a54c61b | ||
|
|
42011f23b4 | ||
|
|
7e6d31627e | ||
|
|
a39ab80a77 | ||
|
|
18adf0ab32 | ||
|
|
3ebfb353e1 | ||
|
|
7769b0e0eb | ||
|
|
b549aa6f53 | ||
|
|
bd4103a2ed | ||
|
|
2e07f0be92 | ||
|
|
87eddd70af | ||
|
|
7034bc6fb3 | ||
|
|
28cfdf8c23 | ||
|
|
00f24b8841 | ||
|
|
8b706be4ba | ||
|
|
eb5d271dc4 | ||
|
|
fe107331fe | ||
|
|
51bac6418f | ||
|
|
598344fb62 | ||
|
|
c1690cc3a0 | ||
|
|
d6ea4109ec | ||
|
|
9f634046e5 | ||
|
|
50af7630e9 | ||
|
|
b30ff1db0e | ||
|
|
a18662ebce | ||
|
|
3cd6fe4e34 | ||
|
|
0b9ac05fbe |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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!
|
||||
|
||||
@@ -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
@@ -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
@@ -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 == ¤tEntry)
|
||||
// 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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user