DEADSOFTWARE

no more global `gItems[]` array; created DynTree for items (not used yet); also,...
authorKetmar Dark <ketmar@ketmar.no-ip.org>
Sat, 19 Aug 2017 19:16:23 +0000 (22:16 +0300)
committerKetmar Dark <ketmar@ketmar.no-ip.org>
Sat, 19 Aug 2017 19:17:13 +0000 (22:17 +0300)
src/game/g_basic.pas
src/game/g_game.pas
src/game/g_items.pas
src/game/g_map.pas
src/game/g_monsters.pas
src/game/g_netmsg.pas
src/game/g_player.pas
src/game/g_triggers.pas
src/game/z_aabbtree.pas
src/shared/binheap.pas

index 2c540aad0a617fc7c2c51f3bc8060e7e4d057861..8430c9f98eecc88eba2aceba3096c4078e3f1704 100644 (file)
@@ -48,7 +48,6 @@ function g_CollideAround(X1, Y1: Integer; Width1, Height1: Word;
                          X2, Y2: Integer; Width2, Height2: Word): Boolean;
 function g_CollidePlayer(X, Y: Integer; Width, Height: Word): Boolean;
 function g_CollideMonster(X, Y: Integer; Width, Height: Word): Boolean;
-function g_CollideItem(X, Y: Integer; Width, Height: Word): Boolean;
 function g_PatchLength(X1, Y1, X2, Y2: Integer): Word;
 function g_TraceVector(X1, Y1, X2, Y2: Integer): Boolean;
 function g_GetAcidHit(X, Y: Integer; Width, Height: Word): Byte;
@@ -161,24 +160,6 @@ begin
       end;
 end;
 
-function g_CollideItem(X, Y: Integer; Width, Height: Word): Boolean;
-var
-  a: Integer;
-begin
-  Result := False;
-
-  if gItems = nil then
-    Exit;
-
-  for a := 0 to High(gItems) do
-    if gItems[a].Live then
-      if g_Obj_Collide(X, Y, Width, Height, @gItems[a].Obj) then
-        begin
-          Result := True;
-          Exit;
-        end;
-end;
-
 function g_TraceVector(X1, Y1, X2, Y2: Integer): Boolean;
 var
   i: Integer;
index d4af70f7fbf8376458c98d9a69a220ede0ced087..497567b23ccf7e36e3e8e5f011c411211d65502c 100644 (file)
@@ -4241,19 +4241,7 @@ begin
       gPlayer2 := g_Player_Get(gLMSPID2);
   end;
 
-  for i := Low(gItems) to High(gItems) do
-  begin
-    if gItems[i].Respawnable then
-    begin
-      gItems[i].QuietRespawn := True;
-      gItems[i].RespawnTime := 0;
-    end
-    else
-    begin
-      g_Items_Remove(i);
-      if g_Game_IsNet then MH_SEND_ItemDestroy(True, i);
-    end;
-  end;
+  g_Items_RestartRound();
 
   for i := Low(gMonsters) to High(gMonsters) do
   begin
index e35fd3399dd7e8eb2dd83773b7ffbab1ccee11b1..6d7cf5a10a7918ea8bc0480160d33c9769c0099e 100644 (file)
@@ -22,7 +22,13 @@ uses
   g_textures, g_phys, g_saveload, BinEditor, MAPDEF;
 
 Type
+  PItem = ^TItem;
   TItem = record
+  private
+    treeNode: Integer;
+    arrIdx: Integer; // in ggItems
+
+  public
     ItemType:      Byte;
     Respawnable:   Boolean;
     InitX, InitY:  Integer;
@@ -35,6 +41,8 @@ Type
     Animation:     TAnimation;
 
     procedure positionChanged (); //WARNING! call this after monster position was changed, or coldet will not work right!
+
+    property myid: Integer read arrIdx;
   end;
 
 procedure g_Items_LoadData();
@@ -50,8 +58,23 @@ procedure g_Items_Remove(ID: DWORD);
 procedure g_Items_SaveState(var Mem: TBinMemoryWriter);
 procedure g_Items_LoadState(var Mem: TBinMemoryReader);
 
+procedure g_Items_RestartRound ();
+
+function g_ItemValidId (idx: Integer): Boolean; inline;
+function g_ItemByIdx (idx: Integer): PItem;
+function g_ItemObjByIdx (idx: Integer): PObj;
+
+procedure g_Item_EmitPickupSound (idx: Integer); // at item position
+procedure g_Item_EmitPickupSoundAt (idx, x, y: Integer);
+
+
+type
+  TItemEachAliveCB = function (it: PItem): Boolean is nested; // return `true` to stop
+
+function g_Items_ForEachAlive (cb: TItemEachAliveCB; backwards: Boolean=false): Boolean;
+
+
 var
-  gItems: Array of TItem = nil;
   gItemsTexturesID: Array [1..ITEM_MAX] of DWORD;
   gMaxDist: Integer = 1;
   ITEM_RESPAWNTIME: Integer = 60 * 36;
@@ -61,8 +84,96 @@ implementation
 uses
   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,
-  e_log;
+  e_log,
+  g_grid, z_aabbtree, binheap;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+var
+  itemTree: TDynAABBTree = nil;
+  ggItems: Array of TItem = nil;
+  freeIds: TBinaryHeapInt = nil; // free item ids
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+function g_ItemValidId (idx: Integer): Boolean; inline;
+begin
+  result := false;
+  if (idx < 0) or (idx > High(ggItems)) then exit;
+  if (ggItems[idx].treeNode = -1) then exit;
+  result := true;
+end;
+
+
+function g_ItemByIdx (idx: Integer): PItem;
+begin
+  if (idx < 0) or (idx > High(ggItems)) then raise Exception.Create('g_ItemObjByIdx: invalid index');
+  result := @ggItems[idx];
+  if (result.treeNode = -1) then raise Exception.Create('g_ItemObjByIdx: requested inexistent item');
+end;
+
 
