Merge remote-tracking branch 'remotes/origin/clientserver' into clientserver

This commit is contained in:
Christoph Oelckers 2019-07-14 17:54:30 +02:00
commit 8595e4e02f
94 changed files with 4814 additions and 1796 deletions

View file

@ -697,6 +697,7 @@ file( GLOB HEADER_FILES
scripting/decorate/*.h
scripting/zscript/*.h
scripting/vm/*.h
network/*.h
sound/midisources/*.h
sound/oplsynth/*.h
sound/oplsynth/dosbox/*.h
@ -952,7 +953,13 @@ set (PCH_SOURCES
d_iwad.cpp
d_main.cpp
d_anonstats.cpp
d_net.cpp
network/net.cpp
network/netsingle.cpp
network/netserver.cpp
network/netsync.cpp
network/netclient.cpp
network/netcommand.cpp
network/i_net.cpp
d_netinfo.cpp
d_protocol.cpp
dobject.cpp
@ -967,7 +974,6 @@ set (PCH_SOURCES
gameconfigfile.cpp
gitinfo.cpp
hu_scores.cpp
i_net.cpp
m_cheat.cpp
m_joy.cpp
m_misc.cpp
@ -1580,6 +1586,7 @@ source_group("Scripting\\ZScript frontend" REGULAR_EXPRESSION "^${CMAKE_CURRENT_
source_group("Scripting\\Compiler backend" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/scripting/backend/.+")
source_group("Scripting\\VM" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/scripting/vm/.+")
source_group("Scripting" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/scripting/.+")
source_group("Network" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/network/.+")
source_group("Utility" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/utility/.+")
source_group("Utility\\Node Builder" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/utility/nodebuilder/.+")
source_group("Utility\\Math" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/utility/math/.+")

View file

@ -61,7 +61,7 @@
#include "r_sky.h"
#include "p_setup.h"
#include "cmdlib.h"
#include "d_net.h"
#include "network/net.h"
#include "v_text.h"
#include "p_lnspec.h"
#include "r_utility.h"
@ -123,8 +123,8 @@ CCMD (god)
if (CheckCheatmode ())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_GOD);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_GOD);
}
CCMD(god2)
@ -132,8 +132,8 @@ CCMD(god2)
if (CheckCheatmode())
return;
Net_WriteByte(DEM_GENERICCHEAT);
Net_WriteByte(CHT_GOD2);
network->WriteByte(DEM_GENERICCHEAT);
network->WriteByte(CHT_GOD2);
}
CCMD (iddqd)
@ -141,8 +141,8 @@ CCMD (iddqd)
if (CheckCheatmode ())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_IDDQD);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_IDDQD);
}
CCMD (buddha)
@ -150,8 +150,8 @@ CCMD (buddha)
if (CheckCheatmode())
return;
Net_WriteByte(DEM_GENERICCHEAT);
Net_WriteByte(CHT_BUDDHA);
network->WriteByte(DEM_GENERICCHEAT);
network->WriteByte(CHT_BUDDHA);
}
CCMD(buddha2)
@ -159,8 +159,8 @@ CCMD(buddha2)
if (CheckCheatmode())
return;
Net_WriteByte(DEM_GENERICCHEAT);
Net_WriteByte(CHT_BUDDHA2);
network->WriteByte(DEM_GENERICCHEAT);
network->WriteByte(CHT_BUDDHA2);
}
CCMD (notarget)
@ -168,8 +168,8 @@ CCMD (notarget)
if (CheckCheatmode ())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_NOTARGET);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_NOTARGET);
}
CCMD (fly)
@ -177,8 +177,8 @@ CCMD (fly)
if (CheckCheatmode ())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_FLY);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_FLY);
}
/*
@ -193,8 +193,8 @@ CCMD (noclip)
if (CheckCheatmode ())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_NOCLIP);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_NOCLIP);
}
CCMD (noclip2)
@ -202,8 +202,8 @@ CCMD (noclip2)
if (CheckCheatmode())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_NOCLIP2);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_NOCLIP2);
}
CCMD (powerup)
@ -211,8 +211,8 @@ CCMD (powerup)
if (CheckCheatmode ())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_POWER);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_POWER);
}
CCMD (morphme)
@ -222,13 +222,13 @@ CCMD (morphme)
if (argv.argc() == 1)
{
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_MORPH);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_MORPH);
}
else
{
Net_WriteByte (DEM_MORPHEX);
Net_WriteString (argv[1]);
network->WriteByte (DEM_MORPHEX);
network->WriteString (argv[1]);
}
}
@ -237,8 +237,8 @@ CCMD (anubis)
if (CheckCheatmode ())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_ANUBIS);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_ANUBIS);
}
// [GRB]
@ -247,8 +247,8 @@ CCMD (resurrect)
if (CheckCheatmode ())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_RESSURECT);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_RESSURECT);
}
EXTERN_CVAR (Bool, chasedemo)
@ -279,8 +279,8 @@ CCMD (chase)
if (gamestate != GS_LEVEL || (!(dmflags2 & DF2_CHASECAM) && deathmatch && CheckCheatmode ()))
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_CHASECAM);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_CHASECAM);
}
}
@ -378,14 +378,14 @@ CCMD (changemap)
{
if (argv.argc() > 2)
{
Net_WriteByte (DEM_CHANGEMAP2);
Net_WriteByte (atoi(argv[2]));
network->WriteByte (DEM_CHANGEMAP2);
network->WriteByte (atoi(argv[2]));
}
else
{
Net_WriteByte (DEM_CHANGEMAP);
network->WriteByte (DEM_CHANGEMAP);
}
Net_WriteString (mapname);
network->WriteString (mapname);
}
}
catch(CRecoverableError &error)
@ -405,12 +405,12 @@ CCMD (give)
if (CheckCheatmode () || argv.argc() < 2)
return;
Net_WriteByte (DEM_GIVECHEAT);
Net_WriteString (argv[1]);
network->WriteByte (DEM_GIVECHEAT);
network->WriteString (argv[1]);
if (argv.argc() > 2)
Net_WriteLong(atoi(argv[2]));
network->WriteLong(atoi(argv[2]));
else
Net_WriteLong(0);
network->WriteLong(0);
}
CCMD (take)
@ -418,12 +418,12 @@ CCMD (take)
if (CheckCheatmode () || argv.argc() < 2)
return;
Net_WriteByte (DEM_TAKECHEAT);
Net_WriteString (argv[1]);
network->WriteByte (DEM_TAKECHEAT);
network->WriteString (argv[1]);
if (argv.argc() > 2)
Net_WriteLong(atoi (argv[2]));
network->WriteLong(atoi (argv[2]));
else
Net_WriteLong (0);
network->WriteLong (0);
}
CCMD(setinv)
@ -431,17 +431,17 @@ CCMD(setinv)
if (CheckCheatmode() || argv.argc() < 2)
return;
Net_WriteByte(DEM_SETINV);
Net_WriteString(argv[1]);
network->WriteByte(DEM_SETINV);
network->WriteString(argv[1]);
if (argv.argc() > 2)
Net_WriteLong(atoi(argv[2]));
network->WriteLong(atoi(argv[2]));
else
Net_WriteLong(0);
network->WriteLong(0);
if (argv.argc() > 3)
Net_WriteByte(!!atoi(argv[3]));
network->WriteByte(!!atoi(argv[3]));
else
Net_WriteByte(0);
network->WriteByte(0);
}
@ -539,18 +539,18 @@ CCMD (puke)
if (script > 0)
{
Net_WriteByte (DEM_RUNSCRIPT);
Net_WriteWord (script);
network->WriteByte (DEM_RUNSCRIPT);
network->WriteWord (script);
}
else
{
Net_WriteByte (DEM_RUNSCRIPT2);
Net_WriteWord (-script);
network->WriteByte (DEM_RUNSCRIPT2);
network->WriteWord (-script);
}
Net_WriteByte (argn);
network->WriteByte (argn);
for (i = 0; i < argn; ++i)
{
Net_WriteLong (arg[i]);
network->WriteLong (arg[i]);
}
}
}
@ -583,12 +583,12 @@ CCMD (pukename)
arg[i] = atoi(argv[argstart + i]);
}
}
Net_WriteByte(DEM_RUNNAMEDSCRIPT);
Net_WriteString(argv[1]);
Net_WriteByte(argn | (always << 7));
network->WriteByte(DEM_RUNNAMEDSCRIPT);
network->WriteString(argv[1]);
network->WriteByte(argn | (always << 7));
for (i = 0; i < argn; ++i)
{
Net_WriteLong(arg[i]);
network->WriteLong(arg[i]);
}
}
}
@ -629,12 +629,12 @@ CCMD (special)
return;
}
}
Net_WriteByte(DEM_RUNSPECIAL);
Net_WriteWord(specnum);
Net_WriteByte(argc - 2);
network->WriteByte(DEM_RUNSPECIAL);
network->WriteWord(specnum);
network->WriteByte(argc - 2);
for (int i = 2; i < argc; ++i)
{
Net_WriteLong(atoi(argv[i]));
network->WriteLong(atoi(argv[i]));
}
}
}
@ -780,10 +780,10 @@ CCMD (warp)
}
else
{
Net_WriteByte (DEM_WARPCHEAT);
Net_WriteWord (atoi (argv[1]));
Net_WriteWord (atoi (argv[2]));
Net_WriteWord (argv.argc() == 3 ? ONFLOORZ/65536 : atoi (argv[3]));
network->WriteByte (DEM_WARPCHEAT);
network->WriteWord (atoi (argv[1]));
network->WriteWord (atoi (argv[2]));
network->WriteWord (argv.argc() == 3 ? ONFLOORZ/65536 : atoi (argv[3]));
}
}
@ -1083,8 +1083,8 @@ CCMD(thaw)
if (CheckCheatmode())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_CLEARFROZENPROPS);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_CLEARFROZENPROPS);
}
//-----------------------------------------------------------------------------

View file

@ -56,7 +56,7 @@
#include "doomstat.h"
#include "d_gui.h"
#include "cmdlib.h"
#include "d_net.h"
#include "network/net.h"
#include "d_event.h"
#include "d_player.h"
#include "gstrings.h"
@ -1243,7 +1243,7 @@ void C_FullConsole ()
{
if (demoplayback)
G_CheckDemoStatus ();
D_QuitNetGame ();
network->D_QuitNetGame ();
advancedemo = false;
ConsoleState = c_down;
HistPos = NULL;

View file

@ -988,7 +988,7 @@ void FBaseCVar::MarkUnsafe()
// This type of cvar is not a "real" cvar. Instead, it gets and sets
// the value of a FIntCVar, modifying it bit-by-bit. As such, it has
// no default, and is not written to the .cfg or transferred around
// the network. The "host" cvar is responsible for that.
// the network-> The "host" cvar is responsible for that.
//
FFlagCVar::FFlagCVar (const char *name, FIntCVar &realvar, uint32_t bitval)

View file

@ -49,7 +49,7 @@
#include "d_player.h"
#include "configfile.h"
#include "v_text.h"
#include "d_net.h"
#include "network/net.h"
#include "d_main.h"
#include "serializer.h"
#include "menu/menu.h"
@ -617,7 +617,7 @@ void C_DoCommand (const char *cmd, int keynum)
button->ReleaseKey (keynum);
if (button == &Button_Mlook && lookspring)
{
Net_WriteByte (DEM_CENTERVIEW);
network->WriteByte (DEM_CENTERVIEW);
}
}
return;

View file

@ -35,7 +35,7 @@
#include "v_text.h"
#include "d_gui.h"
#include "g_input.h"
#include "d_net.h"
#include "network/net.h"
#include "d_event.h"
#include "sbar.h"
#include "v_video.h"
@ -355,16 +355,16 @@ static void ShoveChatStr (const char *str, uint8_t who)
who |= 2;
}
Net_WriteByte (DEM_SAY);
Net_WriteByte (who);
network->WriteByte (DEM_SAY);
network->WriteByte (who);
if (!chat_substitution || !DoSubstitution (substBuff, str))
{
Net_WriteString(MakeUTF8(str));
network->WriteString(MakeUTF8(str));
}
else
{
Net_WriteString(MakeUTF8(substBuff));
network->WriteString(MakeUTF8(substBuff));
}
}

View file

@ -122,15 +122,13 @@ typedef enum
void D_PostEvent (const event_t* ev);
void D_RemoveNextCharEvent();
void D_Render(std::function<void()> action, bool interpolate);
void D_AddPostedEvents();
//
// GLOBAL VARIABLES
//
#define MAXEVENTS 128
extern event_t events[MAXEVENTS];
extern gameaction_t gameaction;

View file

@ -85,7 +85,7 @@
#include "teaminfo.h"
#include "hardware.h"
#include "sbarinfo.h"
#include "d_net.h"
#include "network/net.h"
#include "d_event.h"
#include "d_netinf.h"
#include "m_cheat.h"
@ -102,6 +102,7 @@
#include "i_system.h"
#include "g_cvars.h"
#include "r_data/r_vanillatrans.h"
#include "network/netsingle.h"
#include "atterm.h"
EXTERN_CVAR(Bool, hud_althud)
@ -126,9 +127,7 @@ const FIWADInfo *D_FindIWAD(TArray<FString> &wadfiles, const char *iwad, const c
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
void D_CheckNetGame ();
void D_ProcessEvents ();
void G_BuildTiccmd (ticcmd_t* cmd);
void D_DoAdvanceDemo ();
void D_AddWildFile (TArray<FString> &wadfiles, const char *pattern);
void D_LoadWadSettings ();
@ -222,9 +221,10 @@ FString StoredWarp;
bool advancedemo;
FILE *debugfile;
FILE *hashfile;
event_t events[MAXEVENTS];
int eventhead;
int eventtail;
static TArray<event_t> FrameStartInputEvents;
static event_t events[MAXEVENTS];
static int eventhead;
static int eventtail;
gamestate_t wipegamestate = GS_DEMOSCREEN; // can be -1 to force a wipe
bool PageBlank;
FTexture *Advisory;
@ -292,33 +292,46 @@ void D_ProcessEvents (void)
void D_PostEvent (const event_t *ev)
{
// Do not post duplicate consecutive EV_DeviceChange events.
if (ev->type == EV_DeviceChange && events[eventhead].type == EV_DeviceChange)
FrameStartInputEvents.Push(*ev);
}
void D_AddPostedEvents()
{
unsigned int c = FrameStartInputEvents.Size();
for (unsigned int i = 0; i < c; i++)
{
return;
const event_t *ev = &FrameStartInputEvents[i];
// Do not post duplicate consecutive EV_DeviceChange events.
if (ev->type == EV_DeviceChange && events[eventhead].type == EV_DeviceChange)
{
continue;
}
events[eventhead] = *ev;
if (ev->type == EV_Mouse && menuactive == MENU_Off && ConsoleState != c_down && ConsoleState != c_falling && !primaryLevel->localEventManager->Responder(ev) && !paused)
{
if (Button_Mlook.bDown || freelook)
{
int look = int(ev->y * m_pitch * mouse_sensitivity * 16.0);
if (invertmouse)
look = -look;
G_AddViewPitch(look, true);
events[eventhead].y = 0;
}
if (!Button_Strafe.bDown && !lookstrafe)
{
G_AddViewAngle(int(ev->x * m_yaw * mouse_sensitivity * 8.0), true);
events[eventhead].x = 0;
}
if ((events[eventhead].x | events[eventhead].y) == 0)
{
continue;
}
}
eventhead = (eventhead + 1)&(MAXEVENTS - 1);
}
events[eventhead] = *ev;
if (ev->type == EV_Mouse && menuactive == MENU_Off && ConsoleState != c_down && ConsoleState != c_falling && !primaryLevel->localEventManager->Responder(ev) && !paused)
{
if (Button_Mlook.bDown || freelook)
{
int look = int(ev->y * m_pitch * mouse_sensitivity * 16.0);
if (invertmouse)
look = -look;
G_AddViewPitch (look, true);
events[eventhead].y = 0;
}
if (!Button_Strafe.bDown && !lookstrafe)
{
G_AddViewAngle (int(ev->x * m_yaw * mouse_sensitivity * 8.0), true);
events[eventhead].x = 0;
}
if ((events[eventhead].x | events[eventhead].y) == 0)
{
return;
}
}
eventhead = (eventhead+1)&(MAXEVENTS-1);
FrameStartInputEvents.Clear();
}
//==========================================================================
@ -399,14 +412,14 @@ CUSTOM_CVAR (Int, dmflags, 0, CVAR_SERVERINFO | CVAR_NOINITCALL)
if (self & DF_NO_FREELOOK)
{
Net_WriteByte (DEM_CENTERVIEW);
network->WriteByte (DEM_CENTERVIEW);
}
// If nofov is set, force everybody to the arbitrator's FOV.
if ((self & DF_NO_FOV) && consoleplayer == Net_Arbitrator)
{
float fov;
Net_WriteByte (DEM_FOV);
network->WriteByte (DEM_FOV);
// If the game is started with DF_NO_FOV set, the arbitrator's
// DesiredFOV will not be set when this callback is run, so
@ -416,7 +429,7 @@ CUSTOM_CVAR (Int, dmflags, 0, CVAR_SERVERINFO | CVAR_NOINITCALL)
{
fov = 90;
}
Net_WriteFloat (fov);
network->WriteFloat (fov);
}
}
@ -898,9 +911,6 @@ void D_Display ()
if (!wipe || NoWipe < 0 || wipe_type == wipe_None)
{
if (wipe != nullptr) delete wipe;
wipe = nullptr;
NetUpdate (); // send out any new accumulation
// normal update
// draw ZScript UI stuff
C_DrawConsole (); // draw console
@ -922,7 +932,6 @@ void D_Display ()
wiper->SetTextures(wipe, wipend);
wipestart = I_msTime();
NetUpdate(); // send out any new accumulation
do
{
@ -938,7 +947,6 @@ void D_Display ()
C_DrawConsole (); // console and
M_Drawer (); // menu are drawn even on top of wipes
screen->End2DAndUpdate ();
NetUpdate (); // [RH] not sure this is needed anymore
} while (!done);
delete wiper;
I_FreezeTime(false);
@ -959,10 +967,10 @@ void D_ErrorCleanup ()
{
savegamerestore = false;
primaryLevel->BotInfo.RemoveAllBots (primaryLevel, true);
D_QuitNetGame ();
network->D_QuitNetGame ();
if (demorecording || demoplayback)
G_CheckDemoStatus ();
Net_ClearBuffers ();
network->Net_ClearBuffers ();
G_NewInit ();
M_ClearMenus ();
singletics = false;
@ -986,10 +994,106 @@ void D_ErrorCleanup ()
//
//==========================================================================
void D_DoomLoop ()
class GameTime
{
int lasttic = 0;
public:
void Update()
{
LastTic = CurrentTic;
I_SetFrameTime();
CurrentTic = I_GetTime();
}
int TicsElapsed() const
{
return CurrentTic - LastTic;
}
int BaseGameTic() const
{
return LastTic;
}
private:
int LastTic = 0;
int CurrentTic = 0;
} gametime;
class GameInput
{
public:
void Update()
{
// Not sure why the joystick can't be updated every frame..
bool updateJoystick = gametime.TicsElapsed() > 0;
if (updateJoystick)
{
I_StartFrame(); // To do: rename this silly function to I_UpdateJoystick
}
// Grab input events at the beginning of the frame.
// This ensures the mouse movement matches I_GetTimeFrac precisely.
I_StartTic(); // To do: rename this to I_ProcessWindowMessages
}
void BeforeDisplayUpdate()
{
// Apply the events we recorded in I_StartTic as the events for the next frame.
D_AddPostedEvents();
}
} input;
ticcmd_t G_BuildTiccmd();
class PlaySim
{
public:
void Update()
{
int tics = gametime.TicsElapsed();
if (tics == 0)
return;
P_UnPredictPlayer();
D_ProcessEvents();
for (int i = 0; i < tics; i++)
{
network->SetCurrentTic(gametime.BaseGameTic() + i);
network->WriteLocalInput(G_BuildTiccmd());
if (advancedemo)
D_DoAdvanceDemo();
C_Ticker();
M_Ticker();
G_Ticker();
network->EndCurrentTic();
}
P_PredictPlayer(&players[consoleplayer]);
S_UpdateSounds(players[consoleplayer].camera); // move positional sounds
}
} playsim;
class GameDisplay
{
public:
void Update()
{
// Render frame and present
D_Display();
}
} display;
void D_DoomLoop()
{
// Clamp the timer to TICRATE until the playloop has been entered.
r_NoInterpolate = true;
Page.SetInvalid();
@ -998,56 +1102,36 @@ void D_DoomLoop ()
vid_cursor.Callback();
for (;;)
while (true)
{
try
{
// frame syncronous IO operations
if (gametic > lasttic)
{
lasttic = gametic;
I_StartFrame ();
}
I_SetFrameTime();
gametime.Update();
if (netconnect)
netconnect->Update();
network->Update();
input.Update();
playsim.Update();
input.BeforeDisplayUpdate();
display.Update();
GC::CheckGC();
// process one or more tics
if (singletics)
{
I_StartTic ();
D_ProcessEvents ();
G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]);
if (advancedemo)
D_DoAdvanceDemo ();
C_Ticker ();
M_Ticker ();
G_Ticker ();
// [RH] Use the consoleplayer's camera to update sounds
S_UpdateSounds (players[consoleplayer].camera); // move positional sounds
gametic++;
maketic++;
GC::CheckGC ();
Net_NewMakeTic ();
}
else
{
TryRunTics (); // will run at least one tic
}
// Update display, next frame, with current state.
I_StartTic ();
D_Display ();
if (wantToRestart)
{
P_UnPredictPlayer();
wantToRestart = false;
return;
}
}
catch (CRecoverableError &error)
{
if (error.GetMessage ())
if (error.GetMessage())
{
Printf (PRINT_BOLD, "\n%s\n", error.GetMessage());
Printf(PRINT_BOLD, "\n%s\n", error.GetMessage());
}
D_ErrorCleanup ();
D_ErrorCleanup();
}
catch (CVMAbortException &error)
{
@ -2325,6 +2409,8 @@ void D_DoomMain (void)
D_DoomInit();
network.reset(new NetSinglePlayer());
extern void D_ConfirmSendStats();
D_ConfirmSendStats();
@ -2603,9 +2689,7 @@ void D_DoomMain (void)
if (!restart)
{
if (!batchrun) Printf ("D_CheckNetGame: Checking network game status.\n");
StartScreen->LoadingStatus ("Checking network game status.", 0x3f);
D_CheckNetGame ();
D_SetupUserInfo();
}
// [SP] Force vanilla transparency auto-detection to re-detect our game lumps now
@ -2620,7 +2704,7 @@ void D_DoomMain (void)
// [RH] Run any saved commands from the command line or autoexec.cfg now.
gamestate = GS_FULLCONSOLE;
Net_NewMakeTic ();
network->Startup();
C_RunDelayedCommands();
gamestate = GS_STARTUP;
@ -2703,7 +2787,7 @@ void D_DoomMain (void)
G_BeginRecording(NULL);
}
atterm(D_QuitNetGame); // killough
atterm([] { network->D_QuitNetGame(); }); // killough
}
}
}
@ -2876,7 +2960,7 @@ void FStartupScreen::NetInit(char const *,int) {}
void FStartupScreen::NetProgress(int) {}
void FStartupScreen::NetMessage(char const *,...) {}
void FStartupScreen::NetDone(void) {}
bool FStartupScreen::NetLoop(bool (*)(void *),void *) { return false; }
bool FStartupScreen::NetLoop(std::function<bool()> callback) { return false; }
DEFINE_FIELD_X(InputEventData, event_t, type)
DEFINE_FIELD_X(InputEventData, event_t, subtype)

View file

@ -1,186 +0,0 @@
//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
// DESCRIPTION:
// Networking stuff.
//
//-----------------------------------------------------------------------------
#ifndef __D_NET__
#define __D_NET__
#include "doomtype.h"
#include "doomdef.h"
#include "d_protocol.h"
//
// Network play related stuff.
// There is a data struct that stores network
// communication related stuff, and another
// one that defines the actual packets to
// be transmitted.
//
#define DOOMCOM_ID 0x12345678l
#define MAXNETNODES 8 // max computers in a game
#define BACKUPTICS 36 // number of tics to remember
#define MAXTICDUP 5
#define LOCALCMDTICS (BACKUPTICS*MAXTICDUP)
#ifdef DJGPP
// The DOS drivers provide a pretty skimpy buffer.
// Probably not enough.
#define MAX_MSGLEN (BACKUPTICS*10)
#else
#define MAX_MSGLEN 14000
#endif
#define CMD_SEND 1
#define CMD_GET 2
//
// Network packet data.
//
struct doomcom_t
{
uint32_t id; // should be DOOMCOM_ID
int16_t intnum; // DOOM executes an int to execute commands
// communication between DOOM and the driver
int16_t command; // CMD_SEND or CMD_GET
int16_t remotenode; // dest for send, set by get (-1 = no packet).
int16_t datalength; // bytes in doomdata to be sent
// info common to all nodes
int16_t numnodes; // console is always node 0.
int16_t ticdup; // 1 = no duplication, 2-5 = dup for slow nets
#ifdef DJGPP
int16_t pad[5]; // keep things aligned for DOS drivers
#endif
// info specific to this node
int16_t consoleplayer;
int16_t numplayers;
#ifdef DJGPP
int16_t angleoffset; // does not work, but needed to preserve
int16_t drone; // alignment for DOS drivers
#endif
// packet data to be sent
uint8_t data[MAX_MSGLEN];
};
class FDynamicBuffer
{
public:
FDynamicBuffer ();
~FDynamicBuffer ();
void SetData (const uint8_t *data, int len);
uint8_t *GetData (int *len = NULL);
private:
uint8_t *m_Data;
int m_Len, m_BufferLen;
};
extern FDynamicBuffer NetSpecs[MAXPLAYERS][BACKUPTICS];
// Create any new ticcmds and broadcast to other players.
void NetUpdate (void);
// Broadcasts special packets to other players
// to notify of game exit
void D_QuitNetGame (void);
//? how many ticks to run?
void TryRunTics (void);
//Use for checking to see if the netgame has stalled
void Net_CheckLastReceived(int);
// [RH] Functions for making and using special "ticcmds"
void Net_NewMakeTic ();
void Net_WriteByte (uint8_t);
void Net_WriteWord (short);
void Net_WriteLong (int);
void Net_WriteFloat (float);
void Net_WriteString (const char *);
void Net_WriteBytes (const uint8_t *, int len);
void Net_DoCommand (int type, uint8_t **stream, int player);
void Net_SkipCommand (int type, uint8_t **stream);
void Net_ClearBuffers ();
// Netgame stuff (buffers and pointers, i.e. indices).
// This is the interface to the packet driver, a separate program
// in DOS, but just an abstraction here.
extern doomcom_t doomcom;
extern struct ticcmd_t localcmds[LOCALCMDTICS];
extern int maketic;
extern int nettics[MAXNETNODES];
extern int netdelay[MAXNETNODES][BACKUPTICS];
extern int nodeforplayer[MAXPLAYERS];
extern ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS];
extern int ticdup;
// [RH]
// New generic packet structure:
//
// Header:
// One byte with following flags.
// One byte with starttic
// One byte with master's maketic (master -> slave only!)
// If NCMD_RETRANSMIT set, one byte with retransmitfrom
// If NCMD_XTICS set, one byte with number of tics (minus 3, so theoretically up to 258 tics in one packet)
// If NCMD_QUITTERS, one byte with number of players followed by one byte with each player's consolenum
// If NCMD_MULTI, one byte with number of players followed by one byte with each player's consolenum
// - The first player's consolenum is not included in this list, because it always matches the sender
//
// For each tic:
// Two bytes with consistancy check, followed by tic data
//
// Setup packets are different, and are described just before D_ArbitrateNetStart().
#define NCMD_EXIT 0x80
#define NCMD_RETRANSMIT 0x40
#define NCMD_SETUP 0x20
#define NCMD_MULTI 0x10 // multiple players in this packet
#define NCMD_QUITTERS 0x08 // one or more players just quit (packet server only)
#define NCMD_COMPRESSED 0x04 // remainder of packet is compressed
#define NCMD_XTICS 0x03 // packet contains >2 tics
#define NCMD_2TICS 0x02 // packet contains 2 tics
#define NCMD_1TICS 0x01 // packet contains 1 tic
#define NCMD_0TICS 0x00 // packet contains 0 tics
#endif

View file

@ -40,7 +40,7 @@
#include "doomdef.h"
#include "doomstat.h"
#include "d_netinf.h"
#include "d_net.h"
#include "network/net.h"
#include "d_player.h"
#include "c_dispatch.h"
#include "r_state.h"
@ -544,8 +544,8 @@ void D_UserInfoChanged (FBaseCVar *cvar)
mysnprintf (foo, countof(foo), "\\%s\\%s", cvar->GetName(), escaped_val.GetChars());
Net_WriteByte (DEM_UINFCHANGED);
Net_WriteString (foo);
network->WriteByte (DEM_UINFCHANGED);
network->WriteString (foo);
}
static const char *SetServerVar (char *name, ECVarType type, uint8_t **stream, bool singlebit)
@ -628,15 +628,15 @@ void D_SendServerInfoChange (const FBaseCVar *cvar, UCVarValue value, ECVarType
namelen = strlen (cvar->GetName ());
Net_WriteByte (DEM_SINFCHANGED);
Net_WriteByte ((uint8_t)(namelen | (type << 6)));
Net_WriteBytes ((uint8_t *)cvar->GetName (), (int)namelen);
network->WriteByte (DEM_SINFCHANGED);
network->WriteByte ((uint8_t)(namelen | (type << 6)));
network->WriteBytes ((uint8_t *)cvar->GetName (), (int)namelen);
switch (type)
{
case CVAR_Bool: Net_WriteByte (value.Bool); break;
case CVAR_Int: Net_WriteLong (value.Int); break;
case CVAR_Float: Net_WriteFloat (value.Float); break;
case CVAR_String: Net_WriteString (value.String); break;
case CVAR_Bool: network->WriteByte (value.Bool); break;
case CVAR_Int: network->WriteLong (value.Int); break;
case CVAR_Float: network->WriteFloat (value.Float); break;
case CVAR_String: network->WriteString (value.String); break;
default: break; // Silence GCC
}
}
@ -647,10 +647,10 @@ void D_SendServerFlagChange (const FBaseCVar *cvar, int bitnum, bool set)
namelen = (int)strlen (cvar->GetName ());
Net_WriteByte (DEM_SINFCHANGEDXOR);
Net_WriteByte ((uint8_t)namelen);
Net_WriteBytes ((uint8_t *)cvar->GetName (), namelen);
Net_WriteByte (uint8_t(bitnum | (set << 5)));
network->WriteByte (DEM_SINFCHANGEDXOR);
network->WriteByte ((uint8_t)namelen);
network->WriteBytes ((uint8_t *)cvar->GetName (), namelen);
network->WriteByte (uint8_t(bitnum | (set << 5)));
}
void D_DoServerInfoChange (uint8_t **stream, bool singlebit)

View file

@ -34,7 +34,7 @@
#include "d_protocol.h"
#include "d_net.h"
#include "network/net.h"
#include "doomstat.h"
#include "cmdlib.h"
#include "serializer.h"
@ -290,7 +290,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, ticcmd_t &cmd, ticcmd_
{
if (arc.BeginObject(key))
{
arc("consistency", cmd.consistancy)
arc("consistency", cmd.consistency)
("ucmd", cmd.ucmd)
.EndObject();
}
@ -360,7 +360,7 @@ int SkipTicCmd (uint8_t **stream, int count)
{
bool moreticdata = true;
flow += 2; // Skip consistancy marker
flow += 2; // Skip consistency marker
while (moreticdata)
{
uint8_t type = *flow++;
@ -407,68 +407,6 @@ int SkipTicCmd (uint8_t **stream, int count)
return skip;
}
extern short consistancy[MAXPLAYERS][BACKUPTICS];
void ReadTicCmd (uint8_t **stream, int player, int tic)
{
int type;
uint8_t *start;
ticcmd_t *tcmd;
int ticmod = tic % BACKUPTICS;
tcmd = &netcmds[player][ticmod];
tcmd->consistancy = ReadWord (stream);
start = *stream;
while ((type = ReadByte (stream)) != DEM_USERCMD && type != DEM_EMPTYUSERCMD)
Net_SkipCommand (type, stream);
NetSpecs[player][ticmod].SetData (start, int(*stream - start - 1));
if (type == DEM_USERCMD)
{
UnpackUserCmd (&tcmd->ucmd,
tic ? &netcmds[player][(tic-1)%BACKUPTICS].ucmd : NULL, stream);
}
else
{
if (tic)
{
memcpy (&tcmd->ucmd, &netcmds[player][(tic-1)%BACKUPTICS].ucmd, sizeof(tcmd->ucmd));
}
else
{
memset (&tcmd->ucmd, 0, sizeof(tcmd->ucmd));
}
}
if (player==consoleplayer&&tic>BACKUPTICS)
assert(consistancy[player][ticmod] == tcmd->consistancy);
}
void RunNetSpecs (int player, int buf)
{
uint8_t *stream;
int len;
if (gametic % ticdup == 0)
{
stream = NetSpecs[player][buf].GetData (&len);
if (stream)
{
uint8_t *end = stream + len;
while (stream < end)
{
int type = ReadByte (&stream);
Net_DoCommand (type, &stream, player);
}
if (!demorecording)
NetSpecs[player][buf].SetData (NULL, 0);
}
}
}
uint8_t *lenspot;
// Write the header of an IFF chunk and leave space

View file

@ -63,13 +63,13 @@ struct zdemoheader_s {
struct usercmd_t
{
uint32_t buttons;
short pitch; // up/down
short yaw; // left/right
short roll; // "tilt"
short forwardmove;
short sidemove;
short upmove;
uint32_t buttons = 0;
short pitch = 0; // up/down
short yaw = 0; // left/right
short roll = 0; // "tilt"
short forwardmove = 0;
short sidemove = 0;
short upmove = 0;
};
// When transmitted, the above message is preceded by a byte
@ -238,12 +238,10 @@ int WriteUserCmdMessage (usercmd_t *ucmd, const usercmd_t *basis, uint8_t **stre
struct ticcmd_t
{
usercmd_t ucmd;
int16_t consistancy; // checks for net game
int16_t consistency = 0; // checks for net game
};
int SkipTicCmd (uint8_t **stream, int count);
void ReadTicCmd (uint8_t **stream, int player, int tic);
void RunNetSpecs (int player, int buf);
int ReadByte (uint8_t **stream);
int ReadWord (uint8_t **stream);

View file

@ -79,6 +79,9 @@ extern bool multiplayer;
// [SP] Map dm/coop implementation - invokes fake multiplayer without bots
extern bool multiplayernext;
// clientside playsim
extern bool netclient;
// Flag: true only if started as net deathmatch.
EXTERN_CVAR (Int, deathmatch)

View file

@ -38,7 +38,7 @@
#include "gi.h"
#include "actor.h"
#include "c_dispatch.h"
#include "d_net.h"
#include "network/net.h"
#include "g_game.h"
#include "info.h"
#include "utf8.h"
@ -165,13 +165,13 @@ bool EventManager::SendNetworkEvent(FString name, int arg1, int arg2, int arg3,
if (gamestate != GS_LEVEL)
return false;
Net_WriteByte(DEM_NETEVENT);
Net_WriteString(name);
Net_WriteByte(3);
Net_WriteLong(arg1);
Net_WriteLong(arg2);
Net_WriteLong(arg3);
Net_WriteByte(manual);
network->WriteByte(DEM_NETEVENT);
network->WriteString(name);
network->WriteByte(3);
network->WriteLong(arg1);
network->WriteLong(arg2);
network->WriteLong(arg3);
network->WriteByte(manual);
return true;
}

View file

@ -37,7 +37,7 @@
#include "g_levellocals.h"
#include "a_dynlight.h"
#include "a_sharedglobal.h"
#include "d_net.h"
#include "network/net.h"
#include "p_setup.h"
#include "w_wad.h"
#include "v_text.h"
@ -127,8 +127,8 @@ CCMD (spray)
return;
}
Net_WriteByte (DEM_SPRAY);
Net_WriteString (argv[1]);
network->WriteByte (DEM_SPRAY);
network->WriteString (argv[1]);
}
//==========================================================================

View file

@ -59,7 +59,7 @@
#include "m_png.h"
#include "a_keys.h"
#include "cmdlib.h"
#include "d_net.h"
#include "network/net.h"
#include "d_event.h"
#include "p_acs.h"
#include "p_effect.h"
@ -79,6 +79,7 @@
#include "g_hub.h"
#include "g_levellocals.h"
#include "events.h"
#include "gameconfigfile.h"
static FRandom pr_dmspawn ("DMSpawn");
@ -86,7 +87,7 @@ static FRandom pr_pspawn ("PlayerSpawn");
bool G_CheckDemoStatus (void);
void G_ReadDemoTiccmd (ticcmd_t *cmd, int player);
void G_WriteDemoTiccmd (ticcmd_t *cmd, int player, int buf);
void G_WriteDemoTiccmd (ticcmd_t *cmd, int player);
void G_PlayerReborn (int player);
void G_DoNewGame (void);
@ -152,6 +153,8 @@ bool viewactive;
bool netgame; // only true if packets are broadcast
bool multiplayer;
bool multiplayernext = false; // [SP] Map coop/dm implementation
bool netclient; // clientside playsim
bool netserver = false; // used to enforce 'netplay = true'
player_t players[MAXPLAYERS];
bool playeringame[MAXPLAYERS];
@ -176,9 +179,6 @@ uint8_t* zdembodyend; // end of ZDEM BODY chunk
bool singledemo; // quit after playing a demo from cmdline
bool precache = true; // if true, load all graphics at start
short consistancy[MAXPLAYERS][BACKUPTICS];
#define MAXPLMOVE (forwardmove[1])
@ -310,12 +310,12 @@ CCMD (slot)
CCMD (centerview)
{
Net_WriteByte (DEM_CENTERVIEW);
network->WriteByte (DEM_CENTERVIEW);
}
CCMD(crouch)
{
Net_WriteByte(DEM_CROUCH);
network->WriteByte(DEM_CROUCH);
}
CCMD (land)
@ -525,7 +525,7 @@ static inline int joyint(double val)
// or reads it from the demo buffer.
// If recording a demo, write it out
//
void G_BuildTiccmd (ticcmd_t *cmd)
ticcmd_t G_BuildTiccmd ()
{
int strafe;
int speed;
@ -533,12 +533,9 @@ void G_BuildTiccmd (ticcmd_t *cmd)
int side;
int fly;
ticcmd_t *base;
ticcmd_t cmd;
base = I_BaseTiccmd (); // empty, or external driver
*cmd = *base;
cmd->consistancy = consistancy[consoleplayer][(maketic/ticdup)%BACKUPTICS];
cmd.consistency = network->GetConsoleConsistency();
strafe = Button_Strafe.bDown;
speed = Button_Speed.bDown ^ (int)cl_run;
@ -549,7 +546,7 @@ void G_BuildTiccmd (ticcmd_t *cmd)
// and not the joystick, since we treat the joystick as
// the analog device it is.
if (Button_Left.bDown || Button_Right.bDown)
turnheld += ticdup;
turnheld += network->ticdup;
else
turnheld = 0;
@ -613,32 +610,32 @@ void G_BuildTiccmd (ticcmd_t *cmd)
side -= sidemove[speed];
// buttons
if (Button_Attack.bDown) cmd->ucmd.buttons |= BT_ATTACK;
if (Button_AltAttack.bDown) cmd->ucmd.buttons |= BT_ALTATTACK;
if (Button_Use.bDown) cmd->ucmd.buttons |= BT_USE;
if (Button_Jump.bDown) cmd->ucmd.buttons |= BT_JUMP;
if (Button_Crouch.bDown) cmd->ucmd.buttons |= BT_CROUCH;
if (Button_Zoom.bDown) cmd->ucmd.buttons |= BT_ZOOM;
if (Button_Reload.bDown) cmd->ucmd.buttons |= BT_RELOAD;
if (Button_Attack.bDown) cmd.ucmd.buttons |= BT_ATTACK;
if (Button_AltAttack.bDown) cmd.ucmd.buttons |= BT_ALTATTACK;
if (Button_Use.bDown) cmd.ucmd.buttons |= BT_USE;
if (Button_Jump.bDown) cmd.ucmd.buttons |= BT_JUMP;
if (Button_Crouch.bDown) cmd.ucmd.buttons |= BT_CROUCH;
if (Button_Zoom.bDown) cmd.ucmd.buttons |= BT_ZOOM;
if (Button_Reload.bDown) cmd.ucmd.buttons |= BT_RELOAD;
if (Button_User1.bDown) cmd->ucmd.buttons |= BT_USER1;
if (Button_User2.bDown) cmd->ucmd.buttons |= BT_USER2;
if (Button_User3.bDown) cmd->ucmd.buttons |= BT_USER3;
if (Button_User4.bDown) cmd->ucmd.buttons |= BT_USER4;
if (Button_User1.bDown) cmd.ucmd.buttons |= BT_USER1;
if (Button_User2.bDown) cmd.ucmd.buttons |= BT_USER2;
if (Button_User3.bDown) cmd.ucmd.buttons |= BT_USER3;
if (Button_User4.bDown) cmd.ucmd.buttons |= BT_USER4;
if (Button_Speed.bDown) cmd->ucmd.buttons |= BT_SPEED;
if (Button_Strafe.bDown) cmd->ucmd.buttons |= BT_STRAFE;
if (Button_MoveRight.bDown) cmd->ucmd.buttons |= BT_MOVERIGHT;
if (Button_MoveLeft.bDown) cmd->ucmd.buttons |= BT_MOVELEFT;
if (Button_LookDown.bDown) cmd->ucmd.buttons |= BT_LOOKDOWN;
if (Button_LookUp.bDown) cmd->ucmd.buttons |= BT_LOOKUP;
if (Button_Back.bDown) cmd->ucmd.buttons |= BT_BACK;
if (Button_Forward.bDown) cmd->ucmd.buttons |= BT_FORWARD;
if (Button_Right.bDown) cmd->ucmd.buttons |= BT_RIGHT;
if (Button_Left.bDown) cmd->ucmd.buttons |= BT_LEFT;
if (Button_MoveDown.bDown) cmd->ucmd.buttons |= BT_MOVEDOWN;
if (Button_MoveUp.bDown) cmd->ucmd.buttons |= BT_MOVEUP;
if (Button_ShowScores.bDown) cmd->ucmd.buttons |= BT_SHOWSCORES;
if (Button_Speed.bDown) cmd.ucmd.buttons |= BT_SPEED;
if (Button_Strafe.bDown) cmd.ucmd.buttons |= BT_STRAFE;
if (Button_MoveRight.bDown) cmd.ucmd.buttons |= BT_MOVERIGHT;
if (Button_MoveLeft.bDown) cmd.ucmd.buttons |= BT_MOVELEFT;
if (Button_LookDown.bDown) cmd.ucmd.buttons |= BT_LOOKDOWN;
if (Button_LookUp.bDown) cmd.ucmd.buttons |= BT_LOOKUP;
if (Button_Back.bDown) cmd.ucmd.buttons |= BT_BACK;
if (Button_Forward.bDown) cmd.ucmd.buttons |= BT_FORWARD;
if (Button_Right.bDown) cmd.ucmd.buttons |= BT_RIGHT;
if (Button_Left.bDown) cmd.ucmd.buttons |= BT_LEFT;
if (Button_MoveDown.bDown) cmd.ucmd.buttons |= BT_MOVEDOWN;
if (Button_MoveUp.bDown) cmd.ucmd.buttons |= BT_MOVEUP;
if (Button_ShowScores.bDown) cmd.ucmd.buttons |= BT_SHOWSCORES;
// Handle joysticks/game controllers.
float joyaxes[NUM_JOYAXIS];
@ -676,7 +673,7 @@ void G_BuildTiccmd (ticcmd_t *cmd)
forward += (int)((float)mousey * m_forward);
}
cmd->ucmd.pitch = LocalViewPitch >> 16;
cmd.ucmd.pitch = LocalViewPitch >> 16;
if (SendLand)
{
@ -699,10 +696,10 @@ void G_BuildTiccmd (ticcmd_t *cmd)
else if (side < -MAXPLMOVE)
side = -MAXPLMOVE;
cmd->ucmd.forwardmove += forward;
cmd->ucmd.sidemove += side;
cmd->ucmd.yaw = LocalViewAngle >> 16;
cmd->ucmd.upmove = fly;
cmd.ucmd.forwardmove += forward;
cmd.ucmd.sidemove += side;
cmd.ucmd.yaw = LocalViewAngle >> 16;
cmd.ucmd.upmove = fly;
LocalViewAngle = 0;
LocalViewPitch = 0;
@ -710,42 +707,44 @@ void G_BuildTiccmd (ticcmd_t *cmd)
if (sendturn180)
{
sendturn180 = false;
cmd->ucmd.buttons |= BT_TURN180;
cmd.ucmd.buttons |= BT_TURN180;
}
if (sendpause)
{
sendpause = false;
Net_WriteByte (DEM_PAUSE);
network->WriteByte (DEM_PAUSE);
}
if (sendsave)
{
sendsave = false;
Net_WriteByte (DEM_SAVEGAME);
Net_WriteString (savegamefile);
Net_WriteString (savedescription);
network->WriteByte (DEM_SAVEGAME);
network->WriteString (savegamefile);
network->WriteString (savedescription);
savegamefile = "";
}
if (SendItemUse == (const AActor *)1)
{
Net_WriteByte (DEM_INVUSEALL);
network->WriteByte (DEM_INVUSEALL);
SendItemUse = NULL;
}
else if (SendItemUse != NULL)
{
Net_WriteByte (DEM_INVUSE);
Net_WriteLong (SendItemUse->InventoryID);
network->WriteByte (DEM_INVUSE);
network->WriteLong (SendItemUse->InventoryID);
SendItemUse = NULL;
}
if (SendItemDrop != NULL)
{
Net_WriteByte (DEM_INVDROP);
Net_WriteLong (SendItemDrop->InventoryID);
Net_WriteLong(SendItemDropAmount);
network->WriteByte (DEM_INVDROP);
network->WriteLong (SendItemDrop->InventoryID);
network->WriteLong(SendItemDropAmount);
SendItemDrop = NULL;
}
cmd->ucmd.forwardmove <<= 8;
cmd->ucmd.sidemove <<= 8;
cmd.ucmd.forwardmove <<= 8;
cmd.ucmd.sidemove <<= 8;
return cmd;
}
//[Graf Zahl] This really helps if the mouse update rate can't be increased!
@ -853,7 +852,7 @@ static void ChangeSpy (int changespy)
// has done this for you, since it could desync otherwise.
if (!demoplayback)
{
Net_WriteByte(DEM_REVERTCAMERA);
network->WriteByte(DEM_REVERTCAMERA);
}
return;
}
@ -1115,10 +1114,7 @@ void G_Ticker ()
}
}
// get commands, check consistancy, and build new consistancy check
int buf = (gametic/ticdup)%BACKUPTICS;
// [RH] Include some random seeds and player stuff in the consistancy
// [RH] Include some random seeds and player stuff in the consistency
// check, not just the player's x position like BOOM.
uint32_t rngsum = FRandom::StaticSumSeeds ();
@ -1130,15 +1126,13 @@ void G_Ticker ()
if (playeringame[i])
{
ticcmd_t *cmd = &players[i].cmd;
ticcmd_t *newcmd = &netcmds[i][buf];
ticcmd_t newcmd = network->GetPlayerInput(i);
network->RunCommands(i);
if ((gametic % ticdup) == 0)
{
RunNetSpecs (i, buf);
}
if (demorecording)
{
G_WriteDemoTiccmd (newcmd, i, buf);
G_WriteDemoTiccmd (&newcmd, i);
}
players[i].oldbuttons = cmd->ucmd.buttons;
// If the user alt-tabbed away, paused gets set to -1. In this case,
@ -1150,7 +1144,7 @@ void G_Ticker ()
}
else
{
memcpy(cmd, newcmd, sizeof(ticcmd_t));
*cmd = newcmd;
}
// check for turbo cheats
@ -1160,22 +1154,21 @@ void G_Ticker ()
Printf ("%s is turbo!\n", players[i].userinfo.GetName());
}
if (netgame && players[i].Bot == NULL && !demoplayback && (gametic%ticdup) == 0)
if (netgame && players[i].Bot == NULL && !demoplayback && (gametic%network->ticdup) == 0)
{
//players[i].inconsistant = 0;
if (gametic > BACKUPTICS*ticdup && consistancy[i][buf] != cmd->consistancy)
if (network->IsInconsistent(i, cmd->consistency))
{
players[i].inconsistant = gametic - BACKUPTICS*ticdup;
players[i].inconsistant = gametic - BACKUPTICS*network->ticdup;
}
if (players[i].mo)
{
uint32_t sum = rngsum + int((players[i].mo->X() + players[i].mo->Y() + players[i].mo->Z())*257) + players[i].mo->Angles.Yaw.BAMs() + players[i].mo->Angles.Pitch.BAMs();
sum ^= players[i].health;
consistancy[i][buf] = sum;
network->SetConsistency(i, sum);
}
else
{
consistancy[i][buf] = rngsum;
network->SetConsistency(i, rngsum);
}
}
}
@ -2374,11 +2367,8 @@ CCMD (stop)
extern uint8_t *lenspot;
void G_WriteDemoTiccmd (ticcmd_t *cmd, int player, int buf)
void G_WriteDemoTiccmd (ticcmd_t *cmd, int player)
{
uint8_t *specdata;
int speclen;
if (stoprecording)
{ // use "stop" console command to end demo recording
G_CheckDemoStatus ();
@ -2390,12 +2380,7 @@ void G_WriteDemoTiccmd (ticcmd_t *cmd, int player, int buf)
}
// [RH] Write any special "ticcmds" for this player to the demo
if ((specdata = NetSpecs[player][buf].GetData (&speclen)) && gametic % ticdup == 0)
{
memcpy (demo_p, specdata, speclen);
demo_p += speclen;
NetSpecs[player][buf].SetData (NULL, 0);
}
demo_p += network->CopySpecData(player, demo_p, maxdemosize - (demo_p - demobuffer));
// [RH] Now write out a "normal" ticcmd.
WriteUserCmdMessage (&cmd->ucmd, &players[player].cmd.ucmd, &demo_p);
@ -2796,6 +2781,68 @@ void G_TimeDemo (const char* name)
gameaction = (gameaction == ga_loadgame) ? ga_loadgameplaydemo : ga_playdemo;
}
void G_InitServerNetGame(const char *mapname)
{
netgame = true;
netserver = true;
netclient = false;
multiplayer = true;
multiplayernext = true;
consoleplayer = 0;
players[consoleplayer].settings_controller = true;
playeringame[consoleplayer] = true;
GameConfig->ReadNetVars(); // [RH] Read network ServerInfo cvars
D_SetupUserInfo();
G_NetGameInitNew(mapname);
}
void G_InitClientNetGame(int player, const char* mapname)
{
netgame = true;
netserver = false;
netclient = true;
multiplayer = true;
multiplayernext = true;
consoleplayer = player;
for (int i = 0; i < MAXPLAYERS; i++)
{
playeringame[i] = false;
players[i].settings_controller = false;
}
playeringame[consoleplayer] = true;
GameConfig->ReadNetVars(); // [RH] Read network ServerInfo cvars
D_SetupUserInfo();
G_NetGameInitNew(mapname);
}
void G_EndNetGame()
{
gameaction = ga_fullconsole;
// Should we do this?
//C_RestoreCVars(); // Is this a good idea?
P_SetupWeapons_ntohton();
demoplayback = false;
netgame = false;
netclient = false;
multiplayer = false;
multiplayernext = false;
for (int i = 1; i < MAXPLAYERS; i++)
playeringame[i] = 0;
consoleplayer = 0;
players[0].camera = NULL;
if (StatusBar != NULL)
{
StatusBar->AttachToPlayer(&players[0]);
}
}
/*
===================

View file

@ -92,6 +92,10 @@ void G_PlayDemo (char* name);
void G_TimeDemo (const char* name);
bool G_CheckDemoStatus (void);
void G_InitServerNetGame(const char *mapname);
void G_InitClientNetGame(int player, const char* mapname);
void G_EndNetGame();
void G_Ticker (void);
bool G_Responder (event_t* ev);

View file

@ -67,7 +67,7 @@
#include "sbarinfo.h"
#include "p_lnspec.h"
#include "cmdlib.h"
#include "d_net.h"
#include "network/net.h"
#include "d_netinf.h"
#include "menu/menu.h"
#include "a_sharedglobal.h"
@ -108,6 +108,7 @@ EXTERN_CVAR (String, playerclass)
#define PCLS_ID MAKE_ID('p','c','L','s')
void G_VerifySkill();
void G_DoNewGame();
CUSTOM_CVAR(Bool, gl_brightfog, false, CVAR_ARCHIVE | CVAR_NOINITCALL)
{
@ -167,6 +168,9 @@ extern bool sendpause, sendsave, sendturn180, SendLand;
void *statcopy; // for statistics driver
extern bool netserver; // serverside playsim
extern bool netclient; // clientside playsim
FLevelLocals level; // info about current level
FLevelLocals *primaryLevel = &level; // level for which to display the user interface.
FLevelLocals *currentVMLevel = &level; // level which currently ticks. Used as global input to the VM and some functions called by it.
@ -201,6 +205,15 @@ void G_DeferedInitNew (FGameStartup *gs)
finishstate = FINISH_NoHub;
}
void G_NetGameInitNew(const char *mapname, int newskill)
{
d_mapname = mapname;
d_skill = newskill;
CheckWarpTransMap(d_mapname, true);
gameaction = ga_nothing;
G_DoNewGame();
}
//==========================================================================
//
//
@ -370,7 +383,7 @@ void G_NewInit ()
}
G_ClearSnapshots ();
netgame = false;
netgame = (netclient || netserver);
multiplayer = multiplayernext;
multiplayernext = false;
if (demoplayback)
@ -1007,7 +1020,7 @@ IMPLEMENT_CLASS(DAutosaver, false, false)
void DAutosaver::Tick ()
{
Net_WriteByte (DEM_CHECKAUTOSAVE);
network->WriteByte (DEM_CHECKAUTOSAVE);
Destroy ();
}

View file

@ -18,6 +18,8 @@ void G_DeferedInitNew (const char *mapname, int skill = -1);
struct FGameStartup;
void G_DeferedInitNew (FGameStartup *gs);
void G_NetGameInitNew(const char *mapname, int skill = -1);
enum
{
CHANGELEVEL_KEEPFACING = 1,

View file

@ -42,7 +42,6 @@
#include "p_local.h"
#include "doomstat.h"
#include "g_level.h"
#include "d_net.h"
#include "d_player.h"
#include "r_utility.h"
#include "cmdlib.h"

View file

@ -46,7 +46,7 @@
#include "gi.h"
#include "doomstat.h"
#include "g_level.h"
#include "d_net.h"
#include "network/net.h"
#include "d_player.h"
#include "serializer.h"
#include "r_utility.h"
@ -1380,7 +1380,7 @@ void DBaseStatusBar::DrawConsistancy () const
{
fprintf (debugfile, "%s as of tic %d (%d)\n", conbuff,
players[1-consoleplayer].inconsistant,
players[1-consoleplayer].inconsistant/ticdup);
players[1-consoleplayer].inconsistant/network->ticdup);
}
}
screen->DrawText (SmallFont, CR_GREEN,

View file

@ -43,7 +43,7 @@
#include "gameconfigfile.h"
#include "cmdlib.h"
#include "sbar.h"
#include "d_net.h"
#include "network/net.h"
#include "serializer.h"
#include "vm.h"
@ -464,15 +464,15 @@ void FWeaponSlots::SendDifferences(int playernum, const FWeaponSlots &other)
// The slots differ. Send mine.
if (playernum == consoleplayer)
{
Net_WriteByte(DEM_SETSLOT);
network->WriteByte(DEM_SETSLOT);
}
else
{
Net_WriteByte(DEM_SETSLOTPNUM);
Net_WriteByte(playernum);
network->WriteByte(DEM_SETSLOTPNUM);
network->WriteByte(playernum);
}
Net_WriteByte(i);
Net_WriteByte(Slots[i].Size());
network->WriteByte(i);
network->WriteByte(Slots[i].Size());
for (j = 0; j < Slots[i].Size(); ++j)
{
Net_WriteWeapon(Slots[i].GetWeapon(j));
@ -602,9 +602,9 @@ CCMD (setslot)
Printf ("Slot %d cleared\n", slot);
}
Net_WriteByte(DEM_SETSLOT);
Net_WriteByte(slot);
Net_WriteByte(argv.argc()-2);
network->WriteByte(DEM_SETSLOT);
network->WriteByte(slot);
network->WriteByte(argv.argc()-2);
for (int i = 2; i < argv.argc(); i++)
{
Net_WriteWeapon(PClass::FindActor(argv[i]));
@ -653,8 +653,8 @@ CCMD (addslot)
}
else
{
Net_WriteByte(DEM_ADDSLOT);
Net_WriteByte(slot);
network->WriteByte(DEM_ADDSLOT);
network->WriteByte(slot);
Net_WriteWeapon(type);
}
}
@ -729,8 +729,8 @@ CCMD (addslotdefault)
}
else
{
Net_WriteByte(DEM_ADDSLOTDEFAULT);
Net_WriteByte(slot);
network->WriteByte(DEM_ADDSLOTDEFAULT);
network->WriteByte(slot);
Net_WriteWeapon(type);
}
}
@ -929,12 +929,12 @@ void Net_WriteWeapon(PClassActor *type)
assert(index >= 0 && index <= 32767);
if (index < 128)
{
Net_WriteByte(index);
network->WriteByte(index);
}
else
{
Net_WriteByte(0x80 | index);
Net_WriteByte(index >> 7);
network->WriteByte(0x80 | index);
network->WriteByte(index >> 7);
}
}

View file

@ -38,7 +38,7 @@
#include "doomstat.h"
#include "info.h"
#include "c_dispatch.h"
#include "d_net.h"
#include "network/net.h"
#include "v_text.h"
#include "gi.h"
@ -707,17 +707,17 @@ static void SummonActor (int command, int command2, FCommandLine argv)
Printf ("Unknown actor '%s'\n", argv[1]);
return;
}
Net_WriteByte (argv.argc() > 2 ? command2 : command);
Net_WriteString (type->TypeName.GetChars());
network->WriteByte (argv.argc() > 2 ? command2 : command);
network->WriteString (type->TypeName.GetChars());
if (argv.argc () > 2)
{
Net_WriteWord (atoi (argv[2])); // angle
Net_WriteWord ((argv.argc() > 3) ? atoi(argv[3]) : 0); // TID
Net_WriteByte ((argv.argc() > 4) ? atoi(argv[4]) : 0); // special
network->WriteWord (atoi (argv[2])); // angle
network->WriteWord ((argv.argc() > 3) ? atoi(argv[3]) : 0); // TID
network->WriteByte ((argv.argc() > 4) ? atoi(argv[4]) : 0); // special
for (int i = 5; i < 10; i++)
{ // args[5]
Net_WriteLong((i < argv.argc()) ? atoi(argv[i]) : 0);
network->WriteLong((i < argv.argc()) ? atoi(argv[i]) : 0);
}
}
}

View file

@ -42,7 +42,7 @@
#include "gstrings.h"
#include "doomstat.h"
#include "c_dispatch.h"
#include "d_net.h"
#include "network/net.h"
#include "g_game.h"
#include "m_png.h"
#include "doomerrors.h"
@ -591,7 +591,7 @@ CCMD(finishgame)
return;
}
// This CCMD simulates an end-of-game action and exists to end mods that never exit their last Level->
Net_WriteByte(DEM_FINISHGAME);
network->WriteByte(DEM_FINISHGAME);
}
ADD_STAT(statistics)

View file

@ -46,7 +46,7 @@
#include "d_player.h"
#include "hu_stuff.h"
#include "gstrings.h"
#include "d_net.h"
#include "network/net.h"
#include "c_dispatch.h"
#include "g_levellocals.h"
#include "g_game.h"
@ -436,14 +436,7 @@ static void HU_DrawPlayer (player_t *player, bool highlight, int col1, int col2,
HU_DrawFontScaled(col4, y + ypadding, color, player->userinfo.GetName());
int avgdelay = 0;
for (int i = 0; i < BACKUPTICS; i++)
{
avgdelay += netdelay[nodeforplayer[(int)(player - players)]][i];
}
avgdelay /= BACKUPTICS;
mysnprintf(str, countof(str), "%d", (avgdelay * ticdup) * (1000 / TICRATE));
mysnprintf(str, countof(str), "%d", network->GetPing((int)(player - players)));
HU_DrawFontScaled(col5, y + ypadding, color, str);

File diff suppressed because it is too large Load diff

View file

@ -1,8 +0,0 @@
#ifndef __I_NET_H__
#define __I_NET_H__
// Called by D_DoomMain.
bool I_InitNetwork (void);
void I_NetCmd (void);
#endif

View file

@ -47,7 +47,7 @@
#include "c_bind.h"
#include "p_conversation.h"
#include "menu/menu.h"
#include "d_net.h"
#include "network/net.h"
#include "g_levellocals.h"
#include "utf8.h"
#include "templates.h"
@ -866,7 +866,7 @@ bool DIntermissionController::Responder (event_t *ev)
int res = mScreen->Responder(ev);
if (res == -1 && !mSentAdvance)
{
Net_WriteByte(DEM_ADVANCEINTER);
network->WriteByte(DEM_ADVANCEINTER);
mSentAdvance = true;
}
return !!res;

View file

@ -47,7 +47,7 @@
#include "sbar.h"
#include "c_dispatch.h"
#include "a_keys.h"
#include "d_net.h"
#include "network/net.h"
#include "serializer.h"
#include "r_utility.h"
#include "a_morph.h"
@ -673,6 +673,6 @@ CCMD (mdk)
return;
const char *name = argv.argc() > 1 ? argv[1] : "";
Net_WriteByte (DEM_MDK);
Net_WriteString(name);
network->WriteByte (DEM_MDK);
network->WriteString(name);
}

View file

@ -1423,6 +1423,10 @@ void MapLoader::SpawnThings (int position)
for (int i=0; i < numthings; i++)
{
// Only spawn the player mobj for the client in a client/server game
if (netclient && (MapThingsConverted[i].info->Type || MapThingsConverted[i].info->Special != SMT_Player1Start + position))
continue;
AActor *actor = Level->SpawnMapThing (i, &MapThingsConverted[i], position);
unsigned *udi = MapThingsUserDataIndex.CheckKey((unsigned)i);
if (udi != nullptr)

View file

@ -45,7 +45,7 @@
#include "p_enemy.h"
#include "gstrings.h"
#include "p_setup.h"
#include "d_net.h"
#include "network/net.h"
#include "d_event.h"
#include "doomstat.h"
#include "c_console.h"

445
src/network/i_net.cpp Normal file
View file

@ -0,0 +1,445 @@
//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
// DESCRIPTION:
// Low-level networking code. Uses BSD sockets for UDP networking.
//
//-----------------------------------------------------------------------------
/* [Petteri] Check if compiling for Win32: */
#if defined(__WINDOWS__) || defined(__NT__) || defined(_MSC_VER) || defined(_WIN32)
#ifndef __WIN32__
# define __WIN32__
#endif
#endif
/* Follow #ifdef __WIN32__ marks */
#include <stdlib.h>
#include <string.h>
/* [Petteri] Use Winsock for Win32: */
#ifdef __WIN32__
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <winsock.h>
#else
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <errno.h>
# include <unistd.h>
# include <netdb.h>
# include <sys/ioctl.h>
# ifdef __sun
# include <fcntl.h>
# endif
#endif
#include "doomtype.h"
#include "i_system.h"
#include "net.h"
#include "m_argv.h"
#include "m_crc32.h"
#include "d_player.h"
#include "st_start.h"
#include "m_misc.h"
#include "doomerrors.h"
#include "atterm.h"
#include "i_net.h"
#include "i_time.h"
// As per http://support.microsoft.com/kb/q192599/ the standard
// size for network buffers is 8k.
#define TRANSMIT_SIZE 8000
/* [Petteri] Get more portable: */
#ifndef __WIN32__
typedef int SOCKET;
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1
#define closesocket close
#define ioctlsocket ioctl
#define Sleep(x) usleep (x * 1000)
#define WSAEWOULDBLOCK EWOULDBLOCK
#define WSAECONNRESET ECONNRESET
#define WSAGetLastError() errno
#endif
#ifndef IPPORT_USERRESERVED
#define IPPORT_USERRESERVED 5000
#endif
#ifdef __WIN32__
typedef int socklen_t;
#endif
#ifdef __WIN32__
const char *neterror (void);
#else
#define neterror() strerror(errno)
#endif
class DoomComImpl : public doomcom_t
{
public:
DoomComImpl(int port);
~DoomComImpl();
void PacketSend(const NetOutputPacket &packet) override;
void PacketGet(NetInputPacket &packet) override;
int Connect(const char *name) override;
void Close(int node) override;
private:
void BuildAddress(sockaddr_in *address, const char *name);
int FindNode(const sockaddr_in *address);
SOCKET mSocket = INVALID_SOCKET;
sockaddr_in mNodeEndpoints[MAXNETNODES];
uint64_t mNodeLastUpdate[MAXNETNODES];
uint8_t mTransmitBuffer[TRANSMIT_SIZE];
};
class InitSockets
{
public:
InitSockets()
{
#ifdef __WIN32__
WSADATA wsad;
if (WSAStartup(0x0101, &wsad))
{
I_FatalError("Could not initialize Windows Sockets");
}
#endif
}
~InitSockets()
{
#ifdef __WIN32__
WSACleanup();
#endif
}
};
std::unique_ptr<doomcom_t> I_InitNetwork(int port)
{
static InitSockets initsockets;
return std::unique_ptr<doomcom_t>(new DoomComImpl(port));
}
DoomComImpl::DoomComImpl(int port)
{
memset(mNodeEndpoints, 0, sizeof(mNodeEndpoints));
memset(mNodeLastUpdate, 0, sizeof(mNodeLastUpdate));
mSocket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (mSocket == INVALID_SOCKET)
I_FatalError("can't create socket: %s", neterror());
if (port != 0)
{
sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
int v = bind(mSocket, (sockaddr *)&address, sizeof(address));
if (v == SOCKET_ERROR)
I_FatalError("BindToPort: %s", neterror());
}
#ifndef __sun
u_long trueval = 1;
ioctlsocket(mSocket, FIONBIO, &trueval);
#else
u_long trueval = 1;
fcntl(mysocket, F_SETFL, trueval | O_NONBLOCK);
#endif
}
DoomComImpl::~DoomComImpl()
{
if (mSocket != INVALID_SOCKET)
{
closesocket(mSocket);
mSocket = INVALID_SOCKET;
}
}
int DoomComImpl::Connect(const char *name)
{
sockaddr_in addr;
BuildAddress(&addr, name);
return FindNode(&addr);
}
void DoomComImpl::Close(int node)
{
mNodeLastUpdate[node] = 0;
}
int DoomComImpl::FindNode(const sockaddr_in *address)
{
int slot = -1;
for (int i = 0; i < MAXNETNODES; i++)
{
if (mNodeLastUpdate[i] != 0 && address->sin_addr.s_addr == mNodeEndpoints[i].sin_addr.s_addr && address->sin_port == mNodeEndpoints[i].sin_port)
{
slot = i;
break;
}
else if (mNodeLastUpdate[i] == 0)
{
if (slot == -1)
slot = i;
}
}
if (slot == -1)
return -1;
mNodeEndpoints[slot] = *address;
mNodeLastUpdate[slot] = I_nsTime();
return slot;
}
void DoomComImpl::PacketSend(const NetOutputPacket &packet)
{
assert(!(packet.buffer[0] & NCMD_COMPRESSED));
int packetSize = packet.stream.GetSize() + 1;
if (packetSize >= 10)
{
mTransmitBuffer[0] = packet.buffer[0] | NCMD_COMPRESSED;
uLong size = TRANSMIT_SIZE - 1;
int c = compress2(mTransmitBuffer + 1, &size, packet.buffer + 1, packetSize - 1, 9);
size += 1;
if (c == Z_OK && size < (uLong)packetSize)
{
sendto(mSocket, (char *)mTransmitBuffer, size, 0, (sockaddr *)&mNodeEndpoints[packet.node], sizeof(mNodeEndpoints[packet.node]));
return;
}
}
if (packetSize <= TRANSMIT_SIZE)
{
sendto(mSocket, (char *)packet.buffer, packetSize, 0, (sockaddr *)&mNodeEndpoints[packet.node], sizeof(mNodeEndpoints[packet.node]));
}
else
{
I_Error("NetPacket is too large to be transmitted");
}
}
void DoomComImpl::PacketGet(NetInputPacket &packet)
{
// First check if anything timed out. Treat this as a close.
uint64_t nowtime = I_nsTime();
for (int i = 0; i < MAXNETNODES; i++)
{
if (mNodeLastUpdate[i] != 0 && nowtime - mNodeLastUpdate[i] > 5'000'000'000) // 5 second timeout
{
Close(i);
packet.node = i;
packet.stream.SetBuffer(nullptr, 0);
return;
}
}
while (true)
{
sockaddr_in fromaddress;
socklen_t fromlen = sizeof(fromaddress);
int size = recvfrom(mSocket, (char*)mTransmitBuffer, TRANSMIT_SIZE, 0, (sockaddr *)&fromaddress, &fromlen);
if (size == SOCKET_ERROR)
{
int err = WSAGetLastError();
if (err == WSAECONNRESET) // The remote node aborted unexpectedly. Treat this as a close.
{
int node = FindNode(&fromaddress);
if (node == -1)
continue;
Close(node);
packet.node = node;
packet.stream.SetBuffer(nullptr, 0);
return;
}
else if (err != WSAEWOULDBLOCK)
{
I_Error("GetPacket: %s", neterror());
}
else // no packet
{
packet.node = -1;
packet.stream.SetBuffer(nullptr, 0);
return;
}
}
else if (size > 0)
{
int node = FindNode(&fromaddress);
if (node == -1)
continue;
packet.buffer[0] = mTransmitBuffer[0] & ~NCMD_COMPRESSED;
if ((mTransmitBuffer[0] & NCMD_COMPRESSED) && size > 1)
{
uLongf msgsize = MAX_MSGLEN - 1;
int err = uncompress(packet.buffer + 1, &msgsize, mTransmitBuffer + 1, size - 1);
if (err != Z_OK)
{
Printf("Net decompression failed (zlib error %s)\n", M_ZLibError(err).GetChars());
continue;
}
size = msgsize + 1;
}
else
{
memcpy(packet.buffer + 1, mTransmitBuffer + 1, size - 1);
}
packet.node = node;
packet.stream.SetBuffer(packet.buffer + 1, size - 1);
return;
}
}
}
void DoomComImpl::BuildAddress(sockaddr_in *address, const char *name)
{
hostent *hostentry; // host information entry
u_short port;
const char *portpart;
bool isnamed = false;
int curchar;
char c;
FString target;
address->sin_family = AF_INET;
if ((portpart = strchr(name, ':')))
{
target = FString(name, portpart - name);
port = atoi(portpart + 1);
if (!port)
{
Printf("Weird port: %s (using %d)\n", portpart + 1, DOOMPORT);
port = DOOMPORT;
}
}
else
{
target = name;
port = DOOMPORT;
}
address->sin_port = htons(port);
for (curchar = 0; (c = target[curchar]); curchar++)
{
if ((c < '0' || c > '9') && c != '.')
{
isnamed = true;
break;
}
}
if (!isnamed)
{
address->sin_addr.s_addr = inet_addr(target);
// Printf("Node number %d, address %s\n", numnodes, target.GetChars());
}
else
{
hostentry = gethostbyname(target);
if (!hostentry)
I_FatalError("gethostbyname: couldn't find %s\n%s", target.GetChars(), neterror());
address->sin_addr.s_addr = *(int *)hostentry->h_addr_list[0];
// Printf("Node number %d, hostname %s\n", numnodes, hostentry->h_name);
}
}
#ifdef __WIN32__
const char *neterror (void)
{
static char neterr[16];
int code;
switch (code = WSAGetLastError ()) {
case WSAEACCES: return "EACCES";
case WSAEADDRINUSE: return "EADDRINUSE";
case WSAEADDRNOTAVAIL: return "EADDRNOTAVAIL";
case WSAEAFNOSUPPORT: return "EAFNOSUPPORT";
case WSAEALREADY: return "EALREADY";
case WSAECONNABORTED: return "ECONNABORTED";
case WSAECONNREFUSED: return "ECONNREFUSED";
case WSAECONNRESET: return "ECONNRESET";
case WSAEDESTADDRREQ: return "EDESTADDRREQ";
case WSAEFAULT: return "EFAULT";
case WSAEHOSTDOWN: return "EHOSTDOWN";
case WSAEHOSTUNREACH: return "EHOSTUNREACH";
case WSAEINPROGRESS: return "EINPROGRESS";
case WSAEINTR: return "EINTR";
case WSAEINVAL: return "EINVAL";
case WSAEISCONN: return "EISCONN";
case WSAEMFILE: return "EMFILE";
case WSAEMSGSIZE: return "EMSGSIZE";
case WSAENETDOWN: return "ENETDOWN";
case WSAENETRESET: return "ENETRESET";
case WSAENETUNREACH: return "ENETUNREACH";
case WSAENOBUFS: return "ENOBUFS";
case WSAENOPROTOOPT: return "ENOPROTOOPT";
case WSAENOTCONN: return "ENOTCONN";
case WSAENOTSOCK: return "ENOTSOCK";
case WSAEOPNOTSUPP: return "EOPNOTSUPP";
case WSAEPFNOSUPPORT: return "EPFNOSUPPORT";
case WSAEPROCLIM: return "EPROCLIM";
case WSAEPROTONOSUPPORT: return "EPROTONOSUPPORT";
case WSAEPROTOTYPE: return "EPROTOTYPE";
case WSAESHUTDOWN: return "ESHUTDOWN";
case WSAESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT";
case WSAETIMEDOUT: return "ETIMEDOUT";
case WSAEWOULDBLOCK: return "EWOULDBLOCK";
case WSAHOST_NOT_FOUND: return "HOST_NOT_FOUND";
case WSANOTINITIALISED: return "NOTINITIALISED";
case WSANO_DATA: return "NO_DATA";
case WSANO_RECOVERY: return "NO_RECOVERY";
case WSASYSNOTREADY: return "SYSNOTREADY";
case WSATRY_AGAIN: return "TRY_AGAIN";
case WSAVERNOTSUPPORTED: return "VERNOTSUPPORTED";
case WSAEDISCON: return "EDISCON";
default:
mysnprintf (neterr, countof(neterr), "%d", code);
return neterr;
}
}
#endif

54
src/network/i_net.h Normal file
View file

@ -0,0 +1,54 @@
#pragma once
#include <memory>
#include "netcommand.h"
#define MAX_MSGLEN 14000
#define DOOMPORT 5029
class NetOutputPacket
{
public:
NetOutputPacket(int node) : node(node), stream(buffer + 1, MAX_MSGLEN - 1) { buffer[0] = 0; }
int node = 0;
ByteOutputStream stream;
private:
uint8_t buffer[MAX_MSGLEN];
NetOutputPacket(const NetOutputPacket &) = delete;
NetOutputPacket &operator=(const NetOutputPacket &) = delete;
friend class DoomComImpl;
};
class NetInputPacket
{
public:
NetInputPacket() = default;
int node = -1; // -1 = no packet available
ByteInputStream stream;
private:
uint8_t buffer[MAX_MSGLEN];
NetInputPacket(const NetInputPacket &) = delete;
NetInputPacket &operator=(const NetInputPacket &) = delete;
friend class DoomComImpl;
};
// Network packet data.
struct doomcom_t
{
virtual ~doomcom_t() { }
virtual void PacketSend(const NetOutputPacket &packet) = 0;
virtual void PacketGet(NetInputPacket &packet) = 0;
virtual int Connect(const char *name) = 0;
virtual void Close(int node) = 0;
};
std::unique_ptr<doomcom_t> I_InitNetwork(int port);

1278
src/network/net.cpp Normal file

File diff suppressed because it is too large Load diff

171
src/network/net.h Normal file
View file

@ -0,0 +1,171 @@
//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
// Copyright 2018 Magnus Norddahl
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
#pragma once
#include "doomtype.h"
#include "doomdef.h"
#include "d_protocol.h"
#include "i_net.h"
#include <memory>
#define MAXNETNODES 8 // max computers in a game
#define BACKUPTICS 36 // number of tics to remember
#define MAXTICDUP 5
#define LOCALCMDTICS (BACKUPTICS*MAXTICDUP)
class AActor;
class FDynamicBuffer
{
public:
FDynamicBuffer();
FDynamicBuffer(const FDynamicBuffer &src);
~FDynamicBuffer();
FDynamicBuffer &operator=(const FDynamicBuffer &src);
void Clear() { SetData(nullptr, 0); }
void SetData(const uint8_t *data, int len);
void AppendData(const uint8_t *data, int len);
uint8_t *GetData() { return m_Len ? m_Data : nullptr; }
const uint8_t *GetData() const { return m_Len ? m_Data : nullptr; }
int GetSize() const { return m_Len; }
private:
uint8_t *m_Data = nullptr;
int m_Len = 0;
int m_BufferLen = 0;
};
class Network
{
public:
virtual ~Network() { }
// Check for incoming packets
virtual void Update() = 0;
// Set current tic time
virtual void SetCurrentTic(int localtic) = 0;
// Send any pending outgoing data
virtual void EndCurrentTic() = 0;
// Retrieve data about the current tic
virtual int GetSendTick() const = 0;
virtual ticcmd_t GetPlayerInput(int player) const = 0;
virtual ticcmd_t GetSentInput(int tic) const = 0;
// Run network commands for the current tic
virtual void RunCommands(int player) = 0;
// Write outgoing data for the current tic
virtual void WriteLocalInput(ticcmd_t cmd) = 0;
virtual void WriteBotInput(int player, const ticcmd_t &cmd) = 0;
virtual void WriteBytes(const uint8_t *block, int len) = 0;
void WriteByte(uint8_t it);
void WriteWord(short it);
void WriteLong(int it);
void WriteFloat(float it);
void WriteString(const char *it);
// Statistics
virtual int GetPing(int player) const = 0;
virtual int GetServerPing() const = 0;
int GetHighPingThreshold() const;
// CCMDs
virtual void ListPingTimes() = 0;
virtual void Network_Controller(int playernum, bool add) = 0;
// Playsim events
virtual void ActorSpawned(AActor *actor) { }
virtual void ActorDestroyed(AActor *actor) { }
// Old init/deinit stuff
void Startup() { }
void Net_ClearBuffers() { }
void D_QuitNetGame() { }
// Demo recording
size_t CopySpecData(int player, uint8_t *dest, size_t dest_size) { return 0; }
// Obsolete; only needed for p2p
bool IsInconsistent(int player, int16_t checkvalue) const { return false; }
void SetConsistency(int player, int16_t checkvalue) { }
int16_t GetConsoleConsistency() const { return 0; }
// Should probably be removed.
int ticdup = 1;
};
extern std::unique_ptr<Network> network;
extern std::unique_ptr<Network> netconnect;
void Net_DoCommand (int type, uint8_t **stream, int player);
void Net_SkipCommand (int type, uint8_t **stream);
void Net_RunCommands (FDynamicBuffer &buffer, int player);
// Old packet format. Kept for reference. Should be removed or updated once the c/s migration is complete.
// [RH]
// New generic packet structure:
//
// Header:
// One byte with following flags.
// One byte with starttic
// One byte with master's maketic (master -> slave only!)
// If NCMD_RETRANSMIT set, one byte with retransmitfrom
// If NCMD_XTICS set, one byte with number of tics (minus 3, so theoretically up to 258 tics in one packet)
// If NCMD_QUITTERS, one byte with number of players followed by one byte with each player's consolenum
// If NCMD_MULTI, one byte with number of players followed by one byte with each player's consolenum
// - The first player's consolenum is not included in this list, because it always matches the sender
//
// For each tic:
// Two bytes with consistency check, followed by tic data
//
// Setup packets are different, and are described just before D_ArbitrateNetStart().
#define NCMD_EXIT 0x80
#define NCMD_RETRANSMIT 0x40
#define NCMD_SETUP 0x20
#define NCMD_MULTI 0x10 // multiple players in this packet
#define NCMD_QUITTERS 0x08 // one or more players just quit (packet server only)
#define NCMD_COMPRESSED 0x04 // remainder of packet is compressed
#define NCMD_XTICS 0x03 // packet contains >2 tics
#define NCMD_2TICS 0x02 // packet contains 2 tics
#define NCMD_1TICS 0x01 // packet contains 1 tic
#define NCMD_0TICS 0x00 // packet contains 0 tics
enum
{
PRE_CONNECT, // Sent from guest to host for initial connection
PRE_KEEPALIVE,
PRE_DISCONNECT, // Sent from guest that aborts the game
PRE_ALLHERE, // Sent from host to guest when everybody has connected
PRE_CONACK, // Sent from host to guest to acknowledge PRE_CONNECT receipt
PRE_ALLFULL, // Sent from host to an unwanted guest
PRE_ALLHEREACK, // Sent from guest to host to acknowledge PRE_ALLHEREACK receipt
PRE_GO // Sent from host to guest to continue game startup
};

422
src/network/netclient.cpp Normal file
View file

@ -0,0 +1,422 @@
//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
// Copyright 2018 Magnus Norddahl
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
#include <stddef.h>
#include <inttypes.h>
#include "version.h"
#include "menu/menu.h"
#include "m_random.h"
#include "i_system.h"
#include "i_video.h"
#include "i_net.h"
#include "g_game.h"
#include "doomdef.h"
#include "doomstat.h"
#include "c_console.h"
#include "d_netinf.h"
#include "netclient.h"
#include "netsingle.h"
#include "cmdlib.h"
#include "s_sound.h"
#include "m_cheat.h"
#include "p_local.h"
#include "c_dispatch.h"
#include "sbar.h"
#include "gi.h"
#include "m_misc.h"
#include "gameconfigfile.h"
#include "d_gui.h"
#include "templates.h"
#include "p_acs.h"
#include "p_trace.h"
#include "a_sharedglobal.h"
#include "st_start.h"
#include "teaminfo.h"
#include "p_conversation.h"
#include "g_level.h"
#include "d_event.h"
#include "m_argv.h"
#include "p_lnspec.h"
#include "v_video.h"
#include "p_spec.h"
#include "hardware.h"
#include "r_utility.h"
#include "a_keys.h"
#include "intermission/intermission.h"
#include "g_levellocals.h"
#include "events.h"
#include "i_time.h"
#include <cmath>
CVAR( Int, cl_showspawnnames, 0, CVAR_ARCHIVE )
NetClient::NetClient(FString server)
{
Printf("Connecting to %s..\n", server.GetChars());
mComm = I_InitNetwork(0);
mServerNode = mComm->Connect(server);
mStatus = NodeStatus::InPreGame;
NetOutputPacket packet(mServerNode);
NetCommand cmd ( NetPacketType::ConnectRequest );
cmd.addString("ZDoom Connect Request");
cmd.writeCommandToStream (packet.stream);
mComm->PacketSend(packet);
}
void NetClient::Update()
{
while (true)
{
NetInputPacket packet;
mComm->PacketGet(packet);
if (packet.node == -1)
break;
if (packet.node != mServerNode)
{
mComm->Close(packet.node);
}
else if (packet.stream.IsAtEnd())
{
OnClose();
break;
}
else
{
UpdateLastReceivedTic(packet.stream.ReadByte());
if (mStatus == NodeStatus::InPreGame)
{
ProcessCommands(packet.stream);
}
else
{
auto &ticUpdate = mTicUpdates[mLastReceivedTic % BACKUPTICS];
ticUpdate.Resize(packet.stream.BytesLeft());
packet.stream.ReadBuffer(ticUpdate.Data(), ticUpdate.Size());
}
}
if (mStatus == NodeStatus::Closed)
{
if (network.get() == this)
{
network.reset(new NetSinglePlayer());
G_EndNetGame();
}
else
{
netconnect.reset();
}
return;
}
}
}
void NetClient::SetCurrentTic(int tictime)
{
gametic = tictime;
mSendTic = gametic + 10;
if (abs(gametic + mServerTicDelta - mLastReceivedTic) > jitter)
{
mServerTicDelta = mLastReceivedTic - gametic - jitter;
}
mServerTic = MAX(gametic + mServerTicDelta, 0);
mCurrentInput[consoleplayer] = mSentInput[gametic % BACKUPTICS];
// [BB] Don't check net packets while we are supposed to load a map.
// This way the commands from the full update will not be parsed before we loaded the map.
//if ((gameaction == ga_newgame) || (gameaction == ga_newgame2))
// return;
TArray<uint8_t> &update = mTicUpdates[mServerTic % BACKUPTICS];
if (update.Size() > 0)
{
ByteInputStream stream(update.Data(), update.Size());
ProcessCommands(stream);
update.Clear();
}
}
void NetClient::EndCurrentTic()
{
mCurrentCommands = mSendCommands;
mSendCommands.Clear();
}
int NetClient::GetSendTick() const
{
return mSendTic;
}
ticcmd_t NetClient::GetPlayerInput(int player) const
{
return mCurrentInput[player];
}
ticcmd_t NetClient::GetSentInput(int tic) const
{
return mSentInput[tic % BACKUPTICS];
}
void NetClient::RunCommands(int player)
{
if (player == consoleplayer)
{
Net_RunCommands(mCurrentCommands, consoleplayer);
}
}
void NetClient::WriteLocalInput(ticcmd_t ticcmd)
{
mSentInput[(mSendTic - 1) % BACKUPTICS] = ticcmd;
if (mStatus == NodeStatus::InGame)
{
int targettic = (mSendTic + mServerTicDelta);
NetOutputPacket packet(mServerNode);
NetCommand cmd(NetPacketType::Tic);
cmd.addByte(targettic); // target gametic
cmd.addBuffer(&ticcmd.ucmd, sizeof(usercmd_t));
cmd.writeCommandToStream(packet.stream);
mComm->PacketSend(packet);
}
}
void NetClient::WriteBotInput(int player, const ticcmd_t &cmd)
{
mCurrentInput[player] = cmd;
}
void NetClient::WriteBytes(const uint8_t *block, int len)
{
mSendCommands.AppendData(block, len);
}
int NetClient::GetPing(int player) const
{
return 0;
}
int NetClient::GetServerPing() const
{
return 0;
}
void NetClient::ListPingTimes()
{
}
void NetClient::Network_Controller(int playernum, bool add)
{
}
void NetClient::ActorSpawned(AActor *actor)
{
actor->syncdata.NetID = -1;
}
void NetClient::ActorDestroyed(AActor *actor)
{
}
void NetClient::UpdateLastReceivedTic(int tic)
{
if (mLastReceivedTic != -1)
{
int delta = tic - (mLastReceivedTic & 0xff);
if (delta > 128) delta -= 256;
else if (delta < -128) delta += 256;
mLastReceivedTic += delta;
}
else
{
mLastReceivedTic = tic;
}
mLastReceivedTic = MAX(mLastReceivedTic, 0);
}
void NetClient::ProcessCommands(ByteInputStream &stream)
{
while (stream.IsAtEnd() == false)
{
NetPacketType type = (NetPacketType)stream.ReadByte();
switch (type)
{
default: OnClose(); break;
case NetPacketType::ConnectResponse: OnConnectResponse(stream); break;
case NetPacketType::Disconnect: OnDisconnect(); break;
case NetPacketType::Tic: OnTic(stream); break;
case NetPacketType::SpawnActor: OnSpawnActor(stream); break;
case NetPacketType::DestroyActor: OnDestroyActor(stream); break;
}
}
}
void NetClient::OnClose()
{
mComm->Close(mServerNode);
mServerNode = -1;
mStatus = NodeStatus::Closed;
if (network.get() == this)
{
Printf("Disconnected\n");
}
else
{
Printf("Could not connect\n");
}
}
void NetClient::OnConnectResponse(ByteInputStream &stream)
{
int version = stream.ReadByte(); // Protocol version
if (version == 1)
{
int playernum = stream.ReadByte();
if (playernum > 0 && playernum < MAXPLAYERS) // Join accepted
{
mPlayer = playernum;
mStatus = NodeStatus::InGame;
mServerTicDelta = mLastReceivedTic - gametic - jitter;
G_InitClientNetGame(mPlayer, "e1m1");
network = std::move(netconnect);
}
else // Server full
{
mComm->Close(mServerNode);
mServerNode = -1;
mStatus = NodeStatus::Closed;
Printf("Could not connect: server is full!\n");
}
}
else
{
Printf("Could not connect: version mismatch.\n");
mComm->Close(mServerNode);
mServerNode = -1;
mStatus = NodeStatus::Closed;
}
}
void NetClient::OnDisconnect()
{
mComm->Close(mServerNode);
mServerNode = -1;
mStatus = NodeStatus::Closed;
}
void NetClient::OnTic(ByteInputStream &stream)
{
DVector3 Pos, Vel;
float yaw, pitch;
Pos.X = stream.ReadFloat();
Pos.Y = stream.ReadFloat();
Pos.Z = stream.ReadFloat();
Vel.X = stream.ReadFloat();
Vel.Y = stream.ReadFloat();
Vel.Z = stream.ReadFloat();
yaw = stream.ReadFloat();
pitch = stream.ReadFloat();
if (playeringame[consoleplayer] && players[consoleplayer].mo)
{
AActor *pawn = players[consoleplayer].mo;
if ((Pos - pawn->Pos()).LengthSquared() > 10.0)
pawn->SetOrigin(Pos, false);
else
P_TryMove(pawn, Pos.XY(), true);
pawn->Vel = Vel;
pawn->Angles.Yaw = yaw;
pawn->Angles.Pitch = pitch;
}
while (true)
{
int netID = stream.ReadShort();
if (netID == -1)
break;
float x = stream.ReadFloat();
float y = stream.ReadFloat();
float z = stream.ReadFloat();
float yaw = stream.ReadFloat();
float pitch = stream.ReadFloat();
int sprite = stream.ReadShort();
uint8_t frame = stream.ReadByte();
AActor *netactor = mNetIDList.findPointerByID(netID);
if (netactor)
{
netactor->SetOrigin(x, y, z, true);
netactor->Angles.Yaw = yaw;
netactor->Angles.Pitch = pitch;
netactor->sprite = sprite;
netactor->frame = frame;
}
}
}
void NetClient::OnSpawnActor(ByteInputStream &stream)
{
const int16_t netID = stream.ReadShort();
const float x = stream.ReadFloat();
const float y = stream.ReadFloat();
const float z = stream.ReadFloat();
AActor *oldNetActor = mNetIDList.findPointerByID(netID);
// If there's already an actor with this net ID, destroy it.
if (oldNetActor)
{
oldNetActor->Destroy();
mNetIDList.freeID(netID);
}
ANetSyncActor *actor = Spawn<ANetSyncActor>(primaryLevel, DVector3(x, y, z), NO_REPLACE);
mNetIDList.useID(netID, actor);
}
void NetClient::OnDestroyActor(ByteInputStream &stream)
{
const int16_t netID = stream.ReadShort();
AActor *actor = mNetIDList.findPointerByID(netID);
actor->Destroy();
mNetIDList.freeID(netID);
}
IMPLEMENT_CLASS(ANetSyncActor, false, false)

94
src/network/netclient.h Normal file
View file

@ -0,0 +1,94 @@
//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
// Copyright 2018 Magnus Norddahl
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
#pragma once
#include "netserver.h"
#include "netcommand.h"
#include "g_shared/a_dynlight.h"
class NetClient : public Network
{
public:
NetClient(FString server);
void Update() override;
void SetCurrentTic(int tictime) override;
void EndCurrentTic() override;
int GetSendTick() const override;
ticcmd_t GetPlayerInput(int player) const override;
ticcmd_t GetSentInput(int tic) const override;
void RunCommands(int player) override;
void WriteLocalInput(ticcmd_t cmd) override;
void WriteBotInput(int player, const ticcmd_t &cmd) override;
void WriteBytes(const uint8_t *block, int len) override;
int GetPing(int player) const override;
int GetServerPing() const override;
void ListPingTimes() override;
void Network_Controller(int playernum, bool add) override;
void ActorSpawned(AActor *actor) override;
void ActorDestroyed(AActor *actor) override;
private:
void OnClose();
void OnConnectResponse(ByteInputStream &stream);
void OnDisconnect();
void OnTic(ByteInputStream &stream);
void OnSpawnActor(ByteInputStream &stream);
void OnDestroyActor(ByteInputStream &stream);
void ProcessCommands(ByteInputStream &stream);
void UpdateLastReceivedTic(int tic);
std::unique_ptr<doomcom_t> mComm;
int mServerNode = -1;
int mPlayer = -1;
NodeStatus mStatus = NodeStatus::Closed;
int mSendTic = 0;
int mServerTic = 0;
int mServerTicDelta = -1;
int mLastReceivedTic = -1;
int jitter = 2;
TArray<uint8_t> mTicUpdates[BACKUPTICS];
ticcmd_t mCurrentInput[MAXPLAYERS];
ticcmd_t mSentInput[BACKUPTICS];
FDynamicBuffer mCurrentCommands;
FDynamicBuffer mSendCommands;
IDList<AActor> mNetIDList;
};
class ANetSyncActor : public AActor
{
DECLARE_CLASS(ANetSyncActor, AActor)
public:
};

505
src/network/netcommand.cpp Normal file
View file

@ -0,0 +1,505 @@
//-----------------------------------------------------------------------------
//
// Copyright 2018 Benjamin Berkels
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
#include "d_player.h"
#include "netcommand.h"
#include "i_net.h"
extern bool netserver, netclient;
// [BB] Are we measuring outbound traffic?
static bool g_MeasuringOutboundTraffic = false;
// [BB] Number of bytes sent by NETWORK_Write* since NETWORK_StartTrafficMeasurement() was called.
static int g_OutboundBytesMeasured = 0;
//*****************************************************************************
ByteOutputStream::ByteOutputStream(int size)
{
SetBuffer(size);
}
ByteOutputStream::ByteOutputStream(void *buffer, int size)
{
SetBuffer(buffer, size);
}
void ByteOutputStream::SetBuffer(int size)
{
mBuffer = std::make_shared<DataBuffer>(size);
SetBuffer(mBuffer->data, mBuffer->size);
}
void ByteOutputStream::SetBuffer(void *buffer, int size)
{
mData = (uint8_t*)buffer;
pbStreamEnd = mData + size;
ResetPos();
}
void ByteOutputStream::ResetPos()
{
pbStream = mData;
bitBuffer = nullptr;
bitShift = -1;
}
void ByteOutputStream::AdvancePointer(const int NumBytes, const bool OutboundTraffic)
{
pbStream += NumBytes;
if (g_MeasuringOutboundTraffic && OutboundTraffic)
g_OutboundBytesMeasured += NumBytes;
}
void ByteOutputStream::WriteByte(int Byte)
{
if ((pbStream + 1) > pbStreamEnd)
{
Printf("ByteOutputStream::WriteByte: Overflow!\n");
return;
}
*pbStream = Byte;
// Advance the pointer.
AdvancePointer(1, true);
}
void ByteOutputStream::WriteShort(int Short)
{
if ((pbStream + 2) > pbStreamEnd)
{
Printf("NETWORK_WriteShort: Overflow!\n");
return;
}
pbStream[0] = Short & 0xff;
pbStream[1] = Short >> 8;
// Advance the pointer.
AdvancePointer(2, true);
}
void ByteOutputStream::WriteLong(int Long)
{
if ((pbStream + 4) > pbStreamEnd)
{
Printf("NETWORK_WriteLong: Overflow!\n");
return;
}
pbStream[0] = Long & 0xff;
pbStream[1] = (Long >> 8) & 0xff;
pbStream[2] = (Long >> 16) & 0xff;
pbStream[3] = (Long >> 24);
// Advance the pointer.
AdvancePointer(4, true);
}
void ByteOutputStream::WriteFloat(float Float)
{
union
{
float f;
int l;
} dat;
dat.f = Float;
WriteLong(dat.l);
}
void ByteOutputStream::WriteString(const char *pszString)
{
if ((pszString) && (strlen(pszString) > MAX_NETWORK_STRING))
{
Printf("ByteOutputStream::WriteString: String exceeds %d characters!\n", MAX_NETWORK_STRING);
return;
}
if (pszString)
WriteBuffer("", 1);
else
WriteBuffer(pszString, (int)(strlen(pszString)) + 1);
}
void ByteOutputStream::WriteBuffer(const void *pvBuffer, int nLength)
{
if ((pbStream + nLength) > pbStreamEnd)
{
Printf("NETWORK_WriteLBuffer: Overflow!\n");
return;
}
memcpy(pbStream, pvBuffer, nLength);
// Advance the pointer.
AdvancePointer(nLength, true);
}
void ByteOutputStream::WriteBit(bool bit)
{
// Add a bit to this byte
EnsureBitSpace(1, true);
if (bit)
*bitBuffer |= 1 << bitShift;
++bitShift;
}
void ByteOutputStream::WriteVariable(int value)
{
int length;
// Determine how long we need to send this value
if (value == 0)
length = 0; // 0 - don't bother sending it at all
else if ((value <= 0xFF) && (value >= 0))
length = 1; // Can be sent as a byte
else if ((value <= 0x7FFF) && (value >= -0x8000))
length = 2; // Can be sent as a short
else
length = 3; // Must be sent as a long
// Write this length as two bits
WriteBit(!!(length & 1));
WriteBit(!!(length & 2));
// Depending on the required length, write the value.
switch (length)
{
case 1: WriteByte(value); break;
case 2: WriteShort(value); break;
case 3: WriteLong(value); break;
}
}
void ByteOutputStream::WriteShortByte(int value, int bits)
{
if ((bits < 1) || (bits > 8))
{
Printf("NETWORK_WriteShortByte: bits must be within range [1..8], got %d.\n", bits);
return;
}
EnsureBitSpace(bits, true);
value &= ((1 << bits) - 1); // Form a mask from the bits and trim our value using it.
value <<= bitShift; // Shift the value to its proper position.
*bitBuffer |= value; // Add it to the byte.
bitShift += bits; // Bump the shift value accordingly.
}
void ByteOutputStream::EnsureBitSpace(int bits, bool writing)
{
if ((bitBuffer == nullptr) || (bitShift < 0) || (bitShift + bits > 8))
{
// Not enough bits left in our current byte, we need a new one.
WriteByte(0);
bitBuffer = pbStream - 1;
bitShift = 0;
}
}
//*****************************************************************************
ByteInputStream::ByteInputStream(const void *buffer, int size)
{
SetBuffer(buffer, size);
}
void ByteInputStream::SetBuffer(const void *buffer, int size)
{
mData = (uint8_t*)buffer;
pbStream = mData;
pbStreamEnd = pbStream + size;
bitBuffer = nullptr;
bitShift = -1;
}
int ByteInputStream::ReadByte()
{
int Byte = -1;
if ((pbStream + 1) <= pbStreamEnd)
Byte = *pbStream;
// Advance the pointer.
pbStream += 1;
return (Byte);
}
int ByteInputStream::ReadShort()
{
int Short = -1;
if ((pbStream + 2) <= pbStreamEnd)
Short = (short)((pbStream[0]) + (pbStream[1] << 8));
// Advance the pointer.
pbStream += 2;
return (Short);
}
int ByteInputStream::ReadLong()
{
int Long = -1;
if ((pbStream + 4) <= pbStreamEnd)
{
Long = ((pbStream[0]) + (pbStream[1] << 8) + (pbStream[2] << 16) + (pbStream[3] << 24));
}
// Advance the pointer.
pbStream += 4;
return (Long);
}
float ByteInputStream::ReadFloat()
{
union
{
float f;
int i;
} dat;
dat.i = ReadLong();
return (dat.f);
}
const char *ByteInputStream::ReadString()
{
int c;
static char s_szString[MAX_NETWORK_STRING];
// Read in characters until we've reached the end of the string.
unsigned int ulIdx = 0;
do
{
c = ReadByte();
if (c <= 0)
break;
// Place this character into our string.
// [BB] Even if we don't have enough space in s_szString, we have to fully
// parse the received string. Otherwise we can't continue parsing the packet.
if (ulIdx < MAX_NETWORK_STRING - 1)
s_szString[ulIdx] = static_cast<char> (c);
++ulIdx;
} while (true);
// [BB] We may have read more chars than we can store.
const int endIndex = (ulIdx < MAX_NETWORK_STRING) ? ulIdx : MAX_NETWORK_STRING - 1;
s_szString[endIndex] = '\0';
return (s_szString);
}
bool ByteInputStream::ReadBit()
{
EnsureBitSpace(1, false);
// Use a bit shift to extract a bit from our current byte
bool result = !!(*bitBuffer & (1 << bitShift));
bitShift++;
return result;
}
int ByteInputStream::ReadVariable()
{
// Read two bits to form an integer 0...3
int length = ReadBit();
length |= ReadBit() << 1;
// Use this length to read in an integer of variable length.
switch (length)
{
default:
case 0: return 0;
case 1: return ReadByte();
case 2: return ReadShort();
case 3: return ReadLong();
}
}
int ByteInputStream::ReadShortByte(int bits)
{
if (bits >= 0 && bits <= 8)
{
EnsureBitSpace(bits, false);
int mask = (1 << bits) - 1; // Create a mask to cover the bits we want.
mask <<= bitShift; // Shift the mask so that it covers the correct bits.
int result = *bitBuffer & mask; // Apply the shifted mask on our byte to remove unwanted bits.
result >>= bitShift; // Shift the result back to start from 0.
bitShift += bits; // Increase shift to mark these bits as used.
return result;
}
else
{
return 0;
}
}
void ByteInputStream::ReadBuffer(void *buffer, size_t length)
{
if ((pbStream + length) > pbStreamEnd)
{
Printf("ByteInputStream::ReadBuffer: Overflow!\n");
}
else
{
memcpy(buffer, pbStream, length);
pbStream += length;
}
}
bool ByteInputStream::IsAtEnd() const
{
return (pbStream >= pbStreamEnd);
}
int ByteInputStream::BytesLeft() const
{
return (int)(ptrdiff_t)(pbStreamEnd - pbStream);
}
void ByteInputStream::EnsureBitSpace(int bits, bool writing)
{
if ((bitBuffer == nullptr) || (bitShift < 0) || (bitShift + bits > 8))
{
// No room for the value in this byte, so we need a new one.
if (ReadByte() != -1)
{
bitBuffer = pbStream - 1;
}
else
{
// Argh! No bytes left!
Printf("ByteInputStream::EnsureBitSpace: out of bytes to use\n");
static uint8_t fallback = 0;
bitBuffer = &fallback;
}
bitShift = 0;
}
}
//*****************************************************************************
NetCommand::NetCommand(const NetPacketType Header)
{
// To do: improve memory handling here. 8 kb per command is very wasteful
mStream.SetBuffer(MAX_UDP_PACKET);
addByte(static_cast<int>(Header));
}
void NetCommand::addInteger(const int IntValue, const int Size)
{
for (int i = 0; i < Size; ++i)
mStream.WriteByte((IntValue >> (8 * i)) & 0xff);
}
void NetCommand::addByte(const int ByteValue)
{
addInteger(static_cast<uint8_t> (ByteValue), sizeof(uint8_t));
}
void NetCommand::addShort(const int ShortValue)
{
addInteger(static_cast<int16_t> (ShortValue), sizeof(int16_t));
}
void NetCommand::addLong(const int32_t LongValue)
{
addInteger(LongValue, sizeof(int32_t));
}
void NetCommand::addFloat(const float FloatValue)
{
union
{
float f;
int32_t l;
} dat;
dat.f = FloatValue;
addInteger(dat.l, sizeof(int32_t));
}
void NetCommand::addBit(const bool value)
{
mStream.WriteBit(value);
}
void NetCommand::addVariable(const int value)
{
mStream.WriteVariable(value);
}
void NetCommand::addShortByte(int value, int bits)
{
mStream.WriteShortByte(value, bits);
}
void NetCommand::addString(const char *pszString)
{
const int len = (pszString != nullptr) ? (int)strlen(pszString) : 0;
if (len > MAX_NETWORK_STRING)
{
Printf("NETWORK_WriteString: String exceeds %d characters! Header: %d\n", MAX_NETWORK_STRING, static_cast<const uint8_t*>(mStream.GetData())[0]);
return;
}
for (int i = 0; i < len; ++i)
addByte(pszString[i]);
addByte(0);
}
void NetCommand::addName(FName name)
{
if (name.IsPredefined())
{
addShort(name);
}
else
{
addShort(-1);
addString(name);
}
}
void NetCommand::addBuffer(const void *pvBuffer, int nLength)
{
mStream.WriteBuffer(pvBuffer, nLength);
}
void NetCommand::writeCommandToStream(ByteOutputStream &stream) const
{
stream.WriteBuffer(mStream.GetData(), mStream.GetSize());
}
int NetCommand::getSize() const
{
return mStream.GetSize();
}

127
src/network/netcommand.h Normal file
View file

@ -0,0 +1,127 @@
#pragma once
#include "vectors.h"
#include "r_data/renderstyle.h"
// Maximum size of the packets sent out by the server.
#define MAX_UDP_PACKET 8192
// This is the longest possible string we can pass over the network.
#define MAX_NETWORK_STRING 2048
enum class NetPacketType
{
ConnectRequest,
ConnectResponse,
Disconnect,
Tic,
SpawnActor,
DestroyActor
};
class ByteInputStream
{
public:
ByteInputStream() = default;
ByteInputStream(const void *buffer, int size);
void SetBuffer(const void *buffer, int size);
int ReadByte();
int ReadShort();
int ReadLong();
float ReadFloat();
const char* ReadString();
bool ReadBit();
int ReadVariable();
int ReadShortByte(int bits);
void ReadBuffer(void* buffer, size_t length);
bool IsAtEnd() const;
int BytesLeft() const;
private:
void EnsureBitSpace(int bits, bool writing);
uint8_t *mData = nullptr; // Pointer to our stream of data
uint8_t *pbStream; // Cursor position for next read.
uint8_t *pbStreamEnd; // Pointer to the end of the stream. When pbStream >= pbStreamEnd, the entire stream has been read.
uint8_t *bitBuffer = nullptr;
int bitShift = -1;
};
class ByteOutputStream
{
public:
ByteOutputStream() = default;
ByteOutputStream(int size);
ByteOutputStream(void *buffer, int size);
void SetBuffer(int size);
void SetBuffer(void *buffer, int size);
void ResetPos();
void WriteByte(int Byte);
void WriteShort(int Short);
void WriteLong(int Long);
void WriteFloat(float Float);
void WriteString(const char *pszString);
void WriteBit(bool bit);
void WriteVariable(int value);
void WriteShortByte(int value, int bits);
void WriteBuffer(const void *pvBuffer, int nLength);
const void *GetData() const { return mData; }
int GetSize() const { return (int)(ptrdiff_t)(pbStream - mData); }
private:
void AdvancePointer(const int NumBytes, const bool OutboundTraffic);
void EnsureBitSpace(int bits, bool writing);
uint8_t *mData = nullptr; // Pointer to our stream of data
uint8_t *pbStream; // Cursor position for next write
uint8_t *pbStreamEnd; // Pointer to the end of the data buffer
uint8_t *bitBuffer = nullptr;
int bitShift = -1;
struct DataBuffer
{
DataBuffer(int size) : data(new uint8_t[size]), size(size) { }
~DataBuffer() { delete[] data; }
uint8_t *data;
int size;
};
std::shared_ptr<DataBuffer> mBuffer;
};
/**
* \author Benjamin Berkels
*/
class NetCommand
{
ByteOutputStream mStream;
bool mUnreliable = false;
public:
NetCommand ( const NetPacketType Header );
void addInteger( const int IntValue, const int Size );
void addByte ( const int ByteValue );
void addShort ( const int ShortValue );
void addLong ( const int32_t LongValue );
void addFloat ( const float FloatValue );
void addString ( const char *pszString );
void addName ( FName name );
void addBit ( const bool value );
void addVariable ( const int value );
void addShortByte ( int value, int bits );
void addBuffer ( const void *pvBuffer, int nLength );
void writeCommandToStream ( ByteOutputStream &stream ) const;
bool isUnreliable() const { return mUnreliable; }
void setUnreliable(bool a) { mUnreliable = a; }
int getSize() const;
};

435
src/network/netserver.cpp Normal file
View file

@ -0,0 +1,435 @@
//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
// Copyright 2018 Magnus Norddahl
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
#include <stddef.h>
#include <inttypes.h>
#include "version.h"
#include "menu/menu.h"
#include "m_random.h"
#include "i_system.h"
#include "i_video.h"
#include "i_net.h"
#include "g_game.h"
#include "doomdef.h"
#include "doomstat.h"
#include "c_console.h"
#include "d_netinf.h"
#include "netserver.h"
#include "cmdlib.h"
#include "s_sound.h"
#include "m_cheat.h"
#include "p_local.h"
#include "c_dispatch.h"
#include "sbar.h"
#include "gi.h"
#include "m_misc.h"
#include "gameconfigfile.h"
#include "d_gui.h"
#include "templates.h"
#include "p_acs.h"
#include "p_trace.h"
#include "a_sharedglobal.h"
#include "st_start.h"
#include "teaminfo.h"
#include "p_conversation.h"
#include "g_level.h"
#include "d_event.h"
#include "m_argv.h"
#include "p_lnspec.h"
#include "v_video.h"
#include "p_spec.h"
#include "hardware.h"
#include "r_utility.h"
#include "a_keys.h"
#include "intermission/intermission.h"
#include "g_levellocals.h"
#include "events.h"
#include "i_time.h"
NetServer::NetServer()
{
Printf("Started hosting multiplayer game..\n");
mBroadcastCommands.SetBuffer(MAX_MSGLEN);
for (int i = 0; i < MAXNETNODES; i++)
mNodes[i].NodeIndex = i;
mComm = I_InitNetwork(DOOMPORT);
G_InitServerNetGame("e1m1");
}
void NetServer::Update()
{
// Read all packets currently available from clients
while (true)
{
NetInputPacket packet;
mComm->PacketGet(packet);
if (packet.node == -1)
break; // No more packets. We are done.
NetNode &node = mNodes[packet.node];
if (packet.stream.IsAtEnd()) // Connection to node closed (timed out)
{
OnClose(node, packet.stream);
}
else
{
while (packet.stream.IsAtEnd() == false)
{
NetPacketType type = (NetPacketType)packet.stream.ReadByte();
switch (type)
{
default: OnClose(node, packet.stream); break;
case NetPacketType::ConnectRequest: OnConnectRequest(node, packet.stream); break;
case NetPacketType::Disconnect: OnDisconnect(node, packet.stream); break;
case NetPacketType::Tic: OnTic(node, packet.stream); break;
}
}
}
}
}
void NetServer::SetCurrentTic(int tictime)
{
gametic = tictime;
for (int i = 0; i < MAXNETNODES; i++)
{
NetNode &node = mNodes[i];
if (node.Status == NodeStatus::InGame && node.Player != -1)
{
NetNode::TicUpdate &update = node.TicUpdates[gametic % BACKUPTICS];
if (update.received)
{
mCurrentInput[node.Player].ucmd = update.input;
update.received = false;
}
}
}
}
void NetServer::EndCurrentTic()
{
for (int i = 0; i < MAXNETNODES; i++)
{
if (mNodes[i].Status == NodeStatus::InGame)
{
int player = mNodes[i].Player;
NetOutputPacket packet(i);
packet.stream.WriteByte(gametic + 1);
if (mNodes[i].FirstTic)
{
TThinkerIterator<AActor> it = primaryLevel->GetThinkerIterator<AActor>();
AActor *mo;
while (mo = it.Next())
{
if (mo != players[player].mo)
{
CmdSpawnActor(packet.stream, mo);
}
}
mNodes[i].FirstTic = false;
}
packet.stream.WriteBuffer(mBroadcastCommands.GetData(), mBroadcastCommands.GetSize());
NetCommand cmd ( NetPacketType::Tic);
if (playeringame[player] && players[player].mo)
{
cmd.addFloat(static_cast<float> (players[player].mo->X()));
cmd.addFloat(static_cast<float> (players[player].mo->Y()));
cmd.addFloat(static_cast<float> (players[player].mo->Z()));
cmd.addFloat(static_cast<float> (players[player].mo->Vel.X));
cmd.addFloat(static_cast<float> (players[player].mo->Vel.Y));
cmd.addFloat(static_cast<float> (players[player].mo->Vel.Z));
cmd.addFloat(static_cast<float> (players[player].mo->Angles.Yaw.Degrees));
cmd.addFloat(static_cast<float> (players[player].mo->Angles.Pitch.Degrees));
}
else
{
cmd.addFloat(0.0f);
cmd.addFloat(0.0f);
cmd.addFloat(0.0f);
cmd.addFloat(0.0f);
cmd.addFloat(0.0f);
cmd.addFloat(0.0f);
cmd.addFloat(0.0f);
cmd.addFloat(0.0f);
}
TThinkerIterator<AActor> it = primaryLevel->GetThinkerIterator<AActor>();
AActor *mo;
while (mo = it.Next())
{
if (mo != players[player].mo && mo->syncdata.NetID)
{
cmd.addShort(mo->syncdata.NetID);
cmd.addFloat(static_cast<float> (mo->X()));
cmd.addFloat(static_cast<float> (mo->Y()));
cmd.addFloat(static_cast<float> (mo->Z()));
cmd.addFloat(static_cast<float> (mo->Angles.Yaw.Degrees));
cmd.addFloat(static_cast<float> (mo->Angles.Pitch.Degrees));
cmd.addShort(mo->sprite);
cmd.addByte(mo->frame);
}
}
cmd.addShort(-1);
cmd.writeCommandToStream(packet.stream);
mComm->PacketSend(packet);
}
}
mBroadcastCommands.ResetPos();
mCurrentCommands = mSendCommands;
mSendCommands.Clear();
}
int NetServer::GetSendTick() const
{
return gametic;
}
ticcmd_t NetServer::GetPlayerInput(int player) const
{
return mCurrentInput[player];
}
ticcmd_t NetServer::GetSentInput(int tic) const
{
return mCurrentInput[consoleplayer];
}
void NetServer::RunCommands(int player)
{
if (player == consoleplayer)
{
Net_RunCommands(mCurrentCommands, consoleplayer);
}
}
void NetServer::WriteLocalInput(ticcmd_t cmd)
{
mCurrentInput[consoleplayer] = cmd;
}
void NetServer::WriteBotInput(int player, const ticcmd_t &cmd)
{
mCurrentInput[player] = cmd;
}
void NetServer::WriteBytes(const uint8_t *block, int len)
{
mSendCommands.AppendData(block, len);
}
int NetServer::GetPing(int player) const
{
return 0;
}
int NetServer::GetServerPing() const
{
return 0;
}
void NetServer::ListPingTimes()
{
#if 0
for (int i = 0; i < MAXPLAYERS; i++)
if (playeringame[i])
Printf("% 4" PRId64 " %s\n", currrecvtime[i] - lastrecvtime[i], players[i].userinfo.GetName());
#endif
}
void NetServer::Network_Controller(int playernum, bool add)
{
}
void NetServer::OnClose(NetNode &node, ByteInputStream &stream)
{
if (node.Status == NodeStatus::InGame)
{
Printf("Player %d left the server\n", node.Player);
playeringame[node.Player] = false;
players[node.Player].settings_controller = false;
node.Player = -1;
}
node.Status = NodeStatus::Closed;
mComm->Close(node.NodeIndex);
}
void NetServer::OnConnectRequest(NetNode &node, ByteInputStream &stream)
{
// Make the initial connect packet a bit more complex than a bunch of zeros..
if (strcmp(stream.ReadString(), "ZDoom Connect Request") != 0)
{
if (node.Status == NodeStatus::InGame)
{
Printf("Junk data received from a joined player\n");
}
else
{
node.Status = NodeStatus::Closed;
mComm->Close(node.NodeIndex);
return;
}
}
if (node.Status == NodeStatus::InGame)
return;
// Search for a spot in the player list
for (int i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
{
node.Player = i;
break;
}
}
if (node.Player != -1) // Join accepted.
{
Printf("Player %d joined the server\n", node.Player);
for (int i = 0; i < BACKUPTICS; i++)
node.TicUpdates[i].received = false;
node.FirstTic = true;
node.Status = NodeStatus::InGame;
mNodeForPlayer[node.Player] = node.NodeIndex;
playeringame[node.Player] = true;
players[node.Player].settings_controller = false;
NetOutputPacket response(node.NodeIndex);
response.stream.WriteByte(gametic);
NetCommand cmd ( NetPacketType::ConnectResponse );
cmd.addByte ( 1 ); // Protocol version
cmd.addByte ( node.Player );
cmd.writeCommandToStream ( response.stream );
mComm->PacketSend(response);
}
else // Server is full.
{
node.Status = NodeStatus::Closed;
NetOutputPacket response(node.NodeIndex);
response.stream.WriteByte(gametic);
NetCommand cmd ( NetPacketType::ConnectResponse );
cmd.addByte ( 1 ); // Protocol version
cmd.addByte ( 255 );
cmd.writeCommandToStream (response.stream);
mComm->PacketSend(response);
node.Status = NodeStatus::Closed;
mComm->Close(node.NodeIndex);
}
}
void NetServer::OnDisconnect(NetNode &node, ByteInputStream &stream)
{
if (node.Status == NodeStatus::InGame)
{
Printf("Player %d left the server\n", node.Player);
playeringame[node.Player] = false;
players[node.Player].settings_controller = false;
node.Player = -1;
}
node.Status = NodeStatus::Closed;
mComm->Close(node.NodeIndex);
}
void NetServer::OnTic(NetNode &node, ByteInputStream &stream)
{
if (node.Status != NodeStatus::InGame)
return;
int tic = stream.ReadByte();
int delta = tic - (gametic & 0xff);
if (delta > 128) delta -= 256;
else if (delta < -128) delta += 256;
tic = gametic + delta;
NetNode::TicUpdate update;
update.received = true;
stream.ReadBuffer(&update.input, sizeof(usercmd_t));
if (tic <= gametic)
{
// Packet arrived too late.
tic = gametic + 1;
if (tic < 0 || node.TicUpdates[tic % BACKUPTICS].received)
return; // We already received the proper packet.
}
node.TicUpdates[tic % BACKUPTICS] = update;
}
void NetServer::CmdSpawnActor(ByteOutputStream &stream, AActor *actor)
{
NetCommand cmd(NetPacketType::SpawnActor);
cmd.addShort(actor->syncdata.NetID);
cmd.addFloat(static_cast<float>(actor->X()));
cmd.addFloat(static_cast<float>(actor->Y()));
cmd.addFloat(static_cast<float>(actor->Z()));
cmd.writeCommandToStream(stream);
}
void NetServer::CmdDestroyActor(ByteOutputStream &stream, AActor *actor)
{
NetCommand cmd(NetPacketType::DestroyActor);
cmd.addShort(actor->syncdata.NetID);
cmd.writeCommandToStream(stream);
}
void NetServer::ActorSpawned(AActor *actor)
{
actor->syncdata.NetID = mNetIDList.getNewID();
mNetIDList.useID(actor->syncdata.NetID, actor);
CmdSpawnActor(mBroadcastCommands, actor);
}
void NetServer::ActorDestroyed(AActor *actor)
{
CmdDestroyActor(mBroadcastCommands, actor);
mNetIDList.freeID(actor->syncdata.NetID);
}

103
src/network/netserver.h Normal file
View file

@ -0,0 +1,103 @@
//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
// Copyright 2018 Magnus Norddahl
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
#pragma once
#include "net.h"
#include "netcommand.h"
enum class NodeStatus
{
Closed,
InPreGame,
InGame
};
struct NetNode
{
NodeStatus Status = NodeStatus::Closed;
int Ping = 0;
int Gametic = 0;
int Player = -1;
int NodeIndex = -1;
bool FirstTic = true;
struct TicUpdate
{
bool received = false;
usercmd_t input;
};
TicUpdate TicUpdates[BACKUPTICS];
FDynamicBuffer Commands; // "NetSpecs"
};
class NetServer : public Network
{
public:
NetServer();
void Update() override;
void SetCurrentTic(int tictime) override;
void EndCurrentTic() override;
int GetSendTick() const override;
ticcmd_t GetPlayerInput(int player) const override;
ticcmd_t GetSentInput(int tic) const override;
void RunCommands(int player) override;
void WriteLocalInput(ticcmd_t cmd) override;
void WriteBotInput(int player, const ticcmd_t &cmd) override;
void WriteBytes(const uint8_t *block, int len) override;
int GetPing(int player) const override;
int GetServerPing() const override;
void ListPingTimes() override;
void Network_Controller(int playernum, bool add) override;
void ActorSpawned(AActor *actor) override;
void ActorDestroyed(AActor *actor) override;
private:
void OnClose(NetNode &node, ByteInputStream &stream);
void OnConnectRequest(NetNode &node, ByteInputStream &stream);
void OnDisconnect(NetNode &node, ByteInputStream &stream);
void OnTic(NetNode &node, ByteInputStream &packet);
void CmdSpawnActor(ByteOutputStream &stream, AActor *actor);
void CmdDestroyActor(ByteOutputStream &stream, AActor *actor);
std::unique_ptr<doomcom_t> mComm;
NetNode mNodes[MAXNETNODES];
int mNodeForPlayer[MAXPLAYERS];
ticcmd_t mCurrentInput[MAXPLAYERS];
FDynamicBuffer mCurrentCommands;
FDynamicBuffer mSendCommands;
IDList<AActor> mNetIDList;
ByteOutputStream mBroadcastCommands; // Playsim events everyone should hear about
};

150
src/network/netsingle.cpp Normal file
View file

@ -0,0 +1,150 @@
//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
// Copyright 2018 Magnus Norddahl
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
#include <stddef.h>
#include <inttypes.h>
#include "version.h"
#include "menu/menu.h"
#include "m_random.h"
#include "i_system.h"
#include "i_video.h"
#include "i_net.h"
#include "g_game.h"
#include "doomdef.h"
#include "doomstat.h"
#include "c_console.h"
#include "d_netinf.h"
#include "netsingle.h"
#include "cmdlib.h"
#include "s_sound.h"
#include "m_cheat.h"
#include "p_local.h"
#include "c_dispatch.h"
#include "sbar.h"
#include "gi.h"
#include "m_misc.h"
#include "gameconfigfile.h"
#include "d_gui.h"
#include "templates.h"
#include "p_acs.h"
#include "p_trace.h"
#include "a_sharedglobal.h"
#include "st_start.h"
#include "teaminfo.h"
#include "p_conversation.h"
#include "g_level.h"
#include "d_event.h"
#include "m_argv.h"
#include "p_lnspec.h"
#include "v_video.h"
#include "p_spec.h"
#include "hardware.h"
#include "r_utility.h"
#include "a_keys.h"
#include "intermission/intermission.h"
#include "g_levellocals.h"
#include "events.h"
#include "i_time.h"
extern bool netserver, netclient;
NetSinglePlayer::NetSinglePlayer()
{
netgame = false;
netclient = false;
netserver = false;
multiplayer = false;
consoleplayer = 0;
players[0].settings_controller = true;
playeringame[0] = true;
}
void NetSinglePlayer::Update()
{
}
void NetSinglePlayer::SetCurrentTic(int tictime)
{
gametic = tictime;
}
void NetSinglePlayer::EndCurrentTic()
{
mCurrentCommands = mSendCommands;
mSendCommands.Clear();
}
int NetSinglePlayer::GetSendTick() const
{
return gametic;
}
ticcmd_t NetSinglePlayer::GetPlayerInput(int player) const
{
return mCurrentInput[player];
}
ticcmd_t NetSinglePlayer::GetSentInput(int tic) const
{
return mCurrentInput[consoleplayer];
}
void NetSinglePlayer::RunCommands(int player)
{
if (player == consoleplayer)
{
Net_RunCommands(mCurrentCommands, consoleplayer);
}
}
void NetSinglePlayer::WriteLocalInput(ticcmd_t cmd)
{
mCurrentInput[consoleplayer] = cmd;
}
void NetSinglePlayer::WriteBotInput(int player, const ticcmd_t &cmd)
{
mCurrentInput[player] = cmd;
}
void NetSinglePlayer::WriteBytes(const uint8_t *block, int len)
{
mSendCommands.AppendData(block, len);
}
int NetSinglePlayer::GetPing(int player) const
{
return 0;
}
int NetSinglePlayer::GetServerPing() const
{
return 0;
}
void NetSinglePlayer::ListPingTimes()
{
}
void NetSinglePlayer::Network_Controller(int playernum, bool add)
{
}

57
src/network/netsingle.h Normal file
View file

@ -0,0 +1,57 @@
//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
// Copyright 2018 Magnus Norddahl
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
#pragma once
#include "network/net.h"
class NetSinglePlayer : public Network
{
public:
NetSinglePlayer();
void Update() override;
void SetCurrentTic(int tictime) override;
void EndCurrentTic() override;
int GetSendTick() const override;
ticcmd_t GetPlayerInput(int player) const override;
ticcmd_t GetSentInput(int tic) const override;
void RunCommands(int player) override;
void WriteLocalInput(ticcmd_t cmd) override;
void WriteBotInput(int player, const ticcmd_t &cmd) override;
void WriteBytes(const uint8_t *block, int len) override;
int GetPing(int player) const override;
int GetServerPing() const override;
void ListPingTimes() override;
void Network_Controller(int playernum, bool add) override;
private:
ticcmd_t mCurrentInput[MAXPLAYERS];
FDynamicBuffer mCurrentCommands;
FDynamicBuffer mSendCommands;
};

270
src/network/netsync.cpp Normal file
View file

@ -0,0 +1,270 @@
//-----------------------------------------------------------------------------
//
// Copyright 2018 Benjamin Berkels
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
#include "d_player.h"
#include "netsync.h"
#include "c_dispatch.h"
#include "actor.h"
#include "doomstat.h"
#include "i_net.h"
#include "cmdlib.h"
#include "g_levellocals.h"
extern bool netserver;
void CountActors();
class NetSyncWriter
{
public:
NetSyncWriter(NetCommand &cmd, int netid) : cmd(cmd), NetID(netid) { }
void Write(void *compval, void *actorval, size_t size)
{
if (memcmp(compval, actorval, size) != 0)
{
if (firstwrite)
{
firstwrite = false;
cmd.addShort(NetID);
}
cmd.addByte(fieldindex);
cmd.addBuffer(actorval, (int)size);
memcpy(compval, actorval, size);
}
fieldindex++;
}
void WriteEnd()
{
if (!firstwrite)
{
cmd.addByte(255);
}
}
private:
NetCommand &cmd;
int NetID;
bool firstwrite = true;
int fieldindex = 0;
};
NetSyncClass::NetSyncClass()
{
mSyncVars.Push({ myoffsetof(AActor, Vel.X), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, Vel.Y), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, Vel.Z), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, SpriteAngle.Degrees), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, SpriteRotation.Degrees), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, Angles.Yaw.Degrees), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, Angles.Pitch.Degrees), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, Angles.Roll.Degrees), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, Scale.X), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, Scale.Y), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, Alpha), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, sprite), sizeof(uint32_t) });
mSyncVars.Push({ myoffsetof(AActor, frame), sizeof(uint8_t) });
mSyncVars.Push({ myoffsetof(AActor, effects), sizeof(uint8_t) });
mSyncVars.Push({ myoffsetof(AActor, RenderStyle.AsDWORD), sizeof(uint32_t) });
mSyncVars.Push({ myoffsetof(AActor, Translation), sizeof(uint32_t) });
mSyncVars.Push({ myoffsetof(AActor, RenderRequired), sizeof(uint32_t) });
mSyncVars.Push({ myoffsetof(AActor, RenderHidden), sizeof(uint32_t) });
mSyncVars.Push({ myoffsetof(AActor, renderflags.Value), sizeof(uint32_t) });
mSyncVars.Push({ myoffsetof(AActor, Floorclip), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, VisibleStartAngle.Degrees), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, VisibleStartPitch.Degrees), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, VisibleEndAngle.Degrees), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, VisibleEndPitch.Degrees), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, Speed), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, FloatSpeed), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, CameraHeight), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, CameraFOV), sizeof(double) });
mSyncVars.Push({ myoffsetof(AActor, StealthAlpha), sizeof(double) });
// To do: create NetSyncVariable entries for all the script variables we want sent to the client
}
void NetSyncClass::InitSyncData(AActor *actor)
{
auto &syncdata = actor->syncdata;
size_t scriptvarsize = 0;
for (unsigned int i = 0; i < mSyncVars.Size(); i++)
scriptvarsize += mSyncVars[i].size;
syncdata.CompareData.Resize((unsigned int)scriptvarsize);
syncdata.Pos = actor->Pos();
size_t pos = 0;
for (unsigned int i = 0; i < mSyncVars.Size(); i++)
{
memcpy(syncdata.CompareData.Data() + pos, ((uint8_t*)actor) + mSyncVars[i].offset, scriptvarsize);
pos += mSyncVars[i].size;
}
}
void NetSyncClass::WriteSyncUpdate(NetCommand &cmd, AActor *actor)
{
NetSyncWriter writer(cmd, actor->syncdata.NetID);
DVector3 pos = actor->Pos();
writer.Write(&actor->syncdata.Pos, &pos, sizeof(DVector3));
size_t compareoffset = 0;
for (unsigned int i = 0; i < mSyncVars.Size(); i++)
{
writer.Write(actor->syncdata.CompareData.Data() + compareoffset, ((uint8_t*)actor) + mSyncVars[i].offset, mSyncVars[i].size);
compareoffset += mSyncVars[i].size;
}
writer.WriteEnd();
}
void NetSyncClass::ReadSyncUpdate(ByteInputStream &stream, AActor *actor)
{
while (true)
{
int fieldindex = stream.ReadByte();
if (fieldindex == 255)
break;
if (fieldindex == 0)
{
DVector3 pos;
stream.ReadBuffer(&pos, sizeof(DVector3));
actor->SetOrigin(pos, true);
}
else if (fieldindex <= (int)mSyncVars.Size())
{
stream.ReadBuffer(((uint8_t*)actor) + mSyncVars[fieldindex - 1].offset, mSyncVars[fieldindex - 1].size);
}
}
}
/////////////////////////////////////////////////////////////////////////////
template <typename T>
void IDList<T>::clear()
{
for (unsigned int ulIdx = 0; ulIdx < MAX_NETID; ulIdx++)
freeID(ulIdx);
_firstFreeID = 1;
}
template <typename T>
void IDList<T>::rebuild()
{
clear();
T *pActor;
TThinkerIterator<T> it = primaryLevel->GetThinkerIterator<T>();
while ((pActor = it.Next()))
{
if ((pActor->syncdata.NetID > 0) && (pActor->syncdata.NetID < MAX_NETID))
useID(pActor->syncdata.NetID, pActor);
}
}
template <typename T>
void IDList<T>::useID(const int lNetID, T *pActor)
{
if (isIndexValid(lNetID))
{
if ((_entries[lNetID].bFree == false) && (_entries[lNetID].pActor != pActor))
Printf("IDList<T>::useID is using an already used ID.\n");
_entries[lNetID].bFree = false;
_entries[lNetID].pActor = pActor;
}
}
template <typename T>
unsigned int IDList<T>::getNewID()
{
// Actor's network ID is the first availible net ID.
unsigned int ulID = _firstFreeID;
do
{
_firstFreeID++;
if (_firstFreeID >= MAX_NETID)
_firstFreeID = 1;
if (_firstFreeID == ulID)
{
// [BB] In case there is no free netID, the server has to abort the current game.
if (netserver)
{
// [BB] We can only spawn (MAX_NETID-2) actors with netID, because ID zero is reserved and
// we already check that a new ID for the next actor is available when assign a net ID.
Printf("ACTOR_GetNewNetID: Network ID limit reached (>=%d actors)\n", MAX_NETID - 1);
CountActors();
I_Error("Network ID limit reached (>=%d actors)!\n", MAX_NETID - 1);
}
return (0);
}
} while (_entries[_firstFreeID].bFree == false);
return (ulID);
}
template class IDList<AActor>;
//*****************************************************************************
void CountActors()
{
TMap<FName, int> actorCountMap;
AActor * mo;
int numActors = 0;
int numActorsWithNetID = 0;
TThinkerIterator<AActor> it = primaryLevel->GetThinkerIterator<AActor>();
while ((mo = it.Next()))
{
numActors++;
if (mo->syncdata.NetID > 0)
numActorsWithNetID++;
const FName curName = mo->GetClass()->TypeName.GetChars();
if (actorCountMap.CheckKey(curName) == NULL)
actorCountMap.Insert(curName, 1);
else
actorCountMap[curName] ++;
}
const TMap<FName, int>::Pair *pair;
Printf("%d actors in total found, %d have a NetID. Detailed listing:\n", numActors, numActorsWithNetID);
TMap<FName, int>::ConstIterator mapit(actorCountMap);
while (mapit.NextPair(pair))
{
Printf("%s %d\n", pair->Key.GetChars(), pair->Value);
}
}
CCMD(countactors)
{
CountActors();
}

