From 39984c356bf51161f5eddf8d48fb115098508a97 Mon Sep 17 00:00:00 2001
From: Foz <fozolo@gmail.com>
Date: Mon, 12 Mar 2018 03:50:14 -0400
Subject: [PATCH] Resolve itemframe entity duplication Fixes #405.

The ABM that replaces deleted entities in itemframes and pedestals can sometimes add multiple copies of each entity due to a race condition in which the ABM runs either before the server loads the entities or after they have been removed due to the map block being unloaded.  Due to limitations in determining when all entities have been loaded, this race condition cannot be fully avoided.  This commit converts the ABM, which ran every fifteen seconds, into and LBM that runs on every load and adds a check to the entities when they are loaded such that only the first loaded entity will be kept and any additional copies delete themselves during the loading process.
---
 itemframes/init.lua | 60 ++++++++++++++++++++++++++++++++-------------
 1 file changed, 43 insertions(+), 17 deletions(-)

diff --git a/itemframes/init.lua b/itemframes/init.lua
index d26df19c..21fac2f3 100644
--- a/itemframes/init.lua
+++ b/itemframes/init.lua
@@ -32,6 +32,25 @@ minetest.register_entity("itemframes:item",{
 		if self.nodename == "itemframes:pedestal" then
 			self.object:set_properties({automatic_rotate = 1})
 		end
+		if self.texture ~= nil and self.nodename ~= nil then
+			local entity_pos = vector.round(self.object:get_pos())
+			local objs = minetest.get_objects_inside_radius(entity_pos, 0.5)
+			for _, obj in ipairs(objs) do
+				if obj ~= self.object and
+				   obj:get_luaentity() and
+				   obj:get_luaentity().name == "itemframes:item" and
+				   obj:get_luaentity().nodename == self.nodename and
+				   obj:get_properties() and
+				   obj:get_properties().textures and
+				   obj:get_properties().textures[1] == self.texture then
+					minetest.log("action","[itemframes] Removing extra " ..
+						self.texture .. " found in " .. self.nodename .. " at " ..
+						minetest.pos_to_string(entity_pos))
+					self.object:remove()
+					break
+				end
+			end
+		end
 	end,
 	get_staticdata = function(self)
 		if self.nodename ~= nil and self.texture ~= nil then
@@ -228,24 +247,31 @@ minetest.register_node("itemframes:pedestal",{
 
 -- automatically restore entities lost from frames/pedestals
 -- due to /clearobjects or similar
-
-minetest.register_abm({
+minetest.register_lbm({
+	label = "Maintain itemframe and pedestal entities",
+	name = "itemframes:maintain_entities",
 	nodenames = {"itemframes:frame", "itemframes:pedestal"},
-	interval = 15,
-	chance = 1,
-	action = function(pos, node, active_object_count, active_object_count_wider)
-		local num
-
-		if node.name == "itemframes:frame" then
-			num = #minetest.get_objects_inside_radius(pos, 0.5)
-		elseif node.name == "itemframes:pedestal" then
-			pos.y = pos.y + 1
-			num = #minetest.get_objects_inside_radius(pos, 0.5)
-			pos.y = pos.y - 1
-		end
-
-		if num > 0 then return end
-		update_item(pos, node)
+	run_at_every_load = true,
+	action = function(pos, node)
+		minetest.after(0,
+			function(pos, node)
+				local meta = minetest.get_meta(pos)
+				local itemstring = meta:get_string("item")
+				if itemstring ~= "" then
+					local entity_pos = pos
+					if node.name == "itemframes:pedestal" then
+						entity_pos = {x=pos.x,y=pos.y+1,z=pos.z}
+					end
+					local objs = minetest.get_objects_inside_radius(entity_pos, 0.5)
+					if #objs == 0 then
+						minetest.log("action","[itemframes] Replacing missing " ..
+							itemstring .. " in " .. node.name .. " at " ..
+							minetest.pos_to_string(pos))
+						update_item(pos, node)
+					end
+				end
+			end,
+		pos, node)
 	end
 })
 
-- 
GitLab