diff options
Diffstat (limited to 'src/d_loop.c')
-rw-r--r-- | src/d_loop.c | 752 |
1 files changed, 752 insertions, 0 deletions
diff --git a/src/d_loop.c b/src/d_loop.c new file mode 100644 index 00000000..0206e85a --- /dev/null +++ b/src/d_loop.c @@ -0,0 +1,752 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 1993-1996 Id Software, Inc. +// Copyright(C) 2005 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. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// Main loop code. +// +//----------------------------------------------------------------------------- + +#include <stdlib.h> +#include <string.h> + +#include "doomfeatures.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; + +// +// 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]; + + +// 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->ProcesEvents(); + + // 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); + +#ifdef FEATURE_MULTIPLAYER + + if (net_client_connected) + { + NET_CL_SendTiccmd(&cmd, maketic); + } + +#endif + 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; + +#ifdef FEATURE_MULTIPLAYER + + // Run network subsystems + + NET_CL_Run(); + NET_SV_Run(); + +#endif + + // 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; +} + +boolean D_InitNetGame(net_connect_data_t *connect_data, + net_gamesettings_t *settings) +{ + net_addr_t *addr = NULL; + boolean result = false; + int i; + + offsetms = 0; + recvtic = 0; + + settings->consoleplayer = 0; + settings->num_players = 1; + + //! + // @category net + // + // Use original game sync code. + // + + if (M_CheckParm("-oldsync") > 0) + settings->new_sync = 0; + else + settings->new_sync = 1; + + //! + // @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; + +#ifdef FEATURE_MULTIPLAYER + + //! + // @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); + } + 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]); + + 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_CheckNetGame: Failed to connect to %s\n", + NET_AddrToString(addr)); + } + + printf("D_CheckNetGame: Connected to %s\n", NET_AddrToString(addr)); + + // Wait for game start message received from server. + + NET_WaitForStart(settings); + + // Read the game settings that were received. + + NET_CL_GetSettings(settings); + + result = true; + } + +#endif + + // Set the local player and playeringame[] values. + + localplayer = settings->consoleplayer; + + for (i = 0; i < NET_MAXPLAYERS; ++i) + { + local_playeringame[i] = i < settings->num_players; + } + + // Check for sync mode. + + new_sync = settings->new_sync; + + if (new_sync == false) + { + printf("Syncing netgames like Vanilla Doom.\n"); + } + + return result; +} + + +// +// D_QuitNetGame +// Called before quitting to leave a net game +// without hanging the other players +// +void D_QuitNetGame (void) +{ +#ifdef FEATURE_MULTIPLAYER + + NET_SV_Shutdown(); + NET_CL_Disconnect(); + +#endif + +} + +static int GetLowTic(void) +{ + int lowtic; + + lowtic = maketic; + +#ifdef FEATURE_MULTIPLAYER + if (net_client_connected) + { + if (drone || recvtic < lowtic) + { + lowtic = recvtic; + } + } +#endif + + return lowtic; +} + +static int frameon; +static int frameskip[4]; +static int oldnettics; + +static void OldNetSync(void) +{ + unsigned int i; + unsigned 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"); + + // Don't stay in this loop forever. The menu is still running, + // so return to update the screen + + if (I_GetTime() / ticdup - entertic > 0) + { + 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; +} |