114
src/network/netsync.h Normal file
View file

@ -0,0 +1,114 @@
#pragma once
#include "vectors.h"
#include "r_data/renderstyle.h"
class AActor;
class NetCommand;
class ByteInputStream;
class NetSyncVariable
{
public:
size_t offset;
size_t size;
};
class NetSyncClass
{
public:
NetSyncClass();
void InitSyncData(AActor *actor);
void WriteSyncUpdate(NetCommand &cmd, AActor *actor);
void ReadSyncUpdate(ByteInputStream &stream, AActor *actor);
private:
TArray<NetSyncVariable> mSyncVars;
NetSyncClass(const NetSyncClass &) = delete;
NetSyncClass &operator=(const NetSyncClass &) = delete;
};
class NetSyncData
{
public:
int NetID;
DVector3 Pos;
TArray<uint8_t> CompareData;
NetSyncClass *SyncClass; // Maybe this should be stored in the actor's PClass
};
//==========================================================================
//
// IDList
//
// Manages IDs to reference a certain type of objects over the network.
// Since it still mimics the old Actor ID mechanism, 0 is never assigned as
// ID.
//
// @author Benjamin Berkels
//
//==========================================================================
template <typename T>
class IDList
{
public:
const static int MAX_NETID = 32768;
private:
// List of all possible network ID's for an actor. Slot is true if it available for use.
typedef struct
{
// Is this node occupied, or free to be used by a new actor?
bool bFree;
// If this node is occupied, this is the actor occupying it.
T *pActor;
} IDNODE_t;
IDNODE_t _entries[MAX_NETID];
unsigned int _firstFreeID;
inline bool isIndexValid(const int lNetID) const
{
return (lNetID >= 0) && (lNetID < MAX_NETID);
}
public:
void clear();
// [BB] Rebuild the global list of used / free NetIDs from scratch.
void rebuild();
IDList()
{
clear();
}
void useID(const int lNetID, T *pActor);
void freeID(const int lNetID)
{
if (isIndexValid(lNetID))
{
_entries[lNetID].bFree = true;
_entries[lNetID].pActor = nullptr;
}
}
unsigned int getNewID();
T* findPointerByID(const int lNetID) const
{
if (isIndexValid(lNetID) == false)
return nullptr;
if ((_entries[lNetID].bFree == false) && (_entries[lNetID].pActor))
return _entries[lNetID].pActor;
return nullptr;
}
};

