Something went wrong. Try again.
[WIP] This is Chocolate DOOM but ported to Zig
Something went wrong. Try again.
19 kB · 881 lines
C
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882//// Copyright(C) 1993-1996 Id Software, Inc.// Copyright(C) 2005-2014 Simon Howard//// 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 2// 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.//// DESCRIPTION:// Main loop code.//
#include <stdlib.h>#include <string.h>
#include "d_event.h"#include "d_loop.h"#include "d_ticcmd.h"
#include "i_system.h"#include "i_timer.h"#include "i_video.h"
#include "m_argv.h"#include "m_fixed.h"
#include "net_client.h"#include "net_gui.h"#include "net_io.h"#include "net_query.h"#include "net_server.h"#include "net_sdl.h"#include "net_loop.h"
// The complete set of data for a particular tic.
typedef struct{ ticcmd_t cmds[NET_MAXPLAYERS]; boolean ingame[NET_MAXPLAYERS];} ticcmd_set_t;
// Maximum time that we wait in TryRunTics() for netgame data to be// received before we bail out and render a frame anyway.// Vanilla Doom used 20 for this value, but we use a smaller value// instead for better responsiveness of the menu when we're stuck.#define MAX_NETGAME_STALL_TICS 5
//// gametic is the tic about to (or currently being) run// maketic is the tic that hasn't had control made for it yet// recvtic is the latest tic received from the server.//// a gametic cannot be run until ticcmds are received for it// from all players.//
static ticcmd_set_t ticdata[BACKUPTICS];
// The index of the next tic to be made (with a call to BuildTiccmd).
static int maketic;
// The number of complete tics received from the server so far.
static int recvtic;
// The number of tics that have been run (using RunTic) so far.
int gametic;
// When set to true, a single tic is run each time TryRunTics() is called.// This is used for -timedemo mode.
boolean singletics = false;
// Index of the local player.
static int localplayer;
// Used for original sync code.
static int skiptics = 0;
// Reduce the bandwidth needed by sampling game input less and transmitting// less. If ticdup is 2, sample half normal, 3 = one third normal, etc.
int ticdup;
// Amount to offset the timer for game sync.
fixed_t offsetms;
// Use new client syncronisation code
static boolean new_sync = true;
// Callback functions for loop code.
static loop_interface_t *loop_interface = NULL;
// Current players in the multiplayer game.// This is distinct from playeringame[] used by the game code, which may// modify playeringame[] when playing back multiplayer demos.
static boolean local_playeringame[NET_MAXPLAYERS];
// Requested player class "sent" to the server on connect.// If we are only doing a single player game then this needs to be remembered// and saved in the game settings.
static int player_class;
// 35 fps clock adjusted by offsetms milliseconds
static int GetAdjustedTime(void){ int time_ms;
time_ms = I_GetTimeMS();
if (new_sync) { // Use the adjustments from net_client.c only if we are // using the new sync mode.
time_ms += (offsetms / FRACUNIT); }
return (time_ms * TICRATE) / 1000;}
static boolean BuildNewTic(void){ int gameticdiv; ticcmd_t cmd;
gameticdiv = gametic/ticdup;
I_StartTic (); loop_interface->ProcessEvents();
// Always run the menu
loop_interface->RunMenu();
if (drone) { // In drone mode, do not generate any ticcmds.
return false; }
if (new_sync) { // If playing single player, do not allow tics to buffer // up very far
if (!net_client_connected && maketic - gameticdiv > 2) return false;
// Never go more than ~200ms ahead
if (maketic - gameticdiv > 8) return false; } else { if (maketic - gameticdiv >= 5) return false; }
//printf ("mk:%i ",maketic); memset(&cmd, 0, sizeof(ticcmd_t)); loop_interface->BuildTiccmd(&cmd, maketic);
if (net_client_connected) { NET_CL_SendTiccmd(&cmd, maketic); }
ticdata[maketic % BACKUPTICS].cmds[localplayer] = cmd; ticdata[maketic % BACKUPTICS].ingame[localplayer] = true;
++maketic;
return true;}
//// NetUpdate// Builds ticcmds for console player,// sends out a packet//int lasttime;
void NetUpdate (void){ int nowtime; int newtics; int i;
// If we are running with singletics (timing a demo), this // is all done separately.
if (singletics) return;
// Run network subsystems
NET_CL_Run(); NET_SV_Run();
// check time nowtime = GetAdjustedTime() / ticdup; newtics = nowtime - lasttime;
lasttime = nowtime;
if (skiptics <= newtics) { newtics -= skiptics; skiptics = 0; } else { skiptics -= newtics; newtics = 0; }
// build new ticcmds for console player
for (i=0 ; i<newtics ; i++) { if (!BuildNewTic()) { break; } }}
static void D_Disconnected(void){ // In drone mode, the game cannot continue once disconnected.
if (drone) { I_Error("Disconnected from server in drone mode."); }
// disconnected from server
printf("Disconnected from server.\n");}
//// Invoked by the network engine when a complete set of ticcmds is// available.//
void D_ReceiveTic(ticcmd_t *ticcmds, boolean *players_mask){ int i;
// Disconnected from server?
if (ticcmds == NULL && players_mask == NULL) { D_Disconnected(); return; }
for (i = 0; i < NET_MAXPLAYERS; ++i) { if (!drone && i == localplayer) { // This is us. Don't overwrite it. } else { ticdata[recvtic % BACKUPTICS].cmds[i] = ticcmds[i]; ticdata[recvtic % BACKUPTICS].ingame[i] = players_mask[i]; } }
++recvtic;}
//// Start game loop//// Called after the screen is set but before the game starts running.//
void D_StartGameLoop(void){ lasttime = GetAdjustedTime() / ticdup;}
//// Block until the game start message is received from the server.//
static void BlockUntilStart(net_gamesettings_t *settings, netgame_startup_callback_t callback){ while (!NET_CL_GetSettings(settings)) { NET_CL_Run(); NET_SV_Run();
if (!net_client_connected) { I_Error("Lost connection to server"); }
if (callback != NULL && !callback(net_client_wait_data.ready_players, net_client_wait_data.num_players)) { I_Error("Netgame startup aborted."); }
I_Sleep(100); }}
void D_StartNetGame(net_gamesettings_t *settings, netgame_startup_callback_t callback){ int i;
offsetms = 0; recvtic = 0;
settings->consoleplayer = 0; settings->num_players = 1; settings->player_classes[0] = player_class;
//! // @category net // // Use original network client sync code rather than the improved // sync code. // settings->new_sync = !M_ParmExists("-oldsync");
//! // @category net // @arg <n> // // Send n extra tics in every packet as insurance against dropped // packets. //
i = M_CheckParmWithArgs("-extratics", 1);
if (i > 0) settings->extratics = atoi(myargv[i+1]); else settings->extratics = 1;
//! // @category net // @arg <n> // // Reduce the resolution of the game by a factor of n, reducing // the amount of network bandwidth needed. //
i = M_CheckParmWithArgs("-dup", 1);
if (i > 0) settings->ticdup = atoi(myargv[i+1]); else settings->ticdup = 1;
if (net_client_connected) { // Send our game settings and block until game start is received // from the server.
NET_CL_StartGame(settings); BlockUntilStart(settings, callback);
// Read the game settings that were received.
NET_CL_GetSettings(settings); }
if (drone) { settings->consoleplayer = 0; }
// Set the local player and playeringame[] values.
localplayer = settings->consoleplayer;
for (i = 0; i < NET_MAXPLAYERS; ++i) { local_playeringame[i] = i < settings->num_players; }
// Copy settings to global variables.
ticdup = settings->ticdup; new_sync = settings->new_sync;
if (ticdup < 1) { I_Error("D_StartNetGame: invalid ticdup value (%d)", ticdup); }
// TODO: Message disabled until we fix new_sync. //if (!new_sync) //{ // printf("Syncing netgames like Vanilla Doom.\n"); //}}
boolean D_InitNetGame(net_connect_data_t *connect_data){ boolean result = false; net_addr_t *addr = NULL; int i;
// Call D_QuitNetGame on exit:
I_AtExit(D_QuitNetGame, true);
player_class = connect_data->player_class;
//! // @category net // // Start a multiplayer server, listening for connections. //
if (M_CheckParm("-server") > 0 || M_CheckParm("-privateserver") > 0) { NET_SV_Init(); NET_SV_AddModule(&net_loop_server_module); NET_SV_AddModule(&net_sdl_module); NET_SV_RegisterWithMaster();
net_loop_client_module.InitClient(); addr = net_loop_client_module.ResolveAddress(NULL); NET_ReferenceAddress(addr); } else { //! // @category net // // Automatically search the local LAN for a multiplayer // server and join it. //
i = M_CheckParm("-autojoin");
if (i > 0) { addr = NET_FindLANServer();
if (addr == NULL) { I_Error("No server found on local LAN"); } }
//! // @arg <address> // @category net // // Connect to a multiplayer server running on the given // address. //
i = M_CheckParmWithArgs("-connect", 1);
if (i > 0) { net_sdl_module.InitClient(); addr = net_sdl_module.ResolveAddress(myargv[i+1]); NET_ReferenceAddress(addr);
if (addr == NULL) { I_Error("Unable to resolve '%s'\n", myargv[i+1]); } } }
if (addr != NULL) { if (M_CheckParm("-drone") > 0) { connect_data->drone = true; }
if (!NET_CL_Connect(addr, connect_data)) { I_Error("D_InitNetGame: Failed to connect to %s:\n%s\n", NET_AddrToString(addr), net_client_reject_reason); }
printf("D_InitNetGame: Connected to %s\n", NET_AddrToString(addr)); NET_ReleaseAddress(addr);
// Wait for launch message received from server.
NET_WaitForLaunch();
result = true; }
return result;}
//// D_QuitNetGame// Called before quitting to leave a net game// without hanging the other players//void D_QuitNetGame (void){ NET_SV_Shutdown(); NET_CL_Disconnect();}
static int GetLowTic(void){ int lowtic;
lowtic = maketic;
if (net_client_connected) { if (drone || recvtic < lowtic) { lowtic = recvtic; } }
return lowtic;}
static int frameon;static int frameskip[4];static int oldnettics;
static void OldNetSync(void){ unsigned int i; int keyplayer = -1;
frameon++;
// ideally maketic should be 1 - 3 tics above lowtic // if we are consistantly slower, speed up time
for (i=0 ; i<NET_MAXPLAYERS ; i++) { if (local_playeringame[i]) { keyplayer = i; break; } }
if (keyplayer < 0) { // If there are no players, we can never advance anyway
return; }
if (localplayer == keyplayer) { // the key player does not adapt } else { if (maketic <= recvtic) { lasttime--; // printf ("-"); }
frameskip[frameon & 3] = oldnettics > recvtic; oldnettics = maketic;
if (frameskip[0] && frameskip[1] && frameskip[2] && frameskip[3]) { skiptics = 1; // printf ("+"); } }}
// Returns true if there are players in the game:
static boolean PlayersInGame(void){ boolean result = false; unsigned int i;
// If we are connected to a server, check if there are any players // in the game.
if (net_client_connected) { for (i = 0; i < NET_MAXPLAYERS; ++i) { result = result || local_playeringame[i]; } }
// Whether single or multi-player, unless we are running as a drone, // we are in the game.
if (!drone) { result = true; }
return result;}
// When using ticdup, certain values must be cleared out when running// the duplicate ticcmds.
static void TicdupSquash(ticcmd_set_t *set){ ticcmd_t *cmd; unsigned int i;
for (i = 0; i < NET_MAXPLAYERS ; ++i) { cmd = &set->cmds[i]; cmd->chatchar = 0; if (cmd->buttons & BT_SPECIAL) cmd->buttons = 0; }}
// When running in single player mode, clear all the ingame[] array// except the local player.
static void SinglePlayerClear(ticcmd_set_t *set){ unsigned int i;
for (i = 0; i < NET_MAXPLAYERS; ++i) { if (i != localplayer) { set->ingame[i] = false; } }}
//// TryRunTics//
void TryRunTics (void){ int i; int lowtic; int entertic; static int oldentertics; int realtics; int availabletics; int counts;
// get real tics entertic = I_GetTime() / ticdup; realtics = entertic - oldentertics; oldentertics = entertic;
// in singletics mode, run a single tic every time this function // is called.
if (singletics) { BuildNewTic(); } else { NetUpdate (); }
lowtic = GetLowTic();
availabletics = lowtic - gametic/ticdup;
// decide how many tics to run
if (new_sync) { counts = availabletics; } else { // decide how many tics to run if (realtics < availabletics-1) counts = realtics+1; else if (realtics < availabletics) counts = realtics; else counts = availabletics;
if (counts < 1) counts = 1;
if (net_client_connected) { OldNetSync(); } }
if (counts < 1) counts = 1;
// wait for new tics if needed while (!PlayersInGame() || lowtic < gametic/ticdup + counts) { NetUpdate ();
lowtic = GetLowTic();
if (lowtic < gametic/ticdup) I_Error ("TryRunTics: lowtic < gametic");
// Still no tics to run? Sleep until some are available. if (lowtic < gametic/ticdup + counts) { // If we're in a netgame, we might spin forever waiting for // new network data to be received. So don't stay in here // forever - give the menu a chance to work. if (I_GetTime() / ticdup - entertic >= MAX_NETGAME_STALL_TICS) { return; }
I_Sleep(1); } }
// run the count * ticdup dics while (counts--) { ticcmd_set_t *set;
if (!PlayersInGame()) { return; }
set = &ticdata[(gametic / ticdup) % BACKUPTICS];
if (!net_client_connected) { SinglePlayerClear(set); }
for (i=0 ; i<ticdup ; i++) { if (gametic/ticdup > lowtic) I_Error ("gametic>lowtic");
memcpy(local_playeringame, set->ingame, sizeof(local_playeringame));
loop_interface->RunTic(set->cmds, set->ingame); gametic++;
// modify command for duplicated tics
TicdupSquash(set); }
NetUpdate (); // check for new console commands }}
void D_RegisterLoopCallbacks(loop_interface_t *i){ loop_interface = i;}
// TODO: Move nonvanilla demo functions into a dedicated file.#include "m_misc.h"#include "w_wad.h"
static boolean StrictDemos(void){ //! // @category demo // // When recording or playing back demos, disable any extensions // of the vanilla demo format - record demos as vanilla would do, // and play back demos as vanilla would do. // return M_ParmExists("-strictdemos");}
// If the provided conditional value is true, we're trying to record// a demo file that will include a non-vanilla extension. The function// will return true if the conditional is true and it's allowed to use// this extension (no extensions are allowed if -strictdemos is given// on the command line). A warning is shown on the console using the// provided string describing the non-vanilla expansion.boolean D_NonVanillaRecord(boolean conditional, const char *feature){ if (!conditional || StrictDemos()) { return false; }
printf("Warning: Recording a demo file with a non-vanilla extension " "(%s). Use -strictdemos to disable this extension.\n", feature);
return true;}
// Returns true if the given lump number corresponds to data from a .lmp// file, as opposed to a WAD.static boolean IsDemoFile(int lumpnum){ char *lower; boolean result;
lower = M_StringDuplicate(lumpinfo[lumpnum]->wad_file->path); M_ForceLowercase(lower); result = M_StringEndsWith(lower, ".lmp"); free(lower);
return result;}
// If the provided conditional value is true, we're trying to play back// a demo that includes a non-vanilla extension. We return true if the// conditional is true and it's allowed to use this extension, checking// that:// - The -strictdemos command line argument is not provided.// - The given lumpnum identifying the demo to play back identifies a// demo that comes from a .lmp file, not a .wad file.// - Before proceeding, a warning is shown to the user on the console.boolean D_NonVanillaPlayback(boolean conditional, int lumpnum, const char *feature){ if (!conditional || StrictDemos()) { return false; }
if (!IsDemoFile(lumpnum)) { printf("Warning: WAD contains demo with a non-vanilla extension " "(%s)\n", feature); return false; }
printf("Warning: Playing back a demo file with a non-vanilla extension " "(%s). Use -strictdemos to disable this extension.\n", feature);
return true;}