55import java .util .List ;
66import java .util .Map ;
77import java .util .Objects ;
8+ import java .util .Set ;
89import java .util .UUID ;
910import java .util .concurrent .ConcurrentHashMap ;
1011
1112import org .bukkit .GameEvent ;
1213import org .bukkit .Location ;
1314import org .bukkit .Material ;
1415import org .bukkit .block .BlockState ;
16+ import org .bukkit .block .DoubleChest ;
1517import org .bukkit .entity .CopperGolem ;
1618import org .bukkit .entity .Entity ;
1719import 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