View file

@ -45,7 +45,7 @@
#include "gstrings.h"
#include "i_music.h"
#include "p_setup.h"
#include "d_net.h"
#include "network/net.h"
#include "d_event.h"
#include "doomstat.h"
#include "c_console.h"
@ -255,17 +255,17 @@ DEFINE_ACTION_FUNCTION(DConversationMenu, SendConversationReply)
switch (node)
{
case -1:
Net_WriteByte(DEM_CONVNULL);
network->WriteByte(DEM_CONVNULL);
break;
case -2:
Net_WriteByte(DEM_CONVCLOSE);
network->WriteByte(DEM_CONVCLOSE);
break;
default:
Net_WriteByte(DEM_CONVREPLY);
Net_WriteWord(node);
Net_WriteByte(reply);
network->WriteByte(DEM_CONVREPLY);
network->WriteWord(node);
network->WriteByte(reply);
break;
}
StaticLastReply = reply;

View file

@ -39,7 +39,6 @@
#include "p_trace.h"
#include "decallib.h"
#include "c_dispatch.h"
#include "d_net.h"
#include "serializer.h"
#include "doomdata.h"
#include "g_levellocals.h"

View file

@ -48,6 +48,8 @@
#include "tflags.h"
#include "portal.h"
#include "network/netsync.h"
struct subsector_t;
struct FBlockNode;
struct FPortalGroupArray;
@ -645,6 +647,8 @@ public:
AActor &operator= (const AActor &other);
~AActor ();
NetSyncData syncdata;
virtual void OnDestroy() override;
virtual void Serialize(FSerializer &arc) override;
virtual void PostSerialize() override;

