DEADSOFTWARE

bye-bye, bineditor, we won't miss you
[d2df-sdl.git] / src / game / g_player.pas
index 932e081d6da0b56ef9dabac4c6e190b9ca2ceaa2..e6e0d360227097ccb7a83698395ab0cf4777ca3f 100644 (file)
@@ -20,10 +20,11 @@ unit g_player;
 interface
 
 uses
+  SysUtils, Classes,
   mempool,
   e_graphics, g_playermodel, g_basic, g_textures,
   g_weapons, g_phys, g_sound, g_saveload, MAPDEF,
-  BinEditor, g_panel;
+  g_panel;
 
 const
   KEY_LEFT       = 1;
@@ -310,8 +311,8 @@ type
     procedure   Update(); virtual;
     procedure   RememberState();
     procedure   RecallState();
-    procedure   SaveState(var Mem: TBinMemoryWriter); virtual;
-    procedure   LoadState(var Mem: TBinMemoryReader); virtual;
+    procedure   SaveState (st: TStream); virtual;
+    procedure   LoadState (st: TStream); virtual;
     procedure   PauseSounds(Enable: Boolean);
     procedure   NetFire(Wpn: Byte; X, Y, AX, AY: Integer; WID: Integer = -1);
     procedure   DoLerp(Level: Integer = 2);
@@ -400,15 +401,20 @@ type
   end;
 
   TDifficult = record
+  public
     DiagFire: Byte;
     InvisFire: Byte;
     DiagPrecision: Byte;
     FlyPrecision: Byte;
     Cover: Byte;
     CloseJump: Byte;
-    WeaponPrior: Array [WP_FIRST..WP_LAST] of Byte;
-    CloseWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte;
+    WeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
+    CloseWeaponPrior: packed array [WP_FIRST..WP_LAST] of Byte;
     //SafeWeaponPrior: Array [WP_FIRST..WP_LAST] of Byte;
+
+  public
+    procedure save (st: TStream);
+    procedure load (st: TStream);
   end;
 
   TAIFlag = record
@@ -449,8 +455,8 @@ type
     function    PickItem(ItemType: Byte; force: Boolean; var remove: Boolean): Boolean; override;
     function    Heal(value: Word; Soft: Boolean): Boolean; override;
     procedure   Update(); override;
-    procedure   SaveState(var Mem: TBinMemoryWriter); override;
-    procedure   LoadState(var Mem: TBinMemoryReader); override;
+    procedure   SaveState (st: TStream); override;
+    procedure   LoadState (st: TStream); override;
   end;
 
   PGib = ^TGib;
@@ -502,8 +508,8 @@ type
     procedure   Damage(Value: Word; vx, vy: Integer);
     procedure   Update();
     procedure   Draw();
-    procedure   SaveState(var Mem: TBinMemoryWriter);
-    procedure   LoadState(var Mem: TBinMemoryReader);
+    procedure   SaveState (st: TStream);
+    procedure   LoadState (st: TStream);
 
     procedure getMapBox (out x, y, w, h: Integer); inline;
     procedure moveBy (dx, dy: Integer); inline;
@@ -550,7 +556,7 @@ function  g_Shells_GetMax(): Word;
 procedure g_Player_Init();
 procedure g_Player_Free();
 function  g_Player_Create(ModelName: String; Color: TRGB; Team: Byte; Bot: Boolean): Word;
-function  g_Player_CreateFromState(var Mem: TBinMemoryReader): Word;
+function  g_Player_CreateFromState (st: TStream): Word;
 procedure g_Player_Remove(UID: Word);
 procedure g_Player_ResetTeams();
 procedure g_Player_UpdateAll();
@@ -570,8 +576,8 @@ procedure g_Player_UpdatePhysicalObjects();
 procedure g_Player_DrawCorpses();
 procedure g_Player_DrawShells();
 procedure g_Player_RemoveAllCorpses();
-procedure g_Player_Corpses_SaveState(var Mem: TBinMemoryWriter);
-procedure g_Player_Corpses_LoadState(var Mem: TBinMemoryReader);
+procedure g_Player_Corpses_SaveState (st: TStream);
+procedure g_Player_Corpses_LoadState (st: TStream);
 procedure g_Bot_Add(Team, Difficult: Byte);
 procedure g_Bot_AddList(Team: Byte; lname: ShortString; num: Integer = -1);
 procedure g_Bot_MixNames();
@@ -580,10 +586,13 @@ procedure g_Bot_RemoveAll();
 implementation
 
 uses
-  e_log, g_map, g_items, g_console, SysUtils, g_gfx, Math,
+  e_log, g_map, g_items, g_console, g_gfx, Math,
   g_options, g_triggers, g_menu, g_game, g_grid,
   wadreader, g_main, g_monsters, CONFIG, g_language,
-  g_net, g_netmsg, g_window, GL, g_holmes;
+  g_net, g_netmsg, g_window, GL, g_holmes,
+  utils, xstreams;
+
+const PLR_SAVE_VERSION = 0;
 
 type
   TBotProfile = record
@@ -802,47 +811,36 @@ begin
   Result := gPlayers[a].FUID;
 end;
 
-function g_Player_CreateFromState(var Mem: TBinMemoryReader): Word;
+function g_Player_CreateFromState (st: TStream): Word;
 var
   a, i: Integer;
   ok, Bot: Boolean;
-  sig: DWORD;
   b: Byte;
 begin
-  Result := 0;
-  if Mem = nil then
-    Exit;
+  result := 0;
+  if (st = nil) then exit; //???
 
-// Ñèãíàòóðà èãðîêà:
-  Mem.ReadDWORD(sig);
-  if sig <> PLAYER_SIGNATURE then // 'PLYR'
-  begin
-    raise EBinSizeError.Create('g_Player_CreateFromState: Wrong Player Signature');
-  end;
+  // Ñèãíàòóðà èãðîêà
+  if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
+  if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
 
-// Áîò èëè ÷åëîâåê:
-  Mem.ReadBoolean(Bot);
+  // Áîò èëè ÷åëîâåê:
+  Bot := utils.readBool(st);
 
-  ok := False;
+  ok := false;
   a := 0;
 
-// Åñòü ëè ìåñòî â gPlayers:
-  if gPlayers <> nil then
-    for a := 0 to High(gPlayers) do
-      if gPlayers[a] = nil then
-      begin
-        ok := True;
-        Break;
-      end;
+  // Åñòü ëè ìåñòî â gPlayers:
+  for a := 0 to High(gPlayers) do if (gPlayers[a] = nil) then begin ok := true; break; end;
 
-// Íåò ìåñòà - ðàñøèðÿåì gPlayers:
+  // Íåò ìåñòà - ðàñøèðÿåì gPlayers
   if not ok then
   begin
     SetLength(gPlayers, Length(gPlayers)+1);
     a := High(gPlayers);
   end;
 
