diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/brush/Brush.java b/api/src/main/java/net/thenextlvl/gopaint/api/brush/Brush.java deleted file mode 100644 index ed41ecfb..00000000 --- a/api/src/main/java/net/thenextlvl/gopaint/api/brush/Brush.java +++ /dev/null @@ -1,151 +0,0 @@ -package net.thenextlvl.gopaint.api.brush; - -import com.sk89q.worldedit.EditSession; -import com.sk89q.worldedit.LocalSession; -import com.sk89q.worldedit.MaxChangedBlocksException; -import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.bukkit.BukkitPlayer; -import com.sk89q.worldedit.math.BlockVector3; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import lombok.experimental.Accessors; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.key.Keyed; -import net.kyori.adventure.text.Component; -import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import net.thenextlvl.gopaint.api.math.Surface; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - -import java.util.function.Consumer; - -/** - * This interface represents a brush used for painting blocks in a world. - */ -@Getter -@ToString -@EqualsAndHashCode -@RequiredArgsConstructor -public abstract class Brush implements Comparable, Keyed { - /** - * Retrieves the base64 head value. - */ - private final String headValue; - /** - * The key that identifies this brush - */ - private final @Accessors(fluent = true) Key key; - - /** - * Retrieves the localized name of this brush. - * - * @param audience The audience for whom the name is retrieved. - * @return The localized name of the brush. - */ - public abstract Component getName(Audience audience); - - /** - * Retrieves the localized description of this brush. - * - * @param audience The audience for whom the description is retrieved. - * @return The localized description of the brush. - */ - public abstract Component[] getDescription(Audience audience); - - /** - * Performs a painting action using the provided location, player, and brush settings. - * - * @param location The location the painting action is performed. - * @param player The player who is performing the paint action. - * @param brushSettings The brush settings to be applied while painting. - * @see #performEdit(Player, Consumer) - * @see #setBlock(EditSession, BlockVector3, Material) - */ - public abstract void paint(Location location, Player player, BrushSettings brushSettings); - - /** - * Sets the material of a block in an EditSession. - * - * @param session The EditSession to perform the block update in. - * @param vector3 The block to update. - * @param material The material to set the block to. - * @throws MaxChangedBlocksException If the maximum number of changed blocks is exceeded. - */ - public void setBlock(EditSession session, BlockVector3 vector3, Material material) throws MaxChangedBlocksException { - session.setBlock(vector3, BukkitAdapter.asBlockType(material)); - } - - /** - * Performs an edit using WorldEdit's EditSession. - * This method wraps the edit session in a try-with-resources block to ensure proper cleanup of resources. - * - * @param player The player performing the edit. - * @param edit A Consumer functional interface that defines the actions to be taken within the edit session. - */ - public void performEdit(Player player, Consumer edit) { - BukkitPlayer wrapped = BukkitAdapter.adapt(player); - LocalSession localSession = WorldEdit.getInstance().getSessionManager().get(wrapped); - try (EditSession editsession = localSession.createEditSession(wrapped)) { - try { - edit.accept(editsession); - } finally { - localSession.remember(editsession); - } - } - } - - /** - * Checks if a given block passes the default checks defined by the brush settings. - * - * @param brushSettings The brush settings to be checked against. - * @param player The player using the brush. - * @param session The EditSession object used for performing the checks. - * @param block The block being checked. - * @return true if the block passes the default checks, false otherwise. - */ - public boolean passesDefaultChecks(BrushSettings brushSettings, Player player, EditSession session, Block block) { - return passesMaskCheck(brushSettings, session, block) && passesSurfaceCheck(brushSettings, player, block); - } - - /** - * Checks if a given block passes the surface check defined by the brush settings. - * - * @param brushSettings The brush settings to be checked against. - * @param player The player using the brush. - * @param block The block being checked. - * @return true if the block passes the surface check, false otherwise. - */ - public boolean passesSurfaceCheck(BrushSettings brushSettings, Player player, Block block) { - return Surface.isOnSurface(block, brushSettings.getSurfaceMode(), player.getLocation()); - } - - /** - * Checks if a given block passes the mask check defined by the brush settings. - * - * @param brushSettings The brush settings to be checked against. - * @param session The EditSession object used for performing the mask check. - * @param block The block being checked. - * @return true if the block passes the mask check, false otherwise. - */ - public boolean passesMaskCheck(BrushSettings brushSettings, EditSession session, Block block) { - return switch (brushSettings.getMaskMode()) { - case INTERFACE -> block.getType().equals(brushSettings.getMask()); - case WORLDEDIT -> session.getMask() == null || session.getMask().test( - BlockVector3.at(block.getX(), block.getY(), block.getZ()) - ); - case DISABLED -> true; - }; - } - - @Override - public int compareTo(@NotNull Brush brush) { - return key().compareTo(brush.key()); - } -} diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/brush/BrushRegistry.java b/api/src/main/java/net/thenextlvl/gopaint/api/brush/BrushRegistry.java index b6a31d56..bf3bd593 100644 --- a/api/src/main/java/net/thenextlvl/gopaint/api/brush/BrushRegistry.java +++ b/api/src/main/java/net/thenextlvl/gopaint/api/brush/BrushRegistry.java @@ -15,7 +15,7 @@ public interface BrushRegistry { * * @return The stream of available brushes. */ - Stream getBrushes(); + Stream getBrushes(); /** * Checks if a brush is registered in the BrushController. @@ -23,7 +23,7 @@ public interface BrushRegistry { * @param brush The brush to check if it is registered. * @return true if the brush is registered, false otherwise. */ - boolean isRegistered(Brush brush); + boolean isRegistered(PatternBrush brush); /** * Registers a brush in the BrushManager. @@ -31,7 +31,7 @@ public interface BrushRegistry { * @param brush The brush to be registered. * @throws IllegalStateException if the brush is already registered. */ - void registerBrush(Brush brush) throws IllegalStateException; + void registerBrush(PatternBrush brush) throws IllegalStateException; /** * Unregisters a brush from the Brush Controller. @@ -39,7 +39,7 @@ public interface BrushRegistry { * @param brush The brush to be unregistered. * @throws IllegalStateException if the brush is not registered. */ - void unregisterBrush(Brush brush) throws IllegalStateException; + void unregisterBrush(PatternBrush brush) throws IllegalStateException; /** * Retrieves the brush associated with the provided NamespacedKey. @@ -47,5 +47,5 @@ public interface BrushRegistry { * @param key The NamespacedKey of the brush to retrieve. * @return An Optional containing the brush if found, or an empty Optional if not found. */ - Optional getBrush(Key key); + Optional getBrush(Key key); } diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/brush/PatternBrush.java b/api/src/main/java/net/thenextlvl/gopaint/api/brush/PatternBrush.java new file mode 100644 index 00000000..40f9355d --- /dev/null +++ b/api/src/main/java/net/thenextlvl/gopaint/api/brush/PatternBrush.java @@ -0,0 +1,81 @@ +package net.thenextlvl.gopaint.api.brush; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.command.tool.brush.Brush; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.math.BlockVector3; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.experimental.Accessors; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.key.Keyed; +import net.kyori.adventure.text.Component; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; +import org.jetbrains.annotations.NotNull; + +/** + * This interface represents a brush used for painting blocks in a world. + */ +@Getter +@ToString +@EqualsAndHashCode +@RequiredArgsConstructor +public abstract class PatternBrush implements Comparable, Keyed, Brush { + /** + * Retrieves the base64 head value. + */ + private final String headValue; + /** + * The key that identifies this brush + */ + private final @Accessors(fluent = true) Key key; + + /** + * Retrieves the localized name of this brush. + * + * @param audience The audience for whom the name is retrieved. + * @return The localized name of the brush. + */ + public abstract Component getName(Audience audience); + + /** + * Retrieves the localized description of this brush. + * + * @param audience The audience for whom the description is retrieved. + * @return The localized description of the brush. + */ + public abstract Component[] getDescription(Audience audience); + + /** + * Builds a pattern for the brush based on the provided parameters. + * + * @param session The EditSession to build the pattern for. + * @param position The position of the block where the pattern is located. + * @param player The player using the brush. + * @param settings The brush settings to be used for building. + * @return The built pattern. + */ + public abstract Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings); + + /** + * Builds a pattern in the specified EditSession at the given position with the provided size. + * + * @param session The EditSession to build the pattern in. + * @param position The position of the center block of the pattern. + * @param pattern The pattern. + * @param size The size of the pattern. + * @throws MaxChangedBlocksException If the maximum number of changed blocks is exceeded. + */ + @Override + public abstract void build(EditSession session, BlockVector3 position, Pattern pattern, double size) throws MaxChangedBlocksException; + + @Override + public int compareTo(@NotNull PatternBrush brush) { + return key().compareTo(brush.key()); + } +} diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/brush/SpherePatternBrush.java b/api/src/main/java/net/thenextlvl/gopaint/api/brush/SpherePatternBrush.java new file mode 100644 index 00000000..98171428 --- /dev/null +++ b/api/src/main/java/net/thenextlvl/gopaint/api/brush/SpherePatternBrush.java @@ -0,0 +1,18 @@ +package net.thenextlvl.gopaint.api.brush; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.math.BlockVector3; +import net.kyori.adventure.key.Key; + +public abstract class SpherePatternBrush extends PatternBrush { + public SpherePatternBrush(String headValue, Key key) { + super(headValue, key); + } + + @Override + public final void build(EditSession session, BlockVector3 position, Pattern pattern, double size) throws MaxChangedBlocksException { + session.makeSphere(position, pattern, size, size, size, true); + } +} diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/brush/mask/VisibleMask.java b/api/src/main/java/net/thenextlvl/gopaint/api/brush/mask/VisibleMask.java new file mode 100644 index 00000000..4c734e89 --- /dev/null +++ b/api/src/main/java/net/thenextlvl/gopaint/api/brush/mask/VisibleMask.java @@ -0,0 +1,48 @@ +package net.thenextlvl.gopaint.api.brush.mask; + +import com.fastasyncworldedit.core.math.MutableVector3; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; + +public record VisibleMask(Extent extent, Vector3 viewPoint) implements Mask { + + @Override + public boolean test(BlockVector3 vector) { + + var location = new MutableVector3( + vector.getX(), + vector.getY(), + vector.getZ() + ); + + var distanceX = viewPoint().getX() - location.getX(); + var distanceY = viewPoint().getY() - location.getY(); + var distanceZ = viewPoint().getZ() - location.getZ(); + + location.setComponents( + location.getX() + (distanceX > 1 ? 1 : distanceX > 0 ? 0.5 : 0), + location.getY() + (distanceY > 1 ? 1 : distanceY > 0 ? 0.5 : 0), + location.getZ() + (distanceZ > 1 ? 1 : distanceZ > 0 ? 0.5 : 0) + ); + + var distance = location.distance(viewPoint()); + for (var x = 1; x < distance; x++) { + + var moveX = distanceX * (x / distance); + var moveY = distanceY * (x / distance); + var moveZ = distanceZ * (x / distance); + + var point = location.add(moveX, moveY, moveZ).toBlockPoint(); + + if (!extent().getBlock(point).getMaterial().isAir()) return false; + } + return true; + } + + @Override + public Mask copy() { + return new VisibleMask(extent(), viewPoint()); + } +} diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/brush/mask/package-info.java b/api/src/main/java/net/thenextlvl/gopaint/api/brush/mask/package-info.java new file mode 100644 index 00000000..109b243e --- /dev/null +++ b/api/src/main/java/net/thenextlvl/gopaint/api/brush/mask/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.gopaint.api.brush.mask; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/brush/pattern/BuildPattern.java b/api/src/main/java/net/thenextlvl/gopaint/api/brush/pattern/BuildPattern.java new file mode 100644 index 00000000..fc5cc92e --- /dev/null +++ b/api/src/main/java/net/thenextlvl/gopaint/api/brush/pattern/BuildPattern.java @@ -0,0 +1,45 @@ +package net.thenextlvl.gopaint.api.brush.pattern; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; +import org.bukkit.Material; + +import java.util.Objects; + +public interface BuildPattern extends Pattern { + BlockVector3 position(); + + EditSession session(); + + Player player(); + + BrushSettings settings(); + + @Override + default BaseBlock applyBlock(BlockVector3 position) { + return player().getWorld().getFullBlock(position); + } + + /** + * Picks a random block from {@link BrushSettings#getBlocks()}. + * + * @return The default state of the randomly picked block. + */ + default BlockState getRandomBlockState() { + Material material; + if (settings().getBlocks().size() == 1) { + material = settings().getBlocks().getFirst(); + } else { + var index = settings().getRandom().nextInt(settings().getBlocks().size()); + material = settings().getBlocks().get(index); + } + var type = BukkitAdapter.asBlockType(material); + return Objects.requireNonNull(type).getDefaultState(); + } +} diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/brush/pattern/package-info.java b/api/src/main/java/net/thenextlvl/gopaint/api/brush/pattern/package-info.java new file mode 100644 index 00000000..46fac4e7 --- /dev/null +++ b/api/src/main/java/net/thenextlvl/gopaint/api/brush/pattern/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.gopaint.api.brush.pattern; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/brush/setting/BrushSettings.java b/api/src/main/java/net/thenextlvl/gopaint/api/brush/setting/BrushSettings.java index db036603..7df15ebd 100644 --- a/api/src/main/java/net/thenextlvl/gopaint/api/brush/setting/BrushSettings.java +++ b/api/src/main/java/net/thenextlvl/gopaint/api/brush/setting/BrushSettings.java @@ -1,12 +1,22 @@ package net.thenextlvl.gopaint.api.brush.setting; -import net.thenextlvl.gopaint.api.brush.Brush; -import net.thenextlvl.gopaint.api.model.MaskMode; +import com.fastasyncworldedit.core.function.mask.SingleBlockTypeMask; +import com.fastasyncworldedit.core.function.mask.SurfaceMask; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.mask.ExistingBlockMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.MaskIntersection; +import net.thenextlvl.gopaint.api.brush.PatternBrush; +import net.thenextlvl.gopaint.api.brush.mask.VisibleMask; import net.thenextlvl.gopaint.api.model.SurfaceMode; import org.bukkit.Axis; import org.bukkit.Material; +import javax.annotation.Nullable; import java.util.List; +import java.util.Optional; import java.util.Random; /** @@ -27,7 +37,7 @@ public interface BrushSettings { * * @return The brush used by the brush settings. */ - Brush getBrush(); + PatternBrush getBrush(); /** * Returns the list of blocks used by the brush settings. @@ -44,11 +54,35 @@ public interface BrushSettings { Material getMask(); /** - * Retrieves the mask mode used by the brush settings. + * Determines whether the mask is enabled or not. * - * @return The mask mode used by the brush settings. + * @return true if the mask is enabled, false otherwise */ - MaskMode getMaskMode(); + boolean isMaskEnabled(); + + /** + * Retrieves the WorldEdit mask for the given session according to the brush settings. + * + * @param session The session used for retrieving the mask. + * @return The WorldEdit mask + */ + default Mask getMask(LocalSession session) { + var mask = Optional.ofNullable(session.getMask()) + .orElseGet(() -> new ExistingBlockMask(session.getSelectionWorld())); + return isMaskEnabled() ? Optional.of(getMask()) + .map(BukkitAdapter::asBlockType) + .map(blockType -> new SingleBlockTypeMask(session.getSelectionWorld(), blockType)) + .map(single -> MaskIntersection.of(single, mask)) + .orElse(mask) : mask; + } + + default @Nullable Mask getSurfaceMask(Player player) { + return switch (getSurfaceMode()) { + case VISIBLE -> new VisibleMask(player.getWorld(), player.getLocation().add(0, 1.5, 0)); + case EXPOSED -> new SurfaceMask(player.getWorld()); + case DISABLED -> null; + }; + } /** * Returns the surface mode used by the brush settings. @@ -113,13 +147,6 @@ public interface BrushSettings { */ int getThickness(); - /** - * Picks a random block material from {@link #getBlocks()}. - * - * @return The randomly picked block material. - */ - Material getRandomBlock(); - /** * The random number generator instance. * diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/brush/setting/PlayerBrushSettings.java b/api/src/main/java/net/thenextlvl/gopaint/api/brush/setting/PlayerBrushSettings.java index 6e42f4d2..432e6baf 100644 --- a/api/src/main/java/net/thenextlvl/gopaint/api/brush/setting/PlayerBrushSettings.java +++ b/api/src/main/java/net/thenextlvl/gopaint/api/brush/setting/PlayerBrushSettings.java @@ -1,8 +1,7 @@ package net.thenextlvl.gopaint.api.brush.setting; import core.paper.gui.AbstractGUI; -import net.thenextlvl.gopaint.api.brush.Brush; -import net.thenextlvl.gopaint.api.model.MaskMode; +import net.thenextlvl.gopaint.api.brush.PatternBrush; import net.thenextlvl.gopaint.api.model.PluginConfig; import net.thenextlvl.gopaint.api.model.SurfaceMode; import org.bukkit.Axis; @@ -97,7 +96,7 @@ public interface PlayerBrushSettings extends BrushSettings { * * @param strength The falloff strength to set. */ - void setFalloffStrength(@Range(from = 10, to = 90) int strength); + void setFalloffStrength(@Range(from = 0, to = 100) int strength); /** * Sets the mixing strength of the brush. @@ -105,7 +104,7 @@ public interface PlayerBrushSettings extends BrushSettings { * * @param strength The mixing strength to set. */ - void setMixingStrength(@Range(from = 10, to = 90) int strength); + void setMixingStrength(@Range(from = 0, to = 100) int strength); /** * Sets the angle height difference of the brush. @@ -118,11 +117,11 @@ public interface PlayerBrushSettings extends BrushSettings { void setAngleHeightDifference(double difference); /** - * Sets the mask mode of the brush. + * Sets whether the mask is enabled or disabled for the brush. * - * @param maskMode The mask mode to set. + * @param enabled true to enable the mask, false to disable it */ - void setMaskMode(MaskMode maskMode); + void setMaskEnabled(boolean enabled); /** * Sets the surface mode of the brush. @@ -150,7 +149,7 @@ public interface PlayerBrushSettings extends BrushSettings { * * @param brush The brush to set. */ - void setBrush(Brush brush); + void setBrush(PatternBrush brush); /** * Sets the mask for the brush. @@ -191,7 +190,7 @@ public interface PlayerBrushSettings extends BrushSettings { * @param brush The current brush, if null returns the first brush in the list. * @return The next brush in the list, or the first brush if the current brush is null. */ - Brush getNextBrush(@Nullable Brush brush); + PatternBrush getNextBrush(@Nullable PatternBrush brush); /** * Retrieves the previous brush in the list of available brushes. @@ -199,5 +198,5 @@ public interface PlayerBrushSettings extends BrushSettings { * @param brush The current brush. * @return The previous brush in the list, or the first brush if the current brush is null. */ - Brush getPreviousBrush(@Nullable Brush brush); + PatternBrush getPreviousBrush(@Nullable PatternBrush brush); } diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/math/ConnectedBlocks.java b/api/src/main/java/net/thenextlvl/gopaint/api/math/ConnectedBlocks.java index 5ea4122c..1defebda 100644 --- a/api/src/main/java/net/thenextlvl/gopaint/api/math/ConnectedBlocks.java +++ b/api/src/main/java/net/thenextlvl/gopaint/api/math/ConnectedBlocks.java @@ -18,24 +18,23 @@ */ package net.thenextlvl.gopaint.api.math; -import org.bukkit.Location; -import org.bukkit.block.Block; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; import org.bukkit.block.BlockFace; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedList; -import java.util.List; -import java.util.Queue; import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; public class ConnectedBlocks { private static final BlockFace[] faces = new BlockFace[]{ BlockFace.NORTH, - BlockFace.EAST, BlockFace.SOUTH, + BlockFace.EAST, BlockFace.WEST, BlockFace.UP, BlockFace.DOWN, @@ -45,26 +44,26 @@ public class ConnectedBlocks { * Returns a stream of connected blocks starting from a given location, based on a list of blocks. * Only blocks of the same type as the start block are considered. * - * @param loc the starting location - * @param blocks the list of blocks to check for connectivity + * @param vector3 the starting location + * @param blocks the list of blocks to check for connectivity * @return a stream of connected blocks */ - public static Stream getConnectedBlocks(Location loc, List blocks) { - Block startBlock = loc.getBlock(); - Set connected = new HashSet<>(); - Queue toCheck = new LinkedList<>(); + public static Stream getConnectedBlocks(Extent world, BlockVector3 vector3, Set blocks) { + var startBlock = world.getFullBlock(vector3); + var connected = new HashSet(); + var toCheck = new LinkedList(); - toCheck.add(startBlock); - connected.add(startBlock); + toCheck.add(vector3); + connected.add(vector3); while (!toCheck.isEmpty() && connected.size() < blocks.size()) { - Block current = toCheck.poll(); - List neighbors = Arrays.stream(faces) - .map(current::getRelative) - .filter(relative -> relative.getType().equals(startBlock.getType()) - && !connected.contains(relative) - && blocks.contains(relative)) - .toList(); + var current = toCheck.poll(); + var neighbors = Arrays.stream(faces) + .map(face -> current.add(face.getModX(), face.getModY(), face.getModZ())) + .filter(relative -> !connected.contains(relative)) + .filter(blocks::contains) + .filter(relative -> world.getBlock(relative).getMaterial().equals(startBlock.getMaterial())) + .collect(Collectors.toSet()); connected.addAll(neighbors); toCheck.addAll(neighbors); diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/math/Height.java b/api/src/main/java/net/thenextlvl/gopaint/api/math/Height.java index c780015a..6954da8e 100644 --- a/api/src/main/java/net/thenextlvl/gopaint/api/math/Height.java +++ b/api/src/main/java/net/thenextlvl/gopaint/api/math/Height.java @@ -18,94 +18,93 @@ */ package net.thenextlvl.gopaint.api.math; -import org.bukkit.Location; -import org.bukkit.block.Block; +import net.thenextlvl.gopaint.api.model.Block; public class Height { /** - * Gets the height of the nearest non-empty block at a given location. + * Returns the nearest non-empty block height above or below the given block. * - * @param location the location to check - * @return the height of the nearest non-empty block at the location + * @param block the block to find the nearest non-empty block for + * @return the y-coordinate of the nearest non-empty block */ - public static int getNearestNonEmptyBlock(Location location) { - if (location.getBlock().getType().isEmpty()) { - for (int y = location.getBlockY(); y >= location.getWorld().getMinHeight(); y--) { - Block block = location.getWorld().getBlockAt(location.getBlockX(), y, location.getBlockZ()); - if (!block.isEmpty()) { - return y + 1; - } + public static int getNearestNonEmptyBlock(Block block) { + if (block.material().isAir()) { + for (var y = block.vector().getY(); y >= block.world().getMinY(); y--) { + if (block.world().getBlock(block.vector().getX(), y, block.vector().getZ()).isAir()) continue; + return y + 1; } - return location.getWorld().getMinHeight(); + return block.world().getMinY(); } else { - for (int y = location.getBlockY(); y <= location.getWorld().getMaxHeight(); y++) { - Block block = location.getWorld().getBlockAt(location.getBlockX(), y, location.getBlockZ()); - if (block.isEmpty()) { - return y; - } + for (var y = block.vector().getY(); y <= block.world().getMaxY(); y++) { + if (!block.world().getBlock(block.vector().getX(), y, block.vector().getZ()).isAir()) continue; + return y; } - return location.getWorld().getMaxHeight(); + return block.world().getMaxY(); } } /** - * Calculates the average height difference of the surrounding blocks from a given location, within a specified distance. + * Calculates the average height difference fracture of a block within a given distance. * - * @param location the location to calculate the average height difference from - * @param height the reference height of the nearest non-empty block - * @param distance the distance at which to calculate the average height difference - * @return the average height difference of the surrounding blocks within the specified distance + * @param block The block to calculate the average height difference fracture for. + * @param height The height to compare against. + * @param distance The distance to consider when calculating the average height difference fracture. + * @return The average height difference fracture of the block within the given distance. */ - public static double getAverageHeightDiffFracture(Location location, int height, int distance) { + public static double getAverageHeightDiffFracture(Block block, int height, int distance) { double totalHeight = 0; - totalHeight += Math.abs(getNearestNonEmptyBlock(location.clone().add(distance, 0, -distance))) - height; - totalHeight += Math.abs(getNearestNonEmptyBlock(location.clone().add(distance, 0, distance))) - height; - totalHeight += Math.abs(getNearestNonEmptyBlock(location.clone().add(-distance, 0, distance))) - height; - totalHeight += Math.abs(getNearestNonEmptyBlock(location.clone().add(-distance, 0, -distance))) - height; - totalHeight += Math.abs(getNearestNonEmptyBlock(location.clone().add(0, 0, -distance))) - height; - totalHeight += Math.abs(getNearestNonEmptyBlock(location.clone().add(0, 0, distance))) - height; - totalHeight += Math.abs(getNearestNonEmptyBlock(location.clone().add(-distance, 0, 0))) - height; - totalHeight += Math.abs(getNearestNonEmptyBlock(location.clone().add(distance, 0, 0))) - height; + totalHeight += Math.abs(getNearestNonEmptyBlock(relative(block, distance, -distance))) - height; + totalHeight += Math.abs(getNearestNonEmptyBlock(relative(block, distance, distance))) - height; + totalHeight += Math.abs(getNearestNonEmptyBlock(relative(block, -distance, distance))) - height; + totalHeight += Math.abs(getNearestNonEmptyBlock(relative(block, -distance, -distance))) - height; + totalHeight += Math.abs(getNearestNonEmptyBlock(relative(block, 0, -distance))) - height; + totalHeight += Math.abs(getNearestNonEmptyBlock(relative(block, 0, distance))) - height; + totalHeight += Math.abs(getNearestNonEmptyBlock(relative(block, -distance, 0))) - height; + totalHeight += Math.abs(getNearestNonEmptyBlock(relative(block, distance, 0))) - height; return (totalHeight / 8d) / distance; } + private static Block relative(Block block, int x, int z) { + var vector3 = block.vector().add(x, 0, z); + return new Block(block.world().getFullBlock(vector3), vector3, block.world()); + } + /** - * Calculates the average height difference angle of the surrounding blocks from a given location within a specified distance. + * Calculates the average height difference angle of a block within a given distance. * - * @param location the location to calculate the average height difference angle from - * @param distance the distance at which to calculate the average height difference angle - * @return the average height difference angle of the surrounding blocks within the specified distance + * @param block The block to calculate the average height difference angle for. + * @param distance The distance to consider when calculating the average height difference angle. + * @return The average height difference angle of the block within the given distance. */ - public static double getAverageHeightDiffAngle(Location location, int distance) { - double maxHeightDiff = 0; - double maxHeightDiff2 = 0; - double diff = Math.abs(getNearestNonEmptyBlock(location.clone().add(distance, 0, -distance)) - - getNearestNonEmptyBlock(location.clone().add(-distance, 0, distance))); + public static double getAverageHeightDiffAngle(Block block, int distance) { + var maxHeightDiff = 0; + var maxHeightDiff2 = 0; + var diff = Math.abs(getNearestNonEmptyBlock(relative(block, distance, -distance)) + - getNearestNonEmptyBlock(relative(block, -distance, distance))); if (diff >= maxHeightDiff) { maxHeightDiff = diff; maxHeightDiff2 = maxHeightDiff; } - diff = Math.abs(getNearestNonEmptyBlock(location.clone().add(distance, 0, distance)) - - getNearestNonEmptyBlock(location.clone().add(-distance, 0, -distance))); + diff = Math.abs(getNearestNonEmptyBlock(relative(block, distance, distance)) + - getNearestNonEmptyBlock(relative(block, -distance, -distance))); if (diff > maxHeightDiff) { maxHeightDiff = diff; maxHeightDiff2 = maxHeightDiff; } - diff = Math.abs(getNearestNonEmptyBlock(location.clone().add(distance, 0, 0)) - - getNearestNonEmptyBlock(location.clone().add(-distance, 0, 0))); + diff = Math.abs(getNearestNonEmptyBlock(relative(block, distance, 0)) + - getNearestNonEmptyBlock(relative(block, -distance, 0))); if (diff > maxHeightDiff) { maxHeightDiff = diff; maxHeightDiff2 = maxHeightDiff; } - diff = Math.abs(getNearestNonEmptyBlock(location.clone().add(0, 0, -distance)) - - getNearestNonEmptyBlock(location.clone().add(0, 0, distance))); + diff = Math.abs(getNearestNonEmptyBlock(relative(block, 0, -distance)) + - getNearestNonEmptyBlock(relative(block, 0, distance))); if (diff > maxHeightDiff) { maxHeightDiff = diff; maxHeightDiff2 = maxHeightDiff; } - double height = (maxHeightDiff2 + maxHeightDiff) / 2.0; - return height / (distance * 2d); + return (maxHeightDiff2 + maxHeightDiff) / (distance * 2d); } } diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/math/Sphere.java b/api/src/main/java/net/thenextlvl/gopaint/api/math/Sphere.java index ac75f1fc..1b1c6043 100644 --- a/api/src/main/java/net/thenextlvl/gopaint/api/math/Sphere.java +++ b/api/src/main/java/net/thenextlvl/gopaint/api/math/Sphere.java @@ -18,80 +18,27 @@ */ package net.thenextlvl.gopaint.api.math; -import org.bukkit.Axis; -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.jetbrains.annotations.Nullable; +import com.sk89q.worldedit.math.BlockVector3; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; +import java.util.HashSet; +import java.util.Set; public class Sphere { - /** - * Returns a stream of blocks within a specified radius from a given middle point. - * - * @param middlePoint The middle point from which to calculate the radius. - * @param radius The radius value. - * @param axis The axis along which to calculate the radius (optional). - * @param air Whether air blocks should be included. - * @return A stream of blocks within the specified radius. - */ - public static Stream getBlocksInRadius(Location middlePoint, int radius, @Nullable Axis axis, boolean air) { - List blocks = new ArrayList<>(); - Location loc1 = middlePoint.clone().add(-radius / 2d, -radius / 2d, -radius / 2d).getBlock().getLocation(); - Location loc2 = middlePoint.clone().add(radius / 2d, radius / 2d, radius / 2d).getBlock().getLocation(); + public static Set getBlocksInRadius(BlockVector3 position, double radius) { + var vectors = new HashSet(); + var loc1 = position.subtract((int) radius, (int) radius, (int) radius); + var loc2 = position.add((int) radius, (int) radius, (int) radius); - switch (axis) { - case Y: - for (int x = loc1.getBlockX(); x <= loc2.getBlockX(); x++) { - for (int z = loc1.getBlockZ(); z <= loc2.getBlockZ(); z++) { - Location location = new Location(loc1.getWorld(), x, middlePoint.getBlockY(), z); - if (passesDefaultChecks(location, middlePoint, radius)) { - blocks.add(location.getBlock()); - } - } + for (var x = loc1.getX(); x <= loc2.getX(); x++) { + for (var y = loc1.getY(); y <= loc2.getY(); y++) { + for (var z = loc1.getZ(); z <= loc2.getZ(); z++) { + var vector3 = BlockVector3.at(x, y, z); + if (vector3.distance(position) >= radius) continue; + vectors.add(vector3); } - break; - case X: - for (int y = loc1.getBlockY(); y <= loc2.getBlockY(); y++) { - for (int z = loc1.getBlockZ(); z <= loc2.getBlockZ(); z++) { - Location location = new Location(loc1.getWorld(), middlePoint.getBlockX(), y, z); - if (passesDefaultChecks(location, middlePoint, radius)) { - blocks.add(location.getBlock()); - } - } - } - break; - case Z: - for (int x = loc1.getBlockX(); x <= loc2.getBlockX(); x++) { - for (int y = loc1.getBlockY(); y <= loc2.getBlockY(); y++) { - Location location = new Location(loc1.getWorld(), x, y, middlePoint.getBlockZ()); - if (passesDefaultChecks(location, middlePoint, radius)) { - blocks.add(location.getBlock()); - } - } - } - break; - case null: - for (int x = loc1.getBlockX(); x <= loc2.getBlockX(); x++) { - for (int y = loc1.getBlockY(); y <= loc2.getBlockY(); y++) { - for (int z = loc1.getBlockZ(); z <= loc2.getBlockZ(); z++) { - Location location = new Location(loc1.getWorld(), x, y, z); - if (passesDefaultChecks(location, middlePoint, radius)) { - blocks.add(location.getBlock()); - } - } - } - } - break; + } } - if (air) return blocks.stream(); - return blocks.stream().filter(block -> !block.isEmpty()); - } - - private static boolean passesDefaultChecks(Location location, Location middlePoint, int radius) { - return location.distance(middlePoint) < radius / 2d; + return vectors; } } diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/math/Surface.java b/api/src/main/java/net/thenextlvl/gopaint/api/math/Surface.java deleted file mode 100644 index 5a823328..00000000 --- a/api/src/main/java/net/thenextlvl/gopaint/api/math/Surface.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * goPaint is designed to simplify painting inside of Minecraft. - * Copyright (C) Arcaniax-Development - * Copyright (C) Arcaniax team and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package net.thenextlvl.gopaint.api.math; - -import net.thenextlvl.gopaint.api.model.SurfaceMode; -import org.bukkit.Location; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; - -public class Surface { - - /** - * Checks if the given block is on the surface. - * - * @param block the block to check - * @return true if the block is on the surface, false otherwise - */ - public static boolean isDirectlyOnSurface(Block block) { - return block.isSolid() && !block.getRelative(BlockFace.UP).isSolid(); - } - - /** - * Checks if a block is on the surface, taking into account the player's location. - * - * @param block the block to check if the player is near - * @param playerLoc the player's location - * @return true if the block is on the surface from the player's location, false otherwise - */ - public static boolean isRelativelyOnSurface(Block block, Location playerLoc) { - Location location = block.getLocation(); - - playerLoc.add(0, 1.5, 0); - double distanceX = playerLoc.getX() - block.getX(); - double distanceY = playerLoc.getY() - block.getY(); - double distanceZ = playerLoc.getZ() - block.getZ(); - if (distanceX > 1) { - location.add(1, 0, 0); - } else if (distanceX > 0) { - location.add(0.5, 0, 0); - } - if (distanceY > 1) { - location.add(0, 1, 0); - } else if (distanceY > 0) { - location.add(0, 0.5, 0); - } - if (distanceZ > 1) { - location.add(0, 0, 1); - } else if (distanceZ > 0) { - location.add(0, 0, 0.5); - } - - double distance = location.distance(playerLoc); - for (int x = 1; x < distance; x++) { - double moveX = distanceX * (x / distance); - double moveY = distanceY * (x / distance); - double moveZ = distanceZ * (x / distance); - Location checkLoc = location.add(moveX, moveY, moveZ); - if (!checkLoc.getBlock().isEmpty()) { - return false; - } - } - return true; - } - - /** - * Checks if a given block is on the surface based on the specified surface mode and location. - * - * @param block the block to check - * @param surfaceMode the surface mode to use for the check - * @param location the location to use for the check - * @return true if the block is on the surface based on the surface mode and location, false otherwise - */ - public static boolean isOnSurface(Block block, SurfaceMode surfaceMode, Location location) { - return switch (surfaceMode) { - case RELATIVE -> isRelativelyOnSurface(block, location); - case DIRECT -> isDirectlyOnSurface(block); - case DISABLED -> true; - }; - } -} diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/math/curve/BezierSpline.java b/api/src/main/java/net/thenextlvl/gopaint/api/math/curve/BezierSpline.java index 547f9c4a..3db3321e 100644 --- a/api/src/main/java/net/thenextlvl/gopaint/api/math/curve/BezierSpline.java +++ b/api/src/main/java/net/thenextlvl/gopaint/api/math/curve/BezierSpline.java @@ -18,8 +18,9 @@ */ package net.thenextlvl.gopaint.api.math.curve; +import com.fastasyncworldedit.core.math.MutableBlockVector3; +import com.sk89q.worldedit.math.BlockVector3; import lombok.Getter; -import org.bukkit.util.Vector; import org.jetbrains.annotations.Contract; import java.util.Arrays; @@ -29,12 +30,12 @@ @Getter public class BezierSpline { - private final Vector[] knots; + private final MutableBlockVector3[] knots; private final BezierSplineSegment[] segments; private final double curveLength; - public BezierSpline(List curve) { - this.knots = curve.toArray(new Vector[0]); + public BezierSpline(List curve) { + this.knots = curve.toArray(new MutableBlockVector3[0]); this.segments = new BezierSplineSegment[knots.length - 1]; for (var segment = 0; segment < knots.length - 1; segment++) { segments[segment] = new BezierSplineSegment(knots[segment], knots[segment + 1]); @@ -53,7 +54,7 @@ public double calculateLength() { } @Contract(pure = true) - public Vector getPoint(double point) { + public BlockVector3 getPoint(double point) { if (point >= segments.length) { return getPoint(segments.length - 1, 1); } else { @@ -62,7 +63,7 @@ public Vector getPoint(double point) { } @Contract(pure = true) - public Vector getPoint(int segmentIndex, double factor) { + public BlockVector3 getPoint(int segmentIndex, double factor) { assert (segmentIndex < segments.length); assert (factor > 0 && factor <= 1); return segments[segmentIndex].getPoint(factor); @@ -79,19 +80,19 @@ public void calculateControlPoints() { ? OptionalDouble.of(knots[0].getZ()) : OptionalDouble.empty(); if (segments.length == 1) { - var midpoint = new Vector(0, 0, 0); - midpoint.setX((segments[0].getStartPoint().getX() + segments[0].getEndPoint().getX()) / 2); - midpoint.setY((segments[0].getStartPoint().getY() + segments[0].getEndPoint().getY()) / 2); - midpoint.setZ((segments[0].getStartPoint().getZ() + segments[0].getEndPoint().getZ()) / 2); + var midpoint = new MutableBlockVector3(0, 0, 0); + midpoint.mutX((segments[0].getStartPoint().getX() + segments[0].getEndPoint().getX()) / 2); + midpoint.mutY((segments[0].getStartPoint().getY() + segments[0].getEndPoint().getY()) / 2); + midpoint.mutZ((segments[0].getStartPoint().getZ() + segments[0].getEndPoint().getZ()) / 2); segments[0].setIntermediatePoint1(midpoint); - segments[0].setIntermediatePoint2(midpoint.clone()); + segments[0].setIntermediatePoint2(new MutableBlockVector3(midpoint)); } else { segments[0].setCoefficient1(0); segments[0].setCoefficient2(2); segments[0].setCoefficient3(1); - segments[0].getResult().setX(knots[0].getX() + 2 * knots[1].getX()); - segments[0].getResult().setY(knots[0].getY() + 2 * knots[1].getY()); - segments[0].getResult().setZ(knots[0].getZ() + 2 * knots[1].getZ()); + segments[0].getResult().mutX(knots[0].getX() + 2 * knots[1].getX()); + segments[0].getResult().mutY(knots[0].getY() + 2 * knots[1].getY()); + segments[0].getResult().mutZ(knots[0].getZ() + 2 * knots[1].getZ()); int n = knots.length - 1; float m; @@ -100,44 +101,44 @@ public void calculateControlPoints() { segments[i].setCoefficient1(1); segments[i].setCoefficient2(4); segments[i].setCoefficient3(1); - segments[i].getResult().setX(4 * knots[i].getX() + 2 * knots[i + 1].getX()); - segments[i].getResult().setY(4 * knots[i].getY() + 2 * knots[i + 1].getY()); - segments[i].getResult().setZ(4 * knots[i].getZ() + 2 * knots[i + 1].getZ()); + segments[i].getResult().mutX(4 * knots[i].getX() + 2 * knots[i + 1].getX()); + segments[i].getResult().mutY(4 * knots[i].getY() + 2 * knots[i + 1].getY()); + segments[i].getResult().mutZ(4 * knots[i].getZ() + 2 * knots[i + 1].getZ()); } segments[n - 1].setCoefficient1(2); segments[n - 1].setCoefficient2(7); segments[n - 1].setCoefficient3(0); - segments[n - 1].getResult().setX(8 * knots[n - 1].getX() + knots[n].getX()); - segments[n - 1].getResult().setY(8 * knots[n - 1].getY() + knots[n].getY()); - segments[n - 1].getResult().setZ(8 * knots[n - 1].getZ() + knots[n].getZ()); + segments[n - 1].getResult().mutX(8 * knots[n - 1].getX() + knots[n].getX()); + segments[n - 1].getResult().mutY(8 * knots[n - 1].getY() + knots[n].getY()); + segments[n - 1].getResult().mutZ(8 * knots[n - 1].getZ() + knots[n].getZ()); for (int i = 1; i < n; i++) { m = segments[i].getCoefficient1() / segments[i - 1].getCoefficient2(); segments[i].setCoefficient2(segments[i].getCoefficient2() - m * segments[i - 1].getCoefficient3()); - segments[i].getResult().setX(segments[i].getResult().getX() - m * segments[i - 1].getResult().getX()); - segments[i].getResult().setY(segments[i].getResult().getY() - m * segments[i - 1].getResult().getY()); - segments[i].getResult().setZ(segments[i].getResult().getZ() - m * segments[i - 1].getResult().getZ()); + segments[i].getResult().mutX(segments[i].getResult().getX() - m * segments[i - 1].getResult().getX()); + segments[i].getResult().mutY(segments[i].getResult().getY() - m * segments[i - 1].getResult().getY()); + segments[i].getResult().mutZ(segments[i].getResult().getZ() - m * segments[i - 1].getResult().getZ()); } - segments[n - 1].getIntermediatePoint1().setX(segments[n - 1].getResult().getX() / segments[n - 1].getCoefficient2()); - segments[n - 1].getIntermediatePoint1().setY(segments[n - 1].getResult().getY() / segments[n - 1].getCoefficient2()); - segments[n - 1].getIntermediatePoint1().setZ(segments[n - 1].getResult().getZ() / segments[n - 1].getCoefficient2()); + segments[n - 1].getIntermediatePoint1().mutX(segments[n - 1].getResult().getX() / segments[n - 1].getCoefficient2()); + segments[n - 1].getIntermediatePoint1().mutY(segments[n - 1].getResult().getY() / segments[n - 1].getCoefficient2()); + segments[n - 1].getIntermediatePoint1().mutZ(segments[n - 1].getResult().getZ() / segments[n - 1].getCoefficient2()); for (int i = n - 2; i >= 0; i--) { - segments[i].getIntermediatePoint1().setX((segments[i].getResult().getX() - segments[i].getCoefficient3() * segments[i + 1].getIntermediatePoint1().getX()) / segments[i].getCoefficient2()); - segments[i].getIntermediatePoint1().setY((segments[i].getResult().getY() - segments[i].getCoefficient3() * segments[i + 1].getIntermediatePoint1().getY()) / segments[i].getCoefficient2()); - segments[i].getIntermediatePoint1().setZ((segments[i].getResult().getZ() - segments[i].getCoefficient3() * segments[i + 1].getIntermediatePoint1().getZ()) / segments[i].getCoefficient2()); + segments[i].getIntermediatePoint1().mutX((segments[i].getResult().getX() - segments[i].getCoefficient3() * segments[i + 1].getIntermediatePoint1().getX()) / segments[i].getCoefficient2()); + segments[i].getIntermediatePoint1().mutY((segments[i].getResult().getY() - segments[i].getCoefficient3() * segments[i + 1].getIntermediatePoint1().getY()) / segments[i].getCoefficient2()); + segments[i].getIntermediatePoint1().mutZ((segments[i].getResult().getZ() - segments[i].getCoefficient3() * segments[i + 1].getIntermediatePoint1().getZ()) / segments[i].getCoefficient2()); } for (int i = 0; i < n - 1; i++) { - segments[i].getIntermediatePoint2().setX(2 * knots[i + 1].getX() - segments[i + 1].getIntermediatePoint1().getX()); - segments[i].getIntermediatePoint2().setY(2 * knots[i + 1].getY() - segments[i + 1].getIntermediatePoint1().getY()); - segments[i].getIntermediatePoint2().setZ(2 * knots[i + 1].getZ() - segments[i + 1].getIntermediatePoint1().getZ()); + segments[i].getIntermediatePoint2().mutX(2 * knots[i + 1].getX() - segments[i + 1].getIntermediatePoint1().getX()); + segments[i].getIntermediatePoint2().mutY(2 * knots[i + 1].getY() - segments[i + 1].getIntermediatePoint1().getY()); + segments[i].getIntermediatePoint2().mutZ(2 * knots[i + 1].getZ() - segments[i + 1].getIntermediatePoint1().getZ()); } - segments[n - 1].getIntermediatePoint2().setX(0.5 * (knots[n].getX() + segments[n - 1].getIntermediatePoint1().getX())); - segments[n - 1].getIntermediatePoint2().setY(0.5 * (knots[n].getY() + segments[n - 1].getIntermediatePoint1().getY())); - segments[n - 1].getIntermediatePoint2().setZ(0.5 * (knots[n].getZ() + segments[n - 1].getIntermediatePoint1().getZ())); + segments[n - 1].getIntermediatePoint2().mutX(0.5 * (knots[n].getX() + segments[n - 1].getIntermediatePoint1().getX())); + segments[n - 1].getIntermediatePoint2().mutY(0.5 * (knots[n].getY() + segments[n - 1].getIntermediatePoint1().getY())); + segments[n - 1].getIntermediatePoint2().mutZ(0.5 * (knots[n].getZ() + segments[n - 1].getIntermediatePoint1().getZ())); } xFlat.ifPresent(value -> Arrays.stream(segments).forEach(segment -> segment.setX(value))); @@ -149,4 +150,4 @@ public void calculateControlPoints() { public String toString() { return knots.length + " points."; } -} +} \ No newline at end of file diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/math/curve/BezierSplineSegment.java b/api/src/main/java/net/thenextlvl/gopaint/api/math/curve/BezierSplineSegment.java index 207c3d2e..39706c6e 100644 --- a/api/src/main/java/net/thenextlvl/gopaint/api/math/curve/BezierSplineSegment.java +++ b/api/src/main/java/net/thenextlvl/gopaint/api/math/curve/BezierSplineSegment.java @@ -18,10 +18,11 @@ */ package net.thenextlvl.gopaint.api.math.curve; +import com.fastasyncworldedit.core.math.MutableBlockVector3; +import com.sk89q.worldedit.math.BlockVector3; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; -import org.bukkit.util.Vector; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; @@ -32,11 +33,11 @@ @RequiredArgsConstructor public class BezierSplineSegment { - private final Vector startPoint; - private final Vector endPoint; + private final MutableBlockVector3 startPoint; + private final MutableBlockVector3 endPoint; - private Vector intermediatePoint1 = new Vector(0, 0, 0); - private Vector intermediatePoint2 = new Vector(0, 0, 0); + private MutableBlockVector3 intermediatePoint1 = MutableBlockVector3.at(0, 0, 0); + private MutableBlockVector3 intermediatePoint2 = MutableBlockVector3.at(0, 0, 0); private float coefficient1; private float coefficient2; @@ -44,35 +45,35 @@ public class BezierSplineSegment { private @Nullable Double xFlat, yFlat, zFlat; - private Vector result = new Vector(0, 0, 0); + private MutableBlockVector3 result = MutableBlockVector3.at(0, 0, 0); public void setX(double xFlat) { - startPoint.setX(xFlat); - intermediatePoint1.setX(xFlat); - intermediatePoint2.setX(xFlat); - endPoint.setX(xFlat); + startPoint.mutX(xFlat); + intermediatePoint1.mutX(xFlat); + intermediatePoint2.mutX(xFlat); + endPoint.mutX(xFlat); this.xFlat = xFlat; } public void setY(double yFlat) { - startPoint.setY(yFlat); - intermediatePoint1.setY(yFlat); - intermediatePoint2.setY(yFlat); - endPoint.setY(yFlat); + startPoint.mutY(yFlat); + intermediatePoint1.mutY(yFlat); + intermediatePoint2.mutY(yFlat); + endPoint.mutY(yFlat); this.yFlat = yFlat; } public void setZ(double zFlat) { - startPoint.setZ(zFlat); - intermediatePoint1.setZ(zFlat); - intermediatePoint2.setZ(zFlat); - endPoint.setZ(zFlat); + startPoint.mutZ(zFlat); + intermediatePoint1.mutZ(zFlat); + intermediatePoint2.mutZ(zFlat); + endPoint.mutZ(zFlat); this.zFlat = zFlat; } @Contract(pure = true) public double getCurveLength() { - var current = startPoint.clone(); + BlockVector3 current = startPoint; var lengths = new double[20]; for (int i = 1; i < lengths.length; i++) { var point = getPoint(i * 0.05); @@ -83,7 +84,7 @@ public double getCurveLength() { } @Contract(pure = true) - public Vector getPoint(double factor) { + public BlockVector3 getPoint(double factor) { var x = Objects.requireNonNullElseGet(xFlat, () -> calculatePoint( factor, startPoint.getX(), intermediatePoint1.getX(), intermediatePoint2.getX(), endPoint.getX() )); @@ -93,7 +94,7 @@ public Vector getPoint(double factor) { var z = Objects.requireNonNullElseGet(zFlat, () -> calculatePoint( factor, startPoint.getZ(), intermediatePoint1.getZ(), intermediatePoint2.getZ(), endPoint.getZ() )); - return new Vector(x, y, z); + return BlockVector3.at(x, y, z); } @Contract(pure = true) @@ -101,4 +102,4 @@ private double calculatePoint(double factor, double startPoint, double intermedi return (Math.pow(1 - factor, 3) * startPoint) + (3 * Math.pow(1 - factor, 2) * factor * intermediatePoint1) + (3 * (1 - factor) * factor * factor * intermediatePoint2) + (Math.pow(factor, 3) * endPoint); } -} +} \ No newline at end of file diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/model/Block.java b/api/src/main/java/net/thenextlvl/gopaint/api/model/Block.java new file mode 100644 index 00000000..71921073 --- /dev/null +++ b/api/src/main/java/net/thenextlvl/gopaint/api/model/Block.java @@ -0,0 +1,13 @@ +package net.thenextlvl.gopaint.api.model; + +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.registry.BlockMaterial; + +public record Block(BaseBlock base, BlockVector3 vector, Extent world) { + + public BlockMaterial material() { + return base().getMaterial(); + } +} diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/model/MaskMode.java b/api/src/main/java/net/thenextlvl/gopaint/api/model/MaskMode.java deleted file mode 100644 index c659ab3a..00000000 --- a/api/src/main/java/net/thenextlvl/gopaint/api/model/MaskMode.java +++ /dev/null @@ -1,34 +0,0 @@ -package net.thenextlvl.gopaint.api.model; - -import com.sk89q.worldedit.EditSession; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.experimental.Accessors; -import net.kyori.adventure.translation.Translatable; -import net.thenextlvl.gopaint.api.brush.Brush; -import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import org.bukkit.block.Block; - -@Getter -@RequiredArgsConstructor -@Accessors(fluent = true) -public enum MaskMode implements Translatable { - /** - * This enumeration represents that no mask should be applied. - */ - DISABLED("mask.mode.disabled"), - /** - * This enumeration represents that the mask material defined in the interface should be applied - * - * @see Brush#passesMaskCheck(BrushSettings, EditSession, Block) - */ - INTERFACE("mask.mode.interface"), - /** - * This enumeration represents the complex mask defined by WorldEdit - * - * @see Brush#passesMaskCheck(BrushSettings, EditSession, Block) - */ - WORLDEDIT("mask.mode.worldedit"); - - private final String translationKey; -} diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/model/PluginConfig.java b/api/src/main/java/net/thenextlvl/gopaint/api/model/PluginConfig.java index 4ae709d5..b9a57f68 100644 --- a/api/src/main/java/net/thenextlvl/gopaint/api/model/PluginConfig.java +++ b/api/src/main/java/net/thenextlvl/gopaint/api/model/PluginConfig.java @@ -27,7 +27,7 @@ public record BrushConfig( @SerializedName("disabled-worlds") Set disabledWorlds, @SerializedName("enabled-by-default") boolean enabledByDefault, @SerializedName("default-mask") Material defaultMask, - @SerializedName("mask-mode") MaskMode maskMode, + @SerializedName("mask") boolean mask, @SerializedName("surface-mode") SurfaceMode surfaceMode, @SerializedName("default-blocks") List defaultBlocks ) { diff --git a/api/src/main/java/net/thenextlvl/gopaint/api/model/SurfaceMode.java b/api/src/main/java/net/thenextlvl/gopaint/api/model/SurfaceMode.java index 88f50268..3e0749e8 100644 --- a/api/src/main/java/net/thenextlvl/gopaint/api/model/SurfaceMode.java +++ b/api/src/main/java/net/thenextlvl/gopaint/api/model/SurfaceMode.java @@ -1,35 +1,32 @@ package net.thenextlvl.gopaint.api.model; +import com.fastasyncworldedit.core.function.mask.SurfaceMask; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.experimental.Accessors; import net.kyori.adventure.translation.Translatable; -import net.thenextlvl.gopaint.api.math.Surface; -import org.bukkit.Location; -import org.bukkit.block.Block; +import net.thenextlvl.gopaint.api.brush.mask.VisibleMask; @Getter @RequiredArgsConstructor @Accessors(fluent = true) public enum SurfaceMode implements Translatable { /** - * This enumeration represents a more intuitive check. - * - * @see Surface#isDirectlyOnSurface(Block) + * This enumeration represents that surface mode is disabled. */ - DIRECT("surface.mode.direct"), + DISABLED("surface.mode.disabled"), /** - * This enumeration represents that surface mode is disabled. + * This enumeration represents a more intuitive check. * - * @see Surface#isOnSurface(Block, SurfaceMode, Location) + * @see SurfaceMask */ - DISABLED("surface.mode.disabled"), + EXPOSED("surface.mode.exposed"), /** * This enumeration represents the original surface mode check. * - * @see Surface#isRelativelyOnSurface(Block, Location) + * @see VisibleMask */ - RELATIVE("surface.mode.relative"); + VISIBLE("surface.mode.visible"); private final String translationKey; } diff --git a/src/main/java/net/thenextlvl/gopaint/GoPaintPlugin.java b/src/main/java/net/thenextlvl/gopaint/GoPaintPlugin.java index 2442b822..68b96fab 100644 --- a/src/main/java/net/thenextlvl/gopaint/GoPaintPlugin.java +++ b/src/main/java/net/thenextlvl/gopaint/GoPaintPlugin.java @@ -16,7 +16,6 @@ import net.thenextlvl.gopaint.api.brush.BrushController; import net.thenextlvl.gopaint.api.brush.BrushRegistry; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import net.thenextlvl.gopaint.api.model.MaskMode; import net.thenextlvl.gopaint.api.model.PluginConfig; import net.thenextlvl.gopaint.api.model.SurfaceMode; import net.thenextlvl.gopaint.brush.CraftBrushController; @@ -57,7 +56,7 @@ public class GoPaintPlugin extends JavaPlugin implements GoPaintProvider { private final FileIO configFile = new GsonFile<>(IO.of(getDataFolder(), "config.json"), new PluginConfig( new PluginConfig.BrushConfig(Material.FEATHER, new NamespacedKey("gopaint", "sphere_brush"), 100, 10, 50, - Axis.Y, 50, 50, Set.of("disabled"), true, Material.SPONGE, MaskMode.INTERFACE, SurfaceMode.DIRECT, + Axis.Y, 50, 50, Set.of("disabled"), true, Material.SPONGE, true, SurfaceMode.EXPOSED, List.of(Material.STONE)), new PluginConfig.ThicknessConfig(1, 5), new PluginConfig.AngleConfig(2, 5, 10, 40, 85), diff --git a/src/main/java/net/thenextlvl/gopaint/brush/CraftBrushController.java b/src/main/java/net/thenextlvl/gopaint/brush/CraftBrushController.java index 133f3b41..90e38a1a 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/CraftBrushController.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/CraftBrushController.java @@ -24,7 +24,6 @@ import net.thenextlvl.gopaint.api.brush.BrushController; import net.thenextlvl.gopaint.api.brush.setting.ItemBrushSettings; import net.thenextlvl.gopaint.api.brush.setting.PlayerBrushSettings; -import net.thenextlvl.gopaint.api.model.MaskMode; import net.thenextlvl.gopaint.api.model.SurfaceMode; import net.thenextlvl.gopaint.brush.setting.CraftItemBrushSettings; import net.thenextlvl.gopaint.brush.setting.CraftPlayerBrushSettings; @@ -61,10 +60,7 @@ public Optional parseBrushSettings(ItemMeta itemMeta) { var container = itemMeta.getPersistentDataContainer(); var brushSize = container.get(new NamespacedKey("gopaint", "size"), PersistentDataType.INTEGER); - - var maskMode = Optional.ofNullable(container.get(new NamespacedKey("gopaint", "mask_mode"), PersistentDataType.STRING)) - .map(MaskMode::valueOf) - .orElse(null); + var maskEnabled = container.get(new NamespacedKey("gopaint", "mask_enabled"), PersistentDataType.BOOLEAN); var surfaceMode = Optional.ofNullable(container.get(new NamespacedKey("gopaint", "surface_mode"), PersistentDataType.STRING)) .map(SurfaceMode::valueOf) @@ -75,7 +71,7 @@ public Optional parseBrushSettings(ItemMeta itemMeta) { .flatMap(plugin.brushRegistry()::getBrush) .orElse(null); - if (brushSize == null || maskMode == null || surfaceMode == null || brush == null) + if (brushSize == null || maskEnabled == null || surfaceMode == null || brush == null) return Optional.empty(); var chance = container.getOrDefault(new NamespacedKey("gopaint", "chance"), PersistentDataType.INTEGER, 0); @@ -92,7 +88,7 @@ public Optional parseBrushSettings(ItemMeta itemMeta) { .orElse(Axis.Y); var mask = Optional.ofNullable(container.get(new NamespacedKey("gopaint", "mask"), PersistentDataType.STRING)) .map(Material::matchMaterial) - .orElse(null); + .orElseThrow(); var blocks = Optional.ofNullable(container.get(new NamespacedKey("gopaint", "blocks"), PersistentDataType.STRING)) .map(string -> string.split(",")) .stream() @@ -103,7 +99,7 @@ public Optional parseBrushSettings(ItemMeta itemMeta) { return Optional.of(CraftItemBrushSettings.builder() .brushSize(brushSize) - .maskMode(maskMode) + .maskEnabled(maskEnabled) .surfaceMode(surfaceMode) .brush(brush) .chance(chance) diff --git a/src/main/java/net/thenextlvl/gopaint/brush/CraftBrushRegistry.java b/src/main/java/net/thenextlvl/gopaint/brush/CraftBrushRegistry.java index e405523e..9a380c55 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/CraftBrushRegistry.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/CraftBrushRegistry.java @@ -3,7 +3,7 @@ import com.google.common.base.Preconditions; import net.kyori.adventure.key.Key; import net.thenextlvl.gopaint.GoPaintPlugin; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.PatternBrush; import net.thenextlvl.gopaint.api.brush.BrushRegistry; import net.thenextlvl.gopaint.brush.standard.*; @@ -13,7 +13,7 @@ import java.util.stream.Stream; public class CraftBrushRegistry implements BrushRegistry { - private final List brushes = new LinkedList<>(); + private final List brushes = new LinkedList<>(); public CraftBrushRegistry(GoPaintPlugin plugin) { registerBrush(new SphereBrush(plugin)); @@ -30,28 +30,28 @@ public CraftBrushRegistry(GoPaintPlugin plugin) { } @Override - public Stream getBrushes() { + public Stream getBrushes() { return brushes.stream().sorted(); } @Override - public boolean isRegistered(Brush brush) { + public boolean isRegistered(PatternBrush brush) { return brushes.contains(brush); } @Override - public void registerBrush(Brush brush) throws IllegalStateException { + public void registerBrush(PatternBrush brush) throws IllegalStateException { Preconditions.checkState(!isRegistered(brush), "Brush already registered"); brushes.add(brush); } @Override - public void unregisterBrush(Brush brush) throws IllegalStateException { + public void unregisterBrush(PatternBrush brush) throws IllegalStateException { if (!brushes.remove(brush)) throw new IllegalStateException("Brush not registered"); } @Override - public Optional getBrush(Key key) { + public Optional getBrush(Key key) { return brushes.stream() .filter(brush -> brush.key().equals(key)) .findAny(); diff --git a/src/main/java/net/thenextlvl/gopaint/brush/pattern/AnglePattern.java b/src/main/java/net/thenextlvl/gopaint/brush/pattern/AnglePattern.java new file mode 100644 index 00000000..5d8750ac --- /dev/null +++ b/src/main/java/net/thenextlvl/gopaint/brush/pattern/AnglePattern.java @@ -0,0 +1,31 @@ +package net.thenextlvl.gopaint.brush.pattern; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import net.thenextlvl.gopaint.api.brush.pattern.BuildPattern; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; +import net.thenextlvl.gopaint.api.math.Height; +import net.thenextlvl.gopaint.api.model.Block; + +public record AnglePattern( + EditSession session, + BlockVector3 position, + Player player, + BrushSettings settings +) implements BuildPattern { + + @Override + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + var block = new Block(applyBlock(get), set, extent); + + if (Height.getAverageHeightDiffAngle(block, 1) < 0.1) return false; + if (Height.getAverageHeightDiffAngle(block, settings().getAngleDistance()) + >= Math.tan(Math.toRadians(settings().getAngleHeightDifference())) + ) return false; + + return set.setBlock(extent, getRandomBlockState()); + } +} diff --git a/src/main/java/net/thenextlvl/gopaint/brush/pattern/FracturePattern.java b/src/main/java/net/thenextlvl/gopaint/brush/pattern/FracturePattern.java new file mode 100644 index 00000000..e68ef687 --- /dev/null +++ b/src/main/java/net/thenextlvl/gopaint/brush/pattern/FracturePattern.java @@ -0,0 +1,33 @@ +package net.thenextlvl.gopaint.brush.pattern; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import net.thenextlvl.gopaint.api.brush.pattern.BuildPattern; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; +import net.thenextlvl.gopaint.api.math.Height; +import net.thenextlvl.gopaint.api.model.Block; + +public record FracturePattern( + EditSession session, + BlockVector3 position, + Player player, + BrushSettings settings +) implements BuildPattern { + + @Override + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + var block = new Block(applyBlock(get), set, extent); + + if (Height.getAverageHeightDiffFracture(block, + Height.getNearestNonEmptyBlock(block), 1 + ) < 0.1) return false; + if (Height.getAverageHeightDiffFracture(block, + Height.getNearestNonEmptyBlock(block), settings.getFractureStrength() + ) < 0.1) return false; + + return set.setBlock(extent, getRandomBlockState()); + } +} diff --git a/src/main/java/net/thenextlvl/gopaint/brush/pattern/GradientPattern.java b/src/main/java/net/thenextlvl/gopaint/brush/pattern/GradientPattern.java new file mode 100644 index 00000000..cd9f3781 --- /dev/null +++ b/src/main/java/net/thenextlvl/gopaint/brush/pattern/GradientPattern.java @@ -0,0 +1,45 @@ +package net.thenextlvl.gopaint.brush.pattern; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BlockState; +import net.thenextlvl.gopaint.api.brush.pattern.BuildPattern; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; + +import java.util.Objects; + +public record GradientPattern( + EditSession session, + BlockVector3 position, + Player player, + BrushSettings settings +) implements BuildPattern { + + @Override + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + if (settings().getRandom().nextDouble() <= getRate(set)) return false; + return set.setBlock(extent, getRandomBlockState(set.getY())); + } + + public BlockState getRandomBlockState(int altitude) { + var index = Math.clamp(getRandom(altitude), 0, settings().getBlocks().size() - 1); + var block = BukkitAdapter.asBlockType(settings().getBlocks().get(index)); + return Objects.requireNonNull(block).getDefaultState(); + } + + private int getRandom(int altitude) { + if (settings().getBlocks().size() == 1) return 1; + var y = position().getY() - (settings().getBrushSize() / 2d); + var _y = (altitude - y) / settings().getBrushSize() * settings().getBlocks().size(); + return (int) (_y + (settings().getRandom().nextDouble() * 2 - 1) * (settings().getMixingStrength() / 100d)); + } + + private double getRate(BlockVector3 position) { + var size = settings().getBrushSize() * ((100d - settings().getFalloffStrength()) / 100d); + return (position.distance(position()) - size) / (settings().getBrushSize() - size); + } +} diff --git a/src/main/java/net/thenextlvl/gopaint/brush/pattern/OverlayPattern.java b/src/main/java/net/thenextlvl/gopaint/brush/pattern/OverlayPattern.java new file mode 100644 index 00000000..a5f87701 --- /dev/null +++ b/src/main/java/net/thenextlvl/gopaint/brush/pattern/OverlayPattern.java @@ -0,0 +1,33 @@ +package net.thenextlvl.gopaint.brush.pattern; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BaseBlock; +import net.thenextlvl.gopaint.api.brush.pattern.BuildPattern; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; + +public record OverlayPattern( + EditSession session, + BlockVector3 position, + Player player, + BrushSettings settings +) implements BuildPattern { + + @Override + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + if (!isOverlay(get, applyBlock(get))) return false; + return set.setBlock(extent, getRandomBlockState()); + } + + private boolean isOverlay(BlockVector3 position, BaseBlock block) { + for (var i = 1; i <= settings().getThickness(); i++) { + if (!block.getMaterial().isMovementBlocker()) continue; + if (position.getStateRelativeY(player().getWorld(), i).getMaterial().isMovementBlocker()) continue; + return true; + } + return false; + } +} diff --git a/src/main/java/net/thenextlvl/gopaint/brush/pattern/ShufflePattern.java b/src/main/java/net/thenextlvl/gopaint/brush/pattern/ShufflePattern.java new file mode 100644 index 00000000..b42d434a --- /dev/null +++ b/src/main/java/net/thenextlvl/gopaint/brush/pattern/ShufflePattern.java @@ -0,0 +1,22 @@ +package net.thenextlvl.gopaint.brush.pattern; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import net.thenextlvl.gopaint.api.brush.pattern.BuildPattern; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; + +public record ShufflePattern( + EditSession session, + BlockVector3 position, + Player player, + BrushSettings settings +) implements BuildPattern { + + @Override + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + return set.setBlock(extent, getRandomBlockState()); + } +} diff --git a/src/main/java/net/thenextlvl/gopaint/brush/pattern/SplatterPattern.java b/src/main/java/net/thenextlvl/gopaint/brush/pattern/SplatterPattern.java new file mode 100644 index 00000000..d7dacb38 --- /dev/null +++ b/src/main/java/net/thenextlvl/gopaint/brush/pattern/SplatterPattern.java @@ -0,0 +1,29 @@ +package net.thenextlvl.gopaint.brush.pattern; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import net.thenextlvl.gopaint.api.brush.pattern.BuildPattern; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; + +public record SplatterPattern( + EditSession session, + BlockVector3 position, + Player player, + BrushSettings settings +) implements BuildPattern { + + @Override + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + if (settings().getRandom().nextDouble() <= getRate(set)) return false; + return set.setBlock(extent, getRandomBlockState()); + } + + private double getRate(BlockVector3 position) { + var size = (double) settings().getBrushSize(); + var falloff = (100.0 - (double) settings().getFalloffStrength()) / 100.0; + return (position.distance(position()) - size * falloff) / (size - size * falloff); + } +} diff --git a/src/main/java/net/thenextlvl/gopaint/brush/pattern/SplinePattern.java b/src/main/java/net/thenextlvl/gopaint/brush/pattern/SplinePattern.java new file mode 100644 index 00000000..e8618c20 --- /dev/null +++ b/src/main/java/net/thenextlvl/gopaint/brush/pattern/SplinePattern.java @@ -0,0 +1,42 @@ +package net.thenextlvl.gopaint.brush.pattern; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BlockState; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.experimental.Accessors; +import net.thenextlvl.gopaint.api.brush.pattern.BuildPattern; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; + +import java.util.Objects; + +@Getter +@Setter +@RequiredArgsConstructor +@Accessors(fluent = true, chain = false) +public class SplinePattern implements BuildPattern { + private final EditSession session; + private final BlockVector3 position; + private final Player player; + private final BrushSettings settings; + + private int random; + + @Override + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + return set.setBlock(extent, getRandomBlockState()); + } + + @Override + public BlockState getRandomBlockState() { + var index = Math.clamp(random(), 0, settings().getBlocks().size() - 1); + var block = BukkitAdapter.asBlockType(settings().getBlocks().get(index)); + return Objects.requireNonNull(block).getDefaultState(); + } +} diff --git a/src/main/java/net/thenextlvl/gopaint/brush/pattern/SprayPattern.java b/src/main/java/net/thenextlvl/gopaint/brush/pattern/SprayPattern.java new file mode 100644 index 00000000..54cec33a --- /dev/null +++ b/src/main/java/net/thenextlvl/gopaint/brush/pattern/SprayPattern.java @@ -0,0 +1,23 @@ +package net.thenextlvl.gopaint.brush.pattern; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import net.thenextlvl.gopaint.api.brush.pattern.BuildPattern; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; + +public record SprayPattern( + EditSession session, + BlockVector3 position, + Player player, + BrushSettings settings +) implements BuildPattern { + + @Override + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + if (settings.getRandom().nextInt(100) < settings.getChance()) return false; + return set.setBlock(extent, getRandomBlockState()); + } +} diff --git a/src/main/java/net/thenextlvl/gopaint/brush/pattern/UnderlayPattern.java b/src/main/java/net/thenextlvl/gopaint/brush/pattern/UnderlayPattern.java new file mode 100644 index 00000000..a15b8c9e --- /dev/null +++ b/src/main/java/net/thenextlvl/gopaint/brush/pattern/UnderlayPattern.java @@ -0,0 +1,32 @@ +package net.thenextlvl.gopaint.brush.pattern; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.block.BaseBlock; +import net.thenextlvl.gopaint.api.brush.pattern.BuildPattern; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; + +public record UnderlayPattern( + EditSession session, + BlockVector3 position, + Player player, + BrushSettings settings +) implements BuildPattern { + + @Override + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + if (!isUnderlay(get, applyBlock(get))) return false; + return set.setBlock(extent, getRandomBlockState()); + } + + private boolean isUnderlay(BlockVector3 position, BaseBlock block) { + for (var i = 1; i <= settings().getThickness(); i++) { + if (!block.getMaterial().isMovementBlocker()) return false; + if (!position.getStateRelativeY(player().getWorld(), i).getMaterial().isMovementBlocker()) return false; + } + return true; + } +} diff --git a/src/main/java/net/thenextlvl/gopaint/brush/pattern/package-info.java b/src/main/java/net/thenextlvl/gopaint/brush/pattern/package-info.java new file mode 100644 index 00000000..5036ff8a --- /dev/null +++ b/src/main/java/net/thenextlvl/gopaint/brush/pattern/package-info.java @@ -0,0 +1,10 @@ +@TypesAreNotNullByDefault +@FieldsAreNotNullByDefault +@MethodsReturnNotNullByDefault +@ParametersAreNotNullByDefault +package net.thenextlvl.gopaint.brush.pattern; + +import core.annotation.FieldsAreNotNullByDefault; +import core.annotation.MethodsReturnNotNullByDefault; +import core.annotation.ParametersAreNotNullByDefault; +import core.annotation.TypesAreNotNullByDefault; \ No newline at end of file diff --git a/src/main/java/net/thenextlvl/gopaint/brush/setting/CraftItemBrushSettings.java b/src/main/java/net/thenextlvl/gopaint/brush/setting/CraftItemBrushSettings.java index e9b81e66..159c4e4a 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/setting/CraftItemBrushSettings.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/setting/CraftItemBrushSettings.java @@ -20,13 +20,11 @@ import lombok.Builder; import lombok.Getter; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.PatternBrush; import net.thenextlvl.gopaint.api.brush.setting.ItemBrushSettings; -import net.thenextlvl.gopaint.api.model.MaskMode; import net.thenextlvl.gopaint.api.model.SurfaceMode; import org.bukkit.Axis; import org.bukkit.Material; -import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Random; @@ -34,12 +32,12 @@ @Getter @Builder(builderClassName = "Builder") public final class CraftItemBrushSettings implements ItemBrushSettings { - private final Brush brush; - private final @Nullable Material mask; + private final PatternBrush brush; + private final Material mask; private final List blocks; private final Axis axis; private final SurfaceMode surfaceMode; - private final MaskMode maskMode; + private final boolean maskEnabled; private final int brushSize; private final int chance; private final int thickness; @@ -51,11 +49,6 @@ public final class CraftItemBrushSettings implements ItemBrushSettings { private static final Random random = new Random(); - @Override - public Material getRandomBlock() { - return getBlocks().get(getRandom().nextInt(getBlocks().size())); - } - @Override public Random getRandom() { return random; diff --git a/src/main/java/net/thenextlvl/gopaint/brush/setting/CraftPlayerBrushSettings.java b/src/main/java/net/thenextlvl/gopaint/brush/setting/CraftPlayerBrushSettings.java index 1e3fa305..a3c3b2df 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/setting/CraftPlayerBrushSettings.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/setting/CraftPlayerBrushSettings.java @@ -25,10 +25,9 @@ import net.kyori.adventure.text.JoinConfiguration; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.gopaint.GoPaintPlugin; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.PatternBrush; import net.thenextlvl.gopaint.api.brush.setting.ItemBrushSettings; import net.thenextlvl.gopaint.api.brush.setting.PlayerBrushSettings; -import net.thenextlvl.gopaint.api.model.MaskMode; import net.thenextlvl.gopaint.api.model.SurfaceMode; import net.thenextlvl.gopaint.brush.standard.*; import net.thenextlvl.gopaint.menu.BrushesMenu; @@ -64,10 +63,10 @@ public final class CraftPlayerBrushSettings implements PlayerBrushSettings { private int mixingStrength; private double angleHeightDifference; private Axis axis; - private MaskMode maskMode; + private boolean maskEnabled; private SurfaceMode surfaceMode; - private Brush brush; + private PatternBrush brush; private Material mask; private final List blocks = new ArrayList<>(); @@ -82,7 +81,7 @@ public CraftPlayerBrushSettings(GoPaintPlugin plugin, Player player) { .orElseThrow(() -> new IllegalArgumentException("Unknown default brush: " + defaultBrush.asString())); surfaceMode = plugin.config().brushConfig().surfaceMode(); - maskMode = plugin.config().brushConfig().maskMode(); + maskEnabled = plugin.config().brushConfig().mask(); enabled = plugin.config().brushConfig().enabledByDefault(); chance = plugin.config().brushConfig().defaultChance(); thickness = plugin.config().thicknessConfig().defaultThickness(); @@ -104,11 +103,6 @@ public Random getRandom() { return random; } - @Override - public Material getRandomBlock() { - return getBlocks().get(random.nextInt(getBlocks().size())); - } - @Override public void setMask(Material type) { mask = type; @@ -136,7 +130,7 @@ public void removeBlock(int slot) { } @Override - public void setBrush(Brush brush) { + public void setBrush(PatternBrush brush) { this.brush = brush; mainMenu.updateBrush(); } @@ -171,14 +165,14 @@ public void setAngleDistance(@Range(from = 1, to = Integer.MAX_VALUE) int distan } @Override - public void setFalloffStrength(@Range(from = 10, to = 90) int strength) { - this.falloffStrength = Math.clamp(strength, 10, 90); + public void setFalloffStrength(@Range(from = 0, to = 100) int strength) { + this.falloffStrength = Math.clamp(strength, 0, 100); mainMenu.updateFalloffStrength(); } @Override - public void setMixingStrength(@Range(from = 10, to = 90) int strength) { - this.mixingStrength = Math.clamp(strength, 10, 90); + public void setMixingStrength(@Range(from = 0, to = 100) int strength) { + this.mixingStrength = Math.clamp(strength, 0, 100); mainMenu.updateMixingStrength(); } @@ -191,9 +185,9 @@ public void setAngleHeightDifference(double difference) { } @Override - public void setMaskMode(MaskMode maskMode) { - this.maskMode = maskMode; - mainMenu.updateMaskMode(); + public void setMaskEnabled(boolean maskEnabled) { + this.maskEnabled = maskEnabled; + mainMenu.updateMaskToggle(); } @Override @@ -222,7 +216,7 @@ public void setFractureStrength(@Range(from = 1, to = Integer.MAX_VALUE) int fra } @Override - public Brush getNextBrush(@Nullable Brush brush) { + public PatternBrush getNextBrush(@Nullable PatternBrush brush) { var brushes = plugin.brushRegistry().getBrushes().toList(); if (brush == null) return brushes.getFirst(); int next = brushes.indexOf(brush) + 1; @@ -231,7 +225,7 @@ public Brush getNextBrush(@Nullable Brush brush) { } @Override - public Brush getPreviousBrush(@Nullable Brush brush) { + public PatternBrush getPreviousBrush(@Nullable PatternBrush brush) { var brushes = plugin.brushRegistry().getBrushes().toList(); if (brush == null) return brushes.getFirst(); int back = brushes.indexOf(brush) - 1; @@ -259,7 +253,7 @@ public void exportSettings(ItemStack itemStack) { Placeholder.parsed("distance", String.valueOf(getAngleDistance())))); lore.add(plugin.bundle().component(player, "brush.exported.angle.height", Placeholder.parsed("height", String.valueOf(getAngleHeightDifference())))); - } else if (getBrush() instanceof SplatterBrush) { + } else if (getBrush() instanceof SplatterBrush || getBrush() instanceof PaintBrush) { lore.add(plugin.bundle().component(player, "brush.exported.falloff", Placeholder.parsed("falloff", String.valueOf(getFalloffStrength())))); } else if (getBrush() instanceof GradientBrush) { @@ -280,13 +274,7 @@ public void exportSettings(ItemStack itemStack) { Placeholder.component("blocks", Component.join(JoinConfiguration.commas(true), blocks)))); } - - if (!getMaskMode().equals(MaskMode.DISABLED)) { - var mode = plugin.bundle().component(player, getMaskMode().translationKey()); - lore.add(plugin.bundle().component(player, "brush.exported.mask-mode", - Placeholder.component("mode", mode))); - } - if (getMaskMode().equals(MaskMode.INTERFACE)) { + if (isMaskEnabled()) { lore.add(plugin.bundle().component(player, "brush.exported.mask", Placeholder.component("mask", Component.translatable(getMask().translationKey())))); } @@ -314,7 +302,7 @@ public void exportSettings(ItemStack itemStack) { container.set(new NamespacedKey("gopaint", "mixing_strength"), PersistentDataType.INTEGER, getMixingStrength()); container.set(new NamespacedKey("gopaint", "angle_height_difference"), PersistentDataType.DOUBLE, getAngleHeightDifference()); container.set(new NamespacedKey("gopaint", "axis"), PersistentDataType.STRING, getAxis().name()); - container.set(new NamespacedKey("gopaint", "mask_mode"), PersistentDataType.STRING, getMaskMode().name()); + container.set(new NamespacedKey("gopaint", "mask_enabled"), PersistentDataType.BOOLEAN, isMaskEnabled()); container.set(new NamespacedKey("gopaint", "surface_mode"), PersistentDataType.STRING, getSurfaceMode().name()); container.set(new NamespacedKey("gopaint", "brush"), PersistentDataType.STRING, getBrush().key().asString()); container.set(new NamespacedKey("gopaint", "mask"), PersistentDataType.STRING, getMask().key().asString()); @@ -336,7 +324,7 @@ public void importSettings(ItemBrushSettings settings) { setMixingStrength(settings.getMixingStrength()); setAngleHeightDifference(settings.getAngleHeightDifference()); setAxis(settings.getAxis()); - setMaskMode(settings.getMaskMode()); + setMaskEnabled(settings.isMaskEnabled()); setSurfaceMode(settings.getSurfaceMode()); setBrush(settings.getBrush()); setMask(settings.getMask()); diff --git a/src/main/java/net/thenextlvl/gopaint/brush/standard/AngleBrush.java b/src/main/java/net/thenextlvl/gopaint/brush/standard/AngleBrush.java index 47553cb7..ee4caea6 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/standard/AngleBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/standard/AngleBrush.java @@ -18,22 +18,19 @@ */ package net.thenextlvl.gopaint.brush.standard; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.SpherePatternBrush; import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import net.thenextlvl.gopaint.api.math.Height; -import net.thenextlvl.gopaint.api.math.Sphere; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.Location; +import net.thenextlvl.gopaint.brush.pattern.AnglePattern; import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import java.util.stream.Stream; - -public class AngleBrush extends Brush { +public class AngleBrush extends SpherePatternBrush { private final GoPaintProvider provider; public AngleBrush(GoPaintProvider provider) { @@ -55,15 +52,7 @@ public Component[] getDescription(Audience audience) { } @Override - public void paint(Location location, Player player, BrushSettings brushSettings) { - performEdit(player, session -> { - Stream blocks = Sphere.getBlocksInRadius(location, brushSettings.getBrushSize(), null, false); - blocks.filter(block -> passesDefaultChecks(brushSettings, player, session, block)) - .filter(block -> Height.getAverageHeightDiffAngle(block.getLocation(), 1) >= 0.1 - && Height.getAverageHeightDiffAngle(block.getLocation(), brushSettings.getAngleDistance()) - >= Math.tan(Math.toRadians(brushSettings.getAngleHeightDifference()))) - .map(block -> BlockVector3.at(block.getX(), block.getY(), block.getZ())) - .forEach(vector3 -> setBlock(session, vector3, brushSettings.getRandomBlock())); - }); + public Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings) { + return new AnglePattern(session, position, player, settings); } } diff --git a/src/main/java/net/thenextlvl/gopaint/brush/standard/BucketBrush.java b/src/main/java/net/thenextlvl/gopaint/brush/standard/BucketBrush.java index b088cb20..ba3bb920 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/standard/BucketBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/standard/BucketBrush.java @@ -18,23 +18,22 @@ */ package net.thenextlvl.gopaint.brush.standard; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.PatternBrush; import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; import net.thenextlvl.gopaint.api.math.ConnectedBlocks; import net.thenextlvl.gopaint.api.math.Sphere; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.Location; +import net.thenextlvl.gopaint.brush.pattern.ShufflePattern; import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import java.util.List; -import java.util.stream.Stream; - -public class BucketBrush extends Brush { +public class BucketBrush extends PatternBrush { private final GoPaintProvider provider; public BucketBrush(GoPaintProvider provider) { @@ -56,13 +55,14 @@ public Component[] getDescription(Audience audience) { } @Override - public void paint(Location location, Player player, BrushSettings brushSettings) { - performEdit(player, session -> { - List blocks = Sphere.getBlocksInRadius(location, brushSettings.getBrushSize(), null, false).toList(); - Stream connectedBlocks = ConnectedBlocks.getConnectedBlocks(location, blocks); - connectedBlocks.filter(block -> passesDefaultChecks(brushSettings, player, session, block)) - .map(block -> BlockVector3.at(block.getX(), block.getY(), block.getZ())) - .forEach(vector3 -> setBlock(session, vector3, brushSettings.getRandomBlock())); - }); + public Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings) { + return new ShufflePattern(session, position, player, settings); + } + + @Override + public void build(EditSession session, BlockVector3 position, Pattern pattern, double size) throws MaxChangedBlocksException { + var blocks = Sphere.getBlocksInRadius(position, size); + ConnectedBlocks.getConnectedBlocks(session.getWorld(), position, blocks) + .forEach(vector3 -> session.setBlock(vector3, pattern)); } } diff --git a/src/main/java/net/thenextlvl/gopaint/brush/standard/DiscBrush.java b/src/main/java/net/thenextlvl/gopaint/brush/standard/DiscBrush.java index 6992f81c..d68d67a0 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/standard/DiscBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/standard/DiscBrush.java @@ -18,21 +18,21 @@ */ package net.thenextlvl.gopaint.brush.standard; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.PatternBrush; import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import net.thenextlvl.gopaint.api.math.Sphere; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.Location; +import net.thenextlvl.gopaint.api.brush.pattern.BuildPattern; +import net.thenextlvl.gopaint.brush.pattern.ShufflePattern; import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import java.util.stream.Stream; - -public class DiscBrush extends Brush { +public class DiscBrush extends PatternBrush { private final GoPaintProvider provider; public DiscBrush(GoPaintProvider provider) { @@ -54,12 +54,17 @@ public Component[] getDescription(Audience audience) { } @Override - public void paint(Location location, Player player, BrushSettings brushSettings) { - performEdit(player, session -> { - Stream blocks = Sphere.getBlocksInRadius(location, brushSettings.getBrushSize(), brushSettings.getAxis(), false); - blocks.filter(block -> passesDefaultChecks(brushSettings, player, session, block)) - .map(block -> BlockVector3.at(block.getX(), block.getY(), block.getZ())) - .forEach(vector3 -> setBlock(session, vector3, brushSettings.getRandomBlock())); - }); + public Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings) { + return new ShufflePattern(session, position, player, settings); + } + + @Override + public void build(EditSession session, BlockVector3 position, Pattern pattern, double size) throws MaxChangedBlocksException { + if (!(pattern instanceof BuildPattern buildPattern)) return; + switch (buildPattern.settings().getAxis()) { + case X -> session.makeSphere(position, pattern, 0, size, size, true); + case Y -> session.makeSphere(position, pattern, size, 0, size, true); + case Z -> session.makeSphere(position, pattern, size, size, 0, true); + } } } diff --git a/src/main/java/net/thenextlvl/gopaint/brush/standard/FractureBrush.java b/src/main/java/net/thenextlvl/gopaint/brush/standard/FractureBrush.java index 9817759a..3643a8f3 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/standard/FractureBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/standard/FractureBrush.java @@ -18,22 +18,19 @@ */ package net.thenextlvl.gopaint.brush.standard; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.SpherePatternBrush; import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import net.thenextlvl.gopaint.api.math.Height; -import net.thenextlvl.gopaint.api.math.Sphere; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.Location; +import net.thenextlvl.gopaint.brush.pattern.FracturePattern; import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import java.util.stream.Stream; - -public class FractureBrush extends Brush { +public class FractureBrush extends SpherePatternBrush { private final GoPaintProvider provider; public FractureBrush(GoPaintProvider provider) { @@ -55,22 +52,7 @@ public Component[] getDescription(Audience audience) { } @Override - public void paint(Location location, Player player, BrushSettings brushSettings) { - performEdit(player, session -> { - Stream blocks = Sphere.getBlocksInRadius(location, brushSettings.getBrushSize(), null, false); - blocks.filter(block -> passesMaskCheck(brushSettings, session, block)) - .filter(block -> Height.getAverageHeightDiffFracture( - block.getLocation(), - Height.getNearestNonEmptyBlock(block.getLocation()), - 1 - ) >= 0.1) - .filter(block -> Height.getAverageHeightDiffFracture( - block.getLocation(), - Height.getNearestNonEmptyBlock(block.getLocation()), - brushSettings.getFractureStrength() - ) >= 0.1) - .map(block -> BlockVector3.at(block.getX(), block.getY(), block.getZ())) - .forEach(vector3 -> setBlock(session, vector3, brushSettings.getRandomBlock())); - }); + public Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings) { + return new FracturePattern(session, position, player, settings); } } diff --git a/src/main/java/net/thenextlvl/gopaint/brush/standard/GradientBrush.java b/src/main/java/net/thenextlvl/gopaint/brush/standard/GradientBrush.java index 573b6352..2be68acc 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/standard/GradientBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/standard/GradientBrush.java @@ -18,21 +18,19 @@ */ package net.thenextlvl.gopaint.brush.standard; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.SpherePatternBrush; import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import net.thenextlvl.gopaint.api.math.Sphere; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.Location; +import net.thenextlvl.gopaint.brush.pattern.GradientPattern; import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import java.util.stream.Stream; - -public class GradientBrush extends Brush { +public class GradientBrush extends SpherePatternBrush { private final GoPaintProvider provider; public GradientBrush(GoPaintProvider provider) { @@ -54,30 +52,7 @@ public Component[] getDescription(Audience audience) { } @Override - public void paint(Location location, Player player, BrushSettings brushSettings) { - performEdit(player, session -> { - Stream blocks = Sphere.getBlocksInRadius(location, brushSettings.getBrushSize(), null, false); - blocks.filter(block -> passesDefaultChecks(brushSettings, player, session, block)) - .filter(block -> brushSettings.getRandom().nextDouble() > getRate(location, brushSettings, block)) - .map(block -> BlockVector3.at(block.getX(), block.getY(), block.getZ())) - .forEach(vector3 -> { - int random = getRandom(brushSettings, vector3); - int index = Math.clamp(random, 0, brushSettings.getBlocks().size() - 1); - setBlock(session, vector3, brushSettings.getBlocks().get(index)); - }); - }); - } - - private static int getRandom(BrushSettings brushSettings, BlockVector3 vector3) { - var y = vector3.getY() - (brushSettings.getBrushSize() / 2d); - var mixingStrength = brushSettings.getMixingStrength() / 100d; - var random = brushSettings.getRandom().nextDouble() * 2 - 1; - var size = (vector3.getY() - y) / brushSettings.getBrushSize() * brushSettings.getBlocks().size(); - return (int) (size + random * mixingStrength); - } - - private static double getRate(Location location, BrushSettings brushSettings, Block block) { - double size = (brushSettings.getBrushSize() / 2d) * ((100d - brushSettings.getFalloffStrength()) / 100d); - return (block.getLocation().distance(location) - size) / ((brushSettings.getBrushSize() / 2d) - size); + public Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings) { + return new GradientPattern(session, position, player, settings); } } diff --git a/src/main/java/net/thenextlvl/gopaint/brush/standard/OverlayBrush.java b/src/main/java/net/thenextlvl/gopaint/brush/standard/OverlayBrush.java index d75e6c95..86a9a54f 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/standard/OverlayBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/standard/OverlayBrush.java @@ -18,22 +18,19 @@ */ package net.thenextlvl.gopaint.brush.standard; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.SpherePatternBrush; import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import net.thenextlvl.gopaint.api.math.Sphere; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.Location; +import net.thenextlvl.gopaint.brush.pattern.OverlayPattern; import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; -import java.util.stream.Stream; - -public class OverlayBrush extends Brush { +public class OverlayBrush extends SpherePatternBrush { private final GoPaintProvider provider; public OverlayBrush(GoPaintProvider provider) { @@ -55,22 +52,7 @@ public Component[] getDescription(Audience audience) { } @Override - public void paint(Location location, Player player, BrushSettings brushSettings) { - performEdit(player, session -> { - Stream blocks = Sphere.getBlocksInRadius(location, brushSettings.getBrushSize(), null, false); - blocks.filter(block -> passesMaskCheck(brushSettings, session, block)) - .filter(block -> isOverlay(block, brushSettings.getThickness())) - .map(block -> BlockVector3.at(block.getX(), block.getY(), block.getZ())) - .forEach(vector3 -> setBlock(session, vector3, brushSettings.getRandomBlock())); - }); - } - - private boolean isOverlay(Block block, int thickness) { - for (int i = 1; i <= thickness; i++) { - if (block.isSolid() && !block.getRelative(BlockFace.UP, i).isSolid()) { - return true; - } - } - return false; + public Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings) { + return new OverlayPattern(session, position, player, settings); } } diff --git a/src/main/java/net/thenextlvl/gopaint/brush/standard/PaintBrush.java b/src/main/java/net/thenextlvl/gopaint/brush/standard/PaintBrush.java index 542b6915..3c612739 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/standard/PaintBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/standard/PaintBrush.java @@ -18,25 +18,28 @@ */ package net.thenextlvl.gopaint.brush.standard; +import com.fastasyncworldedit.core.math.MutableBlockVector3; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.bukkit.BukkitPlayer; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.PatternBrush; import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import net.thenextlvl.gopaint.api.math.Height; import net.thenextlvl.gopaint.api.math.Sphere; import net.thenextlvl.gopaint.api.math.curve.BezierSpline; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.Location; +import net.thenextlvl.gopaint.brush.pattern.SplinePattern; import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import org.bukkit.util.Vector; import java.util.*; -public class PaintBrush extends Brush { +public class PaintBrush extends PatternBrush { + private final Map> selectedPoints = new HashMap<>(); private final GoPaintProvider provider; public PaintBrush(GoPaintProvider provider) { @@ -57,68 +60,63 @@ public Component[] getDescription(Audience audience) { return provider.bundle().components(audience, "brush.description.paint"); } - private static final Map> selectedPoints = new HashMap<>(); + @Override + public Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings) { + return new SplinePattern(session, position, player, settings); + } @Override - public void paint(Location target, Player player, BrushSettings brushSettings) { - List locations = selectedPoints.computeIfAbsent(player.getUniqueId(), ignored -> new ArrayList<>()); - locations.add(target); - - if (!player.isSneaking()) { - provider.bundle().sendMessage(player, "brush.paint.point.set", - Placeholder.parsed("x", String.valueOf(target.getBlockX())), - Placeholder.parsed("y", String.valueOf(target.getBlockY())), - Placeholder.parsed("z", String.valueOf(target.getBlockZ())), - Placeholder.parsed("point", String.valueOf(locations.size())) + public void build(EditSession session, BlockVector3 position, Pattern original, double size) throws MaxChangedBlocksException { + if (!(original instanceof SplinePattern pattern)) return; + + var id = pattern.player().getUniqueId(); + selectedPoints.computeIfAbsent(id, ignored -> new ArrayList<>()).add(position); + + if (pattern.player() instanceof BukkitPlayer bukkit && !bukkit.getPlayer().isSneaking()) { + provider.bundle().sendMessage(bukkit.getPlayer(), "brush.paint.point.set", + Placeholder.parsed("x", String.valueOf(position.getX())), + Placeholder.parsed("y", String.valueOf(position.getY())), + Placeholder.parsed("z", String.valueOf(position.getZ())), + Placeholder.parsed("point", String.valueOf(selectedPoints.get(id).size())) ); return; } - selectedPoints.remove(player.getUniqueId()); - - performEdit(player, session -> { - var world = player.getWorld(); - Location first = locations.getFirst(); - Sphere.getBlocksInRadius(first, brushSettings.getBrushSize(), null, false) - .filter(block -> Height.getAverageHeightDiffAngle(block.getLocation(), 1) < 0.1 - || Height.getAverageHeightDiffAngle(block.getLocation(), brushSettings.getAngleDistance()) - < Math.tan(Math.toRadians(brushSettings.getAngleHeightDifference()))) - - .filter(block -> { - var rate = calculateRate(block, first, brushSettings); - return brushSettings.getRandom().nextDouble() > rate; - }).forEach(block -> { - var curve = new LinkedList(); - curve.add(new Vector(block.getX(), block.getY(), block.getZ())); - locations.stream().map(location -> new Vector( - block.getX() + location.getX() - first.getX(), - block.getY() + location.getY() - first.getY(), - block.getZ() + location.getZ() - first.getZ() - )).forEach(curve::add); - - var spline = new BezierSpline(curve); - var maxCount = (spline.getCurveLength() * 2.5) + 1; - - for (int y = 0; y <= maxCount; y++) { - var point = spline.getPoint((y / maxCount) * (locations.size() - 1)).toLocation(world).getBlock(); - - if (point.isEmpty() || !passesDefaultChecks(brushSettings, player, session, point)) { - continue; - } - - var vector3 = BlockVector3.at(point.getX(), point.getY(), point.getZ()); - setBlock(session, vector3, brushSettings.getRandomBlock()); - } - }); - }); - } + if (selectedPoints.get(id).size() <= 1) return; + + var vectors = selectedPoints.remove(id); + + var first = vectors.getFirst(); + var settings = pattern.settings(); - private double calculateRate(Block block, Location first, BrushSettings brushSettings) { - double sizeHalf = brushSettings.getBrushSize() / 2.0; - double falloffStrengthFactor = (100.0 - brushSettings.getFalloffStrength()) / 100.0; - double numerator = block.getLocation().distance(first) - sizeHalf * falloffStrengthFactor; - double denominator = sizeHalf - sizeHalf * falloffStrengthFactor; + Sphere.getBlocksInRadius(first, size).stream() + .filter(vector3 -> { + var rate = getRate(settings.getFalloffStrength(), size, vector3, first); + return settings.getRandom().nextDouble() > rate; + }).forEach(vector3 -> { + pattern.random(settings.getRandom().nextInt(settings.getBlocks().size())); + + var curve = new LinkedList(); + curve.add(MutableBlockVector3.at(vector3.getX(), vector3.getY(), vector3.getZ())); + vectors.stream().skip(1).map(location -> MutableBlockVector3.at( + vector3.getX() + location.getX() - first.getX(), + vector3.getY() + location.getY() - first.getY(), + vector3.getZ() + location.getZ() - first.getZ() + )).forEach(curve::add); + + var spline = new BezierSpline(curve); + var maxCount = (spline.getCurveLength() * 2.5) + 1; + + for (var y = 0; y <= maxCount; y++) { + session.setBlock(spline.getPoint((y / maxCount) * (vectors.size() - 1)), pattern); + } + }); + } + private double getRate(double falloffStrength, double size, BlockVector3 vector3, BlockVector3 first) { + var falloffFactor = (100.0 - falloffStrength) / 100.0; + var numerator = vector3.distance(first) - size * falloffFactor; + var denominator = size - size * falloffFactor; return numerator / denominator; } } diff --git a/src/main/java/net/thenextlvl/gopaint/brush/standard/SphereBrush.java b/src/main/java/net/thenextlvl/gopaint/brush/standard/SphereBrush.java index e5a10c1f..329f09d8 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/standard/SphereBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/standard/SphereBrush.java @@ -18,21 +18,19 @@ */ package net.thenextlvl.gopaint.brush.standard; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.SpherePatternBrush; import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import net.thenextlvl.gopaint.api.math.Sphere; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.Location; +import net.thenextlvl.gopaint.brush.pattern.ShufflePattern; import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import java.util.stream.Stream; - -public class SphereBrush extends Brush { +public class SphereBrush extends SpherePatternBrush { private final GoPaintProvider provider; public SphereBrush(GoPaintProvider provider) { @@ -54,12 +52,7 @@ public Component[] getDescription(Audience audience) { } @Override - public void paint(Location location, Player player, BrushSettings brushSettings) { - performEdit(player, session -> { - Stream blocks = Sphere.getBlocksInRadius(location, brushSettings.getBrushSize(), null, false); - blocks.filter(block -> passesDefaultChecks(brushSettings, player, session, block)) - .map(block -> BlockVector3.at(block.getX(), block.getY(), block.getZ())) - .forEach(vector3 -> setBlock(session, vector3, brushSettings.getRandomBlock())); - }); + public Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings) { + return new ShufflePattern(session, position, player, settings); } } diff --git a/src/main/java/net/thenextlvl/gopaint/brush/standard/SplatterBrush.java b/src/main/java/net/thenextlvl/gopaint/brush/standard/SplatterBrush.java index 4523d71a..0aff3c2a 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/standard/SplatterBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/standard/SplatterBrush.java @@ -18,21 +18,19 @@ */ package net.thenextlvl.gopaint.brush.standard; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.SpherePatternBrush; import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import net.thenextlvl.gopaint.api.math.Sphere; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.Location; +import net.thenextlvl.gopaint.brush.pattern.SplatterPattern; import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import java.util.stream.Stream; - -public class SplatterBrush extends Brush { +public class SplatterBrush extends SpherePatternBrush { private final GoPaintProvider provider; public SplatterBrush(GoPaintProvider provider) { @@ -54,19 +52,7 @@ public Component[] getDescription(Audience audience) { } @Override - public void paint(Location location, Player player, BrushSettings brushSettings) { - performEdit(player, session -> { - Stream blocks = Sphere.getBlocksInRadius(location, brushSettings.getBrushSize(), null, false); - blocks.filter(block -> passesDefaultChecks(brushSettings, player, session, block)) - .filter(block -> brushSettings.getRandom().nextDouble() > getRate(location, brushSettings, block)) - .map(block -> BlockVector3.at(block.getX(), block.getY(), block.getZ())) - .forEach(vector3 -> setBlock(session, vector3, brushSettings.getRandomBlock())); - }); - } - - private static double getRate(Location location, BrushSettings brushSettings, Block block) { - var size = (double) brushSettings.getBrushSize() / 2.0; - var falloff = (100.0 - (double) brushSettings.getFalloffStrength()) / 100.0; - return (block.getLocation().distance(location) - size * falloff) / (size - size * falloff); + public Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings) { + return new SplatterPattern(session, position, player, settings); } } diff --git a/src/main/java/net/thenextlvl/gopaint/brush/standard/SprayBrush.java b/src/main/java/net/thenextlvl/gopaint/brush/standard/SprayBrush.java index 1716d178..0c960e2f 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/standard/SprayBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/standard/SprayBrush.java @@ -18,21 +18,19 @@ */ package net.thenextlvl.gopaint.brush.standard; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.SpherePatternBrush; import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import net.thenextlvl.gopaint.api.math.Sphere; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.Location; +import net.thenextlvl.gopaint.brush.pattern.SprayPattern; import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.entity.Player; -import java.util.stream.Stream; - -public class SprayBrush extends Brush { +public class SprayBrush extends SpherePatternBrush { private final GoPaintProvider provider; public SprayBrush(GoPaintProvider provider) { @@ -54,13 +52,7 @@ public Component[] getDescription(Audience audience) { } @Override - public void paint(Location location, Player player, BrushSettings brushSettings) { - performEdit(player, session -> { - Stream blocks = Sphere.getBlocksInRadius(location, brushSettings.getBrushSize(), null, false); - blocks.filter(block -> passesDefaultChecks(brushSettings, player, session, block)) - .filter(block -> brushSettings.getRandom().nextInt(100) < brushSettings.getChance()) - .map(block -> BlockVector3.at(block.getX(), block.getY(), block.getZ())) - .forEach(vector3 -> setBlock(session, vector3, brushSettings.getRandomBlock())); - }); + public Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings) { + return new SprayPattern(session, position, player, settings); } } diff --git a/src/main/java/net/thenextlvl/gopaint/brush/standard/UnderlayBrush.java b/src/main/java/net/thenextlvl/gopaint/brush/standard/UnderlayBrush.java index 93c6fab1..5e8237d2 100644 --- a/src/main/java/net/thenextlvl/gopaint/brush/standard/UnderlayBrush.java +++ b/src/main/java/net/thenextlvl/gopaint/brush/standard/UnderlayBrush.java @@ -1,21 +1,18 @@ package net.thenextlvl.gopaint.brush.standard; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.SpherePatternBrush; import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; -import net.thenextlvl.gopaint.api.math.Sphere; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.Location; +import net.thenextlvl.gopaint.brush.pattern.UnderlayPattern; import org.bukkit.NamespacedKey; -import org.bukkit.block.Block; -import org.bukkit.block.BlockFace; -import org.bukkit.entity.Player; -import java.util.stream.Stream; - -public class UnderlayBrush extends Brush { +public class UnderlayBrush extends SpherePatternBrush { private final GoPaintProvider provider; public UnderlayBrush(GoPaintProvider provider) { @@ -37,23 +34,7 @@ public Component[] getDescription(Audience audience) { } @Override - public void paint(Location location, Player player, BrushSettings brushSettings) { - performEdit(player, session -> { - Stream blocks = Sphere.getBlocksInRadius(location, brushSettings.getBrushSize(), null, false); - blocks.filter(block -> passesMaskCheck(brushSettings, session, block)) - .filter(block -> isUnderlay(block, brushSettings.getThickness())) - .map(block -> BlockVector3.at(block.getX(), block.getY(), block.getZ())) - .forEach(vector3 -> setBlock(session, vector3, brushSettings.getRandomBlock())); - }); - } - - private boolean isUnderlay(Block block, int thickness) { - for (int i = 1; i <= thickness; i++) { - if (!block.isSolid() || !block.getRelative(BlockFace.UP, i).isSolid()) { - return false; - } - } - return true; + public Pattern buildPattern(EditSession session, BlockVector3 position, Player player, BrushSettings settings) { + return new UnderlayPattern(session, position, player, settings); } - } diff --git a/src/main/java/net/thenextlvl/gopaint/command/GoPaintCommand.java b/src/main/java/net/thenextlvl/gopaint/command/GoPaintCommand.java index 57f8010e..f5929321 100644 --- a/src/main/java/net/thenextlvl/gopaint/command/GoPaintCommand.java +++ b/src/main/java/net/thenextlvl/gopaint/command/GoPaintCommand.java @@ -11,7 +11,7 @@ import net.kyori.adventure.key.Key; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.thenextlvl.gopaint.GoPaintPlugin; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.PatternBrush; import net.thenextlvl.gopaint.api.model.GoPaintProvider; import org.bukkit.entity.Player; @@ -36,7 +36,7 @@ public void register() { .then(Commands.argument("brush", ArgumentTypes.key()) .suggests((context, builder) -> { plugin.brushRegistry().getBrushes() - .map(Brush::key) + .map(PatternBrush::key) .map(Key::asString) .filter(key -> key.contains(builder.getRemaining())) .forEach(builder::suggest); diff --git a/src/main/java/net/thenextlvl/gopaint/listener/InteractListener.java b/src/main/java/net/thenextlvl/gopaint/listener/InteractListener.java index 685cf32a..681fafe4 100644 --- a/src/main/java/net/thenextlvl/gopaint/listener/InteractListener.java +++ b/src/main/java/net/thenextlvl/gopaint/listener/InteractListener.java @@ -18,18 +18,20 @@ */ package net.thenextlvl.gopaint.listener; +import com.fastasyncworldedit.core.function.mask.AirMask; +import com.fastasyncworldedit.core.function.mask.InverseMask; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitPlayer; +import com.sk89q.worldedit.function.mask.MaskIntersection; +import com.sk89q.worldedit.session.request.Request; import lombok.RequiredArgsConstructor; import net.thenextlvl.gopaint.GoPaintPlugin; +import net.thenextlvl.gopaint.api.brush.setting.BrushSettings; import net.thenextlvl.gopaint.api.brush.setting.PlayerBrushSettings; import net.thenextlvl.gopaint.api.model.GoPaintProvider; -import org.bukkit.FluidCollisionMode; -import org.bukkit.Location; -import org.bukkit.block.Block; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; @RequiredArgsConstructor @@ -42,6 +44,11 @@ public void onClick(PlayerInteractEvent event) { if (!player.hasPermission(GoPaintProvider.USE_PERMISSION)) return; + if (!player.hasPermission(GoPaintProvider.WORLD_BYPASS_PERMISSION) + && plugin.config().brushConfig().disabledWorlds().contains(player.getWorld().getName())) { + return; + } + var item = event.getItem(); if (item == null) return; @@ -54,34 +61,54 @@ public void onClick(PlayerInteractEvent event) { if (!event.getAction().isRightClick()) return; - Location location; - if (event.getAction().equals(Action.RIGHT_CLICK_AIR)) { - Block targetBlock = player.getTargetBlockExact(250, FluidCollisionMode.NEVER); - if (targetBlock == null) return; - location = targetBlock.getLocation().clone(); - } else if (event.getClickedBlock() != null) { - location = event.getClickedBlock().getLocation(); - } else { - return; - } - - if (!player.hasPermission(GoPaintProvider.WORLD_BYPASS_PERMISSION) - && plugin.config().brushConfig().disabledWorlds().contains(location.getWorld().getName())) { - return; - } - var settings = !item.getType().equals(plugin.config().brushConfig().defaultBrushType()) ? plugin.brushController().parseBrushSettings(item).orElse(null) : plugin.brushController().getBrushSettings(player); if (settings == null || settings.getBlocks().isEmpty()) return; - if (!(settings instanceof PlayerBrushSettings playerSettings) || playerSettings.isEnabled()) { - BukkitAdapter.adapt(player).runAction( - () -> settings.getBrush().paint(location, player, settings), false, true - ); - } else plugin.bundle().sendMessage(player, "brush.disabled"); + if (!(settings instanceof PlayerBrushSettings playerSettings) || playerSettings.isEnabled()) + handleInteract(BukkitAdapter.adapt(player), settings); + else plugin.bundle().sendMessage(player, "brush.disabled"); event.setCancelled(true); } + + private void handleInteract(BukkitPlayer player, BrushSettings settings) { + player.runAsyncIfFree(() -> { + var session = player.getSession(); + + try (var editSession = session.createEditSession(player)) { + + var blockTrace = player.getSolidBlockTrace(250); + + if (blockTrace == null) { + plugin.bundle().sendMessage(player.getPlayer(), "brush.block.sight"); + editSession.cancel(); + return; + } + var bag = session.getBlockBag(player); + + try { + Request.request().setEditSession(editSession); + + var position = blockTrace.toBlockPoint(); + var mask = MaskIntersection.of(new InverseMask(new AirMask(player.getWorld())), + settings.getMask(session), settings.getSurfaceMask(player)); + var pattern = settings.getBrush().buildPattern(editSession, position, player, settings); + + editSession.setMask(mask); + + settings.getBrush().build(editSession, position, pattern, settings.getBrushSize() / 2d); + + } finally { + + if (bag != null) bag.flushChanges(); + + session.remember(editSession); + Request.reset(); + } + } + }); + } } diff --git a/src/main/java/net/thenextlvl/gopaint/listener/InventoryListener.java b/src/main/java/net/thenextlvl/gopaint/listener/InventoryListener.java index 92a50f8c..c46253ad 100644 --- a/src/main/java/net/thenextlvl/gopaint/listener/InventoryListener.java +++ b/src/main/java/net/thenextlvl/gopaint/listener/InventoryListener.java @@ -20,7 +20,6 @@ import lombok.RequiredArgsConstructor; import net.thenextlvl.gopaint.GoPaintPlugin; -import net.thenextlvl.gopaint.api.model.MaskMode; import net.thenextlvl.gopaint.api.model.SurfaceMode; import net.thenextlvl.gopaint.brush.standard.*; import net.thenextlvl.gopaint.menu.MainMenu; @@ -37,7 +36,7 @@ public final class InventoryListener implements Listener { private final GoPaintPlugin plugin; - @EventHandler(priority = EventPriority.LOWEST) + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void menuClick(InventoryClickEvent event) { if (!(event.getWhoClicked() instanceof Player player)) { return; @@ -150,16 +149,12 @@ public void menuClick(InventoryClickEvent event) { settings.setBrushSize(settings.getBrushSize() - 10); } } else if (event.getRawSlot() == 15 || event.getRawSlot() == 6 || event.getRawSlot() == 24) { - settings.setMaskMode(switch (settings.getMaskMode()) { - case INTERFACE -> MaskMode.WORLDEDIT; - case WORLDEDIT -> MaskMode.DISABLED; - case DISABLED -> MaskMode.INTERFACE; - }); + settings.setMaskEnabled(!settings.isMaskEnabled()); } else if (event.getRawSlot() == 16 || event.getRawSlot() == 7 || event.getRawSlot() == 25) { settings.setSurfaceMode(switch (settings.getSurfaceMode()) { - case DIRECT -> SurfaceMode.RELATIVE; - case RELATIVE -> SurfaceMode.DISABLED; - case DISABLED -> SurfaceMode.DIRECT; + case EXPOSED -> SurfaceMode.VISIBLE; + case VISIBLE -> SurfaceMode.DISABLED; + case DISABLED -> SurfaceMode.EXPOSED; }); } else if ((event.getRawSlot() >= 37 && event.getRawSlot() <= 41) || (event.getRawSlot() >= 46 && event.getRawSlot() <= 50)) { diff --git a/src/main/java/net/thenextlvl/gopaint/menu/BrushesMenu.java b/src/main/java/net/thenextlvl/gopaint/menu/BrushesMenu.java index 4b2316c4..990be91e 100644 --- a/src/main/java/net/thenextlvl/gopaint/menu/BrushesMenu.java +++ b/src/main/java/net/thenextlvl/gopaint/menu/BrushesMenu.java @@ -6,7 +6,7 @@ import lombok.Getter; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.thenextlvl.gopaint.api.brush.Brush; +import net.thenextlvl.gopaint.api.brush.PatternBrush; import net.thenextlvl.gopaint.api.brush.setting.PlayerBrushSettings; import net.thenextlvl.gopaint.api.model.GoPaintProvider; import org.bukkit.Material; @@ -15,7 +15,7 @@ import java.util.Collection; import java.util.stream.IntStream; -public class BrushesMenu extends PagedGUI { +public class BrushesMenu extends PagedGUI { private final @Getter Options options = new Options( IntStream.range(0, getSize() - 9).toArray(), getSize() - 6, @@ -36,7 +36,7 @@ public void formatDefault() { } @Override - public ActionItem constructItem(Brush brush) { + public ActionItem constructItem(PatternBrush brush) { return new ItemBuilder(Material.PLAYER_HEAD) .headValue(brush.getHeadValue()) .itemName(brush.getName(owner).color(NamedTextColor.GOLD)) @@ -54,7 +54,7 @@ public Component getPageFormat(int page) { } @Override - public Collection getElements() { + public Collection getElements() { return plugin.brushRegistry().getBrushes().toList(); } } diff --git a/src/main/java/net/thenextlvl/gopaint/menu/MainMenu.java b/src/main/java/net/thenextlvl/gopaint/menu/MainMenu.java index aaa65025..99a4dad8 100644 --- a/src/main/java/net/thenextlvl/gopaint/menu/MainMenu.java +++ b/src/main/java/net/thenextlvl/gopaint/menu/MainMenu.java @@ -37,7 +37,7 @@ public MainMenu(GoPaintPlugin plugin, PlayerBrushSettings settings, Player owner updateToggle(); updateBrush(); updateSize(); - updateMaskMode(); + updateMaskToggle(); updateSurfaceMode(); updateBlockPalette(); updateMask(); @@ -235,31 +235,20 @@ public void updateToggle() { inventory.setItem(19, placeholder); } - public void updateMaskMode() { - var icon = switch (settings.getMaskMode()) { - case DISABLED -> Material.CARVED_PUMPKIN; - case INTERFACE -> Material.JACK_O_LANTERN; - case WORLDEDIT -> Material.WOODEN_AXE; - }; + public void updateMaskToggle() { + var icon = settings.isMaskEnabled() ? Material.JACK_O_LANTERN : Material.CARVED_PUMPKIN; - var mode = plugin.bundle().component(owner, settings.getMaskMode().translationKey()) - .color(switch (settings.getMaskMode()) { - case DISABLED -> NamedTextColor.RED; - case INTERFACE -> NamedTextColor.GREEN; - case WORLDEDIT -> NamedTextColor.GOLD; - }); + var state = plugin.bundle().component(owner, settings.isMaskEnabled() ? "mask.enabled" : "mask.disabled") + .color(settings.isMaskEnabled() ? NamedTextColor.GREEN : NamedTextColor.RED); inventory.setItem(15, new ItemBuilder(icon) - .itemName(plugin.bundle().component(owner, "mask.mode")) - .lore(plugin.bundle().components(owner, "mask.mode.description", - Placeholder.component("mode", mode))) + .itemName(plugin.bundle().component(owner, "mask.state")) + .lore(plugin.bundle().components(owner, "mask.state.description", + Placeholder.component("state", state))) .itemFlags(ItemFlag.HIDE_ATTRIBUTES)); - var placeholder = new ItemBuilder(switch (settings.getMaskMode()) { - case DISABLED -> Material.RED_STAINED_GLASS_PANE; - case INTERFACE -> Material.LIME_STAINED_GLASS_PANE; - case WORLDEDIT -> Material.ORANGE_STAINED_GLASS_PANE; - }).hideTooltip(true); + var pane = settings.isMaskEnabled() ? Material.LIME_STAINED_GLASS_PANE : Material.RED_STAINED_GLASS_PANE; + var placeholder = new ItemBuilder(pane).hideTooltip(true); inventory.setItem(6, placeholder); inventory.setItem(24, placeholder); @@ -267,16 +256,16 @@ public void updateMaskMode() { public void updateSurfaceMode() { var icon = switch (settings.getSurfaceMode()) { - case DIRECT -> Material.LIGHT_WEIGHTED_PRESSURE_PLATE; + case EXPOSED -> Material.LIGHT_WEIGHTED_PRESSURE_PLATE; case DISABLED -> Material.POLISHED_BLACKSTONE_PRESSURE_PLATE; - case RELATIVE -> Material.HEAVY_WEIGHTED_PRESSURE_PLATE; + case VISIBLE -> Material.HEAVY_WEIGHTED_PRESSURE_PLATE; }; var mode = plugin.bundle().component(owner, settings.getSurfaceMode().translationKey()) .color(switch (settings.getSurfaceMode()) { - case DIRECT -> NamedTextColor.GREEN; + case EXPOSED -> NamedTextColor.GREEN; case DISABLED -> NamedTextColor.RED; - case RELATIVE -> NamedTextColor.GOLD; + case VISIBLE -> NamedTextColor.GOLD; }); inventory.setItem(16, new ItemBuilder(icon) @@ -286,9 +275,9 @@ public void updateSurfaceMode() { .itemFlags(ItemFlag.HIDE_ATTRIBUTES)); var placeholder = new ItemBuilder(switch (settings.getSurfaceMode()) { - case DIRECT -> Material.LIME_STAINED_GLASS_PANE; + case EXPOSED -> Material.LIME_STAINED_GLASS_PANE; case DISABLED -> Material.RED_STAINED_GLASS_PANE; - case RELATIVE -> Material.ORANGE_STAINED_GLASS_PANE; + case VISIBLE -> Material.ORANGE_STAINED_GLASS_PANE; }).hideTooltip(true); inventory.setItem(7, placeholder); diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index eef76ac1..809439c5 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1,8 +1,9 @@ -prefix=goPaint> +prefix=goPaint > command.gopaint.brush.disabled= Disabled brush command.gopaint.brush.enabled= Enabled brush command.gopaint.brush.size= Brush size set to: command.gopaint.reloaded= Reloaded +brush.block.sight= There is no block in sight. brush.disabled= Your brush is disabled, left click to enable the brush or type /gp toggle. brush.paint.point.set= Paint brush point # set. brush.state.enabled=Enabled @@ -14,15 +15,14 @@ brush.toggle.description=\ Click without an item to toggle menu.main.title=goPaint Menu menu.brushes.title=goPaint Brushes -mask.mode.disabled=Disabled -mask.mode.interface=Interface -mask.mode.worldedit=WorldEdit -mask.mode=Mask Mode -mask.mode.description=\ +mask.enabled=Enabled +mask.disabled=Disabled +mask.state=Mask +mask.state.description=\ Click to cycle -surface.mode.direct=Direct surface.mode.disabled=Disabled -surface.mode.relative=Relative +surface.mode.exposed=Exposed +surface.mode.visible=Visible surface.mode=Surface Mode surface.mode.description=\ Click to cycle @@ -67,7 +67,8 @@ slot.set.description=\ Right click to clear mask.block=Current Mask mask.block.description=\ - Click with a block to change + Click with a block to change\ + Right click to clear brush.size=Brush Size: brush.size.description=\ Left click to increase\ @@ -155,6 +156,5 @@ brush.exported.falloff=Falloff strength: brush.exported.mixing=Mixing strength: brush.exported.fracture=Fracture strength: brush.exported.blocks=Blocks: -brush.exported.mask-mode=Mask mode: brush.exported.surface-mode=Surface mode: brush.exported.mask=Mask: \ No newline at end of file diff --git a/src/main/resources/messages_german.properties b/src/main/resources/messages_german.properties index 4e6615c9..498468db 100644 --- a/src/main/resources/messages_german.properties +++ b/src/main/resources/messages_german.properties @@ -2,6 +2,7 @@ command.gopaint.brush.disabled= Der Pinsel wurde deaktiviert command.gopaint.brush.enabled= Der Pinsel wurde aktiviert command.gopaint.brush.size= Die Pinselgröße wurde geändert: command.gopaint.reloaded= Die Konfiguration wurde neu geladen +brush.block.sight= Es ist kein Block in Sicht. brush.disabled= Dein Pinsel ist deaktiviert, linksklick um den Pinsel zu aktivieren oder nutze /gp toggle. brush.paint.point.set= Der Pinselstrich # wurde gesetzt. brush.state.enabled=Aktiviert @@ -13,15 +14,14 @@ brush.toggle.description=\ Klicke ohne Item zum Umschalten menu.main.title=goPaint Menü menu.brushes.title=goPaint Pinsel -mask.mode.disabled=Deaktiviert -mask.mode.interface=Schnittstelle -mask.mode.worldedit=WorldEdit -mask.mode=Maskenmodus -mask.mode.description=\ +mask.enabled=Aktiviert +mask.disabled=Deaktiviert +mask.state=Maske +mask.state.description=\ Klicke zum Anpassen -surface.mode.direct=Direkt +surface.mode.exposed=Freiliegend +surface.mode.visible=Sichtbar surface.mode.disabled=Deaktiviert -surface.mode.relative=Relativ surface.mode=Oberflächenmodus surface.mode.description=\ Klicke zum Anpassen @@ -154,6 +154,5 @@ brush.exported.falloff=Fallstärke: brush.exported.mixing=Mix-Stärke: brush.exported.fracture=Fraktur-Prüfdistanz: brush.exported.blocks=Blöcke: -brush.exported.mask-mode=Maskenmodus: brush.exported.surface-mode=Oberflächenmodus: brush.exported.mask=Maske: \ No newline at end of file