diff --git a/.github/workflows/discord-webhook.yml b/.github/workflows/discord-webhook.yml index 14c37fc1ef..a34070de5d 100644 --- a/.github/workflows/discord-webhook.yml +++ b/.github/workflows/discord-webhook.yml @@ -21,10 +21,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3.6.0 + uses: actions/checkout@v4.1.1 - name: Set up Java JDK 17 - uses: actions/setup-java@v3.12.0 + uses: actions/setup-java@v3.13.0 with: distribution: 'adopt' java-version: '17' diff --git a/.github/workflows/e2e-testing.yml b/.github/workflows/e2e-testing.yml new file mode 100644 index 0000000000..1033765601 --- /dev/null +++ b/.github/workflows/e2e-testing.yml @@ -0,0 +1,71 @@ +name: End to End Testing + +on: + workflow_call: + inputs: + artifact-name: + description: 'Slimefun artifact name' + required: true + type: string + +jobs: + e2e-testing: + name: End to End Testing + runs-on: ubuntu-latest + timeout-minutes: 5 + + strategy: + matrix: + include: + - mcVersion: '1.16.5' + javaVersion: '16' + - mcVersion: '1.17.1' + javaVersion: '17' + - mcVersion: '1.18.2' + javaVersion: '18' + - mcVersion: '1.19.4' + javaVersion: '19' + - mcVersion: '1.20.1' + javaVersion: '20' + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up JDK + uses: actions/setup-java@v3.11.0 + with: + distribution: temurin + java-version: ${{ matrix.javaVersion }} + java-package: jdk + architecture: x64 + + - name: Setup server + run: | + echo 'eula=true' > eula.txt + mkdir plugins + + - name: Download ${{ matrix.mcVersion }} Paper + run: | + VERSION="${{ matrix.mcVersion }}" + BUILD_JAR=$(curl -s "https://api.papermc.io/v2/projects/paper/versions/$VERSION/builds" \ + | jq '.builds[-1] | "\(.build) \(.downloads.application.name)"' -r) + BUILD=$(echo "$BUILD_JAR" | awk '{print $1}') + JAR_FILE=$(echo "$BUILD_JAR" | awk '{print $2}') + curl -o paper.jar \ + "https://api.papermc.io/v2/projects/paper/versions/$VERSION/builds/$BUILD/downloads/$JAR_FILE" + + - name: Download Slimefun + uses: actions/download-artifact@v3 + with: + name: ${{ inputs.artifact-name }} + path: plugins/ + + - name: Download e2e-tester + run: | + curl -o e2e-tester.jar https://preview-builds.walshy.dev/download/e2e-tester/main/latest + mv e2e-tester.jar plugins/e2e-tester.jar + + - name: Run server + run: | + java -jar paper.jar --nogui diff --git a/.github/workflows/json-validator.yml b/.github/workflows/json-validator.yml index 9d8364ceda..512a484828 100644 --- a/.github/workflows/json-validator.yml +++ b/.github/workflows/json-validator.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Validate wiki.json uses: docker://orrosenblatt/validate-json-action:latest@sha256:02370758b8b199e0477da11ecfdd498c75c561685056b5c31b925a4ab95df7f4 env: diff --git a/.github/workflows/maven-compiler.yml b/.github/workflows/maven-compiler.yml index ab13b31e5c..4145068aa7 100644 --- a/.github/workflows/maven-compiler.yml +++ b/.github/workflows/maven-compiler.yml @@ -25,10 +25,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3.12.0 + uses: actions/setup-java@v3.13.0 with: distribution: 'adopt' java-version: '17' diff --git a/.github/workflows/pr-labels.yml b/.github/workflows/pr-labels.yml index 3b8f8f3ebc..0636851f90 100644 --- a/.github/workflows/pr-labels.yml +++ b/.github/workflows/pr-labels.yml @@ -31,7 +31,7 @@ jobs: api: '🔧 API' compatibility: '🤝 Compatibility' - - uses: thollander/actions-comment-pull-request@v2.4.2 + - uses: thollander/actions-comment-pull-request@v2.4.3 name: Leave a comment about the applied label if: ${{ steps.labeller.outputs.applied != 0 }} with: @@ -40,7 +40,7 @@ jobs: Your Pull Request was automatically labelled as: "${{ steps.labeller.outputs.applied }}" Thank you for contributing to this project! ❤️ - - uses: thollander/actions-comment-pull-request@v2.4.2 + - uses: thollander/actions-comment-pull-request@v2.4.3 name: Leave a comment about our branch naming convention if: ${{ steps.labeller.outputs.applied == 0 }} with: diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index cb76b7412d..b564ba9e26 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -14,13 +14,15 @@ jobs: setup-preview-build: name: Preview build runs-on: ubuntu-latest + outputs: + short-commit-hash: ${{ steps.env-setup.outputs.SHORT_COMMIT_HASH }} steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK 17 - uses: actions/setup-java@v3.12.0 + uses: actions/setup-java@v3.13.0 with: distribution: 'adopt' java-version: '17' @@ -35,10 +37,12 @@ jobs: restore-keys: ${{ runner.os }}-m2 # Setup for the preview build - - run: | + - id: env-setup + run: | SHORT_COMMIT_HASH=$(git rev-parse --short=8 ${{ github.sha }}) JAR_VERSION="Preview Build #${{ github.event.number }}-$SHORT_COMMIT_HASH" echo "SHORT_COMMIT_HASH=$SHORT_COMMIT_HASH" >> "$GITHUB_ENV" + echo "SHORT_COMMIT_HASH=$SHORT_COMMIT_HASH" >> "$GITHUB_OUTPUT" echo "JAR_VERSION=$JAR_VERSION" >> "$GITHUB_ENV" sed -i "s/4.9-UNOFFICIAL<\/version>/$JAR_VERSION<\/version>/g" pom.xml @@ -50,3 +54,9 @@ jobs: with: name: slimefun-${{ github.event.number }}-${{ env.SHORT_COMMIT_HASH }} path: 'target/Slimefun v${{ env.JAR_VERSION }}.jar' + + call-workflows: + needs: [setup-preview-build] + uses: ./.github/workflows/e2e-testing.yml + with: + artifact-name: slimefun-${{ github.event.number }}-${{ needs.setup-preview-build.outputs.short-commit-hash }} diff --git a/.github/workflows/release-candidates.yml b/.github/workflows/release-candidates.yml index 1c279e172e..1f4bda442e 100644 --- a/.github/workflows/release-candidates.yml +++ b/.github/workflows/release-candidates.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: 'stable' diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 8128ed2ef2..06abb9b2bf 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -19,12 +19,12 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3.6.0 + uses: actions/checkout@v4.1.1 with: fetch-depth: 0 - name: Set up JDK 17 - uses: actions/setup-java@v3.12.0 + uses: actions/setup-java@v3.13.0 with: distribution: 'adopt' java-version: '17' diff --git a/.github/workflows/yaml-linter.yml b/.github/workflows/yaml-linter.yml index 3d87edf772..a1e22c99c5 100644 --- a/.github/workflows/yaml-linter.yml +++ b/.github/workflows/yaml-linter.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: YAML Linter uses: ibiqlik/action-yamllint@v3.1.1 with: diff --git a/pom.xml b/pom.xml index c800476b1a..f0a1ce2d33 100644 --- a/pom.xml +++ b/pom.xml @@ -146,7 +146,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.1.2 + 3.2.1 org.junit.jupiter:junit-jupiter @@ -158,14 +158,14 @@ org.sonarsource.scanner.maven sonar-maven-plugin - 3.9.1.2184 + 3.10.0.2594 org.jacoco jacoco-maven-plugin - 0.8.10 + 0.8.11 @@ -191,7 +191,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.0 + 3.5.1 @@ -239,7 +239,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.5.0 + 3.6.0 ${project.basedir} @@ -355,7 +355,7 @@ com.github.baked-libs.dough dough-api - d3b0997226 + 99381b2 compile @@ -389,7 +389,7 @@ org.mockito mockito-core - 5.5.0 + 5.6.0 test @@ -418,7 +418,7 @@ com.sk89q.worldedit worldedit-core - 7.2.15 + 7.2.17 provided @@ -432,7 +432,7 @@ com.sk89q.worldedit worldedit-bukkit - 7.2.15 + 7.2.17 provided @@ -446,7 +446,7 @@ com.gmail.nossr50.mcMMO mcMMO - 2.1.223 + 2.1.225 provided @@ -460,7 +460,7 @@ me.clip placeholderapi - 2.11.3 + 2.11.5 provided @@ -488,7 +488,7 @@ com.github.LoneDev6 itemsadder-api - 3.5.0b + 3.6.1 provided diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/api/events/MultiBlockCraftEvent.java b/src/main/java/io/github/thebusybiscuit/slimefun4/api/events/MultiBlockCraftEvent.java new file mode 100644 index 0000000000..7a3fc3c535 --- /dev/null +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/api/events/MultiBlockCraftEvent.java @@ -0,0 +1,120 @@ +package io.github.thebusybiscuit.slimefun4.api.events; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; + +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; + +import io.github.thebusybiscuit.slimefun4.core.multiblocks.MultiBlockMachine; + +/** + * This {@link Event} is called when a {@link Player} crafts an item using a {@link MultiBlockMachine}. + * Unlike the {@link MultiBlockInteractEvent}, this event only fires if an output to a craft is expected. + * If this event is cancelled, ingredients will not be consumed and no output item results. + * + * @author char321 + * @author JustAHuman + */ +public class MultiBlockCraftEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private final MultiBlockMachine machine; + private final ItemStack[] input; + private ItemStack output; + private boolean cancelled; + + /** + * Creates a new {@link MultiBlockCraftEvent}. + * + * @param p The player that crafts using a multiblock + * @param machine The multiblock machine used to craft + * @param input The input items of the craft + * @param output The resulting item of the craft + */ + @ParametersAreNonnullByDefault + public MultiBlockCraftEvent(Player p, MultiBlockMachine machine, ItemStack[] input, ItemStack output) { + super(p); + this.machine = machine; + this.input = input; + this.output = output; + } + + /** + * Creates a new {@link MultiBlockCraftEvent}. + * + * @param p The player that crafts using a multiblock + * @param machine The multiblock machine used to craft + * @param input The input item of the craft + * @param output The resulting item of the craft + */ + @ParametersAreNonnullByDefault + public MultiBlockCraftEvent(Player p, MultiBlockMachine machine, ItemStack input, ItemStack output) { + this(p, machine, new ItemStack[]{input}, output); + } + + /** + * Gets the machine that was used to craft. + * + * @return The {@link MultiBlockMachine} used to craft. + */ + public @Nonnull MultiBlockMachine getMachine() { + return machine; + } + + /** + * Gets the input of the craft. + * + * @return The {@link ItemStack ItemStack[]} input that is used in the craft. + */ + public @Nonnull ItemStack[] getInput() { + return input; + } + + /** + * Gets the output of the craft. + * + * @return The {@link ItemStack} output that results from the craft. + */ + public @Nonnull ItemStack getOutput() { + return output; + } + + /** + * Sets the output of the craft. Keep in mind that this overwrites any existing output. + * + * @param output + * The new item for the event to produce. + * + * @return The previous {@link ItemStack} output that was replaced. + */ + public @Nullable ItemStack setOutput(@Nullable ItemStack output) { + ItemStack oldOutput = this.output; + this.output = output; + return oldOutput; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + public static @Nonnull HandlerList getHandlerList() { + return handlers; + } + + @Override + public @Nonnull HandlerList getHandlers() { + return getHandlerList(); + } +} diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/api/geo/ResourceManager.java b/src/main/java/io/github/thebusybiscuit/slimefun4/api/geo/ResourceManager.java index 3e93255b72..ce4fea9b02 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/api/geo/ResourceManager.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/api/geo/ResourceManager.java @@ -237,7 +237,7 @@ public void scan(@Nonnull Player p, @Nonnull Block block, int page) { for (int i = page * 28; i < resources.size() && i < (page + 1) * 28; i++) { GEOResource resource = resources.get(i); OptionalInt optional = getSupplies(resource, block.getWorld(), x, z); - int supplies = optional.orElse(generate(resource, block.getWorld(), x, block.getY(), z)); + int supplies = optional.orElseGet(() -> generate(resource, block.getWorld(), x, block.getY(), z)); String suffix = Slimefun.getLocalization().getResourceString(p, ChatUtils.checkPlurality("tooltips.unit", supplies)); ItemStack item = new CustomItemStack(resource.getItem(), "&f" + resource.getName(p), "&8\u21E8 &e" + supplies + ' ' + suffix); diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/electric/machines/enchanting/BookBinder.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/electric/machines/enchanting/BookBinder.java index c85e86070b..4ac521faa2 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/electric/machines/enchanting/BookBinder.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/electric/machines/enchanting/BookBinder.java @@ -57,6 +57,10 @@ protected MachineRecipe findNextRecipe(BlockMenu menu) { // Just return if no enchantments exist. This shouldn't ever happen. :NotLikeThis: if (enchantments.size() > 0) { + if (hasIllegalEnchants(storedItemEnchantments) || hasIllegalEnchants(storedTargetEnchantments)) { + return null; + } + ItemStack book = new ItemStack(Material.ENCHANTED_BOOK); EnchantmentStorageMeta enchantMeta = (EnchantmentStorageMeta) book.getItemMeta(); @@ -70,6 +74,11 @@ protected MachineRecipe findNextRecipe(BlockMenu menu) { return null; } + // If the output is the same as one of the inputs: don't consume items + if (enchantMeta.getStoredEnchants().equals(storedItemEnchantments) || enchantMeta.getStoredEnchants().equals(storedTargetEnchantments)) { + return null; + } + book.setItemMeta(enchantMeta); MachineRecipe recipe = new MachineRecipe(25 * (enchantments.size() / this.getSpeed()), new ItemStack[] { target, item }, new ItemStack[] { book }); @@ -97,6 +106,19 @@ private boolean isCompatible(@Nullable ItemStack item) { return item != null && item.getType() == Material.ENCHANTED_BOOK; } + private boolean hasIllegalEnchants(@Nullable Map enchantments) { + if (enchantments == null) { + return false; + } + + for (Map.Entry entry : enchantments.entrySet()) { + if (bypassVanillaMaxLevel.getValue() && entry.getValue() > customMaxLevel.getValue() || !bypassVanillaMaxLevel.getValue() && entry.getValue() > entry.getKey().getMaxLevel()) { + return true; + } + } + return false; + } + @Override public ItemStack getProgressBar() { return new ItemStack(Material.IRON_CHESTPLATE); diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/AbstractSmeltery.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/AbstractSmeltery.java index e11e76b3c8..7f2f27f0cb 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/AbstractSmeltery.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/AbstractSmeltery.java @@ -4,6 +4,7 @@ import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -14,6 +15,7 @@ import org.bukkit.inventory.ItemStack; import io.github.bakedlibs.dough.inventory.InvUtils; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; import io.github.thebusybiscuit.slimefun4.api.recipes.RecipeType; @@ -48,12 +50,14 @@ public void onInteract(Player p, Block b) { for (int i = 0; i < inputs.size(); i++) { if (canCraft(inv, inputs, i)) { ItemStack output = RecipeType.getRecipeOutputList(this, inputs.get(i)).clone(); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, inputs.get(i), output); - if (SlimefunUtils.canPlayerUseItem(p, output, true)) { + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled() && SlimefunUtils.canPlayerUseItem(p, output, true)) { Inventory outputInv = findOutputInventory(output, possibleDispenser, inv); if (outputInv != null) { - craft(p, b, inv, inputs.get(i), output, outputInv); + craft(p, b, inv, inputs.get(i), event.getOutput(), outputInv); } else { Slimefun.getLocalization().sendMessage(p, "machines.full-inventory", true); } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/ArmorForge.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/ArmorForge.java index d8e7dae44b..4ee70ac722 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/ArmorForge.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/ArmorForge.java @@ -4,6 +4,7 @@ import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -13,9 +14,9 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import io.github.thebusybiscuit.slimefun4.core.services.sounds.SoundEffect; import io.github.bakedlibs.dough.items.CustomItemStack; import io.github.bakedlibs.dough.items.ItemUtils; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; import io.github.thebusybiscuit.slimefun4.api.recipes.RecipeType; @@ -43,9 +44,11 @@ public void onInteract(Player p, Block b) { for (ItemStack[] input : inputs) { if (isCraftable(inv, input)) { ItemStack output = RecipeType.getRecipeOutputList(this, input).clone(); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, input, output); - if (SlimefunUtils.canPlayerUseItem(p, output, true)) { - craft(p, output, inv, possibleDispenser); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled() && SlimefunUtils.canPlayerUseItem(p, output, true)) { + craft(p, event.getOutput(), inv, possibleDispenser); } return; diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/AutomatedPanningMachine.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/AutomatedPanningMachine.java index c5e407404d..ca2d0c7124 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/AutomatedPanningMachine.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/AutomatedPanningMachine.java @@ -7,6 +7,7 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.GameMode; import org.bukkit.Material; @@ -18,6 +19,7 @@ import io.github.bakedlibs.dough.items.ItemUtils; import io.github.bakedlibs.dough.scheduling.TaskQueue; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; import io.github.thebusybiscuit.slimefun4.core.multiblocks.MultiBlockMachine; @@ -74,6 +76,14 @@ public void onInteract(Player p, Block b) { return; } + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, input, output); + + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + + ItemStack finalOutput = event.getOutput(); if (p.getGameMode() != GameMode.CREATIVE) { ItemUtils.consumeItem(input, false); } @@ -82,13 +92,13 @@ public void onInteract(Player p, Block b) { queue.thenRepeatEvery(20, 5, () -> b.getWorld().playEffect(b.getRelative(BlockFace.DOWN).getLocation(), Effect.STEP_SOUND, material)); queue.thenRun(20, () -> { - if (output.getType() != Material.AIR) { + if (finalOutput.getType() != Material.AIR) { Optional outputChest = OutputChest.findOutputChestFor(b.getRelative(BlockFace.DOWN), output); if (outputChest.isPresent()) { - outputChest.get().addItem(output.clone()); + outputChest.get().addItem(finalOutput.clone()); } else { - b.getWorld().dropItemNaturally(b.getLocation(), output.clone()); + b.getWorld().dropItemNaturally(b.getLocation(), finalOutput.clone()); } SoundEffect.AUTOMATED_PANNING_MACHINE_SUCCESS_SOUND.playAt(b); diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/Compressor.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/Compressor.java index c799ae879c..f0b48882e6 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/Compressor.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/Compressor.java @@ -6,6 +6,7 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -16,6 +17,7 @@ import org.bukkit.inventory.ItemStack; import io.github.bakedlibs.dough.items.CustomItemStack; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; import io.github.thebusybiscuit.slimefun4.api.recipes.RecipeType; @@ -66,13 +68,19 @@ public void onInteract(Player p, Block b) { if (recipeInput != null && SlimefunUtils.isItemSimilar(item, recipeInput, true)) { ItemStack output = RecipeType.getRecipeOutput(this, recipeInput); Inventory outputInv = findOutputInventory(output, dispBlock, inv); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, item, output); + + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } if (outputInv != null) { ItemStack removing = item.clone(); removing.setAmount(recipeInput.getAmount()); inv.removeItem(removing); - craft(p, output, dispBlock, inv); + craft(p, event.getOutput(), dispBlock, inv); } else { Slimefun.getLocalization().sendMessage(p, "machines.full-inventory", true); } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/EnhancedCraftingTable.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/EnhancedCraftingTable.java index 67b1ebbf58..4486197b62 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/EnhancedCraftingTable.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/EnhancedCraftingTable.java @@ -4,6 +4,7 @@ import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -13,8 +14,8 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import io.github.thebusybiscuit.slimefun4.core.services.sounds.SoundEffect; import io.github.bakedlibs.dough.items.ItemUtils; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; @@ -44,9 +45,11 @@ public void onInteract(Player p, Block b) { for (ItemStack[] input : inputs) { if (isCraftable(inv, input)) { ItemStack output = RecipeType.getRecipeOutputList(this, input).clone(); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, input, output); - if (SlimefunUtils.canPlayerUseItem(p, output, true)) { - craft(inv, possibleDispenser, p, b, output); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled() && SlimefunUtils.canPlayerUseItem(p, output, true)) { + craft(inv, possibleDispenser, p, b, event.getOutput()); } return; diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/GrindStone.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/GrindStone.java index 140aa83f8d..f149093042 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/GrindStone.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/GrindStone.java @@ -6,6 +6,7 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -16,6 +17,7 @@ import org.bukkit.inventory.ItemStack; import io.github.bakedlibs.dough.items.CustomItemStack; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.MinecraftVersion; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; @@ -126,12 +128,18 @@ public void onInteract(Player p, Block b) { if (convert != null && SlimefunUtils.isItemSimilar(current, convert, true)) { ItemStack output = RecipeType.getRecipeOutput(this, convert); Inventory outputInv = findOutputInventory(output, possibleDispenser, inv); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, current, output); + + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } if (outputInv != null) { ItemStack removing = current.clone(); removing.setAmount(1); inv.removeItem(removing); - outputInv.addItem(output); + outputInv.addItem(event.getOutput()); SoundEffect.GRIND_STONE_INTERACT_SOUND.playAt(b); } else { Slimefun.getLocalization().sendMessage(p, "machines.full-inventory", true); diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/Juicer.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/Juicer.java index 40978b05ca..a81c731434 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/Juicer.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/Juicer.java @@ -6,6 +6,7 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.Material; import org.bukkit.block.Block; @@ -17,6 +18,7 @@ import org.bukkit.inventory.ItemStack; import io.github.bakedlibs.dough.items.CustomItemStack; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; import io.github.thebusybiscuit.slimefun4.api.recipes.RecipeType; @@ -62,12 +64,18 @@ public void onInteract(Player p, Block b) { if (convert != null && SlimefunUtils.isItemSimilar(current, convert, true)) { ItemStack adding = RecipeType.getRecipeOutput(this, convert); Inventory outputInv = findOutputInventory(adding, possibleDispenser, inv); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, current, adding); + + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } if (outputInv != null) { ItemStack removing = current.clone(); removing.setAmount(1); inv.removeItem(removing); - outputInv.addItem(adding); + outputInv.addItem(event.getOutput()); SoundEffect.JUICER_USE_SOUND.playAt(b); // Not changed since this is supposed to be a natural sound. diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/MagicWorkbench.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/MagicWorkbench.java index 98cebcf884..25ce8cdcc8 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/MagicWorkbench.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/MagicWorkbench.java @@ -4,6 +4,7 @@ import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.Material; import org.bukkit.block.Block; @@ -15,6 +16,7 @@ import org.bukkit.inventory.ItemStack; import io.github.bakedlibs.dough.items.CustomItemStack; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; @@ -50,9 +52,11 @@ public void onInteract(Player p, Block b) { for (ItemStack[] input : inputs) { if (isCraftable(inv, input)) { ItemStack output = RecipeType.getRecipeOutputList(this, input).clone(); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, input, output); - if (SlimefunUtils.canPlayerUseItem(p, output, true)) { - craft(inv, possibleDispener, p, b, output); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled() && SlimefunUtils.canPlayerUseItem(p, output, true)) { + craft(inv, possibleDispener, p, b, event.getOutput()); } return; diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/OreCrusher.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/OreCrusher.java index 2a0dad3d61..ea5141f099 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/OreCrusher.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/OreCrusher.java @@ -7,6 +7,7 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.Material; import org.bukkit.block.Block; @@ -18,6 +19,7 @@ import org.bukkit.inventory.ItemStack; import io.github.bakedlibs.dough.items.CustomItemStack; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.MinecraftVersion; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.ItemSetting; @@ -190,13 +192,15 @@ public void onInteract(Player p, Block b) { if (convert != null && SlimefunUtils.isItemSimilar(current, convert, true)) { ItemStack adding = RecipeType.getRecipeOutput(this, convert); Inventory outputInv = findOutputInventory(adding, possibleDispenser, inv); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, current, adding); - if (SlimefunUtils.canPlayerUseItem(p, adding, true)) { + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled() && SlimefunUtils.canPlayerUseItem(p, adding, true)) { if (outputInv != null) { ItemStack removing = current.clone(); removing.setAmount(convert.getAmount()); inv.removeItem(removing); - outputInv.addItem(adding); + outputInv.addItem(event.getOutput()); p.getWorld().playEffect(b.getLocation(), Effect.STEP_SOUND, 1); } else { Slimefun.getLocalization().sendMessage(p, "machines.full-inventory", true); diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/OreWasher.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/OreWasher.java index 80dc6c6cc3..25a3282369 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/OreWasher.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/OreWasher.java @@ -2,12 +2,12 @@ import java.util.List; import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.Material; import org.bukkit.block.Block; @@ -18,6 +18,7 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; import io.github.thebusybiscuit.slimefun4.core.multiblocks.MultiBlockMachine; @@ -84,8 +85,8 @@ protected void registerDefaultRecipes(List recipes) { } @Override - public List getDisplayRecipes() { - return recipes.stream().map(items -> items[0]).collect(Collectors.toList()); + public @Nonnull List getDisplayRecipes() { + return recipes.stream().map(items -> items[0]).toList(); } @Override @@ -93,15 +94,14 @@ public void onInteract(Player p, Block b) { Block dispBlock = b.getRelative(BlockFace.UP); BlockState state = PaperLib.getBlockState(dispBlock, false).getState(); - if (state instanceof Dispenser) { - Dispenser disp = (Dispenser) state; + if (state instanceof Dispenser disp) { Inventory inv = disp.getInventory(); for (ItemStack input : inv.getContents()) { if (input != null) { if (SlimefunUtils.isItemSimilar(input, SlimefunItems.SIFTED_ORE, true)) { ItemStack output = getRandomDust(); - Inventory outputInv = null; + Inventory outputInv; if (!legacyMode) { /* @@ -119,7 +119,12 @@ public void onInteract(Player p, Block b) { outputInv = findOutputInventory(output, dispBlock, inv); } - removeItem(p, b, inv, outputInv, input, output, 1); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, input, output); + if (event.isCancelled()) { + return; + } + + removeItem(p, b, inv, outputInv, input, event.getOutput(), 1); if (outputInv != null) { outputInv.addItem(SlimefunItems.STONE_CHUNK); @@ -130,14 +135,25 @@ public void onInteract(Player p, Block b) { ItemStack output = SlimefunItems.SALT; Inventory outputInv = findOutputInventory(output, dispBlock, inv); - removeItem(p, b, inv, outputInv, input, output, 2); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, input, output); + if (event.isCancelled()) { + return; + } + + removeItem(p, b, inv, outputInv, input, event.getOutput(), 2); return; } else if (SlimefunUtils.isItemSimilar(input, SlimefunItems.PULVERIZED_ORE, true)) { ItemStack output = SlimefunItems.PURE_ORE_CLUSTER; Inventory outputInv = findOutputInventory(output, dispBlock, inv); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, input, output); + + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } - removeItem(p, b, inv, outputInv, input, output, 1); + removeItem(p, b, inv, outputInv, input, event.getOutput(), 1); return; } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/PressureChamber.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/PressureChamber.java index 4080aa31a0..c8b75de5a3 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/PressureChamber.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/PressureChamber.java @@ -6,6 +6,7 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.Material; import org.bukkit.block.Block; @@ -17,6 +18,7 @@ import org.bukkit.inventory.ItemStack; import io.github.bakedlibs.dough.items.CustomItemStack; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; import io.github.thebusybiscuit.slimefun4.api.recipes.RecipeType; @@ -51,13 +53,19 @@ public void onInteract(Player p, Block b) { if (convert != null && SlimefunUtils.isItemSimilar(current, convert, true)) { ItemStack output = RecipeType.getRecipeOutput(this, convert); Inventory outputInv = findOutputInventory(output, possibleDispenser, inv); + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, current, output); + + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } if (outputInv != null) { ItemStack removing = current.clone(); removing.setAmount(convert.getAmount()); inv.removeItem(removing); - craft(p, b, output, inv, possibleDispenser); + craft(p, b, event.getOutput(), inv, possibleDispenser); } else { Slimefun.getLocalization().sendMessage(p, "machines.full-inventory", true); } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/TableSaw.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/TableSaw.java index 0e920cbe04..40bb1b2fdd 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/TableSaw.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/items/multiblocks/TableSaw.java @@ -8,6 +8,7 @@ import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; +import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.GameMode; import org.bukkit.Material; @@ -19,6 +20,7 @@ import org.bukkit.inventory.ItemStack; import io.github.bakedlibs.dough.items.ItemUtils; +import io.github.thebusybiscuit.slimefun4.api.events.MultiBlockCraftEvent; import io.github.thebusybiscuit.slimefun4.api.items.ItemGroup; import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItemStack; import io.github.thebusybiscuit.slimefun4.core.multiblocks.MultiBlockMachine; @@ -107,11 +109,18 @@ public void onInteract(@Nonnull Player p, @Nonnull Block b) { return; } + MultiBlockCraftEvent event = new MultiBlockCraftEvent(p, this, item, output); + + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + if (p.getGameMode() != GameMode.CREATIVE) { ItemUtils.consumeItem(item, true); } - outputItems(b, output); + outputItems(b, event.getOutput()); b.getWorld().playEffect(b.getLocation(), Effect.STEP_SOUND, item.getType()); } diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/BlockListener.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/BlockListener.java index 30593427ee..58d066aae7 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/BlockListener.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/BlockListener.java @@ -90,20 +90,34 @@ public void onBlockPlace(BlockPlaceEvent e) { Slimefun.getProtectionManager().logAction(e.getPlayer(), e.getBlock(), Interaction.PLACE_BLOCK); } if (sfItem != null && !(sfItem instanceof NotPlaceable)) { - if (!sfItem.canUse(e.getPlayer(), true)) { + Player player = e.getPlayer(); + + if (!sfItem.canUse(player, true)) { e.setCancelled(true); } else { - SlimefunBlockPlaceEvent placeEvent = new SlimefunBlockPlaceEvent(e.getPlayer(), item, e.getBlock(), sfItem); + Block block = e.getBlockPlaced(); + + /* + * Resolves an issue when placing a block in a location currently in the deletion queue + * TODO This can be safely removed if/when the deletion no longer has a delay associated with it. + */ + if (Slimefun.getTickerTask().isDeletedSoon(block.getLocation())) { + Slimefun.getLocalization().sendMessage(player, "messages.await-deletion"); + e.setCancelled(true); + return; + } + + SlimefunBlockPlaceEvent placeEvent = new SlimefunBlockPlaceEvent(player, item, block, sfItem); Bukkit.getPluginManager().callEvent(placeEvent); if (placeEvent.isCancelled()) { e.setCancelled(true); } else { - if (Slimefun.getBlockDataService().isTileEntity(e.getBlock().getType())) { - Slimefun.getBlockDataService().setBlockData(e.getBlock(), sfItem.getId()); + if (Slimefun.getBlockDataService().isTileEntity(block.getType())) { + Slimefun.getBlockDataService().setBlockData(block, sfItem.getId()); } - BlockStorage.addBlockInfo(e.getBlock(), "id", sfItem.getId(), true); + BlockStorage.addBlockInfo(block, "id", sfItem.getId(), true); sfItem.callItemHandler(BlockPlaceHandler.class, handler -> handler.onPlayerPlace(e)); } } @@ -141,15 +155,15 @@ public void onBlockBreak(BlockBreakEvent e) { } } - if (!e.isCancelled()) { - checkForSensitiveBlockAbove(e.getPlayer(), e.getBlock(), item); + List drops = new ArrayList<>(); + if (!item.getType().isAir()) { int fortune = getBonusDropsWithFortune(item, e.getBlock()); - List drops = new ArrayList<>(); + callToolHandler(e, item, fortune, drops); + } - if (!item.getType().isAir()) { - callToolHandler(e, item, fortune, drops); - } + if (!e.isCancelled()) { + checkForSensitiveBlockAbove(e.getPlayer(), e.getBlock(), item); callBlockHandler(e, item, drops, sfItem); dropItems(e, drops); diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/ElytraImpactListener.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/ElytraImpactListener.java index 344fe14471..02fc80c56c 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/ElytraImpactListener.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/ElytraImpactListener.java @@ -1,9 +1,14 @@ package io.github.thebusybiscuit.slimefun4.implementation.listeners; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; +import java.util.UUID; import javax.annotation.Nonnull; +import org.bukkit.Sound; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -18,20 +23,35 @@ import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; import io.github.thebusybiscuit.slimefun4.implementation.items.armor.ElytraCap; import io.github.thebusybiscuit.slimefun4.implementation.items.armor.SlimefunArmorPiece; +import org.bukkit.event.entity.EntityToggleGlideEvent; /** * The {@link Listener} for the {@link ElytraCap}. * * @author Seggan + * @author J3fftw1 * * @see ElytraCap */ public class ElytraImpactListener implements Listener { + private final Set gliding = new HashSet<>(); + public ElytraImpactListener(@Nonnull Slimefun plugin) { plugin.getServer().getPluginManager().registerEvents(this, plugin); } + @EventHandler + public void onGlideToggle(EntityToggleGlideEvent event) { + Entity entity = event.getEntity(); + if (entity instanceof Player player && player.isGliding()) { + UUID uuid = player.getUniqueId(); + gliding.add(uuid); + } + // We tick 1 tick later because the player is being toggled of at the same tick as it takes damage. + Slimefun.instance().getServer().getScheduler().runTaskLater(Slimefun.instance(), gliding::clear, 1); + } + @EventHandler public void onPlayerCrash(EntityDamageEvent e) { if (!(e.getEntity() instanceof Player p)) { @@ -39,7 +59,9 @@ public void onPlayerCrash(EntityDamageEvent e) { return; } - if (e.getCause() == DamageCause.FALL || e.getCause() == DamageCause.FLY_INTO_WALL && p.isGliding()) { + if ((e.getCause() == DamageCause.FALL || e.getCause() == DamageCause.FLY_INTO_WALL) + && (p.isGliding() || gliding.contains(p.getUniqueId())) + ) { Optional optional = PlayerProfile.find(p); if (optional.isEmpty()) { @@ -48,7 +70,7 @@ public void onPlayerCrash(EntityDamageEvent e) { } PlayerProfile profile = optional.get(); - Optional helmet = profile.getArmor()[0].getItem(); + Optional helmet = profile.getArmor()[3].getItem(); if (helmet.isPresent()) { SlimefunItem item = helmet.get(); diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/SlimefunGuideListener.java b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/SlimefunGuideListener.java index 783dcf59f8..53897013a2 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/SlimefunGuideListener.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/implementation/listeners/SlimefunGuideListener.java @@ -81,7 +81,7 @@ private void openGuide(Player p, PlayerRightClickEvent e, SlimefunGuideMode layo @ParametersAreNonnullByDefault private Result tryOpenGuide(Player p, PlayerRightClickEvent e, SlimefunGuideMode layout) { ItemStack item = e.getItem(); - if (SlimefunUtils.isItemSimilar(item, SlimefunGuide.getItem(layout), true, false)) { + if (SlimefunUtils.isItemSimilar(item, SlimefunGuide.getItem(layout), false, false)) { if (!Slimefun.getWorldSettingsService().isWorldEnabled(p.getWorld())) { Slimefun.getLocalization().sendMessage(p, "messages.disabled-item", true); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 6c1211d8d1..5652a1e995 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -88,4 +88,4 @@ research-ranks: - Excellent Master - Great Master - Proficient Master -- The chosen One +- The Chosen One diff --git a/src/main/resources/languages/en/messages.yml b/src/main/resources/languages/en/messages.yml index 537174be63..711e965ed7 100644 --- a/src/main/resources/languages/en/messages.yml +++ b/src/main/resources/languages/en/messages.yml @@ -176,6 +176,7 @@ messages: bee-suit-slow-fall: '&eYour Bee Wings will help you to get back to the ground safe and slow' deprecated-item: '&4This item has been deprecated and will be removed from Slimefun soon.' researching-is-disabled: '&cResearching has been disabled on this server. Everything is unlocked by default!' + await-deletion: '&cYou cannot place a Slimefun block so soon after breaking one. Try again shortly.' multi-tool: mode-change: '&b%device% mode changed to: &9%mode%' diff --git a/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockBreakEvent.java b/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockBreakEvent.java index 2f94729d38..9eb9225213 100644 --- a/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockBreakEvent.java +++ b/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockBreakEvent.java @@ -58,7 +58,7 @@ void testEventIsFired() { Player player = new PlayerMock(server, "SomePlayer"); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, TestUtilities.randomInt(), 100, TestUtilities.randomInt())); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); BlockStorage.addBlockInfo(block, "id", "FOOD_COMPOSTER"); @@ -75,7 +75,7 @@ void testGetters() { player.getInventory().setItemInMainHand(itemStack); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, TestUtilities.randomInt(), 100, TestUtilities.randomInt())); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); BlockStorage.addBlockInfo(block, "id", "FOOD_COMPOSTER"); @@ -106,7 +106,7 @@ public void onBlockBreak(SlimefunBlockBreakEvent event) { player.getInventory().setItemInMainHand(itemStack); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, TestUtilities.randomInt(), 100, TestUtilities.randomInt())); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); BlockStorage.addBlockInfo(block, "id", "FOOD_COMPOSTER"); @@ -119,4 +119,24 @@ public void onBlockBreak(SlimefunBlockBreakEvent event) { return true; }); } + + @Test + @DisplayName("Test that breaking a Slimefun block gets queued for deletion") + void testBlockBreaksGetQueuedForDeletion() { + Player player = new PlayerMock(server, "SomePlayer"); + ItemStack itemStack = new ItemStack(Material.IRON_PICKAXE); + player.getInventory().setItemInMainHand(itemStack); + + World world = server.addSimpleWorld("my_world"); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, TestUtilities.randomInt(), 100, TestUtilities.randomInt())); + + Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); + BlockStorage.addBlockInfo(block, "id", "FOOD_COMPOSTER"); + + BlockBreakEvent blockBreakEvent = new BlockBreakEvent(block, player); + server.getPluginManager().callEvent(blockBreakEvent); + server.getPluginManager().assertEventFired(SlimefunBlockBreakEvent.class, e -> true); + + Assertions.assertTrue(Slimefun.getTickerTask().isDeletedSoon(block.getLocation())); + } } diff --git a/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockPlaceEvent.java b/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockPlaceEvent.java index b599f13eb6..f6d8d2868d 100644 --- a/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockPlaceEvent.java +++ b/src/test/java/io/github/thebusybiscuit/slimefun4/api/events/TestSlimefunBlockPlaceEvent.java @@ -16,6 +16,7 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; @@ -61,9 +62,10 @@ void testEventIsFired() { player.getInventory().setItemInMainHand(itemStack); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); - Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, 1, 0, 1)); - BlockStorage.clearBlockInfo(block); + int x = TestUtilities.randomInt(); + int z = TestUtilities.randomInt(); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, x, 0, z)); + Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, x, 1, z)); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); @@ -83,9 +85,10 @@ void testGetters() { player.getInventory().setItemInMainHand(itemStack); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); - Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, 1, 0, 1)); - BlockStorage.clearBlockInfo(block); + int x = TestUtilities.randomInt(); + int z = TestUtilities.randomInt(); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, x, 0, z)); + Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, x, 1, z)); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); @@ -119,9 +122,10 @@ public void onBlockPlace(SlimefunBlockPlaceEvent event) { player.getInventory().setItemInMainHand(itemStack); World world = server.addSimpleWorld("my_world"); - Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, 1, 1, 1)); - Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, 1, 0, 1)); - BlockStorage.clearBlockInfo(block); + int x = TestUtilities.randomInt(); + int z = TestUtilities.randomInt(); + Block block = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, x, 0, z)); + Block blockAgainst = new BlockMock(Material.GRASS, new Location(world, x, 1, z)); Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); @@ -136,4 +140,48 @@ public void onBlockPlace(SlimefunBlockPlaceEvent event) { return true; }); } + + @Test + @DisplayName("Test that you cannot place before a SlimefunBlock is fully cleared") + void testBlockPlacementBeforeFullDeletion() { + Player player = new PlayerMock(server, "SomePlayer"); + ItemStack itemStack = slimefunItem.getItem(); + player.getInventory().setItemInMainHand(itemStack); + + // Place first block + World world = server.addSimpleWorld("my_world"); + int x = TestUtilities.randomInt(); + int z = TestUtilities.randomInt(); + Block firstBlock = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, x, 0, z)); + Block firstBlockAgainst = new BlockMock(Material.GRASS, new Location(world, x, 1, z)); + + Slimefun.getRegistry().getWorlds().put("my_world", new BlockStorage(world)); + + BlockPlaceEvent firstBlockPlaceEvent = new BlockPlaceEvent( + firstBlock, firstBlock.getState(), firstBlockAgainst, itemStack, player, true, EquipmentSlot.HAND + ); + + server.getPluginManager().callEvent(firstBlockPlaceEvent); + server.getPluginManager().assertEventFired(SlimefunBlockPlaceEvent.class, e -> { + Assertions.assertFalse(e.isCancelled()); + return true; + }); + + // Break block + server.getPluginManager().callEvent(new BlockBreakEvent(firstBlock, player)); + server.getPluginManager().assertEventFired(SlimefunBlockBreakEvent.class, e -> true); + + // Assert that the block is not fully deleted + Assertions.assertTrue(Slimefun.getTickerTask().isDeletedSoon(firstBlock.getLocation())); + + // Place second block in the same location + Block secondBlock = new BlockMock(Material.GREEN_TERRACOTTA, new Location(world, x, 0, z)); + Block secondBlockAgainst = new BlockMock(Material.GRASS, new Location(world, x, 1, z)); + + BlockPlaceEvent secondBlockPlaceEvent = new BlockPlaceEvent( + secondBlock, secondBlock.getState(), secondBlockAgainst, itemStack, player, true, EquipmentSlot.HAND + ); + server.getPluginManager().callEvent(secondBlockPlaceEvent); + Assertions.assertTrue(secondBlockPlaceEvent.isCancelled()); + } } diff --git a/src/test/java/io/github/thebusybiscuit/slimefun4/test/TestUtilities.java b/src/test/java/io/github/thebusybiscuit/slimefun4/test/TestUtilities.java index 0f520306aa..56f517da20 100644 --- a/src/test/java/io/github/thebusybiscuit/slimefun4/test/TestUtilities.java +++ b/src/test/java/io/github/thebusybiscuit/slimefun4/test/TestUtilities.java @@ -2,6 +2,7 @@ import static org.mockito.Mockito.when; +import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -30,6 +31,8 @@ public final class TestUtilities { + private static final Random random = new Random(); + private TestUtilities() {} @ParametersAreNonnullByDefault @@ -76,4 +79,14 @@ private TestUtilities() {} latch.await(2, TimeUnit.SECONDS); return ref.get(); } + + @ParametersAreNonnullByDefault + public static @Nonnull int randomInt() { + return random.nextInt(Integer.MAX_VALUE); + } + + @ParametersAreNonnullByDefault + public static @Nonnull int randomInt(int upperBound) { + return random.nextInt(upperBound); + } }