View file

@ -44,7 +44,7 @@
#include "p_local.h"
#include "cmdlib.h"
#include "teaminfo.h"
#include "d_net.h"
#include "network/net.h"
#include "serializer.h"
#include "d_player.h"
#include "w_wad.h"
@ -212,7 +212,7 @@ CCMD (removebots)
return;
}
Net_WriteByte (DEM_KILLBOTS);
network->WriteByte (DEM_KILLBOTS);
}
CCMD (freeze)
@ -226,8 +226,8 @@ CCMD (freeze)
return;
}
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_FREEZE);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_FREEZE);
}
CCMD (listbots)

View file

@ -87,7 +87,7 @@ Everything that is changed is marked (maybe commented) with "Added by MC"
#include "sbar.h"
#include "p_acs.h"
#include "teaminfo.h"
#include "d_net.h"
#include "network/net.h"
#include "d_netinf.h"
#include "d_player.h"
#include "events.h"
@ -292,8 +292,8 @@ bool FCajunMaster::SpawnBot (const char *name, int color)
thebot->inuse = BOTINUSE_Waiting;
Net_WriteByte (DEM_ADDBOT);
Net_WriteByte (botshift);
network->WriteByte (DEM_ADDBOT);
network->WriteByte (botshift);
{
//Set color.
char concat[512];
@ -307,12 +307,12 @@ bool FCajunMaster::SpawnBot (const char *name, int color)
mysnprintf (concat + strlen(concat), countof(concat) - strlen(concat),
"\\team\\%d\n", thebot->lastteam);
}
Net_WriteString (concat);
network->WriteString (concat);
}
Net_WriteByte(thebot->skill.aiming);
Net_WriteByte(thebot->skill.perfection);
Net_WriteByte(thebot->skill.reaction);
Net_WriteByte(thebot->skill.isp);
network->WriteByte(thebot->skill.aiming);
network->WriteByte(thebot->skill.perfection);
network->WriteByte(thebot->skill.reaction);
network->WriteByte(thebot->skill.isp);
return true;
}