+function g_ItemObjByIdx (idx: Integer): PObj;
+begin
+  if (idx < 0) or (idx > High(ggItems)) then raise Exception.Create('g_ItemObjByIdx: invalid index');
+  if (ggItems[idx].treeNode = -1) then raise Exception.Create('g_ItemObjByIdx: requested inexistent item');
+  result := @ggItems[idx].Obj;
+end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+type
+  TDynAABBTreeItem = class(TDynAABBTree)
+    function getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh; tag: Integer): Boolean; override;
+  end;
+
+function TDynAABBTreeItem.getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh; tag: Integer): Boolean;
+var
+  it: PItem;
+begin
+  result := false;
+  if (flesh = nil) then begin aabb := AABB2D.Create(0, 0, 0, 0); exit; end;
+  //if not g_ItemValidId(tag) then raise Exception.Create('DynTree: trying to get dimensions of inexistant item');
+  if (tag < 0) or (tag > High(ggItems)) then raise Exception.Create('DynTree: trying to get dimensions of inexistant item');
+  it := @ggItems[tag];
+  if (it.Obj.Rect.Width < 1) or (it.Obj.Rect.Height < 1) then exit;
+  aabb := AABB2D.Create(it.Obj.X, it.Obj.Y, it.Obj.X+it.Obj.Rect.Width, it.Obj.Y+it.Obj.Rect.Height);
+  if not aabb.valid then raise Exception.Create('wutafuuuuuuu?!');
+  result := true;
+end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+procedure TItem.positionChanged ();
+var
+  x, y: Integer;
+begin
+  if (treeNode = -1) then
+  begin
+    treeNode := itemTree.insertObject(itemTree{doesn't matter}, arrIdx, true); // static object
+    itemTree.getNodeXY(treeNode, x, y);
+    {$IF DEFINED(D2F_DEBUG)}e_WriteLog(Format('item #%d: inserted into the tree; nodeid=%d; x=%d; y=%d', [arrIdx, treeNode, x, y]), MSG_NOTIFY);{$ENDIF}
+  end
+  else
+  begin
+    itemTree.getNodeXY(treeNode, x, y);
+    if (Obj.X = x) and (Obj.Y = y) then exit; // nothing to do
+    {$IF DEFINED(D2F_DEBUG)}e_WriteLog(Format('item #%d: updating tree; nodeid=%d; x=%d; y=%d', [arrIdx, treeNode, x, y]), MSG_NOTIFY);{$ENDIF}
+
+    {$IFDEF TRUE}
+    itemTree.updateObject(treeNode);
+    {$ELSE}
+    itemTree.removeObject(treeNode);
+    treeNode := itemTree.insertObject(itemTree{doesn't matter}, arrIdx, true); // static object
+    {$ENDIF}
+
+    itemTree.getNodeXY(treeNode, x, y);
+    {$IF DEFINED(D2F_DEBUG)}e_WriteLog(Format('item #%d: updated tree; nodeid=%d; x=%d; y=%d', [arrIdx, treeNode, x, y]), MSG_NOTIFY);{$ENDIF}
+  end;
+end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
 const
   ITEM_SIGNATURE = $4D455449; // 'ITEM'
 
@@ -194,8 +305,12 @@ begin
   g_Texture_CreateWADEx('ITEM_JETPACK', GameWAD+':TEXTURES\JETPACK');
 
   InitTextures();
+
+  itemTree := TDynAABBTreeItem.Create();
+  freeIds := binHeapNewIntLess();
 end;
 
+
 procedure g_Items_FreeData();
 begin
   e_WriteLog('Releasing items data...', MSG_NOTIFY);
@@ -248,443 +363,597 @@ begin
   g_Texture_Delete('ITEM_WEAPON_KASTET');
   g_Texture_Delete('ITEM_MEDKIT_BLACK');
   g_Texture_Delete('ITEM_JETPACK');
+
+  itemTree.Free();
+  freeIds.Free();
 end;
 
-function FindItem(): DWORD;
+
+procedure releaseItem (idx: Integer);
 var
-  i: Integer;
+  it: PItem;
 begin
-  if gItems <> nil then
-    for i := 0 to High(gItems) do
-      if gItems[i].ItemType = ITEM_NONE then
-      begin
-        Result := i;
-        Exit;
-      end;
+  if (idx < 0) or (idx > High(ggItems)) then raise Exception.Create('releaseItem: invalid item id');
+  it := @ggItems[idx];
+  if (it.treeNode = -1) then raise Exception.Create('releaseItem: trying to release unallocated item');
+  if (it.arrIdx <> idx) then raise Exception.Create('releaseItem: arrIdx inconsistency');
+  itemTree.removeObject(it.treeNode);
+  it.treeNode := -1;
+  if (it.Animation <> nil) then
+  begin
+    it.Animation.Free();
+    it.Animation := nil;
+  end;
+  it.Live := False;
+  it.SpawnTrigger := -1;
+  it.ItemType := ITEM_NONE;
+  freeIds.insert(it.arrIdx);
+end;
 
-  if gItems = nil then
-    begin
-      SetLength(gItems, 32);
-      Result := 0;
-    end
-  else
+
+function allocItem (): DWORD;
+var
+  i, olen: Integer;
+  it: PItem;
+begin
+  if (freeIds.count = 0) then
+  begin
+    // no free slots
+    olen := Length(ggItems);
+    SetLength(ggItems, olen+64);
+    for i := olen to High(ggItems) do
     begin
-      Result := High(gItems) + 1;
-      SetLength(gItems, Length(gItems) + 32);
+      it := @ggItems[i];
+      it.treeNode := -1;
+      it.arrIdx := i;
+      it.ItemType := ITEM_NONE;
+      it.Animation := nil;
+      it.Live := false;
+      it.SpawnTrigger := -1;
+      it.Respawnable := false;
+      freeIds.insert(i);
     end;
+  end;
+
+  result := freeIds.front;
+  freeIds.popFront();
+
+  if (result > High(ggItems)) then raise Exception.Create('allocItem: freeid list corrupted');
+  if (ggItems[result].arrIdx <> result) then raise Exception.Create('allocItem: arrIdx inconsistency');
 end;
 
-procedure g_Items_Init();
+
+// it will be slow if the slot is free (we have to rebuild the heap)
+function wantItemSlot (slot: Integer): Integer;
 var
-  a, b: Integer;
+  i, olen: Integer;
+  it: PItem;
+  rebuildFreeList: Boolean = true;
 begin
-  if gMapInfo.Height > gPlayerScreenSize.Y then
-    a := gMapInfo.Height - gPlayerScreenSize.Y
-  else
-    a := gMapInfo.Height;
+  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.treeNode := -1;
+      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 gMapInfo.Width > gPlayerScreenSize.X then
-    b := gMapInfo.Width - gPlayerScreenSize.X
+  it := @ggItems[slot];
+  if (it.treeNode = -1) 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 (ggItems[i].treeNode = -1) then freeIds.insert(i);
+      end;
+    end;
+  end
   else
-    b := gMapInfo.Width;
+  begin
+    // it will be readded
+    itemTree.removeObject(it.treeNode);
+    it.treeNode := -1;
+  end;
+
+  result := slot;
+end;
+
 
+// ////////////////////////////////////////////////////////////////////////// //
+procedure g_Items_Init ();
+var
+  a, b: Integer;
+begin
+  if gMapInfo.Height > gPlayerScreenSize.Y then a := gMapInfo.Height-gPlayerScreenSize.Y else a := gMapInfo.Height;
+  if gMapInfo.Width > gPlayerScreenSize.X then b := gMapInfo.Width-gPlayerScreenSize.X else b := gMapInfo.Width;
   gMaxDist := Trunc(Hypot(a, b));
 end;
 
-procedure g_Items_Free();
+
+procedure g_Items_Free ();
 var
   i: Integer;
 begin
-  if gItems <> nil then
+  if (ggItems <> nil) then
   begin
-    for i := 0 to High(gItems) do
-      gItems[i].Animation.Free();
-    gItems := nil;
+    for i := 0 to High(ggItems) do ggItems[i].Animation.Free();
+    ggItems := nil;
   end;
+  if (itemTree <> nil) then itemTree.reset();
+  freeIds.clear();
 end;
 
-function g_Items_Create(X, Y: Integer; ItemType: Byte;
+
+function g_Items_Create (X, Y: Integer; ItemType: Byte;
            Fall, Respawnable: Boolean; AdjCoord: Boolean = False; ForcedID: Integer = -1): DWORD;
 var
   find_id: DWORD;
   ID: DWORD;
+  it: PItem;
 begin
-  if ForcedID < 0 then
-    find_id := FindItem()
-  else
-  begin
-    find_id := ForcedID;
-    if Integer(find_id) > High(gItems) then
-      SetLength(gItems, find_id + 32);
-  end;
+  if ForcedID < 0 then find_id := allocItem() else find_id := wantItemSlot(ForcedID);
+
+  {$IF DEFINED(D2F_DEBUG)}e_WriteLog(Format('allocated item #%d', [Integer(find_id)]), MSG_NOTIFY);{$ENDIF}
+
+  it := @ggItems[find_id];
+
+  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.QuietRespawn := False;
 
-  gItems[find_id].ItemType := ItemType;
-  gItems[find_id].Respawnable := Respawnable;
-  if g_Game_IsServer and (ITEM_RESPAWNTIME = 0) then
-    gItems[find_id].Respawnable := False;
-  gItems[find_id].InitX := X;
-  gItems[find_id].InitY := Y;
-  gItems[find_id].RespawnTime := 0;
-  gItems[find_id].Fall := Fall;
-  gItems[find_id].Live := True;
-  gItems[find_id].QuietRespawn := False;
-
-  g_Obj_Init(@gItems[find_id].Obj);
-  gItems[find_id].Obj.X := X;
-  gItems[find_id].Obj.Y := Y;
-  gItems[find_id].Obj.Rect.Width := ITEMSIZE[ItemType][0];
-  gItems[find_id].Obj.Rect.Height := ITEMSIZE[ItemType][1];
-
-  gItems[find_id].Animation := nil;
-  gItems[find_id].SpawnTrigger := -1;
-
-// Êîîðäèíàòû îòíîñèòåëüíî öåíòðà íèæíåãî ðåáðà:
+  if (it.treeNode <> -1) then raise Exception.Create('g_Items_Create: trying to reuse already allocated item');
+  if (it.arrIdx <> find_id) then raise Exception.Create('g_Items_Create: arrIdx inconsistency');
+  //it.treeNode := -1;
+  //it.arrIdx := find_id;
+
+  g_Obj_Init(@it.Obj);
+  it.Obj.X := X;
+  it.Obj.Y := Y;
+  it.Obj.Rect.Width := ITEMSIZE[ItemType][0];
+  it.Obj.Rect.Height := ITEMSIZE[ItemType][1];
+
+  it.Animation := nil;
+  it.SpawnTrigger := -1;
+
+  // Êîîðäèíàòû îòíîñèòåëüíî öåíòðà íèæíåãî ðåáðà
   if AdjCoord then
-    with gItems[find_id] do
+  begin
+    with it^ do
     begin
       Obj.X := X - (Obj.Rect.Width div 2);
       Obj.Y := Y - Obj.Rect.Height;
       InitX := Obj.X;
       InitY := Obj.Y;
     end;
+  end;
 
-// Óñòàíîâêà àíèìàöèè:
-  with gItems[find_id] do
-  begin
-    case ItemType of
-      ITEM_ARMOR_GREEN:
-        if g_Frames_Get(ID, 'FRAMES_ITEM_ARMORGREEN') then
-          Animation := TAnimation.Create(ID, True, 20);
-      ITEM_ARMOR_BLUE:
-        if g_Frames_Get(ID, 'FRAMES_ITEM_ARMORBLUE') then
-          Animation := TAnimation.Create(ID, True, 20);
-      ITEM_SPHERE_BLUE:
-        if g_Frames_Get(ID, 'FRAMES_ITEM_BLUESPHERE') then
-          Animation := TAnimation.Create(ID, True, 15);
-      ITEM_SPHERE_WHITE:
-        if g_Frames_Get(ID, 'FRAMES_ITEM_WHITESPHERE') then
-          Animation := TAnimation.Create(ID, True, 20);
-      ITEM_INVUL:
-        if g_Frames_Get(ID, 'FRAMES_ITEM_INVUL') then
-          Animation := TAnimation.Create(ID, True, 20);
-      ITEM_INVIS:
-        if g_Frames_Get(ID, 'FRAMES_ITEM_INVIS') then
-          Animation := TAnimation.Create(ID, True, 20);
-      ITEM_BOTTLE:
-        if g_Frames_Get(ID, 'FRAMES_ITEM_BOTTLE') then
-          Animation := TAnimation.Create(ID, True, 20);
-      ITEM_HELMET:
-        if g_Frames_Get(ID, 'FRAMES_ITEM_HELMET') then
-          Animation := TAnimation.Create(ID, True, 20);
-    end;
+  // Óñòàíîâêà àíèìàöèè
+  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_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);
+    ITEM_INVIS: if g_Frames_Get(ID, 'FRAMES_ITEM_INVIS') then it.Animation := TAnimation.Create(ID, True, 20);
+    ITEM_BOTTLE: if g_Frames_Get(ID, 'FRAMES_ITEM_BOTTLE') then it.Animation := TAnimation.Create(ID, True, 20);
+    ITEM_HELMET: if g_Frames_Get(ID, 'FRAMES_ITEM_HELMET') then it.Animation := TAnimation.Create(ID, True, 20);
   end;
 
-  Result := find_id;
+  it.positionChanged();
+
+  result := find_id;
 end;
 
-procedure g_Items_Update();
+
+procedure g_Items_Update ();
 var
   i, j, k: Integer;
-  ID: DWORD;
+  ID: DWord;
   Anim: TAnimation;
   m: Word;
   r, nxt: Boolean;
 begin
-  if gItems <> nil then
-    for i := 0 to High(gItems) do
-      if gItems[i].ItemType <> ITEM_NONE then
-        with gItems[i] do
+  if (ggItems = nil) then exit;
+
+  for i := 0 to High(ggItems) do
+  begin
+    if (ggItems[i].ItemType = ITEM_NONE) then continue;
+
+    with ggItems[i] do
+    begin
+      nxt := False;
+
+      if Live then
+      begin
+        if Fall then
         begin
-          nxt := False;
+          m := g_Obj_Move(@Obj, True, True);
+          positionChanged(); // this updates spatial accelerators
+
+          // Ñîïðîòèâëåíèå âîçäóõà
+          if gTime mod (GAME_TICK*2) = 0 then Obj.Vel.X := z_dec(Obj.Vel.X, 1);
 
-          if Live then
+          // Åñëè âûïàë çà êàðòó
+          if WordBool(m and MOVE_FALLOUT) then
           begin
-            if Fall then
+            if SpawnTrigger = -1 then
             begin
-              m := g_Obj_Move(@Obj, True, True);
-              positionChanged(); // this updates spatial accelerators
-
-            // Ñîïðîòèâëåíèå âîçäóõà:
-              if gTime mod (GAME_TICK*2) = 0 then
-                Obj.Vel.X := z_dec(Obj.Vel.X, 1);
-            // Åñëè âûïàë çà êàðòó:
-              if WordBool(m and MOVE_FALLOUT) then
-              begin
-                if SpawnTrigger = -1 then
-                  g_Items_Pick(i)
-                else begin
-                  g_Items_Remove(i);
-                  if g_Game_IsServer and g_Game_IsNet then MH_SEND_ItemDestroy(True, i);
-                end;
-                Continue;
-              end;
-            end;
-
-          // Åñëè èãðîêè ïîáëèçîñòè:
-            if gPlayers <> nil then
+              g_Items_Pick(i);
+            end
+            else
             begin
-              j := Random(Length(gPlayers)) - 1;
-
-              for k := 0 to High(gPlayers) do
-              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
-                begin
-                  if g_Game_IsClient then Continue;
-
-                  if not gPlayers[j].PickItem(ItemType, Respawnable, r) then
-                    Continue;
-
-                  if g_Game_IsNet then MH_SEND_PlayerStats(gPlayers[j].UID);
-
-{
-  Doom 2D: Original:
-  1. I_NONE,I_CLIP,I_SHEL,I_ROCKET,I_CELL,I_AMMO,I_SBOX,I_RBOX,I_CELP,I_BPACK,I_CSAW,I_SGUN,I_SGUN2,I_MGUN,I_LAUN,I_PLAS,I_BFG,I_GUN2
-  +2. I_MEGA,I_INVL,I_SUPER
-  3. I_STIM,I_MEDI,I_ARM1,I_ARM2,I_AQUA,I_KEYR,I_KEYG,I_KEYB,I_SUIT,I_RTORCH,I_GTORCH,I_BTORCH,I_GOR1,I_FCAN
-}
-
-                  if gSoundEffectsDF then
-                  begin
-                    if ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL,
-                                    ITEM_INVIS, ITEM_MEDKIT_BLACK, ITEM_JETPACK] then
-                      g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ',
-                        gPlayers[j].Obj.X, gPlayers[j].Obj.Y)
-                    else
-                      if ItemType in [ITEM_WEAPON_SAW, ITEM_WEAPON_PISTOL, ITEM_WEAPON_SHOTGUN1, ITEM_WEAPON_SHOTGUN2,
-                                      ITEM_WEAPON_CHAINGUN, ITEM_WEAPON_ROCKETLAUNCHER, ITEM_WEAPON_PLASMA,
-                                      ITEM_WEAPON_BFG, ITEM_WEAPON_SUPERPULEMET, ITEM_WEAPON_FLAMETHROWER,
-                                      ITEM_AMMO_BACKPACK] then
-                        g_Sound_PlayExAt('SOUND_ITEM_GETWEAPON',
-                          gPlayers[j].Obj.X, gPlayers[j].Obj.Y)
-                      else
-                        g_Sound_PlayExAt('SOUND_ITEM_GETITEM',
-                          gPlayers[j].Obj.X, gPlayers[j].Obj.Y);
-                  end
-                  else
-                  begin
-                    if ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_SUIT,
-                                    ITEM_MEDKIT_BLACK, ITEM_INVUL, ITEM_INVIS, ITEM_JETPACK] then
-                      g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ',
-                        gPlayers[j].Obj.X, gPlayers[j].Obj.Y)
-                    else
-                      if ItemType in [ITEM_WEAPON_SAW, ITEM_WEAPON_PISTOL, ITEM_WEAPON_SHOTGUN1, ITEM_WEAPON_SHOTGUN2,
-                                      ITEM_WEAPON_CHAINGUN, ITEM_WEAPON_ROCKETLAUNCHER, ITEM_WEAPON_PLASMA,
-                                      ITEM_WEAPON_BFG, ITEM_WEAPON_SUPERPULEMET, ITEM_WEAPON_FLAMETHROWER] then
-                        g_Sound_PlayExAt('SOUND_ITEM_GETWEAPON',
-                          gPlayers[j].Obj.X, gPlayers[j].Obj.Y)
-                      else
-                        g_Sound_PlayExAt('SOUND_ITEM_GETITEM',
-                          gPlayers[j].Obj.X, gPlayers[j].Obj.Y);
-                  end;
-
-                // Íàäî óáðàòü ñ êàðòû, åñëè ýòî íå êëþ÷, êîòîðûì íóæíî ïîäåëèòüñÿ ñ äðóãèì èãðîêîì:
-                  if r then
-                  begin
-                    if not Respawnable then
-                      g_Items_Remove(i)
-                    else
-                      g_Items_Pick(i);
-
-                    if g_Game_IsNet then MH_SEND_ItemDestroy(False, i);
-                    nxt := True;
-                    Break;
-                  end;
-                end;
-              end;
+              g_Items_Remove(i);
+              if g_Game_IsServer and g_Game_IsNet then MH_SEND_ItemDestroy(True, i);
             end;
