Jump to content

Recommended Posts

Posted

Hi :) I created a block, which is supposed to have 255 different possible textures. That sounds like an awful lot, but most of these are just rotations. In fact it's 24 different ones (if I didn't miss any).

The problem I'm facing is the metadata of a block. Since it is only 4 bits, I can only have 16 different states, or am I missing something? If it's the case, is there an alternative, or simply a way to use perhaps a bit more :P?

 

If not, I also thought about a second class, one for, say, 12 states and one for the next 12, and somehow merge them.. But it's my second mod, and it already took me 2 days to get 2 different variants placed, based on where on the block you click :D (like slabs, which can be on the top or bottom half of a block).

 

Thanks in advance for all answers :)

Reygok

Posted

How common is your block?

 

How many .png combinations there are? (simplier, how mny .png you want to use for single block)?

1.7.10 is no longer supported by forge, you are on your own.

Posted

I dont know that much about setting textures in 1.8 via code, except of basic metadata with 16 variants, but one way might to save which texture you want to use in a tileEntity. Of course this only a good idea if your block isnt common, because TE's are quite expensive.

One thing you might do is create 12 Block with 12 states.

Maybe you can categorize your block a bit more, like chisel mod

Posted

I dont know that much about setting textures in 1.8 via code, except of basic metadata with 16 variants, but one way might to save which texture you want to use in a tileEntity. Of course this only a good idea if your block isnt common, because TE's are quite expensive.

One thing you might do is create 12 Block with 12 states.

Maybe you can categorize your block a bit more, like chisel mod

In 1.8 a good alternative is a non-ticking TileEntity (TEs are non-ticking by default). They only take up memory, no CPU power.

Don't PM me with questions. They will be ignored! Make a thread on the appropriate board for support.

 

1.12 -> 1.13 primer by williewillus.

 

1.7.10 and older versions of Minecraft are no longer supported due to it's age! Update to the latest version for support.

 

http://www.howoldisminecraft1710.today/

Posted

I was with you up until "this way using a TileEntity". I have no idea how TileEntities work, even after watching a tutorial and looking at the vanilla TileEntities.

Right now, the model used when the block is placed is decided based upon it's state. Which is set the moment it is placed, by the exact location I click. The problem occurs when the method "getMetaFromState" is called, because it can only return 16 different values, whereas I have 24 states. So, by using a TileEntity (somehow), I can bypass this problem, because what? Will the metadata then be ignored?

 

I created a new TileEntity class, and put it into my custom block class, but what now? Do I simply create a variable "state" inside, and set it in the "onBlockPlaced" method in the block? I'm a bit confused right now, I'm not that deep into forge yet :/

 

Thanks for any help!

Posted

I was with you up until "this way using a TileEntity". I have no idea how TileEntities work, even after watching a tutorial and looking at the vanilla TileEntities...So, by using a TileEntity (somehow), I can bypass this problem, because what? Will the metadata then be ignored?

 

A tile entity is basically a class that can provide some "intelligence" for a given block position in the world. Not every block can have such intelligence because it would take up too much memory, processing power and networking to make it work. But for blocks that are somewhat rarer it is possible to use them.

 

A block has metadata which is limited to the 4 bits. But the tile entity has NBT data which is essentially unlimited. So the idea is that you can easily store an int value in tile entity to cover your 255 texture states.

 

The metadata for the block still exists as well, so it is possible to use both if you want.

 

Another important point is that the block properties used in the block state is not actually limited to 4 bits, rather just the information you want to save/load is limited to 4 bits. So you can use your tile entity (which can store more information in its save/load) and then update the corresponding block states to match, which then can determine the model or texture or whatever of the block.

 

Tile entities also have the advantage that they can be "ticked" if you want them to be -- so they can process game logic every tick. But that will use up some CPU processing so you should only use if the block will be quite rare.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