-// Ñîçäàåì îáúåêò èãðîêà:
+  // Ñîçäàåì îáúåêò èãðîêà
   if Bot then
     gPlayers[a] := TBot.Create()
   else
@@ -850,133 +848,115 @@ begin
   gPlayers[a].FIamBot := Bot;
   gPlayers[a].FPhysics := True;
 
-// UID èãðîêà:
-  Mem.ReadWord(gPlayers[a].FUID);
-// Èìÿ èãðîêà:
-  Mem.ReadString(gPlayers[a].FName);
-// Êîìàíäà:
-  Mem.ReadByte(gPlayers[a].FTeam);
+  // UID èãðîêà
+  gPlayers[a].FUID := utils.readWord(st);
+  // Èìÿ èãðîêà
+  gPlayers[a].FName := utils.readStr(st);
+  // Êîìàíäà
+  gPlayers[a].FTeam := utils.readByte(st);
   gPlayers[a].FPreferredTeam := gPlayers[a].FTeam;
-// Æèâ ëè:
-  Mem.ReadBoolean(gPlayers[a].FAlive);
-// Èçðàñõîäîâàë ëè âñå æèçíè:
-  Mem.ReadBoolean(gPlayers[a].FNoRespawn);
-// Íàïðàâëåíèå:
-  Mem.ReadByte(b);
-  if b = 1 then
-    gPlayers[a].FDirection := D_LEFT
-  else // b = 2
-    gPlayers[a].FDirection := D_RIGHT;
-// Çäîðîâüå:
-  Mem.ReadInt(gPlayers[a].FHealth);
-// Æèçíè:
-  Mem.ReadByte(gPlayers[a].FLives);
-// Áðîíÿ:
-  Mem.ReadInt(gPlayers[a].FArmor);
-// Çàïàñ âîçäóõà:
-  Mem.ReadInt(gPlayers[a].FAir);
-// Çàïàñ ãîðþ÷åãî:
-  Mem.ReadInt(gPlayers[a].FJetFuel);
-// Áîëü:
-  Mem.ReadInt(gPlayers[a].FPain);
-// Óáèë:
-  Mem.ReadInt(gPlayers[a].FKills);
-// Óáèë ìîíñòðîâ:
-  Mem.ReadInt(gPlayers[a].FMonsterKills);
-// Ôðàãîâ:
-  Mem.ReadInt(gPlayers[a].FFrags);
-// Ôðàãîâ ïîäðÿä:
-  Mem.ReadByte(gPlayers[a].FFragCombo);
-// Âðåìÿ ïîñëåäíåãî ôðàãà:
-  Mem.ReadDWORD(gPlayers[a].FLastFrag);
-// Ñìåðòåé:
-  Mem.ReadInt(gPlayers[a].FDeath);
-// Êàêîé ôëàã íåñåò:
-  Mem.ReadByte(gPlayers[a].FFlag);
-// Íàøåë ñåêðåòîâ:
-  Mem.ReadInt(gPlayers[a].FSecrets);
-// Òåêóùåå îðóæèå:
-  Mem.ReadByte(gPlayers[a].FCurrWeap);
-// Ñëåäóþùåå æåëàåìîå îðóæèå:
-  Mem.ReadWord(gPlayers[a].FNextWeap);
-// ...è ïàóçà:
-  Mem.ReadByte(gPlayers[a].FNextWeapDelay);
-// Âðåìÿ çàðÿäêè BFG:
-  Mem.ReadSmallInt(gPlayers[a].FBFGFireCounter);
-// Áóôåð óðîíà:
-  Mem.ReadInt(gPlayers[a].FDamageBuffer);
-// Ïîñëåäíèé óäàðèâøèé:
-  Mem.ReadWord(gPlayers[a].FLastSpawnerUID);
-// Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà:
-  Mem.ReadByte(gPlayers[a].FLastHit);
-// Îáúåêò èãðîêà:
-  Obj_LoadState(@gPlayers[a].FObj, Mem);
-// Òåêóùåå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_HIGH do
-    Mem.ReadWord(gPlayers[a].FAmmo[i]);
-// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_HIGH do
-    Mem.ReadWord(gPlayers[a].FMaxAmmo[i]);
-// Íàëè÷èå îðóæèÿ:
-  for i := WP_FIRST to WP_LAST do
-    Mem.ReadBoolean(gPlayers[a].FWeapon[i]);
-// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
-  for i := WP_FIRST to WP_LAST do
-    Mem.ReadWord(gPlayers[a].FReloading[i]);
-// Íàëè÷èå ðþêçàêà:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(gPlayers[a].FRulez, R_ITEM_BACKPACK);
-// Íàëè÷èå êðàñíîãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(gPlayers[a].FRulez, R_KEY_RED);
-// Íàëè÷èå çåëåíîãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(gPlayers[a].FRulez, R_KEY_GREEN);
-// Íàëè÷èå ñèíåãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(gPlayers[a].FRulez, R_KEY_BLUE);
-// Íàëè÷èå áåðñåðêà:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(gPlayers[a].FRulez, R_BERSERK);
-// Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ:
-  for i := MR_SUIT to MR_MAX do
-    Mem.ReadDWORD(gPlayers[a].FMegaRulez[i]);
-// Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà:
-  for i := T_RESPAWN to T_FLAGCAP do
-    Mem.ReadDWORD(gPlayers[a].FTime[i]);
-
-// Íàçâàíèå ìîäåëè:
-  Mem.ReadString(gPlayers[a].FActualModelName);
-// Öâåò ìîäåëè:
-  Mem.ReadByte(gPlayers[a].FColor.R);
-  Mem.ReadByte(gPlayers[a].FColor.G);
-  Mem.ReadByte(gPlayers[a].FColor.B);
-// Îáíîâëÿåì ìîäåëü èãðîêà:
+  // Æèâ ëè
+  gPlayers[a].FAlive := utils.readBool(st);
+  // Èçðàñõîäîâàë ëè âñå æèçíè
+  gPlayers[a].FNoRespawn := utils.readBool(st);
+  // Íàïðàâëåíèå
+  b := utils.readByte(st);
+  if b = 1 then gPlayers[a].FDirection := D_LEFT else gPlayers[a].FDirection := D_RIGHT; // b = 2
+  // Çäîðîâüå
+  gPlayers[a].FHealth := utils.readLongInt(st);
+  // Æèçíè
+  gPlayers[a].FLives := utils.readByte(st);
+  // Áðîíÿ
+  gPlayers[a].FArmor := utils.readLongInt(st);
+  // Çàïàñ âîçäóõà
+  gPlayers[a].FAir := utils.readLongInt(st);
+  // Çàïàñ ãîðþ÷åãî
+  gPlayers[a].FJetFuel := utils.readLongInt(st);
+  // Áîëü
+  gPlayers[a].FPain := utils.readLongInt(st);
+  // Óáèë
+  gPlayers[a].FKills := utils.readLongInt(st);
+  // Óáèë ìîíñòðîâ
+  gPlayers[a].FMonsterKills := utils.readLongInt(st);
+  // Ôðàãîâ
+  gPlayers[a].FFrags := utils.readLongInt(st);
+  // Ôðàãîâ ïîäðÿä
+  gPlayers[a].FFragCombo := utils.readByte(st);
+  // Âðåìÿ ïîñëåäíåãî ôðàãà
+  gPlayers[a].FLastFrag := utils.readLongWord(st);
+  // Ñìåðòåé
+  gPlayers[a].FDeath := utils.readLongInt(st);
+  // Êàêîé ôëàã íåñåò
+  gPlayers[a].FFlag := utils.readByte(st);
+  // Íàøåë ñåêðåòîâ
+  gPlayers[a].FSecrets := utils.readLongInt(st);
+  // Òåêóùåå îðóæèå
+  gPlayers[a].FCurrWeap := utils.readByte(st);
+  // Ñëåäóþùåå æåëàåìîå îðóæèå
+  gPlayers[a].FNextWeap := utils.readWord(st);
+  // ...è ïàóçà
+  gPlayers[a].FNextWeapDelay := utils.readByte(st);
+  // Âðåìÿ çàðÿäêè BFG
+  gPlayers[a].FBFGFireCounter := utils.readSmallInt(st);
+  // Áóôåð óðîíà
+  gPlayers[a].FDamageBuffer := utils.readLongInt(st);
+  // Ïîñëåäíèé óäàðèâøèé
+  gPlayers[a].FLastSpawnerUID := utils.readWord(st);
+  // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
+  gPlayers[a].FLastHit := utils.readByte(st);
+  // Îáúåêò èãðîêà:
+  Obj_LoadState(@gPlayers[a].FObj, st);
+  // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
+  for i := A_BULLETS to A_HIGH do gPlayers[a].FAmmo[i] := utils.readWord(st);
+  // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
+  for i := A_BULLETS to A_HIGH do gPlayers[a].FMaxAmmo[i] := utils.readWord(st);
+  // Íàëè÷èå îðóæèÿ
+  for i := WP_FIRST to WP_LAST do gPlayers[a].FWeapon[i] := utils.readBool(st);
+  // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
+  for i := WP_FIRST to WP_LAST do gPlayers[a].FReloading[i] := utils.readWord(st);
+  // Íàëè÷èå ðþêçàêà
+  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_ITEM_BACKPACK);
+  // Íàëè÷èå êðàñíîãî êëþ÷à
+  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_RED);
+  // Íàëè÷èå çåëåíîãî êëþ÷à
+  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_GREEN);
+  // Íàëè÷èå ñèíåãî êëþ÷à
+  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_KEY_BLUE);
+  // Íàëè÷èå áåðñåðêà
+  if utils.readBool(st) then Include(gPlayers[a].FRulez, R_BERSERK);
+  // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
+  for i := MR_SUIT to MR_MAX do gPlayers[a].FMegaRulez[i] := utils.readLongWord(st);
+  // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
+  for i := T_RESPAWN to T_FLAGCAP do gPlayers[a].FTime[i] := utils.readLongWord(st);
+
+  // Íàçâàíèå ìîäåëè:
+  gPlayers[a].FActualModelName := utils.readStr(st);
+  // Öâåò ìîäåëè
+  gPlayers[a].FColor.R := utils.readByte(st);
+  gPlayers[a].FColor.G := utils.readByte(st);
+  gPlayers[a].FColor.B := utils.readByte(st);
+  // Îáíîâëÿåì ìîäåëü èãðîêà
   gPlayers[a].SetModel(gPlayers[a].FActualModelName);
 
