From 8180ff00cb45132b67f63a6923b642b4728c96bc Mon Sep 17 00:00:00 2001 From: Roch Blonndiaux Date: Mon, 23 Jan 2023 15:53:46 +0100 Subject: [PATCH] Item equip detection, basic stats recalculation & events --- .../MMOInventoryRefreshEvent.java} | 6 +- .../event/inventory/MMOItemEquipEvent.java | 42 ++++ .../mmoitems/api/player/PlayerData.java | 24 ++- .../mmoitems/api/player/PlayerStats.java | 2 +- .../api/player/inventory/EquippedItem.java | 1 - .../inventory/PlayerInventoryUpdater.java | 188 ++++++++++++++++-- .../inventory/model/PlayerMMOInventory.java | 18 ++ .../inventory/model/SlotEquippedItem.java | 27 +++ .../particle/api/ParticleRunnable.java | 34 ++-- 9 files changed, 304 insertions(+), 38 deletions(-) rename MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/event/{RefreshInventoryEvent.java => inventory/MMOInventoryRefreshEvent.java} (80%) create mode 100644 MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/event/inventory/MMOItemEquipEvent.java diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/event/RefreshInventoryEvent.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/event/inventory/MMOInventoryRefreshEvent.java similarity index 80% rename from MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/event/RefreshInventoryEvent.java rename to MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/event/inventory/MMOInventoryRefreshEvent.java index 054a0a11..f4d531f2 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/event/RefreshInventoryEvent.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/event/inventory/MMOInventoryRefreshEvent.java @@ -1,4 +1,4 @@ -package net.Indyuce.mmoitems.api.event; +package net.Indyuce.mmoitems.api.event.inventory; import net.Indyuce.mmoitems.api.player.PlayerData; import net.Indyuce.mmoitems.api.player.inventory.EquippedItem; @@ -9,7 +9,7 @@ import org.jetbrains.annotations.NotNull; import java.util.List; -public class RefreshInventoryEvent extends Event { +public class MMOInventoryRefreshEvent extends Event { @NotNull final List itemsToEquip; @NotNull public List getItemsToEquip() { return itemsToEquip; } @@ -27,7 +27,7 @@ public class RefreshInventoryEvent extends Event { @NotNull final Player player; @NotNull final PlayerData playerData; - public RefreshInventoryEvent(@NotNull List itemsToEquip, @NotNull Player player, @NotNull PlayerData playerData) { + public MMOInventoryRefreshEvent(@NotNull List itemsToEquip, @NotNull Player player, @NotNull PlayerData playerData) { this.itemsToEquip = itemsToEquip; this.player = player; this.playerData = playerData; diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/event/inventory/MMOItemEquipEvent.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/event/inventory/MMOItemEquipEvent.java new file mode 100644 index 00000000..ba327e68 --- /dev/null +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/event/inventory/MMOItemEquipEvent.java @@ -0,0 +1,42 @@ +package net.Indyuce.mmoitems.api.event.inventory; + +import net.Indyuce.mmoitems.api.player.inventory.EquippedItem; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class MMOItemEquipEvent extends Event { + + private final int oldHashcode; + private final int newHashcode; + private final @Nullable EquippedItem oldItem; + private final @Nullable EquippedItem newItem; + + public MMOItemEquipEvent(int oldHashcode, int newHashcode, @Nullable EquippedItem oldItem, @Nullable EquippedItem newItem) { + this.oldHashcode = oldHashcode; + this.newHashcode = newHashcode; + this.oldItem = oldItem; + this.newItem = newItem; + } + + public int getOldHashcode() { + return oldHashcode; + } + + public int getNewHashcode() { + return newHashcode; + } + + public @Nullable EquippedItem getOldItem() { + return oldItem; + } + + public @Nullable EquippedItem getNewItem() { + return newItem; + } + + @NotNull static final HandlerList handlers = new HandlerList(); + @NotNull public HandlerList getHandlers() { return handlers; } + @NotNull public static HandlerList getHandlerList() { return handlers; } +} diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java index 628f7a40..bc694322 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerData.java @@ -15,7 +15,7 @@ import net.Indyuce.mmoitems.api.ConfigFile; import net.Indyuce.mmoitems.api.ItemSet; import net.Indyuce.mmoitems.api.ItemSet.SetBonuses; import net.Indyuce.mmoitems.api.crafting.CraftingStatus; -import net.Indyuce.mmoitems.api.event.RefreshInventoryEvent; +import net.Indyuce.mmoitems.api.event.inventory.MMOInventoryRefreshEvent; import net.Indyuce.mmoitems.api.interaction.Tool; import net.Indyuce.mmoitems.api.item.ItemReference; import net.Indyuce.mmoitems.api.item.mmoitem.VolatileMMOItem; @@ -212,7 +212,7 @@ public class PlayerData { } // Call Bukkit event - Bukkit.getPluginManager().callEvent(new RefreshInventoryEvent(inventory.equipped(), getPlayer(), this)); + Bukkit.getPluginManager().callEvent(new MMOInventoryRefreshEvent(inventory.equipped(), getPlayer(), this)); for (EquippedItem equipped : inventory.equipped()) { final VolatileMMOItem item = equipped.getCached(); @@ -355,6 +355,10 @@ public class PlayerData { return permanentEffects.values(); } + public Map getPermanentPotionEffectsMap() { + return permanentEffects; + } + public PlayerStats getStats() { return stats; } @@ -406,6 +410,22 @@ public class PlayerData { return mmoData.getCooldownMap().getInfo(ref).getRemaining() / 1000d; } + public @NotNull Set getItemParticles() { + return itemParticles; + } + + public @Nullable ParticleRunnable getOverridingItemParticles() { + return overridingItemParticles; + } + + public void resetOverridingItemParticles() { + overridingItemParticles = null; + } + + public @NotNull Set permissions() { + return permissions; + } + @NotNull public static PlayerData get(@NotNull OfflinePlayer player) { return get(player.getUniqueId()); diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerStats.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerStats.java index 9ba0c830..3ac7d5db 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerStats.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/PlayerStats.java @@ -64,7 +64,7 @@ public class PlayerStats { // The index of the mmoitem stat modifier being added int index = 0; - for (EquippedItem item : playerData.getInventory().getEquipped()) { + for (EquippedItem item : playerData.getInventory().equipped()) { double value = item.getNBT().getStat(stat.getId()); if (value != 0) { diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/inventory/EquippedItem.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/inventory/EquippedItem.java index 4e5f6a8d..2497cbd2 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/inventory/EquippedItem.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/api/player/inventory/EquippedItem.java @@ -64,7 +64,6 @@ public abstract class EquippedItem { * @return If item placement is legal */ public boolean isPlacementLegal() { - // Vanilla items are ignored final @Nullable String typeFormat = item.getString("MMOITEMS_ITEM_TYPE"); if (typeFormat == null) diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/PlayerInventoryUpdater.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/PlayerInventoryUpdater.java index c2022f98..da9ef645 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/PlayerInventoryUpdater.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/PlayerInventoryUpdater.java @@ -1,14 +1,24 @@ 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.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.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -17,36 +27,182 @@ import java.util.Map; * * @author Roch Blondiaux (Kiwix). */ -public class PlayerInventoryUpdater extends BukkitRunnable { +public class PlayerInventoryUpdater implements Runnable { private final PlayerData data; - private final Map lastHashCodes = new HashMap<>(); + private final Map lastHashCodes = new HashMap<>(); + private final Map CACHE = new HashMap<>(); + + private boolean running; public PlayerInventoryUpdater(PlayerData data) { this.data = data; + this.running = true; } @Override public void run() { - // Current equipment - MMOItems.plugin.getInventory().inventory(data.getPlayer()) + if (!this.running) + return; + if (!this.data.isOnline()) { + this.stop(); + return; + } + MMOItems.plugin.getInventory() + .inventory(data.getPlayer()) .stream() + .filter(item -> item instanceof SlotEquippedItem) + .map(item -> (SlotEquippedItem) item) .filter(this::needsUpdate) - .forEach(equippedItem -> { - Bukkit.broadcastMessage("Updating " + equippedItem.getNBT().getItem().getType()); + .forEach(eItem -> { + final int currentHashcode = lastHashCodes.getOrDefault(eItem.getSlotNumber(), -99); + final int newHashcode = isEmpty(eItem) ? -1 : eItem.hashCode(); + final SlotEquippedItem oldItem = CACHE.get(currentHashcode); + + // Call item equip event + Bukkit.getPluginManager().callEvent(new MMOItemEquipEvent(currentHashcode, newHashcode, oldItem, eItem)); + + // Remove all old item attributes & effects + if (oldItem != null && !isEmpty(oldItem)) { + final MMOItem mmoItem = oldItem.getCached().clone(); + + // 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())); + + // 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(); + } + } else { + this.data.getItemParticles() + .stream() + .filter(particleRunnable -> particleRunnable.getParticleData().equals(particleData)) + .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 permissions = new ArrayList<>(((StringListData) mmoItem.getData(ItemStats.GRANTED_PERMISSIONS)).getList()); + permissions.forEach(s -> { + this.data.permissions().remove(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)) { +// ModifierSource modSource = oldItem.getCached().getType().getModifierSource(); +// ((AbilityListData) mmoItem.getData(ItemStats.ABILITIES)) +// .getAbilities() +// .forEach(abilityData -> this.data.getMMOPlayerData() +// .getPassiveSkillMap() +// .getModifiers() +// .removeIf(passiveSkill -> passiveSkill.getSource().equals(modSource) +// && passiveSkill.getType().equals(abilityData.getTrigger()))); +// } + } + + // Check if the new item is empty + if (isEmpty(eItem)) + 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 new item hashcode & item + cache(eItem); + + // Add item to MMO inventory + this.data.getInventory().addItem(eItem); + + // Add all new item attributes & effects + final MMOItem mmoItem = eItem.getCached().clone(); + final EquipmentSlot equipmentSlot = eItem.getSlot(); + + // Abilities + if (mmoItem.hasData(ItemStats.ABILITIES)) { + for (AbilityData abilityData : ((AbilityListData) mmoItem.getData(ItemStats.ABILITIES)).getAbilities()) { + ModifierSource modSource = eItem.getCached().getType().getModifierSource(); + this.data.getMMOPlayerData().getPassiveSkillMap().addModifier(new PassiveSkill("MMOItemsItem", abilityData, equipmentSlot, modSource)); + } + } + + // Modifier application rules + final ModifierSource source = mmoItem.getType().getModifierSource(); + if (!EquipmentSlot.MAIN_HAND.isCompatible(source, equipmentSlot)) + return; + + // Apply permanent potion effects + if (mmoItem.hasData(ItemStats.PERM_EFFECTS)) + ((PotionEffectListData) mmoItem.getData(ItemStats.PERM_EFFECTS)) + .getEffects() + .stream() + .filter(potionEffectData -> this.data.getPermanentPotionEffectAmplifier(potionEffectData.getType()) < potionEffectData.getLevel() - 1) + .forEach(effect -> this.data.getPermanentPotionEffectsMap().put(effect.getType(), effect.toEffect())); + + if (MMOItems.plugin.hasPermissions() && mmoItem.hasData(ItemStats.GRANTED_PERMISSIONS)) { + 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)) + .forEach(perm -> perms.playerAdd(this.data.getPlayer(), perm)); + } + + + + }); + + // TODO: Call inventory refresh event + // Bukkit.getPluginManager().callEvent(new MMOInventoryRefreshEvent(inventory.equipped(), getPlayer(), this)); } - private boolean needsUpdate(@Nullable EquippedItem item) { - if (isEmpty(item)) - return true; - int hash = item.getNBT().hashCode(); - boolean result = lastHashCodes.getOrDefault(item.getSlot(), 0) != hash; - lastHashCodes.put(item.getSlot(), hash); - return result; + private boolean needsUpdate(@NotNull SlotEquippedItem item) { + return !lastHashCodes.containsKey(item.getSlotNumber()) || lastHashCodes.get(item.getSlotNumber()) != (isEmpty(item) ? -1 : item.hashCode()); } - private boolean isEmpty(@Nullable EquippedItem item) { - return item == null || item.getNBT().getItem().getType().isAir(); + private void cache(@NotNull SlotEquippedItem item) { + final int hashCode = isEmpty(item) ? -1 : item.hashCode(); + lastHashCodes.put(item.getSlotNumber(), hashCode); + CACHE.put(hashCode, item); + item.cacheItem(); + } + + 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; } } diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/model/PlayerMMOInventory.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/model/PlayerMMOInventory.java index 8062f1ce..02f5e54c 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/model/PlayerMMOInventory.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/model/PlayerMMOInventory.java @@ -1,8 +1,10 @@ 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 org.jetbrains.annotations.NotNull; import java.util.*; @@ -16,10 +18,13 @@ import java.util.*; public class PlayerMMOInventory { private final UUID uniqueId; + private final PlayerData data; private final Map content = new HashMap<>(); public PlayerMMOInventory(@NotNull PlayerData data) { this.uniqueId = data.getUniqueId(); + this.data = data; + this.updater = new PlayerInventoryUpdater(data); } public void addItem(@NotNull EquippedItem item) { @@ -65,4 +70,17 @@ public class PlayerMMOInventory { 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!"); + } } diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/model/SlotEquippedItem.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/model/SlotEquippedItem.java index 2f0c7c4a..6c544e0d 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/model/SlotEquippedItem.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/comp/inventory/model/SlotEquippedItem.java @@ -63,4 +63,31 @@ public class SlotEquippedItem extends EquippedItem { break; } } + + public ItemStack getItem() { + switch (getSlotNumber()) { + case -106: + return getPlayer().getInventory().getItemInOffHand(); + case -7: + return getPlayer().getInventory().getItemInMainHand(); + case 103: + return getPlayer().getInventory().getHelmet(); + case 102: + return getPlayer().getInventory().getChestplate(); + case 101: + return getPlayer().getInventory().getLeggings(); + case 100: + return getPlayer().getInventory().getBoots(); + default: + return getPlayer().getInventory().getItem(getSlotNumber()); + } + } + + @Override + public int hashCode() { + int accumulator = 0; + accumulator ^= getSlot().hashCode(); + accumulator ^= getItem().hashCode(); + return accumulator; + } } diff --git a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/particle/api/ParticleRunnable.java b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/particle/api/ParticleRunnable.java index 133d777a..3676474d 100644 --- a/MMOItems-API/src/main/java/net/Indyuce/mmoitems/particle/api/ParticleRunnable.java +++ b/MMOItems-API/src/main/java/net/Indyuce/mmoitems/particle/api/ParticleRunnable.java @@ -1,24 +1,28 @@ package net.Indyuce.mmoitems.particle.api; -import org.bukkit.scheduler.BukkitRunnable; - import net.Indyuce.mmoitems.api.player.PlayerData; import net.Indyuce.mmoitems.stat.data.ParticleData; +import org.bukkit.scheduler.BukkitRunnable; public abstract class ParticleRunnable extends BukkitRunnable { - protected final ParticleData particle; - protected final PlayerData player; - public ParticleRunnable(ParticleData particle, PlayerData player) { - this.particle = particle; - this.player = player; - } + protected final ParticleData particle; + protected final PlayerData player; - @Override - public void run() { - if(!player.isOnline()) return; - createParticles(); - } - - public abstract void createParticles(); + public ParticleRunnable(ParticleData particle, PlayerData player) { + this.particle = particle; + this.player = player; + } + + @Override + public void run() { + if (!player.isOnline()) return; + createParticles(); + } + + public abstract void createParticles(); + + public ParticleData getParticleData() { + return particle; + } }