Okay, that was already pretty helpful, seems to be what I need. The problem is, I don't know how to implement it correctly. I was following the tutorials of Grey Ghost http://greyminecraftcoder.blogspot.com.au/ But he uses IBakedModel, which is now deprecated, but from his description, it fits my case better :/

I'll take a closer look at his TileEntity version now :)

Posted

Okay, that was already pretty helpful, seems to be what I need. The problem is, I don't know how to implement it correctly.

 

There may be better ways to do it, but just think of the tile entity as something that stores data that you can use to bridge the other things you want to do. For example, you could create a couple block properties to indicate your 24 textures and various rotations but instead of saving those as metadata you would convert those to an integer that is stored in NBT of your tile entity. Then, if you need to change the texture during the game you would do that through the tile entity (i.e. you interact with the block and then change the value stored in the tile entity) and also update the block state. Then you would just take those block states and map them to textures in the JSON files, just like other 1.8 blocks.

 

In other words, the "official" value of the block state is maintained by the tile entity and you just make sure you sync the actual block state whenever you load game or interact in way that is supposed to change the state.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

Okay, I tried that, but appearantly the game still needs the method "getMetaFromState()", because I get this error:

java.lang.IllegalArgumentException: Don't know how to convert octablocks:tinyDirt[botfacing=north,botshape=zero,topfacing=north,topshape=zero] back into data...
at net.minecraft.block.Block.getMetaFromState(Block.java:272) ~[block.class:?]

 

So I tried to @Override this method, but what should I return? I tried like this:

@Override
public int getMetaFromState(IBlockState state) {
	return tileEntityTinyBlock.getShapeNr();
}

 

I initialized the "shapeNr" int in the tileEntityTinyBlock to 0, but then I get a NullpointerException in the "getMetaFromState()" method...

 

What's still missing?

Posted

You don't need to turn the values from your TE into metadata. They are already saved in the TE... And do you seriously have a "tileEntityTinyBlock" field in your Block? That's a bad idea and shows you do not understand how blocks work.

 

To expand on this.

 

Yes you do need a getMetaFromState() method but that doesn't have to do anything.

 

Regarding the tileEntityTinyBlock field, you need to understand an important concept -- a Block instance is a "singleton". There is only one instance of each block class in the whole game, no matter how many of those blocks are placed in the world. Instead of creating an instance for every block placed, the location and metadata is simply recorded in a 3d array of chunk data. However, a tile entity is not a singleton, but rather an instance is created each time it is placed. This is why you can't place a large number of tile entities as it will use up a lot of memory, processing power and networking (to sync client). But the good thing is you can do complicated coding that processes every tick in a tile entity.

 

So it doesn't make sense for the block to have a field for the tile entity instance as there will be hundreds (possibly) of the tile entity but only one block instance. So the field would only end up with the last tile entity assigned, not remembering each.

 

To put it one more way -- when you set the metadata of a block you're not actually setting data in a block instance but rather setting the metadata in the array of chunk data.

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted

@diesieben07 and @jabelar:

 

Yeah, I really didn't know there is only one instance of a block  :P I just followed basic tutorials, where this is never mentioned. So I thought when you place a block, it creates a new instance. Now that I think of it, this would be a whole ducking lot of data, and would make the game even laggier than it is haha :)

 

Ok, that already helped, understanding what one is doing often helps find out error-sources by yourself ;)

 

EDIT: Ok, I got rid of that error, but now I have another one I don't even understand:

