Decent version

I finally got my brain on that and coded something cleaner and finally working (sets are not working yet tho)
This commit is contained in:
Roch Blonndiaux 2023-01-24 14:34:55 +01:00
parent 68a047fda6
commit 12219601a1
10 changed files with 368 additions and 440 deletions

View File

@ -40,6 +40,7 @@ import net.Indyuce.mmoitems.comp.rpg.McMMOHook;
import net.Indyuce.mmoitems.comp.rpg.RPGHandler;
import net.Indyuce.mmoitems.gui.PluginInventory;
import net.Indyuce.mmoitems.gui.edition.recipe.RecipeBrowserGUI;
import net.Indyuce.mmoitems.listener.InventoryListener;
import net.Indyuce.mmoitems.manager.*;
import net.Indyuce.mmoitems.util.PluginUtils;
import org.apache.commons.lang.Validate;
@ -187,6 +188,7 @@ public class MMOItems extends JavaPlugin {
Bukkit.getPluginManager().registerEvents(entityManager, this);
Bukkit.getPluginManager().registerEvents(dropTableManager, this);
Bukkit.getPluginManager().registerEvents(new InventoryListener(), this);
// Load Dist module
// Load MMOCore-Bukkit module
@ -203,7 +205,7 @@ public class MMOItems extends JavaPlugin {
*/
Bukkit.getScheduler().runTaskTimer(this, () -> {
for (Player player : Bukkit.getOnlinePlayers())
PlayerData.get(player).getInventory().updateCheck();
PlayerData.get(player).getInventory().update();
}, 100, getConfig().getInt("inventory-update-delay"));
PluginUtils.isDependencyPresent("mcMMO", unused -> Bukkit.getPluginManager().registerEvents(new McMMONonRPGHook(), this));

View File

@ -173,7 +173,7 @@ public class PlayerData {
* Very important, clear particle data AFTER canceling the runnable
* otherwise it cannot cancel and the runnable keeps going (severe)
*/
inventory.content().clear();
//inventory.content().clear();
permanentEffects.clear();
cancelRunnables();
mmoData.getPassiveSkillMap().removeModifiers("MMOItemsItem");
@ -436,6 +436,11 @@ public class PlayerData {
overridingItemParticles = null;
}
@ApiStatus.Internal
public void setOverridingItemParticles(@NotNull ParticleRunnable overridingItemParticles) {
this.overridingItemParticles = overridingItemParticles;
}
@ApiStatus.Internal
public @NotNull Set<String> permissions() {
return permissions;

View File

@ -78,6 +78,7 @@ public class PlayerStats {
}
}
// Finally run a stat update after all modifiers have been gathered in the packet
packet.runUpdate();
}

View File

@ -47,6 +47,7 @@ public abstract class EquippedItem {
public void cacheItem() {
Validate.isTrue(cached == null, "MMOItem has already been cached");
cached = new VolatileMMOItem(item);
MMOItems.log("Cached item " + cached.getType().getName() + " in slot " + slot);
}
public NBTItem getNBT() {

View File

@ -1,98 +1,176 @@
package net.Indyuce.mmoitems.comp.inventory;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.player.modifier.ModifierSource;
import io.lumine.mythic.lib.player.skill.PassiveSkill;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.player.inventory.EquippedItem;
import net.Indyuce.mmoitems.comp.inventory.model.PlayerInventory;
import org.bukkit.Bukkit;
import net.Indyuce.mmoitems.api.item.mmoitem.VolatileMMOItem;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.comp.inventory.model.PlayerInventoryImage;
import net.Indyuce.mmoitems.comp.inventory.model.PlayerMMOInventory;
import net.Indyuce.mmoitems.stat.data.*;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
/**
* Previously, only one Player Inventory was allowed.
* This makes it so plugins may register all the Player Inventories they want.
* <p></p>
* For context, a 'Player Inventory' tells MMOItems where to look for equipped items,
* (items that will add their stats to the player).
* mmoitems
* 17/01/2023
*
* @author Roch Blondiaux (Kiwix).
*/
public class PlayerInventoryHandler {
public class PlayerInventoryHandler implements Runnable {
/**
* Gets the registered Player Inventories --- The places where MMOItems determines player equipped items.
*/
@NotNull
private final List<PlayerInventory> registeredInventories = new ArrayList<>();
private volatile boolean running = false;
private final PlayerData data;
private final Player player;
private final PlayerMMOInventory inventory;
private PlayerInventoryImage image;
/**
* Use this to tell MMOItems to search equipment here ---
* Items that will give their stats to the player.
* <p></p>
* Note that if the items are not held in the correct slot (OFF_CATALYST not held in OFFHAND)
* they won't provide their stats. You may fool MMOItems by telling the <code>EquippedItem</code> that it is
* in their correct slot though, but that is up to you.
* <p></p>
* <b>Calling twice will cause duplicates</b> but is nevertheless allowed if you really want to.
*/
public void register(@NotNull PlayerInventory pInventory) {
registeredInventories.add(pInventory);
if (pInventory instanceof Listener)
Bukkit.getPluginManager().registerEvents((Listener) pInventory, MMOItems.plugin);
public PlayerInventoryHandler(@NotNull PlayerData data, @NotNull PlayerMMOInventory inventory) {
this.data = data;
this.inventory = inventory;
this.player = data.getPlayer();
this.image = PlayerInventoryImage.make(data);
}
public void unregisterIf(Predicate<PlayerInventory> filter) {
Iterator<PlayerInventory> iterator = registeredInventories.iterator();
while (iterator.hasNext()) {
PlayerInventory next = iterator.next();
if (filter.test(next)) {
if (next instanceof Listener)
HandlerList.unregisterAll((Listener) next);
iterator.remove();
@Override
public void run() {
if (!data.getPlayer().isOnline()) this.running = false;
if (!running) return;
// Create a new image and compare it to the old one
final PlayerInventoryImage newImage = PlayerInventoryImage.make(data);
if (!newImage.isDifferent(image))
return;
newImage.difference(this.image)
.forEach(item -> {
final int slot = item.getSlotNumber();
final VolatileMMOItem oldItem = image.getCached(slot).filter(volatileMMOItem -> !volatileMMOItem.getId().isEmpty()).orElse(null);
final VolatileMMOItem newItem = (item.getItem() == null || item.getItem().getType().isAir()) ? null : new VolatileMMOItem(item.getNBT());
// TODO: remove the following line
MMOItems.log(String.format("Slot #%d: %s -> %s", slot, oldItem == null ? "AIR" : oldItem.getId(), newItem == null ? "AIR" : newItem.getId()));
// Process old item
if (oldItem != null) {
// Potion effects
if (oldItem.hasData(ItemStats.PERM_EFFECTS))
((PotionEffectListData) oldItem.getData(ItemStats.PERM_EFFECTS)).getEffects()
.stream()
.filter(e -> this.data.getPermanentPotionEffectAmplifier(e.getType()) == e.getLevel() - 1)
.forEach(e -> this.data.getPermanentPotionEffectsMap().remove(e.getType(), e.toEffect()));
// Abilities
// Item particles
if (oldItem.hasData(ItemStats.ITEM_PARTICLES)) {
ParticleData particleData = (ParticleData) oldItem.getData(ItemStats.ITEM_PARTICLES);
if (particleData.getType().hasPriority())
this.data.resetOverridingItemParticles();
else
this.data.getItemParticles().removeIf(particleRunnable -> particleRunnable.getParticleData().equals(particleData));
}
// Permissions
if (MMOItems.plugin.hasPermissions() && oldItem.hasData(ItemStats.GRANTED_PERMISSIONS)) {
final Permission perms = MMOItems.plugin.getVault().getPermissions();
((StringListData) oldItem.getData(ItemStats.GRANTED_PERMISSIONS)).getList()
.forEach(s -> {
this.data.permissions().remove(s);
perms.playerRemove(player, s);
});
}
// Remove the item from the inventory
this.inventory.remove(slot);
}
// Process new item
if (newItem != null) {
if (!item.isPlacementLegal() || !this.data.getRPG().canUse(item.getNBT(), false, false))
return;
// Cache item and add it to the inventory
item.cacheItem();
this.inventory.addItem(item);
// Abilities
if (newItem.hasData(ItemStats.ABILITIES))
for (AbilityData abilityData : ((AbilityListData) newItem.getData(ItemStats.ABILITIES)).getAbilities()) {
ModifierSource modSource = item.getCached().getType().getModifierSource();
this.data.getMMOPlayerData().getPassiveSkillMap().addModifier(new PassiveSkill("MMOItemsItem", abilityData, item.getSlot(), modSource));
}
// Modifier application rules
final ModifierSource source = newItem.getType().getModifierSource();
final EquipmentSlot equipmentSlot = item.getSlot();
if (!EquipmentSlot.MAIN_HAND.isCompatible(source, equipmentSlot))
return;
// Potion effects
if (newItem.hasData(ItemStats.PERM_EFFECTS))
((PotionEffectListData) newItem.getData(ItemStats.PERM_EFFECTS)).getEffects()
.stream()
.filter(e -> this.data.getPermanentPotionEffectAmplifier(e.getType()) < e.getLevel() - 1)
.forEach(effect -> this.data.getPermanentPotionEffectsMap().put(effect.getType(), effect.toEffect()));
// Item particles
if (newItem.hasData(ItemStats.ITEM_PARTICLES)) {
ParticleData particleData = (ParticleData) newItem.getData(ItemStats.ITEM_PARTICLES);
if (particleData.getType().hasPriority()) {
if (this.data.getOverridingItemParticles() == null)
this.data.setOverridingItemParticles(particleData.start(this.data));
} else
this.data.getItemParticles().add(particleData.start(this.data));
}
// Permissions
if (MMOItems.plugin.hasPermissions() && newItem.hasData(ItemStats.GRANTED_PERMISSIONS)) {
final Permission perms = MMOItems.plugin.getVault().getPermissions();
this.data.permissions().addAll(((StringListData) newItem.getData(ItemStats.GRANTED_PERMISSIONS)).getList());
this.data.permissions()
.stream()
.filter(s -> !perms.has(player, s))
.forEach(perm -> perms.playerAdd(player, perm));
}
}
});
// Calculate player stats
this.data.getStats().updateStats();
// Update stats from external plugins
MMOItems.plugin.getRPG().refreshStats(this.data);
// Cache the new image
this.image = newImage;
this.image.cache();
// Stop the task
this.running = false;
}
/* Task */
public boolean isRunning() {
return running;
}
public void start() {
running = true;
new BukkitRunnable() {
@Override
public void run() {
PlayerInventoryHandler.this.run();
}
}
}.runTaskAsynchronously(MMOItems.plugin);
}
/**
* Can be used by external plugins to clear current inventory
* handlers if you want offhand and mainhand items removed
* from the player inventory
*/
public void unregisterAll() {
// Unregister events
for (PlayerInventory inv : registeredInventories)
if (inv instanceof Listener)
HandlerList.unregisterAll((Listener) inv);
registeredInventories.clear();
}
/**
* @return A copy of the list of registered inventories.
*/
public ArrayList<PlayerInventory> getAll() {
return new ArrayList<>(registeredInventories);
}
/**
* @return Gets the totality of items from all the PlayerInventories ie all the items that will add their stats to the player.
*/
@NotNull
public List<EquippedItem> getInventory(@NotNull Player player) {
// Get and add lists from every registered inventories
ArrayList<EquippedItem> cummulative = new ArrayList<>();
// For every registered inventory
for (PlayerInventory inv : registeredInventories) {
// Get
cummulative.addAll(inv.getInventory(player));
}
Bukkit.broadcastMessage("Cummulative: " + cummulative.size() + " PlayerInventoryHandler#93");
// Return thay result
return cummulative;
public void stop() {
running = false;
}
}

View File

@ -1,308 +0,0 @@
package net.Indyuce.mmoitems.comp.inventory;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import io.lumine.mythic.lib.player.modifier.ModifierSource;
import io.lumine.mythic.lib.player.skill.PassiveSkill;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ItemSet;
import net.Indyuce.mmoitems.api.event.inventory.MMOItemEquipEvent;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.player.inventory.EquippedItem;
import net.Indyuce.mmoitems.comp.inventory.model.SlotEquippedItem;
import net.Indyuce.mmoitems.stat.data.*;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* mmoitems
* 17/01/2023
*
* @author Roch Blondiaux (Kiwix).
*/
public class PlayerInventoryUpdater implements Runnable {
private final PlayerData data;
private final Map<Integer, Integer> lastHashCodes = new HashMap<>();
private final Map<Integer, SlotEquippedItem> CACHE = new HashMap<>();
private final Map<Integer, List<UUID>> MODIFIERS_CACHE = new HashMap<>();
private boolean running;
public PlayerInventoryUpdater(PlayerData data) {
this.data = data;
this.running = true;
}
@Override
public void run() {
if (!this.running)
return;
if (!this.data.isOnline()) {
this.stop();
return;
}
// Refresh item by item
MMOItems.plugin.getInventory()
.inventory(data.getPlayer())
.stream()
.filter(item -> item instanceof SlotEquippedItem)
.map(item -> (SlotEquippedItem) item)
.filter(this::needsUpdate)
.forEach(eItem -> {
final int currentHashcode = lastHashCodes.getOrDefault(eItem.getSlotNumber(), -1);
final int newHashcode = isEmpty(eItem) ? -1 : eItem.hashCode();
final SlotEquippedItem oldItem = CACHE.get(currentHashcode);
if (currentHashcode == newHashcode)
return;
// Call item equip event
Bukkit.getPluginManager().callEvent(new MMOItemEquipEvent(currentHashcode, newHashcode, oldItem, eItem));
MMOItems.log("Calling item equip event for slot: " + eItem.getSlotNumber() + " with hashcodes: " + currentHashcode + " -> " + newHashcode);
this.removeStats(oldItem, currentHashcode);
this.addStats(eItem, newHashcode);
});
// Refresh item sets
List<EquippedItem> equippedItems = MMOItems.plugin.getInventory()
.inventory(data.getPlayer());
this.recalculateItemSet(equippedItems);
// Calculate player stats
this.data.getStats().updateStats();
// Update stats from external plugins
MMOItems.plugin.getRPG().refreshStats(this.data);
// TODO: Call inventory refresh event
// Bukkit.getPluginManager().callEvent(new MMOInventoryRefreshEvent(inventory.equipped(), getPlayer(), this));
}
private void recalculateItemSet(@NotNull List<EquippedItem> equippedItems) {
final Map<ItemSet, Integer> itemSetCount = new HashMap<>();
for (EquippedItem equipped : equippedItems) {
final String tag = equipped.getNBT().getString("MMOITEMS_ITEM_SET");
final @Nullable ItemSet itemSet = MMOItems.plugin.getSets().get(tag);
if (itemSet == null)
continue;
itemSetCount.put(itemSet, itemSetCount.getOrDefault(itemSet, 0) + 1);
}
// Reset and compute item set bonuses
this.data.resetSetBonuses();
for (Map.Entry<ItemSet, Integer> equippedSetBonus : itemSetCount.entrySet()) {
if (this.data.getSetBonuses() == null)
this.data.setSetBonuses(equippedSetBonus.getKey().getBonuses(equippedSetBonus.getValue()));
else
this.data.getSetBonuses().merge(equippedSetBonus.getKey().getBonuses(equippedSetBonus.getValue()));
}
}
/**
* Remove all stats from the old item
*
* @param oldItem The old item
*/
private void removeStats(@Nullable SlotEquippedItem oldItem, int hashcode) {
// Remove all old item attributes & effects
if (oldItem == null || isEmpty(oldItem))
return;
MMOItems.log("Removing old item attributes & effects");
final MMOItem mmoItem = oldItem.getCached().clone();
this.data.getInventory().remove(oldItem);
// Potion effects
if (mmoItem.hasData(ItemStats.PERM_EFFECTS)) {
((PotionEffectListData) mmoItem.getData(ItemStats.PERM_EFFECTS))
.getEffects()
.forEach(effect -> {
this.data.getPermanentPotionEffectsMap().remove(effect.getType(), effect.toEffect());
MMOItems.log("Pot effect removed: " + effect.getType());
});
}
// Item particles
if (mmoItem.hasData(ItemStats.ITEM_PARTICLES)) {
ParticleData particleData = (ParticleData) mmoItem.getData(ItemStats.ITEM_PARTICLES);
if (particleData.getType().hasPriority()) {
if (this.data.getOverridingItemParticles() != null
&& this.data.getOverridingItemParticles().getParticleData().equals(particleData)) {
this.data.getOverridingItemParticles().cancel();
this.data.resetOverridingItemParticles();
MMOItems.log("Overriding particle removed: " + particleData.getType());
}
} else {
this.data.getItemParticles()
.stream()
.filter(particleRunnable -> particleRunnable.getParticleData().equals(particleData))
.peek(particleRunnable -> MMOItems.log("Particle removed: " + particleRunnable.getParticleData().getType()))
.forEach(BukkitRunnable::cancel);
this.data.getItemParticles().removeIf(BukkitRunnable::isCancelled);
}
}
// Permissions
if (MMOItems.plugin.hasPermissions() && mmoItem.hasData(ItemStats.GRANTED_PERMISSIONS)) {
final Permission perms = MMOItems.plugin.getVault().getPermissions();
List<String> permissions = new ArrayList<>(((StringListData) mmoItem.getData(ItemStats.GRANTED_PERMISSIONS)).getList());
permissions.forEach(s -> {
this.data.permissions().remove(s);
MMOItems.log("Perm removed: " + s);
if (perms.has(this.data.getPlayer(), s))
perms.playerRemove(this.data.getPlayer(), s);
});
}
// Abilities
// TODO: find a solution for that:
// Idea 1: cache ability uuid and remove it from the map
if (mmoItem.hasData(ItemStats.ABILITIES))
MODIFIERS_CACHE.getOrDefault(hashcode, Collections.emptyList())
.forEach(uuid -> this.data.getMMOPlayerData()
.getPassiveSkillMap()
.getModifiers()
.removeIf(passiveSkill -> passiveSkill.getUniqueId().equals(uuid)));
}
/**
* Add all new item attributes & effects
*
* @param eItem The new item
*/
private void addStats(@NotNull SlotEquippedItem eItem, int hashcode) {
MMOItems.log("Adding new item attributes & effects");
// Cache new item hashcode & item
cache(eItem);
// Check if the new item is empty
if (isEmpty(eItem)) {
MMOItems.log("New item is empty");
return;
}
// Check if item is legal
if (!eItem.isPlacementLegal() || !this.data.getRPG().canUse(eItem.getNBT(), false, false)) {
if (!eItem.isPlacementLegal())
MMOItems.log("Illegal item placement detected.");
else
MMOItems.log("Illegal item usage detected.");
return;
}
// Cache item
eItem.cacheItem();
// Add item to MMO inventory
this.data.getInventory().addItem(eItem);
// Add all new item attributes & effects
final MMOItem mmoItem = eItem.getCached();
final EquipmentSlot equipmentSlot = eItem.getSlot();
// Abilities
MMOItems.log("Adding abilities");
if (mmoItem.hasData(ItemStats.ABILITIES)) {
for (AbilityData abilityData : ((AbilityListData) mmoItem.getData(ItemStats.ABILITIES)).getAbilities()) {
MMOItems.log("Ability found: " + abilityData.getAbility().getName());
ModifierSource modSource = eItem.getCached().getType().getModifierSource();
PassiveSkill skill = this.data.getMMOPlayerData()
.getPassiveSkillMap()
.addModifier(new PassiveSkill("MMOItemsItem", abilityData, equipmentSlot, modSource));
MODIFIERS_CACHE.computeIfAbsent(hashcode, i -> new ArrayList<>()).add(skill.getUniqueId());
MMOItems.log("Ability added: " + abilityData.getTrigger());
}
} else
MMOItems.log("No abilities found");
// Modifier application rules
final ModifierSource source = mmoItem.getType().getModifierSource();
MMOItems.log("Modifier source: " + source.name());
if (!EquipmentSlot.MAIN_HAND.isCompatible(source, equipmentSlot)) {
MMOItems.log("Modifier source is not compatible with equipment slot");
return;
}
// Apply permanent potion effects
MMOItems.log("Adding permanent potion effects");
if (mmoItem.hasData(ItemStats.PERM_EFFECTS)) {
MMOItems.log("Permanent potion effects found");
((PotionEffectListData) mmoItem.getData(ItemStats.PERM_EFFECTS))
.getEffects()
.stream()
.filter(potionEffectData -> this.data.getPermanentPotionEffectAmplifier(potionEffectData.getType()) < potionEffectData.getLevel() - 1)
.peek(potionEffectData -> MMOItems.log("Pot effect added: " + potionEffectData.getType()))
.forEach(effect -> this.data.getPermanentPotionEffectsMap().put(effect.getType(), effect.toEffect()));
} else
MMOItems.log("No permanent potion effects found");
// Permissions
MMOItems.log("Adding permissions");
if (MMOItems.plugin.hasPermissions() && mmoItem.hasData(ItemStats.GRANTED_PERMISSIONS)) {
MMOItems.log("Permissions found");
final Permission perms = MMOItems.plugin.getVault().getPermissions();
this.data.permissions().addAll(((StringListData) mmoItem.getData(ItemStats.GRANTED_PERMISSIONS)).getList());
this.data.permissions()
.stream()
.filter(s -> !perms.has(this.data.getPlayer(), s))
.peek(s -> MMOItems.log("Perm added: " + s))
.forEach(perm -> perms.playerAdd(this.data.getPlayer(), perm));
} else
MMOItems.log("No permissions found");
}
/**
* Checks if the item needs to be updated
*
* @param item The item to check
* @return True if the item needs to be updated
*/
private boolean needsUpdate(@NotNull SlotEquippedItem item) {
return !lastHashCodes.containsKey(item.getSlotNumber()) || lastHashCodes.get(item.getSlotNumber()) != (isEmpty(item) ? -1 : item.hashCode());
}
/**
* Cache the item
*
* @param item The item to cache
*/
private void cache(@NotNull SlotEquippedItem item) {
final int hashCode = isEmpty(item) ? -1 : item.hashCode();
lastHashCodes.put(item.getSlotNumber(), hashCode);
CACHE.put(hashCode, item);
}
/**
* Checks if the item is empty
*
* @param item The item to check
* @return True if the item is empty
*/
private boolean isEmpty(@Nullable SlotEquippedItem item) {
return item == null || item.getNBT() == null || item.getNBT().getItem() == null || item.getNBT().getItem().getType().isAir();
}
public void start() {
this.running = true;
}
public void stop() {
this.running = false;
}
public boolean isRunning() {
return this.running;
}
}

View File

@ -0,0 +1,103 @@
package net.Indyuce.mmoitems.comp.inventory.model;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.mmoitem.VolatileMMOItem;
import net.Indyuce.mmoitems.api.player.PlayerData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
/**
* mmoitems
* 24/01/2023
*
* @author Roch Blondiaux (Kiwix).
*/
public class PlayerInventoryImage {
private final PlayerData data;
private final List<SlotEquippedItem> equipped;
private final Map<Integer, VolatileMMOItem> cache;
private final Map<Integer, Integer> hashCodes;
private final long timestamp;
public PlayerInventoryImage(@NotNull PlayerData data) {
this.data = data;
this.equipped = new ArrayList<>();
this.hashCodes = new HashMap<>();
this.timestamp = System.currentTimeMillis();
this.cache = new HashMap<>();
}
public @NotNull PlayerData data() {
return data;
}
public @NotNull List<SlotEquippedItem> equipped() {
return equipped;
}
public @NotNull Map<Integer, Integer> hashCodes() {
return hashCodes;
}
public long timestamp() {
return timestamp;
}
public @NotNull Optional<SlotEquippedItem> get(int slot) {
return equipped.stream().filter(e -> e.getSlotNumber() == slot).findFirst();
}
public @NotNull Optional<VolatileMMOItem> getCached(int slot) {
return Optional.ofNullable(cache.get(slot));
}
public void cache() {
this.cache.clear();
this.equipped.stream()
.filter(item -> item.getNBT() != null)
.forEach(e -> this.cache.put(e.getSlotNumber(), new VolatileMMOItem(e.getNBT())));
}
public boolean isDifferent(@NotNull PlayerInventoryImage image) {
return image.equipped.size() != equipped.size() || !hashCodes.equals(image.hashCodes());
}
public List<SlotEquippedItem> difference(@NotNull PlayerInventoryImage image) {
if (!isDifferent(image)) return Collections.emptyList();
return hashCodes.entrySet()
.stream()
.filter(e -> !Objects.equals(e.getValue(), image.hashCodes().get(e.getKey())))
.map(Map.Entry::getKey)
.map(i -> equipped.stream().filter(e -> e.getSlotNumber() == i).findFirst().orElse(null))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public static @NotNull PlayerInventoryImage make(@NotNull PlayerData data) {
PlayerInventoryImage image = new PlayerInventoryImage(data);
MMOItems.plugin.getInventory()
.inventory(data.getPlayer())
.stream()
.filter(Objects::nonNull)
.filter(i -> i instanceof SlotEquippedItem)
.map(i -> (SlotEquippedItem) i)
.forEach(i -> {
image.equipped.add(i);
image.hashCodes.put(i.getSlotNumber(), isEmpty(i) ? -1 : i.hashCode());
});
return image;
}
private static boolean isEmpty(@Nullable SlotEquippedItem item) {
return item == null || item.getItem() == null;
}
@Override
public int hashCode() {
return equipped.hashCode();
}
}

View File

@ -1,13 +1,15 @@
package net.Indyuce.mmoitems.comp.inventory.model;
import io.lumine.mythic.lib.api.player.EquipmentSlot;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.player.inventory.EquippedItem;
import net.Indyuce.mmoitems.comp.inventory.PlayerInventoryUpdater;
import net.Indyuce.mmoitems.comp.inventory.PlayerInventoryHandler;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
/**
* mmoitems
@ -18,25 +20,33 @@ import java.util.*;
public class PlayerMMOInventory {
private final UUID uniqueId;
private final PlayerData data;
private final Map<EquippedItem, Integer> content = new HashMap<>();
private final List<EquippedItem> content = new ArrayList<>();
private final PlayerInventoryHandler handler;
public PlayerMMOInventory(@NotNull PlayerData data) {
this.uniqueId = data.getUniqueId();
this.data = data;
this.updater = new PlayerInventoryUpdater(data);
this.handler = new PlayerInventoryHandler(data, this);
}
public void addItem(@NotNull EquippedItem item) {
content.put(item, item.hashCode());
content.add(item);
}
public void remove(@NotNull EquippedItem item) {
content.remove(item);
}
public void remove(@NotNull EquipmentSlot slot) {
content.keySet().removeIf((item) -> item.getSlot() == slot);
public void remove(int slot) {
content.removeIf(item -> item instanceof SlotEquippedItem && ((SlotEquippedItem) item).getSlotNumber() == slot);
}
public void update() {
handler.start();
}
@Deprecated
@ApiStatus.ScheduledForRemoval
public void scheduleUpdate() {
}
/* Getters */
@ -45,42 +55,6 @@ public class PlayerMMOInventory {
}
public List<EquippedItem> equipped() {
return Collections.unmodifiableList(new ArrayList<>(content.keySet()));
}
public List<Integer> hashCodes() {
return Collections.unmodifiableList(new ArrayList<>(content.values()));
}
public int hashCode(EquipmentSlot slot) {
return content.entrySet()
.stream()
.filter(entry -> entry.getKey().getSlot() == slot)
.map(Map.Entry::getValue)
.findFirst()
.orElse(0);
}
public Map<EquippedItem, Integer> content() {
return content;
}
@Override
public int hashCode() {
return Objects.hash(uniqueId, content.keySet());
}
// Old weird code
@Deprecated
public void scheduleUpdate() {
MMOItems.log("PlayerMMOInventory#scheduleUpdate called!");
}
private final PlayerInventoryUpdater updater;
@Deprecated
public void updateCheck(){
this.updater.run();
// MMOItems.log("PlayerMMOInventory#updateCheck called!");
return Collections.unmodifiableList(content);
}
}

View File

@ -8,7 +8,7 @@ import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SlotEquippedItem extends EquippedItem {
public class SlotEquippedItem extends EquippedItem implements Cloneable {
private final Player player;
private final int slotNumber;
@ -90,4 +90,13 @@ public class SlotEquippedItem extends EquippedItem {
accumulator ^= getItem().hashCode();
return accumulator;
}
@Override
public SlotEquippedItem clone() {
try {
return (SlotEquippedItem) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}

View File

@ -0,0 +1,63 @@
package net.Indyuce.mmoitems.listener;
import io.lumine.mythic.lib.api.event.armorequip.ArmorEquipEvent;
import net.Indyuce.mmoitems.api.player.PlayerData;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryMoveItemEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
import org.jetbrains.annotations.NotNull;
/**
* mmoitems
* 24/01/2023
*
* @author Roch Blondiaux (Kiwix).
*/
public class InventoryListener implements Listener {
@EventHandler(priority = EventPriority.MONITOR)
public void onItemDrop(PlayerDropItemEvent e) {
if (!e.isCancelled())
triggerUpdate(e.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onHandSwap(PlayerSwapHandItemsEvent e) {
if (!e.isCancelled())
triggerUpdate(e.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onSlotChange(PlayerItemHeldEvent e) {
if (!e.isCancelled())
triggerUpdate(e.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onItemDrag(InventoryDragEvent e) {
if (!e.isCancelled())
triggerUpdate((Player) e.getView().getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onItemMove(InventoryMoveItemEvent e) {
if (!e.isCancelled() && e.getDestination().getHolder() instanceof Player)
triggerUpdate((Player) e.getDestination().getHolder());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onArmorEquip(ArmorEquipEvent e) {
if (!e.isCancelled())
triggerUpdate(e.getPlayer());
}
private void triggerUpdate(@NotNull Player player) {
PlayerData.get(player).getInventory().update();
}
}