-// Íåò ìîäåëè - ñîçäàíèå íå âîçìîæíî:
-  if gPlayers[a].FModel = nil then
+  // Íåò ìîäåëè - ñîçäàíèå íåâîçìîæíî
+  if (gPlayers[a].FModel = nil) then
   begin
     gPlayers[a].Free();
     gPlayers[a] := nil;
     g_FatalError(Format(_lc[I_GAME_ERROR_MODEL], [gPlayers[a].FActualModelName]));
-    Exit;
+    exit;
   end;
 
-// Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû:
+  // Åñëè êîìàíäíàÿ èãðà - êðàñèì ìîäåëü â öâåò êîìàíäû
   if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
     gPlayers[a].FModel.Color := TEAMCOLOR[gPlayers[a].FTeam]
   else
     gPlayers[a].FModel.Color := gPlayers[a].FColor;
 
-  Result := gPlayers[a].FUID;
+  result := gPlayers[a].FUID;
 end;
 
+
 procedure g_Player_ResetTeams();
 var
   a: Integer;
@@ -1874,76 +1854,66 @@ begin
   SetLength(gCorpses, MaxCorpses);
 end;
 
-procedure g_Player_Corpses_SaveState(var Mem: TBinMemoryWriter);
+procedure g_Player_Corpses_SaveState (st: TStream);
 var
   count, i: Integer;
-  b: Boolean;
 begin
-// Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðóïîâ:
+  // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ òðóïîâ
   count := 0;
-  if gCorpses <> nil then
-    for i := 0 to High(gCorpses) do
-      if gCorpses[i] <> nil then
-        count := count + 1;
-
-  Mem := TBinMemoryWriter.Create((count+1) * 128);
+  for i := 0 to High(gCorpses) do if (gCorpses[i] <> nil) then Inc(count);
 
-// Êîëè÷åñòâî òðóïîâ:
-  Mem.WriteInt(count);
+  // Êîëè÷åñòâî òðóïîâ
+  utils.writeInt(st, LongInt(count));
 
-  if count = 0 then
-    Exit;
+  if (count = 0) then exit;
 
-// Ñîõðàíÿåì òðóïû:
+  // Ñîõðàíÿåì òðóïû
   for i := 0 to High(gCorpses) do
+  begin
     if gCorpses[i] <> nil then
     begin
-    // Íàçâàíèå ìîäåëè:
-      Mem.WriteString(gCorpses[i].FModelName);
-    // Òèï ñìåðòè:
-      b := gCorpses[i].Mess;
-      Mem.WriteBoolean(b);
-    // Ñîõðàíÿåì äàííûå òðóïà:
-      gCorpses[i].SaveState(Mem);
+      // Íàçâàíèå ìîäåëè
+      utils.writeStr(st, gCorpses[i].FModelName);
+      // Òèï ñìåðòè
+      utils.writeBool(st, gCorpses[i].Mess);
+      // Ñîõðàíÿåì äàííûå òðóïà:
+      gCorpses[i].SaveState(st);
     end;
+  end;
 end;
 
-procedure g_Player_Corpses_LoadState(var Mem: TBinMemoryReader);
+
+procedure g_Player_Corpses_LoadState (st: TStream);
 var
   count, i: Integer;
   str: String;
   b: Boolean;
 begin