[14:09:20] [server thread/FATAL] [FML]: Exception caught executing FutureTask: java.util.concurrent.ExecutionException: java.lang.NullPointerException
java.util.concurrent.ExecutionException: java.lang.NullPointerException
at java.util.concurrent.FutureTask.report(Unknown Source) ~[?:1.8.0_45]
at java.util.concurrent.FutureTask.get(Unknown Source) ~[?:1.8.0_45]
at net.minecraftforge.fml.common.FMLCommonHandler.callFuture(FMLCommonHandler.java:715) [FMLCommonHandler.class:?]
at net.minecraft.server.MinecraftServer.updateTimeLightAndEntities(MinecraftServer.java:727) [MinecraftServer.class:?]
at net.minecraft.server.MinecraftServer.tick(MinecraftServer.java:669) [MinecraftServer.class:?]
at net.minecraft.server.integrated.IntegratedServer.tick(IntegratedServer.java:171) [integratedServer.class:?]
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:540) [MinecraftServer.class:?]
at java.lang.Thread.run(Unknown Source) [?:1.8.0_45]
Caused by: java.lang.NullPointerException
at com.reygok.octablocks.blocks.TinyBlock.onBlockPlaced(TinyBlock.java:95) ~[TinyBlock.class:?]
at net.minecraft.item.ItemBlock.onItemUse(ItemBlock.java:73) ~[itemBlock.class:?]
at net.minecraftforge.common.ForgeHooks.onPlaceItemIntoWorld(ForgeHooks.java:570) ~[ForgeHooks.class:?]
at net.minecraft.item.ItemStack.onItemUse(ItemStack.java:146) ~[itemStack.class:?]
at net.minecraft.server.management.ItemInWorldManager.activateBlockOrUseItem(ItemInWorldManager.java:488) ~[itemInWorldManager.class:?]
at net.minecraft.network.NetHandlerPlayServer.processPlayerBlockPlacement(NetHandlerPlayServer.java:624) ~[NetHandlerPlayServer.class:?]
at net.minecraft.network.play.client.C08PacketPlayerBlockPlacement.processPacket(C08PacketPlayerBlockPlacement.java:67) ~[C08PacketPlayerBlockPlacement.class:?]
at net.minecraft.network.play.client.C08PacketPlayerBlockPlacement.processPacket(C08PacketPlayerBlockPlacement.java:114) ~[C08PacketPlayerBlockPlacement.class:?]
at net.minecraft.network.PacketThreadUtil$1.run(PacketThreadUtil.java:24) ~[PacketThreadUtil$1.class:?]
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) ~[?:1.8.0_45]
at java.util.concurrent.FutureTask.run(Unknown Source) ~[?:1.8.0_45]
at net.minecraftforge.fml.common.FMLCommonHandler.callFuture(FMLCommonHandler.java:714) ~[FMLCommonHandler.class:?]
... 5 more

 

 

I'll just post my classes now:

 

Main Class

package com.reygok.octablocks;

import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.entity.RenderItem;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.item.Item;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.fml.relauncher.Side;

import com.reygok.octablocks.blocks.TileEntityTinyBlock;
import com.reygok.octablocks.blocks.TinyDirt;

@Mod(modid = OctablocksMod.MODID, version = OctablocksMod.VERSION, name = OctablocksMod.NAME)
public class OctablocksMod {

public static final String MODID = "octablocks";
public static final String VERSION = "0.1";
public static final String NAME = "Octablocks";

public static Block tinyDirt;

@EventHandler
public void preInit(FMLPreInitializationEvent event)
{
	tinyDirt = new TinyDirt();
	GameRegistry.registerTileEntity(TileEntityTinyBlock.class, "tinyBlock_TE");
}

@EventHandler
public void init(FMLInitializationEvent event)
{
	MinecraftForge.EVENT_BUS.register(new BlockBreakEventHandler());

	if(event.getSide() == Side.CLIENT)
    	{
    		RenderItem renderItem = Minecraft.getMinecraft().getRenderItem();
    		
    		ModelResourceLocation tinyDirtLoc =
    				new ModelResourceLocation(MODID + ":" + ((TinyDirt) tinyDirt).getName(), "inventory");
    		    		
    		renderItem.getItemModelMesher().register(Item.getItemFromBlock(tinyDirt), 0, tinyDirtLoc);
    	}

}



}

 

TinyBlock

package com.reygok.octablocks.blocks;

import java.util.Collection;

import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.block.properties.IProperty;
import net.minecraft.block.properties.PropertyDirection;
import net.minecraft.block.properties.PropertyEnum;
import net.minecraft.block.state.BlockState;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.Item;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumFacing;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.registry.GameRegistry;