-
-            if nxt then
-              Continue;
+            continue;
           end;
+        end;
+
+        // Åñëè èãðîêè ïîáëèçîñòè
+        if (gPlayers <> nil) then
+        begin
+          j := Random(Length(gPlayers))-1;
 
-          if Respawnable and g_Game_IsServer then
+          for k := 0 to High(gPlayers) do
           begin
-            DecMin(RespawnTime, 0);
-            if (RespawnTime = 0) and (not Live) then
+            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
             begin
-              if not QuietRespawn then
-                g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', InitX, InitY);
+              if g_Game_IsClient then continue;
+
+              if not gPlayers[j].PickItem(ItemType, Respawnable, r) then continue;
+
+              if g_Game_IsNet then MH_SEND_PlayerStats(gPlayers[j].UID);
 
-              if g_Frames_Get(ID, 'FRAMES_ITEM_RESPAWN') then
+              {
+                Doom 2D: Original:
+                1. I_NONE,I_CLIP,I_SHEL,I_ROCKET,I_CELL,I_AMMO,I_SBOX,I_RBOX,I_CELP,I_BPACK,I_CSAW,I_SGUN,I_SGUN2,I_MGUN,I_LAUN,I_PLAS,I_BFG,I_GUN2
+                +2. I_MEGA,I_INVL,I_SUPER
+                3. I_STIM,I_MEDI,I_ARM1,I_ARM2,I_AQUA,I_KEYR,I_KEYG,I_KEYB,I_SUIT,I_RTORCH,I_GTORCH,I_BTORCH,I_GOR1,I_FCAN
+              }
+              g_Item_EmitPickupSoundAt(i, gPlayers[j].Obj.X, gPlayers[j].Obj.Y);
+
+              // Íàäî óáðàòü ñ êàðòû, åñëè ýòî íå êëþ÷, êîòîðûì íóæíî ïîäåëèòüñÿ ñ äðóãèì èãðîêîì
+              if r then
               begin
-                Anim := TAnimation.Create(ID, False, 4);
-                g_GFX_OnceAnim(InitX+(Obj.Rect.Width div 2)-16, InitY+(Obj.Rect.Height div 2)-16, Anim);
-                Anim.Free();
+                if not Respawnable then g_Items_Remove(i) else g_Items_Pick(i);
+                if g_Game_IsNet then MH_SEND_ItemDestroy(False, i);
+                nxt := True;
+                break;
               end;
+            end;
+          end;
+        end;
 
-              Obj.X := InitX;
-              Obj.Y := InitY;
-              Obj.Vel.X := 0;
-              Obj.Vel.Y := 0;
-              Obj.Accel.X := 0;
-              Obj.Accel.Y := 0;
-              positionChanged(); // this updates spatial accelerators
+        if nxt then continue;
+      end;
 
-              Live := True;
+      if Respawnable and g_Game_IsServer then
+      begin
+        DecMin(RespawnTime, 0);
+        if (RespawnTime = 0) and (not Live) then
+        begin
+          if not QuietRespawn then g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', InitX, InitY);
 
-              if g_Game_IsNet then MH_SEND_ItemSpawn(QuietRespawn, i);
-              QuietRespawn := False;
-            end;
+          if g_Frames_Get(ID, 'FRAMES_ITEM_RESPAWN') then
+          begin
+            Anim := TAnimation.Create(ID, False, 4);
+            g_GFX_OnceAnim(InitX+(Obj.Rect.Width div 2)-16, InitY+(Obj.Rect.Height div 2)-16, Anim);
+            Anim.Free();
           end;
 
-          if Animation <> nil then
-            Animation.Update();
+          Obj.X := InitX;
+          Obj.Y := InitY;
+          Obj.Vel.X := 0;
+          Obj.Vel.Y := 0;
+          Obj.Accel.X := 0;
+          Obj.Accel.Y := 0;
+          positionChanged(); // this updates spatial accelerators
+
+          Live := true;
+
+          if g_Game_IsNet then MH_SEND_ItemSpawn(QuietRespawn, i);
+          QuietRespawn := false;
         end;
+      end;
+
+      if (Animation <> nil) then Animation.Update();
+    end;
+  end;
 end;
 
-procedure g_Items_Draw();
+
+procedure g_Items_Draw ();
 var
   i: Integer;
 begin
-  if gItems <> nil then
-    for i := 0 to High(gItems) do
-      if gItems[i].Live then
-        with gItems[i] do
-          if g_Collide(Obj.X, Obj.Y, Obj.Rect.Width, Obj.Rect.Height,
-                       sX, sY, sWidth, sHeight) then
-          begin
-            if Animation = nil then
-              e_Draw(gItemsTexturesID[ItemType], Obj.X, Obj.Y, 0, True, False)
-            else
-              Animation.Draw(Obj.X, Obj.Y, M_NONE);
+  if (ggItems = nil) then exit;
 
