diff --git a/ChestsPlusPlusAPI/src/main/java/com/jamesdpeters/minecraft/chests/CraftingProvider.java b/ChestsPlusPlusAPI/src/main/java/com/jamesdpeters/minecraft/chests/CraftingProvider.java index 78d472f..7f8f662 100644 --- a/ChestsPlusPlusAPI/src/main/java/com/jamesdpeters/minecraft/chests/CraftingProvider.java +++ b/ChestsPlusPlusAPI/src/main/java/com/jamesdpeters/minecraft/chests/CraftingProvider.java @@ -9,5 +9,5 @@ public interface CraftingProvider { CraftingResult craft(Player player, World world, ItemStack[] items); - Recipe getRecipe(Player player, World world, ItemStack[] items); + Recipe getRecipe(World world, ItemStack[] items); } diff --git a/ChestsPlusPlus_1_16/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R1/Crafting.java b/ChestsPlusPlus_1_16/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R1/Crafting.java index 80254b7..12d2552 100644 --- a/ChestsPlusPlus_1_16/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R1/Crafting.java +++ b/ChestsPlusPlus_1_16/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R1/Crafting.java @@ -63,7 +63,7 @@ public class Crafting implements CraftingProvider { } @Override - public Recipe getRecipe(Player player, World world, ItemStack[] items) { + public Recipe getRecipe(World world, ItemStack[] items) { Container container = new Container(null, -1) { @Override public InventoryView getBukkitView() { diff --git a/ChestsPlusPlus_1_16_R2/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R2/Crafting.java b/ChestsPlusPlus_1_16_R2/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R2/Crafting.java index 30fc9e2..3760595 100644 --- a/ChestsPlusPlus_1_16_R2/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R2/Crafting.java +++ b/ChestsPlusPlus_1_16_R2/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R2/Crafting.java @@ -63,7 +63,7 @@ public class Crafting implements CraftingProvider { } @Override - public Recipe getRecipe(Player player, World world, ItemStack[] items) { + public Recipe getRecipe(World world, ItemStack[] items) { Container container = new Container(null, -1) { @Override public InventoryView getBukkitView() { diff --git a/ChestsPlusPlus_1_16_R3/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R3/Crafting.java b/ChestsPlusPlus_1_16_R3/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R3/Crafting.java index a140d1a..de2e86a 100644 --- a/ChestsPlusPlus_1_16_R3/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R3/Crafting.java +++ b/ChestsPlusPlus_1_16_R3/src/main/java/com/jamesdpeters/minecraft/chests/v1_16_R3/Crafting.java @@ -63,7 +63,7 @@ public class Crafting implements CraftingProvider { } @Override - public Recipe getRecipe(Player player, World world, ItemStack[] items) { + public Recipe getRecipe(World world, ItemStack[] items) { Container container = new Container(null, -1) { @Override public InventoryView getBukkitView() { diff --git a/ChestsPlusPlus_LatestMC/src/main/java/com/jamesdpeters/minecraft/chests/latest/Crafting.java b/ChestsPlusPlus_LatestMC/src/main/java/com/jamesdpeters/minecraft/chests/latest/Crafting.java index 26da3db..d3076b7 100644 --- a/ChestsPlusPlus_LatestMC/src/main/java/com/jamesdpeters/minecraft/chests/latest/Crafting.java +++ b/ChestsPlusPlus_LatestMC/src/main/java/com/jamesdpeters/minecraft/chests/latest/Crafting.java @@ -22,7 +22,7 @@ public class Crafting implements CraftingProvider { } @Override - public Recipe getRecipe(Player player, World world, ItemStack[] items) { + public Recipe getRecipe(World world, ItemStack[] items) { return Bukkit.getCraftingRecipe(items, world); } } diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/ChestsPlusPlus.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/ChestsPlusPlus.java index 3ba3001..63aaab6 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/ChestsPlusPlus.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/ChestsPlusPlus.java @@ -17,6 +17,7 @@ import com.jamesdpeters.minecraft.chests.misc.Stats; import com.jamesdpeters.minecraft.chests.misc.Utils; import com.jamesdpeters.minecraft.chests.party.PlayerParty; import com.jamesdpeters.minecraft.chests.party.PlayerPartyStorage; +import com.jamesdpeters.minecraft.chests.players.PlayerStorage; import com.jamesdpeters.minecraft.chests.serialize.Config; import com.jamesdpeters.minecraft.chests.serialize.ConfigStorage; import com.jamesdpeters.minecraft.chests.serialize.LocationInfo; @@ -141,6 +142,7 @@ public class ChestsPlusPlus extends JavaPlugin { getServer().getPluginManager().registerEvents(new InventoryListener(), this); getServer().getPluginManager().registerEvents(new HopperListener(), this); getServer().getPluginManager().registerEvents(new WorldListener(), this); + getServer().getPluginManager().registerEvents(new PlayerStorage(), this); Config.getStorageTypes().forEach(storageType -> getServer().getPluginManager().registerEvents(storageType, this)); getLogger().info("Chests++ enabled!"); }, 1); diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/crafting/Crafting.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/crafting/Crafting.java index efea76d..2202078 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/crafting/Crafting.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/crafting/Crafting.java @@ -32,8 +32,8 @@ public class Crafting { }); } - public static Recipe getRecipe(Player player, ItemStack[] craftingTable) { - return ApiSpecific.getNmsProvider().getCraftingProvider().getRecipe(player, Bukkit.getWorlds().get(0), craftingTable); + public static Recipe getRecipe(ItemStack[] craftingTable) { + return ApiSpecific.getNmsProvider().getCraftingProvider().getRecipe(Bukkit.getWorlds().get(0), craftingTable); } public static CraftingResult craft(Player player, ItemStack[] recipe) { diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/interfaces/VirtualCraftingHolder.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/interfaces/VirtualCraftingHolder.java index 2c71572..cdcd247 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/interfaces/VirtualCraftingHolder.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/interfaces/VirtualCraftingHolder.java @@ -18,6 +18,7 @@ import org.bukkit.block.BlockFace; import org.bukkit.block.Container; import org.bukkit.block.Hopper; import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryEvent; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; @@ -33,6 +34,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; public class VirtualCraftingHolder implements InventoryHolder { @@ -44,6 +46,7 @@ public class VirtualCraftingHolder implements InventoryHolder { private ItemStack[][] recipeChoices = new ItemStack[9][]; private ItemStack result; + private ItemStack[] matrixResult; // stored matrix result after a crafting event private final int[] recipeChoiceIndex = new int[9]; private boolean hasCompleteRecipe = false; @@ -71,6 +74,7 @@ public class VirtualCraftingHolder implements InventoryHolder { public void setCrafting(ShapelessRecipe shapelessRecipe) { result = shapelessRecipe.getResult(); + matrixResult = null; List choiceList = shapelessRecipe.getChoiceList(); for (int i = 0; i < choiceList.size(); i++) { RecipeChoice recipeChoice = choiceList.get(i); @@ -84,6 +88,7 @@ public class VirtualCraftingHolder implements InventoryHolder { public void setCrafting(ShapedRecipe recipe) { result = recipe.getResult(); + matrixResult = null; int row = 0; for (String r : recipe.getShape()) { int col = 0; @@ -101,12 +106,13 @@ public class VirtualCraftingHolder implements InventoryHolder { setHasCompleteRecipe(); } - public void setCrafting(Recipe recipe, ItemStack[] matrix) { + public void setCrafting(Recipe recipe, ItemStack[] matrix, ItemStack[] matrixResult) { if (recipe instanceof ShapedRecipe) setCrafting((ShapedRecipe) recipe); else if (recipe instanceof ShapelessRecipe) setCrafting((ShapelessRecipe) recipe); else { // For ComplexRecipes or other implementations just use the result and original matrix for choices. - result = ApiSpecific.getNmsProvider().getCraftingProvider().craft(storage.getOwner().getPlayer(), Bukkit.getWorlds().get(0), matrix).result(); + result = recipe.getResult(); + this.matrixResult = matrixResult; for (int i = 0; i < matrix.length; i++) { ItemStack item = matrix[i]; if (item != null) { @@ -131,15 +137,18 @@ public class VirtualCraftingHolder implements InventoryHolder { stopCraftingItems(); } - public void updateCrafting() { + public void updateCrafting(InventoryEvent event) { var crafting = Arrays.copyOfRange(inventory.getContents(),1, inventory.getContents().length); - Recipe recipe = Crafting.getRecipe(storage.getOwner().getPlayer(), crafting); - getStorage().setRecipe(recipe, crafting); // Only store the crafting matrix if the recipe is valid + Player player = (Player) event.getView().getPlayer(); + var craftingResult = ApiSpecific.getNmsProvider().getCraftingProvider().craft(player, player.getWorld(), Arrays.copyOf(crafting, crafting.length)); + + Recipe recipe = Crafting.getRecipe(crafting); + getStorage().setRecipe(recipe, crafting, craftingResult.matrixResult()); // Only store the crafting matrix if the recipe is valid resetChoices(); if (recipe != null) { - setCrafting(recipe, crafting); + setCrafting(recipe, crafting, craftingResult.matrixResult()); playSound(Sound.BLOCK_NOTE_BLOCK_CHIME, 0.5f, 1f); } else { stopCraftingItems(); @@ -426,13 +435,16 @@ public class VirtualCraftingHolder implements InventoryHolder { Inventory tempOutput = sameInv ? sameInventory : Utils.copyInventory(output); HashMap map = tempOutput.addItem(craftingResult.result()); - boolean isEmpty = Arrays.stream(craftingResult.matrixResult()) - .anyMatch(itemStack -> (itemStack == null || itemStack.getType() == Material.AIR)); + // Remove nulls from matrix result. + var matrix = Arrays.stream(craftingResult.matrixResult()) + .filter(Objects::nonNull).toList(); + + boolean isEmpty = matrix.stream().allMatch(itemStack -> itemStack.getType() == Material.AIR); // Add any leftover items from the recipe e.g buckets. HashMap craftingMatrixLeftOvers = isEmpty ? Maps.newHashMap() - : tempOutput.addItem(craftingResult.matrixResult()); + : tempOutput.addItem(matrix.toArray(ItemStack[]::new)); //If result fits into output copy over the temporary inventories. if (map.isEmpty() && craftingMatrixLeftOvers.isEmpty()) { diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/listeners/InventoryListener.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/listeners/InventoryListener.java index 5e15593..1ba65cd 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/listeners/InventoryListener.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/listeners/InventoryListener.java @@ -113,10 +113,9 @@ public class InventoryListener implements Listener { } private void craftingUpdate(InventoryInteractEvent event) { - InventoryHolder holder = event.getInventory().getHolder(); - if (holder instanceof VirtualCraftingHolder) { - Bukkit.getScheduler().scheduleSyncDelayedTask(ChestsPlusPlus.PLUGIN, (((VirtualCraftingHolder) holder).setUpdatingRecipe(true))::updateCrafting, 1); - Bukkit.getScheduler().scheduleSyncDelayedTask(ChestsPlusPlus.PLUGIN, (((VirtualCraftingHolder) holder))::forceUpdateInventory, 1); + if (event.getInventory().getHolder() instanceof VirtualCraftingHolder vHolder) { + Bukkit.getScheduler().scheduleSyncDelayedTask(ChestsPlusPlus.PLUGIN, () -> vHolder.setUpdatingRecipe(true).updateCrafting(event), 1); + Bukkit.getScheduler().scheduleSyncDelayedTask(ChestsPlusPlus.PLUGIN, vHolder::forceUpdateInventory, 1); } } diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/misc/OfflinePlayerUtil.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/misc/OfflinePlayerUtil.java new file mode 100644 index 0000000..1b3dea1 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/misc/OfflinePlayerUtil.java @@ -0,0 +1,5 @@ +package com.jamesdpeters.minecraft.chests.misc; + +public class OfflinePlayerUtil { + +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/players/PlayerStorage.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/players/PlayerStorage.java new file mode 100644 index 0000000..4f8f1a3 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/players/PlayerStorage.java @@ -0,0 +1,92 @@ +package com.jamesdpeters.minecraft.chests.players; + +import com.jamesdpeters.minecraft.chests.reflection.craftbukkit.CraftPlayer; +import com.jamesdpeters.minecraft.chests.reflection.craftbukkit.CraftServer; +import com.jamesdpeters.minecraft.chests.reflection.craftbukkit.CraftWorld; +import com.jamesdpeters.minecraft.chests.reflection.minecraft.EntityPlayer; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.PrepareItemCraftEvent; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.event.world.WorldSaveEvent; + +import java.util.HashMap; +import java.util.UUID; +import java.util.function.Consumer; + +public class PlayerStorage implements Listener { + + private static final HashMap offlinePlayers = new HashMap<>(); + + @EventHandler + public void onPlayerJoin(PlayerLoginEvent event) { + if (event.getResult() == PlayerLoginEvent.Result.ALLOWED) { + var player = offlinePlayers.remove(event.getPlayer().getUniqueId()); + if (player != null) { + player.saveData(); + event.getPlayer().loadData(); + } + } + } + + @EventHandler + public void onSave(WorldSaveEvent event) { + offlinePlayers.forEach((uuid, player) -> { + player.saveData(); + }); + } + + @EventHandler + public void onCraft(PrepareItemCraftEvent event) { + Player player = (Player) event.getView().getPlayer(); + player.giveExp(10); + + Bukkit.broadcastMessage("Inventory type: "+player.getOpenInventory().getType()); + } + + public static void runMethodOnOfflinePlayer(OfflinePlayer offlinePlayer, World world, Consumer playerConsumer) { + Player player = getPlayer(offlinePlayer, world); + if (player != null) { + playerConsumer.accept(player); + } + } + + private static Player getPlayer(OfflinePlayer offlinePlayer, World world) { + Player player = offlinePlayer.getPlayer(); + if (player != null) + return player; + + player = offlinePlayers.get(offlinePlayer.getUniqueId()); + if (player != null) + return player; + + try { + player = generateFakePlayer(offlinePlayer, world); + offlinePlayers.put(offlinePlayer.getUniqueId(), player); + return player; + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + return null; + } + + private static Player generateFakePlayer(OfflinePlayer offlinePlayer, World world) throws NoSuchMethodException { + var craftServer = new CraftServer(Bukkit.getServer()); + var minecraftServer = craftServer.getServer(); + var craftWorld = new CraftWorld(world); + var worldServer = craftWorld.getWorldServer(); + + var gameProfile = minecraftServer.getGameProfile(offlinePlayer.getUniqueId(), offlinePlayer.getName()); + var entityPlayer = new EntityPlayer(minecraftServer, worldServer, gameProfile); + minecraftServer.loadEntity(entityPlayer); + + var craftPlayer = new CraftPlayer(craftServer, entityPlayer); + var player = craftPlayer.getOriginalObj(); + player.loadData(); + return player; + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/craftbukkit/CraftPlayer.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/craftbukkit/CraftPlayer.java new file mode 100644 index 0000000..49752a7 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/craftbukkit/CraftPlayer.java @@ -0,0 +1,40 @@ +package com.jamesdpeters.minecraft.chests.reflection.craftbukkit; + +import com.jamesdpeters.minecraft.chests.reflection.helpers.BaseReflection; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectMethod; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectionUtil; +import com.jamesdpeters.minecraft.chests.reflection.minecraft.EntityPlayer; +import org.bukkit.entity.Player; + +import java.lang.reflect.Constructor; +import java.util.Objects; + +public class CraftPlayer extends BaseReflection { + + public static final Class clazz = ReflectionUtil.getCraftBukkitClass("entity.CraftPlayer"); + private static final Constructor constructor = ReflectionUtil.getConstructor(clazz, CraftServer.clazz, EntityPlayer.clazz); + + private static final ReflectMethod getHandle = ReflectionUtil.getMethod("getHandle", clazz); + + static { + assert clazz != null; + assert constructor != null; + } + + public CraftPlayer(CraftServer craftServer, EntityPlayer entityPlayer) throws NoSuchMethodException { + super(clazz, Objects.requireNonNull(constructor), craftServer, entityPlayer); + } + + public CraftPlayer(Player player) { + super(clazz, player); + } + + @Override + public Player getOriginalObj() { + return (Player) getHandle(); + } + + public EntityPlayer getEntityPlayer() { + return new EntityPlayer(getHandle.invoke(getHandle())); + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/craftbukkit/CraftServer.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/craftbukkit/CraftServer.java new file mode 100644 index 0000000..a349a08 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/craftbukkit/CraftServer.java @@ -0,0 +1,27 @@ +package com.jamesdpeters.minecraft.chests.reflection.craftbukkit; + +import com.jamesdpeters.minecraft.chests.reflection.helpers.BaseReflection; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectMethod; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectionUtil; +import com.jamesdpeters.minecraft.chests.reflection.minecraft.MinecraftServer; +import org.bukkit.Server; + +public class CraftServer extends BaseReflection { + + public static final Class clazz = ReflectionUtil.getCraftBukkitClass("CraftServer"); + private static final ReflectMethod getServer = ReflectionUtil.getMethod("getServer", clazz); + + static { + assert clazz != null; + assert getServer != null; + } + + public CraftServer(Server server) { + super(clazz, server); + } + + public MinecraftServer getServer() { + assert getServer != null; + return new MinecraftServer(getServer.invoke(getOriginalObj())); + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/craftbukkit/CraftWorld.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/craftbukkit/CraftWorld.java new file mode 100644 index 0000000..cd7261d --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/craftbukkit/CraftWorld.java @@ -0,0 +1,27 @@ +package com.jamesdpeters.minecraft.chests.reflection.craftbukkit; + +import com.jamesdpeters.minecraft.chests.reflection.helpers.BaseReflection; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectMethod; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectionUtil; +import com.jamesdpeters.minecraft.chests.reflection.minecraft.WorldServer; +import org.bukkit.World; + +public class CraftWorld extends BaseReflection { + + private static final Class clazz = ReflectionUtil.getCraftBukkitClass("CraftWorld"); + + public static final ReflectMethod getHandle = ReflectionUtil.getMethod("getHandle", clazz); + + static { + assert clazz != null; + assert getHandle != null; + } + + public CraftWorld(World world) { + super(clazz, world); + } + + public WorldServer getWorldServer() { + return new WorldServer(this); + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/helpers/BaseReflection.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/helpers/BaseReflection.java new file mode 100644 index 0000000..e8b1932 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/helpers/BaseReflection.java @@ -0,0 +1,42 @@ +package com.jamesdpeters.minecraft.chests.reflection.helpers; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; + +public class BaseReflection { + + protected Object handle; + private final T originalObj; + private final Class clazz; + + public BaseReflection(Class clazz, T from) { + assert clazz.isAssignableFrom(from.getClass()); + this.handle = clazz.cast(from); + this.clazz = clazz; + this.originalObj = from; + } + + public BaseReflection(Class clazz, Constructor constructor, BaseReflection... parameters) throws NoSuchMethodException { + this.clazz = clazz; + this.originalObj = null; + try { + var handles = Arrays.stream(parameters).map(BaseReflection::getHandle).toArray(); + this.handle = constructor.newInstance(handles); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + } + + public T getOriginalObj() { + return originalObj; + } + + public Object getHandle() { + return handle; + } + + public Class getClazz() { + return clazz; + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/helpers/ReflectMethod.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/helpers/ReflectMethod.java new file mode 100644 index 0000000..0092c57 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/helpers/ReflectMethod.java @@ -0,0 +1,22 @@ +package com.jamesdpeters.minecraft.chests.reflection.helpers; + +import java.lang.reflect.Method; + +public class ReflectMethod { + + private final Method method; + + public ReflectMethod(Method method) { + method.setAccessible(true); + this.method = method; + } + + public Object invoke(Object instance, Object... args) { + try { + return method.invoke(instance, args); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/helpers/ReflectionUtil.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/helpers/ReflectionUtil.java new file mode 100644 index 0000000..75653a6 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/helpers/ReflectionUtil.java @@ -0,0 +1,86 @@ +package com.jamesdpeters.minecraft.chests.reflection.helpers; + +import org.bukkit.Bukkit; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; + +public class ReflectionUtil { + + private static final String version; + + static { + String packageName = Bukkit.getServer().getClass().getPackage().getName(); + var tempVersion = packageName.substring(packageName.lastIndexOf(".") + 1); + if (tempVersion.equals("craftbukkit")) + version = ""; + else version = tempVersion; + } + + public static String getNmsPrefix() { + return "net.minecraft.server." + getVersionPrefix() ; + } + + private static String getVersionPrefix() { + return version.isEmpty() ? "" : version + "."; + } + + public static String getCraftBukkitPrefix() { + return "org.bukkit.craftbukkit." +getVersionPrefix(); + } + + public static Class getNmsClass(String name) { + return getClass("net.minecraft.server." + name , false); + } + + public static Class getNbtClass(String name) { + return getClass("net.minecraft.nbt." + name , false); + } + + public static Class getNmsClassAsArray(String name) { + return getClass("net.minecraft.server." + name , true); + } + + public static Class getCraftBukkitClass(String name) { + return getClass("org.bukkit.craftbukkit." + getVersionPrefix() + name , false); + } + + public static Class getCraftBukkitClassAsArray(String name) { + return getClass("org.bukkit.craftbukkit." + getVersionPrefix() + name , true); + } + + public static Class getMojangAuthClass(String name) { + return getClass("com.mojang.authlib." + name , false); + } + + private static Class getClass(String name, boolean asArray) { + try { + if(asArray) return Array.newInstance(Class.forName(name), 0).getClass(); + else return Class.forName(name); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public static ReflectMethod getMethod(String name , Class clazz , Class... parameterClasses) { + try { + return new ReflectMethod(clazz.getDeclaredMethod(name, parameterClasses)); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public static Constructor getConstructor(Class clazz, Class... constructorParameters) { + try { + return clazz.getConstructor(constructorParameters); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + return null; + } + } + + + +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/minecraft/EntityPlayer.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/minecraft/EntityPlayer.java new file mode 100644 index 0000000..daf4360 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/minecraft/EntityPlayer.java @@ -0,0 +1,37 @@ +package com.jamesdpeters.minecraft.chests.reflection.minecraft; + + +import com.jamesdpeters.minecraft.chests.reflection.helpers.BaseReflection; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectMethod; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectionUtil; +import com.jamesdpeters.minecraft.chests.reflection.mojangauth.GameProfile; + +import java.lang.reflect.Constructor; +import java.util.Objects; + +public class EntityPlayer extends BaseReflection { + + public static final Class clazz = ReflectionUtil.getNmsClass("level.EntityPlayer"); + private static final Constructor constructor = ReflectionUtil.getConstructor(clazz, MinecraftServer.clazz, WorldServer.clazz, GameProfile.clazz); + + private static final Class nbtTagCompoundClazz = ReflectionUtil.getNbtClass("NBTTagCompound"); + private static final ReflectMethod loadGameTypes = ReflectionUtil.getMethod("loadGameTypes", clazz, nbtTagCompoundClazz); + + static { + assert clazz != null; + assert constructor != null; + } + + public EntityPlayer(MinecraftServer minecraftServer, WorldServer worldServer, GameProfile gameProfile) throws NoSuchMethodException { + super(clazz, Objects.requireNonNull(constructor), minecraftServer, worldServer, gameProfile); + } + + public EntityPlayer(Object object) { + super(clazz, object); + } + + public void loadGameTypes(Object nbtTagCompound) { + Object tag = nbtTagCompoundClazz.cast(nbtTagCompound); + loadGameTypes.invoke(getHandle(), tag); + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/minecraft/MinecraftServer.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/minecraft/MinecraftServer.java new file mode 100644 index 0000000..5906013 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/minecraft/MinecraftServer.java @@ -0,0 +1,55 @@ +package com.jamesdpeters.minecraft.chests.reflection.minecraft; + + +import com.jamesdpeters.minecraft.chests.reflection.helpers.BaseReflection; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectMethod; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectionUtil; +import com.jamesdpeters.minecraft.chests.reflection.mojangauth.GameProfile; + +import java.util.Optional; +import java.util.UUID; + +public class MinecraftServer extends BaseReflection { + + public static final Class clazz = ReflectionUtil.getNmsClass("MinecraftServer"); + private static final Class userCache = ReflectionUtil.getNmsClass("players.UserCache"); + private static final Class playerList = ReflectionUtil.getNmsClass("players.PlayerList"); + + + private static final ReflectMethod getProfileCache = ReflectionUtil.getMethod("getProfileCache", clazz); + private static final ReflectMethod userCache_get = ReflectionUtil.getMethod("get", userCache, UUID.class); + + private static final ReflectMethod getPlayerList = ReflectionUtil.getMethod("getPlayerList", clazz); + private static final ReflectMethod playerList_loadEntity = ReflectionUtil.getMethod("load", playerList, EntityPlayer.clazz); + private static final ReflectMethod playerList_saveEntity = ReflectionUtil.getMethod("save", playerList, EntityPlayer.clazz); + + static { + assert clazz != null; + assert userCache_get != null; + assert getProfileCache != null; + assert userCache_get != null; + } + + public MinecraftServer(Object dediServer) { + super(clazz, dediServer); + } + + public GameProfile getGameProfile(UUID uuid, String defaultName) { + assert getProfileCache != null; + var profileCache = getProfileCache.invoke(getHandle()); + var opt = userCache_get.invoke(profileCache, uuid); + return GameProfile.fromOptional((Optional) opt, uuid, defaultName); + } + + public void loadEntity(EntityPlayer entityPlayer) { + var playerList = getPlayerList.invoke(getHandle()); + var result = playerList_loadEntity.invoke(playerList, entityPlayer.getHandle()); + entityPlayer.loadGameTypes(result); + } + + public void saveEntity(EntityPlayer entityPlayer) { + var playerList = getPlayerList.invoke(getHandle()); + playerList_saveEntity.invoke(playerList, entityPlayer.getHandle()); + } + +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/minecraft/WorldServer.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/minecraft/WorldServer.java new file mode 100644 index 0000000..0d1f0e8 --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/minecraft/WorldServer.java @@ -0,0 +1,21 @@ +package com.jamesdpeters.minecraft.chests.reflection.minecraft; + +import com.jamesdpeters.minecraft.chests.reflection.craftbukkit.CraftWorld; +import com.jamesdpeters.minecraft.chests.reflection.helpers.BaseReflection; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectionUtil; + +import java.util.Objects; + +public class WorldServer extends BaseReflection { + + public static final Class clazz = ReflectionUtil.getNmsClass("level.WorldServer"); + + static { + assert clazz != null; + } + + public WorldServer(CraftWorld craftWorld) { + super(clazz, Objects.requireNonNull(CraftWorld.getHandle).invoke(craftWorld.getHandle())); + } + +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/mojangauth/GameProfile.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/mojangauth/GameProfile.java new file mode 100644 index 0000000..59aad4a --- /dev/null +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/reflection/mojangauth/GameProfile.java @@ -0,0 +1,31 @@ +package com.jamesdpeters.minecraft.chests.reflection.mojangauth; + +import com.jamesdpeters.minecraft.chests.reflection.helpers.BaseReflection; +import com.jamesdpeters.minecraft.chests.reflection.helpers.ReflectionUtil; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Optional; +import java.util.UUID; + +public class GameProfile extends BaseReflection { + + public static final Class clazz = ReflectionUtil.getMojangAuthClass("GameProfile"); + private static final Constructor constructor = ReflectionUtil.getConstructor(clazz, UUID.class, String.class); + + public GameProfile(Object from) { + super(clazz, from); + } + + public static GameProfile fromOptional(Optional from, UUID defaultUUID, String defaultName) { + return new GameProfile(from.orElseGet(() -> { + try { + constructor.newInstance(defaultName, defaultName); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + return null; + } + return null; + })); + } +} diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/serialize/RecipeSerializable.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/serialize/RecipeSerializable.java index 85864d0..ef8d2c5 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/serialize/RecipeSerializable.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/serialize/RecipeSerializable.java @@ -7,14 +7,12 @@ import org.bukkit.Keyed; import org.bukkit.NamespacedKey; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.SerializableAs; -import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.ShapedRecipe; import org.bukkit.inventory.ShapelessRecipe; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; @SerializableAs("C++Recipe") @@ -25,10 +23,12 @@ public class RecipeSerializable implements ConfigurationSerializable { // Store items used for ComplexRecipes private ItemStack[] items; + private ItemStack[] returnedItems; // Stores and items returned from the recipe. i.e Buckets/bottles etc. - public RecipeSerializable(Recipe recipe, ItemStack[] items) { + public RecipeSerializable(Recipe recipe, ItemStack[] items, ItemStack[] returnedItems) { this.recipe = recipe; this.items = items; + this.returnedItems = returnedItems; if (recipe instanceof Keyed){ namespacedKey = ((Keyed) recipe).getKey(); } @@ -37,18 +37,22 @@ public class RecipeSerializable implements ConfigurationSerializable { public RecipeSerializable(Map map) { Object obj = map.get("items"); if (obj != null) { - //noinspection unchecked items = (ItemStack[]) obj; } + Object retItems = map.get("returnedItems"); + if (retItems != null) { + returnedItems = (ItemStack[]) retItems; + } + //noinspection deprecation namespacedKey = new NamespacedKey((String) map.get("namespace"), (String) map.get("key")); recipe = Crafting.getRecipeByKey(namespacedKey); } - public void updateRecipe(Player player) { + public void updateRecipe() { if (recipe == null) { - recipe = ApiSpecific.getNmsProvider().getCraftingProvider().getRecipe(player, Bukkit.getWorlds().get(0), items); + recipe = ApiSpecific.getNmsProvider().getCraftingProvider().getRecipe(Bukkit.getWorlds().get(0), items); } } @@ -59,6 +63,7 @@ public class RecipeSerializable implements ConfigurationSerializable { map.put("key", namespacedKey.getKey()); if (!(recipe instanceof ShapedRecipe || recipe instanceof ShapelessRecipe)) { map.put("items", items); + map.put("returnedItems", returnedItems); } return map; } @@ -74,4 +79,8 @@ public class RecipeSerializable implements ConfigurationSerializable { public ItemStack[] getItems() { return items; } + + public ItemStack[] getReturnedItems() { + return returnedItems; + } } diff --git a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/autocraft/AutoCraftingStorage.java b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/autocraft/AutoCraftingStorage.java index 9161769..a1a27f7 100644 --- a/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/autocraft/AutoCraftingStorage.java +++ b/ChestsPlusPlus_Main/src/main/java/com/jamesdpeters/minecraft/chests/storage/autocraft/AutoCraftingStorage.java @@ -52,7 +52,7 @@ public class AutoCraftingStorage extends AbstractStorage implements Configuratio recipeSerializable = (RecipeSerializable) map.get("recipe"); //If autocraft doesn't have a recipe in it it will be throw a NPE, this check seems to be working. Also the exception caused data loss in the data file if (recipeSerializable != null) { - recipeSerializable.updateRecipe(getOwner().getPlayer()); + recipeSerializable.updateRecipe(); } identifier = (String) map.get("identifier"); initInventory(); @@ -63,12 +63,12 @@ public class AutoCraftingStorage extends AbstractStorage implements Configuratio return false; } - public void setRecipe(Recipe recipe, ItemStack[] items) { + public void setRecipe(Recipe recipe, ItemStack[] items, ItemStack[] returnedItems) { if (recipe == null) { recipeSerializable = null; return; } - recipeSerializable = new RecipeSerializable(recipe, items); + recipeSerializable = new RecipeSerializable(recipe, items, returnedItems); } @Override @@ -109,7 +109,7 @@ public class AutoCraftingStorage extends AbstractStorage implements Configuratio virtualCraftingHolder.setCrafting((ShapedRecipe) recipe); } else { - virtualCraftingHolder.setCrafting(recipe, recipeSerializable.getItems()); + virtualCraftingHolder.setCrafting(recipe, recipeSerializable.getItems(), recipeSerializable.getReturnedItems()); } } else { virtualCraftingHolder.resetChoices(); diff --git a/ChestsPlusPlus_Main/src/main/resources/lang/en_GB.properties b/ChestsPlusPlus_Main/src/main/resources/lang/en_GB.properties index e862fb2..dc5fbbb 100644 --- a/ChestsPlusPlus_Main/src/main/resources/lang/en_GB.properties +++ b/ChestsPlusPlus_Main/src/main/resources/lang/en_GB.properties @@ -1,4 +1,4 @@ -# Chests++ Language File (Version 2.5.2-BETA-2) +# Chests++ Language File (Version 2.5.2-BETA-3) # NOTE: This file gets replaced when the plugin launches! If you want to make modifications create a copy first! # To create a new language file simply create a copy of this file and rename it to your desired choice for example 'en_US.properties' # It should be located in the 'lang' folder