import com.google.common.collect.ImmutableMap;
import com.reygok.octablocks.EnumShape;

public class TinyBlock extends Block{

public static final PropertyEnum TOPSHAPE = PropertyEnum.create("topshape", EnumShape.class);
public static final PropertyEnum BOTSHAPE = PropertyEnum.create("botshape", EnumShape.class);
public static final PropertyDirection TOPFACING = PropertyDirection.create("topfacing", EnumFacing.Plane.HORIZONTAL);
public static final PropertyDirection BOTFACING = PropertyDirection.create("botfacing", EnumFacing.Plane.HORIZONTAL);


protected TinyBlock(Material materialIn) {
	super(materialIn);
}

@Override
public TileEntity createTileEntity(World worldIn, IBlockState state) {
	return new TileEntityTinyBlock();
}

public boolean isOpaqueCube()
{
	return false;
}

public boolean isFullCube()
{
	return false;
}

public int quantityDropped()
{
	return 1;
}

public Item getItemDropped(int par1, int par2)
{
	return Item.getItemFromBlock(this);
}

@Override
protected BlockState createBlockState() {
	return new BlockState(this, new IProperty[] {TOPSHAPE, TOPFACING, BOTSHAPE, BOTFACING});
}

        @Override
public int getMetaFromState(IBlockState state) {
	return 0;
}

@Override
public IBlockState onBlockPlaced(World worldIn, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer)
{
         TileEntity tileentity = worldIn.getTileEntity(pos);
         TileEntityTinyBlock tileEntityTinyBlock = null;
         if (tileentity instanceof TileEntityTinyBlock) { // prevent a crash if not the right type, or is null
    	        tileEntityTinyBlock = (TileEntityTinyBlock) tileentity;
        }
	IBlockState iblockstate = this.getDefaultState();
	if((double)hitX < 0.5D)
	{// WEST
		if((double)hitZ < 0.5D)
		{// NORTH
			if((double)hitY < 0.5D)
			{// BOTTOM
				tileEntityTinyBlock.setShapeNr(0);
				return iblockstate
						.withProperty(TOPSHAPE, EnumShape.ZERO)
						.withProperty(BOTSHAPE, EnumShape.ONE)
						.withProperty(TOPFACING, EnumFacing.NORTH)
						.withProperty(BOTFACING, EnumFacing.NORTH);				
			}
			else
			{// TOP
				tileEntityTinyBlock.setShapeNr(4);
				return iblockstate
						.withProperty(TOPSHAPE, EnumShape.ONE)
						.withProperty(BOTSHAPE, EnumShape.ZERO)
						.withProperty(TOPFACING, EnumFacing.NORTH)
						.withProperty(BOTFACING, EnumFacing.NORTH);
			}
		}
		else
		{// SOUTH
			if((double)hitY < 0.5D)
			{// BOTTOM
				tileEntityTinyBlock.setShapeNr(1);
				return iblockstate
						.withProperty(TOPSHAPE, EnumShape.ZERO)
						.withProperty(BOTSHAPE, EnumShape.ONE)
						.withProperty(TOPFACING, EnumFacing.SOUTH)
						.withProperty(BOTFACING, EnumFacing.SOUTH);				
			}
			else
			{// TOP
				tileEntityTinyBlock.setShapeNr(5);
				return iblockstate
						.withProperty(TOPSHAPE, EnumShape.ONE)
						.withProperty(BOTSHAPE, EnumShape.ZERO)
						.withProperty(TOPFACING, EnumFacing.SOUTH)
						.withProperty(BOTFACING, EnumFacing.SOUTH);
			}
		}
	}
	else
	{// EAST
		if((double)hitZ < 0.5D)
		{// NORTH
			if((double)hitY < 0.5D)  
			{// BOTTOM
				tileEntityTinyBlock.setShapeNr(2);
				return iblockstate
						.withProperty(TOPSHAPE, EnumShape.ZERO)
						.withProperty(BOTSHAPE, EnumShape.ONE)
						.withProperty(TOPFACING, EnumFacing.EAST)
						.withProperty(BOTFACING, EnumFacing.EAST);				
			}
			else
			{// TOP
				tileEntityTinyBlock.setShapeNr(6);
				return iblockstate
						.withProperty(TOPSHAPE, EnumShape.ONE)
						.withProperty(BOTSHAPE, EnumShape.ZERO)
						.withProperty(TOPFACING, EnumFacing.EAST)
						.withProperty(BOTFACING, EnumFacing.EAST);
			}
		}
		else
		{// SOUTH
			if((double)hitY < 0.5D)  
			{// BOTTOM
				tileEntityTinyBlock.setShapeNr(3);
				return iblockstate
						.withProperty(TOPSHAPE, EnumShape.ZERO)
						.withProperty(BOTSHAPE, EnumShape.ONE)
						.withProperty(TOPFACING, EnumFacing.WEST)
						.withProperty(BOTFACING, EnumFacing.WEST);				
			}
			else
			{// TOP
				tileEntityTinyBlock.setShapeNr(7);
				return iblockstate
						.withProperty(TOPSHAPE, EnumShape.ONE)
						.withProperty(BOTSHAPE, EnumShape.ZERO)
						.withProperty(TOPFACING, EnumFacing.WEST)
						.withProperty(BOTFACING, EnumFacing.WEST);
			}

		}
	}

}

}

 

