1010import io .papermc .paper .event .player .PlayerTradeEvent ;
1111import io .papermc .paper .event .player .PrePlayerAttackEntityEvent ;
1212import io .papermc .paper .event .server .ServerResourcesReloadedEvent ;
13+ import net .kyori .adventure .util .Ticks ;
1314import org .bukkit .Bukkit ;
1415import org .bukkit .GameMode ;
1516import org .bukkit .Location ;
3334import org .bukkit .util .Vector ;
3435import org .jspecify .annotations .NullMarked ;
3536
37+ import java .lang .ref .WeakReference ;
3638import java .util .HashSet ;
3739import java .util .Iterator ;
3840import java .util .Set ;
@@ -51,6 +53,8 @@ public final class ShopListener implements Listener {
5153 private final LoadingCache <AbstractVillager , ShopVillager > villagerCache ;
5254 private final Set <ShopVillager > villagerSaveQueue = new HashSet <>();
5355
56+ private final Set <MerchantEntry > openedMerchants = new HashSet <>();
57+
5458 public ShopListener (CaManager manager ) {
5559 this .manager = manager ;
5660 this .shopKey = new NamespacedKey (manager .getPlugin (), "shop" );
@@ -161,8 +165,7 @@ public void onEntityInteract(PlayerInteractEntityEvent event) {
161165 Player player = event .getPlayer ();
162166
163167 if (!player .isSneaking ()) {
164- boolean opened = ShopMenu .openMerchantMenu (this .manager .getPlugin (), shop , player );
165- if (!opened ) {
168+ if (!this .openTradeInventory (shop , player )) {
166169 player .sendMessage (CaManager .getPrefix ().append (translatable ("ca.menu.shop.no-trades" )));
167170 if (shop .isOwner (player )) {
168171 player .sendMessage (CaManager .getPrefix ().append (translatable ("ca.menu.shop.no-trades.owner-hit" )));
@@ -219,13 +222,23 @@ public void onPlayerTrade(PlayerTradeEvent event) {
219222 if (!(event .getPlayer ().getOpenInventory () instanceof MerchantView )) {
220223 return ; // exited merchant inventory menu
221224 }
222- if (!ShopMenu . openMerchantMenu ( this .manager . getPlugin (), shop , event .getPlayer ())) {
225+ if (!this .openTradeInventory ( shop , event .getPlayer ())) {
223226 event .getPlayer ().closeInventory (); // no trades present
224227 }
225228 });
226229 }
227230 }
228231
232+ public boolean openTradeInventory (ShopVillager shop , Player player ) {
233+ AbstractVillager merchant = ShopMenu .openMerchantMenu (this .manager .getPlugin (), shop , player );
234+ if (merchant == null ) {
235+ return false ; // failed
236+ }
237+ // keep track of opened merchants
238+ this .openedMerchants .add (new MerchantEntry (shop , player , merchant ));
239+ return true ;
240+ }
241+
229242 @ EventHandler
230243 public void onTickEnd (ServerTickEndEvent event ) {
231244 // players may bulk-buy a few items, so we have a queue for saving shop villager data to prevent lags
@@ -239,5 +252,50 @@ public void onTickEnd(ServerTickEndEvent event) {
239252 }
240253 }
241254 }
255+ this .openedMerchants .removeIf (MerchantEntry ::tick );
256+ }
257+
258+ private static class MerchantEntry {
259+
260+ private static final int TTL = Ticks .TICKS_PER_SECOND * 60 * 5 ; // 5 minutes
261+
262+ private final ShopVillager shop ;
263+ private final WeakReference <Player > player ;
264+ private final WeakReference <AbstractVillager > merchant ;
265+
266+ private int ttl = TTL ;
267+
268+ public MerchantEntry (ShopVillager shop , Player player , AbstractVillager merchant ) {
269+ this .shop = shop ;
270+ this .player = new WeakReference <>(player );
271+ this .merchant = new WeakReference <>(merchant );
272+ }
273+
274+ public boolean checkValid () {
275+ Player player = this .player .get ();
276+ if (player == null || !player .isValid () || !player .isConnected ()) {
277+ return false ; // player has left/died
278+ }
279+ AbstractVillager merchant = this .merchant .get ();
280+ if (merchant == null || !merchant .isValid ()) {
281+ return false ; // opened merchant has become invalid
282+ } else if (this .ttl < 1 ) {
283+ return false ; // expired
284+ }
285+ this .shop .ensureLoaded (); // check original shop villager
286+ return true ;
287+ }
288+
289+ public boolean tick () {
290+ if (!this .checkValid ()) {
291+ Player player = this .player .get ();
292+ if (player != null && player .getOpenInventory () instanceof MerchantView ) {
293+ player .closeInventory ();
294+ }
295+ return true ; // removed
296+ }
297+ this .ttl --;
298+ return false ;
299+ }
242300 }
243301}
0 commit comments