-            if g_debug_Frames then
-            begin
-              e_DrawQuad(Obj.X+Obj.Rect.X,
-                         Obj.Y+Obj.Rect.Y,
-                         Obj.X+Obj.Rect.X+Obj.Rect.Width-1,
-                         Obj.Y+Obj.Rect.Y+Obj.Rect.Height-1,
-                         0, 255, 0);
-            end;
-          end;
+  for i := 0 to High(ggItems) do
+  begin
+    if not ggItems[i].Live then continue;
+
+    with ggItems[i] do
+    begin
+      if g_Collide(Obj.X, Obj.Y, Obj.Rect.Width, Obj.Rect.Height, sX, sY, sWidth, sHeight) then
+      begin
+        if (Animation = nil) then
+        begin
+          e_Draw(gItemsTexturesID[ItemType], Obj.X, Obj.Y, 0, true, false);
+        end
+        else
+        begin
+          Animation.Draw(Obj.X, Obj.Y, M_NONE);
+        end;
+
+        if g_debug_Frames then
+        begin
+          e_DrawQuad(Obj.X+Obj.Rect.X,
+                     Obj.Y+Obj.Rect.Y,
+                     Obj.X+Obj.Rect.X+Obj.Rect.Width-1,
+                     Obj.Y+Obj.Rect.Y+Obj.Rect.Height-1,
+                     0, 255, 0);
+        end;
+      end;
+    end;
+  end;
 end;
 
-procedure g_Items_Pick(ID: DWORD);
+
+procedure g_Items_Pick (ID: DWORD);
 begin
-  gItems[ID].Live := False;
-  gItems[ID].RespawnTime := ITEM_RESPAWNTIME;
+  ggItems[ID].Live := false;
+  ggItems[ID].RespawnTime := ITEM_RESPAWNTIME;
 end;
 
-procedure g_Items_Remove(ID: DWORD);
+
+procedure g_Items_Remove (ID: DWORD);
+var
+  it: PItem;
+  trig: Integer;
+  x, y: Integer;
 begin
-  gItems[ID].ItemType := ITEM_NONE;
+  if not g_ItemValidId(ID) then raise Exception.Create('g_Items_Remove: invalid item id');
 
-  if gItems[ID].Animation <> nil then
-  begin
-    gItems[ID].Animation.Free();
-    gItems[ID].Animation := nil;
-  end;
+  it := @ggItems[ID];
+  if (it.arrIdx <> ID) then raise Exception.Create('g_Items_Remove: arrIdx desync');
 
-  gItems[ID].Live := False;
+  itemTree.getNodeXY(it.treeNode, x, y);
+  {$IF DEFINED(D2F_DEBUG)}e_WriteLog(Format('removing item #%d: updating tree; nodeid=%d; x=%d; y=%d (%d,%d)', [it.arrIdx, it.treeNode, x, y, it.Obj.X, it.Obj.Y]), MSG_NOTIFY);{$ENDIF}
 
-  if gItems[ID].SpawnTrigger > -1 then
-  begin
-    g_Triggers_DecreaseSpawner(gItems[ID].SpawnTrigger);
-    gItems[ID].SpawnTrigger := -1;
-  end;
+  trig := it.SpawnTrigger;
+
+  releaseItem(ID);
+
+  if (trig > -1) then g_Triggers_DecreaseSpawner(trig);
 end;
 
-procedure g_Items_SaveState(var Mem: TBinMemoryWriter);
+
+procedure g_Items_SaveState (var Mem: TBinMemoryWriter);
 var
   count, i: Integer;
   sig: DWORD;
 begin
-// Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ïðåäìåòîâ:
+  // Ñ÷èòàåì êîëè÷åñòâî ñóùåñòâóþùèõ ïðåäìåòîâ
   count := 0;
-  if gItems <> nil then
-    for i := 0 to High(gItems) do
-      if gItems[i].ItemType <> ITEM_NONE then
-        count := count + 1;
+  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);
 
-// Êîëè÷åñòâî ïðåäìåòîâ:
+  // Êîëè÷åñòâî ïðåäìåòîâ
   Mem.WriteInt(count);
 
-  if count = 0 then
-    Exit;
+  if (count = 0) then exit;
 
-  for i := 0 to High(gItems) do
-    if gItems[i].ItemType <> ITEM_NONE then
+  for i := 0 to High(ggItems) do
+  begin
+    if (ggItems[i].ItemType <> ITEM_NONE) then
     begin
-    // Ñèãíàòóðà ïðåäìåòà:
+      // Ñèãíàòóðà ïðåäìåòà
       sig := ITEM_SIGNATURE; // 'ITEM'
       Mem.WriteDWORD(sig);
-    // Òèï ïðåäìåòà:
-      Mem.WriteByte(gItems[i].ItemType);
-    // Åñòü ëè ðåñïàóí:
-      Mem.WriteBoolean(gItems[i].Respawnable);
-    // Êîîðäèíàòû ðåñïóíà:
-      Mem.WriteInt(gItems[i].InitX);
-      Mem.WriteInt(gItems[i].InitY);
-    // Âðåìÿ äî ðåñïàóíà:
-      Mem.WriteWord(gItems[i].RespawnTime);
-    // Ñóùåñòâóåò ëè ýòîò ïðåäìåò:
-      Mem.WriteBoolean(gItems[i].Live);
-    // Ìîæåò ëè îí ïàäàòü:
-      Mem.WriteBoolean(gItems[i].Fall);
-    // Èíäåêñ òðèããåðà, ñîçäàâøåãî ïðåäìåò:
-      Mem.WriteInt(gItems[i].SpawnTrigger);
-    // Îáúåêò ïðåäìåòà:
-      Obj_SaveState(@gItems[i].Obj, Mem);
+      // Òèï ïðåäìåòà
+      Mem.WriteByte(ggItems[i].ItemType);
+      // Åñòü ëè ðåñïàóí
+      Mem.WriteBoolean(ggItems[i].Respawnable);
+      // Êîîðäèíàòû ðåñïóíà
+      Mem.WriteInt(ggItems[i].InitX);
+      Mem.WriteInt(ggItems[i].InitY);
+      // Âðåìÿ äî ðåñïàóíà
+      Mem.WriteWord(ggItems[i].RespawnTime);
+      // Ñóùåñòâóåò ëè ýòîò ïðåäìåò
+      Mem.WriteBoolean(ggItems[i].Live);
+      // Ìîæåò ëè îí ïàäàòü
+      Mem.WriteBoolean(ggItems[i].Fall);
+      // Èíäåêñ òðèããåðà, ñîçäàâøåãî ïðåäìåò
+      Mem.WriteInt(ggItems[i].SpawnTrigger);
+      // Îáúåêò ïðåäìåòà
+      Obj_SaveState(@ggItems[i].Obj, Mem);
     end;
+  end;
 end;
 
-procedure g_Items_LoadState(var Mem: TBinMemoryReader);
+
+procedure g_Items_LoadState (var Mem: TBinMemoryReader);
 var
   count, i, a: Integer;
   sig: DWORD;
   b: Byte;
 begin
-  if Mem = nil then
-    Exit;
+  if (Mem = nil) then exit;
 
   g_Items_Free();
 
-// Êîëè÷åñòâî ïðåäìåòîâ:
+  // Êîëè÷åñòâî ïðåäìåòîâ
   Mem.ReadInt(count);
 
-  if count = 0 then
-    Exit;
+  if (count = 0) then Exit;
 
   for a := 0 to count-1 do
   begin
-  // Ñèãíàòóðà ïðåäìåòà:
+    // Ñèãíàòóðà ïðåäìåòà
     Mem.ReadDWORD(sig);
-    if sig <> ITEM_SIGNATURE then // 'ITEM'
-    begin
-      raise EBinSizeError.Create('g_Items_LoadState: Wrong Item Signature');
-    end;
-  // Òèï ïðåäìåòà:
+    if (sig <> ITEM_SIGNATURE) then raise EBinSizeError.Create('g_Items_LoadState: Wrong Item Signature'); // 'ITEM'
+    // Òèï ïðåäìåòà
     Mem.ReadByte(b);
-  // Ñîçäàåì ïðåäìåò:
+    // Ñîçäàåì ïðåäìåò
     i := g_Items_Create(0, 0, b, False, False);
-  // Åñòü ëè ðåñïàóí:
-    Mem.ReadBoolean(gItems[i].Respawnable);
-  // Êîîðäèíàòû ðåñïóíà:
-    Mem.ReadInt(gItems[i].InitX);
-    Mem.ReadInt(gItems[i].InitY);
-  // Âðåìÿ äî ðåñïàóíà:
-    Mem.ReadWord(gItems[i].RespawnTime);
-  // Ñóùåñòâóåò ëè ýòîò ïðåäìåò:
-    Mem.ReadBoolean(gItems[i].Live);
-  // Ìîæåò ëè îí ïàäàòü:
-    Mem.ReadBoolean(gItems[i].Fall);
-  // Èíäåêñ òðèããåðà, ñîçäàâøåãî ïðåäìåò:
-    Mem.ReadInt(gItems[i].SpawnTrigger);
-  // Îáúåêò ïðåäìåòà:
-    Obj_LoadState(@gItems[i].Obj, Mem);
+    // Åñòü ëè ðåñïàóí
+    Mem.ReadBoolean(ggItems[i].Respawnable);
+    // Êîîðäèíàòû ðåñïóíà
+    Mem.ReadInt(ggItems[i].InitX);
+    Mem.ReadInt(ggItems[i].InitY);
+    // Âðåìÿ äî ðåñïàóíà
+    Mem.ReadWord(ggItems[i].RespawnTime);
+    // Ñóùåñòâóåò ëè ýòîò ïðåäìåò
+    Mem.ReadBoolean(ggItems[i].Live);
+    // Ìîæåò ëè îí ïàäàòü
+    Mem.ReadBoolean(ggItems[i].Fall);
+    // Èíäåêñ òðèããåðà, ñîçäàâøåãî ïðåäìåò
+    Mem.ReadInt(ggItems[i].SpawnTrigger);
+    // Îáúåêò ïðåäìåòà
+    Obj_LoadState(@ggItems[i].Obj, Mem);
   end;
 end;
 
 
