📄 sv_main.pas
字号:
begin
while (NET_GetPacket (NS_SERVER, net_from, net_message)) do begin
// check for connectionless packet ($ffffffff) first
if (PInteger(net_message.data)^ = -1) then begin
SV_ConnectionlessPacket ();
continue;
end;
// read the qport out of the message so we can fix up
// stupid address translating routers
MSG_BeginReading (net_message);
MSG_ReadLong (net_message); // sequence number
MSG_ReadLong (net_message); // sequence number
qport := MSG_ReadShort (net_message) and $ffff;
// check for packets from connected clients
for i := 0 to Round(maxclients^.value)-1 do begin
cl := @svs.clients^[i];
if (cl^.state = cs_free) then
continue;
if (not NET_CompareBaseAdr (net_from, cl^.netchan.remote_address)) then
continue;
if (cl^.netchan.qport <> qport) then
continue;
if (cl^.netchan.remote_address.port <> net_from.port) then begin
Com_Printf ('SV_ReadPackets: fixing up a translated port'#10);
cl^.netchan.remote_address.port := net_from.port;
end;
if (Netchan_Process(cl^.netchan, net_message)) then begin
// this is a valid, sequenced packet, so process it
if (cl^.state <> cs_zombie) then begin
cl^.lastmessage := svs.realtime; // don't timeout
SV_ExecuteClientMessage (cl);
end;
end;
break;
end;
if (i <> maxclients^.value) then
continue;
end;
end;
(*
==================
SV_CheckTimeouts
If a packet has not been received from a client for timeout^.value
seconds, drop the conneciton. Server frames are used instead of
realtime to aprocedure dropping the local client while debugging.
When a client is normally dropped, the client_t goes into a zombie state
for a few seconds to make sure any final reliable message gets resent
if necessary
==================
*)
procedure SV_CheckTimeouts ();
var
i: integer;
cl: client_p;
droppoint: integer;
zombiepoint: integer;
begin
droppoint := svs.realtime - Round(1000*timeout^.value);
zombiepoint := svs.realtime - Round(1000*zombietime^.value);
for i := 0 to round(maxclients^.value)-1 do begin
cl := @svs.clients^[i];
// message times may be wrong across a changelevel
if (cl^.lastmessage > svs.realtime) then
cl^.lastmessage := svs.realtime;
if (cl^.state = cs_zombie) and (cl^.lastmessage < zombiepoint) then begin
cl^.state := cs_free; // can now be reused
continue;
end;
if ((cl^.state = cs_connected) or (cl^.state = cs_spawned)) and
(cl^.lastmessage < droppoint) then begin
SV_BroadcastPrintf (PRINT_HIGH, '%s timed out'#10, [cl^.name]);
SV_DropClient (cl);
cl^.state := cs_free; // don't bother with zombie state
end;
end;
end;
(*
================
SV_PrepWorldFrame
This has to be done before the world logic, because
player processing happens outside RunWorldFrame
================
*)
procedure SV_PrepWorldFrame ();
var
ent: edict_p;
i: integer;
begin
for i := 0 to ge^.num_edicts-1 do begin
ent := EDICT_NUM(i);
// events only last for a single message
ent^.s.event := entity_event_t(0);
Inc(ent);
end;
end;
(*
=================
SV_RunGameFrame
=================
*)
procedure SV_RunGameFrame ();
begin
if (host_speeds^.value <> 0) then
time_before_game := Sys_Milliseconds ();
// we always need to bump framenum, even if we
// don't run the world, otherwise the delta
// compression can get confused when a client
// has the "current" frame
Inc(sv.framenum);
sv.time := sv.framenum*100;
// don't run if paused
if (sv_paused^.value=0) or (maxclients^.value > 1) then begin
ge^.RunFrame ();
// never get more than one tic behind
if (sv.time < svs.realtime) then begin
if (sv_showclamp^.value <> 0) then
Com_Printf ('sv highclamp'#10);
svs.realtime := sv.time;
end;
end;
if (host_speeds^.value<>0) then
time_after_game := Sys_Milliseconds ();
end;
(*
==================
SV_Frame
==================
*)
procedure SV_Frame (msec: integer);
begin
time_before_game := 0;
time_after_game := 0;
// if server is not active, do nothing
if (not svs.initialized) then
exit;
svs.realtime := svs.realtime + msec;
// keep the random time dependent
Random;
// check timeouts
SV_CheckTimeouts ();
// get packets from clients
SV_ReadPackets ();
// move autonomous things around if enough time has passed
if (sv_timedemo^.value=0) and (svs.realtime < sv.time) then begin
// never let the time get too far off
if (sv.time - svs.realtime > 100) then begin
if (sv_showclamp^.value <> 0) then
Com_Printf ('sv lowclamp'#10);
svs.realtime := sv.time - 100;
end;
NET_Sleep(sv.time - svs.realtime);
exit;
end;
// update ping based on the last known frame from all clients
SV_CalcPings ();
// give the clients some timeslices
SV_GiveMsec ();
// let everything in the world think and move
SV_RunGameFrame ();
// send messages back to the clients that had packets read this frame
SV_SendClientMessages ();
// save the entire world state if recording a serverdemo
SV_RecordDemoMessage ();
// send a heartbeat to the master if needed
Master_Heartbeat ();
// clear teleport flags, etc for next frame
SV_PrepWorldFrame ();
end;
//============================================================================
(*
================
Master_Heartbeat
Send a message to the master every few minutes to
let it know we are alive, and log information
================
*)
procedure Master_Heartbeat ();
var
string_: pchar;
i: integer;
begin
// pgm post3.19 change, cvar pointer not validated before dereferencing
if (dedicated = nil) or (dedicated^.value = 0) then
exit; // only dedicated servers send heartbeats
// pgm post3.19 change, cvar pointer not validated before dereferencing
if (public_server = nil) or (public_server^.value = 0) then
exit; // a private dedicated game
// check for time wraparound
if (svs.last_heartbeat > svs.realtime) then
svs.last_heartbeat := svs.realtime;
if (svs.realtime - svs.last_heartbeat < HEARTBEAT_SECONDS*1000) then
exit; // not time to send yet
svs.last_heartbeat := svs.realtime;
// send the same string that we would give for a status OOB command
string_ := SV_StatusString();
// send to group master
for i := 0 to MAX_MASTERS-1 do
if (master_adr[i].port<>0) then begin
Com_Printf ('Sending heartbeat to %s'#10, [NET_AdrToString (master_adr[i])]);
Netchan_OutOfBandPrint (NS_SERVER, master_adr[i], 'heartbeat'#10'%s', [string_]);
end;
end;
(*
=================
Master_Shutdown
Informs all masters that this server is going down
=================
*)
procedure Master_Shutdown ();
var
i: integer;
begin
// pgm post3.19 change, cvar pointer not validated before dereferencing
if (dedicated=nil) or (dedicated^.value = 0) then
exit; // only dedicated servers send heartbeats
// pgm post3.19 change, cvar pointer not validated before dereferencing
if (public_server=nil) or (public_server^.value=0) then
exit; // a private dedicated game
// send to group master
for i := 0 to MAX_MASTERS-1 do
if (master_adr[i].port<>0) then begin
if (i > 0) then
Com_Printf ('Sending heartbeat to %s'#10, [NET_AdrToString (master_adr[i])]);
Netchan_OutOfBandPrint (NS_SERVER, master_adr[i], 'shutdown', []);
end;
end;
//============================================================================
(*
=================
SV_UserinfoChanged
Pull specific info from a newly changed userinfo string
into a more C freindly form.
=================
*)
procedure SV_UserinfoChanged (cl: client_p);
var
val: pchar;
i: integer;
begin
// call prog code to allow overrides
ge^.ClientUserinfoChanged (cl^.edict, cl^.userinfo);
// name for C code
strncpy (cl^.name, Info_ValueForKey (cl^.userinfo, 'name'), sizeof(cl^.name)-1);
// mask off high bit
for i := 0 to sizeof(cl^.name)-1 do
cl^.name[i] := char(byte(cl^.name[i]) and 127);
// rate command
val := Info_ValueForKey (cl^.userinfo, 'rate');
if (strlen(val)<>0) then begin
i := StrToInt(val);
cl^.rate := i;
if (cl^.rate < 100) then
cl^.rate := 100;
if (cl^.rate > 15000) then
cl^.rate := 15000;
end
else
cl^.rate := 5000;
// msg command
val := Info_ValueForKey (cl^.userinfo, 'msg');
if (strlen(val)<>0) then begin
cl^.messagelevel := StrToInt(val);
end;
end;
//============================================================================
(*
===============
SV_Init
Only called at quake2.exe startup, not for each game
===============
*)
procedure SV_Init ();
begin
SV_InitOperatorCommands ();
rcon_password := Cvar_Get ('rcon_password', '', 0);
Cvar_Get ('skill', '1', 0);
Cvar_Get ('deathmatch', '0', CVAR_LATCH);
Cvar_Get ('coop', '0', CVAR_LATCH);
Cvar_Get ('dmflags', va('%d', [DF_INSTANT_ITEMS]), CVAR_SERVERINFO);
Cvar_Get ('fraglimit', '0', CVAR_SERVERINFO);
Cvar_Get ('timelimit', '0', CVAR_SERVERINFO);
Cvar_Get ('cheats', '0', CVAR_SERVERINFO or CVAR_LATCH);
Cvar_Get ('protocol', va('%d', [PROTOCOL_VERSION]), CVAR_SERVERINFO or CVAR_NOSET);;
maxclients := Cvar_Get ('maxclients', '1', CVAR_SERVERINFO or CVAR_LATCH);
hostname := Cvar_Get ('hostname', 'noname', CVAR_SERVERINFO or CVAR_ARCHIVE);
timeout := Cvar_Get ('timeout', '125', 0);
zombietime := Cvar_Get ('zombietime', '2', 0);
sv_showclamp := Cvar_Get ('showclamp', '0', 0);
sv_paused := Cvar_Get ('paused', '0', 0);
sv_timedemo := Cvar_Get ('timedemo', '0', 0);
sv_enforcetime := Cvar_Get ('sv_enforcetime', '0', 0);
allow_download := Cvar_Get ('allow_download', '1', CVAR_ARCHIVE);
allow_download_players := Cvar_Get ('allow_download_players', '0', CVAR_ARCHIVE);
allow_download_models := Cvar_Get ('allow_download_models', '1', CVAR_ARCHIVE);
allow_download_sounds := Cvar_Get ('allow_download_sounds', '1', CVAR_ARCHIVE);
allow_download_maps := Cvar_Get ('allow_download_maps', '1', CVAR_ARCHIVE);
sv_noreload := Cvar_Get ('sv_noreload', '0', 0);
sv_airaccelerate := Cvar_Get('sv_airaccelerate', '0', CVAR_LATCH);
public_server := Cvar_Get ('public', '0', 0);
sv_reconnect_limit := Cvar_Get ('sv_reconnect_limit', '3', CVAR_ARCHIVE);
SZ_Init (net_message, @net_message_buffer, sizeof(net_message_buffer));
end;
(*
==================
SV_FinalMessage
Used by SV_Shutdown to send a final message to all
connected clients before the server goes down. The messages are sent immediately,
not just stuck on the outgoing message list, because the server is going
to totally exit after returning from this function.
==================
*)
procedure SV_FinalMessage (message_: pchar; reconnect: qboolean);
var
i: integer;
cl: client_p;
begin
SZ_Clear (net_message);
MSG_WriteByte (net_message, Integer(svc_print));
MSG_WriteByte (net_message, PRINT_HIGH);
MSG_WriteString (net_message, message_);
if (reconnect) then
MSG_WriteByte (net_message, Integer(svc_reconnect))
else
MSG_WriteByte (net_message, Integer(svc_disconnect));
// send it twice
// stagger the packets to crutch operating system limited buffers
for i := 0 to round(maxclients^.value)-1 do begin
cl := @svs.clients^[i];
if (cl^.state >= cs_connected) then
Netchan_Transmit (cl^.netchan, net_message.cursize, PByte(net_message.data));
end;
for i := 0 to Round(maxclients^.value)-1 do begin
cl := @svs.clients^[i];
if (cl^.state >= cs_connected) then
Netchan_Transmit (cl^.netchan, net_message.cursize, PByte(net_message.data));
end;
end;
(*
================
SV_Shutdown
Called when each game quits,
before Sys_Quit or Sys_Error
================
*)
procedure SV_Shutdown (finalmsg: pchar; reconnect: qboolean);
begin
if (svs.clients <> nil) then
SV_FinalMessage (finalmsg, reconnect);
Master_Shutdown ();
SV_ShutdownGameProgs ();
// free current level
if (sv.demofile>0) then
FileClose (sv.demofile);
sv.demofile := 0;
FillChar (sv, sizeof(sv), #0);
Com_SetServerState (Integer(sv.state));
// free server static data
if (svs.clients <> nil) then
Z_Free (svs.clients);
if (svs.client_entities <> nil) then
Z_Free (svs.client_entities);
if (svs.demofile > 0) then
FileClose (svs.demofile);
svs.demofile := 0;
FillChar (svs, sizeof(svs), 0);
end;
end.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -