001package net.tnemc.item.bukkit;
002
003/*
004 * The New Item Library Minecraft Server Plugin
005 *
006 * Copyright (C) 2024 Daniel "creatorfromhell" Vidmar
007 *
008 * This program is free software; you can redistribute it and/or
009 * modify it under the terms of the GNU Lesser General Public
010 * License as published by the Free Software Foundation; either
011 * version 3 of the License, or (at your option) any later version.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
016 * Lesser General Public License for more details.
017 *
018 * You should have received a copy of the GNU Lesser General Public License
019 * along with this program; if not, write to the Free Software Foundation,
020 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
021 */
022
023import net.tnemc.item.InventoryType;
024import net.tnemc.item.bukkit.platform.BukkitItemPlatform;
025import net.tnemc.item.providers.CalculationsProvider;
026import net.tnemc.item.providers.ItemProvider;
027import net.tnemc.item.providers.VersionUtil;
028import org.bukkit.Bukkit;
029import org.bukkit.Material;
030import org.bukkit.OfflinePlayer;
031import org.bukkit.block.Container;
032import org.bukkit.block.ShulkerBox;
033import org.bukkit.entity.Item;
034import org.bukkit.entity.Player;
035import org.bukkit.inventory.Inventory;
036import org.bukkit.inventory.ItemStack;
037import org.bukkit.inventory.meta.BlockStateMeta;
038import org.bukkit.inventory.meta.BundleMeta;
039
040import java.util.ArrayList;
041import java.util.Collection;
042import java.util.Iterator;
043import java.util.List;
044import java.util.Map;
045import java.util.Optional;
046import java.util.UUID;
047
048/**
049 * Represents a Bukkit implementation of the {@link CalculationsProvider}.
050 *
051 * @author creatorfromhell
052 * @since 0.1.5.0
053 */
054public class BukkitCalculationsProvider implements CalculationsProvider<BukkitItemStack, ItemStack, Inventory> {
055
056  /**
057   * Removes items from a collection based on certain criteria.
058   *
059   * @param left      The collection of items from which to remove items.
060   * @param player    The UUID of the player associated with the removal operation.
061   * @param setOwner  Indicates whether to set the owner of the removed items.(supports spigot/paper 1.16.5+)
062   *
063   * @return True if the removal operation was successful, false otherwise.
064   */
065  @Override
066  public boolean drop(final Collection<BukkitItemStack> left, final UUID player, final boolean setOwner) {
067
068    final Player playerObj = Bukkit.getPlayer(player);
069    if(playerObj == null) {
070      return false;
071    }
072
073    for(final BukkitItemStack stack : left) {
074
075      if(setOwner && VersionUtil.isOneSixteen(BukkitItemPlatform.instance().version())) {
076
077        final Item it = playerObj.getWorld().dropItemNaturally(playerObj.getLocation(), stack.provider().locale(stack));
078        it.setOwner(player);
079        continue;
080      }
081
082      playerObj.getWorld().dropItemNaturally(playerObj.getLocation(), stack.provider().locale(stack));
083    }
084    return false;
085  }
086
087  /**
088   * Removes all items that are equal to the stack from an inventory.
089   *
090   * @param stack     The stack to compare to for removal from the inventory.
091   * @param inventory The inventory to remove the items from.
092   */
093  @Override
094  public int removeAll(final BukkitItemStack stack, final Inventory inventory) {
095
096    final ItemStack compare = stack.provider().locale(stack).clone();
097    compare.setAmount(1);
098
099    int amount = 0;
100    //final BukkitItemStack comp = new BukkitItemStack().of(compare);
101    final ItemProvider<ItemStack> provider = stack.provider();
102
103    for(int i = 0; i < inventory.getStorageContents().length; i++) {
104
105      final ItemStack item = inventory.getItem(i);
106      if(item == null) {
107        continue;
108      }
109
110      if(provider.similar(stack, item)) {
111        amount += item.getAmount();
112        inventory.setItem(i, null);
113        continue;
114      }
115
116      if(item.getItemMeta() instanceof final BlockStateMeta meta && meta.getBlockState() instanceof final ShulkerBox shulker) {
117
118        System.out.println("Entering container");
119
120        final Inventory shulkerInventory = shulker.getInventory();
121        for(int shulkerSlot = 0; shulkerSlot < shulkerInventory.getStorageContents().length; shulkerSlot++) {
122
123          System.out.println("Slot: " + shulkerSlot);
124
125          final ItemStack shulkerStack = shulkerInventory.getItem(shulkerSlot);
126          if(shulkerStack == null) {
127
128            System.out.println("Stack is null");
129            continue;
130          }
131
132          if(!provider.similar(stack, shulkerStack)) {
133            System.out.println("Stacks aren't similar");
134            continue;
135          }
136
137          amount += item.getAmount();
138          inventory.setItem(shulkerSlot, null);
139          shulkerInventory.setItem(shulkerSlot, null);
140        }
141
142        System.out.println("Leaving container");
143        shulker.update(true);
144        meta.setBlockState(shulker);
145        item.setItemMeta(meta);
146        inventory.setItem(i, item);
147      }
148
149      if(item.getItemMeta() instanceof final BundleMeta bundle) {
150
151        final List<ItemStack> items = new ArrayList<>(bundle.getItems());
152        final Iterator<ItemStack> it = items.iterator();
153        while(it.hasNext()) {
154
155          final ItemStack bundleStack = it.next();
156          if(bundleStack == null) {
157
158            continue;
159          }
160
161          if(provider.similar(stack, bundleStack)) {
162
163            amount += item.getAmount();
164            it.remove();
165          }
166        }
167
168        bundle.setItems(items);
169        item.setItemMeta(bundle);
170        inventory.setItem(i, item);
171      }
172    }
173    return amount;
174  }
175
176  /**
177   * Returns a count of items equal to the specific stack in an inventory.
178   *
179   * @param stack     The stack to get a count of.
180   * @param inventory The inventory to check.
181   *
182   * @return The total count of items in the inventory.
183   */
184  @Override
185  public int count(final BukkitItemStack stack, final Inventory inventory) {
186
187    final ItemStack compare = stack.provider().locale(stack).clone();
188    compare.setAmount(1);
189
190    //TODO: make this more efficient
191    final ItemProvider<ItemStack> provider = stack.provider();
192    int amount = 0;
193
194    for(final ItemStack item : inventory.getStorageContents()) {
195
196      if(item == null) {
197        continue;
198      }
199
200      if(provider.similar(stack, item)) {
201
202        amount += item.getAmount();
203      }
204
205      if(item.getItemMeta() instanceof final BlockStateMeta meta && meta.getBlockState() instanceof final ShulkerBox shulker) {
206
207        final Inventory shulkerInventory = shulker.getInventory();
208        if(shulkerInventory.isEmpty()) {
209
210          continue;
211        }
212
213        for(int ci = 0; ci < shulkerInventory.getStorageContents().length; ci++) {
214
215          final ItemStack shulkerStack = shulkerInventory.getItem(ci);
216          if(shulkerStack == null) {
217
218            continue;
219          }
220
221          if(provider.similar(stack, shulkerStack)) {
222
223            amount += shulkerStack.getAmount();
224          }
225        }
226      }
227
228      if(item.getItemMeta() instanceof final BundleMeta bundle) {
229
230        for(final ItemStack bundleItem : bundle.getItems()) {
231
232          if(bundleItem == null) {
233
234            continue;
235          }
236
237          if(provider.similar(stack, bundleItem)) {
238
239            amount += bundleItem.getAmount();
240          }
241        }
242      }
243    }
244    return amount;
245  }
246
247  /**
248   * Takes a collection of items from an inventory.
249   *
250   * @param items     The collection of items to remove.
251   * @param inventory The inventory to remove the items from.
252   */
253  @Override
254  public void takeItems(final Collection<BukkitItemStack> items, final Inventory inventory) {
255
256    items.forEach(itemStack->removeItem(itemStack, inventory));
257  }
258
259  /**
260   * Adds a collection of net.tnemc.item stacks to an inventory, dropping them on the ground if it's
261   * a player inventory and overflow exists.
262   *
263   * @param items     The collection of items to add to the inventory.
264   * @param inventory The inventory to add the collection of items to.
265   */
266  @Override
267  public Collection<BukkitItemStack> giveItems(final Collection<BukkitItemStack> items, final Inventory inventory) {
268
269    final Collection<BukkitItemStack> leftOver = new ArrayList<>();
270
271    for(final BukkitItemStack item : items) {
272
273      if(item == null) {
274        continue;
275      }
276
277      final Map<Integer, ItemStack> left = inventory.addItem(item.provider().locale(item, item.amount()));
278      if(left.isEmpty()) {
279
280        continue;
281      }
282
283      for(final Map.Entry<Integer, ItemStack> entry : left.entrySet()) {
284        final ItemStack i = entry.getValue();
285
286        if(i == null || i.getType() == Material.AIR) {
287          continue;
288        }
289
290        leftOver.add(new BukkitItemStack().of(i));
291      }
292    }
293    return leftOver;
294  }
295
296  /**
297   * Removes an ItemStack with a specific amount from an inventory.
298   *
299   * @param stack     The stack, with the correct amount, to remove.
300   * @param inventory The inventory to return the net.tnemc.item stack from.
301   *
302   * @return The remaining amount of items to remove.
303   */
304  @Override
305  public int removeItem(final BukkitItemStack stack, final Inventory inventory) {
306
307    int left = stack.provider().locale(stack).clone().getAmount();
308
309    final ItemStack compare = stack.provider().locale(stack).clone();
310    compare.setAmount(1);
311
312    System.out.println("Calc removeItem: Left: " + left);
313
314    //TODO: improve this
315
316    final ItemProvider<ItemStack> provider = stack.provider();
317
318    for(int i = 0; i < inventory.getStorageContents().length; i++) {
319      if(left <= 0) break;
320
321      final ItemStack item = inventory.getItem(i);
322
323      if(item == null) continue;
324
325      if(provider.similar(stack, item)) {
326
327        if(item.getAmount() > left) {
328          item.setAmount(item.getAmount() - left);
329          inventory.setItem(i, item);
330          left = 0;
331          break;
332        }
333
334        if(item.getAmount() == left) {
335          inventory.setItem(i, null);
336          left = 0;
337          break;
338        }
339
340        left -= item.getAmount();
341        inventory.setItem(i, null);
342      }
343
344      if(item.getItemMeta() instanceof final BlockStateMeta meta && meta.getBlockState() instanceof final ShulkerBox shulker) {
345
346        System.out.println("Entering container");
347
348        final Inventory shulkerInventory = shulker.getInventory();
349        for(int shulkerSlot = 0; shulkerSlot < shulkerInventory.getStorageContents().length; shulkerSlot++) {
350
351          System.out.println("Slot: " + shulkerSlot);
352          if(left <= 0) break;
353
354          final ItemStack shulkerStack = shulkerInventory.getItem(shulkerSlot);
355          if(shulkerStack == null) {
356
357            System.out.println("Stack is null");
358            continue;
359          }
360
361          if(!provider.similar(stack, shulkerStack)) {
362            System.out.println("Stacks aren't similar");
363            continue;
364          }
365
366          if(shulkerStack.getAmount() > left) {
367
368            System.out.println("changing stack size");
369
370            shulkerStack.setAmount(shulkerStack.getAmount() - left);
371            shulkerInventory.setItem(shulkerSlot, shulkerStack);
372            left = 0;
373            break;
374          }
375
376          if(shulkerStack.getAmount() == left) {
377            System.out.println("Removing stack containerStack.getAmount() == left");
378            shulkerInventory.setItem(shulkerSlot, null);
379            left = 0;
380            break;
381          }
382
383          System.out.println("Removing stack left -= containerStack.getAmount()");
384          left -= shulkerStack.getAmount();
385          shulkerInventory.setItem(shulkerSlot, null);
386        }
387
388        System.out.println("Leaving container");
389        shulker.update(true);
390        meta.setBlockState(shulker);
391        item.setItemMeta(meta);
392        inventory.setItem(i, item);
393      }
394
395      if(item.getItemMeta() instanceof final BundleMeta bundle) {
396
397        final List<ItemStack> items = new ArrayList<>(bundle.getItems());
398        final Iterator<ItemStack> it = items.iterator();
399        while(it.hasNext()) {
400
401          final ItemStack bundleStack = it.next();
402          if(bundleStack == null) {
403
404            System.out.println("Stack is null");
405            continue;
406          }
407
408          if(!provider.similar(stack, bundleStack)) {
409            System.out.println("Stacks aren't similar");
410            continue;
411          }
412
413          if(bundleStack.getAmount() > left) {
414
415            System.out.println("changing stack size");
416
417            bundleStack.setAmount(bundleStack.getAmount() - left);
418            left = 0;
419            break;
420          }
421
422          if(bundleStack.getAmount() == left) {
423            System.out.println("Removing stack containerStack.getAmount() == left");
424            it.remove();
425            left = 0;
426            break;
427          }
428
429          System.out.println("Removing stack left -= containerStack.getAmount()");
430          left -= bundleStack.getAmount();
431          it.remove();
432        }
433
434        bundle.setItems(items);
435        item.setItemMeta(bundle);
436        inventory.setItem(i, item);
437      }
438    }
439    return left;
440  }
441
442  /**
443   * Used to locate an inventory for a UUID identifier.
444   *
445   * @param identifier The identifier to use for the search.
446   * @param type       The inventory type to return.
447   *
448   * @return An optional containing the inventory if it works, otherwise false.
449   */
450  @Override
451  public Optional<Inventory> inventory(final UUID identifier, final InventoryType type) {
452
453    final OfflinePlayer player = Bukkit.getOfflinePlayer(identifier);
454    if(player.isOnline() && player.getPlayer() != null) {
455
456      if(type.equals(InventoryType.ENDER_CHEST)) {
457        return Optional.of(player.getPlayer().getEnderChest());
458      } else {
459        return Optional.of(player.getPlayer().getInventory());
460      }
461    }
462    return Optional.empty();
463  }
464}