
Drakmyth
Members-
Posts
10 -
Joined
-
Last visited
Everything posted by Drakmyth
-
I know world.isRemote indicates whether the code is running on the logical server or the logical client. I further know that in most cases interacting with the world from the logical client causes desync/ghost entities/etc and so it is important to only interact with the world from the logical server. Finally, although I haven't learned much about GUIs yet, I know those are one of the primary places where logical client-specific code exists. What I'm wondering is if there is any guidance on what scenarios world.isRemote needs to be checked, or how to tell if any particular method should include that check? My current impression is that with the exception of registration code (which I'm doing through DeferredRegister and thus is registered on each side according to how Forge knows how to do it) there generally should not be any logic that should run on both sides and so world.isRemote should be checked in pretty much every method. Further, unless there is logic that is specifically known that it should only be executed client-side, generally all logic should be logical server-side only. Obviously this may differ wildly depending on the particular mods goals, but as a general-case implementing vanilla-like items and blocks mod goes that seems to be the case. For instance: I believe I should check (!isRemote) in Item#onItemUse, but not in Block#createTileEntity (because tile entities *should* be created in the client?) but what about in Block#getShape or TileEntity#tick? If there's not a general rule-of-thumb (such as "never execute on the client unless you know you have to"), what kinds of properties or scenarios should I be looking for to understand when I do need to check? How do I tell which sides a particular method will even be executed on?
-
I am creating a conveyor belt block which uses a tile entity to hold the current item on a specific conveyor at any given time. I render the sides of the conveyor using json, but then render the belt and the item on the belt using a TESR. Everything works fine until I place 64 conveyors in a single chunk. Upon placing the 64th, the TESR breaks, the tile entity stops ticking, and the number of chunk updates skyrockets (from around 0-1 to 160-170). Conveyors in other chunks continue to operate perfectly fine (unless I reach 64+ in that chunk, in which case the same thing happens within that chunk). I haven't been able to determine what limit I'm hitting that causes this to happen. It happening only within a single chunk suggests to me that it maybe has something to do with marking the chunk as dirty, or putting too much info in the update packet. I'm not sure. Edit: I've added a gif showing the issue: [spoiler=Example] My mod is open source at https://github.com/Drakmyth/Manufactory. Relevant files are included below. [spoiler=BlockConveyor.java] public class BlockConveyor extends Block implements ITileEntityProvider { private static final AxisAlignedBB AABB = new AxisAlignedBB(0.0f, 0.0f, 0.0f, 1.0f, 0.5f, 1.0f); /** * BlockState Properties */ private static final PropertyDirection FACING = PropertyDirection.create("facing", EnumFacing.Plane.HORIZONTAL); /** * Actual State Properties */ private static final PropertyBool EAST_WALL = PropertyBool.create("east_wall"); private static final PropertyBool WEST_WALL = PropertyBool.create("west_wall"); private final Item _itemBlock; public BlockConveyor() { super(Material.ROCK, MapColor.BLACK); this.setRegistryName(Constants.BlockNames.CONVEYOR_BELT); this.setUnlocalizedName(Constants.MODID + "." + Constants.BlockNames.CONVEYOR_BELT); this.setCreativeTab(CreativeTabs.MISC); this.setDefaultState(this.getBlockState().getBaseState() .withProperty(FACING, EnumFacing.NORTH) .withProperty(EAST_WALL, true) .withProperty(WEST_WALL, true)); _itemBlock = new ItemBlock(this).setRegistryName(this.getRegistryName()); } public Item getItemBlock() { return _itemBlock; } @Override @Nonnull public AxisAlignedBB getBoundingBox(final IBlockState state, final IBlockAccess source, final BlockPos pos) { return AABB; } /** * Returns a new instance of a block's tile entity class. Called on placing the block. */ @Override public TileEntity createNewTileEntity(@Nonnull final World world, final int meta) { return new TileEntityConveyor(getStateFromMeta(meta).getValue(FACING)); } /** * Called when the block is right clicked by a player. */ @Override public boolean onBlockActivated(final World world, final BlockPos position, final IBlockState blockState, final EntityPlayer player, final EnumHand hand, EnumFacing facing, final float hitX, final float hitY, final float hitZ) { if (world.isRemote) { return true; } final TileEntity entity = world.getTileEntity(position); if (!(entity instanceof TileEntityConveyor)) { return true; } final TileEntityConveyor tileEntity = (TileEntityConveyor)entity; final ItemStack itemOnConveyor = tileEntity.getItemOnConveyor(); if (player.inventory.addItemStackToInventory(itemOnConveyor)) { player.inventoryContainer.detectAndSendChanges(); tileEntity.clearItem(); } return true; } /** * Used to determine ambient occlusion and culling when rebuilding chunks for render */ @Override public boolean isOpaqueCube(IBlockState state) { return false; } @Override public boolean isFullCube(IBlockState state) { return false; } /** * The type of render function called. MODEL for mixed tesr and static model, MODELBLOCK_ANIMATED for TESR-only, * LIQUID for vanilla liquids, INVISIBLE to skip all rendering */ @Override @Nonnull public EnumBlockRenderType getRenderType(IBlockState state) { return EnumBlockRenderType.MODEL; } /** * Called by ItemBlocks just before a block is actually set in the world, to allow for adjustments to the * IBlockstate */ @Override @Nonnull public IBlockState getStateForPlacement(final World world, final BlockPos position, final EnumFacing blockSideClicked, final float hitX, final float hitY, final float hitZ, final int meta, final EntityLivingBase placer) { EnumFacing placerFacing = placer.getHorizontalFacing(); if (placerFacing.equals(blockSideClicked.getOpposite())) { IBlockState targetState = world.getBlockState(position.offset(placerFacing)); boolean isConveyor = targetState.getBlock() instanceof BlockConveyor; if (isConveyor) { EnumFacing targetStateFacing = targetState.getValue(FACING); if (placerFacing.equals(targetStateFacing.getOpposite())) { placerFacing = targetStateFacing; } } } return this.getDefaultState().withProperty(FACING, placerFacing); } @Override @Nonnull protected BlockStateContainer createBlockState() { return new BlockStateContainer(this, FACING, EAST_WALL, WEST_WALL); } /** * Convert the given metadata into a BlockState for this Block */ @Override @Nonnull public IBlockState getStateFromMeta(final int meta) { final EnumFacing facing = EnumFacing.getHorizontal(meta); return this.getDefaultState().withProperty(FACING, facing); } /** * Convert the BlockState into the correct metadata value */ @Override public int getMetaFromState(final IBlockState state) { final EnumFacing facing = state.getValue(FACING); return facing.getHorizontalIndex(); } /** * Get the actual Block state of this Block at the given position. This applies properties not visible in the * metadata, such as fence connections. */ @Override @Nonnull public IBlockState getActualState(@Nonnull final IBlockState state, final IBlockAccess world, final BlockPos position) { final EnumFacing facing = state.getValue(FACING); boolean eastWall = !isFacingTargetLocation(world, position.offset(facing.rotateY()), position); boolean westWall = !isFacingTargetLocation(world, position.offset(facing.rotateYCCW()), position); return state.withProperty(EAST_WALL, eastWall) .withProperty(WEST_WALL, westWall); } private boolean isFacingTargetLocation(final IBlockAccess world, final BlockPos position, final BlockPos target) { IBlockState state = world.getBlockState(position); boolean isConveyor = state.getBlock() instanceof BlockConveyor; return isConveyor && position.offset(state.getValue(FACING)).equals(target); } /** * Called serverside after this block is replaced with another in Chunk, but before the Tile Entity is updated */ @Override public void breakBlock(@Nonnull final World world, @Nonnull final BlockPos position, @Nonnull final IBlockState state) { final TileEntity entity = world.getTileEntity(position); if (!(entity instanceof TileEntityConveyor)) { return; } final TileEntityConveyor tileEntity = (TileEntityConveyor)entity; final ItemStack itemOnConveyor = tileEntity.getItemOnConveyor(); if (!itemOnConveyor.isEmpty()) { final EntityItem item = new EntityItem(world, position.getX() + 0.5, position.getY() + 0.5, position.getZ() + 0.5, itemOnConveyor); final float multiplier = 0.1f; final float motionX = world.rand.nextFloat() - 0.5f; final float motionY = world.rand.nextFloat() - 0.5f; final float motionZ = world.rand.nextFloat() - 0.5f; item.motionX = motionX * multiplier; item.motionY = motionY * multiplier; item.motionZ = motionZ * multiplier; world.spawnEntity(item); } super.breakBlock(world, position, state); } } [spoiler=TileEntityConveyor.java] public class TileEntityConveyor extends TileEntity implements ITickable, IConveyorReceiver { private static final String NBT_ITEM_ON_CONVEYOR = "itemOnConveyor"; private static final String NBT_IS_HEAD = "isHead"; private static final String NBT_BELT_OFFSET = "beltOffset"; private static final String NBT_ITEM_OFFSET = "itemOffset"; private static final String NBT_ITEM_RELEASE_OFFSET = "itemReleaseOffset"; private static final String NBT_FACING = "facing"; private static final String NBT_LAST_ITEM_SOURCE_FACING = "lastItemSourceFacing"; private static final int MAX_BELT_OFFSET = 16; // 16/x = number of pixels to rotate per tick private static final int MAX_ITEM_HOLD_DURATION = MAX_BELT_OFFSET; private boolean _isHead; private ItemStack _itemOnConveyor; private EnumFacing _facing; private int _beltOffset; private int _itemOffset; private int _itemReleaseOffset; private boolean _justReceivedItem; private Vec3i _lastItemSourceFacing; @SuppressWarnings("unused") public TileEntityConveyor() { this(EnumFacing.NORTH); } public TileEntityConveyor(final EnumFacing facing) { _isHead = true; _itemOnConveyor = ItemStack.EMPTY; _facing = facing; _beltOffset = 0; _itemOffset = 0; _itemReleaseOffset = 0; _justReceivedItem = false; _lastItemSourceFacing = facing.getDirectionVec(); } @Override public SPacketUpdateTileEntity getUpdatePacket() { final NBTTagCompound compound = new NBTTagCompound(); writeToNBT(compound); return new SPacketUpdateTileEntity(pos, getBlockMetadata(), compound); } @Override public void onDataPacket(final NetworkManager networkManager, final SPacketUpdateTileEntity packet) { readFromNBT(packet.getNbtCompound()); } @Override @Nonnull public NBTTagCompound writeToNBT(final NBTTagCompound compound) { super.writeToNBT(compound); final NBTTagCompound itemOnConveyorNBT = new NBTTagCompound(); _itemOnConveyor.writeToNBT(itemOnConveyorNBT); compound.setTag(NBT_ITEM_ON_CONVEYOR, itemOnConveyorNBT); compound.setBoolean(NBT_IS_HEAD, _isHead); compound.setInteger(NBT_BELT_OFFSET, _beltOffset); compound.setInteger(NBT_ITEM_OFFSET, _itemOffset); compound.setInteger(NBT_ITEM_RELEASE_OFFSET, _itemReleaseOffset); compound.setInteger(NBT_FACING, _facing.getHorizontalIndex()); compound.setIntArray(NBT_LAST_ITEM_SOURCE_FACING, new int[] { _lastItemSourceFacing.getX(), _lastItemSourceFacing.getY(), _lastItemSourceFacing.getZ() }); return compound; } @Override public void readFromNBT(final NBTTagCompound compound) { super.readFromNBT(compound); _itemOnConveyor = ItemStack.EMPTY; if (compound.hasKey(NBT_ITEM_ON_CONVEYOR)) { final NBTTagCompound itemOnConveyorNBT = compound.getCompoundTag(NBT_ITEM_ON_CONVEYOR); _itemOnConveyor = new ItemStack(itemOnConveyorNBT); } _isHead = true; if (compound.hasKey(NBT_IS_HEAD)) { _isHead = compound.getBoolean(NBT_IS_HEAD); } _beltOffset = 0; if (compound.hasKey(NBT_BELT_OFFSET)) { _beltOffset = compound.getInteger(NBT_BELT_OFFSET); } _itemOffset = 0; if (compound.hasKey(NBT_ITEM_OFFSET)) { _itemOffset = compound.getInteger(NBT_ITEM_OFFSET); } _itemReleaseOffset = 0; if (compound.hasKey(NBT_ITEM_RELEASE_OFFSET)) { _itemReleaseOffset = compound.getInteger(NBT_ITEM_RELEASE_OFFSET); } _facing = EnumFacing.NORTH; if (compound.hasKey(NBT_FACING)) { _facing = EnumFacing.getHorizontal(compound.getInteger(NBT_FACING)); } // TODO: Should be able to sync this to the client without using NBT _lastItemSourceFacing = _facing.getDirectionVec(); if (compound.hasKey(NBT_LAST_ITEM_SOURCE_FACING)) { final int[] facingVals = compound.getIntArray(NBT_LAST_ITEM_SOURCE_FACING); _lastItemSourceFacing = new Vec3i(facingVals[0], facingVals[1], facingVals[2]); } _justReceivedItem = false; } @Override public void update() { if (!hasWorld() || getWorld().isRemote) { return; } // TODO: this check only needs to happen onNeighborBlockChanged, not every tick _isHead = amIHead(); if (_isHead) { updateConveyorHead(); } } private boolean amIHead() { final TileEntity destination = getWorld().getTileEntity(pos.offset(_facing)); if (destination == null || !(destination instanceof TileEntityConveyor)) { return true; } final TileEntityConveyor tileEntity = (TileEntityConveyor)destination; return tileEntity.getFacing() != _facing; } private void updateConveyorHead() { if (!_justReceivedItem && canGiveItemToDestination()) { final TileEntity destination = getWorld().getTileEntity(pos.offset(_facing)); if (destination instanceof IConveyorReceiver) { if (!giveItemToDestination()) { return; } } else { ejectItem(); } } _justReceivedItem = false; updateConveyorChild((_beltOffset + 1) % MAX_BELT_OFFSET, (_itemOffset + 1) % MAX_ITEM_HOLD_DURATION); final Queue<TileEntityConveyor> children = getChildren(); children.forEach(conveyor -> conveyor.updateConveyorChild(_beltOffset, _itemOffset)); } private Queue<TileEntityConveyor> getChildren() { final Queue<TileEntityConveyor> children = new ArrayDeque<>(); final World world = getWorld(); final EnumFacing backFacing = _facing.getOpposite(); BlockPos nextPos = pos.offset(backFacing); while (true) { final TileEntity tileEntity = world.getTileEntity(nextPos); if (tileEntity == null || !(tileEntity instanceof TileEntityConveyor)) { break; } final TileEntityConveyor child = (TileEntityConveyor)tileEntity; if (child.getFacing() != _facing) { break; } children.add(child); nextPos = nextPos.offset(backFacing); } return children; } private void updateConveyorChild(final int currentBeltOffset, final int currentItemOffset) { if (_beltOffset == currentBeltOffset) { return; } _beltOffset = currentBeltOffset; _itemOffset = currentItemOffset; if (canGiveItemToDestination()) { giveItemToDestination(); } if (canGrabItemFromAbove()) { grabItemFromAbove(); } markDirty(); World world = getWorld(); IBlockState blockState = world.getBlockState(pos); world.notifyBlockUpdate(pos, blockState, blockState, 0); } private boolean canGiveItemToDestination() { return !_itemOnConveyor.isEmpty() && _itemOffset == _itemReleaseOffset; } private boolean giveItemToDestination() { boolean gaveItemToDestination = false; final TileEntity potentialDestination = getWorld().getTileEntity(pos.offset(_facing)); if (potentialDestination instanceof IConveyorReceiver) { final IConveyorReceiver destination = (IConveyorReceiver)potentialDestination; gaveItemToDestination = destination.onReceiveItem(_itemOnConveyor, pos); } if (gaveItemToDestination) { _itemOnConveyor = ItemStack.EMPTY; } return gaveItemToDestination; } // TODO: Can this be replaced with a simple onReceiveItem call? private boolean canGrabItemFromAbove() { if (!_itemOnConveyor.isEmpty()) { return false; } final boolean okInFront; if (_isHead) { okInFront = true; } else { final TileEntity tileEntity = getWorld().getTileEntity(pos.offset(_facing)); if (tileEntity instanceof TileEntityConveyor) { final TileEntityConveyor conveyor = (TileEntityConveyor)tileEntity; final int timeBeforeItemRelease = conveyor.getTimeBeforeItemRelease(); okInFront = timeBeforeItemRelease < MAX_ITEM_HOLD_DURATION / 2 || timeBeforeItemRelease == Integer.MAX_VALUE; } else { okInFront = true; } } final boolean okBehind; final TileEntity tileEntity = getWorld().getTileEntity(pos.offset(_facing.getOpposite())); if (tileEntity instanceof TileEntityConveyor) { final TileEntityConveyor conveyor = (TileEntityConveyor)tileEntity; okBehind = conveyor.getFacing() != _facing || conveyor.getTimeBeforeItemRelease() > MAX_ITEM_HOLD_DURATION / 2; } else { okBehind = true; } return okInFront && okBehind; } private void grabItemFromAbove() { final AxisAlignedBB aabb = new AxisAlignedBB(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1.0D, pos.getY() + 1.0D, pos.getZ() + 1.0D); final List<Entity> entitiesAboveConveyor = getWorld().getEntitiesWithinAABB(EntityItem.class, aabb); for (final Entity entity : entitiesAboveConveyor) { final EntityItem item = (EntityItem)entity; if (!item.isAirBorne) { // TODO: This should probably call onReceiveItem to check for rejection instead of setting _itemOnConveyor directly _itemOnConveyor = item.getEntityItem().splitStack(1); if (item.getEntityItem().getCount() < 1) { item.isDead = true; } _itemReleaseOffset = calculateItemReleaseOffset(MAX_ITEM_HOLD_DURATION / 2); break; } } } public void clearItem() { _itemOnConveyor = ItemStack.EMPTY; markDirty(); IBlockState state = getWorld().getBlockState(pos); getWorld().notifyBlockUpdate(pos, state, state, 0); } public EnumFacing getFacing() { return _facing; } private int getTimeBeforeItemRelease() { if (_itemOnConveyor.isEmpty()) { return Integer.MAX_VALUE; } if (_itemOffset < _itemReleaseOffset) { return _itemReleaseOffset - _itemOffset; } return (_itemReleaseOffset + MAX_ITEM_HOLD_DURATION) - _itemOffset; } public float getBeltTextureOffset() { return _beltOffset / (float)MAX_BELT_OFFSET; } public float getItemOffset() { return 1.0f - (getTimeBeforeItemRelease() / (float)MAX_ITEM_HOLD_DURATION); } private void ejectItem() { final Vec3i facing = _facing.getDirectionVec(); final double spawnX = pos.getX() + 0.5 + (facing.getX() * 0.7); final double spawnY = pos.getY() + (5.0 / 16); final double spawnZ = pos.getZ() + 0.5 + (facing.getZ() * 0.7); final EntityItem item = new EntityItem(getWorld(), spawnX, spawnY, spawnZ, _itemOnConveyor); final double velocity = 0.1; item.motionX = facing.getX() * velocity; item.motionY = facing.getY() * velocity; item.motionZ = facing.getZ() * velocity; getWorld().spawnEntity(item); _itemOnConveyor = ItemStack.EMPTY; } public ItemStack getItemOnConveyor() { return _itemOnConveyor; } @Override public boolean onReceiveItem(final ItemStack itemStack, final BlockPos sourceDir) { boolean reject = false; final BlockPos behindMe = pos.offset(_facing.getOpposite()); if (!_itemOnConveyor.isEmpty() || sourceDir.equals(pos.offset(_facing))) { reject = true; } else if (!sourceDir.equals(behindMe)) { final TileEntity tileEntity = getWorld().getTileEntity(behindMe); if (tileEntity instanceof TileEntityConveyor) { final TileEntityConveyor conveyor = (TileEntityConveyor)tileEntity; reject = conveyor.getFacing() == _facing && conveyor.getTimeBeforeItemRelease() < MAX_ITEM_HOLD_DURATION; } } if (!reject) { _itemOnConveyor = itemStack; _itemReleaseOffset = calculateItemReleaseOffset(MAX_ITEM_HOLD_DURATION); if (_isHead) { _justReceivedItem = true; } _lastItemSourceFacing = pos.subtract(sourceDir); markDirty(); IBlockState state = getWorld().getBlockState(pos); getWorld().notifyBlockUpdate(pos, state, state, 0); } return !reject; } public Vec3i getLastItemSourceFacingVector() { return _lastItemSourceFacing; } private int calculateItemReleaseOffset(final int duration) { return (_itemOffset + duration) % MAX_ITEM_HOLD_DURATION; } } [spoiler=TileEntityConveyorRenderer.java] public class TileEntityConveyorRenderer extends TileEntitySpecialRenderer<TileEntityConveyor> { private static final ResourceLocation beltTexture = new ResourceLocation("manufactory:textures/blocks/conveyor_belt.png"); private final RenderEntityItem _itemRender; public TileEntityConveyorRenderer() { _itemRender = new RenderEntityItem(Minecraft.getMinecraft().getRenderManager(), Minecraft.getMinecraft().getRenderItem()) { @Override public boolean shouldBob() { return false; } }; } @Override public void renderTileEntityAt(final TileEntityConveyor tileEntity, final double x, final double y, final double z, final float partialTicks, final int destroyStage) { final ItemStack itemOnConveyor = tileEntity.getItemOnConveyor(); GlStateManager.pushMatrix(); GlStateManager.translate(x, y, z); drawConveyorBelt(tileEntity); if (itemOnConveyor != null) { drawItemOnConveyor(tileEntity, itemOnConveyor); } GlStateManager.popMatrix(); } private void drawItemOnConveyor(final TileEntityConveyor tileEntity, final ItemStack itemOnConveyor) { GlStateManager.pushMatrix(); final EntityItem customItem = new EntityItem(tileEntity.getWorld()); customItem.hoverStart = 0.0f; customItem.setEntityItemStack(itemOnConveyor); final Vec3i sourceDir = tileEntity.getLastItemSourceFacingVector(); final Vec3i facing = tileEntity.getFacing().getDirectionVec(); final float progressOffset = tileEntity.getItemOffset() - 0.5f; GlStateManager.translate(0.5f, 0.2f, 0.5f); piecewiseTranslateOnSourceDir(sourceDir, facing, progressOffset); _itemRender.doRender(customItem, 0, 0, 0, 0, 0); GlStateManager.scale(0.7f, 0.7f, 0.7f); GlStateManager.popMatrix(); } private void piecewiseTranslateOnSourceDir(final Vec3i sourceDir, final Vec3i facing, final float progressOffset) { final Vec3i dirToUse = progressOffset >= 0 ? facing : sourceDir; GlStateManager.translate(dirToUse.getX() * progressOffset, 0, dirToUse.getZ() * progressOffset); } private void drawConveyorBelt(final TileEntityConveyor tileEntity) { final Tessellator tessellator = Tessellator.getInstance(); final VertexBuffer worldRenderer = tessellator.getBuffer(); final float beltOffset = tileEntity.getBeltTextureOffset(); this.bindTexture(beltTexture); GlStateManager.pushMatrix(); GlStateManager.disableLighting(); GlStateManager.disableBlend(); GlStateManager.enableDepth(); GlStateManager.translate(0.5f, 0.0f, 0.5f); worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX); addBeltVertices(worldRenderer, beltOffset); final float angle; switch (tileEntity.getFacing()) { case WEST: angle = 90; break; case SOUTH: angle = 180; break; case EAST: angle = 270; break; default: angle = 0; break; } GlStateManager.rotate(angle, 0, 1, 0); tessellator.draw(); GlStateManager.popMatrix(); } private void addBeltVertices(final VertexBuffer worldrenderer, final float beltOffset) { final double[][] vertexTable = { { -7.0 / 16, 5.005 / 16, -8.0 / 16, 0.0, 0.0 + beltOffset }, //1 { -7.0 / 16, 5.005 / 16, 8.0 / 16, 0.0, 1.0 + beltOffset }, { 7.0 / 16, 5.005 / 16, 8.0 / 16, 1.0, 1.0 + beltOffset }, { 7.0 / 16, 5.005 / 16, -8.0 / 16, 1.0, 0.0 + beltOffset }, { -7.0 / 16, 5.0 / 16, 8.005 / 16, 0.0, 0.0 + beltOffset }, //2 { -7.0 / 16, 0.0 / 16, 8.005 / 16, 0.0, (5.0 / 16) + beltOffset }, { 7.0 / 16, 0.0 / 16, 8.005 / 16, 1.0, (5.0 / 16) + beltOffset }, { 7.0 / 16, 5.0 / 16, 8.005 / 16, 1.0, 0.0 + beltOffset }, { -7.0 / 16, 5.0 / 16, -8.005 / 16, 0.0, (4.0 / 16) + beltOffset }, //3 { 7.0 / 16, 5.0 / 16, -8.005 / 16, 1.0, (4.0 / 16) + beltOffset }, { 7.0 / 16, 0.0 / 16, -8.005 / 16, 1.0, (-1.0 / 16) + beltOffset }, { -7.0 / 16, 0.0 / 16, -8.005 / 16, 0.0, (-1.0 / 16) + beltOffset } }; for (final double[] vertex : vertexTable) { worldrenderer.pos(vertex[0], vertex[1], vertex[2]).tex(vertex[3], vertex[4]).endVertex(); } } } Note: This code was originally written in 1.8 and was recently ported to 1.11. I mention this because this is my first mod, and so if there are new ways of doing things or things are not done in a correct way, it is because I am still learning. (Most notably I believe my IConveyorReceiver should likely be replaced by a Capability, but I haven't learned that yet). Any critiques or suggestions to any part of the codebase beyond just the problem I'm asking about would be greatly appreciated as well. This is my first minecraft mod, but I am a software engineer by trade and am very familiar with Java.
-
My guess is you're not passing in the right model path. I would expect the obj file to be in the "models\block" or "models\item" folders.
-
Thanks so much for the info TGG. It at least gives me a starting point to dive a little deeper. I just had no clue where to even begin before. I'll see if I can walk through the baking stuff and see what I find.
-
Been away for a few days but I could still use help with this. Ideally, I like to avoid having to implement a custom blockstate file parser to pick those rotation values back up. Also, I looked at performing the rotation manually on the vertex data inside the AssembledBakedModel.getFaceQuads and AssembledBakedModel.getGeneralQuads methods, but I can't tell what format the vertex data is in. The values are huge float numbers, not the 0-1 or 0-16 I would expect.
-
I converted my conveyors from using the vanilla block rendering to using an ISmartBlockModel to change the block by loading JSON sub-models depending on its neighbors. That logic works perfectly. The problem I'm running into is that the blockstates file defines the rotation depending on facing, and it is no longer respected. I assume this is because I'm using a custom model loader, but I'm having trouble figuring out how to either use the vanilla loader to load my ISBM (which I don't think is possible) or getting my loader to respect the rotation. I developed my ISBM implementation following MBE05 though I admit I'm having a bit of a hard time following the logic through the different model classes. I'm not sure what's actually necessary and what's just abstraction for the sake of the tutorial. Relevant files below. I'm not sure I like the idea of implementing a custom model loader for smart models that explicitly checks for each model, so if there is a better or more generic way of doing this already built into Forge please enlighten me! blockstates/conveyor.json ClientProxy.java SmartModelLoader.java ConveyorModel.java CompositeModel.java AssembledBakedModel.java Thanks for the help!
-
[Request] Documentation for all Forge-provided 1.8 model features
Drakmyth replied to williewillus's topic in Modder Support
Sounds like a very good idea to me. Reading threads and trying things I have been able to get a handle on many things around Forge modding, but the rendering pipeline and custom network packets are two things that always prove elusive. I figured out simple blocks and the basic json model format, and TESR's are pretty simple, but as soon as you move beyond that I've had a hard time keeping track of things, and while the MinecraftByExample samples are certainly helpful, they aren't always easy to follow. The only problem I see with using the wiki for this is that the vast majority of the information in there is very out of date (or seemingly is anyway). Any time I've ventured in there to figure out how to do something, I've only been able to find examples from Minecraft 1.4 or 1.5, or just links back into these forums (and occasionally to broken links) and thus quickly end up back on the forums reading through threads. Keeping information up to date is definitely the difficult part of such an endeavor, and while a forum might not be the best format for that, I question if the wiki in its current form is very much better. -
That... is a good idea that didn't occur to me. It seems unfortunate that I need to store that value in the NBT data since while the block is there I can just look it up and when the block is gone it's not useful anymore, but it's a much nicer solution than having the renderer check the block state directly.
-
Hello, Long time lurker, first time poster. Before I get into my problem I just wanted to extend a long belated thanks to the members of this community. The posts on this forum have helped me immensely getting me to where I am now. Anyway, to my issue, I have created a Conveyor block that uses standard block rendering for the actual block itself, but then uses a TESR to render the items on the conveyor as well as animating the belt. I am now running into an issue where I set up two conveyors pointing at each other. If I put an item on one, then break that block, the game will crash with the following error on the client side: http://pastebin.com/p5M8X48i I have tracked this down to my TESR trying to look up a property from the BlockState but not being able to find it. What I'm not quite sure about though, is why it can't find it. Relevant code below: BlockConveyor.java TileEntityConveyor.java TileEntityConveyorRenderer.java The crash occurs in TileEntityConveyorRenderer, in both the drawConveyorBelt and drawItemOnConveyor methods. When tileEntity.getFacing() is called, the following line in TileEntityConveyor.getFacing fails: return (EnumFacing)getWorld().getBlockState(pos).getValue(Constants.BlockStateProperties.FACING_HORIZONTAL); because the requested property is not on the BlockState at that position. I can only assume this occurs because the block has already been destroyed but the tile entity (and the accompanying renderer) have not yet. What is the best way to deal with this? Should I put a check at the beginning of the render method to ensure the block is still there and is the right type? Or is there a way to ensure the tile entity (and renderer) gets destroyed before the block does? Thanks in advance!