-procedure TItem.positionChanged ();
+procedure g_Items_RestartRound ();
+var
+  i: Integer;
+  it: PItem;
+begin
+  for i := 0 to High(ggItems) do
+  begin
+    it := @ggItems[i];
+    if it.Respawnable then
+    begin
+      it.QuietRespawn := True;
+      it.RespawnTime := 0;
+    end
+    else
+    begin
+      g_Items_Remove(i);
+      if g_Game_IsNet then MH_SEND_ItemDestroy(True, i);
+    end;
+  end;
+end;
+
+
+function g_Items_ForEachAlive (cb: TItemEachAliveCB; backwards: Boolean=false): Boolean;
+var
+  idx: Integer;
+begin
+  result := false;
+  if (ggItems = nil) or not assigned(cb) then exit;
+
+  if backwards then
+  begin
+    for idx := High(ggItems) downto 0 do
+    begin
+      if ggItems[idx].Live then
+      begin
+        result := cb(@ggItems[idx]);
+        if result then exit;
+      end;
+    end;
+  end
+  else
+  begin
+    for idx := 0 to High(ggItems) do
+    begin
+      if ggItems[idx].Live then
+      begin
+        result := cb(@ggItems[idx]);
+        if result then exit;
+      end;
+    end;
+  end;
+end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+procedure g_Item_EmitPickupSound (idx: Integer);
+var
+  it: PItem;
 begin
+  if not g_ItemValidId(idx) then exit;
+  it := @ggItems[idx];
+  g_Item_EmitPickupSoundAt(idx, it.Obj.X, it.Obj.Y);
+end;
+
+procedure g_Item_EmitPickupSoundAt (idx, x, y: Integer);
+var
+  it: PItem;
+begin
+  if not g_ItemValidId(idx) then exit;
+
+  it := @ggItems[idx];
+  if gSoundEffectsDF then
+  begin
+    if it.ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL,
+                       ITEM_INVIS, ITEM_MEDKIT_BLACK, ITEM_JETPACK] then
+    begin
+      g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', x, y);
+    end
+    else if it.ItemType in [ITEM_WEAPON_SAW, ITEM_WEAPON_PISTOL, ITEM_WEAPON_SHOTGUN1, ITEM_WEAPON_SHOTGUN2,
+                            ITEM_WEAPON_CHAINGUN, ITEM_WEAPON_ROCKETLAUNCHER, ITEM_WEAPON_PLASMA,
+                            ITEM_WEAPON_BFG, ITEM_WEAPON_SUPERPULEMET, ITEM_WEAPON_FLAMETHROWER,
+                            ITEM_AMMO_BACKPACK] then
+    begin
+      g_Sound_PlayExAt('SOUND_ITEM_GETWEAPON', x, y);
+    end
+    else
+    begin
+      g_Sound_PlayExAt('SOUND_ITEM_GETITEM', x, y);
+    end;
+  end
+  else
+  begin
+    if it.ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_SUIT,
+                       ITEM_MEDKIT_BLACK, ITEM_INVUL, ITEM_INVIS, ITEM_JETPACK] then
+    begin
+      g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ', x, y);
+    end
+    else if it.ItemType in [ITEM_WEAPON_SAW, ITEM_WEAPON_PISTOL, ITEM_WEAPON_SHOTGUN1, ITEM_WEAPON_SHOTGUN2,
+                            ITEM_WEAPON_CHAINGUN, ITEM_WEAPON_ROCKETLAUNCHER, ITEM_WEAPON_PLASMA,
+                            ITEM_WEAPON_BFG, ITEM_WEAPON_SUPERPULEMET, ITEM_WEAPON_FLAMETHROWER] then
+    begin
+      g_Sound_PlayExAt('SOUND_ITEM_GETWEAPON', x, y);
+    end
+    else
+    begin
+      g_Sound_PlayExAt('SOUND_ITEM_GETITEM', x, y);
+    end;
+  end;
 end;
 
 end.
index f3e15a21ef10847f71df7a34d342ff75b3dece9b..cd2aff9951cc4908138407a965ef2acb17d5b77d 100644 (file)
@@ -189,7 +189,6 @@ const
   FLAG_SIGNATURE = $47414C46; // 'FLAG'
 
 
-
 function panelTypeToTag (panelType: Word): Integer;
 begin
   case panelType of
@@ -234,10 +233,10 @@ type
 
 type
   TDynAABBTreeMap = class(TDynAABBTree)
-    function getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh): Boolean; override;
+    function getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh; tag: Integer): Boolean; override;
   end;
 
-function TDynAABBTreeMap.getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh): Boolean;
+function TDynAABBTreeMap.getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh; tag: Integer): Boolean;
 var
   pan: TPanel;
 begin
index 0d8f3841d1eab4f1127def30e016d7eabdd7852b..690626078d17713d17936ec942a69007ca21ae1d 100644 (file)
@@ -1688,8 +1688,8 @@ begin
         it := g_Items_Create(FObj.X + (FObj.Rect.Width div 2),
                              FObj.Y + (FObj.Rect.Height div 2),
                              c, True, False);
-        g_Obj_Push(@gItems[it].Obj, (FObj.Vel.X div 2)-3+Random(7),
-                                    (FObj.Vel.Y div 2)-Random(4));
+        g_Obj_Push(g_ItemObjByIdx(it), (FObj.Vel.X div 2)-3+Random(7),
+                                       (FObj.Vel.Y div 2)-Random(4));
         positionChanged(); // this updates spatial accelerators
         if g_Game_IsServer and g_Game_IsNet then
           MH_SEND_ItemSpawn(True, it);
index 237c9dec3e939dd4148dc572683ef36042eaa833..33273f896a9ab3e339d1ca731a0a4ec53a129959 100644 (file)
@@ -627,11 +627,20 @@ end;
 // GAME (SEND)
 
 procedure MH_SEND_Everything(CreatePlayers: Boolean = False; ID: Integer = NET_EVERYONE);
+
+  function sendItemRespawn (it: PItem): Boolean;
+  begin
+    result := false; // don't stop
+    MH_SEND_ItemSpawn(True, it.myid, ID);
+  end;
+
 var
   I: Integer;
 begin
   if gPlayers <> nil then
+  begin
     for I := Low(gPlayers) to High(gPlayers) do
+    begin
       if gPlayers[I] <> nil then
       begin
         if CreatePlayers then MH_SEND_PlayerCreate(gPlayers[I].UID, ID);
@@ -641,14 +650,11 @@ begin
         if (gPlayers[I].Flag <> FLAG_NONE) and (gGameSettings.GameMode = GM_CTF) then
           MH_SEND_FlagEvent(FLAG_STATE_CAPTURED, gPlayers[I].Flag, gPlayers[I].UID, True, ID);
       end;
-
-  if gItems <> nil then
-  begin
-    for I := High(gItems) downto Low(gItems) do
-      if gItems[I].Live then
-        MH_SEND_ItemSpawn(True, I, ID);
+    end;
   end;
 
+  g_Items_ForEachAlive(sendItemRespawn, true); // backwards
+
   if gMonsters <> nil then
     for I := 0 to High(gMonsters) do
       if gMonsters[I] <> nil then
@@ -1179,17 +1185,21 @@ end;
 // ITEM (SEND)
 
 procedure MH_SEND_ItemSpawn(Quiet: Boolean; IID: Word; ID: Integer = NET_EVERYONE);
+var
+  it: PItem;
 begin
+  it := g_ItemByIdx(IID);
+
   e_Buffer_Write(@NetOut, Byte(NET_MSG_ISPAWN));
   e_Buffer_Write(@NetOut, IID);
   e_Buffer_Write(@NetOut, Byte(Quiet));
-  e_Buffer_Write(@NetOut, gItems[IID].ItemType);
-  e_Buffer_Write(@NetOut, Byte(gItems[IID].Fall));
-  e_Buffer_Write(@NetOut, Byte(gItems[IID].Respawnable));
-  e_Buffer_Write(@NetOut, gItems[IID].Obj.X);
-  e_Buffer_Write(@NetOut, gItems[IID].Obj.Y);
-  e_Buffer_Write(@NetOut, gItems[IID].Obj.Vel.X);
-  e_Buffer_Write(@NetOut, gItems[IID].Obj.Vel.Y);
+  e_Buffer_Write(@NetOut, it.ItemType);
+  e_Buffer_Write(@NetOut, Byte(it.Fall));
+  e_Buffer_Write(@NetOut, Byte(it.Respawnable));
+  e_Buffer_Write(@NetOut, it.Obj.X);
+  e_Buffer_Write(@NetOut, it.Obj.Y);
+  e_Buffer_Write(@NetOut, it.Obj.Vel.X);
+  e_Buffer_Write(@NetOut, it.Obj.Vel.Y);
 
   g_Net_Host_Send(ID, True, NET_CHAN_LARGEDATA);
 end;
@@ -2339,6 +2349,7 @@ var
   T: Byte;
   Quiet, Fall{, Resp}: Boolean;
   Anim: TAnimation;
+  it: PItem;
 begin
   if not gGameOn then Exit;
   ID := e_Raw_Read_Word(P);
@@ -2352,8 +2363,10 @@ begin
   VY := e_Raw_Read_LongInt(P);
 
   g_Items_Create(X, Y, T, Fall, False, False, ID);
-  gItems[ID].Obj.Vel.X := VX;
-  gItems[ID].Obj.Vel.Y := VY;
+
+  it := g_ItemByIdx(ID);
+  it.Obj.Vel.X := VX;
+  it.Obj.Vel.Y := VY;
 
   if not Quiet then
   begin
@@ -2361,7 +2374,7 @@ begin
     if g_Frames_Get(AID, 'FRAMES_ITEM_RESPAWN') then
     begin
       Anim := TAnimation.Create(AID, False, 4);
-      g_GFX_OnceAnim(X+(gItems[ID].Obj.Rect.Width div 2)-16, Y+(gItems[ID].Obj.Rect.Height div 2)-16, Anim);
+      g_GFX_OnceAnim(X+(it.Obj.Rect.Width div 2)-16, Y+(it.Obj.Rect.Height div 2)-16, Anim);
       Anim.Free();
     end;
   end;
@@ -2375,43 +2388,10 @@ begin
   if not gGameOn then Exit;
   ID := e_Raw_Read_Word(P);
   Quiet := e_Raw_Read_Byte(P) <> 0;
-  if gItems = nil then Exit;
-  if (ID > High(gItems)) then Exit;
 
