package openblocks.common.tileentity; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.primitives.Doubles; import com.google.common.primitives.Ints; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.ChatComponentTranslation; import openblocks.Config; import openblocks.common.item.ItemGuide; import openblocks.shapes.CoordShape; import openblocks.shapes.GuideShape; import openmods.api.IAddAwareTile; import openmods.api.INeighbourAwareTile; import openmods.geometry.HalfAxis; import openmods.geometry.Orientation; import openmods.shapes.IShapeGenerator; import openmods.shapes.IShapeable; import openmods.sync.ISyncListener; import openmods.sync.ISyncableObject; import openmods.sync.SyncableBoolean; import openmods.sync.SyncableEnum; import openmods.sync.SyncableInt; import openmods.sync.SyncableVarInt; import openmods.sync.drops.DroppableTileEntity; import openmods.sync.drops.StoreOnDrop; import openmods.utils.CollectionUtils; import openmods.utils.ColorUtils; import openmods.utils.ColorUtils.ColorMeta; import openmods.utils.Coord; import openmods.utils.MathUtils; import openperipheral.api.adapter.Asynchronous; import openperipheral.api.adapter.method.Alias; import openperipheral.api.adapter.method.Arg; import openperipheral.api.adapter.method.ReturnType; import openperipheral.api.adapter.method.ScriptCallable; import openperipheral.api.struct.ScriptStruct; import openperipheral.api.struct.StructField; public class TileEntityGuide extends DroppableTileEntity implements ISyncListener, INeighbourAwareTile, IAddAwareTile { private static final Comparator<Coord> COORD_COMPARATOR = new Comparator<Coord>() { @Override public int compare(Coord o1, Coord o2) { { // first, go from bottom to top int result = Ints.compare(o1.y, o2.y); if (result != 0) return result; } { // then sort by angle, to make placement more intuitive final double angle1 = Math.atan2(o1.z, o1.x); final double angle2 = Math.atan2(o2.z, o2.x); int result = Doubles.compare(angle1, angle2); if (result != 0) return result; } { // then sort by distance, far ones first final double length1 = MathUtils.lengthSq(o1.x, o1.z); final double length2 = MathUtils.lengthSq(o2.x, o2.z); int result = Doubles.compare(length2, length1); if (result != 0) return result; } // then sort by x and z to make all unique coordinates are included { int result = Ints.compare(o1.x, o2.x); if (result != 0) return result; } { int result = Ints.compare(o1.z, o2.z); return result; } } }; private CoordShape shape; private CoordShape previousShape; private CoordShape toDeleteShape; private float timeSinceChange = 0; private AxisAlignedBB renderAABB; @StoreOnDrop(name = ItemGuide.TAG_POS_X) protected SyncableVarInt posX; @StoreOnDrop(name = ItemGuide.TAG_POS_Y) protected SyncableVarInt posY; @StoreOnDrop(name = ItemGuide.TAG_POS_Z) protected SyncableVarInt posZ; @StoreOnDrop(name = ItemGuide.TAG_NEG_X) protected SyncableVarInt negX; @StoreOnDrop(name = ItemGuide.TAG_NEG_Y) protected SyncableVarInt negY; @StoreOnDrop(name = ItemGuide.TAG_NEG_Z) protected SyncableVarInt negZ; @StoreOnDrop(name = ItemGuide.TAG_SHAPE) protected SyncableEnum<GuideShape> mode; @StoreOnDrop(name = ItemGuide.TAG_COLOR) protected SyncableInt color; protected SyncableBoolean active; private final Map<HalfAxis, SyncableVarInt> axisDimensions = Maps.newEnumMap(HalfAxis.class); public TileEntityGuide() { syncMap.addUpdateListener(this); axisDimensions.put(HalfAxis.NEG_X, negX); axisDimensions.put(HalfAxis.NEG_Y, negY); axisDimensions.put(HalfAxis.NEG_Z, negZ); axisDimensions.put(HalfAxis.POS_X, posX); axisDimensions.put(HalfAxis.POS_Y, posY); axisDimensions.put(HalfAxis.POS_Z, posZ); } @Override protected void createSyncedFields() { posX = new SyncableVarInt(8); posY = new SyncableVarInt(8); posZ = new SyncableVarInt(8); negX = new SyncableVarInt(8); negY = new SyncableVarInt(8); negZ = new SyncableVarInt(8); mode = SyncableEnum.create(GuideShape.Sphere); color = new SyncableInt(0xFFFFFF); active = new SyncableBoolean(); } @ScriptStruct public static class ShapeSize { @StructField public int negX; @StructField public int negY; @StructField public int negZ; @StructField public int posX; @StructField public int posY; @StructField public int posZ; } @Asynchronous @ScriptCallable(returnTypes = ReturnType.TABLE) public ShapeSize getSize() { ShapeSize result = new ShapeSize(); result.negX = negX.get(); result.negY = negY.get(); result.negZ = negZ.get(); result.posX = posX.get(); result.posY = posY.get(); result.posZ = posZ.get(); return result; } @ScriptCallable public void setSize(ShapeSize size) { Preconditions.checkArgument(size.negX > 0, "NegX must be > 0"); negX.set(size.negX); Preconditions.checkArgument(size.negY > 0, "NegY must be > 0"); negY.set(size.negY); Preconditions.checkArgument(size.negZ > 0, "NegZ must be > 0"); negZ.set(size.negZ); Preconditions.checkArgument(size.posX > 0, "PosX must be > 0"); posX.set(size.posX); Preconditions.checkArgument(size.negY > 0, "PosY must be > 0"); posY.set(size.posY); Preconditions.checkArgument(size.negZ > 0, "PosZ must be > 0"); posZ.set(size.posZ); recreateShape(); sync(); } @Asynchronous @ScriptCallable(returnTypes = ReturnType.NUMBER) public int getColor() { return color.get() & 0x00FFFFFF; } @Asynchronous @ScriptCallable(returnTypes = ReturnType.NUMBER) public int getCount() { if (shape == null) recreateShape(); return shape.size(); } @Asynchronous @ScriptCallable(returnTypes = ReturnType.STRING, name = "getShape") public GuideShape getCurrentMode() { return mode.get(); } @ScriptCallable public void setShape(@Arg(name = "shape") GuideShape shape) { mode.set(shape); recreateShape(); sync(); } @ScriptCallable public void setColor(@Arg(name = "color") int color) { this.color.set(color & 0x00FFFFFF); sync(); } public boolean incrementHalfAxis(HalfAxis axis, EntityPlayerMP player) { final SyncableVarInt v = axisDimensions.get(axis); v.modify(+1); afterDimensionsChange(player); return true; } public boolean decrementHalfAxis(HalfAxis axis, EntityPlayerMP player) { final SyncableVarInt v = axisDimensions.get(axis); if (v.get() > 0) { v.modify(-1); afterDimensionsChange(player); return true; } return false; } public boolean copyHalfAxis(HalfAxis from, HalfAxis to, EntityPlayerMP player) { final SyncableVarInt fromV = axisDimensions.get(from); final SyncableVarInt toV = axisDimensions.get(to); toV.set(fromV.get()); afterDimensionsChange(player); return true; } public void incrementMode(EntityPlayer player) { incrementMode(); displayModeChange(player); displayBlockCount(player); } public void decrementMode(EntityPlayer player) { decrementMode(); displayModeChange(player); displayBlockCount(player); } private void displayModeChange(EntityPlayer player) { player.addChatMessage(new ChatComponentTranslation("openblocks.misc.change_mode", getCurrentMode().getLocalizedName())); } private void displayBlockCount(EntityPlayer player) { player.addChatMessage(new ChatComponentTranslation("openblocks.misc.total_blocks", shape.size())); } public boolean shouldRender() { return Config.guideRedstone == 0 || ((Config.guideRedstone < 0) ^ active.get()); } @Override public void updateEntity() { if (worldObj.isRemote) { if (timeSinceChange < 1.0) { timeSinceChange = (float)Math.min(1.0f, timeSinceChange + 0.1); } } } public float getTimeSinceChange() { return timeSinceChange; } private void recreateShape() { toDeleteShape = previousShape; previousShape = shape; shape = new CoordShape(generateShape()); renderAABB = null; } private List<Coord> generateShape() { final IShapeGenerator generator = getCurrentMode().generator; final Set<Coord> uniqueResults = Sets.newHashSet(); final IShapeable collector = new IShapeable() { @Override public void setBlock(int x, int y, int z) { if (canAddCoord(x, y, z)) uniqueResults.add(new Coord(x, y, z)); } }; generator.generateShape(-negX.get(), -negY.get(), -negZ.get(), posX.get(), posY.get(), posZ.get(), collector); final List<Coord> sortedResults = Lists.newArrayList(uniqueResults); Collections.sort(sortedResults, COORD_COMPARATOR); final List<Coord> rotatedResult = Lists.newArrayList(); final Orientation orientation = getOrientation(); for (Coord c : sortedResults) { final int tx = orientation.transformX(c.x, c.y, c.z); final int ty = orientation.transformY(c.x, c.y, c.z); final int tz = orientation.transformZ(c.x, c.y, c.z); rotatedResult.add(new Coord(tx, ty, tz)); } return ImmutableList.copyOf(rotatedResult); } protected boolean canAddCoord(int x, int y, int z) { return true; } public CoordShape getShape() { return shape; } public CoordShape getPreviousShape() { return previousShape; } public CoordShape getAndDeleteShape() { CoordShape toDel = toDeleteShape; toDeleteShape = null; return toDel; } @Override public void updateContainingBlockInfo() { super.updateContainingBlockInfo(); // remote world will be updated by desctiption packet from block rotate if (!worldObj.isRemote) recreateShape(); } @Override @SideOnly(Side.CLIENT) public AxisAlignedBB getRenderBoundingBox() { if (renderAABB == null) renderAABB = createRenderAABB(); return renderAABB.copy(); } private AxisAlignedBB createRenderAABB() { final AxisAlignedBB box = AxisAlignedBB.getBoundingBox(0, 0, 0, 1, 1, 1); if (shape != null) { for (Coord c : shape.getCoords()) { if (box.maxX < c.x) box.maxX = c.x; if (box.maxY < c.y) box.maxY = c.y; if (box.maxZ < c.z) box.maxZ = c.z; if (box.minX > c.x) box.minX = c.x; if (box.minY > c.y) box.minY = c.y; if (box.minZ > c.z) box.minZ = c.z; } } return box.offset(xCoord, yCoord, zCoord); } @Override @SideOnly(Side.CLIENT) public double getMaxRenderDistanceSquared() { return Config.guideRenderRangeSq; } @Alias("cycleShape") @ScriptCallable(returnTypes = ReturnType.STRING) public GuideShape incrementMode() { final GuideShape shape = mode.increment(); recreateShape(); sync(); return shape; } @ScriptCallable(returnTypes = ReturnType.STRING) public GuideShape decrementMode() { final GuideShape shape = mode.decrement(); recreateShape(); sync(); return shape; } private void notifyPlayer(EntityPlayer player) { player.addChatMessage(new ChatComponentTranslation("openblocks.misc.change_box_size", -negX.get(), -negY.get(), -negZ.get(), +posX.get(), +posY.get(), +posZ.get())); displayBlockCount(player); } private void afterDimensionsChange(EntityPlayer player) { recreateShape(); sync(); notifyPlayer(player); } @Override public void onSync(Set<ISyncableObject> changes) { if (changes.contains(negX) || changes.contains(negY) || changes.contains(negZ) || changes.contains(posX) || changes.contains(posY) || changes.contains(posZ) || changes.contains(mode)) { recreateShape(); timeSinceChange = 0; } } @Override public boolean shouldRenderInPass(int pass) { return pass == 1 && shouldRender(); } private void updateRedstone() { if (Config.guideRedstone != 0) { boolean redstoneState = worldObj.isBlockIndirectlyGettingPowered(xCoord, yCoord, zCoord); active.set(redstoneState); sync(); } } @Override public void onNeighbourChanged(Block block) { updateRedstone(); } @Override public void onAdded() { updateRedstone(); } protected CoordShape getShapeSafe() { if (shape == null) recreateShape(); return shape; } public boolean onItemUse(EntityPlayerMP player, ItemStack heldStack, int side, float hitX, float hitY, float hitZ) { Set<ColorMeta> colors = ColorUtils.stackToColor(heldStack); if (!colors.isEmpty()) { ColorMeta selected = CollectionUtils.getRandom(colors); color.set(selected.rgb); if (!worldObj.isRemote) sync(); return true; } return false; } }