diff --git a/src/main/java/com/volmit/iris/core/TreeManager.java b/src/main/java/com/volmit/iris/core/TreeManager.java index 5cdf80bd2..fa64de21f 100644 --- a/src/main/java/com/volmit/iris/core/TreeManager.java +++ b/src/main/java/com/volmit/iris/core/TreeManager.java @@ -4,18 +4,17 @@ import com.volmit.iris.Iris; import com.volmit.iris.engine.IrisWorlds; import com.volmit.iris.engine.framework.IrisAccess; import com.volmit.iris.engine.object.*; +import com.volmit.iris.engine.object.common.IObjectPlacer; +import com.volmit.iris.engine.object.tile.TileData; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.math.RNG; import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.TreeType; +import org.bukkit.block.TileState; +import org.bukkit.block.data.BlockData; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.world.StructureGrowEvent; -import org.bukkit.util.Vector; -import org.jetbrains.annotations.NotNull; -import java.util.Arrays; import java.util.Objects; public class TreeManager implements Listener { @@ -46,6 +45,7 @@ public class TreeManager implements Listener { return; } + // Get world access IrisAccess worldAccess; try { worldAccess = Objects.requireNonNull(IrisWorlds.access(event.getWorld())); @@ -53,20 +53,157 @@ public class TreeManager implements Listener { Iris.reportError(e); return; } - IrisTreeSettings settings = worldAccess.getCompound().getRootDimension().getSaplingSettings(); - Iris.debug("Custom saplings are enabled: " + (settings.isEnabled() ? "Yes" : "No")); + Iris.debug("Custom saplings are " + (worldAccess.getCompound().getRootDimension().getSaplingSettings().isEnabled() ? "" : "NOT") + " enabled."); - // Must have override enabled - if (!settings.isEnabled()) { + // Calculate size, type & placement + IrisTreeType type = IrisTreeType.fromTreeType(event.getSpecies()); + IrisTreeSize size = getTreeSize(event.getLocation(), type); + IrisObjectPlacement placement = getObjectPlacement(worldAccess, type, event.getLocation(), size); + + // Make sure placement was found + if (placement == null){ return; } - KList treeObjects = new KList<>(); + // Get object from placer + IrisObject f = worldAccess.getData().getObjectLoader().load(placement.getPlace().getRandom(RNG.r)); - // Get biome and region - IrisBiome biome = worldAccess.getBiome(event.getLocation().getBlockX(), event.getLocation().getBlockY(), event.getLocation().getBlockZ()); - IrisRegion region = worldAccess.getCompound().getDefaultEngine().getRegion(event.getLocation().getBlockX(), event.getLocation().getBlockZ()); + // TODO: Implement placer + IObjectPlacer placer = new IObjectPlacer(){ + @Override + public int getHighest(int x, int z) { + return 0; + } + + @Override + public int getHighest(int x, int z, boolean ignoreFluid) { + return 0; + } + + @Override + public void set(int x, int y, int z, BlockData d) { + + } + + @Override + public BlockData get(int x, int y, int z) { + return null; + } + + @Override + public boolean isPreventingDecay() { + return false; + } + + @Override + public boolean isSolid(int x, int y, int z) { + return false; + } + + @Override + public boolean isUnderwater(int x, int z) { + return false; + } + + @Override + public int getFluidHeight() { + return 0; + } + + @Override + public boolean isDebugSmartBore() { + return false; + } + + @Override + public void setTile(int xx, int yy, int zz, TileData tile) { + + } + }; + + // TODO: Figure out how to place without wrecking claims, other builds, etc. + // Especially with large object + + // Place the object with the placer + f.place( + event.getLocation().getBlockX(), + event.getLocation().getBlockY(), + event.getLocation().getBlockZ(), + placer, + placement, + RNG.r, + Objects.requireNonNull(IrisWorlds.access(event.getWorld())).getData() + ); + } + + /** + * Finds the tree size + * @param location The location the event triggers from. This sapling's Material type is used to check other locations + * @return The size of the tree + */ + private IrisTreeSize getTreeSize(Location location, IrisTreeType type) { + KList validSizes = new KList<>(); + + IrisTreeSize.isSizeValid(); + + return IrisTreeSize.bestSize(validSizes); + } + + /** + * Finds a single object placement (which may contain more than one object) for the requirements species, location & size + * @param worldAccess The world to access (check for biome, region, dimension, etc) + * @param type The bukkit TreeType to match + * @param location The location of the growth event (For biome/region finding) + * @param size The size of the sapling area + * @return An object placement which contains the matched tree, or null if none were found / it's disabled. + */ + private IrisObjectPlacement getObjectPlacement(IrisAccess worldAccess, IrisTreeType type, Location location, IrisTreeSize size) { + IrisDimension dimension = worldAccess.getCompound().getRootDimension(); + + // Return null if not enabled + if (!dimension.getSaplingSettings().isEnabled()) { + return null; + } + + KList placements = new KList<>(); + + // Retrieve objectPlacements of type `species` from biome + IrisBiome biome = worldAccess.getBiome(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + placements.addAll(matchObjectPlacements(biome.getObjects(), size, type)); + + // Add more or find any in the region + if (dimension.getSaplingSettings().getMode().equals(IrisTreeModes.ALL) || placements.isEmpty()){ + IrisRegion region = worldAccess.getCompound().getDefaultEngine().getRegion(location.getBlockX(), location.getBlockZ()); + placements.addAll(matchObjectPlacements(region.getObjects(), size, type)); + } + + // Add more or find any in the dimension + if (dimension.getSaplingSettings().getMode().equals(IrisTreeModes.ALL) || placements.isEmpty()){ + //TODO: Implement object placement in dimension & here + //placements.addAll(matchObjectPlacements(dimension.getObjects(), size, type)); + } + + // Check if no matches were found, return a random one if they are + return placements.isNotEmpty() ? placements.getRandom(RNG.r) : null; + } + + /** + * Filters out mismatches and returns matches + * @param objects The object placements to check + * @param size The size of the sapling area to filter with + * @param type The type of the tree to filter with + * @return A list of objectPlacements that matched. May be empty. + */ + private KList matchObjectPlacements(KList objects, IrisTreeSize size, IrisTreeType type) { + KList objectPlacements = new KList<>(); + objects.stream() + .filter(objectPlacement -> objectPlacement.getTreeOptions().isEnabled()) + .filter(objectPlacement -> objectPlacement.getTreeOptions().getTrees().stream().anyMatch(irisTree -> + irisTree.getSizes().stream().anyMatch(treeSize -> treeSize == size) && + irisTree.getTreeTypes().stream().anyMatch(treeType -> treeType == type))) + .forEach(objectPlacements::add); + return objectPlacements; } } diff --git a/src/main/java/com/volmit/iris/engine/object/IrisTreeOptions.java b/src/main/java/com/volmit/iris/engine/object/IrisTreeOptions.java index 2aea54e6e..20cbd5881 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisTreeOptions.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisTreeOptions.java @@ -1,5 +1,6 @@ package com.volmit.iris.engine.object; +import com.volmit.iris.engine.object.annotations.ArrayType; import com.volmit.iris.engine.object.annotations.Desc; import com.volmit.iris.util.collection.KList; import lombok.AllArgsConstructor; @@ -18,5 +19,6 @@ public class IrisTreeOptions { private boolean enabled = false; @Desc("Tree overrides affected by these object placements") + @ArrayType(min = 1, type = IrisTree.class) private KList trees = new KList<>(); } diff --git a/src/main/java/com/volmit/iris/engine/object/IrisTreeSize.java b/src/main/java/com/volmit/iris/engine/object/IrisTreeSize.java index 1f89ec267..71dcfe509 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisTreeSize.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisTreeSize.java @@ -1,6 +1,11 @@ package com.volmit.iris.engine.object; import com.volmit.iris.engine.object.annotations.Desc; +import com.volmit.iris.util.collection.KList; +import org.bukkit.Location; +import org.bukkit.Material; + +import java.util.Objects; @Desc("Sapling override object picking options") public enum IrisTreeSize { @@ -11,25 +16,184 @@ public enum IrisTreeSize { @Desc("Two by two area") TWO, - @Desc("Three by three area with center") - THREE_CENTER, - @Desc("Three by three any location") THREE_ANY, + @Desc("Three by three area with center") + THREE_CENTER, + @Desc("Four by four") FOUR, - @Desc("Five by five center") - FIVE_CENTER, - @Desc("Five by five") - FIVE_ANY; + FIVE_ANY, - public static boolean isAnySize(IrisTreeSize treeSize){ + @Desc("Five by five center") + FIVE_CENTER; + + /** + * Whether the position of the any type (not fixed at a center) + * @param treeSize The treesize to check + */ + public static boolean isAnyPosition(IrisTreeSize treeSize){ return switch (treeSize) { - case THREE_CENTER, FIVE_CENTER -> false; + case ONE, THREE_CENTER, FIVE_CENTER -> false; default -> true; }; } + + /** + * Get the best size to match against from a list of sizes + * @param sizes The list of sizes + * @return The best size (highest & center > any) + */ + public static IrisTreeSize bestSize(KList sizes){ + if (sizes.contains(FIVE_CENTER)){ + return FIVE_CENTER; + } + else if (sizes.contains(FIVE_ANY)){ + return FIVE_ANY; + } + else if (sizes.contains(FOUR)){ + return FOUR; + } + else if (sizes.contains(THREE_CENTER)){ + return THREE_CENTER; + } + else if (sizes.contains(THREE_ANY)){ + return THREE_ANY; + } + else if (sizes.contains(TWO)){ + return TWO; + } + else if (sizes.contains(ONE)){ + return ONE; + } else { + return null; + } + } + + public static IrisTreeSize getBestSize(Location location){ + KList sizes = new KList<>(ONE, TWO, THREE_ANY, THREE_CENTER, FOUR, FIVE_ANY, FIVE_CENTER); + while (sizes.isNotEmpty()){ + + // Find the best size & remove from list + IrisTreeSize bestSize = bestSize(sizes); + assert bestSize != null; + sizes.remove(bestSize); + + // Find the best match + KList> best = isSizeValid(bestSize, location); + if (best != null){ + return bestSize; + } + } + return ONE; + } + + /** + * Check if the size at a specific location is valid + * @param size the IrisTreeSize to check + * @param location at this location + * @return A list of locations if any match, or null if not. + */ + public static KList> isSizeValid(IrisTreeSize size, Location location) { + switch (size){ + case ONE -> { + return new KList>(new KList<>(location)); + } + case TWO -> { + return loopLocation(location, 2, location.getBlock().getType()); + } + case THREE_ANY -> { + return loopLocation(location, 3, location.getBlock().getType()); + } + case THREE_CENTER -> { + KList> locations = getMap(3, location, true); + if (locations == null) { + return null; + } + return isMapValid(locations, location.getBlock().getType()) ? locations : null; + } + case FOUR -> { + return loopLocation(location, 4, location.getBlock().getType()); + } + case FIVE_ANY -> { + return loopLocation(location, 5, location.getBlock().getType()); + } + case FIVE_CENTER -> { + KList> locations = getMap(5, location, true); + if (locations == null) { + return null; + } + return isMapValid(locations, location.getBlock().getType()) ? locations : null; + } + default -> { + return null; + } + } + } + + /** + * Loops over all possible squares based on + * @param location top left position + * @param size a square size + * @param blockType and a type of a block + * @return A list of matching locations, or null. + */ + private static KList> loopLocation(Location location, int size, Material blockType){ + Location leftTop = location.add(-size + 1, 0, -size + 1); + KList> locations; + for (int i = -size + 1; i <= 0; i++){ + for (int j = -size + 1; j <= 0; j++){ + locations = getMap(size, leftTop.add(i, 0, j)); + if (isMapValid(locations, blockType)){ + return locations; + } + } + } + return null; + } + + /** + * Get if the map is valid, compared to a block material + * @param map The map to check inside of + * @param block The block material to check with + * @return True if it's valid + */ + private static boolean isMapValid(KList> map, Material block){ + return map.stream().allMatch(row -> row.stream().allMatch(location -> location.getBlock().getType().equals(block))); + } + + /** + * Get a map with all blocks in a + * @param size `size * size` square area + * @param leftTop starting from the here + * @return A map with all block locations in the area + */ + private static KList> getMap(int size, Location leftTop){ + KList> locations = new KList<>(); + for (int i = 0; i < size; i++){ + KList row = new KList<>(); + for (int j = 0; j < size; j++){ + row.add(leftTop.add(i, 0, j)); + } + locations.add(row); + } + return locations; + } + + /** + * Get a map with all blocks in a + * @param size `size * size` square to check, must be odd (returns null if not) + * @param center from a center + * @param useCenter boolean toggle to call this function over IrisTreeSize#getMap(size, leftTop) + * @return A map with all block locations in the area + */ + private static KList> getMap(int size, Location center, boolean useCenter){ + if (size % 2 != 1){ + return null; + } + return getMap(size, center.add(-(size - 1) / 2d, 0, -(size - 1) / 2d)); + } }