-  if not Quiet then
-    if gSoundEffectsDF then
-    begin
-      if gItems[ID].ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_INVUL,
-                                 ITEM_INVIS, ITEM_MEDKIT_BLACK, ITEM_JETPACK] then
-        g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ',
-          gItems[ID].Obj.X, gItems[ID].Obj.Y)
-        else
-          if gItems[ID].ItemType in [ITEM_WEAPON_SAW, ITEM_WEAPON_PISTOL, ITEM_WEAPON_SHOTGUN1, ITEM_WEAPON_SHOTGUN2,
-                                     ITEM_WEAPON_CHAINGUN, ITEM_WEAPON_ROCKETLAUNCHER, ITEM_WEAPON_PLASMA,
-                                     ITEM_WEAPON_BFG, ITEM_WEAPON_SUPERPULEMET, ITEM_WEAPON_FLAMETHROWER,
-                                     ITEM_AMMO_BACKPACK] then
-            g_Sound_PlayExAt('SOUND_ITEM_GETWEAPON',
-              gItems[ID].Obj.X, gItems[ID].Obj.Y)
-            else
-              g_Sound_PlayExAt('SOUND_ITEM_GETITEM',
-                gItems[ID].Obj.X, gItems[ID].Obj.Y);
-    end
-    else
-    begin
-      if gItems[ID].ItemType in [ITEM_SPHERE_BLUE, ITEM_SPHERE_WHITE, ITEM_SUIT,
-                                 ITEM_MEDKIT_BLACK, ITEM_INVUL, ITEM_INVIS, ITEM_JETPACK] then
-        g_Sound_PlayExAt('SOUND_ITEM_GETRULEZ',
-          gItems[ID].Obj.X, gItems[ID].Obj.Y)
-      else
-        if gItems[ID].ItemType in [ITEM_WEAPON_SAW, ITEM_WEAPON_PISTOL, ITEM_WEAPON_SHOTGUN1, ITEM_WEAPON_SHOTGUN2,
-                                   ITEM_WEAPON_CHAINGUN, ITEM_WEAPON_ROCKETLAUNCHER, ITEM_WEAPON_PLASMA,
-                                   ITEM_WEAPON_BFG, ITEM_WEAPON_SUPERPULEMET, ITEM_WEAPON_FLAMETHROWER] then
-          g_Sound_PlayExAt('SOUND_ITEM_GETWEAPON',
-            gItems[ID].Obj.X, gItems[ID].Obj.Y)
-        else
-          g_Sound_PlayExAt('SOUND_ITEM_GETITEM',
-            gItems[ID].Obj.X, gItems[ID].Obj.Y);
-    end;
+  if not g_ItemValidId(ID) then exit;
+
+  if not Quiet then g_Item_EmitPickupSound(ID);
 
   g_Items_Remove(ID);
 end;
index f4e3b0fff39acd98b14a65fa9bc9bcfe82bdb5d6..0ee9365f2adbee0cf6183a774a83500781d6ea23 100644 (file)
@@ -2973,31 +2973,33 @@ var
   DoFrags: Boolean;
   OldLR: Byte;
   KP: TPlayer;
+  it: PItem;
 
   procedure PushItem(t: Byte);
   var
     id: DWORD;
   begin
     id := g_Items_Create(FObj.X, FObj.Y, t, True, False);
+    it := g_ItemByIdx(id);
     if KillType = K_EXTRAHARDKILL then // -7..+7; -8..0
     begin
-      g_Obj_Push(@gItems[id].Obj, (FObj.Vel.X div 2)-7+Random(15),
-                                  (FObj.Vel.Y div 2)-Random(9));
-      gItems[id].positionChanged(); // this updates spatial accelerators
+      g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-7+Random(15),
+                          (FObj.Vel.Y div 2)-Random(9));
+      it.positionChanged(); // this updates spatial accelerators
     end
     else
     begin
       if KillType = K_HARDKILL then // -5..+5; -5..0
       begin
-        g_Obj_Push(@gItems[id].Obj, (FObj.Vel.X div 2)-5+Random(11),
-                                    (FObj.Vel.Y div 2)-Random(6));
+        g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-5+Random(11),
+                            (FObj.Vel.Y div 2)-Random(6));
       end
       else // -3..+3; -3..0
       begin
-        g_Obj_Push(@gItems[id].Obj, (FObj.Vel.X div 2)-3+Random(7),
-                                    (FObj.Vel.Y div 2)-Random(4));
+        g_Obj_Push(@it.Obj, (FObj.Vel.X div 2)-3+Random(7),
+                            (FObj.Vel.Y div 2)-Random(4));
       end;
-      gItems[id].positionChanged(); // this updates spatial accelerators
+      it.positionChanged(); // this updates spatial accelerators
     end;
 
     if g_Game_IsNet and g_Game_IsServer then
index 5283e7008751eee7805ac02aca4c6eff48534159..7238c8046c4bec7b4a75921f1de6f47b38a1807c 100644 (file)
@@ -954,6 +954,7 @@ var
   Anim: TAnimation;
   UIDType: Byte;
   TargetUID: Word;
+  it: PItem;
 begin
   Result := False;
   if g_Game_IsClient then
@@ -1305,51 +1306,55 @@ begin
 
                 if Data.ItemMax > 0 then
                 begin
-                  gItems[iid].SpawnTrigger := ID;
+                  it := g_ItemByIdx(iid);
+                  it.SpawnTrigger := ID;
                   Inc(SpawnedCount);
                 end;
 
                 case Data.ItemEffect of
                   EFFECT_TELEPORT: begin
+                    it := g_ItemByIdx(iid);
                     if g_Frames_Get(FramesID, 'FRAMES_TELEPORT') then
                     begin
                       Anim := TAnimation.Create(FramesID, False, 3);
                       g_Sound_PlayExAt('SOUND_GAME_TELEPORT', Data.ItemPos.X, Data.ItemPos.Y);
-                      g_GFX_OnceAnim(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-32,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+(gItems[iid].Obj.Rect.Height div 2)-32, Anim);
+                      g_GFX_OnceAnim(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
+                                     it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-32, Anim);
                       Anim.Free();
                     end;
                     if g_Game_IsServer and g_Game_IsNet then
-                      MH_SEND_Effect(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-32,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+(gItems[iid].Obj.Rect.Height div 2)-32, 1,
+                      MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
+                                     it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-32, 1,
                                      NET_GFX_TELE);
                   end;
                   EFFECT_RESPAWN: begin
+                    it := g_ItemByIdx(iid);
                     if g_Frames_Get(FramesID, 'FRAMES_ITEM_RESPAWN') then
                     begin
                       Anim := TAnimation.Create(FramesID, False, 4);
                       g_Sound_PlayExAt('SOUND_ITEM_RESPAWNITEM', Data.ItemPos.X, Data.ItemPos.Y);
-                      g_GFX_OnceAnim(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-16,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+(gItems[iid].Obj.Rect.Height div 2)-16, Anim);
+                      g_GFX_OnceAnim(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-16,
+                                     it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-16, Anim);
                       Anim.Free();
                     end;
                     if g_Game_IsServer and g_Game_IsNet then
-                      MH_SEND_Effect(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-16,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+(gItems[iid].Obj.Rect.Height div 2)-16, 1,
+                      MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-16,
+                                     it.Obj.Y+it.Obj.Rect.Y+(it.Obj.Rect.Height div 2)-16, 1,
                                      NET_GFX_RESPAWN);
                   end;
                   EFFECT_FIRE: begin
+                    it := g_ItemByIdx(iid);
                     if g_Frames_Get(FramesID, 'FRAMES_FIRE') then
                     begin
                       Anim := TAnimation.Create(FramesID, False, 4);
                       g_Sound_PlayExAt('SOUND_FIRE', Data.ItemPos.X, Data.ItemPos.Y);
-                      g_GFX_OnceAnim(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-32,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+gItems[iid].Obj.Rect.Height-128, Anim);
+                      g_GFX_OnceAnim(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
+                                     it.Obj.Y+it.Obj.Rect.Y+it.Obj.Rect.Height-128, Anim);
                       Anim.Free();
                     end;
                     if g_Game_IsServer and g_Game_IsNet then
-                      MH_SEND_Effect(gItems[iid].Obj.X+gItems[iid].Obj.Rect.X+(gItems[iid].Obj.Rect.Width div 2)-32,
-                                     gItems[iid].Obj.Y+gItems[iid].Obj.Rect.Y+gItems[iid].Obj.Rect.Height-128, 1,
+                      MH_SEND_Effect(it.Obj.X+it.Obj.Rect.X+(it.Obj.Rect.Width div 2)-32,
+                                     it.Obj.Y+it.Obj.Rect.Y+it.Obj.Rect.Height-128, 1,
                                      NET_GFX_FIRE);
                   end;
                 end;
index 5b3fa623d9b13dd14d4f8a6c695d5242f1b4ce50..a64e6322b7cbd98518e4ee9671cdb2a70aaccd94 100644 (file)
@@ -148,12 +148,13 @@ type
         //nextNodeId: Integer;
         // a node is either a leaf (has data) or is an internal node (has children)
         children: array [0..1] of Integer; // left and right child of the node (children[0] = left child)
-        //TODO: `flesh` can be united with `children`
-        flesh: TTreeFlesh;
         // height of the node in the tree (-1 for free nodes)
         height: SmallInt;
         // fat axis aligned bounding box (AABB) corresponding to the node
         aabb: AABB2D;
+        //TODO: `flesh` can be united with `children`
+        flesh: TTreeFlesh;
+        fleshX, fleshY: TreeNumber;
         tag: Integer; // just a user-defined tag
       public
         // return true if the node is a leaf of the tree
@@ -162,6 +163,8 @@ type
         function isfree (): Boolean; inline;
         property nextNodeId: Integer read parentId write parentId;
         //property flesh: Integer read children[0] write children[0];
+
+        procedure dumpToLog ();
       end;
 
       TVisitCheckerCB = function (node: PTreeNode): Boolean is nested;
@@ -236,8 +239,11 @@ type
     function getNodeObjectId (nodeid: Integer): TTreeFlesh; inline;
     procedure getNodeFatAABB (var aabb: AABB2D; nodeid: Integer); inline;
 
+    // returns `false` if nodeid is not leaf
+    function getNodeXY (nodeid: Integer; out x, y: Integer): Boolean; inline;
+
     // return `false` for invalid flesh
-    function getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh): Boolean; virtual; abstract;
+    function getFleshAABB (var aabb: AABB2D; flesh: TTreeFlesh; tag: Integer): Boolean; virtual; abstract;
 
     // insert an object into the tree
     // this method creates a new leaf node in the tree and returns the id of the corresponding node or -1 on error
@@ -264,7 +270,8 @@ type
      *
      * return `true` if the tree was modified.
      *)