-  if Mem = nil then
-    Exit;
+  assert(st <> nil);
 
   g_Player_RemoveAllCorpses();
 
-// Êîëè÷åñòâî òðóïîâ:
-  Mem.ReadInt(count);
+  // Êîëè÷åñòâî òðóïîâ:
+  count := utils.readLongInt(st);
+  if (count < 0) or (count > Length(gCorpses)) then raise XStreamError.Create('invalid number of corpses');
 
-  if count > Length(gCorpses) then
-  begin
-    raise EBinSizeError.Create('g_Player_Corpses_LoadState: Too Many Corpses');
-  end;
+  if (count = 0) then exit;
 
-  if count = 0 then
-    Exit;
-
-// Çàãðóæàåì òðóïû:
+  // Çàãðóæàåì òðóïû
   for i := 0 to count-1 do
   begin
-  // Íàçâàíèå ìîäåëè:
-    Mem.ReadString(str);
-  // Òèï ñìåðòè:
-    Mem.ReadBoolean(b);
-  // Ñîçäàåì òðóï:
+    // Íàçâàíèå ìîäåëè:
+    str := utils.readStr(st);
+    // Òèï ñìåðòè
+    b := utils.readBool(st);
+    // Ñîçäàåì òðóï
     gCorpses[i] := TCorpse.Create(0, 0, str, b);
-  // Çàãðóæàåì äàííûå òðóïà:
-    gCorpses[i].LoadState(Mem);
+    // Çàãðóæàåì äàííûå òðóïà
+    gCorpses[i].LoadState(st);
   end;
 end;
 
+
 { T P l a y e r : }
 
 function TPlayer.isValidViewPort (): Boolean; inline; begin result := (viewPortW > 0) and (viewPortH > 0); end;
@@ -5673,280 +5643,214 @@ begin
     MH_SEND_PlayerStats(FUID);
 end;
 
-procedure TPlayer.SaveState(var Mem: TBinMemoryWriter);
+procedure TPlayer.SaveState (st: TStream);
 var
   i: Integer;
-  sig: DWORD;
-  str: String;
   b: Byte;
 begin
