diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/IEnchantment.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/IEnchantment.java new file mode 100644 index 0000000..df57cb2 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/IEnchantment.java @@ -0,0 +1,83 @@ +package su.nightexpress.excellentenchants.api.enchantment; + +import org.bukkit.Keyed; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.api.config.JYML; +import su.nightexpress.excellentenchants.enchantment.type.ObtainType; +import su.nightexpress.excellentenchants.tier.Tier; + +import java.util.List; +import java.util.Set; + +public interface IEnchantment extends Keyed { + + boolean isAvailableToUse(@NotNull LivingEntity entity); + + @NotNull JYML getConfig(); + + @NotNull String getId(); + + @NotNull String getDisplayName(); + + @NotNull String getNameFormatted(int level); + + @NotNull String getNameFormatted(int level, int charges); + + @NotNull List getDescription(); + + @NotNull List getDescription(int level); + + @NotNull Set getConflicts(); + + @NotNull Tier getTier(); + + int getMaxLevel(); + + int getStartLevel(); + + int getLevelByEnchantCost(int expLevel); + + double getObtainChance(@NotNull ObtainType obtainType); + + int getObtainLevelMin(@NotNull ObtainType obtainType); + + int getObtainLevelMax(@NotNull ObtainType obtainType); + + int generateLevel(@NotNull ObtainType obtainType); + + int getAnvilMergeCost(int level); + + boolean conflictsWith(@NotNull Enchantment enchantment); + + boolean canEnchantItem(@Nullable ItemStack item); + + boolean isCursed(); + + boolean isTreasure(); + + boolean isChargesEnabled(); + + int getChargesMax(int level); + + int getChargesConsumeAmount(int level); + + int getChargesRechargeAmount(int level); + + @NotNull ItemStack getChargesFuel(); + + boolean isChargesFuel(@NotNull ItemStack item); + + int getCharges(@NotNull ItemStack item); + + boolean isFullOfCharges(@NotNull ItemStack item); + + boolean isOutOfCharges(@NotNull ItemStack item); + + void consumeCharges(@NotNull ItemStack item, int level); + + void consumeChargesNoUpdate(@NotNull ItemStack item, int level); +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/type/FitItemType.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/type/FitItemType.java new file mode 100644 index 0000000..392e94c --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/type/FitItemType.java @@ -0,0 +1,59 @@ +package su.nightexpress.excellentenchants.enchantment.type; + +import org.bukkit.Material; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.utils.ItemUtil; +import su.nightexpress.excellentenchants.config.Config; + +public enum FitItemType { + + HELMET, CHESTPLATE, LEGGINGS, BOOTS, ELYTRA, + WEAPON, TOOL, ARMOR, UNIVERSAL, + SWORD, TRIDENT, AXE, BOW, CROSSBOW, + HOE, PICKAXE, SHOVEL, FISHING_ROD; + + @Nullable + public static FitItemType getByEnchantmentTarget(@NotNull EnchantmentTarget target) { + return switch (target) { + case ARMOR -> ARMOR; + case ARMOR_FEET -> BOOTS; + case ARMOR_LEGS -> LEGGINGS; + case ARMOR_TORSO -> CHESTPLATE; + case ARMOR_HEAD -> HELMET; + case WEAPON -> WEAPON; + case TOOL -> TOOL; + case BOW -> BOW; + case FISHING_ROD -> FISHING_ROD; + case TRIDENT -> TRIDENT; + case CROSSBOW -> CROSSBOW; + case BREAKABLE, WEARABLE -> UNIVERSAL; + default -> null; + }; + } + + public boolean isIncluded(@NotNull ItemStack item) { + return switch (this) { + case UNIVERSAL -> ARMOR.isIncluded(item) || WEAPON.isIncluded(item) || TOOL.isIncluded(item) || BOW.isIncluded(item) || FISHING_ROD.isIncluded(item) || ELYTRA.isIncluded(item); + case HELMET -> ItemUtil.isHelmet(item); + case CHESTPLATE -> ItemUtil.isChestplate(item) || (Config.ENCHANTMENTS_ITEM_CHESTPLATE_ENCHANTS_TO_ELYTRA.get() && ELYTRA.isIncluded(item)); + case LEGGINGS -> ItemUtil.isLeggings(item); + case BOOTS -> ItemUtil.isBoots(item); + case ELYTRA -> item.getType() == Material.ELYTRA; + case WEAPON -> SWORD.isIncluded(item) || ItemUtil.isTrident(item); + case TOOL -> ItemUtil.isTool(item); + case ARMOR -> ItemUtil.isArmor(item); + case SWORD -> ItemUtil.isSword(item) || (Config.ENCHANTMENTS_ITEM_SWORD_ENCHANTS_TO_AXES.get() && AXE.isIncluded(item)); + case TRIDENT -> ItemUtil.isTrident(item); + case AXE -> ItemUtil.isAxe(item); + case BOW -> item.getType() == Material.BOW || (Config.ENCHANTMENTS_ITEM_BOW_ENCHANTS_TO_CROSSBOW.get() && CROSSBOW.isIncluded(item)); + case CROSSBOW -> item.getType() == Material.CROSSBOW; + case HOE -> ItemUtil.isHoe(item); + case PICKAXE -> ItemUtil.isPickaxe(item); + case SHOVEL -> ItemUtil.isShovel(item); + case FISHING_ROD -> item.getType() == Material.FISHING_ROD; + }; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/type/ObtainType.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/type/ObtainType.java new file mode 100644 index 0000000..c4c2ace --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/type/ObtainType.java @@ -0,0 +1,23 @@ +package su.nightexpress.excellentenchants.enchantment.type; + +import org.jetbrains.annotations.NotNull; + +public enum ObtainType { + + ENCHANTING("Enchanting_Table"), + VILLAGER("Villagers"), + LOOT_GENERATION("Loot_Generation"), + FISHING("Fishing"), + MOB_SPAWNING("Mob_Spawning"); + + private final String pathName; + + ObtainType(@NotNull String pathName) { + this.pathName = pathName; + } + + @NotNull + public String getPathName() { + return pathName; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/Evaluator.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/Evaluator.java new file mode 100644 index 0000000..00a4e18 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/Evaluator.java @@ -0,0 +1,94 @@ +package su.nightexpress.excellentenchants.enchantment.util; + +import org.jetbrains.annotations.NotNull; + +public class Evaluator { + + public static double evaluate(@NotNull final String str) { + return new Object() { + int pos = -1, ch; + + void nextChar() { + ch = (++pos < str.length()) ? str.charAt(pos) : -1; + } + + boolean eat(int charToEat) { + while (ch == ' ') nextChar(); + if (ch == charToEat) { + nextChar(); + return true; + } + return false; + } + + double parse() { + nextChar(); + double x = parseExpression(); + if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch); + return x; + } + + // Grammar: + // expression = term | expression `+` term | expression `-` term + // term = factor | term `*` factor | term `/` factor + // factor = `+` factor | `-` factor | `(` expression `)` | number + // | functionName `(` expression `)` | functionName factor + // | factor `^` factor + + double parseExpression() { + double x = parseTerm(); + for (;;) { + if (eat('+')) x += parseTerm(); // addition + else if (eat('-')) x -= parseTerm(); // subtraction + else return x; + } + } + + double parseTerm() { + double x = parseFactor(); + for (;;) { + if (eat('*')) x *= parseFactor(); // multiplication + else if (eat('/')) x /= parseFactor(); // division + else return x; + } + } + + double parseFactor() { + if (eat('+')) return +parseFactor(); // unary plus + if (eat('-')) return -parseFactor(); // unary minus + + double x; + int startPos = this.pos; + if (eat('(')) { // parentheses + x = parseExpression(); + if (!eat(')')) throw new RuntimeException("Missing ')'"); + } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers + while ((ch >= '0' && ch <= '9') || ch == '.') nextChar(); + x = Double.parseDouble(str.substring(startPos, this.pos)); + } else if (ch >= 'a' && ch <= 'z') { // functions + while (ch >= 'a' && ch <= 'z') nextChar(); + String func = str.substring(startPos, this.pos); + if (eat('(')) { + x = parseExpression(); + if (!eat(')')) throw new RuntimeException("Missing ')' after argument to " + func); + } else { + x = parseFactor(); + } + x = switch (func) { + case "sqrt" -> Math.sqrt(x); + case "sin" -> Math.sin(Math.toRadians(x)); + case "cos" -> Math.cos(Math.toRadians(x)); + case "tan" -> Math.tan(Math.toRadians(x)); + default -> throw new RuntimeException("Unknown function: " + func); + }; + } else { + throw new RuntimeException("Unexpected: " + (char)ch); + } + + if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation + + return x; + } + }.parse(); + } +} diff --git a/V1_20_R2/src/main/java/su/nightexpress/excellentenchants/nms/V1_20_R2.java b/V1_20_R2/src/main/java/su/nightexpress/excellentenchants/nms/V1_20_R2.java new file mode 100644 index 0000000..333b2e4 --- /dev/null +++ b/V1_20_R2/src/main/java/su/nightexpress/excellentenchants/nms/V1_20_R2.java @@ -0,0 +1,116 @@ +package su.nightexpress.excellentenchants.nms; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.game.ClientboundAnimatePacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.SpawnEggItem; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlock; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftFishHook; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Set; + +public class V1_20_R2 implements EnchantNMS { + + @Override + public void sendAttackPacket(@NotNull Player player, int id) { + CraftPlayer craftPlayer = (CraftPlayer) player; + ServerPlayer entity = craftPlayer.getHandle(); + ClientboundAnimatePacket packet = new ClientboundAnimatePacket(entity, id); + craftPlayer.getHandle().connection.send(packet); + } + + @Override + public void retrieveHook(@NotNull FishHook hook, @NotNull ItemStack item) { + CraftFishHook craftFishHook = (CraftFishHook) hook; + FishingHook handle = craftFishHook.getHandle(); + handle.retrieve(CraftItemStack.asNMSCopy(item)); + } + + @Override + @Nullable + public ItemStack getSpawnEgg(@NotNull LivingEntity entity) { + CraftLivingEntity craftLivingEntity = (CraftLivingEntity) entity; + net.minecraft.world.entity.LivingEntity livingEntity = craftLivingEntity.getHandle(); + + SpawnEggItem eggItem = SpawnEggItem.byId(livingEntity.getType()); + if (eggItem == null) return null; + + return CraftItemStack.asBukkitCopy(eggItem.getDefaultInstance()); + } + + @Override + @NotNull + public Set handleFlameWalker(@NotNull LivingEntity bukkitEntity, @NotNull Location location, int level) { + Entity entity = ((CraftLivingEntity) bukkitEntity).getHandle(); + BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + ServerLevel world = ((CraftWorld) bukkitEntity.getWorld()).getHandle(); + + int radius = Math.min(16, 2 + level); + BlockState bStone = Blocks.MAGMA_BLOCK.defaultBlockState(); + BlockPos.MutableBlockPos posAbove = new BlockPos.MutableBlockPos(); + + Set blocks = new HashSet<>(); + for (BlockPos posNear : BlockPos.betweenClosed(pos.offset(-radius, -1, -radius), pos.offset(radius, -1, radius))) { + if (!posNear.closerThan(entity.blockPosition(), radius)) continue; + + posAbove.set(posNear.getX(), posNear.getY() + 1, posNear.getZ()); + + BlockState bLavaAbove = world.getBlockState(posAbove); + BlockState bLava = world.getBlockState(posNear); + + if (!bLavaAbove.isAir()) continue; + if (!bLava.getBlock().equals(Blocks.LAVA)) continue; + if (bLava.getValue(LiquidBlock.LEVEL) != 0) continue; + if (!bStone.canSurvive(world, posNear)) continue; + if (!world.isUnobstructed(bStone, posNear, CollisionContext.empty())) continue; + if (!CraftEventFactory.handleBlockFormEvent(world, posNear, bStone, entity)) continue; + //world.scheduleTick(posNear, Blocks.STONE, Rnd.get(60, 120)); + + Location bukkitLoc = new Location(world.getWorld(), posNear.getX(), posNear.getY(), posNear.getZ()); + blocks.add(bukkitLoc.getBlock()); + } + return blocks; + } + + @NotNull + public Item popResource(@NotNull Block block, @NotNull ItemStack item) { + Level world = ((CraftWorld)block.getWorld()).getHandle(); + BlockPos pos = ((CraftBlock)block).getPosition(); + net.minecraft.world.item.ItemStack itemstack = CraftItemStack.asNMSCopy(item); + + float yMod = EntityType.ITEM.getHeight() / 2.0F; + double x = (pos.getX() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + double y = (pos.getY() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D) - yMod; + double z = (pos.getZ() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + + ItemEntity itemEntity = new ItemEntity(world, x, y, z, itemstack); + itemEntity.setDefaultPickUpDelay(); + return (Item) itemEntity.getBukkitEntity(); + } +}