exposed the simple multiblock system to the API

This commit is contained in:
Ellpeck 2018-11-23 19:21:52 +01:00
parent e1f0bf63c3
commit c6f0086d09
9 changed files with 318 additions and 97 deletions

View file

@ -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<BlockPos, MutableInt> consumer) {
world.profiler.func_194340_a(() -> NaturesAura.MOD_ID + ":getSpotsInArea");

View file

@ -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<ResourceLocation, Supplier<IDrainSpotEffect>> 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<ResourceLocation, IMultiblock> 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)
*/

View file

@ -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<BlockPos, MutableInt> consumer) {

View file

@ -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<BlockPos, Matcher, Boolean> 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<BlockPos, Matcher> 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];
}
}

View file

@ -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<BlockPos, Matcher, Boolean> function);
BlockPos getStart(BlockPos center);
char getChar(BlockPos offset);
ResourceLocation getName();
Map<BlockPos, Matcher> getMatchers();
int getWidth();
int getHeight();
int getDepth();
int getXOffset();
int getYOffset();
int getZOffset();
char[][][] getRawPattern();
}

View file

@ -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<IBlockState> 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);
}
}

View file

@ -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<ResourceLocation, Multiblock> MULTIBLOCKS = new HashMap<>();
public final ResourceLocation name;
public final Map<BlockPos, Matcher> 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<BlockPos, de.ellpeck.naturesaura.api.multiblock.Matcher> 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<BlockPos, Matcher, Boolean> function) {
@Override
public boolean forEach(BlockPos center, char c, BiFunction<BlockPos, de.ellpeck.naturesaura.api.multiblock.Matcher, Boolean> function) {
BlockPos start = this.getStart(center);
for (Map.Entry<BlockPos, Matcher> entry : this.matchers.entrySet()) {
for (Map.Entry<BlockPos, de.ellpeck.naturesaura.api.multiblock.Matcher> 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<IBlockState> 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<BlockPos, de.ellpeck.naturesaura.api.multiblock.Matcher> 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;
}
}

View file

@ -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());
}

View file

@ -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<Multiblock> multiblocks;
private static List<IMultiblock> 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<Multiblock> multiblocks() {
private static List<IMultiblock> multiblocks() {
if (multiblocks == null) {
multiblocks = new ArrayList<>();
multiblocks.addAll(Multiblock.MULTIBLOCKS.values());
multiblocks.addAll(NaturesAuraAPI.MULTIBLOCKS.values());
}
return multiblocks;
}