diff --git a/src/game/g_netmsg.pas b/src/game/g_netmsg.pas
index ec78e3020b83f0328be0831527027955a9ca6b4f..52e7ae64e0fa0d563a40b52ca1e1fb076e9b89b3 100644 (file)
--- a/src/game/g_netmsg.pas
+++ b/src/game/g_netmsg.pas
*
* 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.
+ * the Free Software Foundation, version 3 of the License ONLY.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
NET_MSG_TIME_SYNC = 194;
NET_MSG_VOTE_EVENT = 195;
+ {
NET_MSG_MAP_REQUEST = 201;
NET_MSG_MAP_RESPONSE = 202;
NET_MSG_RES_REQUEST = 203;
NET_MSG_RES_RESPONSE = 204;
+ }
NET_CHAT_SYSTEM = 0;
NET_CHAT_PLAYER = 1;
// HOST MESSAGES
+procedure MH_ProcessFirstSpawn (C: pTNetClient);
+
procedure MH_RECV_Info(C: pTNetClient; var M: TMsg);
procedure MH_RECV_Chat(C: pTNetClient; var M: TMsg);
procedure MH_RECV_FullStateRequest(C: pTNetClient; var M: TMsg);
procedure MH_RECV_CheatRequest(C: pTNetClient; var M: TMsg);
procedure MH_RECV_RCONPassword(C: pTNetClient; var M: TMsg);
procedure MH_RECV_RCONCommand(C: pTNetClient; var M: TMsg);
-procedure MH_RECV_MapRequest(C: pTNetClient; var M: TMsg);
-procedure MH_RECV_ResRequest(C: pTNetClient; var M: TMsg);
+//procedure MH_RECV_MapRequest(C: pTNetClient; var M: TMsg);
+//procedure MH_RECV_ResRequest(C: pTNetClient; var M: TMsg);
procedure MH_RECV_Vote(C: pTNetClient; var M: TMsg);
// GAME
-procedure MH_SEND_Everything(CreatePlayers: Boolean = False; ID: Integer = NET_EVERYONE);
+procedure MH_SEND_Everything(CreatePlayers: Boolean {= False}; ID: Integer {= NET_EVERYONE});
procedure MH_SEND_Info(ID: Byte);
procedure MH_SEND_Chat(Txt: string; Mode: Byte; ID: Integer = NET_EVERYONE);
procedure MH_SEND_Effect(X, Y: Integer; Ang: SmallInt; Kind: Byte; ID: Integer = NET_EVERYONE);
procedure MC_SEND_RCONCommand(Cmd: string);
procedure MC_SEND_Vote(Start: Boolean = False; Command: string = 'a');
// DOWNLOAD
-procedure MC_SEND_MapRequest();
-procedure MC_SEND_ResRequest(const resName: AnsiString);
+//procedure MC_SEND_MapRequest();
+//procedure MC_SEND_ResRequest(const resName: AnsiString);
+
type
TExternalResourceInfo = record
ExternalResources: array of TExternalResourceInfo;
end;
-function MapDataFromMsgStream(msgStream: TMemoryStream):TMapDataMsg;
-function ResDataFromMsgStream(msgStream: TMemoryStream):TResDataMsg;
+function IsValidFileName(const S: String): Boolean;
+function IsValidFilePath(const S: String): Boolean;
+
implementation
//kDirPrev: TDirection = D_LEFT;
//HostGameTime: Word = 0;
+
+function IsValidFileName(const S: String): Boolean;
+const
+ Forbidden: set of Char = ['<', '>', '|', '"', ':', '*', '?'];
+var
+ I: Integer;
+begin
+ Result := S <> '';
+ for I := 1 to Length(S) do
+ Result := Result and (not(S[I] in Forbidden));
+end;
+
+function IsValidFilePath(const S: String): Boolean;
+var
+ I: Integer;
+begin
+ Result := False;
+ if not IsValidFileName(S) then exit;
+ if FileExists(S) then exit;
+ I := LastDelimiter('\/', S);
+ if (I > 0) then
+ if (not DirectoryExists(Copy(S, 1, I-1))) then
+ exit;
+ Result := True;
+end;
+
+
// HOST MESSAGES //
end;
C^.Player := PID;
+ C^.WaitForFirstSpawn := false;
g_Console_Add(Format(_lc[I_PLAYER_JOIN], [PName]), True);
e_WriteLog('NET: Client ' + PName + ' [' + IntToStr(C^.ID) +
FNoRespawn := True;
Spectate;
FWantsInGame := True; // TODO: look into this later
+ C^.WaitForFirstSpawn := true;
end
else
- Respawn(gGameSettings.GameType = GT_SINGLE);
+ begin
+ e_LogWritefln('*** client #%u (cid #%u) authenticated...', [C.ID, C.Player]);
+ //e_LogWritefln('spawning player with pid #%u...', [PID]);
+ //Respawn(gGameSettings.GameType = GT_SINGLE);
+ //k8: no, do not spawn a player yet, wait for "request full state" packet
+ Lives := 0;
+ Spectate;
+ FNoRespawn := True;
+ // `FWantsInGame` seems to mean "spawn the player on the next occasion".
+ // that is, if we'll set it to `true`, the player can be spawned after
+ // warmup time ran out, for example, regardless of the real player state.
+ // also, this seems to work only for the initial connection. further
+ // map changes could initiate resource downloading, but the player will
+ // be spawned immediately.
+ // the proper solution will require another player state, "ephemeral".
+ // the player should start any map in "ephemeral" state, and turned into
+ // real mobj only when they sent a special "i am ready" packet. this packet
+ // must be sent after receiving the full state, so the player will get a full
+ // map view before going into game.
+ FWantsInGame := false;
+ C^.WaitForFirstSpawn := true;
+ end;
end;
- for I := Low(NetClients) to High(NetClients) do
+ //if not C^.WaitForFirstSpawn then
begin
- if NetClients[I].ID = C^.ID then Continue;
- MH_SEND_PlayerCreate(PID, NetClients[I].ID);
- MH_SEND_PlayerPos(True, PID, NetClients[I].ID);
- MH_SEND_PlayerStats(PID, NetClients[I].ID);
+ for I := Low(NetClients) to High(NetClients) do
+ begin
+ if NetClients[I].ID = C^.ID then Continue;
+ MH_SEND_PlayerCreate(PID, NetClients[I].ID);
+ MH_SEND_PlayerPos(True, PID, NetClients[I].ID);
+ MH_SEND_PlayerStats(PID, NetClients[I].ID);
+ end;
end;
if gState in [STATE_INTERCUSTOM, STATE_FOLD] then
MH_SEND_GameEvent(NET_EV_MAPEND, 0, 'N', C^.ID);
- if NetUseMaster then g_Net_Slist_Update;
+ if NetUseMaster then
+ begin
+ //g_Net_Slist_Update;
+ g_Net_Slist_Pulse();
+ end;
+end;
+
+
+procedure MH_ProcessFirstSpawn (C: pTNetClient);
+var
+ plr: TPlayer;
+begin
+ if not C.WaitForFirstSpawn then exit;
+ plr := g_Player_Get(C^.Player);
+ if not assigned(plr) then exit;
+ g_Net_Slist_ServerPlayerComes();
+ e_LogWritefln('*** client #%u (cid #%u) first spawn', [C.ID, C.Player]);
+ C.WaitForFirstSpawn := false;
+ plr.FNoRespawn := false;
+ plr.FWantsInGame := true; // TODO: look into this later
+ plr.Respawn(False);
end;
+
procedure MH_RECV_FullStateRequest(C: pTNetClient; var M: TMsg);
begin
+ //e_LogWritefln('*** client #%u (cid #%u) full state request', [C.ID, C.Player]);
if gGameOn then
+ begin
MH_SEND_Everything((C^.State = NET_STATE_AUTH), C^.ID)
+ end
else
+ begin
C^.RequestedFullUpdate := True;
+ end;
end;
// PLAYER
// GAME (SEND)
-procedure MH_SEND_Everything(CreatePlayers: Boolean = False; ID: Integer = NET_EVERYONE);
+procedure MH_SEND_Everything(CreatePlayers: Boolean {= False}; ID: Integer {= NET_EVERYONE});
function sendItemRespawn (it: PItem): Boolean;
begin
@@ -668,6 +752,12 @@ procedure MH_SEND_Everything(CreatePlayers: Boolean = False; ID: Integer = NET_E
var
I: Integer;
begin
+ if (ID >= 0) and (ID < length(NetClients)) then
+ begin
+ e_LogWritefln('*** client #%u (cid #%u) will get everything', [ID, NetClients[ID].Player]);
+ MH_ProcessFirstSpawn(@NetClients[ID]);
+ end;
+
if gPlayers <> nil then
begin
for I := Low(gPlayers) to High(gPlayers) do
if gLMSRespawn > LMS_RESPAWN_NONE then
begin
+ e_LogWritefln('*** client #%u (cid #%u) WARMUP', [ID, NetClients[ID].Player]);
MH_SEND_GameEvent(NET_EV_LMS_WARMUP, (gLMSRespawnTime - gTime) div 1000, 'N', ID);
end;
end;
procedure MH_SEND_Info(ID: Byte);
-var
- Map: string;
begin
- Map := g_ExtractFileName(gMapInfo.Map);
-
NetOut.Clear();
NetOut.Write(Byte(NET_MSG_INFO));
NetOut.Write(ID);
NetOut.Write(NetClients[ID].Player);
- NetOut.Write(gGameSettings.WAD);
- NetOut.Write(Map);
+ NetOut.Write(ExtractFileName(gGameSettings.WAD));
+ NetOut.Write(g_ExtractFileName(gMapInfo.Map));
NetOut.Write(gWADHash);
NetOut.Write(gGameSettings.GameMode);
NetOut.Write(gGameSettings.GoalLimit);
i1, i2: TStrings_Locale;
pln: String;
cnt: Byte;
+ goodCmd: Boolean = true;
begin
FillChar(EvHash, Sizeof(EvHash), 0);
EvType := M.ReadByte();
gTime := EvTime;
+ if (g_Res_received_map_start <> 0) then
+ begin
+ if (g_Res_received_map_start < 0) then exit;
+ goodCmd := false;
+ case EvType of
+ NET_EV_MAPSTART: goodCmd := true;
+ NET_EV_MAPEND: goodCmd := true;
+ NET_EV_PLAYER_KICK: goodCmd := true;
+ NET_EV_PLAYER_BAN: goodCmd := true;
+ end;
+ if not goodCmd then exit;
+ end;
+
case EvType of
NET_EV_MAPSTART:
begin
- gGameOn := False;
- g_Game_ClearLoading();
- g_Game_StopAllSounds(True);
+ if (g_Res_received_map_start <> 0) then
+ begin
+ g_Res_received_map_start := -1;
+ end
+ else
+ begin
+ gGameOn := False;
+ g_Game_ClearLoading();
+ g_Game_StopAllSounds(True);
- gSwitchGameMode := Byte(EvNum);
- gGameSettings.GameMode := gSwitchGameMode;
+ gSwitchGameMode := Byte(EvNum);
+ gGameSettings.GameMode := gSwitchGameMode;
- gWADHash := EvHash;
- if not g_Game_StartMap(EvStr, True) then
- begin
- if not isWadPath(EvStr) then
- g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [gGameSettings.WAD + ':\' + EvStr]))
- else
- g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [EvStr]));
- Exit;
- end;
+ gWADHash := EvHash;
+ if not g_Game_StartMap(false{asMegawad}, EvStr, True) then
+ begin
+ if not isWadPath(EvStr) then
+ g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [gGameSettings.WAD + ':\' + EvStr]))
+ else
+ g_FatalError(Format(_lc[I_GAME_ERROR_MAP_LOAD], [EvStr]));
+ Exit;
+ end;
- MC_SEND_FullStateRequest;
+ MC_SEND_FullStateRequest;
+ end;
end;
NET_EV_MAPEND:
begin
- gMissionFailed := EvNum <> 0;
- gExit := EXIT_ENDLEVELCUSTOM;
+ if (g_Res_received_map_start <> 0) then
+ begin
+ g_Res_received_map_start := -1;
+ end
+ else
+ begin
+ gMissionFailed := EvNum <> 0;
+ gExit := EXIT_ENDLEVELCUSTOM;
+ end;
end;
NET_EV_RCON:
end;
NET_EV_PLAYER_KICK:
- g_Console_Add(Format(_lc[I_PLAYER_KICK], [EvStr]), True);
+ begin
+ g_Console_Add(Format(_lc[I_PLAYER_KICK], [EvStr]), True);
+ if (g_Res_received_map_start <> 0) then g_Res_received_map_start := -1;
+ end;
NET_EV_PLAYER_BAN:
- g_Console_Add(Format(_lc[I_PLAYER_BAN], [EvStr]), True);
+ begin
+ g_Console_Add(Format(_lc[I_PLAYER_BAN], [EvStr]), True);
+ if (g_Res_received_map_start <> 0) then g_Res_received_map_start := -1;
+ end;
NET_EV_LMS_WARMUP:
g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [EvNum]), True);
MPlaying := M.ReadByte() <> 0;
MPos := M.ReadLongWord();
MPaused := M.ReadByte() <> 0;
+ MPos := MPos+1; //k8: stfu, fpc!
if MPlaying then
begin
g_Net_Client_Send(True, NET_CHAN_SERVICE);
end;
-// i have no idea why all this stuff is in here
-
-function ReadFile(const FileName: TFileName): AByte;
-var
- FileStream : TStream;
- fname: string;
-begin
- e_WriteLog(Format('NETWORK: looking for file "%s"', [FileName]), TMsgType.Notify);
- fname := findDiskWad(FileName);
- if length(fname) = 0 then
- begin
- e_WriteLog(Format('NETWORK: file "%s" not found!', [FileName]), TMsgType.Fatal);
- SetLength(Result, 0);
- exit;
- end;
- e_WriteLog(Format('NETWORK: found file "%s"', [fname]), TMsgType.Notify);
- Result := nil;
- FileStream := openDiskFileRO(fname);
- try
- if FileStream.Size > 0 then
- begin
- SetLength(Result, FileStream.Size);
- FileStream.Read(Result[0], FileStream.Size);
- end;
- finally
- FileStream.Free;
- end;
-end;
-
-function CreateMapDataMsg(const FileName: TFileName; ResList: TStringList): TMapDataMsg;
-var
- i: Integer;
-begin
- Result.MsgId := NET_MSG_MAP_RESPONSE;
- Result.FileData := ReadFile(FileName);
- Result.FileSize := Length(Result.FileData);
-
- SetLength(Result.ExternalResources, ResList.Count);
- for i:=0 to ResList.Count-1 do
- begin
- Result.ExternalResources[i].Name := ResList.Strings[i];
- Result.ExternalResources[i].md5 := MD5File(GameDir+'/wads/'+ResList.Strings[i]);
- end;
-end;
-
-procedure ResDataMsgToBytes(var bytes: AByte; const ResData: TResDataMsg);
-var
- ResultStream: TMemoryStream;
-begin
- ResultStream := TMemoryStream.Create;
-
- ResultStream.WriteBuffer(ResData.MsgId, SizeOf(ResData.MsgId)); //msgId
- ResultStream.WriteBuffer(ResData.FileSize, SizeOf(ResData.FileSize)); //file size
- ResultStream.WriteBuffer(ResData.FileData[0], ResData.FileSize); //file data
-
- SetLength(bytes, ResultStream.Size);
- ResultStream.Seek(0, soFromBeginning);
- ResultStream.ReadBuffer(bytes[0], ResultStream.Size);
-
- ResultStream.Free;
-end;
-
-function ResDataFromMsgStream(msgStream: TMemoryStream):TResDataMsg;
-begin
- msgStream.ReadBuffer(Result.MsgId, SizeOf(Result.MsgId));
- msgStream.ReadBuffer(Result.FileSize, SizeOf(Result.FileSize));
- SetLength(Result.FileData, Result.FileSize);
- msgStream.ReadBuffer(Result.FileData[0], Result.FileSize);
-end;
-
-procedure MapDataMsgToBytes(var bytes: AByte; const MapDataMsg: TMapDataMsg);
-var
- ResultStream: TMemoryStream;
- resCount: Integer;
-begin
- resCount := Length(MapDataMsg.ExternalResources);
-
- ResultStream := TMemoryStream.Create;
-
- ResultStream.WriteBuffer(MapDataMsg.MsgId, SizeOf(MapDataMsg.MsgId)); //msgId
- ResultStream.WriteBuffer(MapDataMsg.FileSize, SizeOf(MapDataMsg.FileSize)); //file size
- ResultStream.WriteBuffer(MapDataMsg.FileData[0], MapDataMsg.FileSize); //file data
-
- ResultStream.WriteBuffer(resCount, SizeOf(resCount)); //res count
- ResultStream.WriteBuffer(MapDataMsg.ExternalResources[0], resCount*SizeOf(TExternalResourceInfo)); //res data
-
- SetLength(bytes, ResultStream.Size);
- ResultStream.Seek(0, soFromBeginning);
- ResultStream.ReadBuffer(bytes[0], ResultStream.Size);
-
- ResultStream.Free;
-end;
-
-function MapDataFromMsgStream(msgStream: TMemoryStream):TMapDataMsg;
-var
- resCount: Integer;
-begin
- msgStream.ReadBuffer(Result.MsgId, SizeOf(Result.MsgId));
- msgStream.ReadBuffer(Result.FileSize, SizeOf(Result.FileSize)); //file size
-
- SetLength(Result.FileData, Result.FileSize);
- msgStream.ReadBuffer(Result.FileData[0], Result.FileSize); //file data
-
- msgStream.ReadBuffer(resCount, SizeOf(resCount)); //res count
- SetLength(Result.ExternalResources, resCount);
-
- msgStream.ReadBuffer(Result.ExternalResources[0], resCount * SizeOf(TExternalResourceInfo)); //res data
-end;
-
-function IsValidFileName(const S: String): Boolean;
-const
- Forbidden: set of Char = ['<', '>', '|', '"', ':', '*', '?'];
-var
- I: Integer;
-begin
- Result := S <> '';
- for I := 1 to Length(S) do
- Result := Result and (not(S[I] in Forbidden));
-end;
-
-function IsValidFilePath(const S: String): Boolean;
-var
- I: Integer;
-begin
- Result := False;
- if not IsValidFileName(S) then exit;
- if FileExists(S) then exit;
- I := LastDelimiter('\/', S);
- if (I > 0) then
- if (not DirectoryExists(Copy(S, 1, I-1))) then
- exit;
- Result := True;
-end;
-
-procedure MC_SEND_MapRequest();
-begin
- NetOut.Write(Byte(NET_MSG_MAP_REQUEST));
- g_Net_Client_Send(True, NET_CHAN_IMPORTANT);
-end;
-
-procedure MC_SEND_ResRequest(const resName: AnsiString);
-begin
- NetOut.Write(Byte(NET_MSG_RES_REQUEST));
- NetOut.Write(resName);
- g_Net_Client_Send(True, NET_CHAN_IMPORTANT);
-end;
-
-procedure MH_RECV_MapRequest(C: pTNetClient; var M: TMsg);
-var
- payload: AByte;
- peer: pENetPeer;
- mapDataMsg: TMapDataMsg;
-begin
- e_WriteLog('NET: Received map request from ' +
- DecodeIPV4(C^.Peer.address.host), TMsgType.Notify);
-
- mapDataMsg := CreateMapDataMsg(MapsDir + gGameSettings.WAD, gExternalResources);
- peer := NetClients[C^.ID].Peer;
-
- MapDataMsgToBytes(payload, mapDataMsg);
- g_Net_SendData(payload, peer, True, NET_CHAN_DOWNLOAD);
-
- payload := nil;
- mapDataMsg.FileData := nil;
- mapDataMsg.ExternalResources := nil;
-end;
-
-procedure MH_RECV_ResRequest(C: pTNetClient; var M: TMsg);
-var
- payload: AByte;
- peer: pENetPeer;
- FileName: String;
- resDataMsg: TResDataMsg;
-begin
- FileName := ExtractFileName(M.ReadString());
- e_WriteLog('NET: Received res request: ' + FileName +
- ' from ' + DecodeIPV4(C^.Peer.address.host), TMsgType.Notify);
-
- if not IsValidFilePath(FileName) then
- begin
- e_WriteLog('Invalid filename: ' + FileName, TMsgType.Warning);
- exit;
- end;
-
- peer := NetClients[C^.ID].Peer;
-
- if gExternalResources.IndexOf(FileName) > -1 then
- begin
- resDataMsg.MsgId := NET_MSG_RES_RESPONSE;
- resDataMsg.FileData := ReadFile(GameDir+'/wads/'+FileName);
- resDataMsg.FileSize := Length(resDataMsg.FileData);
-
- ResDataMsgToBytes(payload, resDataMsg);
- g_Net_SendData(payload, peer, True, NET_CHAN_DOWNLOAD);
- end;
-end;
end.