DEADSOFTWARE

Make autoswitch server-side. Add option to skip empty weapons by travi$
[d2df-sdl.git] / src / game / g_netmsg.pas
index b170097d80296ec8ad3b7c7ed96e0bea1b54ccb4..55b99b3705acbc0ddbe05e00b04ba3657ea2c9fd 100644 (file)
@@ -2,8 +2,7 @@
  *
  * 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
@@ -32,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;
@@ -45,6 +45,7 @@ const
 
   NET_MSG_ISPAWN = 121;
   NET_MSG_IDEL   = 122;
+  NET_MSG_IPOS   = 123;
 
   NET_MSG_MSPAWN = 131;
   NET_MSG_MPOS   = 132;
@@ -52,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;
@@ -67,10 +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;
+  }
 
   NET_CHAT_SYSTEM = 0;
   NET_CHAT_PLAYER = 1;
@@ -109,6 +112,9 @@ const
   NET_EV_LMS_DRAW     = 16;
   NET_EV_KILLCOMBO    = 17;
   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;
@@ -124,11 +130,16 @@ 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);
@@ -137,12 +148,12 @@ procedure MH_RECV_PlayerSettings(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);
@@ -154,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);
@@ -167,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);
@@ -196,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;
@@ -209,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);
@@ -239,8 +254,9 @@ procedure MC_SEND_RCONPassword(Password: string);
 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
@@ -261,8 +277,9 @@ 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
 
@@ -273,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
@@ -322,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
@@ -369,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;
@@ -377,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) +
@@ -393,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
@@ -431,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
@@ -451,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;
@@ -474,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
@@ -494,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:
@@ -506,10 +677,32 @@ 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;
+    NET_CHEAT_READY:
+    begin
+      if gState <> STATE_INTERCUSTOM then Exit;
+      Pl.FReady := not Pl.FReady;
+      if Pl.FReady then
+      begin
+        MH_SEND_GameEvent(NET_EV_INTER_READY, Pl.UID, 'Y');
+        Inc(gInterReadyCount);
+      end
+      else
+      begin
+        MH_SEND_GameEvent(NET_EV_INTER_READY, Pl.UID, 'N');
+        Dec(gInterReadyCount);
+      end;
+    end;
+    NET_CHEAT_DROPFLAG:
+      Pl.TryDropFlag();
   end;
 end;
 
@@ -519,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;
@@ -545,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;
 
@@ -553,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
@@ -568,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
@@ -587,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;
@@ -626,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
@@ -650,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
@@ -707,25 +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);
@@ -937,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));
@@ -1003,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);
@@ -1023,7 +1276,6 @@ procedure MH_SEND_PlayerStats(PID: Word; ID: Integer = NET_EVERYONE);
 var
   P: TPlayer;
   I: Integer;
-  ww: Word;
 begin
   P := g_Player_Get(PID);
   if P = nil then Exit;
@@ -1042,10 +1294,8 @@ begin
     NetOut.Write(Lives);
     NetOut.Write(Team);
 
-    // collect all weapons in one word
-    ww := 0;
-    for I := WP_FIRST to WP_LAST do if (FWeapon[I]) then ww := ww or (1 shl (i-WP_FIRST));
-    NetOut.Write(Word(ww));
+    for I := WP_FIRST to WP_LAST do
+      NetOut.Write(Byte(FWeapon[I]));
 
     for I := A_BULLETS to A_HIGH do
       NetOut.Write(FAmmo[I]);
@@ -1056,30 +1306,25 @@ begin
     for I := MR_SUIT to MR_MAX do
       NetOut.Write(LongWord(FMegaRulez[I]));
 
