X-Git-Url: http://deadsoftware.ru/gitweb?p=d2df-sdl.git;a=blobdiff_plain;f=src%2Fgame%2Fg_netmsg.pas;h=55b99b3705acbc0ddbe05e00b04ba3657ea2c9fd;hp=15079d6b614449376ecb6e3bb94f539c55b425d3;hb=4204edd3c7df01198a2289af4896be0575fff15c;hpb=cc289b13a569e26fa6fb389f215719af031c35a2 diff --git a/src/game/g_netmsg.pas b/src/game/g_netmsg.pas index 15079d6..55b99b3 100644 --- a/src/game/g_netmsg.pas +++ b/src/game/g_netmsg.pas @@ -31,6 +31,7 @@ const NET_MSG_FLAG = 107; NET_MSG_REQFST = 108; NET_MSG_GSET = 109; + NET_MSG_FLAGPOS= 110; NET_MSG_PLR = 111; NET_MSG_PLRPOS = 112; @@ -44,6 +45,7 @@ const NET_MSG_ISPAWN = 121; NET_MSG_IDEL = 122; + NET_MSG_IPOS = 123; NET_MSG_MSPAWN = 131; NET_MSG_MPOS = 132; @@ -51,8 +53,8 @@ const NET_MSG_MSHOT = 134; NET_MSG_MDEL = 135; - NET_MSG_PSTATE = 141; - NET_MSG_PTEX = 142; + NET_MSG_PSTATE = 141; + NET_MSG_PTEX = 142; NET_MSG_TSOUND = 151; NET_MSG_TMUSIC = 152; @@ -66,34 +68,12 @@ const 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; - - // chunked file transfers - // it goes this way: - // client requests file (FILE_REQUEST) - // server sends file header info (FILE_HEADER) - // client acks chunk -1 (CHUNK_ACK) to initiate transfer, or cancels (FILE_CANCEL) - // server start sending data chunks (one at a time, waiting for an ACK for each one) - // when client acks the last chunk, transfer is complete - // this scheme sux, of course; we can do better by spamming with unreliable unsequenced packets, - // and use client acks to drive server sends, but meh... let's do it this way first, and - // we can improve it later. - - // client: request a file - NET_MSG_FILE_REQUEST = 210; - // server: file info response - NET_MSG_FILE_HEADER = 211; - // client: request transfer cancellation - // server: something went wrong, transfer cancelled, bomb out - NET_MSG_FILE_CANCEL = 212; - // server: file chunk data - NET_MSG_FILE_CHUNK_DATA = 213; - // client: file chunk ack - NET_MSG_FILE_CHUNK_ACK = 214; - + } NET_CHAT_SYSTEM = 0; NET_CHAT_PLAYER = 1; @@ -134,6 +114,7 @@ const NET_EV_PLAYER_TOUCH = 18; NET_EV_SECRET = 19; NET_EV_INTER_READY = 20; + NET_EV_LMS_NOSPAWN = 21; NET_VE_STARTED = 1; NET_VE_PASSED = 2; @@ -150,11 +131,15 @@ const NET_CHEAT_SUICIDE = 1; NET_CHEAT_SPECTATE = 2; NET_CHEAT_READY = 3; + NET_CHEAT_DROPFLAG = 4; NET_MAX_DIFFTIME = 5000 div 36; // HOST MESSAGES +procedure MH_MalformedPacket(C: pTNetClient); +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); @@ -168,7 +153,7 @@ procedure MH_RECV_RCONCommand(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); @@ -180,6 +165,7 @@ procedure MH_SEND_GameStats(ID: Integer = NET_EVERYONE); procedure MH_SEND_CoopStats(ID: Integer = NET_EVERYONE); procedure MH_SEND_GameEvent(EvType: Byte; EvNum: Integer = 0; EvStr: string = 'N'; ID: Integer = NET_EVERYONE); procedure MH_SEND_FlagEvent(EvType: Byte; Flag: Byte; PID: Word; Quiet: Boolean = False; ID: Integer = NET_EVERYONE); +procedure MH_SEND_FlagPos(Flag: Byte; ID: Integer = NET_EVERYONE); procedure MH_SEND_GameSettings(ID: Integer = NET_EVERYONE); // PLAYER procedure MH_SEND_PlayerCreate(PID: Word; ID: Integer = NET_EVERYONE); @@ -193,6 +179,7 @@ procedure MH_SEND_PlayerSettings(PID: Word; Mdl: string = ''; ID: Integer = NET_ // ITEM procedure MH_SEND_ItemSpawn(Quiet: Boolean; IID: Word; ID: Integer = NET_EVERYONE); procedure MH_SEND_ItemDestroy(Quiet: Boolean; IID: Word; ID: Integer = NET_EVERYONE); +procedure MH_SEND_ItemPos(IID: Word; ID: Integer = NET_EVERYONE); // PANEL procedure MH_SEND_PanelTexture(PGUID: Integer; AnimLoop: Byte; ID: Integer = NET_EVERYONE); procedure MH_SEND_PanelState(PGUID: Integer; ID: Integer = NET_EVERYONE); @@ -222,6 +209,7 @@ procedure MC_RECV_GameStats(var M: TMsg); procedure MC_RECV_CoopStats(var M: TMsg); procedure MC_RECV_GameEvent(var M: TMsg); procedure MC_RECV_FlagEvent(var M: TMsg); +procedure MC_RECV_FlagPos(var M: TMsg); procedure MC_RECV_GameSettings(var M: TMsg); // PLAYER function MC_RECV_PlayerCreate(var M: TMsg): Word; @@ -235,6 +223,7 @@ procedure MC_RECV_PlayerSettings(var M: TMsg); // ITEM procedure MC_RECV_ItemSpawn(var M: TMsg); procedure MC_RECV_ItemDestroy(var M: TMsg); +procedure MC_RECV_ItemPos(var M: TMsg); // PANEL procedure MC_RECV_PanelTexture(var M: TMsg); procedure MC_RECV_PanelState(var M: TMsg); @@ -288,12 +277,10 @@ type 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 uses @@ -303,40 +290,81 @@ uses g_language, g_monsters, g_netmaster, utils, wadreader, MAPDEF; const - NET_KEY_LEFT = 1; - NET_KEY_RIGHT = 2; - NET_KEY_UP = 4; - NET_KEY_DOWN = 8; - NET_KEY_JUMP = 16; - NET_KEY_FIRE = 32; - NET_KEY_OPEN = 64; - NET_KEY_NW = 256; - NET_KEY_PW = 512; - NET_KEY_CHAT = 2048; - NET_KEY_FORCEDIR = 4096; + NET_KEY_LEFT = 1 shl 0; + NET_KEY_RIGHT = 1 shl 1; + NET_KEY_UP = 1 shl 2; + NET_KEY_DOWN = 1 shl 3; + NET_KEY_JUMP = 1 shl 4; + NET_KEY_FIRE = 1 shl 5; + NET_KEY_OPEN = 1 shl 6; + NET_KEY_CHAT = 1 shl 7; + NET_KEY_FORCEDIR = 1 shl 8; //var //kBytePrev: Word = 0; //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 // // GAME +procedure MH_MalformedPacket(C: pTNetClient); +begin + g_Console_Add(_lc[I_NET_MSG] + _lc[I_NET_MSG_HOST_REJECT] + + _lc[I_NET_DISC_PROTOCOL]); + enet_peer_disconnect(C^.Peer, NET_DISC_PROTOCOL); +end; + procedure MH_RECV_Chat(C: pTNetClient; var M: TMsg); var Txt: string; Mode: Byte; PID: Word; Pl: TPlayer; + Err: Boolean; begin PID := C^.Player; Pl := g_Player_Get(PID); - Txt := M.ReadString(); - Mode := M.ReadByte(); + Err := False; + try + Txt := M.ReadString(); + Mode := M.ReadByte(); + except + Err := True; + end; + + if Err then begin MH_MalformedPacket(C); Exit; end; + if (Mode = NET_CHAT_SYSTEM) then Mode := NET_CHAT_PLAYER; // prevent sending system messages from clients if (Mode = NET_CHAT_TEAM) and (not gGameSettings.GameMode in [GM_TDM, GM_CTF]) then @@ -352,18 +380,34 @@ procedure MH_RECV_Info(C: pTNetClient; var M: TMsg); var Ver, PName, Model, Pw: string; R, G, B, T: Byte; + WeapSwitch: Byte; + TmpPrefArray: Array [WP_FIRST .. WP_LAST + 1] of Byte; + SwitchEmpty: Byte; PID: Word; Color: TRGB; I: Integer; + Err: Boolean; begin - Ver := M.ReadString(); - Pw := M.ReadString(); - PName := M.ReadString(); - Model := M.ReadString(); - R := M.ReadByte(); - G := M.ReadByte(); - B := M.ReadByte(); - T := M.ReadByte(); + Err := False; + try + Ver := M.ReadString(); + Pw := M.ReadString(); + PName := M.ReadString(); + Model := M.ReadString(); + R := M.ReadByte(); + G := M.ReadByte(); + B := M.ReadByte(); + T := M.ReadByte(); + WeapSwitch := M.ReadByte(); + if (WeapSwitch = 2) then + for I := WP_FIRST to WP_LAST + 1 do + TmpPrefArray[I] := M.ReadByte(); + SwitchEmpty := M.ReadByte(); + except + Err := True; + end; + + if Err then begin MH_MalformedPacket(C); Exit; end; if Ver <> GAME_VERSION then begin @@ -399,6 +443,13 @@ begin Exit; end; + if (C^.Player <> 0) then + begin + // already received info + g_Net_Penalize(C, 'client info spam'); + Exit; + end; + Color.R := R; Color.B := B; Color.G := G; @@ -407,10 +458,16 @@ begin with g_Player_Get(PID) do begin Name := PName; + WeapSwitchMode := WeapSwitch; + if (WeapSwitch = 2) then + SetWeaponPrefs(TmpPrefArray); + SwitchToEmpty := SwitchEmpty; Reset(True); end; C^.Player := PID; + C^.WaitForFirstSpawn := false; + C^.AuthTime := 0; g_Console_Add(Format(_lc[I_PLAYER_JOIN], [PName]), True); e_WriteLog('NET: Client ' + PName + ' [' + IntToStr(C^.ID) + @@ -423,37 +480,88 @@ begin Name := PName; FClientID := C^.ID; // round in progress, don't spawn - if (gGameSettings.MaxLives > 0) and (gLMSRespawn = LMS_RESPAWN_NONE) then - begin - Lives := 0; - FNoRespawn := True; - Spectate; - FWantsInGame := True; // TODO: look into this later - end - else - Respawn(gGameSettings.GameType = GT_SINGLE); + 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; - 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 + + if (gGameSettings.MaxLives > 0) and (gLMSRespawn = LMS_RESPAWN_NONE) then + begin + plr.Spectate; + MH_SEND_GameEvent(NET_EV_LMS_NOSPAWN, 0, 'N', C.ID); + end + else + begin + plr.Respawn(False); + if gLMSRespawn > LMS_RESPAWN_NONE then + MH_SEND_GameEvent(NET_EV_LMS_WARMUP, gLMSRespawnTime - gTime, 'N', C.ID); + end; 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 @@ -461,16 +569,26 @@ end; function MH_RECV_PlayerPos(C: pTNetClient; var M: TMsg): Word; var Dir, i: Byte; + WeaponAct: Byte; WeaponSelect: Word; PID: Word; kByte: Word; Pl: TPlayer; GT: LongWord; + Err: Boolean; begin Result := 0; + Err := False; if not gGameOn then Exit; - GT := M.ReadLongWord(); + try + GT := M.ReadLongWord(); + except + Err := True; + end; + + if Err then begin MH_MalformedPacket(C); Exit; end; + PID := C^.Player; Pl := g_Player_Get(PID); if Pl = nil then @@ -481,9 +599,17 @@ begin with Pl do begin NetTime := GT; - kByte := M.ReadWord(); - Dir := M.ReadByte(); - WeaponSelect := M.ReadWord(); + try + kByte := M.ReadWord(); + Dir := M.ReadByte(); + WeaponAct := M.ReadByte(); + WeaponSelect := M.ReadWord(); + except + Err := True; + end; + + if Err then begin MH_MalformedPacket(C); Exit; end; + //e_WriteLog(Format('R:ws=%d', [WeaponSelect]), MSG_WARNING); if Direction <> TDirection(Dir) then JustTeleported := False; @@ -504,8 +630,15 @@ begin if LongBool(kByte and NET_KEY_JUMP) then PressKey(KEY_JUMP, 10000); if LongBool(kByte and NET_KEY_FIRE) then PressKey(KEY_FIRE, 10000); if LongBool(kByte and NET_KEY_OPEN) then PressKey(KEY_OPEN, 10000); - if LongBool(kByte and NET_KEY_NW) then PressKey(KEY_NEXTWEAPON, 10000); - if LongBool(kByte and NET_KEY_PW) then PressKey(KEY_PREVWEAPON, 10000); + + for i := 0 to 7 do + begin + if (WeaponAct and Byte(1 shl i)) <> 0 then + begin + //e_WriteLog(Format(' R:wn=%d', [i]), MSG_WARNING); + ProcessWeaponAction(i); + end; + end; for i := 0 to 15 do begin @@ -524,11 +657,19 @@ procedure MH_RECV_CheatRequest(C: pTNetClient; var M: TMsg); var CheatKind: Byte; Pl: TPlayer; + Err: Boolean; begin + Err := False; Pl := g_Player_Get(C^.Player); if Pl = nil then Exit; - CheatKind := M.ReadByte(); + try + CheatKind := M.ReadByte(); + except + Err := True; + end; + + if Err then begin MH_MalformedPacket(C); Exit; end; case CheatKind of NET_CHEAT_SUICIDE: @@ -536,7 +677,12 @@ begin NET_CHEAT_SPECTATE: begin if Pl.FSpectator then - Pl.Respawn(False) + begin + if (gGameSettings.MaxLives = 0) or (gLMSRespawn > LMS_RESPAWN_NONE) then + Pl.Respawn(False) + else + MH_SEND_GameEvent(NET_EV_LMS_NOSPAWN, Pl.UID); + end else Pl.Spectate; end; @@ -555,6 +701,8 @@ begin Dec(gInterReadyCount); end; end; + NET_CHEAT_DROPFLAG: + Pl.TryDropFlag(); end; end; @@ -564,14 +712,31 @@ var TmpModel: string; TmpColor: TRGB; TmpTeam: Byte; + TmpWeapSwitch: Byte; + TmpPrefArray: Array [WP_FIRST .. WP_LAST + 1] of Byte; + TmpSwEmpty: Byte; + I: Integer; Pl: TPlayer; + Err: Boolean; begin - TmpName := M.ReadString(); - TmpModel := M.ReadString(); - TmpColor.R := M.ReadByte(); - TmpColor.G := M.ReadByte(); - TmpColor.B := M.ReadByte(); - TmpTeam := M.ReadByte(); + Err := False; + try + TmpName := M.ReadString(); + TmpModel := M.ReadString(); + TmpColor.R := M.ReadByte(); + TmpColor.G := M.ReadByte(); + TmpColor.B := M.ReadByte(); + TmpTeam := M.ReadByte(); + TmpWeapSwitch := M.ReadByte(); + if (TmpWeapSwitch = 2) then + for I := WP_FIRST to WP_LAST + 1 do + TmpPrefArray[I] := M.ReadByte(); + TmpSwEmpty := M.ReadByte(); + except + Err := True; + end; + + if Err then begin MH_MalformedPacket(C); Exit; end; Pl := g_Player_Get(C^.Player); if Pl = nil then Exit; @@ -590,6 +755,14 @@ begin if TmpModel <> Pl.Model.Name then Pl.SetModel(TmpModel); + if (TmpWeapSwitch <> Pl.WeapSwitchMode) then + Pl.WeapSwitchMode := TmpWeapSwitch; + + if (TmpWeapSwitch = 2) then + Pl.SetWeaponPrefs(TmpPrefArray); + + if (TmpSwEmpty <> Pl.SwitchToEmpty) then + Pl.SwitchToEmpty := TmpSwEmpty; MH_SEND_PlayerSettings(Pl.UID, TmpModel); end; @@ -598,8 +771,15 @@ end; procedure MH_RECV_RCONPassword(C: pTNetClient; var M: TMsg); var Pwd: string; + Err: Boolean; begin - Pwd := M.ReadString(); + Err := False; + try + Pwd := M.ReadString(); + except + Err := True; + end; + if Err then begin MH_MalformedPacket(C); Exit; end; if not NetAllowRCON then Exit; if Pwd = NetRCONPassword then begin @@ -613,8 +793,15 @@ end; procedure MH_RECV_RCONCommand(C: pTNetClient; var M: TMsg); var Cmd: string; + Err: Boolean; begin - Cmd := M.ReadString(); + Err := False; + try + Cmd := M.ReadString(); + except + Err := True; + end; + if Err then begin MH_MalformedPacket(C); Exit; end; if not NetAllowRCON then Exit; if not C^.RCONAuth then begin @@ -632,9 +819,17 @@ var Name, Command: string; Need: Integer; Pl: TPlayer; + Err: Boolean; begin - Start := M.ReadByte() <> 0; - Command := M.ReadString(); + Err := False; + try + Start := M.ReadByte() <> 0; + Command := M.ReadString(); + except + Err := True; + end; + + if Err then begin MH_MalformedPacket(C); Exit; end; Pl := g_Player_Get(C^.Player); if Pl = nil then Exit; @@ -671,7 +866,7 @@ end; // 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 @@ -695,6 +890,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 @@ -752,27 +953,18 @@ begin if CreatePlayers and (ID >= 0) then NetClients[ID].State := NET_STATE_GAME; - if gLMSRespawn > LMS_RESPAWN_NONE then - begin - MH_SEND_GameEvent(NET_EV_LMS_WARMUP, (gLMSRespawnTime - gTime) div 1000, 'N', ID); - end; - g_Net_Flush(); 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); @@ -984,10 +1176,23 @@ begin NetOut.Write(gFlags[Flag].Obj.Y); NetOut.Write(gFlags[Flag].Obj.Vel.X); NetOut.Write(gFlags[Flag].Obj.Vel.Y); + NetOut.Write(Byte(gFlags[Flag].Direction)); g_Net_Host_Send(ID, True, NET_CHAN_IMPORTANT); end; +procedure MH_SEND_FlagPos(Flag: Byte; ID: Integer = NET_EVERYONE); +begin + NetOut.Write(Byte(NET_MSG_FLAGPOS)); + NetOut.Write(Flag); + NetOut.Write(gFlags[Flag].Obj.X); + NetOut.Write(gFlags[Flag].Obj.Y); + NetOut.Write(gFlags[Flag].Obj.Vel.X); + NetOut.Write(gFlags[Flag].Obj.Vel.Y); + + g_Net_Host_Send(ID, False, NET_CHAN_IMPORTANT); +end; + procedure MH_SEND_GameSettings(ID: Integer = NET_EVERYONE); begin NetOut.Write(Byte(NET_MSG_GSET)); @@ -1050,9 +1255,10 @@ begin if IsKeyPressed(KEY_UP) then kByte := kByte or NET_KEY_UP; if IsKeyPressed(KEY_DOWN) then kByte := kByte or NET_KEY_DOWN; if IsKeyPressed(KEY_JUMP) then kByte := kByte or NET_KEY_JUMP; - if JustTeleported then kByte := kByte or NET_KEY_FORCEDIR; end; + if JustTeleported then kByte := kByte or NET_KEY_FORCEDIR; + NetOut.Write(kByte); if Direction = TDirection.D_LEFT then NetOut.Write(Byte(0)) else NetOut.Write(Byte(1)); NetOut.Write(GameX); @@ -1118,6 +1324,7 @@ begin NetOut.Write(Byte(FJetpack)); NetOut.Write(FFireTime); NetOut.Write(Byte(FFlaming)); + NetOut.Write(FSpawnInvul); end; g_Net_Host_Send(ID, True, NET_CHAN_PLAYER); @@ -1225,6 +1432,22 @@ begin g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA); end; +procedure MH_SEND_ItemPos(IID: Word; ID: Integer = NET_EVERYONE); +var + it: PItem; +begin + it := g_Items_ByIdx(IID); + + NetOut.Write(Byte(NET_MSG_IPOS)); + NetOut.Write(IID); + NetOut.Write(it.Obj.X); + NetOut.Write(it.Obj.Y); + NetOut.Write(it.Obj.Vel.X); + NetOut.Write(it.Obj.Vel.Y); + + g_Net_Host_Send(ID, False, NET_CHAN_LARGEDATA); +end; + // PANEL procedure MH_SEND_PanelTexture(PGUID: Integer; AnimLoop: Byte; ID: Integer = NET_EVERYONE); @@ -1454,22 +1677,25 @@ begin if Mode <> NET_CHAT_SYSTEM then begin - if Mode = NET_CHAT_PLAYER then - begin - g_Console_Add(Txt, True); - e_WriteLog('[Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify); - g_Game_ChatSound(b_Text_Unformat(Txt)); - end else - if (Mode = NET_CHAT_TEAM) and (gPlayer1 <> nil) then + if NetDeafLevel = 0 then begin - if gPlayer1.Team = TEAM_RED then - g_Console_Add(b_Text_Format('\r[Team] ') + Txt, True); - if gPlayer1.Team = TEAM_BLUE then - g_Console_Add(b_Text_Format('\b[Team] ') + Txt, True); - e_WriteLog('[Team Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify); - g_Game_ChatSound(b_Text_Unformat(Txt)); + if Mode = NET_CHAT_PLAYER then + begin + g_Console_Add(Txt, True); + e_WriteLog('[Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify); + g_Game_ChatSound(b_Text_Unformat(Txt)); + end else + if (Mode = NET_CHAT_TEAM) and (gPlayer1 <> nil) then + begin + if gPlayer1.Team = TEAM_RED then + g_Console_Add(b_Text_Format('\r[Team] ') + Txt, True); + if gPlayer1.Team = TEAM_BLUE then + g_Console_Add(b_Text_Format('\b[Team] ') + Txt, True); + e_WriteLog('[Team Chat] ' + b_Text_Unformat(Txt), TMsgType.Notify); + g_Game_ChatSound(b_Text_Unformat(Txt)); + end; end; - end else + end else if (NetDeafLevel < 2) then g_Console_Add(Txt, True); end; @@ -1716,6 +1942,7 @@ begin NET_EV_MAPEND: goodCmd := true; NET_EV_PLAYER_KICK: goodCmd := true; NET_EV_PLAYER_BAN: goodCmd := true; + NET_EV_LMS_WARMUP: goodCmd := true; end; if not goodCmd then exit; end; @@ -1737,7 +1964,7 @@ begin gGameSettings.GameMode := gSwitchGameMode; gWADHash := EvHash; - if not g_Game_StartMap(EvStr, True) then + 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])) @@ -1752,6 +1979,8 @@ begin NET_EV_MAPEND: begin + gLMSRespawn := LMS_RESPAWN_NONE; + gLMSRespawnTime := 0; if (g_Res_received_map_start <> 0) then begin g_Res_received_map_start := -1; @@ -1777,10 +2006,13 @@ begin NET_EV_CHANGE_TEAM: begin - if EvNum = TEAM_RED then - g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_RED], [EvStr]), True); - if EvNum = TEAM_BLUE then - g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_BLUE], [EvStr]), True); + if NetDeafLevel < 2 then + begin + if EvNum = TEAM_RED then + g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_RED], [EvStr]), True); + if EvNum = TEAM_BLUE then + g_Console_Add(Format(_lc[I_PLAYER_CHTEAM_BLUE], [EvStr]), True); + end; end; NET_EV_PLAYER_KICK: @@ -1796,13 +2028,24 @@ begin end; NET_EV_LMS_WARMUP: - g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [EvNum]), True); + begin + if EvNum > 0 then + begin + gLMSRespawn := LMS_RESPAWN_WARMUP; + gLMSRespawnTime := gTime + EvNum; + g_Console_Add(Format(_lc[I_MSG_WARMUP_START], [EvNum div 1000]), True); + end + else if gPlayer1 = nil then + begin + g_Console_Add(_lc[I_PLAYER_SPECT4], True); + end; + end; NET_EV_LMS_SURVIVOR: g_Console_Add('*** ' + _lc[I_MESSAGE_LMS_SURVIVOR] + ' ***', True); NET_EV_BIGTEXT: - g_Game_Message(AnsiUpperCase(EvStr), Word(EvNum)); + if NetDeafLevel < 2 then g_Game_Message(AnsiUpperCase(EvStr), Word(EvNum)); NET_EV_SCORE: begin @@ -1882,6 +2125,7 @@ begin NET_EV_LMS_START: begin g_Player_RemoveAllCorpses; + gLMSRespawn := LMS_RESPAWN_NONE; g_Game_Message(_lc[I_MESSAGE_LMS_START], 144); end; @@ -1902,6 +2146,9 @@ begin NET_EV_LMS_DRAW: g_Game_Message(_lc[I_GAME_WIN_DRAW], 144); + NET_EV_LMS_NOSPAWN: + g_Console_Add(_lc[I_PLAYER_SPECT4], True); + NET_EV_KILLCOMBO: g_Game_Announce_KillCombo(EvNum); @@ -1930,6 +2177,18 @@ begin end; end; +procedure MC_RECV_FlagPos(var M: TMsg); +var + Fl: Byte; +begin + Fl := M.ReadByte(); + if Fl = FLAG_NONE then Exit; + gFlags[Fl].Obj.X := M.ReadLongInt(); + gFlags[Fl].Obj.Y := M.ReadLongInt(); + gFlags[Fl].Obj.Vel.X := M.ReadLongInt(); + gFlags[Fl].Obj.Vel.Y := M.ReadLongInt(); +end; + procedure MC_RECV_FlagEvent(var M: TMsg); var PID: Word; @@ -1953,6 +2212,7 @@ begin gFlags[Fl].Obj.Y := M.ReadLongInt(); gFlags[Fl].Obj.Vel.X := M.ReadLongInt(); gFlags[Fl].Obj.Vel.Y := M.ReadLongInt(); + gFlags[Fl].Direction := TDirection(M.ReadByte()); Pl := g_Player_Get(PID); if (Pl = nil) and @@ -2142,7 +2402,8 @@ begin end; end; - g_Console_Add(Format(_lc[I_PLAYER_JOIN], [PName]), True); + if NetDeafLevel < 3 then + g_Console_Add(Format(_lc[I_PLAYER_JOIN], [PName]), True); e_WriteLog('NET: Player ' + PName + ' [' + IntToStr(PID) + '] added.', TMsgType.Notify); Result := PID; end; @@ -2185,7 +2446,7 @@ begin ReleaseKeys; - if (kByte = NET_KEY_CHAT) then + if LongBool(kByte and NET_KEY_CHAT) then PressKey(KEY_CHAT, 10000) else begin @@ -2196,7 +2457,9 @@ begin if LongBool(kByte and NET_KEY_JUMP) then PressKey(KEY_JUMP, 10000); end; - if ((Pl <> gPlayer1) and (Pl <> gPlayer2)) or LongBool(kByte and NET_KEY_FORCEDIR) then + JustTeleported := LongBool(kByte and NET_KEY_FORCEDIR); + + if ((Pl <> gPlayer1) and (Pl <> gPlayer2)) or JustTeleported then SetDirection(TDirection(Dir)); GameVelX := M.ReadLongInt(); @@ -2265,23 +2528,29 @@ begin FSpectator := M.ReadByte() <> 0; if FSpectator then begin - if Pl = gPlayer1 then + if UID = NetPlrUID1 then begin - gLMSPID1 := UID; + gSpectLatchPID1 := UID; gPlayer1 := nil; end; - if Pl = gPlayer2 then + if UID = NetPlrUID2 then begin - gLMSPID2 := UID; + gSpectLatchPID2 := UID; gPlayer2 := nil; end; end else begin - if (gPlayer1 = nil) and (gLMSPID1 > 0) then - gPlayer1 := g_Player_Get(gLMSPID1); - if (gPlayer2 = nil) and (gLMSPID2 > 0) then - gPlayer2 := g_Player_Get(gLMSPID2); + if (gPlayer1 = nil) and (gSpectLatchPID1 > 0) and (UID = gSpectLatchPID1) then + begin + gPlayer1 := Pl; + gSpectLatchPID1 := 0; + end; + if (gPlayer2 = nil) and (gSpectLatchPID2 > 0) and (UID = gSpectLatchPID2) then + begin + gPlayer2 := Pl; + gSpectLatchPID2 := 0; + end; end; FGhost := M.ReadByte() <> 0; FPhysics := M.ReadByte() <> 0; @@ -2293,6 +2562,7 @@ begin if (OldFire <= 0) and (FFireTime > 0) then g_Sound_PlayExAt('SOUND_IGNITE', Obj.X, Obj.Y); Flam := M.ReadByte() <> 0; + FSpawnInvul := M.ReadLongInt(); if OldJet and not FJetpack then JetpackOff else if not OldJet and FJetpack then @@ -2366,7 +2636,8 @@ begin Result := 0; if Pl = nil then Exit; - g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [Pl.Name]), True); + if NetDeafLevel < 3 then + g_Console_Add(Format(_lc[I_PLAYER_LEAVE], [Pl.Name]), True); e_WriteLog('NET: Player ' + Pl.Name + ' [' + IntToStr(PID) + '] removed.', TMsgType.Notify); g_Player_Remove(PID); @@ -2405,6 +2676,7 @@ var TmpModel: string; TmpColor: TRGB; TmpTeam: Byte; + i: Integer; Pl: TPlayer; PID: Word; begin @@ -2431,7 +2703,8 @@ begin if Pl.Name <> TmpName then begin - g_Console_Add(Format(_lc[I_PLAYER_NAME], [Pl.Name, TmpName]), True); + if NetDeafLevel < 3 then + g_Console_Add(Format(_lc[I_PLAYER_NAME], [Pl.Name, TmpName]), True); Pl.Name := TmpName; end; @@ -2497,6 +2770,31 @@ begin g_Items_Remove(ID); end; +procedure MC_RECV_ItemPos(var M: TMsg); +var + ID: Word; + X, Y, VX, VY: Integer; + it: PItem; +begin + if not gGameOn then Exit; + + ID := M.ReadWord(); + X := M.ReadLongInt(); + Y := M.ReadLongInt(); + VX := M.ReadLongInt(); + VY := M.ReadLongInt(); + + if g_Items_ValidId(ID) then + begin + it := g_Items_ByIdx(ID); + it.Obj.X := X; + it.Obj.Y := Y; + it.Obj.Vel.X := VX; + it.Obj.Vel.Y := VY; + it.positionChanged(); + end; +end; + // PANEL procedure MC_RECV_PanelTexture(var M: TMsg); @@ -2825,23 +3123,25 @@ begin Str1 := M.ReadString(); Str2 := M.ReadString(); - case EvID of - NET_VE_STARTED: - g_Console_Add(Format(_lc[I_MESSAGE_VOTE_STARTED], [Str1, Str2, Int1]), True); - NET_VE_PASSED: - g_Console_Add(Format(_lc[I_MESSAGE_VOTE_PASSED], [Str1]), True); - NET_VE_FAILED: - g_Console_Add(_lc[I_MESSAGE_VOTE_FAILED], True); - NET_VE_VOTE: - g_Console_Add(Format(_lc[I_MESSAGE_VOTE_VOTE], [Str1, Int1, Int2]), True); - NET_VE_INPROGRESS: - g_Console_Add(Format(_lc[I_MESSAGE_VOTE_INPROGRESS], [Str1]), True); - end; + if NetDeafLevel < 2 then + case EvID of + NET_VE_STARTED: + g_Console_Add(Format(_lc[I_MESSAGE_VOTE_STARTED], [Str1, Str2, Int1]), True); + NET_VE_PASSED: + g_Console_Add(Format(_lc[I_MESSAGE_VOTE_PASSED], [Str1]), True); + NET_VE_FAILED: + g_Console_Add(_lc[I_MESSAGE_VOTE_FAILED], True); + NET_VE_VOTE: + g_Console_Add(Format(_lc[I_MESSAGE_VOTE_VOTE], [Str1, Int1, Int2]), True); + NET_VE_INPROGRESS: + g_Console_Add(Format(_lc[I_MESSAGE_VOTE_INPROGRESS], [Str1]), True); + end; end; // CLIENT SEND procedure MC_SEND_Info(Password: string); +var i: Integer; begin NetOut.Clear(); @@ -2854,6 +3154,11 @@ begin NetOut.Write(gPlayer1Settings.Color.G); NetOut.Write(gPlayer1Settings.Color.B); NetOut.Write(gPlayer1Settings.Team); + NetOut.Write(gPlayer1Settings.WeaponSwitch); + if (gPlayer1Settings.WeaponSwitch = 2) then + for i := WP_FIRST to WP_LAST + 1 do + NetOut.Write(gPlayer1Settings.WeaponPreferences[i]); + NetOut.Write(gPlayer1Settings.SwitchToEmpty); g_Net_Client_Send(True, NET_CHAN_SERVICE); end; @@ -2872,6 +3177,7 @@ var kByte: Word; Predict: Boolean; strafeDir: Byte; + WeaponAct: Byte = 0; WeaponSelect: Word = 0; i: Integer; begin @@ -2945,11 +3251,15 @@ begin end; if gPlayerAction[0, ACTION_ATTACK] then kByte := kByte or NET_KEY_FIRE; if gPlayerAction[0, ACTION_ACTIVATE] then kByte := kByte or NET_KEY_OPEN; - if gPlayerAction[0, ACTION_WEAPNEXT] then kByte := kByte or NET_KEY_NW; - if gPlayerAction[0, ACTION_WEAPPREV] then kByte := kByte or NET_KEY_PW; - gPlayerAction[0, ACTION_WEAPNEXT] := False; // HACK, remove after readyweaon&pendinweapon implementation - gPlayerAction[0, ACTION_WEAPPREV] := False; // HACK, remove after readyweaon&pendinweapon implementation + for i := WP_FACT to WP_LACT do + begin + if gWeaponAction[0, i] then + begin + WeaponAct := WeaponAct or Byte(1 shl i); + gWeaponAction[0, i] := False + end + end; for i := WP_FIRST to WP_LAST do begin @@ -2970,6 +3280,7 @@ begin NetOut.Write(gTime); NetOut.Write(kByte); NetOut.Write(Byte(gPlayer1.Direction)); + NetOut.Write(WeaponAct); NetOut.Write(WeaponSelect); //e_WriteLog(Format('S:ws=%d', [WeaponSelect]), MSG_WARNING); g_Net_Client_Send(True, NET_CHAN_PLAYERPOS); @@ -2987,6 +3298,7 @@ begin end; procedure MC_SEND_PlayerSettings(); +var i: Integer; begin NetOut.Write(Byte(NET_MSG_PLRSET)); NetOut.Write(gPlayer1Settings.Name); @@ -2995,7 +3307,11 @@ begin NetOut.Write(gPlayer1Settings.Color.G); NetOut.Write(gPlayer1Settings.Color.B); NetOut.Write(gPlayer1Settings.Team); - + NetOut.Write(gPlayer1Settings.WeaponSwitch); + if (gPlayer1Settings.WeaponSwitch = 2) then + for i := WP_FIRST to WP_LAST + 1 do + NetOut.Write(gPlayer1Settings.WeaponPreferences[i]); + NetOut.Write(gPlayer1Settings.SwitchToEmpty); g_Net_Client_Send(True, NET_CHAN_IMPORTANT); end; @@ -3028,204 +3344,5 @@ 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 - peer: pENetPeer; - payload: AByte; - 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.