View file

@ -47,7 +47,7 @@
#include "p_local.h"
#include "b_bot.h"
#include "g_game.h"
#include "d_net.h"
#include "network/net.h"
#include "d_event.h"
#include "d_player.h"
#include "actorinlines.h"
@ -58,9 +58,7 @@ static FRandom pr_botmove ("BotMove");
//so this is what the bot does.
void DBot::Think ()
{
ticcmd_t *cmd = &netcmds[player - players][((gametic + 1)/ticdup)%BACKUPTICS];
memset (cmd, 0, sizeof(*cmd));
ticcmd_t cmd;
if (enemy && enemy->health <= 0)
enemy = nullptr;
@ -75,16 +73,16 @@ void DBot::Think ()
DAngle oldpitch = actor->Angles.Pitch;
Set_enemy ();
ThinkForMove (cmd);
ThinkForMove (&cmd);
TurnToAng ();
cmd->ucmd.yaw = (short)((actor->Angles.Yaw - oldyaw).Degrees * (65536 / 360.f)) / ticdup;
cmd->ucmd.pitch = (short)((oldpitch - actor->Angles.Pitch).Degrees * (65536 / 360.f));
if (cmd->ucmd.pitch == -32768)
cmd->ucmd.pitch = -32767;
cmd->ucmd.pitch /= ticdup;
actor->Angles.Yaw = oldyaw + DAngle(cmd->ucmd.yaw * ticdup * (360 / 65536.f));
actor->Angles.Pitch = oldpitch - DAngle(cmd->ucmd.pitch * ticdup * (360 / 65536.f));
cmd.ucmd.yaw = (short)((actor->Angles.Yaw - oldyaw).Degrees * (65536 / 360.f)) / network->ticdup;
cmd.ucmd.pitch = (short)((oldpitch - actor->Angles.Pitch).Degrees * (65536 / 360.f));
if (cmd.ucmd.pitch == -32768)
cmd.ucmd.pitch = -32767;
cmd.ucmd.pitch /= network->ticdup;
actor->Angles.Yaw = oldyaw + DAngle(cmd.ucmd.yaw * network->ticdup * (360 / 65536.f));
actor->Angles.Pitch = oldpitch - DAngle(cmd.ucmd.pitch * network->ticdup * (360 / 65536.f));
}
if (t_active) t_active--;
@ -101,8 +99,10 @@ void DBot::Think ()
}
else if (player->mo->health <= 0)
{ // Time to respawn
cmd->ucmd.buttons |= BT_USE;
cmd.ucmd.buttons |= BT_USE;
}
network->WriteBotInput((int)(player - players), cmd);
}
#define THINKDISTSQ (50000.*50000./(65536.*65536.))