-  if FIamBot then
-    i := 512
-  else
-    i := 256;
-
-  Mem := TBinMemoryWriter.Create(i);
-
-// Ñèãíàòóðà èãðîêà:
-  sig := PLAYER_SIGNATURE; // 'PLYR'
-  Mem.WriteDWORD(sig);
-// Áîò èëè ÷åëîâåê:
-  Mem.WriteBoolean(FIamBot);
-// UID èãðîêà:
-  Mem.WriteWord(FUID);
-// Èìÿ èãðîêà:
-  Mem.WriteString(FName, 32);
-// Êîìàíäà:
-  Mem.WriteByte(FTeam);
-// Æèâ ëè:
-  Mem.WriteBoolean(FAlive);
-// Èçðàñõîäîâàë ëè âñå æèçíè:
-  Mem.WriteBoolean(FNoRespawn);
-// Íàïðàâëåíèå:
-  if FDirection = D_LEFT then
-    b := 1
-  else // D_RIGHT
-    b := 2;
-  Mem.WriteByte(b);
-// Çäîðîâüå:
-  Mem.WriteInt(FHealth);
-// Æèçíè:
-  Mem.WriteByte(FLives);
-// Áðîíÿ:
-  Mem.WriteInt(FArmor);
-// Çàïàñ âîçäóõà:
-  Mem.WriteInt(FAir);
-// Çàïàñ ãîðþ÷åãî:
-  Mem.WriteInt(FJetFuel);
-// Áîëü:
-  Mem.WriteInt(FPain);
-// Óáèë:
-  Mem.WriteInt(FKills);
-// Óáèë ìîíñòðîâ:
-  Mem.WriteInt(FMonsterKills);
-// Ôðàãîâ:
-  Mem.WriteInt(FFrags);
-// Ôðàãîâ ïîäðÿä:
-  Mem.WriteByte(FFragCombo);
-// Âðåìÿ ïîñëåäíåãî ôðàãà:
-  Mem.WriteDWORD(FLastFrag);
-// Ñìåðòåé:
-  Mem.WriteInt(FDeath);
-// Êàêîé ôëàã íåñåò:
-  Mem.WriteByte(FFlag);
-// Íàøåë ñåêðåòîâ:
-  Mem.WriteInt(FSecrets);
-// Òåêóùåå îðóæèå:
-  Mem.WriteByte(FCurrWeap);
-// Æåëàåìîå îðóæèå:
-  Mem.WriteWord(FNextWeap);
-// ...è ïàóçà
-  Mem.WriteByte(FNextWeapDelay);
-// Âðåìÿ çàðÿäêè BFG:
-  Mem.WriteSmallInt(FBFGFireCounter);
-// Áóôåð óðîíà:
-  Mem.WriteInt(FDamageBuffer);
-// Ïîñëåäíèé óäàðèâøèé:
-  Mem.WriteWord(FLastSpawnerUID);
-// Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà:
-  Mem.WriteByte(FLastHit);
-// Îáúåêò èãðîêà:
-  Obj_SaveState(@FObj, Mem);
-// Òåêóùåå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_HIGH do
-    Mem.WriteWord(FAmmo[i]);
-// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_HIGH do
-    Mem.WriteWord(FMaxAmmo[i]);
-// Íàëè÷èå îðóæèÿ:
-  for i := WP_FIRST to WP_LAST do
-    Mem.WriteBoolean(FWeapon[i]);
-// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
-  for i := WP_FIRST to WP_LAST do
-    Mem.WriteWord(FReloading[i]);
-// Íàëè÷èå ðþêçàêà:
-  if R_ITEM_BACKPACK in FRulez then
-    b := 1
-  else
-    b := 0;
-  Mem.WriteByte(b);
-// Íàëè÷èå êðàñíîãî êëþ÷à:
-  if R_KEY_RED in FRulez then
-    b := 1
-  else
-    b := 0;
-  Mem.WriteByte(b);
-// Íàëè÷èå çåëåíîãî êëþ÷à:
-  if R_KEY_GREEN in FRulez then
-    b := 1
-  else
-    b := 0;
-  Mem.WriteByte(b);
-// Íàëè÷èå ñèíåãî êëþ÷à:
-  if R_KEY_BLUE in FRulez then
-    b := 1
-  else
-    b := 0;
-  Mem.WriteByte(b);
-// Íàëè÷èå áåðñåðêà:
-  if R_BERSERK in FRulez then
-    b := 1
-  else
-    b := 0;
-  Mem.WriteByte(b);
-// Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ:
-  for i := MR_SUIT to MR_MAX do
-    Mem.WriteDWORD(FMegaRulez[i]);
-// Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà:
-  for i := T_RESPAWN to T_FLAGCAP do
-    Mem.WriteDWORD(FTime[i]);
-// Íàçâàíèå ìîäåëè:
-  str := FModel.Name;
-  Mem.WriteString(str);
-// Öâåò ìîäåëè:
-  b := FColor.R;
-  Mem.WriteByte(b);
-  b := FColor.G;
-  Mem.WriteByte(b);
-  b := FColor.B;
-  Mem.WriteByte(b);
-end;
-
-procedure TPlayer.LoadState(var Mem: TBinMemoryReader);
+  // Ñèãíàòóðà èãðîêà
+  utils.writeSign(st, 'PLYR');
+  utils.writeInt(st, Byte(PLR_SAVE_VERSION)); // version
+  // Áîò èëè ÷åëîâåê
+  utils.writeBool(st, FIamBot);
+  // UID èãðîêà
+  utils.writeInt(st, Word(FUID));
+  // Èìÿ èãðîêà
+  utils.writeStr(st, FName);
+  // Êîìàíäà
+  utils.writeInt(st, Byte(FTeam));
+  // Æèâ ëè
+  utils.writeBool(st, FAlive);
+  // Èçðàñõîäîâàë ëè âñå æèçíè
+  utils.writeBool(st, FNoRespawn);
+  // Íàïðàâëåíèå
+  if FDirection = D_LEFT then b := 1 else b := 2; // D_RIGHT
+  utils.writeInt(st, Byte(b));
+  // Çäîðîâüå
+  utils.writeInt(st, LongInt(FHealth));
+  // Æèçíè
+  utils.writeInt(st, Byte(FLives));
+  // Áðîíÿ
+  utils.writeInt(st, LongInt(FArmor));
+  // Çàïàñ âîçäóõà
+  utils.writeInt(st, LongInt(FAir));
+  // Çàïàñ ãîðþ÷åãî
+  utils.writeInt(st, LongInt(FJetFuel));
+  // Áîëü
+  utils.writeInt(st, LongInt(FPain));
+  // Óáèë
+  utils.writeInt(st, LongInt(FKills));
+  // Óáèë ìîíñòðîâ
+  utils.writeInt(st, LongInt(FMonsterKills));
+  // Ôðàãîâ
+  utils.writeInt(st, LongInt(FFrags));
+  // Ôðàãîâ ïîäðÿä
+  utils.writeInt(st, Byte(FFragCombo));
+  // Âðåìÿ ïîñëåäíåãî ôðàãà
+  utils.writeInt(st, LongWord(FLastFrag));
+  // Ñìåðòåé
+  utils.writeInt(st, LongInt(FDeath));
+  // Êàêîé ôëàã íåñåò
+  utils.writeInt(st, Byte(FFlag));
+  // Íàøåë ñåêðåòîâ
+  utils.writeInt(st, LongInt(FSecrets));
+  // Òåêóùåå îðóæèå
+  utils.writeInt(st, Byte(FCurrWeap));
+  // Æåëàåìîå îðóæèå
+  utils.writeInt(st, Word(FNextWeap));
+  // ...è ïàóçà
+  utils.writeInt(st, Byte(FNextWeapDelay));
+  // Âðåìÿ çàðÿäêè BFG
+  utils.writeInt(st, SmallInt(FBFGFireCounter));
+  // Áóôåð óðîíà
+  utils.writeInt(st, LongInt(FDamageBuffer));
+  // Ïîñëåäíèé óäàðèâøèé
+  utils.writeInt(st, Word(FLastSpawnerUID));
+  // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
+  utils.writeInt(st, Byte(FLastHit));
+  // Îáúåêò èãðîêà
+  Obj_SaveState(st, @FObj);
+  // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
+  for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FAmmo[i]));
+  // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
+  for i := A_BULLETS to A_HIGH do utils.writeInt(st, Word(FMaxAmmo[i]));
+  // Íàëè÷èå îðóæèÿ
+  for i := WP_FIRST to WP_LAST do utils.writeBool(st, FWeapon[i]);
+  // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
+  for i := WP_FIRST to WP_LAST do utils.writeInt(st, Word(FReloading[i]));
+  // Íàëè÷èå ðþêçàêà
+  utils.writeBool(st, (R_ITEM_BACKPACK in FRulez));
+  // Íàëè÷èå êðàñíîãî êëþ÷à
+  utils.writeBool(st, (R_KEY_RED in FRulez));
+  // Íàëè÷èå çåëåíîãî êëþ÷à
+  utils.writeBool(st, (R_KEY_GREEN in FRulez));
+  // Íàëè÷èå ñèíåãî êëþ÷à
+  utils.writeBool(st, (R_KEY_BLUE in FRulez));
+  // Íàëè÷èå áåðñåðêà
+  utils.writeBool(st, (R_BERSERK in FRulez));
+  // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
+  for i := MR_SUIT to MR_MAX do utils.writeInt(st, LongWord(FMegaRulez[i]));
+  // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
+  for i := T_RESPAWN to T_FLAGCAP do utils.writeInt(st, LongWord(FTime[i]));
+  // Íàçâàíèå ìîäåëè
+  utils.writeStr(st, FModel.Name);
+  // Öâåò ìîäåëè
+  utils.writeInt(st, Byte(FColor.R));
+  utils.writeInt(st, Byte(FColor.G));
+  utils.writeInt(st, Byte(FColor.B));
+end;
+
+
+procedure TPlayer.LoadState (st: TStream);
 var
   i: Integer;
-  sig: DWORD;
   str: String;
   b: Byte;
 begin
