Skip to content

Commit 72e1bed

Browse files
committed
Keep track of opened merchant inventory
1 parent 113c6e3 commit 72e1bed

File tree

2 files changed

+66
-9
lines changed

2 files changed

+66
-9
lines changed

src/main/java/dev/booky/craftattack/shops/ShopListener.java

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import io.papermc.paper.event.player.PlayerTradeEvent;
1111
import io.papermc.paper.event.player.PrePlayerAttackEntityEvent;
1212
import io.papermc.paper.event.server.ServerResourcesReloadedEvent;
13+
import net.kyori.adventure.util.Ticks;
1314
import org.bukkit.Bukkit;
1415
import org.bukkit.GameMode;
1516
import org.bukkit.Location;
@@ -33,6 +34,7 @@
3334
import org.bukkit.util.Vector;
3435
import org.jspecify.annotations.NullMarked;
3536

37+
import java.lang.ref.WeakReference;
3638
import java.util.HashSet;
3739
import java.util.Iterator;
3840
import 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
}

src/main/java/dev/booky/craftattack/shops/ShopMenu.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import net.kyori.adventure.key.Key;
1818
import net.kyori.adventure.sound.Sound;
1919
import net.kyori.adventure.text.Component;
20-
import net.kyori.adventure.util.Ticks;
2120
import org.bukkit.Keyed;
2221
import org.bukkit.Material;
2322
import org.bukkit.NamespacedKey;
@@ -61,7 +60,6 @@ public final class ShopMenu {
6160
private static final int TRADES_PER_PAGE = 4;
6261
private static final int MAX_TRADES = 20;
6362
public static final Material EMPTY_INGREDIENT = Material.STRUCTURE_VOID;
64-
private static final int PER_PLAYER_TRADER_TTL = Ticks.TICKS_PER_SECOND * 60 * 5;
6563

6664
private static final Map<Villager.Type, Material> VILL_TYPE_ITEMS = Map.ofEntries(
6765
Map.entry(Villager.Type.DESERT, Material.SAND),
@@ -125,10 +123,10 @@ public static List<MerchantRecipe> buildRecipes(ShopVillager shop) {
125123
return filtered;
126124
}
127125

128-
public static boolean openMerchantMenu(Plugin plugin, ShopVillager shop, Player player) {
126+
public static @Nullable AbstractVillager openMerchantMenu(Plugin plugin, ShopVillager shop, Player player) {
129127
List<MerchantRecipe> recipes = buildRecipes(shop);
130128
if (recipes.isEmpty()) {
131-
return false; // no recipes available
129+
return null; // no recipes available
132130
}
133131
// spawn separate merchant per player to allow multiple players to access one shop at the same time
134132
AbstractVillager merchant = shop.getMerchant();
@@ -138,7 +136,8 @@ public static boolean openMerchantMenu(Plugin plugin, ShopVillager shop, Player
138136
trader.setSilent(true);
139137
trader.setVisibleByDefault(false);
140138
trader.setCollidable(false);
141-
trader.setDespawnDelay(PER_PLAYER_TRADER_TTL);
139+
// instantly despawn after player has closed the inventory
140+
trader.setDespawnDelay(1);
142141
trader.setRecipes(recipes);
143142

144143
NamespacedKey refKey = new NamespacedKey(plugin, "shop/reference");
@@ -150,7 +149,7 @@ public static boolean openMerchantMenu(Plugin plugin, ShopVillager shop, Player
150149
.checkReachable(true)
151150
.title(translatable("ca.menu.shop.merchant"))
152151
.build(player).open();
153-
return true; // report success!
152+
return spawnedMerchant; // report success!
154153
}
155154

156155
public static void openMenu(ShopVillager shop, Player player) {

0 commit comments

Comments
 (0)