Item equip detection, basic stats recalculation & events

This commit is contained in:
Roch Blonndiaux 2023-01-23 15:53:46 +01:00
parent 4f5192975d
commit 8180ff00cb
9 changed files with 304 additions and 38 deletions

View File

@ -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<EquippedItem> itemsToEquip;
@NotNull public List<EquippedItem> getItemsToEquip() { return itemsToEquip; }
@ -27,7 +27,7 @@ public class RefreshInventoryEvent extends Event {
@NotNull final Player player;
@NotNull final PlayerData playerData;
public RefreshInventoryEvent(@NotNull List<EquippedItem> itemsToEquip, @NotNull Player player, @NotNull PlayerData playerData) {
public MMOInventoryRefreshEvent(@NotNull List<EquippedItem> itemsToEquip, @NotNull Player player, @NotNull PlayerData playerData) {
this.itemsToEquip = itemsToEquip;
this.player = player;
this.playerData = playerData;

View File

@ -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; }
}

View File

@ -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<PotionEffectType, PotionEffect> 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<ParticleRunnable> getItemParticles() {
return itemParticles;
}
public @Nullable ParticleRunnable getOverridingItemParticles() {
return overridingItemParticles;
}
public void resetOverridingItemParticles() {
overridingItemParticles = null;
}
public @NotNull Set<String> permissions() {
return permissions;
}
@NotNull
public static PlayerData get(@NotNull OfflinePlayer player) {
return get(player.getUniqueId());

View File

@ -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) {

View File

@ -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)

View File

@ -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<EquipmentSlot, Integer> lastHashCodes = new HashMap<>();
private final Map<Integer, Integer> lastHashCodes = new HashMap<>();
private final Map<Integer, SlotEquippedItem> 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<String> 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;
}
}

View File

@ -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<EquippedItem, Integer> 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!");
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}