-    function updateObject (nodeId: Integer; dispX, dispY: TreeNumber; forceReinsert: Boolean=false): Boolean;
+    function updateObject (nodeId: Integer; dispX, dispY: TreeNumber; forceReinsert: Boolean=false): Boolean; overload;
+    function updateObject (nodeId: Integer; forceReinsert: Boolean=false): Boolean; overload;
 
     function aabbQuery (ax, ay, aw, ah: TreeNumber; cb: TQueryOverlapCB; tagmask: Integer=-1): TTreeFlesh;
     function pointQuery (ax, ay: TreeNumber; cb: TQueryOverlapCB): TTreeFlesh;
@@ -542,6 +549,13 @@ begin
   aabb.maxY := 0;
 end;
 
+procedure TDynAABBTree.TTreeNode.dumpToLog ();
+begin
+  e_WriteLog(Format('NODE: parentId=%d; children=[%d,%d]; height=%d; tag=%d; fleshX=%d; fleshY=%d; aabb=(%d,%d)-(%d,%d)',
+    [parentId, children[0], children[1], Integer(height), tag, fleshX, fleshY, aabb.minX, aabb.minY, aabb.maxX, aabb.maxY]),
+    MSG_NOTIFY);
+end;
+
 
 // ////////////////////////////////////////////////////////////////////////// //
 // allocate and return a node to use in the tree
@@ -555,7 +569,7 @@ begin
   begin
     {$IFDEF aabbtree_many_asserts}assert(mNodeCount = mAllocCount);{$ENDIF}
     // allocate more nodes in the tree
-    if (mAllocCount < 32768) then newsz := mAllocCount*2 else newsz := mAllocCount+16384;
+    if (mAllocCount <= 16384) then newsz := mAllocCount*2 else newsz := mAllocCount+16384;
     SetLength(mNodes, newsz);
     mAllocCount := newsz;
     // initialize the allocated nodes
@@ -569,7 +583,7 @@ begin
   end;
   // get the next free node
   freeNodeId := mFreeNodeId;
-  {$IFDEF aabbtree_many_asserts}assert((freeNodeId >= mNodeCount) and (freeNodeId < mAllocCount));{$ENDIF}
+  {$IFDEF aabbtree_many_asserts}assert(freeNodeId < mAllocCount);{$ENDIF}
   node := @mNodes[freeNodeId];
   mFreeNodeId := node.nextNodeId;
   node.clear();
@@ -577,6 +591,8 @@ begin
   node.height := 0;
   Inc(mNodeCount);
   result := freeNodeId;
+
+  //e_WriteLog(Format('tree: allocated node #%d', [result]), MSG_NOTIFY);
 end;
 
 
@@ -591,6 +607,8 @@ begin
   mNodes[nodeId].flesh := nil;
   mFreeNodeId := nodeId;
   Dec(mNodeCount);
+
+  //e_WriteLog(Format('tree: released node #%d', [nodeId]), MSG_NOTIFY);
 end;
 
 
@@ -998,28 +1016,31 @@ end;
 function TDynAABBTree.insertObjectInternal (var aabb: AABB2D; staticObject: Boolean): Integer;
 var
   nodeId: Integer;
+  node: PTreeNode;
 begin
   // get the next available node (or allocate new ones if necessary)
   nodeId := allocateNode();
 
+  node := @mNodes[nodeId];
+
   // create the fat aabb to use in the tree
-  mNodes[nodeId].aabb := AABB2D.Create(aabb);
+  node.aabb := AABB2D.Create(aabb);
   if (not staticObject) then
   begin
-    mNodes[nodeId].aabb.minX -= mExtraGap;
-    mNodes[nodeId].aabb.minY -= mExtraGap;
-    mNodes[nodeId].aabb.maxX += mExtraGap;
-    mNodes[nodeId].aabb.maxY += mExtraGap;
+    node.aabb.minX -= mExtraGap;
+    node.aabb.minY -= mExtraGap;
+    node.aabb.maxX += mExtraGap;
+    node.aabb.maxY += mExtraGap;
   end;
 
   // set the height of the node in the tree
-  mNodes[nodeId].height := 0;
+  node.height := 0;
 
   // insert the new leaf node in the tree
   insertLeafNode(nodeId);
-  {$IFDEF aabbtree_many_asserts}assert(mNodes[nodeId].leaf);{$ENDIF}
 
-  {$IFDEF aabbtree_many_asserts}assert(nodeId >= 0);{$ENDIF}
+  {$IFDEF aabbtree_many_asserts}node := @mNodes[nodeId];{$ENDIF}
+  {$IFDEF aabbtree_many_asserts}assert(node.leaf);{$ENDIF}
 
   // return the id of the node
   result := nodeId;
@@ -1074,7 +1095,7 @@ function TDynAABBTree.forEachLeaf (dg: TForEachLeafCB): Boolean;
       {$ENDIF}
       if pNode.leaf then
       begin
-        getFleshAABB(aabb, pNode.flesh);
+        getFleshAABB(aabb, pNode.flesh, pNode.tag);
         {$IFDEF aabbtree_use_floats}
         e_WriteLog(Format('  LEAF AABB:(%f,%f)-(%f,%f); valid=%d; volume=%f', [aabb.minX, aabb.minY, aabb.maxX, aabb.maxY, Integer(aabb.valid), aabb.volume]), MSG_NOTIFY);
         {$ELSE}
@@ -1299,7 +1320,7 @@ function TDynAABBTree.computeTreeHeight (): Integer; begin result := computeHeig
 // return the root AABB of the tree
 procedure TDynAABBTree.getRootAABB (var aabb: AABB2D);
 begin
-  {$IFDEF aabbtree_many_asserts}assert((mRootNodeId >= 0) and (mRootNodeId < mNodeCount));{$ENDIF}
+  {$IFDEF aabbtree_many_asserts}assert((mRootNodeId >= 0) and (mRootNodeId < mAllocCount));{$ENDIF}
   aabb := mNodes[mRootNodeId].aabb;
 end;
 
@@ -1308,20 +1329,42 @@ end;
 // WARNING: ids of removed objects can be reused on later insertions!
 function TDynAABBTree.isValidId (id: Integer): Boolean;
 begin
-  result := (id >= 0) and (id < mNodeCount) and (mNodes[id].leaf);
+  result := (id >= 0) and (id < mAllocCount) and (mNodes[id].leaf);
 end;
 
 
 // get object by nodeid; can return nil for invalid ids
 function TDynAABBTree.getNodeObjectId (nodeid: Integer): TTreeFlesh;
 begin
-  if (nodeid >= 0) and (nodeid < mNodeCount) and (mNodes[nodeid].leaf) then result := mNodes[nodeid].flesh else result := nil;
+  if (nodeid >= 0) and (nodeid < mAllocCount) and (mNodes[nodeid].leaf) then result := mNodes[nodeid].flesh else result := nil;
 end;
 
 // get fat object AABB by nodeid; returns random shit for invalid ids
 procedure TDynAABBTree.getNodeFatAABB (var aabb: AABB2D; nodeid: Integer);
 begin
-  if (nodeid >= 0) and (nodeid < mNodeCount) and (not mNodes[nodeid].isfree) then aabb.copyFrom(mNodes[nodeid].aabb) else aabb.setDims(0, 0, 0, 0);
+  if (nodeid >= 0) and (nodeid < mAllocCount) and (not mNodes[nodeid].isfree) then aabb.copyFrom(mNodes[nodeid].aabb) else aabb.setDims(0, 0, 0, 0);
+end;
+
+function TDynAABBTree.getNodeXY (nodeid: Integer; out x, y: Integer): Boolean; inline;
+begin
+  if (nodeid >= 0) and (nodeid < mAllocCount) and (mNodes[nodeid].leaf) then
+  begin
+    result := true;
+    {$IFDEF aabbtree_use_floats}
+    x := round(mNodes[nodeid].fleshX);
+    y := round(mNodes[nodeid].fleshY);
+    {$ELSE}
+    x := mNodes[nodeid].fleshX;
+    y := mNodes[nodeid].fleshY;
+    {$ENDIF}
+  end
+  else
+  begin
+    result := false;
+    x := 0;
+    y := 0;
+    //if (nodeid >= 0) and (nodeid < mAllocCount) then mNodes[nodeid].dumpToLog();
+  end;
 end;
 
 
@@ -1332,9 +1375,9 @@ end;
 function TDynAABBTree.insertObject (flesh: TTreeFlesh; tag: Integer; staticObject: Boolean=false): Integer;
 var
   aabb: AABB2D;
-  nodeId: Integer;
+  nodeId, fx, fy: Integer;
 begin
-  if not getFleshAABB(aabb, flesh) then
+  if not getFleshAABB(aabb, flesh, tag) then
   begin
     {$IFDEF aabbtree_use_floats}
     e_WriteLog(Format('trying to insert FUCKED FLESH:(%f,%f)-(%f,%f); volume=%f; valid=%d', [aabb.minX, aabb.minY, aabb.maxX, aabb.maxY, aabb.volume, Integer(aabb.valid)]), MSG_WARNING);
@@ -1357,10 +1400,14 @@ begin
     exit;
   end;
   //e_WriteLog(Format('inserting AABB:(%f,%f)-(%f,%f); volume=%f; valid=%d', [aabb.minX, aabb.minY, aabb.maxX, aabb.maxY, aabb.volume, Integer(aabb.valid)]), MSG_NOTIFY);
+  fx := aabb.minX;
+  fy := aabb.minY;
   nodeId := insertObjectInternal(aabb, staticObject);
   {$IFDEF aabbtree_many_asserts}assert(mNodes[nodeId].leaf);{$ENDIF}
   mNodes[nodeId].flesh := flesh;
   mNodes[nodeId].tag := tag;
+  mNodes[nodeId].fleshX := fx;
+  mNodes[nodeId].fleshY := fy;
   result := nodeId;
 end;
 
@@ -1369,57 +1416,94 @@ end;
 // WARNING: ids of removed objects can be reused on later insertions!
 procedure TDynAABBTree.removeObject (nodeId: Integer);
 begin
-  if (nodeId < 0) or (nodeId >= mNodeCount) or (not mNodes[nodeId].leaf) then raise Exception.Create('invalid node id in TDynAABBTree');
+  if (nodeId < 0) or (nodeId >= mAllocCount) or (not mNodes[nodeId].leaf) then raise Exception.Create('invalid node id in TDynAABBTree');
   // remove the node from the tree
   removeLeafNode(nodeId);
   releaseNode(nodeId);
 end;
 
 