-    // collect all special flags in one byte
-    ww := 0;
-    if (R_ITEM_BACKPACK in FRulez) then ww := ww or $01;
-    if (R_KEY_RED in FRulez) then ww := ww or $02;
-    if (R_KEY_GREEN in FRulez) then ww := ww or $04;
-    if (R_KEY_BLUE in FRulez) then ww := ww or $08;
-    if (R_BERSERK in FRulez) then ww := ww or $10;
-    NetOut.Write(Byte(ww));
+    NetOut.Write(Byte(R_ITEM_BACKPACK in FRulez));
+    NetOut.Write(Byte(R_KEY_RED in FRulez));
+    NetOut.Write(Byte(R_KEY_GREEN in FRulez));
+    NetOut.Write(Byte(R_KEY_BLUE in FRulez));
+    NetOut.Write(Byte(R_BERSERK in FRulez));
 
     NetOut.Write(Frags);
     NetOut.Write(Death);
 
     NetOut.Write(CurrWeap);
 
-    // other flags
-    ww := 0;
-    if (FSpectator) then ww := ww or $01;
-    if (FGhost) then ww := ww or $02;
-    if (FPhysics) then ww := ww or $04;
-    if (FNoRespawn) then ww := ww or $08;
-    if (FJetpack) then ww := ww or $10;
-    NetOut.Write(Byte(ww));
-
+    NetOut.Write(Byte(FSpectator));
+    NetOut.Write(Byte(FGhost));
+    NetOut.Write(Byte(FPhysics));
+    NetOut.Write(Byte(FNoRespawn));
+    NetOut.Write(Byte(FJetpack));
     NetOut.Write(FFireTime);
+    NetOut.Write(Byte(FFlaming));
+    NetOut.Write(FSpawnInvul);
   end;
 
   g_Net_Host_Send(ID, True, NET_CHAN_PLAYER);
@@ -1187,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);
@@ -1416,22 +1677,25 @@ begin
 
   if Mode <> NET_CHAT_SYSTEM then
   begin
-    if Mode = NET_CHAT_PLAYER then
+    if NetDeafLevel = 0 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));
+      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;
 
@@ -1653,6 +1917,7 @@ var
   i1, i2: TStrings_Locale;
   pln: String;
   cnt: Byte;
+  goodCmd: Boolean = true;
 begin
   FillChar(EvHash, Sizeof(EvHash), 0);
   EvType := M.ReadByte();
@@ -1668,33 +1933,63 @@ begin
 
   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;
+      NET_EV_LMS_WARMUP: 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;
+      gLMSRespawn := LMS_RESPAWN_NONE;
+      gLMSRespawnTime := 0;
+      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:
@@ -1711,26 +2006,46 @@ 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:
-      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);
+    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
@@ -1810,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;
 
@@ -1830,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);
 
@@ -1840,15 +2159,42 @@ begin
         pl.Touch();
     end;
 
+    NET_EV_SECRET:
+    begin
+      pl := g_Player_Get(EvNum);
+      if pl <> nil then
+      begin
+        g_Console_Add(Format(_lc[I_PLAYER_SECRET], [pl.Name]), True);
+        g_Sound_PlayEx('SOUND_GAME_SECRET');
+      end;
+    end;
+
+    NET_EV_INTER_READY:
+    begin
+      pl := g_Player_Get(EvNum);
+      if pl <> nil then pl.FReady := (EvStr = 'Y');
+    end;
   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;
   Pl: TPlayer;
   EvType: Byte;
-  Fl: Byte;
+  Fl, a: Byte;
   Quiet: Boolean;
   s, ts: string;
 begin
@@ -1866,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
@@ -1885,6 +2232,16 @@ begin
         s := _lc[I_PLAYER_FLAG_BLUE];
 
       g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