And the Entity for the TinyBlock

package com.reygok.octablocks.blocks;

import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.Packet;
import net.minecraft.network.play.server.S35PacketUpdateTileEntity;
import net.minecraft.tileentity.TileEntity;

public class TileEntityTinyBlock extends TileEntity {

@Override
public Packet getDescriptionPacket() {
	NBTTagCompound nbtTagCompound = new NBTTagCompound();
	writeToNBT(nbtTagCompound);
	int metadata = getBlockMetadata();
	return new S35PacketUpdateTileEntity(this.pos, metadata, nbtTagCompound);
}

private int shapeNr = 0;

@Override
public void writeToNBT(NBTTagCompound parentNBTTagCompound)
{
	super.writeToNBT(parentNBTTagCompound); // The super call is required to save the tiles location
	parentNBTTagCompound.setInteger("shapeNr", shapeNr);
}

@Override
public void readFromNBT(NBTTagCompound parentNBTTagCompound)
{
	super.readFromNBT(parentNBTTagCompound); // The super call is required to load the tiles location
	shapeNr = parentNBTTagCompound.getInteger("shapeNr");
}

public int getShapeNr() {
	return shapeNr;
}

public void setShapeNr(int shapeNr) {
	this.shapeNr = shapeNr;
}
}

Posted

I'm no pro at modding, you certainly noticed that. So my answer is, because the state of my block is set the instant it is placed. And what state it will be in, is decided by where you click, like with vanilla stairs. For those, in the onBlockPlaced method, the state is decided with the help of the hitX, hitY and hitZ parameters.

But if I understand correctly, I cannot do it this way, since I'm using a TileEntity. Well then I have no idea how to do it, since my whole knowledge about modding is what you saw in my classes.

Posted

As I said you would have to use onBlockPlacedBy. But that does not give you the information about where exactly you clicked, so you need a custom ItemBlock to apply the data in your TE from there.

 

"As I said", well you said it in your head, because this method has not been mentioned in this thread. Perhaps you meant that you infered it by saying "when you query the world for the block state." But in that case you didn't understand what I meant by "I'm not a pro", because that means I never heard of that method, neither do I know what it does.

 

Additionally, I think my general approach is not efficient, since it will require nested if's with over 200 different cases. Perhaps the jump is just too big, my first and only mod is the addition of a new ore and tools. I'm leaping too far here, obviously.

Posted

Oh okay :) Well thank you, that's very kind, because I took a closer look at onItemUse and the ItemBlock in general, and it is just so much new information and methods I don't fully understand...

 