-  if Mem = nil then
-    Exit;
-
-// Ñèãíàòóðà èãðîêà:
-  Mem.ReadDWORD(sig);
-  if sig <> PLAYER_SIGNATURE then // 'PLYR'
-  begin
-    raise EBinSizeError.Create('TPlayer.LoadState: Wrong Player Signature');
-  end;
-// Áîò èëè ÷åëîâåê:
-  Mem.ReadBoolean(FIamBot);
-// UID èãðîêà:
-  Mem.ReadWord(FUID);
-// Èìÿ èãðîêà:
-  Mem.ReadString(str);
-  if (Self <> gPlayer1) and (Self <> gPlayer2) then
-    FName := str;
-// Êîìàíäà:
-  Mem.ReadByte(FTeam);
-// Æèâ ëè:
-  Mem.ReadBoolean(FAlive);
-// Èçðàñõîäîâàë ëè âñå æèçíè:
-  Mem.ReadBoolean(FNoRespawn);
-// Íàïðàâëåíèå:
-  Mem.ReadByte(b);
-  if b = 1 then
-    FDirection := D_LEFT
-  else // b = 2
-    FDirection := D_RIGHT;
-// Çäîðîâüå:
-  Mem.ReadInt(FHealth);
-// Æèçíè:
-  Mem.ReadByte(FLives);
-// Áðîíÿ:
-  Mem.ReadInt(FArmor);
-// Çàïàñ âîçäóõà:
-  Mem.ReadInt(FAir);
-// Çàïàñ ãîðþ÷åãî:
-  Mem.ReadInt(FJetFuel);
-// Áîëü:
-  Mem.ReadInt(FPain);
-// Óáèë:
-  Mem.ReadInt(FKills);
-// Óáèë ìîíñòðîâ:
-  Mem.ReadInt(FMonsterKills);
-// Ôðàãîâ:
-  Mem.ReadInt(FFrags);
-// Ôðàãîâ ïîäðÿä:
-  Mem.ReadByte(FFragCombo);
-// Âðåìÿ ïîñëåäíåãî ôðàãà:
-  Mem.ReadDWORD(FLastFrag);
-// Ñìåðòåé:
-  Mem.ReadInt(FDeath);
-// Êàêîé ôëàã íåñåò:
-  Mem.ReadByte(FFlag);
-// Íàøåë ñåêðåòîâ:
-  Mem.ReadInt(FSecrets);
-// Òåêóùåå îðóæèå:
-  Mem.ReadByte(FCurrWeap);
-// Æåëàåìîå îðóæèå:
-  Mem.ReadWord(FNextWeap);
-// ...è ïàóçà
-  Mem.ReadByte(FNextWeapDelay);
-// Âðåìÿ çàðÿäêè BFG:
-  Mem.ReadSmallInt(FBFGFireCounter);
-// Áóôåð óðîíà:
-  Mem.ReadInt(FDamageBuffer);
-// Ïîñëåäíèé óäàðèâøèé:
-  Mem.ReadWord(FLastSpawnerUID);
-// Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà:
-  Mem.ReadByte(FLastHit);
-// Îáúåêò èãðîêà:
-  Obj_LoadState(@FObj, Mem);
-// Òåêóùåå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_HIGH do
-    Mem.ReadWord(FAmmo[i]);
-// Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ:
-  for i := A_BULLETS to A_HIGH do
-    Mem.ReadWord(FMaxAmmo[i]);
-// Íàëè÷èå îðóæèÿ:
-  for i := WP_FIRST to WP_LAST do
-    Mem.ReadBoolean(FWeapon[i]);
-// Âðåìÿ ïåðåçàðÿäêè îðóæèÿ:
-  for i := WP_FIRST to WP_LAST do
-    Mem.ReadWord(FReloading[i]);
-// Íàëè÷èå ðþêçàêà:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(FRulez, R_ITEM_BACKPACK);
-// Íàëè÷èå êðàñíîãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(FRulez, R_KEY_RED);
-// Íàëè÷èå çåëåíîãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(FRulez, R_KEY_GREEN);
-// Íàëè÷èå ñèíåãî êëþ÷à:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(FRulez, R_KEY_BLUE);
-// Íàëè÷èå áåðñåðêà:
-  Mem.ReadByte(b);
-  if b = 1 then
-    Include(FRulez, R_BERSERK);
-// Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ:
-  for i := MR_SUIT to MR_MAX do
-    Mem.ReadDWORD(FMegaRulez[i]);
-// Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà:
-  for i := T_RESPAWN to T_FLAGCAP do
-    Mem.ReadDWORD(FTime[i]);
-// Íàçâàíèå ìîäåëè:
-  Mem.ReadString(str);
-// Öâåò ìîäåëè:
-  Mem.ReadByte(FColor.R);
-  Mem.ReadByte(FColor.G);
-  Mem.ReadByte(FColor.B);
-  if Self = gPlayer1 then
+  assert(st <> nil);
+
+  // Ñèãíàòóðà èãðîêà
+  if not utils.checkSign(st, 'PLYR') then raise XStreamError.Create('invalid player signature');
+  if (utils.readByte(st) <> PLR_SAVE_VERSION) then raise XStreamError.Create('invalid player version');
+  // Áîò èëè ÷åëîâåê:
+  FIamBot := utils.readBool(st);
+  // UID èãðîêà
+  FUID := utils.readWord(st);
+  // Èìÿ èãðîêà
+  str := utils.readStr(st);
+  if (self <> gPlayer1) and (self <> gPlayer2) then FName := str;
+  // Êîìàíäà
+  FTeam := utils.readByte(st);
+  // Æèâ ëè
+  FAlive := utils.readBool(st);
+  // Èçðàñõîäîâàë ëè âñå æèçíè
+  FNoRespawn := utils.readBool(st);
+  // Íàïðàâëåíèå
+  b := utils.readByte(st);
+  if b = 1 then FDirection := D_LEFT else FDirection := D_RIGHT; // b = 2
+  // Çäîðîâüå
+  FHealth := utils.readLongInt(st);
+  // Æèçíè
+  FLives := utils.readByte(st);
+  // Áðîíÿ
+  FArmor := utils.readLongInt(st);
+  // Çàïàñ âîçäóõà
+  FAir := utils.readLongInt(st);
+  // Çàïàñ ãîðþ÷åãî
+  FJetFuel := utils.readLongInt(st);
+  // Áîëü
+  FPain := utils.readLongInt(st);
+  // Óáèë
+  FKills := utils.readLongInt(st);
+  // Óáèë ìîíñòðîâ
+  FMonsterKills := utils.readLongInt(st);
+  // Ôðàãîâ
+  FFrags := utils.readLongInt(st);
+  // Ôðàãîâ ïîäðÿä
+  FFragCombo := utils.readByte(st);
+  // Âðåìÿ ïîñëåäíåãî ôðàãà
+  FLastFrag := utils.readLongWord(st);
+  // Ñìåðòåé
+  FDeath := utils.readLongInt(st);
+  // Êàêîé ôëàã íåñåò
+  FFlag := utils.readByte(st);
+  // Íàøåë ñåêðåòîâ
+  FSecrets := utils.readLongInt(st);
+  // Òåêóùåå îðóæèå
+  FCurrWeap := utils.readByte(st);
+  // Æåëàåìîå îðóæèå
+  FNextWeap := utils.readWord(st);
+  // ...è ïàóçà
+  FNextWeapDelay := utils.readByte(st);
+  // Âðåìÿ çàðÿäêè BFG
+  FBFGFireCounter := utils.readSmallInt(st);
+  // Áóôåð óðîíà
+  FDamageBuffer := utils.readLongInt(st);
+  // Ïîñëåäíèé óäàðèâøèé
+  FLastSpawnerUID := utils.readWord(st);
+  // Òèï ïîñëåäíåãî ïîëó÷åííîãî óðîíà
+  FLastHit := utils.readByte(st);
+  // Îáúåêò èãðîêà
+  Obj_LoadState(@FObj, st);
+  // Òåêóùåå êîëè÷åñòâî ïàòðîíîâ
+  for i := A_BULLETS to A_HIGH do FAmmo[i] := utils.readWord(st);
+  // Ìàêñèìàëüíîå êîëè÷åñòâî ïàòðîíîâ
+  for i := A_BULLETS to A_HIGH do FMaxAmmo[i] := utils.readWord(st);
+  // Íàëè÷èå îðóæèÿ
+  for i := WP_FIRST to WP_LAST do FWeapon[i] := utils.readBool(st);
+  // Âðåìÿ ïåðåçàðÿäêè îðóæèÿ
+  for i := WP_FIRST to WP_LAST do FReloading[i] := utils.readWord(st);
+  // Íàëè÷èå ðþêçàêà
+  if utils.readBool(st) then Include(FRulez, R_ITEM_BACKPACK);
+  // Íàëè÷èå êðàñíîãî êëþ÷à
+  if utils.readBool(st) then Include(FRulez, R_KEY_RED);
+  // Íàëè÷èå çåëåíîãî êëþ÷à
+  if utils.readBool(st) then Include(FRulez, R_KEY_GREEN);
+  // Íàëè÷èå ñèíåãî êëþ÷à
+  if utils.readBool(st) then Include(FRulez, R_KEY_BLUE);
+  // Íàëè÷èå áåðñåðêà
+  if utils.readBool(st) then Include(FRulez, R_BERSERK);
+  // Âðåìÿ äåéñòâèÿ ñïåöèàëüíûõ ïðåäìåòîâ
+  for i := MR_SUIT to MR_MAX do FMegaRulez[i] := utils.readLongWord(st);
+  // Âðåìÿ äî ïîâòîðíîãî ðåñïàóíà, ñìåíû îðóæèÿ, èñîëüçîâàíèÿ, çàõâàòà ôëàãà
+  for i := T_RESPAWN to T_FLAGCAP do FTime[i] := utils.readLongWord(st);
+  // Íàçâàíèå ìîäåëè
+  str := utils.readStr(st);
+  // Öâåò ìîäåëè
+  FColor.R := utils.readByte(st);
+  FColor.G := utils.readByte(st);
+  FColor.B := utils.readByte(st);
+  if (self = gPlayer1) then
   begin
     str := gPlayer1Settings.Model;
     FColor := gPlayer1Settings.Color;