+
+      if ((Pl = gPlayer1) or (Pl = gPlayer2)
+      or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
+      or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
+        a := 0
+      else
+        a := 1;
+
+      if not sound_ret_flag[a].IsPlaying() then
+        sound_ret_flag[a].Play();
     end;
 
     FLAG_STATE_CAPTURED:
@@ -1900,6 +2257,16 @@ begin
 
       g_Console_Add(Format(_lc[I_PLAYER_FLAG_GET], [Pl.Name, s]), True);
       g_Game_Message(Format(_lc[I_MESSAGE_FLAG_GET], [AnsiUpperCase(s)]), 144);
+
+      if ((Pl = gPlayer1) or (Pl = gPlayer2)
+      or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
+      or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
+        a := 0
+      else
+        a := 1;
+
+      if not sound_get_flag[a].IsPlaying() then
+        sound_get_flag[a].Play();
     end;
 
     FLAG_STATE_DROPPED:
@@ -1915,6 +2282,16 @@ begin
 
       g_Console_Add(Format(_lc[I_PLAYER_FLAG_DROP], [Pl.Name, s]), True);
       g_Game_Message(Format(_lc[I_MESSAGE_FLAG_DROP], [AnsiUpperCase(s)]), 144);
+
+      if ((Pl = gPlayer1) or (Pl = gPlayer2)
+      or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
+      or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
+        a := 0
+      else
+        a := 1;
+
+      if not sound_lost_flag[a].IsPlaying() then
+        sound_lost_flag[a].Play();
     end;
 
     FLAG_STATE_SCORED:
@@ -1933,6 +2310,16 @@ begin
       Insert('.', ts, Length(ts) + 1 - 3);
       g_Console_Add(Format(_lc[I_PLAYER_FLAG_CAPTURE], [Pl.Name, s, ts]), True);
       g_Game_Message(Format(_lc[I_MESSAGE_FLAG_CAPTURE], [AnsiUpperCase(s)]), 144);
+
+      if ((Pl = gPlayer1) or (Pl = gPlayer2)
+      or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
+      or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
+        a := 0
+      else
+        a := 1;
+
+      if not sound_cap_flag[a].IsPlaying() then
+        sound_cap_flag[a].Play();
     end;
 
     FLAG_STATE_RETURNED:
@@ -1946,6 +2333,16 @@ begin
         s := _lc[I_PLAYER_FLAG_BLUE];
 
       g_Game_Message(Format(_lc[I_MESSAGE_FLAG_RETURN], [AnsiUpperCase(s)]), 144);
+
+      if ((Pl = gPlayer1) or (Pl = gPlayer2)
+      or ((gPlayer1 <> nil) and (gPlayer1.Team = Pl.Team))
+      or ((gPlayer2 <> nil) and (gPlayer2.Team = Pl.Team))) then
+        a := 0
+      else
+        a := 1;
+
+      if not sound_ret_flag[a].IsPlaying() then
+        sound_ret_flag[a].Play();
     end;
   end;
 end;
@@ -2005,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;
@@ -2048,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
@@ -2059,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();
@@ -2075,10 +2475,9 @@ function MC_RECV_PlayerStats(var M: TMsg): Word;
 var
   PID: Word;
   Pl: TPlayer;
-  I: Integer;
-  OldJet: Boolean;
+  I, OldFire: Integer;
+  OldJet, Flam: Boolean;
   NewTeam: Byte;
-  ww: Word;
 begin
   PID := M.ReadWord();
   Pl := g_Player_Get(PID);
@@ -2097,12 +2496,8 @@ begin
     Lives := M.ReadByte();
     NewTeam := M.ReadByte();
 
-    ww := M.ReadWord();
     for I := WP_FIRST to WP_LAST do
-    begin
-      FWeapon[I] := ((ww and $01) <> 0);
-      ww := ww shr 1;
-    end;
+      FWeapon[I] := (M.ReadByte() <> 0);
 
     for I := A_BULLETS to A_HIGH do
       FAmmo[I] := M.ReadWord();
@@ -2114,54 +2509,66 @@ begin
       FMegaRulez[I] := M.ReadLongWord();
 
     FRulez := [];
-    // unpack special flags
-    ww := M.ReadByte();
-    if ((ww and $01) <> 0) then FRulez := FRulez+[R_ITEM_BACKPACK];
-    if ((ww and $02) <> 0) then FRulez := FRulez+[R_KEY_RED];
-    if ((ww and $04) <> 0) then FRulez := FRulez+[R_KEY_GREEN];
-    if ((ww and $08) <> 0) then FRulez := FRulez+[R_KEY_BLUE];
-    if ((ww and $10) <> 0) then FRulez := FRulez+[R_BERSERK];
+    if (M.ReadByte() <> 0) then
+      FRulez := FRulez + [R_ITEM_BACKPACK];
+    if (M.ReadByte() <> 0) then
+      FRulez := FRulez + [R_KEY_RED];
+    if (M.ReadByte() <> 0) then
+      FRulez := FRulez + [R_KEY_GREEN];
+    if (M.ReadByte() <> 0) then
+      FRulez := FRulez + [R_KEY_BLUE];
+    if (M.ReadByte() <> 0) then
+      FRulez := FRulez + [R_BERSERK];
 
     Frags := M.ReadLongInt();
     Death := M.ReadLongInt();
 
     SetWeapon(M.ReadByte());
 
-    // other flags
-    ww := M.ReadByte();
-
-    FSpectator := ((ww and $01) <> 0);
+    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 := ((ww and $02) <> 0);
-    FPhysics := ((ww and $04) <> 0);
-    FNoRespawn := ((ww and $08) <> 0);
+    FGhost := M.ReadByte() <> 0;
+    FPhysics := M.ReadByte() <> 0;
+    FNoRespawn := M.ReadByte() <> 0;
     OldJet := FJetpack;
-    FJetpack := ((ww and $10) <> 0);
+    FJetpack := M.ReadByte() <> 0;
+    OldFire := FFireTime;
     FFireTime := M.ReadLongInt();
+    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
       JetpackOn;
+    if FFlaming and not Flam then
+      FlamerOff;
     if Team <> NewTeam then
       Pl.ChangeTeam(NewTeam);
   end;
@@ -2229,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);
@@ -2268,6 +2676,7 @@ var
   TmpModel: string;
   TmpColor: TRGB;
   TmpTeam: Byte;
+  i: Integer;
   Pl: TPlayer;
   PID: Word;
 begin
@@ -2294,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;
 
@@ -2360,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);
@@ -2503,12 +2938,13 @@ begin
   MPlaying := M.ReadByte() <> 0;
   MPos := M.ReadLongWord();
   MPaused := M.ReadByte() <> 0;
+  MPos := MPos+1; //k8: stfu, fpc!
 
   if MPlaying then
   begin
     gMusic.SetByName(MName);
     gMusic.Play(True);
-    gMusic.SetPosition(MPos);
+    // gMusic.SetPosition(MPos);
     gMusic.SpecPause := MPaused;
   end
   else
@@ -2594,7 +3030,7 @@ end;
 
 procedure MC_RECV_MonsterState(var M: TMsg);
 var
-  ID: Integer;
+  ID, OldFire: Integer;
   MState, MFAnm: Byte;
   Mon: TMonster;
   AnimRevert: Boolean;
@@ -2615,7 +3051,10 @@ begin
     MonsterAmmo := M.ReadLongInt();
     MonsterPain := M.ReadLongInt();
     AnimRevert := M.ReadByte() <> 0;
+    OldFire := FFireTime;
     FFireTime := M.ReadLongInt();
+    if (OldFire <= 0) and (FFireTime > 0) then
+      g_Sound_PlayExAt('SOUND_IGNITE', Obj.X, Obj.Y);
     RevertAnim(AnimRevert);
 
     if MonsterState <> MState then
@@ -2684,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();
 
@@ -2713,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;
@@ -2726,20 +3172,14 @@ begin
   g_Net_Client_Send(True, NET_CHAN_CHAT);
 end;
 
-function isKeyPressed (key1: Word; key2: Word): Boolean;
-begin
-  if (key1 <> 0) and e_KeyPressed(key1) then begin result := true; exit; end;
-  if (key2 <> 0) and e_KeyPressed(key2) then begin result := true; exit; end;
-  result := false;
-end;
-
 procedure MC_SEND_PlayerPos();
 var
   kByte: Word;
   Predict: Boolean;
   strafeDir: Byte;
+  WeaponAct: Byte = 0;
   WeaponSelect: Word = 0;
-  I: Integer;
+  i: Integer;
 begin
   if not gGameOn then Exit;
   if gPlayers = nil then Exit;
@@ -2752,86 +3192,95 @@ begin
   begin
     strafeDir := P1MoveButton shr 4;
     P1MoveButton := P1MoveButton and $0F;
-    with gGameControls.P1Control do
+
+    if gPlayerAction[0, ACTION_MOVELEFT] and (not gPlayerAction[0, ACTION_MOVERIGHT]) then
+      P1MoveButton := 1
+    else if (not gPlayerAction[0, ACTION_MOVELEFT]) and gPlayerAction[0, ACTION_MOVERIGHT] then
+      P1MoveButton := 2
+    else if (not gPlayerAction[0, ACTION_MOVELEFT]) and (not gPlayerAction[0, ACTION_MOVERIGHT]) then
+      P1MoveButton := 0;
+
+    // strafing
+    if gPlayerAction[0, ACTION_STRAFE] then
     begin
-           if isKeyPressed(KeyLeft, KeyLeft2) and (not isKeyPressed(KeyRight, KeyRight2)) then P1MoveButton := 1
-      else if (not isKeyPressed(KeyLeft, KeyLeft2)) and isKeyPressed(KeyRight, KeyRight2) then P1MoveButton := 2
-      else if (not isKeyPressed(KeyLeft, KeyLeft2)) and (not isKeyPressed(KeyRight, KeyRight2)) then P1MoveButton := 0;
+      // new strafe mechanics
+      if (strafeDir = 0) then
+        strafeDir := P1MoveButton; // start strafing
+      // now set direction according to strafe (reversed)
+      if (strafeDir = 2) then
+        gPlayer1.SetDirection(TDirection.D_LEFT)
+      else if (strafeDir = 1) then
+        gPlayer1.SetDirection(TDirection.D_RIGHT)
+    end
+    else
+    begin
+      strafeDir := 0; // not strafing anymore
+      if (P1MoveButton = 2) and gPlayerAction[0, ACTION_MOVELEFT] then
+        gPlayer1.SetDirection(TDirection.D_LEFT)
+      else if (P1MoveButton = 1) and gPlayerAction[0, ACTION_MOVERIGHT] then
+        gPlayer1.SetDirection(TDirection.D_RIGHT)
+      else if P1MoveButton <> 0 then
+        gPlayer1.SetDirection(TDirection(P1MoveButton-1));
+    end;
 
-      // strafing
-      if isKeyPressed(KeyStrafe, KeyStrafe2) then
+    gPlayer1.ReleaseKeys;
+    if P1MoveButton = 1 then
+    begin
+      kByte := kByte or NET_KEY_LEFT;
+      if Predict then gPlayer1.PressKey(KEY_LEFT, 10000);
+    end;
+    if P1MoveButton = 2 then
+    begin
+      kByte := kByte or NET_KEY_RIGHT;
+      if Predict then gPlayer1.PressKey(KEY_RIGHT, 10000);
+    end;
+    if gPlayerAction[0, ACTION_LOOKUP] then
+    begin
+      kByte := kByte or NET_KEY_UP;
+      gPlayer1.PressKey(KEY_UP, 10000);
+    end;
+    if gPlayerAction[0, ACTION_LOOKDOWN] then
+    begin
+      kByte := kByte or NET_KEY_DOWN;
+      gPlayer1.PressKey(KEY_DOWN, 10000);
+    end;
+    if gPlayerAction[0, ACTION_JUMP] then
+    begin
+      kByte := kByte or NET_KEY_JUMP;
+      // gPlayer1.PressKey(KEY_JUMP, 10000); // TODO: Make a prediction option
+    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;
+
+    for i := WP_FACT to WP_LACT do
+    begin
+      if gWeaponAction[0, i] then
       begin
-        // new strafe mechanics
-        if (strafeDir = 0) then strafeDir := P1MoveButton; // start strafing
-        // now set direction according to strafe (reversed)
-             if (strafeDir = 2) then gPlayer1.SetDirection(TDirection.D_LEFT)
-        else if (strafeDir = 1) then gPlayer1.SetDirection(TDirection.D_RIGHT);
+        WeaponAct := WeaponAct or Byte(1 shl i);
+        gWeaponAction[0, i] := False
       end
-      else
-      begin
-             if (P1MoveButton = 2) and isKeyPressed(KeyLeft, KeyLeft2) then gPlayer1.SetDirection(TDirection.D_LEFT)
-        else if (P1MoveButton = 1) and isKeyPressed(KeyRight, KeyRight2) then gPlayer1.SetDirection(TDirection.D_RIGHT)
-        else if P1MoveButton <> 0 then gPlayer1.SetDirection(TDirection(P1MoveButton-1));
-      end;
-
-      gPlayer1.ReleaseKeys;
-      gPlayer1.weaponSwitchKeysStateChange(-1, isKeyPressed(KeyNextWeapon, KeyNextWeapon2));
-      gPlayer1.weaponSwitchKeysStateChange(-2, isKeyPressed(KeyPrevWeapon, KeyPrevWeapon2));
+    end;
 
-      if P1MoveButton = 1 then
-      begin
-        kByte := kByte or NET_KEY_LEFT;
-        if Predict then gPlayer1.PressKey(KEY_LEFT, 10000);
-      end;
-      if P1MoveButton = 2 then
-      begin
-        kByte := kByte or NET_KEY_RIGHT;
-        if Predict then gPlayer1.PressKey(KEY_RIGHT, 10000);
-      end;
-      if isKeyPressed(KeyUp, KeyUp2) then
-      begin
-        kByte := kByte or NET_KEY_UP;
-        gPlayer1.PressKey(KEY_UP, 10000);
-      end;
-      if isKeyPressed(KeyDown, KeyDown2) then
-      begin
-        kByte := kByte or NET_KEY_DOWN;
-        gPlayer1.PressKey(KEY_DOWN, 10000);
-      end;
-      if isKeyPressed(KeyJump, KeyJump2) then
-      begin
-        kByte := kByte or NET_KEY_JUMP;
-        // gPlayer1.PressKey(KEY_JUMP, 10000); // TODO: Make a prediction option
-      end;
-      if isKeyPressed(KeyFire, KeyFire2) then kByte := kByte or NET_KEY_FIRE;
-      if isKeyPressed(KeyOpen, KeyOpen2) then kByte := kByte or NET_KEY_OPEN;
-      if isKeyPressed(KeyNextWeapon, KeyNextWeapon2) and gPlayer1.isWeaponSwitchKeyReleased(-1) then kByte := kByte or NET_KEY_NW;
-      if isKeyPressed(KeyPrevWeapon, KeyPrevWeapon2) and gPlayer1.isWeaponSwitchKeyReleased(-2) then kByte := kByte or NET_KEY_PW;
-      for I := 0 to High(KeyWeapon) do
+    for i := WP_FIRST to WP_LAST do
+    begin
+      if gSelectWeapon[0, i] then
       begin
-        if isKeyPressed(KeyWeapon[I], KeyWeapon2[I]) then
-        begin
-          gPlayer1.weaponSwitchKeysStateChange(i, true);
-          if gPlayer1.isWeaponSwitchKeyReleased(i) then WeaponSelect := WeaponSelect or Word(1 shl I);
-        end
-        else
-        begin
-          gPlayer1.weaponSwitchKeysStateChange(i, false);
-        end;
-      end;
+        WeaponSelect := WeaponSelect or Word(1 shl i);
+        gSelectWeapon[0, i] := False
+      end
     end;
+
     // fix movebutton state
     P1MoveButton := P1MoveButton or (strafeDir shl 4);
   end
   else
     kByte := NET_KEY_CHAT;
 
-  gPlayer1.weaponSwitchKeysShiftNewStates();
-
   NetOut.Write(Byte(NET_MSG_PLRPOS));
   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);
@@ -2849,6 +3298,7 @@ begin
 end;
 
 procedure MC_SEND_PlayerSettings();
+var i: Integer;
 begin
   NetOut.Write(Byte(NET_MSG_PLRSET));
   NetOut.Write(gPlayer1Settings.Name);
@@ -2857,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;
 
@@ -2890,201 +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
-  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.