Drakmyth Posted December 19, 2016 Posted December 19, 2016 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. Quote
Drakmyth Posted January 9, 2017 Author Posted January 9, 2017 I re-wrote my conveyors using capabilities. After the re-write, this issue no longer occurs. Don't know what it was. Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.