diff --git a/src/main/java/de/ellpeck/naturesaura/InternalHooks.java b/src/main/java/de/ellpeck/naturesaura/InternalHooks.java index 0bad80e4..04dcdc08 100644 --- a/src/main/java/de/ellpeck/naturesaura/InternalHooks.java +++ b/src/main/java/de/ellpeck/naturesaura/InternalHooks.java @@ -3,9 +3,12 @@ package de.ellpeck.naturesaura; import baubles.api.BaublesApi; import de.ellpeck.naturesaura.api.NaturesAuraAPI; import de.ellpeck.naturesaura.api.aura.chunk.IAuraChunk; +import de.ellpeck.naturesaura.api.multiblock.IMultiblock; +import de.ellpeck.naturesaura.blocks.multi.Multiblock; import de.ellpeck.naturesaura.compat.Compat; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; @@ -64,6 +67,11 @@ public class InternalHooks implements NaturesAuraAPI.IInternalHooks { } } + @Override + public IMultiblock createMultiblock(ResourceLocation name, String[][] pattern, Object... rawMatchers) { + return new Multiblock(name, pattern, rawMatchers); + } + @Override public void getAuraSpotsInArea(World world, BlockPos pos, int radius, BiConsumer consumer) { world.profiler.func_194340_a(() -> NaturesAura.MOD_ID + ":getSpotsInArea"); diff --git a/src/main/java/de/ellpeck/naturesaura/api/NaturesAuraAPI.java b/src/main/java/de/ellpeck/naturesaura/api/NaturesAuraAPI.java index bd947e55..df12891f 100644 --- a/src/main/java/de/ellpeck/naturesaura/api/NaturesAuraAPI.java +++ b/src/main/java/de/ellpeck/naturesaura/api/NaturesAuraAPI.java @@ -7,6 +7,8 @@ import de.ellpeck.naturesaura.api.aura.item.IAuraRecharge; import de.ellpeck.naturesaura.api.aura.type.BasicAuraType; import de.ellpeck.naturesaura.api.aura.type.IAuraType; import de.ellpeck.naturesaura.api.internal.StubHooks; +import de.ellpeck.naturesaura.api.multiblock.IMultiblock; +import de.ellpeck.naturesaura.api.multiblock.Matcher; import de.ellpeck.naturesaura.api.recipes.AltarRecipe; import de.ellpeck.naturesaura.api.recipes.OfferingRecipe; import de.ellpeck.naturesaura.api.recipes.TreeRitualRecipe; @@ -38,7 +40,7 @@ public final class NaturesAuraAPI { public static final String MOD_ID = "naturesaura"; public static final String API_ID = MOD_ID + "api"; - public static final String VERSION = "3"; + public static final String VERSION = "4"; /** * The list of all {@link AltarRecipe} instances which are the recipes used @@ -90,6 +92,12 @@ public final class NaturesAuraAPI { * once a second for every drain spot currently loaded. */ public static final Map> DRAIN_SPOT_EFFECTS = new HashMap<>(); + /** + * A map of all {@link IMultiblock} objects which are multiblock structures + * that can easily be looped through and checked, and also easily created + * using the multiblock maker debug tool. + */ + public static final Map MULTIBLOCKS = new HashMap<>(); /** * The capability for any item or block that stores Aura in the form of an @@ -199,6 +207,22 @@ public final class NaturesAuraAPI { */ void spawnParticleStream(float startX, float startY, float startZ, float endX, float endY, float endZ, float speed, int color, float scale); + /** + * This method is used to create a custom multiblock from within the + * API. The multiblock will automatically be registered both to Nature's + * Aura's multiblock registry and Patchouli's multiblock registry. + * + * @param name The name the multiblock should have + * @param pattern The pattern that the multiblock should have, where + * each character is mapped to a raw matcher + * @param rawMatchers Each char matcher in the form of the char followed + * by a matcher, either in the form of a Block, an + * IBlockState or a {@link Matcher}, similar to the + * old way that crafting recipes work. + * @return the multiblock instance + */ + IMultiblock createMultiblock(ResourceLocation name, String[][] pattern, Object... rawMatchers); + /** * @see IAuraChunk#getSpotsInArea(World, BlockPos, int, BiConsumer) */ diff --git a/src/main/java/de/ellpeck/naturesaura/api/internal/StubHooks.java b/src/main/java/de/ellpeck/naturesaura/api/internal/StubHooks.java index 77b41ce4..799b2eb2 100644 --- a/src/main/java/de/ellpeck/naturesaura/api/internal/StubHooks.java +++ b/src/main/java/de/ellpeck/naturesaura/api/internal/StubHooks.java @@ -1,7 +1,9 @@ package de.ellpeck.naturesaura.api.internal; import de.ellpeck.naturesaura.api.NaturesAuraAPI; +import de.ellpeck.naturesaura.api.multiblock.IMultiblock; import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import org.apache.commons.lang3.mutable.MutableInt; @@ -24,6 +26,11 @@ public class StubHooks implements NaturesAuraAPI.IInternalHooks { } + @Override + public IMultiblock createMultiblock(ResourceLocation name, String[][] pattern, Object... rawMatchers) { + return new StubMultiblock(); + } + @Override public void getAuraSpotsInArea(World world, BlockPos pos, int radius, BiConsumer consumer) { diff --git a/src/main/java/de/ellpeck/naturesaura/api/internal/StubMultiblock.java b/src/main/java/de/ellpeck/naturesaura/api/internal/StubMultiblock.java new file mode 100644 index 00000000..cc71733d --- /dev/null +++ b/src/main/java/de/ellpeck/naturesaura/api/internal/StubMultiblock.java @@ -0,0 +1,82 @@ +package de.ellpeck.naturesaura.api.internal; + +import de.ellpeck.naturesaura.api.NaturesAuraAPI; +import de.ellpeck.naturesaura.api.multiblock.IMultiblock; +import de.ellpeck.naturesaura.api.multiblock.Matcher; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import java.util.Collections; +import java.util.Map; +import java.util.function.BiFunction; + +public class StubMultiblock implements IMultiblock { + + private static final ResourceLocation NAME = new ResourceLocation(NaturesAuraAPI.MOD_ID, "stub"); + + @Override + public boolean isComplete(World world, BlockPos center) { + return false; + } + + @Override + public boolean forEach(BlockPos center, char c, BiFunction function) { + return false; + } + + @Override + public BlockPos getStart(BlockPos center) { + return BlockPos.ORIGIN; + } + + @Override + public char getChar(BlockPos offset) { + return 0; + } + + @Override + public ResourceLocation getName() { + return NAME; + } + + @Override + public Map getMatchers() { + return Collections.emptyMap(); + } + + @Override + public int getWidth() { + return 0; + } + + @Override + public int getHeight() { + return 0; + } + + @Override + public int getDepth() { + return 0; + } + + @Override + public int getXOffset() { + return 0; + } + + @Override + public int getYOffset() { + return 0; + } + + @Override + public int getZOffset() { + return 0; + } + + @Override + public char[][][] getRawPattern() { + return new char[0][0][0]; + } +} diff --git a/src/main/java/de/ellpeck/naturesaura/api/multiblock/IMultiblock.java b/src/main/java/de/ellpeck/naturesaura/api/multiblock/IMultiblock.java new file mode 100644 index 00000000..0415357a --- /dev/null +++ b/src/main/java/de/ellpeck/naturesaura/api/multiblock/IMultiblock.java @@ -0,0 +1,37 @@ +package de.ellpeck.naturesaura.api.multiblock; + +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import java.util.Map; +import java.util.function.BiFunction; + +public interface IMultiblock { + + boolean isComplete(World world, BlockPos center); + + boolean forEach(BlockPos center, char c, BiFunction function); + + BlockPos getStart(BlockPos center); + + char getChar(BlockPos offset); + + ResourceLocation getName(); + + Map getMatchers(); + + int getWidth(); + + int getHeight(); + + int getDepth(); + + int getXOffset(); + + int getYOffset(); + + int getZOffset(); + + char[][][] getRawPattern(); +} diff --git a/src/main/java/de/ellpeck/naturesaura/api/multiblock/Matcher.java b/src/main/java/de/ellpeck/naturesaura/api/multiblock/Matcher.java new file mode 100644 index 00000000..f14007b9 --- /dev/null +++ b/src/main/java/de/ellpeck/naturesaura/api/multiblock/Matcher.java @@ -0,0 +1,64 @@ +package de.ellpeck.naturesaura.api.multiblock; + +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Blocks; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.oredict.OreDictionary; + +import java.util.ArrayList; +import java.util.List; + +public class Matcher { + + private final IBlockState defaultState; + private final ICheck check; + + public Matcher(IBlockState defaultState, ICheck check) { + this.defaultState = defaultState; + this.check = check; + } + + public IBlockState getDefaultState() { + return this.defaultState; + } + + public ICheck getCheck() { + return this.check; + } + + public static Matcher wildcard() { + return new Matcher(Blocks.AIR.getDefaultState(), null); + } + + public static Matcher oreDict(Block defaultBlock, String name) { + return new Matcher(defaultBlock.getDefaultState(), new ICheck() { + private List states; + + @Override + public boolean matches(World world, BlockPos start, BlockPos offset, BlockPos pos, IBlockState state, char c) { + if (this.states == null) { + this.states = new ArrayList<>(); + for (ItemStack stack : OreDictionary.getOres(name)) { + Block block = Block.getBlockFromItem(stack.getItem()); + if (block != null && block != Blocks.AIR) { + int damage = stack.getItemDamage(); + if (damage == OreDictionary.WILDCARD_VALUE) + this.states.addAll(block.getBlockState().getValidStates()); + else + this.states.add(block.getStateFromMeta(damage)); + } + } + } + + return this.states.isEmpty() || this.states.contains(state); + } + }); + } + + public interface ICheck { + boolean matches(World world, BlockPos start, BlockPos offset, BlockPos pos, IBlockState state, char c); + } +} diff --git a/src/main/java/de/ellpeck/naturesaura/blocks/multi/Multiblock.java b/src/main/java/de/ellpeck/naturesaura/blocks/multi/Multiblock.java index 30644298..6fc30dd3 100644 --- a/src/main/java/de/ellpeck/naturesaura/blocks/multi/Multiblock.java +++ b/src/main/java/de/ellpeck/naturesaura/blocks/multi/Multiblock.java @@ -1,34 +1,31 @@ package de.ellpeck.naturesaura.blocks.multi; +import de.ellpeck.naturesaura.api.NaturesAuraAPI; +import de.ellpeck.naturesaura.api.multiblock.IMultiblock; +import de.ellpeck.naturesaura.api.multiblock.Matcher; +import de.ellpeck.naturesaura.api.multiblock.Matcher.ICheck; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; -import net.minecraft.init.Blocks; -import net.minecraft.item.ItemStack; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; -import net.minecraftforge.oredict.OreDictionary; import vazkii.patchouli.api.PatchouliAPI; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.function.BiFunction; -public class Multiblock { +public class Multiblock implements IMultiblock { - public static final Map MULTIBLOCKS = new HashMap<>(); - - public final ResourceLocation name; - public final Map matchers = new HashMap<>(); - public final int width; - public final int height; - public final int depth; - public final int xOffset; - public final int yOffset; - public final int zOffset; - public final char[][][] rawPattern; + private final ResourceLocation name; + private final Map matchers = new HashMap<>(); + private final int width; + private final int height; + private final int depth; + private final int xOffset; + private final int yOffset; + private final int zOffset; + private final char[][][] rawPattern; public Multiblock(ResourceLocation name, String[][] pattern, Object... rawMatchers) { this.name = name; @@ -84,11 +81,15 @@ public class Multiblock { continue; Object value = rawMatchers[i + 1]; - if (value instanceof IBlockState) - matchers.put(c, Matcher.state((IBlockState) value)); - else if (value instanceof Block) - matchers.put(c, Matcher.block((Block) value)); - else + if (value instanceof IBlockState) { + IBlockState state = (IBlockState) value; + matchers.put(c, new Matcher(state, + (world, start, offset, pos, other, otherC) -> other == state)); + } else if (value instanceof Block) { + Block block = (Block) value; + matchers.put(c, new Matcher(block.getDefaultState(), + (world, start, offset, pos, state, otherC) -> state.getBlock() == block)); + } else matchers.put(c, (Matcher) value); } @@ -98,36 +99,39 @@ public class Multiblock { Matcher matcher = matchers.get(this.rawPattern[x][y][z]); if (matcher == null) throw new IllegalStateException(); - if (matcher.check != null) + if (matcher.getCheck() != null) this.matchers.put(new BlockPos(x, y, z), matcher); } for (int i = 1; i < rawMatchers.length; i += 2) { if (rawMatchers[i] instanceof Matcher) { Matcher matcher = (Matcher) rawMatchers[i]; - if (matcher.check == null) + ICheck check = matcher.getCheck(); + if (check == null) rawMatchers[i] = PatchouliAPI.instance.anyMatcher(); else - rawMatchers[i] = PatchouliAPI.instance.predicateMatcher(matcher.defaultState, - state -> matcher.check.matches(null, null, null, null, state, (char) 0)); + rawMatchers[i] = PatchouliAPI.instance.predicateMatcher(matcher.getDefaultState(), + state -> check.matches(null, null, null, null, state, (char) 0)); } } PatchouliAPI.instance.registerMultiblock(name, PatchouliAPI.instance.makeMultiblock(pattern, rawMatchers)); - MULTIBLOCKS.put(this.name, this); + NaturesAuraAPI.MULTIBLOCKS.put(this.name, this); } + @Override public boolean isComplete(World world, BlockPos center) { BlockPos start = this.getStart(center); return this.forEach(center, (char) 0, (pos, matcher) -> { BlockPos offset = pos.subtract(start); - return matcher.check.matches(world, start, offset, pos, world.getBlockState(pos), this.getChar(offset)); + return matcher.getCheck().matches(world, start, offset, pos, world.getBlockState(pos), this.getChar(offset)); }); } - public boolean forEach(BlockPos center, char c, BiFunction function) { + @Override + public boolean forEach(BlockPos center, char c, BiFunction function) { BlockPos start = this.getStart(center); - for (Map.Entry entry : this.matchers.entrySet()) { + for (Map.Entry entry : this.matchers.entrySet()) { BlockPos offset = entry.getKey(); if (c == 0 || this.getChar(offset) == c) if (!function.apply(start.add(offset), entry.getValue())) @@ -136,65 +140,58 @@ public class Multiblock { return true; } + @Override public BlockPos getStart(BlockPos center) { return center.add(-this.xOffset, -this.yOffset, -this.zOffset); } + @Override public char getChar(BlockPos offset) { return this.rawPattern[offset.getX()][offset.getY()][offset.getZ()]; } - public static class Matcher { - - public final IBlockState defaultState; - public final IMatcher check; - - public Matcher(IBlockState defaultState, IMatcher check) { - this.defaultState = defaultState; - this.check = check; - } - - public static Matcher state(IBlockState state) { - return new Matcher(state, - (world, start, offset, pos, other, c) -> other == state); - } - - public static Matcher block(Block block) { - return new Matcher(block.getDefaultState(), - (world, start, offset, pos, state, c) -> state.getBlock() == block); - } - - public static Matcher wildcard() { - return new Matcher(Blocks.AIR.getDefaultState(), null); - } - - public static Matcher oreDict(Block defaultBlock, String name) { - return new Matcher(defaultBlock.getDefaultState(), new IMatcher() { - private List states; - - @Override - public boolean matches(World world, BlockPos start, BlockPos offset, BlockPos pos, IBlockState state, char c) { - if (this.states == null) { - this.states = new ArrayList<>(); - for (ItemStack stack : OreDictionary.getOres(name)) { - Block block = Block.getBlockFromItem(stack.getItem()); - if (block != null && block != Blocks.AIR) { - int damage = stack.getItemDamage(); - if (damage == OreDictionary.WILDCARD_VALUE) - this.states.addAll(block.getBlockState().getValidStates()); - else - this.states.add(block.getStateFromMeta(damage)); - } - } - } - - return this.states.isEmpty() || this.states.contains(state); - } - }); - } + @Override + public ResourceLocation getName() { + return this.name; } - public interface IMatcher { - boolean matches(World world, BlockPos start, BlockPos offset, BlockPos pos, IBlockState state, char c); + @Override + public Map getMatchers() { + return this.matchers; + } + + @Override + public int getWidth() { + return this.width; + } + + @Override + public int getHeight() { + return this.height; + } + + @Override + public int getDepth() { + return this.depth; + } + + @Override + public int getXOffset() { + return this.xOffset; + } + + @Override + public int getYOffset() { + return this.yOffset; + } + + @Override + public int getZOffset() { + return this.zOffset; + } + + @Override + public char[][][] getRawPattern() { + return this.rawPattern; } } diff --git a/src/main/java/de/ellpeck/naturesaura/blocks/multi/Multiblocks.java b/src/main/java/de/ellpeck/naturesaura/blocks/multi/Multiblocks.java index 666f32fd..7fed0cb2 100644 --- a/src/main/java/de/ellpeck/naturesaura/blocks/multi/Multiblocks.java +++ b/src/main/java/de/ellpeck/naturesaura/blocks/multi/Multiblocks.java @@ -2,8 +2,9 @@ package de.ellpeck.naturesaura.blocks.multi; import de.ellpeck.naturesaura.NaturesAura; import de.ellpeck.naturesaura.api.NaturesAuraAPI; +import de.ellpeck.naturesaura.api.multiblock.IMultiblock; +import de.ellpeck.naturesaura.api.multiblock.Matcher; import de.ellpeck.naturesaura.blocks.ModBlocks; -import de.ellpeck.naturesaura.blocks.multi.Multiblock.Matcher; import net.minecraft.block.BlockLog; import net.minecraft.block.BlockSapling; import net.minecraft.block.BlockStoneBrick; @@ -13,7 +14,7 @@ import net.minecraft.util.ResourceLocation; public final class Multiblocks { - public static final Multiblock ALTAR = new Multiblock( + public static final IMultiblock ALTAR = NaturesAuraAPI.instance().createMultiblock( new ResourceLocation(NaturesAura.MOD_ID, "altar"), new String[][]{ {" M ", " ", " ", " ", "M M", " ", " ", " ", " M "}, @@ -26,17 +27,17 @@ public final class Multiblocks { 'M', Blocks.STONEBRICK.getDefaultState().withProperty(BlockStoneBrick.VARIANT, EnumType.MOSSY), '0', ModBlocks.NATURE_ALTAR, ' ', Matcher.wildcard()); - public static final Multiblock TREE_RITUAL = new Multiblock( + public static final IMultiblock TREE_RITUAL = NaturesAuraAPI.instance().createMultiblock( new ResourceLocation(NaturesAura.MOD_ID, "tree_ritual"), new String[][]{ {" W ", " W W ", " GGG ", " GG GG ", "W G 0 G W", " GG GG ", " GGG ", " W W ", " W "}}, 'W', new Matcher(ModBlocks.WOOD_STAND.getDefaultState(), - (world, start, offset, pos, state, c) -> world != null || state.getBlock() == ModBlocks.WOOD_STAND), + (world, start, offset, pos, state, c) -> world != null || state.getBlock() == ModBlocks.WOOD_STAND), 'G', ModBlocks.GOLD_POWDER, '0', new Matcher(Blocks.SAPLING.getDefaultState(), - (world, start, offset, pos, state, c) -> state.getBlock() instanceof BlockSapling || state.getBlock() instanceof BlockLog), + (world, start, offset, pos, state, c) -> state.getBlock() instanceof BlockSapling || state.getBlock() instanceof BlockLog), ' ', Matcher.wildcard()); - public static final Multiblock POTION_GENERATOR = new Multiblock( + public static final IMultiblock POTION_GENERATOR = NaturesAuraAPI.instance().createMultiblock( new ResourceLocation(NaturesAura.MOD_ID, "potion_generator"), new String[][]{ {"R R", " ", " ", " ", " ", " ", "R R"}, @@ -47,12 +48,12 @@ public final class Multiblocks { 'R', Blocks.RED_NETHER_BRICK, '0', ModBlocks.POTION_GENERATOR, ' ', Matcher.wildcard()); - public static final Multiblock OFFERING_TABLE = new Multiblock( + public static final IMultiblock OFFERING_TABLE = NaturesAuraAPI.instance().createMultiblock( new ResourceLocation(NaturesAura.MOD_ID, "offering_table"), new String[][]{ {" RRRRR ", " R R ", "R RRR R", "R R R R", "R R 0 R R", "R R R R", "R RRR R", " R R ", " RRRRR "}}, 'R', new Matcher(Blocks.RED_FLOWER.getDefaultState(), - (world, start, offset, pos, state, c) -> NaturesAuraAPI.FLOWERS.contains(state)), + (world, start, offset, pos, state, c) -> NaturesAuraAPI.FLOWERS.contains(state)), '0', ModBlocks.OFFERING_TABLE, ' ', Matcher.wildcard()); } diff --git a/src/main/java/de/ellpeck/naturesaura/items/ItemMultiblockMaker.java b/src/main/java/de/ellpeck/naturesaura/items/ItemMultiblockMaker.java index 8c56f5bf..cb3dfef3 100644 --- a/src/main/java/de/ellpeck/naturesaura/items/ItemMultiblockMaker.java +++ b/src/main/java/de/ellpeck/naturesaura/items/ItemMultiblockMaker.java @@ -1,6 +1,7 @@ package de.ellpeck.naturesaura.items; -import de.ellpeck.naturesaura.blocks.multi.Multiblock; +import de.ellpeck.naturesaura.api.NaturesAuraAPI; +import de.ellpeck.naturesaura.api.multiblock.IMultiblock; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; @@ -16,7 +17,7 @@ import java.util.List; public class ItemMultiblockMaker extends ItemImpl { - private static List multiblocks; + private static List multiblocks; public ItemMultiblockMaker() { super("multiblock_maker"); @@ -39,13 +40,13 @@ public class ItemMultiblockMaker extends ItemImpl { @Override public EnumActionResult onItemUse(EntityPlayer player, World worldIn, BlockPos pos, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) { if (player.capabilities.isCreativeMode) { - Multiblock multi = multiblocks().get(getMultiblock(player.getHeldItem(hand))); + IMultiblock multi = multiblocks().get(getMultiblock(player.getHeldItem(hand))); if (multi == null) return EnumActionResult.PASS; if (!worldIn.isRemote) multi.forEach(pos.up(), (char) 0, (blockPos, matcher) -> { - worldIn.setBlockState(blockPos, matcher.defaultState); + worldIn.setBlockState(blockPos, matcher.getDefaultState()); return true; }); @@ -57,8 +58,8 @@ public class ItemMultiblockMaker extends ItemImpl { @Override public String getItemStackDisplayName(ItemStack stack) { String name = super.getItemStackDisplayName(stack); - Multiblock multi = multiblocks().get(getMultiblock(stack)); - return multi == null ? name : name + " (" + multi.name + ")"; + IMultiblock multi = multiblocks().get(getMultiblock(stack)); + return multi == null ? name : name + " (" + multi.getName() + ")"; } private static int getMultiblock(ItemStack stack) { @@ -67,10 +68,10 @@ public class ItemMultiblockMaker extends ItemImpl { return stack.getTagCompound().getInteger("multiblock"); } - private static List multiblocks() { + private static List multiblocks() { if (multiblocks == null) { multiblocks = new ArrayList<>(); - multiblocks.addAll(Multiblock.MULTIBLOCKS.values()); + multiblocks.addAll(NaturesAuraAPI.MULTIBLOCKS.values()); } return multiblocks; }