View file

@ -54,7 +54,7 @@
#include "d_player.h"
#include "gi.h"
#include "sbar.h"
#include "d_net.h"
#include "network/net.h"
#include "d_netinf.h"
#include "a_morph.h"
#include "vm.h"
@ -1836,8 +1836,8 @@ CCMD (kill)
if (CheckCheatmode ())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_MASSACRE);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_MASSACRE);
}
else if (!stricmp (argv[1], "baddies"))
{
@ -1845,13 +1845,13 @@ CCMD (kill)
if (CheckCheatmode ())
return;
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (CHT_MASSACRE2);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (CHT_MASSACRE2);
}
else
{
Net_WriteByte (DEM_KILLCLASSCHEAT);
Net_WriteString (argv[1]);
network->WriteByte (DEM_KILLCLASSCHEAT);
network->WriteString (argv[1]);
}
}
else
@ -1861,7 +1861,7 @@ CCMD (kill)
return;
// Kill the player
Net_WriteByte (DEM_SUICIDE);
network->WriteByte (DEM_SUICIDE);
}
C_HideConsole ();
}
@ -1873,8 +1873,8 @@ CCMD(remove)
if (CheckCheatmode())
return;
Net_WriteByte(DEM_REMOVE);
Net_WriteString(argv[1]);
network->WriteByte(DEM_REMOVE);
network->WriteString(argv[1]);
C_HideConsole();
}
else
@ -1882,5 +1882,4 @@ CCMD(remove)
Printf("Usage: remove <actor class name>\n");
return;
}
}