So, to my idea: I created a new block, TinyBlock, which will essentially be a smaller version (an 8th, to be exact) of most vanilla blocks. So there will be an extended class for each type, for now I only have TinyDirt, for testing purposes. You receive 8 of these TinyBlocks as a drop from a normal Dirt Block.

 

Now, the moment you place it down, it should be placed exactly where you place it, in the corner of an empty 1x1x1 block space; so there are 8 possible locations. If you place 4 on one block, in a square, you created a Slab of dirt. That's the idea.

 

My approach was to have the base class, TinyBlock, with 255 different states, because there are 256 different possibilities to place 8th blocks in the space of a normal sized block, minus "no block", so 255. Instead of having an enum with 255 cases, I divided the whole thing in a bottom and a top layer, and 4 possible facings for each layer. For one layer, there are now only 6 possible layouts, and each layer has a facing. Now there are a lot of redundant states, but I haven't figured out how to optimise it.

The problem with this approach is the size of all of this, the model file for each type of tiny block will have hundreds of lines, and the method to place the block and decide the state it will be in has the same amount of cases, depending on 1. where you click, so where the "new" tiny block will render, and 2. the shape of the TinyBlocks already there.

 

I hope you understand what I mean :)

Posted

Yeah, as I said before, I noticed it's really unefficient ^^ But I didn't know I would get that many states when I thought about how to do it.

 

Okay, yeah, I have a TileEntitiy, but I have no idea how to:

1. Set the shape of the block in the TileEntity, since this happens the moment the block is placed. So in which method do I do that?

2. "Transfer it to the renderer"?

3. Use IExtendedBlockstates, but perhaps I can find that one with The Grey Ghosts Tutorials (http://greyminecraftcoder.blogspot.com.au/)

 

Thanks :)

Posted

Okay, I created an ItemBlockTinyBlock class, and the setTileEntityNBT inside it to set the NBT data in the tileentity, and I think it works.

But for the rendering, I don't know how to "use getExtendedState", I did this:

 

	@Override
public IBlockState getExtendedState(IBlockState state, IBlockAccess world, BlockPos pos) {
	if (state instanceof IExtendedBlockState) {  // avoid crash in case of mismatch
		IExtendedBlockState stateExt = (IExtendedBlockState)state;
		TileEntityTinyBlock tileEntity = (TileEntityTinyBlock) world.getTileEntity(pos);
		System.out.println("Found tileentity, getting states");

		boolean bot_nw = tileEntity.isBot_nw();
		boolean bot_ne = tileEntity.isBot_ne();
		boolean bot_se = tileEntity.isBot_se();
		boolean bot_sw = tileEntity.isBot_sw();
		boolean top_nw = tileEntity.isTop_nw();
		boolean top_ne = tileEntity.isTop_ne();
		boolean top_se = tileEntity.isTop_se();
		boolean top_sw = tileEntity.isTop_sw();

		return stateExt
				.withProperty(BOT_NW, bot_nw).withProperty(BOT_NE, bot_ne).withProperty(BOT_SE, bot_se)	.withProperty(BOT_SW, bot_sw)
				.withProperty(TOP_NW, top_nw).withProperty(TOP_NE, top_ne).withProperty(TOP_SE, top_se)	.withProperty(TOP_SW, top_sw);
	}
	return state;
}

 

But the method is never called. I presume I now have to use onBlockPlacedBy in some way, but I don't know what to do in there, since the vanilla super method is emtpy :/

Posted

I'm sorry for double posting, but appearantly the thread got a bit lost.

I tried to follow TheGreyGhosts Example about SmartBlockModels, but I can't get it to work, perhaps because I don't understand the part with the quads at all..

https://github.com/TheGreyGhost/MinecraftByExample/tree/master/src/main/java/minecraftbyexample/mbe05_block_smartblockmodel2

 

Could it also be a problem that IBakedModel is deprecated?

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.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.