-  end;
-  if Self = gPlayer2 then
+  end
+  else if (self = gPlayer2) then
   begin
     str := gPlayer2Settings.Model;
     FColor := gPlayer2Settings.Color;
   end;
-// Îáíîâëÿåì ìîäåëü èãðîêà:
+  // Îáíîâëÿåì ìîäåëü èãðîêà
   SetModel(str);
   if gGameSettings.GameMode in [GM_TDM, GM_CTF] then
     FModel.Color := TEAMCOLOR[FTeam]
@@ -5954,6 +5858,7 @@ begin
     FModel.Color := FColor;
 end;
 
+
 procedure TPlayer.AllRulez(Health: Boolean);
 var
   a: Integer;
@@ -6332,80 +6237,73 @@ begin
     FAnimationMask.Update();
 end;
 
-procedure TCorpse.SaveState(var Mem: TBinMemoryWriter);
+
+procedure TCorpse.SaveState (st: TStream);
 var
-  sig: DWORD;
   anim: Boolean;
 begin
-  if Mem = nil then
-    Exit;
-
-// Ñèãíàòóðà òðóïà:
-  sig := CORPSE_SIGNATURE; // 'CORP'
-  Mem.WriteDWORD(sig);
-// Ñîñòîÿíèå:
-  Mem.WriteByte(FState);
-// Íàêîïëåííûé óðîí:
-  Mem.WriteByte(FDamage);
-// Öâåò:
-  Mem.WriteByte(FColor.R);
-  Mem.WriteByte(FColor.G);
-  Mem.WriteByte(FColor.B);
-// Îáúåêò òðóïà:
-  Obj_SaveState(@FObj, Mem);
-// Åñòü ëè àíèìàöèÿ:
-  anim := FAnimation <> nil;
-  Mem.WriteBoolean(anim);
-// Åñëè åñòü - ñîõðàíÿåì:
-  if anim then
-    FAnimation.SaveState(Mem);
-// Åñòü ëè ìàñêà àíèìàöèè:
-  anim := FAnimationMask <> nil;
-  Mem.WriteBoolean(anim);
-// Åñëè åñòü - ñîõðàíÿåì:
-  if anim then
-    FAnimationMask.SaveState(Mem);
-end;
-
-procedure TCorpse.LoadState(var Mem: TBinMemoryReader);
+  assert(st <> nil);
+
+  // Ñèãíàòóðà òðóïà
+  utils.writeSign(st, 'CORP');
+  utils.writeInt(st, Byte(0));
+  // Ñîñòîÿíèå
+  utils.writeInt(st, Byte(FState));
+  // Íàêîïëåííûé óðîí
+  utils.writeInt(st, Byte(FDamage));
+  // Öâåò
+  utils.writeInt(st, Byte(FColor.R));
+  utils.writeInt(st, Byte(FColor.G));
+  utils.writeInt(st, Byte(FColor.B));
+  // Îáúåêò òðóïà
+  Obj_SaveState(st, @FObj);
+  // Åñòü ëè àíèìàöèÿ
+  anim := (FAnimation <> nil);
+  utils.writeBool(st, anim);
+  // Åñëè åñòü - ñîõðàíÿåì
+  if anim then FAnimation.SaveState(st);
+  // Åñòü ëè ìàñêà àíèìàöèè
+  anim := (FAnimationMask <> nil);
+  utils.writeBool(st, anim);
+  // Åñëè åñòü - ñîõðàíÿåì
+  if anim then FAnimationMask.SaveState(st);
+end;
+
+
+procedure TCorpse.LoadState (st: TStream);
 var
-  sig: DWORD;
   anim: Boolean;
 begin
-  if Mem = nil then
-    Exit;
-
-// Ñèãíàòóðà òðóïà:
-  Mem.ReadDWORD(sig);
-  if sig <> CORPSE_SIGNATURE then // 'CORP'
-  begin
-    raise EBinSizeError.Create('TCorpse.LoadState: Wrong Corpse Signature');
-  end;
-// Ñîñòîÿíèå:
-  Mem.ReadByte(FState);
-// Íàêîïëåííûé óðîí:
-  Mem.ReadByte(FDamage);
-// Öâåò:
-  Mem.ReadByte(FColor.R);
-  Mem.ReadByte(FColor.G);
-  Mem.ReadByte(FColor.B);
-// Îáúåêò òðóïà:
-  Obj_LoadState(@FObj, Mem);
-// Åñòü ëè àíèìàöèÿ:
-  Mem.ReadBoolean(anim);
-// Åñëè åñòü - çàãðóæàåì:
+  assert(st <> nil);
+
+  // Ñèãíàòóðà òðóïà
+  if not utils.checkSign(st, 'CORP') then raise XStreamError.Create('invalid corpse signature');
+  if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid corpse version');
+  // Ñîñòîÿíèå
+  FState := utils.readByte(st);
+  // Íàêîïëåííûé óðîí
+  FDamage := utils.readByte(st);
+  // Öâåò
+  FColor.R := utils.readByte(st);
+  FColor.G := utils.readByte(st);
+  FColor.B := utils.readByte(st);
+  // Îáúåêò òðóïà
+  Obj_LoadState(@FObj, st);
+  // Åñòü ëè àíèìàöèÿ
+  anim := utils.readBool(st);
+  // Åñëè åñòü - çàãðóæàåì
   if anim then
   begin
     Assert(FAnimation <> nil, 'TCorpse.LoadState: no FAnimation');