View file

@ -46,7 +46,7 @@
#include "gi.h"
#include "p_conversation.h"
#include "p_3dmidtex.h"
#include "d_net.h"
#include "network/net.h"
#include "d_event.h"
#include "gstrings.h"
#include "po_man.h"
@ -3125,7 +3125,7 @@ FUNC(LS_Autosave)
if (gameaction != ga_savegame)
{
Level->flags2 &= ~LEVEL2_NOAUTOSAVEHINT;
Net_WriteByte (DEM_CHECKAUTOSAVE);
network->WriteByte (DEM_CHECKAUTOSAVE);
}
return true;
}

View file

@ -98,6 +98,7 @@
#include "events.h"
#include "actorinlines.h"
#include "a_dynlight.h"
#include "network/net.h"
#include "fragglescript/t_fs.h"
// MACROS ------------------------------------------------------------------
@ -4550,6 +4551,9 @@ void ConstructActor(AActor *actor, const DVector3 &pos, bool SpawningMapThing)
}
// force scroller check in the first tic.
actor->flags8 |= MF8_INSCROLLSEC;
// notify network that we got a new actor
network->ActorSpawned(actor);
}
@ -4819,6 +4823,9 @@ void AActor::CallDeactivate(AActor *activator)
void AActor::OnDestroy ()
{
// Notify network
network->ActorDestroyed(this);
// [ZZ] call destroy event hook.
// note that this differs from ThingSpawned in that you can actually override OnDestroy to avoid calling the hook.
// but you can't really do that without utterly breaking the game, so it's ok.

View file

@ -76,7 +76,7 @@
#include "intermission/intermission.h"
#include "c_console.h"
#include "c_dispatch.h"
#include "d_net.h"
#include "network/net.h"
#include "serializer.h"
#include "r_renderer.h"
#include "d_player.h"
@ -478,7 +478,7 @@ void player_t::SetFOV(float fov)
{
if (consoleplayer == Net_Arbitrator)
{
Net_WriteByte(DEM_MYFOV);
network->WriteByte(DEM_MYFOV);
}
else
{
@ -488,9 +488,9 @@ void player_t::SetFOV(float fov)
}
else
{
Net_WriteByte(DEM_MYFOV);
network->WriteByte(DEM_MYFOV);
}
Net_WriteFloat(clamp<float>(fov, 5.f, 179.f));
network->WriteFloat(clamp<float>(fov, 5.f, 179.f));
}
}
@ -637,9 +637,9 @@ void player_t::SendPitchLimits() const
uppitch = downpitch = (int)maxviewpitch;
}
Net_WriteByte(DEM_SETPITCHLIMIT);
Net_WriteByte(uppitch);
Net_WriteByte(downpitch);
network->WriteByte(DEM_SETPITCHLIMIT);
network->WriteByte(uppitch);
network->WriteByte(downpitch);
}
}
@ -1392,7 +1392,7 @@ void P_PredictPlayer (player_t *player)
return;
}
maxtic = maketic;
maxtic = network->GetSendTick();
if (gametic == maxtic)
{
@ -1446,13 +1446,13 @@ void P_PredictPlayer (player_t *player)
act->BlockNode = NULL;
// Values too small to be usable for lerping can be considered "off".
bool CanLerp = (!(cl_predict_lerpscale < 0.01f) && (ticdup == 1)), DoLerp = false, NoInterpolateOld = R_GetViewInterpolationStatus();
bool CanLerp = (!(cl_predict_lerpscale < 0.01f) && (network->ticdup == 1)), DoLerp = false, NoInterpolateOld = R_GetViewInterpolationStatus();
for (int i = gametic; i < maxtic; ++i)
{
if (!NoInterpolateOld)
R_RebuildViewInterpolation(player);
player->cmd = localcmds[i % LOCALCMDTICS];
player->cmd = network->GetSentInput(i);
P_PlayerThink (player);
player->mo->Tick ();

View file

@ -50,7 +50,6 @@
#include "cmdlib.h"
#include "atterm.h"
void I_Tactile(int /*on*/, int /*off*/, int /*total*/)
{
}

View file

@ -72,7 +72,7 @@ public:
virtual void NetProgress(int count);
virtual void NetMessage(const char *format, ...);
virtual void NetDone();
virtual bool NetLoop(bool (*timerCallback)(void*), void* userData);
virtual bool NetLoop(std::function<bool()> callback);
};
@ -154,11 +154,11 @@ void FBasicStartupScreen::NetDone()
FConsoleWindow::GetInstance().NetDone();
}
bool FBasicStartupScreen::NetLoop(bool (*timerCallback)(void*), void* const userData)
bool FBasicStartupScreen::NetLoop(std::function<bool()> callback)
{
while (true)
{
if (timerCallback(userData))
if (callback())
{
break;
}

View file

@ -71,17 +71,6 @@ void I_StartFrame (void);
// Can call D_PostEvent.
void I_StartTic (void);
// Asynchronous interrupt functions should maintain private queues
// that are read by the synchronous functions
// to be converted into events.
// Either returns a null ticcmd,
// or calls a loadable driver to build it.
// This ticcmd will then be modified by the gameloop
// for normal input.
ticcmd_t *I_BaseTiccmd (void);
// Called by M_Responder when quit is selected.
// Clean exit, displays sell blurb.
void I_Quit (void);

View file

@ -48,7 +48,7 @@
#include "x86.h"
#include "d_main.h"
#include "d_net.h"
#include "network/net.h"
#include "g_game.h"
#include "c_dispatch.h"
#include "atterm.h"
@ -75,12 +75,6 @@ void I_Tactile (int /*on*/, int /*off*/, int /*total*/)
{
}
ticcmd_t emptycmd;
ticcmd_t *I_BaseTiccmd(void)
{
return &emptycmd;
}
void I_BeginRead(void)
{
}

View file

@ -59,7 +59,7 @@ class FTTYStartupScreen : public FStartupScreen
void NetProgress(int count);
void NetMessage(const char *format, ...); // cover for printf
void NetDone();
bool NetLoop(bool (*timer_callback)(void *), void *userdata);
bool NetLoop(std::function<bool()> callback);
protected:
bool DidNetInit;
int NetMaxPos, NetCurPos;
@ -305,7 +305,7 @@ void FTTYStartupScreen::NetProgress(int count)
//
//===========================================================================
bool FTTYStartupScreen::NetLoop(bool (*timer_callback)(void *), void *userdata)
bool FTTYStartupScreen::NetLoop(std::function<bool()> callback)
{
fd_set rfds;
struct timeval tv;
@ -329,7 +329,7 @@ bool FTTYStartupScreen::NetLoop(bool (*timer_callback)(void *), void *userdata)
}
else if (retval == 0)
{
if (timer_callback (userdata))
if (callback())
{
fputc ('\n', stderr);
return true;

View file

@ -34,7 +34,7 @@
#include "templates.h"
#include "doomdef.h"
#include "d_net.h"
#include "network/net.h"
#include "doomstat.h"
#include "m_random.h"
#include "m_bbox.h"

View file

@ -29,7 +29,6 @@
#include "r_data/r_interpolate.h"
#include "r_data/models/models.h"
#include "poly_renderer.h"
#include "d_net.h"
#include "po_man.h"
#include "st_stuff.h"
#include "g_levellocals.h"

View file

@ -33,7 +33,6 @@
#include "w_wad.h"
#include "stats.h"
#include "a_sharedglobal.h"
#include "d_net.h"
#include "g_level.h"
#include "g_levellocals.h"
#include "r_wallsetup.h"

View file

@ -33,7 +33,6 @@
#include "a_sharedglobal.h"
#include "c_console.h"
#include "cmdlib.h"
#include "d_net.h"
#include "g_level.h"
#include "v_palette.h"
#include "r_data/colormaps.h"

View file

@ -35,7 +35,6 @@
#include "w_wad.h"
#include "stats.h"
#include "a_sharedglobal.h"
#include "d_net.h"
#include "g_level.h"
#include "g_levellocals.h"
#include "r_wallsetup.h"

View file

@ -37,7 +37,6 @@
#include "r_sky.h"
#include "po_man.h"
#include "r_data/colormaps.h"
#include "d_net.h"
#include "swrenderer/r_memory.h"
#include "swrenderer/r_renderthread.h"
#include "swrenderer/drawers/r_draw.h"

View file

@ -33,7 +33,6 @@
#include "m_swap.h"
#include "a_sharedglobal.h"
#include "d_net.h"
#include "g_level.h"
#include "r_walldraw.h"
#include "v_palette.h"

View file

@ -34,7 +34,6 @@
#include "w_wad.h"
#include "stats.h"
#include "a_sharedglobal.h"
#include "d_net.h"
#include "g_level.h"
#include "r_wallsetup.h"
#include "v_palette.h"

View file

@ -33,7 +33,6 @@
#include "a_sharedglobal.h"
#include "c_console.h"
#include "cmdlib.h"
#include "d_net.h"
#include "g_level.h"
#include "g_levellocals.h"
#include "swrenderer/scene/r_opaque_pass.h"

View file

@ -34,7 +34,6 @@
#include "a_sharedglobal.h"
#include "c_console.h"
#include "cmdlib.h"
#include "d_net.h"
#include "g_level.h"
#include "a_dynlight.h"
#include "swrenderer/plane/r_visibleplane.h"

View file

@ -33,7 +33,6 @@
#include "a_sharedglobal.h"
#include "c_console.h"
#include "cmdlib.h"
#include "d_net.h"
#include "g_level.h"
#include "swrenderer/scene/r_opaque_pass.h"
#include "r_skyplane.h"

View file

@ -33,7 +33,6 @@
#include "a_sharedglobal.h"
#include "c_console.h"
#include "cmdlib.h"
#include "d_net.h"
#include "g_level.h"
#include "g_levellocals.h"
#include "swrenderer/scene/r_opaque_pass.h"

View file

@ -34,7 +34,6 @@
#include "a_sharedglobal.h"
#include "c_console.h"
#include "cmdlib.h"
#include "d_net.h"
#include "g_level.h"
#include "a_dynlight.h"
#include "swrenderer/r_memory.h"

View file

@ -34,7 +34,6 @@
#include "a_sharedglobal.h"
#include "c_console.h"
#include "cmdlib.h"
#include "d_net.h"
#include "g_level.h"
#include "a_dynlight.h"
#include "swrenderer/r_memory.h"

View file

@ -35,7 +35,6 @@
#include "c_console.h"
#include "c_dispatch.h"
#include "cmdlib.h"
#include "d_net.h"
#include "g_level.h"
#include "r_utility.h"
#include "d_player.h"

View file

@ -26,7 +26,6 @@
#include "templates.h"
#include "doomdef.h"
#include "d_net.h"
#include "doomstat.h"
#include "m_random.h"
#include "m_bbox.h"

View file

@ -34,7 +34,6 @@
#include "c_console.h"
#include "c_dispatch.h"
#include "cmdlib.h"
#include "d_net.h"
#include "g_level.h"
#include "p_effect.h"
#include "po_man.h"

View file

@ -35,7 +35,6 @@
#include "r_sky.h"
#include "po_man.h"
#include "r_data/colormaps.h"
#include "d_net.h"
#include "swrenderer/r_memory.h"
#include "swrenderer/drawers/r_draw.h"
#include "swrenderer/scene/r_3dfloors.h"

View file

@ -33,7 +33,6 @@
#include "w_wad.h"
#include "stats.h"
#include "a_sharedglobal.h"
#include "d_net.h"
#include "g_level.h"
#include "swrenderer/scene/r_opaque_pass.h"
#include "r_decal.h"

View file

@ -40,7 +40,6 @@
#include "r_sky.h"
#include "cmdlib.h"
#include "g_level.h"
#include "d_net.h"
#include "colormatcher.h"
#include "d_netinf.h"
#include "p_effect.h"

View file

@ -41,7 +41,6 @@
#include "r_sky.h"
#include "cmdlib.h"
#include "g_level.h"
#include "d_net.h"
#include "colormatcher.h"
#include "d_netinf.h"
#include "p_effect.h"

View file

@ -41,7 +41,6 @@
#include "r_sky.h"
#include "cmdlib.h"
#include "g_level.h"
#include "d_net.h"
#include "colormatcher.h"
#include "d_netinf.h"
#include "p_effect.h"

View file

@ -39,7 +39,6 @@
#include "r_data/colormaps.h"
#include "r_data/voxels.h"
#include "r_data/sprites.h"
#include "d_net.h"
#include "po_man.h"
#include "r_utility.h"
#include "i_time.h"

View file

@ -41,7 +41,6 @@
#include "r_sky.h"
#include "cmdlib.h"
#include "g_level.h"
#include "d_net.h"
#include "colormatcher.h"
#include "d_netinf.h"
#include "p_effect.h"

View file

@ -33,7 +33,6 @@
#include "a_sharedglobal.h"
#include "c_console.h"
#include "cmdlib.h"
#include "d_net.h"
#include "g_level.h"
#include "r_utility.h"
#include "swrenderer/viewport/r_viewport.h"

View file

@ -35,6 +35,8 @@
** Actual implementation is system-specific.
*/
#include <functional>
class FStartupScreen
{
public:
@ -51,7 +53,7 @@ public:
virtual void NetProgress(int count);
virtual void NetMessage(const char *format, ...); // cover for printf
virtual void NetDone();
virtual bool NetLoop(bool (*timer_callback)(void *), void *userdata);
virtual bool NetLoop(std::function<bool()> callback);
protected:
int MaxPos, CurPos, NotchPos;
};

View file

@ -31,7 +31,7 @@
#include "c_dispatch.h"
#include "d_event.h"
#include "gi.h"
#include "d_net.h"
#include "network/net.h"
#include "doomstat.h"
#include "g_level.h"
#include "g_levellocals.h"
@ -433,8 +433,8 @@ static bool CheatAddKey (cheatseq_t *cheat, uint8_t key, bool *eat)
static bool Cht_Generic (cheatseq_t *cheat)
{
Net_WriteByte (DEM_GENERICCHEAT);
Net_WriteByte (cheat->Args[0]);
network->WriteByte (DEM_GENERICCHEAT);
network->WriteByte (cheat->Args[0]);
return true;
}

View file

@ -286,3 +286,14 @@ FName::NameManager::~NameManager()
NumNames = MaxNames = 0;
memset (Buckets, -1, sizeof(Buckets));
}
//==========================================================================
//
// [TP] FName :: IsPredefined
//
//==========================================================================
bool FName::IsPredefined() const
{
return static_cast<unsigned>( Index ) < countof( PredefinedNames );
}

View file

@ -86,6 +86,9 @@ public:
bool operator > (ENamedName index) const { return Index > index; }
bool operator >= (ENamedName index) const { return Index >= index; }
// [TP]
bool IsPredefined() const;
protected:
int Index;

View file

@ -73,7 +73,6 @@
#include "utf8.h"
#include "d_main.h"
#include "d_net.h"
#include "g_game.h"
#include "i_input.h"
#include "c_dispatch.h"
@ -144,7 +143,6 @@ int sys_ostype = 0;
// PRIVATE DATA DEFINITIONS ------------------------------------------------
static ticcmd_t emptycmd;
static bool HasExited;
static WadStuff *WadList;
@ -168,20 +166,6 @@ void I_Tactile(int on, int off, int total)
on = off = total = 0;
}
//==========================================================================
//
// I_BaseTiccmd
//
// Returns an empty ticcmd. I have no idea why this should be system-
// specific.
//
//==========================================================================
ticcmd_t *I_BaseTiccmd()
{
return &emptycmd;
}
//==========================================================================
//
// I_DetectOS

View file

@ -62,16 +62,6 @@ void I_StartFrame (void);
// Can call D_PostEvent.
void I_StartTic (void);
// Asynchronous interrupt functions should maintain private queues
// that are read by the synchronous functions
// to be converted into events.
// Either returns a null ticcmd,
// or calls a loadable driver to build it.
// This ticcmd will then be modified by the gameloop
// for normal input.
ticcmd_t *I_BaseTiccmd (void);
// Called by M_Responder when quit is selected.
// Clean exit, displays sell blurb.

View file

@ -121,7 +121,7 @@ public:
void NetProgress(int count);
void NetMessage(const char *format, ...); // cover for printf
void NetDone();
bool NetLoop(bool (*timer_callback)(void *), void *userdata);
bool NetLoop(std::function<bool()> callback);
protected:
LRESULT NetMarqueeMode;
int NetMaxPos, NetCurPos;
@ -529,7 +529,7 @@ void FBasicStartupScreen :: NetProgress(int count)
//
//==========================================================================
bool FBasicStartupScreen::NetLoop(bool (*timer_callback)(void *), void *userdata)
bool FBasicStartupScreen::NetLoop(std::function<bool()> callback)
{
BOOL bRet;
MSG msg;
@ -550,7 +550,7 @@ bool FBasicStartupScreen::NetLoop(bool (*timer_callback)(void *), void *userdata
{
if (msg.message == WM_TIMER && msg.hwnd == Window && msg.wParam == 1337)
{
if (timer_callback (userdata))
if (callback())
{
KillTimer (NetStartPane, 1);
return true;

View file

@ -7,6 +7,7 @@ version "4.2"
#include "zscript/events.zs"
#include "zscript/destructible.zs"
#include "zscript/level_compatibility.zs"
#include "zscript/network.txt"
#include "zscript/actors/actor.zs"
#include "zscript/actors/checks.zs"

View file

@ -0,0 +1,4 @@
class NetSyncActor : Actor native
{
}