-function TDynAABBTree.updateObject (nodeId: Integer; dispX, dispY: TreeNumber; forceReinsert: Boolean=false): Boolean;
+function TDynAABBTree.updateObject (nodeId: Integer; forceReinsert: Boolean=false): Boolean; overload;
 var
   newAABB: AABB2D;
+  dispX, dispY: TreeNumber;
 begin
-  if (nodeId < 0) or (nodeId >= mNodeCount) or (not mNodes[nodeId].leaf) then raise Exception.Create('invalid node id in TDynAABBTree.updateObject');
+  if (nodeId < 0) or (nodeId >= mAllocCount) or (not mNodes[nodeId].leaf) then raise Exception.Create('invalid node id in TDynAABBTree.updateObject');
 
-  if not getFleshAABB(newAABB, mNodes[nodeId].flesh) then raise Exception.Create('invalid node id in TDynAABBTree.updateObject');
+  if not getFleshAABB(newAABB, mNodes[nodeId].flesh, mNodes[nodeId].tag) then raise Exception.Create('invalid flesh dimensions in TDynAABBTree.updateObject');
   if not newAABB.valid then raise Exception.Create('invalid flesh aabb in TDynAABBTree.updateObject');
 
+  dispX := newAABB.minX-mNodes[nodeId].fleshX;
+  dispY := newAABB.minY-mNodes[nodeId].fleshY;
+
+  if (dispX < -16) then dispX := -16 else if (dispX > 16) then dispX := 16;
+  if (dispY < -16) then dispY := -16 else if (dispY > 16) then dispY := 16;
+
+  result := updateObject(nodeId, dispX, dispY, forceReinsert);
+end;
+
+function TDynAABBTree.updateObject (nodeId: Integer; dispX, dispY: TreeNumber; forceReinsert: Boolean=false): Boolean; overload;
+var
+  newAABB: AABB2D;
+  fx, fy: Integer;
+  node: PTreeNode;
+begin
+  if (nodeId < 0) or (nodeId >= mAllocCount) or (not mNodes[nodeId].leaf) then raise Exception.Create('invalid node id in TDynAABBTree.updateObject');
+
+  if not getFleshAABB(newAABB, mNodes[nodeId].flesh, mNodes[nodeId].tag) then raise Exception.Create('invalid flesh dimensions in TDynAABBTree.updateObject');
+  if not newAABB.valid then raise Exception.Create('invalid flesh aabb in TDynAABBTree.updateObject');
+
+  fx := newAABB.minX;
+  fy := newAABB.minY;
+
   // if the new AABB is still inside the fat AABB of the node
-  if (not forceReinsert) and (mNodes[nodeId].aabb.contains(newAABB)) then begin result := false; exit; end;
+  if (not forceReinsert) and (mNodes[nodeId].aabb.contains(newAABB)) then
+  begin
+    node := @mNodes[nodeId];
+    node.fleshX := fx;
+    node.fleshY := fy;
+    result := false;
+    exit;
+  end;
 
   // if the new AABB is outside the fat AABB, we remove the corresponding node
   removeLeafNode(nodeId);
 
+  node := @mNodes[nodeId];
+
   // compute the fat AABB by inflating the AABB with a constant gap
-  mNodes[nodeId].aabb := newAABB;
+  node.aabb.copyFrom(newAABB);
+  node.fleshX := fx;
+  node.fleshY := fy;
+
   if (not forceReinsert) and ((dispX <> 0) or (dispY <> 0)) then
   begin
-    mNodes[nodeId].aabb.minX := mNodes[nodeId].aabb.minX-mExtraGap;
-    mNodes[nodeId].aabb.minY := mNodes[nodeId].aabb.minY-mExtraGap;
-    mNodes[nodeId].aabb.maxX := mNodes[nodeId].aabb.maxX+mExtraGap;
-    mNodes[nodeId].aabb.maxY := mNodes[nodeId].aabb.maxY+mExtraGap;
+    node.aabb.minX -= mExtraGap;
+    node.aabb.minY += mExtraGap;
+    node.aabb.maxX += mExtraGap;
+    node.aabb.maxY += mExtraGap;
   end;
 
   // inflate the fat AABB in direction of the linear motion of the AABB
   if (dispX < 0) then
   begin
-    mNodes[nodeId].aabb.minX := mNodes[nodeId].aabb.minX+LinearMotionGapMultiplier*dispX {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
+    node.aabb.minX += LinearMotionGapMultiplier*dispX {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
   end
   else
   begin
-    mNodes[nodeId].aabb.maxX := mNodes[nodeId].aabb.maxX+LinearMotionGapMultiplier*dispX {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
+    node.aabb.maxX += LinearMotionGapMultiplier*dispX {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
   end;
+
   if (dispY < 0) then
   begin
-    mNodes[nodeId].aabb.minY := mNodes[nodeId].aabb.minY+LinearMotionGapMultiplier*dispY {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
+    node.aabb.minY += LinearMotionGapMultiplier*dispY {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
   end
   else
   begin
-    mNodes[nodeId].aabb.maxY := mNodes[nodeId].aabb.maxY+LinearMotionGapMultiplier*dispY {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
+    node.aabb.maxY += LinearMotionGapMultiplier*dispY {$IFDEF aabbtree_use_floats}{$ELSE}div 10{$ENDIF};
   end;
 
-  {$IFDEF aabbtree_many_asserts}assert(mNodes[nodeId].aabb.contains(newAABB));{$ENDIF}
+  {$IFDEF aabbtree_many_asserts}assert(node.aabb.contains(newAABB));{$ENDIF}
 
   // reinsert the node into the tree
   insertLeafNode(nodeId);
@@ -1466,7 +1550,7 @@ begin
   if not assigned(cb) then cb := dummycb;
   caabb := AABB2D.Create(ax, ay, ax+1, ay+1);
   nid := visit(caabb, ModePoint, checker, cb);
-  {$IFDEF aabbtree_many_asserts}assert((nid < 0) or ((nid >= 0) and (nid < mNodeCount) and (mNodes[nid].leaf)));{$ENDIF}
+  {$IFDEF aabbtree_many_asserts}assert((nid < 0) or ((nid >= 0) and (nid < mAllocCount) and (mNodes[nid].leaf)));{$ENDIF}
   if (nid >= 0) then result := mNodes[nid].flesh else result := nil;
 end;
 
index 968c5f94221e63a73ced2a96a109eda6ff99daf0..50531ed6f453d79044597739bf48d813dfe96c6f 100644 (file)
@@ -21,11 +21,14 @@ interface
 
 
 type
-  TBinaryHeapLessFn = function (a, b: TObject): Boolean;
+  // WARNING! don't put structures into heap, use ponters or ids!
+  generic TBinaryHeapBase<ITP> = class(TObject)
+  private
+    type
+      TBinaryHeapLessFn = function (a, b: ITP): Boolean;
 
-  TBinaryHeapObj = class(TObject)
   private
-    elem: array of TObject;
+    elem: array of ITP;
     elemUsed: Integer;
     lessfn: TBinaryHeapLessFn;
 
@@ -38,15 +41,24 @@ type
 
     procedure clear ();
 
-    procedure insert (val: TObject);
+    procedure insert (val: ITP);
 
-    function front (): TObject;
+    function front (): ITP;
     procedure popFront ();
 
     property count: Integer read elemUsed;
   end;
 
 
+type
+  TBinaryHeapObj = specialize TBinaryHeapBase<TObject>;
+  TBinaryHeapInt = specialize TBinaryHeapBase<Integer>;
+
+
+function binHeapNewIntLess (): TBinaryHeapInt;
+function binHeapNewIntGreat (): TBinaryHeapInt;
+
+
 implementation
 
 uses
@@ -54,32 +66,41 @@ uses
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-constructor TBinaryHeapObj.Create (alessfn: TBinaryHeapLessFn);
+function intLess (a, b: Integer): Boolean; begin result := (a < b); end;
+function intGreat (a, b: Integer): Boolean; begin result := (a > b); end;
+
+
+function binHeapNewIntLess (): TBinaryHeapInt; begin result := TBinaryHeapInt.Create(@intLess); end;
+function binHeapNewIntGreat (): TBinaryHeapInt; begin result := TBinaryHeapInt.Create(@intGreat); end;
+
+
+// ////////////////////////////////////////////////////////////////////////// //
+constructor TBinaryHeapBase.Create (alessfn: TBinaryHeapLessFn);
 begin
   if not assigned(alessfn) then raise Exception.Create('wutafuck?!');
   lessfn := alessfn;
-  SetLength(elem, 8192); // 'cause why not?
+  SetLength(elem, 128); // 'cause why not?
   elemUsed := 0;
 end;
 
 
-destructor TBinaryHeapObj.Destroy ();
+destructor TBinaryHeapBase.Destroy ();
 begin
   elem := nil;
   inherited;
 end;
 
 
-procedure TBinaryHeapObj.clear ();
+procedure TBinaryHeapBase.clear ();
 begin
   elemUsed := 0;
 end;
 
 
-procedure TBinaryHeapObj.heapify (root: Integer);
+procedure TBinaryHeapBase.heapify (root: Integer);
 var
   smallest, right: Integer;
-  tmp: TObject;
+  tmp: ITP;
 begin
   while true do
   begin
@@ -98,14 +119,21 @@ begin
 end;
 
 
-procedure TBinaryHeapObj.insert (val: TObject);
+procedure TBinaryHeapBase.insert (val: ITP);
 var
   i, par: Integer;
 begin
-  if (val = nil) then exit;
+  //if (val = nil) then exit;
   i := elemUsed;
-  if (i = Length(elem)) then SetLength(elem, Length(elem)+16384); // arbitrary number
+  // grow?
+  if (i = Length(elem)) then
+  begin
+    if (i <= 65536) then par := i*2 else par := i+65536; // arbitrary numbers
+    SetLength(elem, par);
+  end;
+  // increase counter
   Inc(elemUsed);
+  // insert element
   while (i <> 0) do
   begin
     par := (i-1) div 2; // parent
@@ -116,13 +144,13 @@ begin
   elem[i] := val;
 end;
 
-function TBinaryHeapObj.front (): TObject;
+function TBinaryHeapBase.front (): ITP;
 begin
-  if elemUsed > 0 then result := elem[0] else result := nil;
+  if elemUsed > 0 then result := elem[0] else result := Default(ITP);
 end;
 
 
-procedure TBinaryHeapObj.popFront ();
+procedure TBinaryHeapBase.popFront ();
 begin
   if (elemUsed > 0) then
   begin