-    FAnimation.LoadState(Mem);
+    FAnimation.LoadState(st);
   end;
-// Åñòü ëè ìàñêà àíèìàöèè:
-  Mem.ReadBoolean(anim);
-// Åñëè åñòü - çàãðóæàåì:
+  // Åñòü ëè ìàñêà àíèìàöèè
+  anim := utils.readBool(st);
+  // Åñëè åñòü - çàãðóæàåì
   if anim then
   begin
     Assert(FAnimationMask <> nil, 'TCorpse.LoadState: no FAnimationMask');
-    FAnimationMask.LoadState(Mem);
+    FAnimationMask.LoadState(st);
   end;
 end;
 
@@ -7611,64 +7509,84 @@ begin
   Result := Round((255-a)/255*radius*(Random(2)-1));
 end;
 
-procedure TBot.SaveState(var Mem: TBinMemoryWriter);
+
+procedure TDifficult.save (st: TStream);
+begin
+  utils.writeInt(st, Byte(DiagFire));
+  utils.writeInt(st, Byte(InvisFire));
+  utils.writeInt(st, Byte(DiagPrecision));
+  utils.writeInt(st, Byte(FlyPrecision));
+  utils.writeInt(st, Byte(Cover));
+  utils.writeInt(st, Byte(CloseJump));
+  st.WriteBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
+  st.WriteBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
+end;
+
+procedure TDifficult.load (st: TStream);
+begin
+  DiagFire := utils.readByte(st);
+  InvisFire := utils.readByte(st);
+  DiagPrecision := utils.readByte(st);
+  FlyPrecision := utils.readByte(st);
+  Cover := utils.readByte(st);
+  CloseJump := utils.readByte(st);
+  st.ReadBuffer(WeaponPrior[Low(WeaponPrior)], sizeof(WeaponPrior));
+  st.ReadBuffer(CloseWeaponPrior[Low(CloseWeaponPrior)], sizeof(CloseWeaponPrior));
+end;
+
+
+procedure TBot.SaveState (st: TStream);
 var
   i: Integer;
-  dw: DWORD;
-  p: Pointer;
-begin
-  inherited SaveState(Mem);
-
-// Âûáðàííîå îðóæèå:
-  Mem.WriteByte(FSelectedWeapon);
-// UID öåëè:
-  Mem.WriteWord(FTargetUID);
-// Âðåìÿ ïîòåðè öåëè:
-  Mem.WriteDWORD(FLastVisible);
-// Êîëè÷åñòâî ôëàãîâ ÈÈ:
+  dw: Integer;
+begin
+  inherited SaveState(st);
+  utils.writeSign(st, 'BOT0');
+  // Âûáðàííîå îðóæèå
+  utils.writeInt(st, Byte(FSelectedWeapon));
+  // UID öåëè
+  utils.writeInt(st, Word(FTargetUID));
+  // Âðåìÿ ïîòåðè öåëè
+  utils.writeInt(st, LongWord(FLastVisible));
+  // Êîëè÷åñòâî ôëàãîâ ÈÈ
   dw := Length(FAIFlags);
-  Mem.WriteDWORD(dw);
-// Ôëàãè ÈÈ:
-  for i := 0 to Integer(dw)-1 do
+  utils.writeInt(st, LongInt(dw));
+  // Ôëàãè ÈÈ
+  for i := 0 to dw-1 do
   begin
-    Mem.WriteString(FAIFlags[i].Name, 20);
-    Mem.WriteString(FAIFlags[i].Value, 20);
+    utils.writeStr(st, FAIFlags[i].Name, 20);
+    utils.writeStr(st, FAIFlags[i].Value, 20);
   end;
-// Íàñòðîéêè ñëîæíîñòè:
-  p := @FDifficult;
-  Mem.WriteMemory(p, SizeOf(TDifficult));
+  // Íàñòðîéêè ñëîæíîñòè
+  FDifficult.save(st);
 end;
 
-procedure TBot.LoadState(var Mem: TBinMemoryReader);
+
+procedure TBot.LoadState (st: TStream);
 var
   i: Integer;
-  dw: DWORD;
-  p: Pointer;
-begin
-  inherited LoadState(Mem);
-
-// Âûáðàííîå îðóæèå:
-  Mem.ReadByte(FSelectedWeapon);
-// UID öåëè:
-  Mem.ReadWord(FTargetUID);
-// Âðåìÿ ïîòåðè öåëè:
-  Mem.ReadDWORD(FLastVisible);
-// Êîëè÷åñòâî ôëàãîâ ÈÈ:
-  Mem.ReadDWORD(dw);
+  dw: Integer;
+begin
+  inherited LoadState(st);
+  if not utils.checkSign(st, 'BOT0') then raise XStreamError.Create('invalid bot signature');
+  // Âûáðàííîå îðóæèå
+  FSelectedWeapon := utils.readByte(st);
+  // UID öåëè
+  FTargetUID := utils.readWord(st);
+  // Âðåìÿ ïîòåðè öåëè
+  FLastVisible := utils.readLongWord(st);
+  // Êîëè÷åñòâî ôëàãîâ ÈÈ
+  dw := utils.readLongInt(st);
+  if (dw < 0) or (dw > 16384) then raise XStreamError.Create('invalid number of bot AI flags');
   SetLength(FAIFlags, dw);
-// Ôëàãè ÈÈ:
-  for i := 0 to Integer(dw)-1 do
-  begin
-    Mem.ReadString(FAIFlags[i].Name);
-    Mem.ReadString(FAIFlags[i].Value);
-  end;
-// Íàñòðîéêè ñëîæíîñòè:
-  Mem.ReadMemory(p, dw);
-  if dw <> SizeOf(TDifficult) then
+  // Ôëàãè ÈÈ
+  for i := 0 to dw-1 do
   begin
-    raise EBinSizeError.Create('TBot.LoadState: Wrong FDifficult Size');
+    FAIFlags[i].Name := utils.readStr(st, 20);
+    FAIFlags[i].Value := utils.readStr(st, 20);
   end;
-  FDifficult := TDifficult(p^);
+  // Íàñòðîéêè ñëîæíîñòè
+  FDifficult.load(st);
 end;