DEADSOFTWARE

fix weaponstay when item respawn is off
[d2df-sdl.git] / src / game / g_items.pas
index 995651748561cc0073261b288feea077fe1bf4d4..ae047f82b8afcc86bfa283e05012af0964c9b3a5 100644 (file)
@@ -1,9 +1,8 @@
-(* Copyright (C)  DooM 2D:Forever Developers
+(* Copyright (C)  Doom 2D: Forever Developers
  *
  * 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
@@ -19,7 +18,8 @@ unit g_items;
 interface
 
 uses
-  g_textures, g_phys, g_saveload, BinEditor, MAPDEF;
+  SysUtils, Classes,
+  MAPDEF, g_textures, g_phys, g_saveload;
 
 Type
   PItem = ^TItem;
@@ -30,16 +30,17 @@ Type
     arrIdx: Integer; // in ggItems
 
   public
-    ItemType:      Byte;
-    Respawnable:   Boolean;
-    InitX, InitY:  Integer;
-    RespawnTime:   Word;
-    Live:          Boolean;
-    Fall:          Boolean;
-    QuietRespawn:  Boolean;
-    SpawnTrigger:  Integer;
-    Obj:           TObj;
-    Animation:     TAnimation;
+    ItemType: Byte;
+    Respawnable: Boolean;
+    InitX, InitY: Integer;
+    RespawnTime: Word;
+    alive: Boolean;
+    Fall: Boolean;
+    QuietRespawn: Boolean;
+    SpawnTrigger: Integer;
+    Obj: TObj;
+    Animation: TAnimation;
+    dropped: Boolean; // dropped from the monster? drops should be rendered after corpses, so zombie corpse will not obscure ammo container, for example
 
     procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
 
@@ -52,12 +53,14 @@ procedure g_Items_Init();
 procedure g_Items_Free();
 function g_Items_Create(X, Y: Integer; ItemType: Byte;
            Fall, Respawnable: Boolean; AdjCoord: Boolean = False; ForcedID: Integer = -1): DWORD;
+procedure g_Items_SetDrop (ID: DWORD);
 procedure g_Items_Update();
 procedure g_Items_Draw();
+procedure g_Items_DrawDrop();
 procedure g_Items_Pick(ID: DWORD);
 procedure g_Items_Remove(ID: DWORD);
-procedure g_Items_SaveState(var Mem: TBinMemoryWriter);
-procedure g_Items_LoadState(var Mem: TBinMemoryReader);
+procedure g_Items_SaveState (st: TStream);
+procedure g_Items_LoadState (st: TStream);
 
 procedure g_Items_RestartRound ();
 
@@ -68,6 +71,8 @@ function g_Items_ObjByIdx (idx: Integer): PObj;
 procedure g_Items_EmitPickupSound (idx: Integer); // at item position
 procedure g_Items_EmitPickupSoundAt (idx, x, y: Integer);
 
+procedure g_Items_AddDynLights();
+
 
 type
   TItemEachAliveCB = function (it: PItem): Boolean is nested; // return `true` to stop
@@ -77,16 +82,17 @@ function g_Items_ForEachAlive (cb: TItemEachAliveCB; backwards: Boolean=false):
 
 var
   gItemsTexturesID: Array [1..ITEM_MAX] of DWORD;
-  gMaxDist: Integer = 1;
+  gMaxDist: Integer = 1; // for sounds
   ITEM_RESPAWNTIME: Integer = 60 * 36;
 
 implementation
 
 uses
+  Math,
   g_basic, e_graphics, g_sound, g_main, g_gfx, g_map,
-  Math, g_game, g_triggers, g_console, SysUtils, g_player, g_net, g_netmsg,
+  g_game, g_triggers, g_console, g_player, g_net, g_netmsg,
   e_log,
-  g_grid, binheap;
+  g_grid, binheap, idpool, utils, xstreams;
 
 
 var
@@ -95,7 +101,7 @@ var
 
 // ////////////////////////////////////////////////////////////////////////// //
 var
-  freeIds: TBinaryHeapInt = nil; // free item ids
+  freeIds: TIdPool = nil;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
@@ -205,12 +211,11 @@ begin
   g_Texture_Get('ITEM_KEY_BLUE',         gItemsTexturesID[ITEM_KEY_BLUE]);
   g_Texture_Get('ITEM_WEAPON_KASTET',    gItemsTexturesID[ITEM_WEAPON_KASTET]);
   g_Texture_Get('ITEM_WEAPON_PISTOL',    gItemsTexturesID[ITEM_WEAPON_PISTOL]);
-  g_Texture_Get('ITEM_JETPACK',          gItemsTexturesID[ITEM_JETPACK]);
 end;
 
 procedure g_Items_LoadData();
 begin
-  e_WriteLog('Loading items data...', MSG_NOTIFY);
+  e_WriteLog('Loading items data...', TMsgType.Notify);
 
   g_Sound_CreateWADEx('SOUND_ITEM_RESPAWNITEM', GameWAD+':SOUNDS\RESPAWNITEM');
   g_Sound_CreateWADEx('SOUND_ITEM_GETRULEZ', GameWAD+':SOUNDS\GETRULEZ');
@@ -221,6 +226,7 @@ begin
   g_Frames_CreateWAD(nil, 'FRAMES_ITEM_WHITESPHERE', GameWAD+':TEXTURES\SWHITE', 32, 32, 4, True);
   g_Frames_CreateWAD(nil, 'FRAMES_ITEM_ARMORGREEN', GameWAD+':TEXTURES\ARMORGREEN', 32, 16, 3, True);
   g_Frames_CreateWAD(nil, 'FRAMES_ITEM_ARMORBLUE', GameWAD+':TEXTURES\ARMORBLUE', 32, 16, 3, True);
+  g_Frames_CreateWAD(nil, 'FRAMES_ITEM_JETPACK', GameWAD+':TEXTURES\JETPACK', 32, 32, 3, True);
   g_Frames_CreateWAD(nil, 'FRAMES_ITEM_INVUL', GameWAD+':TEXTURES\INVUL', 32, 32, 4, True);
   g_Frames_CreateWAD(nil, 'FRAMES_ITEM_INVIS', GameWAD+':TEXTURES\INVIS', 32, 32, 4, True);
   g_Frames_CreateWAD(nil, 'FRAMES_ITEM_RESPAWN', GameWAD+':TEXTURES\ITEMRESPAWN', 32, 32, 5, True);
@@ -259,17 +265,16 @@ begin
   g_Texture_CreateWADEx('ITEM_SUIT', GameWAD+':TEXTURES\SUIT');
   g_Texture_CreateWADEx('ITEM_WEAPON_KASTET', GameWAD+':TEXTURES\KASTET');
   g_Texture_CreateWADEx('ITEM_MEDKIT_BLACK', GameWAD+':TEXTURES\BMED');
-  g_Texture_CreateWADEx('ITEM_JETPACK', GameWAD+':TEXTURES\JETPACK');
 
   InitTextures();
 
-  freeIds := binHeapNewIntLess();
+  freeIds := TIdPool.Create();
 end;
 
 
 procedure g_Items_FreeData();
 begin
-  e_WriteLog('Releasing items data...', MSG_NOTIFY);
+  e_WriteLog('Releasing items data...', TMsgType.Notify);
 
   g_Sound_Delete('SOUND_ITEM_RESPAWNITEM');
   g_Sound_Delete('SOUND_ITEM_GETRULEZ');
@@ -280,6 +285,7 @@ begin
   g_Frames_DeleteByName('FRAMES_ITEM_WHITESPHERE');
   g_Frames_DeleteByName('FRAMES_ITEM_ARMORGREEN');
   g_Frames_DeleteByName('FRAMES_ITEM_ARMORBLUE');
+  g_Frames_DeleteByName('FRAMES_ITEM_JETPACK');
   g_Frames_DeleteByName('FRAMES_ITEM_INVUL');
   g_Frames_DeleteByName('FRAMES_ITEM_INVIS');
   g_Frames_DeleteByName('FRAMES_ITEM_RESPAWN');
@@ -318,9 +324,9 @@ begin
   g_Texture_Delete('ITEM_SUIT');
   g_Texture_Delete('ITEM_WEAPON_KASTET');
   g_Texture_Delete('ITEM_MEDKIT_BLACK');
-  g_Texture_Delete('ITEM_JETPACK');
 
   freeIds.Free();
+  freeIds := nil;
 end;
 
 
@@ -329,8 +335,9 @@ var
   it: PItem;
 begin
   if (idx < 0) or (idx > High(ggItems)) then raise Exception.Create('releaseItem: invalid item id');
+  if not freeIds.hasAlloced[LongWord(idx)] then raise Exception.Create('releaseItem: trying to release unallocated item (0)');
   it := @ggItems[idx];
-  if not it.slotIsUsed then raise Exception.Create('releaseItem: trying to release unallocated item');
+  if not it.slotIsUsed then raise Exception.Create('releaseItem: trying to release unallocated item (1)');
   if (it.arrIdx <> idx) then raise Exception.Create('releaseItem: arrIdx inconsistency');
   it.slotIsUsed := false;
   if (it.Animation <> nil) then
@@ -338,40 +345,41 @@ begin
     it.Animation.Free();
     it.Animation := nil;
   end;
-  it.Live := False;
+  it.alive := False;
   it.SpawnTrigger := -1;
   it.ItemType := ITEM_NONE;
-  freeIds.insert(it.arrIdx);
+  freeIds.release(LongWord(idx));
 end;
 
 
-function allocItem (): DWORD;
+procedure growItemArrayTo (newsz: Integer);
 var
   i, olen: Integer;
   it: PItem;
 begin
-  if (freeIds.count = 0) then
+  if (newsz < Length(ggItems)) then exit;
+  // no free slots
+  olen := Length(ggItems);
+  SetLength(ggItems, newsz);
+  for i := olen to High(ggItems) do
   begin
-    // no free slots
-    olen := Length(ggItems);
-    SetLength(ggItems, olen+64);
-    for i := olen to High(ggItems) do
-    begin
-      it := @ggItems[i];
-      it.slotIsUsed := false;
-      it.arrIdx := i;
-      it.ItemType := ITEM_NONE;
-      it.Animation := nil;
-      it.Live := false;
-      it.SpawnTrigger := -1;
-      it.Respawnable := false;
-      freeIds.insert(i);
-    end;
+    it := @ggItems[i];
+    it.slotIsUsed := false;
+    it.arrIdx := i;
+    it.ItemType := ITEM_NONE;
+    it.Animation := nil;
+    it.alive := false;
+    it.SpawnTrigger := -1;
+    it.Respawnable := false;
+    //if not freeIds.hasFree[LongWord(i)] then raise Exception.Create('internal error in item idx manager');
   end;
+end;
 
-  result := freeIds.front;
-  freeIds.popFront();
 
+function allocItem (): DWORD;
+begin
+  result := freeIds.alloc();
+  if (result >= Length(ggItems)) then growItemArrayTo(Integer(result)+64);
   if (Integer(result) > High(ggItems)) then raise Exception.Create('allocItem: freeid list corrupted');
   if (ggItems[result].arrIdx <> Integer(result)) then raise Exception.Create('allocItem: arrIdx inconsistency');
 end;
@@ -380,51 +388,24 @@ end;
 // it will be slow if the slot is free (we have to rebuild the heap)
 function wantItemSlot (slot: Integer): Integer;
 var
-  i, olen: Integer;
+  olen: Integer;
   it: PItem;
-  rebuildFreeList: Boolean = true;
 begin
   if (slot < 0) or (slot > $0fffffff) then raise Exception.Create('wantItemSlot: bad item slot request');
   // do we need to grow item storate?
   olen := Length(ggItems);
-  if (slot >= olen) then
-  begin
-    // need more spice!
-    SetLength(ggItems, slot+64);
-    // add free slots to free list
-    for i := olen to High(ggItems) do
-    begin
-      it := @ggItems[i];
-      it.slotIsUsed := false;
-      it.arrIdx := i;
-      it.ItemType := ITEM_NONE;
-      it.Animation := nil;
-      it.Live := false;
-      it.SpawnTrigger := -1;
-      it.Respawnable := false;
-      if (i <> slot) then freeIds.insert(i);
-    end;
-    rebuildFreeList := false;
-  end;
+  if (slot >= olen) then growItemArrayTo(slot+64);
 
   it := @ggItems[slot];
   if not it.slotIsUsed then
   begin
-    // this is unused slot; get it, and rebuild id list
-    if rebuildFreeList then
-    begin
-      freeIds.clear();
-      for i := 0 to High(ggItems) do
-      begin
-        if (i <> slot) and (not it.slotIsUsed) then freeIds.insert(i);
-      end;
-    end;
+    freeIds.alloc(LongWord(slot));
   end
   else
   begin
-    // it will be readded
-    it.slotIsUsed := false;
+    if not freeIds.hasAlloced[slot] then raise Exception.Create('wantItemSlot: internal error in item idx manager');
   end;
+  it.slotIsUsed := false;
 
   result := slot;
 end;
@@ -473,13 +454,13 @@ begin
 
   it.ItemType := ItemType;
   it.Respawnable := Respawnable;
-  if g_Game_IsServer and (ITEM_RESPAWNTIME = 0) then it.Respawnable := False;
   it.InitX := X;
   it.InitY := Y;
   it.RespawnTime := 0;
   it.Fall := Fall;
-  it.Live := True;
+  it.alive := True;
   it.QuietRespawn := False;
+  it.dropped := false;
 
   g_Obj_Init(@it.Obj);
   it.Obj.X := X;
@@ -506,6 +487,7 @@ begin
   case it.ItemType of
     ITEM_ARMOR_GREEN: if g_Frames_Get(ID, 'FRAMES_ITEM_ARMORGREEN') then it.Animation := TAnimation.Create(ID, True, 20);
     ITEM_ARMOR_BLUE: if g_Frames_Get(ID, 'FRAMES_ITEM_ARMORBLUE') then it.Animation := TAnimation.Create(ID, True, 20);
+    ITEM_JETPACK: if g_Frames_Get(ID, 'FRAMES_ITEM_JETPACK') then it.Animation := TAnimation.Create(ID, True, 15);
     ITEM_SPHERE_BLUE: if g_Frames_Get(ID, 'FRAMES_ITEM_BLUESPHERE') then it.Animation := TAnimation.Create(ID, True, 15);
     ITEM_SPHERE_WHITE: if g_Frames_Get(ID, 'FRAMES_ITEM_WHITESPHERE') then it.Animation := TAnimation.Create(ID, True, 20);
     ITEM_INVUL: if g_Frames_Get(ID, 'FRAMES_ITEM_INVUL') then it.Animation := TAnimation.Create(ID, True, 20);
@@ -527,18 +509,20 @@ var
   Anim: TAnimation;
   m: Word;
   r, nxt: Boolean;
+  actualRespawnable: Boolean;
 begin
   if (ggItems = nil) then exit;
 
   for i := 0 to High(ggItems) do
   begin
     if (ggItems[i].ItemType = ITEM_NONE) then continue;
+    if not ggItems[i].slotIsUsed then continue; // just in case
 
     with ggItems[i] do
     begin
       nxt := False;
 
-      if Live then
+      if alive then
       begin
         if Fall then
         begin
@@ -574,7 +558,7 @@ begin
             Inc(j);
             if j > High(gPlayers) then j := 0;
 
-            if (gPlayers[j] <> nil) and gPlayers[j].Live and g_Obj_Collide(@gPlayers[j].Obj, @Obj) then
+            if (gPlayers[j] <> nil) and gPlayers[j].alive and g_Obj_Collide(@gPlayers[j].Obj, @Obj) then
             begin
               if g_Game_IsClient then continue;
 
@@ -593,7 +577,9 @@ begin
               // Íàäî óáðàòü ñ êàðòû, åñëè ýòî íå êëþ÷, êîòîðûì íóæíî ïîäåëèòüñÿ ñ äðóãèì èãðîêîì
               if r then
               begin
-                if not Respawnable then g_Items_Remove(i) else g_Items_Pick(i);
+                actualRespawnable := Respawnable
+                  and ((ITEM_RESPAWNTIME > 0) and LongBool(gGameSettings.Options and GAME_OPTION_RESPAWNITEMS));
+                if not actualRespawnable then g_Items_Remove(i) else g_Items_Pick(i);
                 if g_Game_IsNet then MH_SEND_ItemDestroy(False, i);
                 nxt := True;
                 break;
@@ -608,7 +594,7 @@ begin
       if Respawnable and g_Game_IsServer then
       begin
         DecMin(RespawnTime, 0);
-        if (RespawnTime = 0) and (not Live) then
+        if (RespawnTime = 0) and (not alive) then
         begin
           if not QuietRespawn then g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', InitX, InitY);
 
@@ -627,7 +613,7 @@ begin
           Obj.Accel.Y := 0;
           positionChanged(); // this updates spatial accelerators
 
-          Live := true;
+          alive := true;
 
           if g_Game_IsNet then MH_SEND_ItemSpawn(QuietRespawn, i);
           QuietRespawn := false;
@@ -640,17 +626,21 @@ begin
 end;
 
 
-procedure g_Items_Draw ();
+procedure itemsDrawInternal (dropflag: Boolean);
 var
   i: Integer;
+  it: PItem;
 begin
   if (ggItems = nil) then exit;
 
   for i := 0 to High(ggItems) do
   begin
-    if not ggItems[i].Live then continue;
+    it := @ggItems[i];
+    if (not it.slotIsUsed) or (it.ItemType = ITEM_NONE) then continue; // just in case
+    if not it.alive then continue;
+    if (it.dropped <> dropflag) then continue;
 
-    with ggItems[i] do
+    with it^ do
     begin
       if g_Collide(Obj.X, Obj.Y, Obj.Rect.Width, Obj.Rect.Height, sX, sY, sWidth, sHeight) then
       begin
@@ -660,7 +650,7 @@ begin
         end
         else
         begin
-          Animation.Draw(Obj.X, Obj.Y, M_NONE);
+          Animation.Draw(Obj.X, Obj.Y, TMirrorType.None);
         end;
 
         if g_debug_Frames then
@@ -677,10 +667,33 @@ begin
 end;
 
 
+procedure g_Items_Draw ();
+begin
+  itemsDrawInternal(false);
+end;
+
+procedure g_Items_DrawDrop ();
+begin
+  itemsDrawInternal(true);
+end;
+
+
+procedure g_Items_SetDrop (ID: DWORD);
+begin
+  if (ID < Length(ggItems)) then
+  begin
+    ggItems[ID].dropped := true;
+  end;
+end;
+
+
 procedure g_Items_Pick (ID: DWORD);
 begin
-  ggItems[ID].Live := false;
-  ggItems[ID].RespawnTime := ITEM_RESPAWNTIME;
+  if (ID < Length(ggItems)) then
+  begin
+    ggItems[ID].alive := false;
+    ggItems[ID].RespawnTime := ITEM_RESPAWNTIME;
+  end;
 end;
 
 
@@ -689,7 +702,12 @@ var
   it: PItem;
   trig: Integer;
 begin
-  if not g_Items_ValidId(ID) then raise Exception.Create('g_Items_Remove: invalid item id');
+  if not g_Items_ValidId(ID) then
+  begin
+    //writeln('g_Items_Remove: invalid item id: ', ID);
+    raise Exception.Create('g_Items_Remove: invalid item id');
+    //exit;
+  end;
 
   it := @ggItems[ID];
   if (it.arrIdx <> Integer(ID)) then raise Exception.Create('g_Items_Remove: arrIdx desync');
@@ -702,93 +720,89 @@ begin
 end;
 
 
-procedure g_Items_SaveState (var Mem: TBinMemoryWriter);
+procedure g_Items_SaveState (st: TStream);
 var
   count, i: Integer;
-  sig: DWORD;
+  tt: Byte;
 begin
   // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ïðåäìåòîâ
   count := 0;
-  if (ggItems <> nil) then
-  begin
-    for i := 0 to High(ggItems) do if (ggItems[i].ItemType <> ITEM_NONE) then Inc(count);
-  end;
-
-  Mem := TBinMemoryWriter.Create((count+1) * 60);
+  for i := 0 to High(ggItems) do if (ggItems[i].ItemType <> ITEM_NONE) and (ggItems[i].slotIsUsed) then Inc(count);
 
   // Êîëè÷åñòâî ïðåäìåòîâ
-  Mem.WriteInt(count);
-
+  utils.writeInt(st, LongInt(count));
   if (count = 0) then exit;
 
   for i := 0 to High(ggItems) do
   begin
-    if (ggItems[i].ItemType <> ITEM_NONE) then
+    if (ggItems[i].ItemType <> ITEM_NONE) and (ggItems[i].slotIsUsed) then
     begin
       // Ñèãíàòóðà ïðåäìåòà
-      sig := ITEM_SIGNATURE; // 'ITEM'
-      Mem.WriteDWORD(sig);
+      utils.writeSign(st, 'ITEM');
+      utils.writeInt(st, Byte(0));
       // Òèï ïðåäìåòà
-      Mem.WriteByte(ggItems[i].ItemType);
+      tt := ggItems[i].ItemType;
+      if ggItems[i].dropped then tt := tt or $80;
+      utils.writeInt(st, Byte(tt));
       // Åñòü ëè ðåñïàóí
-      Mem.WriteBoolean(ggItems[i].Respawnable);
+      utils.writeBool(st, ggItems[i].Respawnable);
       // Êîîðäèíàòû ðåñïóíà
-      Mem.WriteInt(ggItems[i].InitX);
-      Mem.WriteInt(ggItems[i].InitY);
+      utils.writeInt(st, LongInt(ggItems[i].InitX));
+      utils.writeInt(st, LongInt(ggItems[i].InitY));
       // Âðåìÿ äî ðåñïàóíà
-      Mem.WriteWord(ggItems[i].RespawnTime);
+      utils.writeInt(st, Word(ggItems[i].RespawnTime));
       // Ñóùåñòâóåò ëè ýòîò ïðåäìåò
-      Mem.WriteBoolean(ggItems[i].Live);
+      utils.writeBool(st, ggItems[i].alive);
       // Ìîæåò ëè îí ïàäàòü
-      Mem.WriteBoolean(ggItems[i].Fall);
+      utils.writeBool(st, ggItems[i].Fall);
       // Èíäåêñ òðèããåðà, ñîçäàâøåãî ïðåäìåò
-      Mem.WriteInt(ggItems[i].SpawnTrigger);
+      utils.writeInt(st, LongInt(ggItems[i].SpawnTrigger));
       // Îáúåêò ïðåäìåòà
-      Obj_SaveState(@ggItems[i].Obj, Mem);
+      Obj_SaveState(st, @ggItems[i].Obj);
     end;
   end;
 end;
 
 
-procedure g_Items_LoadState (var Mem: TBinMemoryReader);
+procedure g_Items_LoadState (st: TStream);
 var
   count, i, a: Integer;
-  sig: DWORD;
   b: Byte;
 begin
-  if (Mem = nil) then exit;
+  assert(st <> nil);
 
   g_Items_Free();
 
   // Êîëè÷åñòâî ïðåäìåòîâ
-  Mem.ReadInt(count);
-
-  if (count = 0) then Exit;
+  count := utils.readLongInt(st);
+  if (count = 0) then exit;
+  if (count < 0) or (count > 1024*1024) then raise XStreamError.Create('invalid number of items');
 
   for a := 0 to count-1 do
   begin
     // Ñèãíàòóðà ïðåäìåòà
-    Mem.ReadDWORD(sig);
-    if (sig <> ITEM_SIGNATURE) then raise EBinSizeError.Create('g_Items_LoadState: Wrong Item Signature'); // 'ITEM'
+    if not utils.checkSign(st, 'ITEM') then raise XStreamError.Create('invalid item signature');
+    if (utils.readByte(st) <> 0) then raise XStreamError.Create('invalid item version');
     // Òèï ïðåäìåòà
-    Mem.ReadByte(b);
+    b := utils.readByte(st); // bit7=1: monster drop
     // Ñîçäàåì ïðåäìåò
-    i := g_Items_Create(0, 0, b, False, False);
+    i := g_Items_Create(0, 0, b and $7F, False, False);
+    if ((b and $80) <> 0) then g_Items_SetDrop(i);
     // Åñòü ëè ðåñïàóí
-    Mem.ReadBoolean(ggItems[i].Respawnable);
+    ggItems[i].Respawnable := utils.readBool(st);
     // Êîîðäèíàòû ðåñïóíà
-    Mem.ReadInt(ggItems[i].InitX);
-    Mem.ReadInt(ggItems[i].InitY);
+    ggItems[i].InitX := utils.readLongInt(st);
+    ggItems[i].InitY := utils.readLongInt(st);
     // Âðåìÿ äî ðåñïàóíà
-    Mem.ReadWord(ggItems[i].RespawnTime);
+    ggItems[i].RespawnTime := utils.readWord(st);
     // Ñóùåñòâóåò ëè ýòîò ïðåäìåò
-    Mem.ReadBoolean(ggItems[i].Live);
+    ggItems[i].alive := utils.readBool(st);
     // Ìîæåò ëè îí ïàäàòü
-    Mem.ReadBoolean(ggItems[i].Fall);
+    ggItems[i].Fall := utils.readBool(st);
     // Èíäåêñ òðèããåðà, ñîçäàâøåãî ïðåäìåò
-    Mem.ReadInt(ggItems[i].SpawnTrigger);
+    ggItems[i].SpawnTrigger := utils.readLongInt(st);
     // Îáúåêò ïðåäìåòà
-    Obj_LoadState(@ggItems[i].Obj, Mem);
+    Obj_LoadState(@ggItems[i].Obj, st);
   end;
 end;
 
@@ -801,7 +815,8 @@ begin
   for i := 0 to High(ggItems) do
   begin
     it := @ggItems[i];
-    if it.Respawnable then
+    if not it.slotIsUsed then continue;
+    if it.Respawnable and (it.ItemType <> ITEM_NONE) then
     begin
       it.QuietRespawn := True;
       it.RespawnTime := 0;
@@ -826,7 +841,7 @@ begin
   begin
     for idx := High(ggItems) downto 0 do
     begin
-      if ggItems[idx].Live then
+      if ggItems[idx].alive and ggItems[idx].slotIsUsed then
       begin
         result := cb(@ggItems[idx]);
         if result then exit;
@@ -837,7 +852,7 @@ begin
   begin
     for idx := 0 to High(ggItems) do
     begin
-      if ggItems[idx].Live then
+      if ggItems[idx].alive and ggItems[idx].slotIsUsed then
       begin
         result := cb(@ggItems[idx]);
         if result then exit;
@@ -903,4 +918,32 @@ begin
   end;
 end;
 
+
+procedure g_Items_AddDynLights();
+var
+  f: Integer;
+  it: PItem;
+begin
+  for f := 0 to High(ggItems) do
+  begin
+    it := @ggItems[f];
+    if not it.alive then continue;
+    case it.ItemType of
+      ITEM_KEY_RED: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 24,  1.0, 0.0, 0.0, 0.6);
+      ITEM_KEY_GREEN: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 24,  0.0, 1.0, 0.0, 0.6);
+      ITEM_KEY_BLUE: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 24,  0.0, 0.0, 1.0, 0.6);
+      ITEM_ARMOR_GREEN: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 42,  0.0, 1.0, 0.0, 0.6);
+      ITEM_ARMOR_BLUE: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 42,  0.0, 0.0, 1.0, 0.6);
+      ITEM_JETPACK: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 32,  1.0, 1.0, 1.0, 0.6);
+      ITEM_SPHERE_BLUE: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 32,  0.0, 1.0, 0.0, 0.6);
+      ITEM_SPHERE_WHITE: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 32,  1.0, 1.0, 1.0, 0.6);
+      ITEM_INVUL: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 32,  1.0, 0.0, 0.0, 0.6);
+      ITEM_INVIS: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 32,  1.0, 1.0, 0.0, 0.6);
+      ITEM_BOTTLE: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 16,  0.0, 0.0, 0.8, 0.6);
+      ITEM_HELMET: g_AddDynLight(it.Obj.X+(it.Obj.Rect.Width div 2), it.Obj.Y+(it.Obj.Rect.Height div 2), 16,  0.0, 0.8, 0.0, 0.6);
+    end;
+  end;
+end;
+
+
 end.