Skip to content

Commit 0f0cecc

Browse files
committed
Fix copper golem logging not working with double chests
Works around Paper bug where the entity is null when a golem closes a double chest
1 parent 893ce9a commit 0f0cecc

File tree

1 file changed

+196
-19
lines changed

1 file changed

+196
-19
lines changed

src/main/java/net/coreprotect/paper/listener/CopperGolemChestListener.java

Lines changed: 196 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
import java.util.List;
66
import java.util.Map;
77
import java.util.Objects;
8+
import java.util.Set;
89
import java.util.UUID;
910
import java.util.concurrent.ConcurrentHashMap;
1011

1112
import org.bukkit.GameEvent;
1213
import org.bukkit.Location;
1314
import org.bukkit.Material;
1415
import org.bukkit.block.BlockState;
16+
import org.bukkit.block.DoubleChest;
1517
import org.bukkit.entity.CopperGolem;
1618
import org.bukkit.entity.Entity;
1719
import org.bukkit.event.EventHandler;
@@ -43,6 +45,8 @@ public final class CopperGolemChestListener implements Listener {
4345
private final CoreProtect plugin;
4446
private final Map<UUID, OpenInteraction> openInteractions = new ConcurrentHashMap<>();
4547
private final Map<UUID, RecentEmptyCopperChestSkip> recentEmptyCopperChestSkips = new ConcurrentHashMap<>();
48+
private final Map<TransactionKey, OpenInteractionIndex> openInteractionIndexByContainerKey = new ConcurrentHashMap<>();
49+
private final Map<TransactionKey, Set<UUID>> emptySkipGolemsByContainerKey = new ConcurrentHashMap<>();
4650
private volatile long lastCleanupMillis;
4751

4852
public CopperGolemChestListener(CoreProtect plugin) {
@@ -61,8 +65,15 @@ public void onGenericGameEvent(GenericGameEvent event) {
6165
}
6266

6367
Entity entity = event.getEntity();
64-
if (!(entity instanceof CopperGolem)) {
65-
return;
68+
if (gameEvent == GameEvent.CONTAINER_OPEN) {
69+
if (!(entity instanceof CopperGolem)) {
70+
return;
71+
}
72+
}
73+
else {
74+
if (entity != null && !(entity instanceof CopperGolem)) {
75+
return;
76+
}
6677
}
6778

6879
Location eventLocation = event.getLocation();
@@ -85,7 +96,7 @@ public void onGenericGameEvent(GenericGameEvent event) {
8596
}
8697

8798
Material containerType = blockState.getType();
88-
boolean isCopperChest = BukkitAdapter.ADAPTER.isCopperChest(containerType);
99+
boolean isCopperChest = isCopperChest(containerType);
89100
boolean isStandardChest = containerType == Material.CHEST || containerType == Material.TRAPPED_CHEST;
90101
if (!isCopperChest && !isStandardChest) {
91102
return;
@@ -94,25 +105,59 @@ public void onGenericGameEvent(GenericGameEvent event) {
94105
long now = System.currentTimeMillis();
95106
cleanupOpenInteractions(now);
96107

97-
CopperGolem golem = (CopperGolem) entity;
98-
TransactionKey containerKey = TransactionKey.of(containerLocation);
108+
InventoryHolder inventoryHolder = (InventoryHolder) blockState;
109+
Inventory inventory = inventoryHolder.getInventory();
110+
Location canonicalLocation = getCanonicalContainerLocation(containerLocation, inventory);
111+
TransactionKey containerKey = TransactionKey.of(canonicalLocation);
99112

100113
if (gameEvent == GameEvent.CONTAINER_OPEN) {
101-
handleContainerOpen(golem, containerLocation, containerKey, containerType, (InventoryHolder) blockState, now);
114+
handleContainerOpen((CopperGolem) entity, canonicalLocation, containerKey, containerType, inventoryHolder, now);
102115
}
103116
else {
104-
handleContainerClose(golem, containerLocation, containerKey, containerType, (InventoryHolder) blockState, now);
117+
if (entity instanceof CopperGolem) {
118+
handleContainerClose((CopperGolem) entity, canonicalLocation, containerKey, containerType, inventoryHolder, now);
119+
}
120+
else if (entity == null) {
121+
handleContainerCloseWithoutEntity(containerKey, containerType, now);
122+
}
105123
}
106124
}
107125

126+
static Location getCanonicalContainerLocation(Location containerLocation, Inventory inventory) {
127+
if (containerLocation == null || containerLocation.getWorld() == null || inventory == null) {
128+
return containerLocation;
129+
}
130+
131+
InventoryHolder holder = inventory.getHolder();
132+
if (!(holder instanceof DoubleChest)) {
133+
return containerLocation;
134+
}
135+
136+
Location doubleChestLocation = ((DoubleChest) holder).getLocation();
137+
if (doubleChestLocation == null) {
138+
return containerLocation;
139+
}
140+
141+
Location canonical = new Location(containerLocation.getWorld(), doubleChestLocation.getBlockX(), doubleChestLocation.getBlockY(), doubleChestLocation.getBlockZ());
142+
if (canonical.getWorld() == null) {
143+
return containerLocation;
144+
}
145+
146+
return canonical;
147+
}
148+
149+
private static boolean isCopperChest(Material material) {
150+
return BukkitAdapter.ADAPTER != null && BukkitAdapter.ADAPTER.isCopperChest(material);
151+
}
152+
108153
private void handleContainerOpen(CopperGolem golem, Location containerLocation, TransactionKey containerKey, Material containerType, InventoryHolder inventoryHolder, long nowMillis) {
109154
Inventory inventory = inventoryHolder.getInventory();
110155
if (inventory == null) {
111156
return;
112157
}
113158

114159
HeldItemSnapshot held = getHeldItemSnapshot(golem);
115-
boolean isCopperChest = BukkitAdapter.ADAPTER.isCopperChest(containerType);
160+
boolean isCopperChest = isCopperChest(containerType);
116161
if (isCopperChest) {
117162
if (held.material != null) {
118163
return;
@@ -131,7 +176,12 @@ private void handleContainerOpen(CopperGolem golem, Location containerLocation,
131176

132177
if (isCopperChest) {
133178
if (isInventoryEmpty(contents)) {
134-
recentEmptyCopperChestSkips.put(golem.getUniqueId(), new RecentEmptyCopperChestSkip(containerKey, nowMillis));
179+
UUID golemId = golem.getUniqueId();
180+
RecentEmptyCopperChestSkip previous = recentEmptyCopperChestSkips.put(golemId, new RecentEmptyCopperChestSkip(containerKey, nowMillis));
181+
if (previous != null) {
182+
unindexEmptySkip(previous.containerKey, golemId);
183+
}
184+
indexEmptySkip(containerKey, golemId);
135185
return;
136186
}
137187
}
@@ -153,18 +203,29 @@ private void handleContainerOpen(CopperGolem golem, Location containerLocation,
153203
Material heldMaterial = isCopperChest ? null : held.material;
154204
int heldAmount = isCopperChest ? 0 : held.amount;
155205
OpenInteraction interaction = new OpenInteraction(containerKey, containerLocation.clone(), containerType, baseline, heldMaterial, heldAmount, nowMillis);
156-
recentEmptyCopperChestSkips.remove(golem.getUniqueId());
157-
openInteractions.put(golem.getUniqueId(), interaction);
206+
UUID golemId = golem.getUniqueId();
207+
RecentEmptyCopperChestSkip removedSkip = recentEmptyCopperChestSkips.remove(golemId);
208+
if (removedSkip != null) {
209+
unindexEmptySkip(removedSkip.containerKey, golemId);
210+
}
211+
212+
OpenInteraction previous = openInteractions.put(golemId, interaction);
213+
if (previous != null) {
214+
openInteractionIndexByContainerKey.remove(previous.containerKey, new OpenInteractionIndex(golemId, previous.openedAtMillis));
215+
}
216+
openInteractionIndexByContainerKey.put(containerKey, new OpenInteractionIndex(golemId, nowMillis));
158217
}
159218

160219
private void handleContainerClose(CopperGolem golem, Location containerLocation, TransactionKey containerKey, Material containerType, InventoryHolder inventoryHolder, long nowMillis) {
161220
UUID golemId = golem.getUniqueId();
162221
OpenInteraction interaction = openInteractions.get(golemId);
163222
if (interaction == null) {
164-
if (BukkitAdapter.ADAPTER.isCopperChest(containerType)) {
223+
if (isCopperChest(containerType)) {
165224
RecentEmptyCopperChestSkip emptySkip = recentEmptyCopperChestSkips.get(golemId);
166225
if (emptySkip != null && emptySkip.containerKey.equals(containerKey) && (nowMillis - emptySkip.skippedAtMillis) <= EMPTY_COPPER_CHEST_SKIP_TTL_MILLIS) {
167-
recentEmptyCopperChestSkips.remove(golemId, emptySkip);
226+
if (recentEmptyCopperChestSkips.remove(golemId, emptySkip)) {
227+
unindexEmptySkip(emptySkip.containerKey, golemId);
228+
}
168229
return;
169230
}
170231

@@ -176,7 +237,9 @@ private void handleContainerClose(CopperGolem golem, Location containerLocation,
176237
}
177238

178239
if (nowMillis - interaction.openedAtMillis > OPEN_INTERACTION_TIMEOUT_MILLIS) {
179-
openInteractions.remove(golemId, interaction);
240+
if (openInteractions.remove(golemId, interaction)) {
241+
openInteractionIndexByContainerKey.remove(interaction.containerKey, new OpenInteractionIndex(golemId, interaction.openedAtMillis));
242+
}
180243
return;
181244
}
182245

@@ -185,9 +248,79 @@ private void handleContainerClose(CopperGolem golem, Location containerLocation,
185248
}
186249

187250
openInteractions.remove(golemId, interaction);
251+
openInteractionIndexByContainerKey.remove(interaction.containerKey, new OpenInteractionIndex(golemId, interaction.openedAtMillis));
188252
scheduleCloseFinalize(golemId, interaction, containerKey, 1);
189253
}
190254

255+
private void handleContainerCloseWithoutEntity(TransactionKey containerKey, Material containerType, long nowMillis) {
256+
if (containerKey == null) {
257+
return;
258+
}
259+
260+
if (isCopperChest(containerType)) {
261+
clearRecentEmptyCopperChestSkipsByKey(containerKey);
262+
}
263+
264+
OpenInteractionIndex index = openInteractionIndexByContainerKey.get(containerKey);
265+
if (index == null) {
266+
return;
267+
}
268+
269+
UUID golemId = index.golemId;
270+
OpenInteraction interaction = openInteractions.get(golemId);
271+
if (interaction == null) {
272+
openInteractionIndexByContainerKey.remove(containerKey, index);
273+
return;
274+
}
275+
if (interaction.openedAtMillis != index.openedAtMillis || !interaction.containerKey.equals(containerKey)) {
276+
openInteractionIndexByContainerKey.remove(containerKey, index);
277+
return;
278+
}
279+
if (nowMillis - interaction.openedAtMillis > OPEN_INTERACTION_TIMEOUT_MILLIS) {
280+
if (openInteractions.remove(golemId, interaction)) {
281+
openInteractionIndexByContainerKey.remove(containerKey, index);
282+
}
283+
return;
284+
}
285+
286+
if (openInteractions.remove(golemId, interaction)) {
287+
openInteractionIndexByContainerKey.remove(containerKey, index);
288+
scheduleCloseFinalize(golemId, interaction, containerKey, 1);
289+
}
290+
}
291+
292+
private void clearRecentEmptyCopperChestSkipsByKey(TransactionKey containerKey) {
293+
Set<UUID> golemIds = emptySkipGolemsByContainerKey.remove(containerKey);
294+
if (golemIds == null || golemIds.isEmpty()) {
295+
return;
296+
}
297+
298+
for (UUID golemId : golemIds) {
299+
RecentEmptyCopperChestSkip skip = recentEmptyCopperChestSkips.get(golemId);
300+
if (skip != null && skip.containerKey.equals(containerKey)) {
301+
recentEmptyCopperChestSkips.remove(golemId, skip);
302+
}
303+
}
304+
}
305+
306+
private void indexEmptySkip(TransactionKey containerKey, UUID golemId) {
307+
emptySkipGolemsByContainerKey.computeIfAbsent(containerKey, key -> ConcurrentHashMap.newKeySet()).add(golemId);
308+
}
309+
310+
private void unindexEmptySkip(TransactionKey containerKey, UUID golemId) {
311+
if (containerKey == null || golemId == null) {
312+
return;
313+
}
314+
Set<UUID> golemIds = emptySkipGolemsByContainerKey.get(containerKey);
315+
if (golemIds == null) {
316+
return;
317+
}
318+
golemIds.remove(golemId);
319+
if (golemIds.isEmpty()) {
320+
emptySkipGolemsByContainerKey.remove(containerKey, golemIds);
321+
}
322+
}
323+
191324
private void scheduleCloseFinalize(UUID golemId, OpenInteraction interaction, TransactionKey containerKey, int attempt) {
192325
plugin.getServer().getScheduler().runTaskLater(plugin, () -> finalizeContainerClose(golemId, interaction, containerKey, attempt), CLOSE_FINALIZE_DELAY_TICKS);
193326
}
@@ -240,7 +373,7 @@ private void finalizeUntrackedCopperChestClose(UUID golemId, Location containerL
240373
return;
241374
}
242375

243-
if (!BukkitAdapter.ADAPTER.isCopperChest(containerType)) {
376+
if (!isCopperChest(containerType)) {
244377
return;
245378
}
246379

@@ -259,7 +392,7 @@ private void finalizeUntrackedCopperChestClose(UUID golemId, Location containerL
259392
}
260393

261394
BlockState blockState = containerLocation.getBlock().getState();
262-
if (!(blockState instanceof InventoryHolder) || !BukkitAdapter.ADAPTER.isCopperChest(blockState.getType())) {
395+
if (!(blockState instanceof InventoryHolder) || !isCopperChest(blockState.getType())) {
263396
return;
264397
}
265398

@@ -283,7 +416,7 @@ private void finalizeUntrackedCopperChestClose(UUID golemId, Location containerL
283416
}
284417

285418
private boolean isAttributableToGolem(CopperGolem golem, OpenInteraction interaction, ItemStack[] currentContents) {
286-
boolean isCopperChest = BukkitAdapter.ADAPTER.isCopperChest(interaction.containerType);
419+
boolean isCopperChest = isCopperChest(interaction.containerType);
287420
HeldItemSnapshot heldNow = getHeldItemSnapshot(golem);
288421

289422
if (isCopperChest) {
@@ -389,11 +522,23 @@ private void recordForcedContainerState(Location location, ItemStack[] contents)
389522
}
390523

391524
String loggingContainerId = USERNAME + "." + location.getBlockX() + "." + location.getBlockY() + "." + location.getBlockZ();
525+
392526
List<ItemStack[]> forceList = ConfigHandler.forceContainer.get(loggingContainerId);
527+
List<ItemStack[]> oldList = ConfigHandler.oldContainer.get(loggingContainerId);
528+
529+
boolean hasPendingBaseline = oldList != null && !oldList.isEmpty();
530+
boolean hasStaleForceSnapshots = forceList != null && !forceList.isEmpty() && (forceList.get(0) == null || forceList.get(0).length != snapshot.length);
531+
532+
if (!hasPendingBaseline || hasStaleForceSnapshots) {
533+
ConfigHandler.forceContainer.remove(loggingContainerId);
534+
forceList = null;
535+
}
536+
393537
if (forceList == null) {
394538
forceList = Collections.synchronizedList(new ArrayList<>());
395539
ConfigHandler.forceContainer.put(loggingContainerId, forceList);
396540
}
541+
397542
forceList.add(snapshot);
398543
}
399544

@@ -408,15 +553,19 @@ private void cleanupOpenInteractions(long nowMillis) {
408553
UUID golemId = entry.getKey();
409554
OpenInteraction interaction = entry.getValue();
410555
if (interaction == null || nowMillis - interaction.openedAtMillis > OPEN_INTERACTION_TIMEOUT_MILLIS || plugin.getServer().getEntity(golemId) == null) {
411-
openInteractions.remove(golemId, interaction);
556+
if (openInteractions.remove(golemId, interaction) && interaction != null) {
557+
openInteractionIndexByContainerKey.remove(interaction.containerKey, new OpenInteractionIndex(golemId, interaction.openedAtMillis));
558+
}
412559
}
413560
}
414561

415562
for (Map.Entry<UUID, RecentEmptyCopperChestSkip> entry : recentEmptyCopperChestSkips.entrySet()) {
416563
UUID golemId = entry.getKey();
417564
RecentEmptyCopperChestSkip skip = entry.getValue();
418565
if (skip == null || nowMillis - skip.skippedAtMillis > EMPTY_COPPER_CHEST_SKIP_TTL_MILLIS || plugin.getServer().getEntity(golemId) == null) {
419-
recentEmptyCopperChestSkips.remove(golemId, skip);
566+
if (recentEmptyCopperChestSkips.remove(golemId, skip) && skip != null) {
567+
unindexEmptySkip(skip.containerKey, golemId);
568+
}
420569
}
421570
}
422571
}
@@ -625,4 +774,32 @@ public int hashCode() {
625774
}
626775

627776
}
777+
778+
private static final class OpenInteractionIndex {
779+
780+
private final UUID golemId;
781+
private final long openedAtMillis;
782+
783+
private OpenInteractionIndex(UUID golemId, long openedAtMillis) {
784+
this.golemId = golemId;
785+
this.openedAtMillis = openedAtMillis;
786+
}
787+
788+
@Override
789+
public boolean equals(Object obj) {
790+
if (this == obj) {
791+
return true;
792+
}
793+
if (!(obj instanceof OpenInteractionIndex)) {
794+
return false;
795+
}
796+
OpenInteractionIndex other = (OpenInteractionIndex) obj;
797+
return openedAtMillis == other.openedAtMillis && golemId.equals(other.golemId);
798+
}
799+
800+
@Override
801+
public int hashCode() {
802+
return Objects.hash(golemId, openedAtMillis);
803+
}
804+
}
628805
}

0 commit comments

Comments
 (0)