/** Runes of Wizardry Mod for Minecraft
* Licensed under the GNU GPL version 3
*
* this file was created by Xilef11 on 2015-08-18
*/
package com.zpig333.runesofwizardry.tileentity;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.play.server.SPacketUpdateTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextComponentTranslation;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import com.zpig333.runesofwizardry.api.DustRegistry;
import com.zpig333.runesofwizardry.api.IDust;
import com.zpig333.runesofwizardry.api.RuneEntity;
import com.zpig333.runesofwizardry.core.References;
import com.zpig333.runesofwizardry.core.WizardryLogger;
import com.zpig333.runesofwizardry.core.WizardryRegistry;
/**The TileEntity that holds placed dust
* @author Xilef11
*
*/
public class TileEntityDustPlaced extends TileEntity implements IInventory{
public static final int ROWS=4, COLS=4;
//the dusts placed in this block
protected ItemStack[][] contents = new ItemStack[ROWS][COLS];
protected RuneEntity rune=null;
//the colors for rendering the center of the dusts
private int[][] centralColors;
//the internal connector data
private Set<int[]> internalConnectors;
//external connector data
private List<int[]> externalConnectors;
/* return the coordinates of a slot based on its id
* NORTH (Z-)
* [0][1][2][3]
* [4][5][6][7] EAST (X+)
* [8][9][10][11]
* [12][13][14][15]
*/
public static int[] getPositionFromSlotID(int id){
int row = id / ROWS;
int col = id % COLS;
return new int[]{row,col};
}
//the other way around
public static int getSlotIDfromPosition(int row, int col){
return row * ROWS + col;
}
public TileEntityDustPlaced() {
super();
for(int i=0;i<contents.length;i++){
Arrays.fill(contents[i],ItemStack.EMPTY);
}
}
/**
* Returns the Contents of this TE (not a copy)
* @return
*/
public ItemStack[][] getContents(){
return contents;
}
/**sets the contents of this TE
*
* @throws IllegalArgumentException if the given ItemStack[][] is not of the right size
*/
public void setContents(ItemStack[][] stacks){
if(stacks.length!=ROWS || stacks[0].length!=COLS)throw new IllegalArgumentException("Contents must be "+ROWS+" by "+COLS );
this.contents=stacks;
}
/** returns the rune associated with this dust**/
public RuneEntity getRune(){
return rune;
}
public void setRune(RuneEntity rune){
this.rune=rune;
}
/**returns the color of all center points**/
public int[][] getCenterColors(){
if(centralColors==null)updateCenterColors();
return centralColors;
}
//updates the array of central colors
protected void updateCenterColors(){
int[][]result = new int[ROWS][COLS];
for(int i=0;i<result.length;i++){
for(int j=0;j<result[i].length;j++){
//FIXME must not have properly converted the stack somewhere...
//if(contents[i][j]!.isEmpty()){
if(!contents[i][j].isEmpty()){
result[i][j]=DustRegistry.getDustFromItemStack(contents[i][j]).getPlacedColor(contents[i][j]);
}else{
result[i][j]=-1;
}
}
}
centralColors=result;
}
/**returns the data on the internal connectors to draw
*
* @return a set of arrays with the following structure: [row1, col1, row2, col2, color1, color2]
*/
public Set<int[]> getInternalConnectors(){
if (internalConnectors==null) updateInternalConnectors();
return internalConnectors;
}
//update the data for the internal connectors
protected void updateInternalConnectors(){
HashSet<int[]> result = new HashSet<int[]>();
for(int i=0;i<contents.length;i++){
for(int j=0;j<contents[i].length;j++){
if(i+1<contents.length && dustsMatch(contents[i][j],contents[i+1][j])){
int color1 = DustRegistry.getDustFromItemStack(contents[i][j]).getPlacedColor(contents[i][j]);
int color2 = DustRegistry.getDustFromItemStack(contents[i+1][j]).getPlacedColor(contents[i+1][j]);
result.add(new int[]{i,j,i+1,j, color1,color2});
}if(j+1<contents[i].length && dustsMatch(contents[i][j],contents[i][j+1])){
int color1 = DustRegistry.getDustFromItemStack(contents[i][j]).getPlacedColor(contents[i][j]);
int color2 = DustRegistry.getDustFromItemStack(contents[i][j+1]).getPlacedColor(contents[i][j+1]);
result.add(new int[]{i,j,i,j+1, color1,color2});
}
}
}
internalConnectors = result;
}
public void updateRendering(){
this.updateCenterColors();
this.updateExternalConnectors();
this.updateInternalConnectors();
}
/**returns the data on the external connectors
* @return a Linked List of int[] in the following format: [row,col,color, index of the facing]
* @see EnumFacing#getIndex()
* @see EnumFacing#getFront(int)
*/
public List<int[]>getExternalConnectors(){
if(externalConnectors==null)updateExternalConnectors();
//updateExternalConnectors();
return externalConnectors;
}
//update the external connectors
public void updateExternalConnectors(){
List<int[]> result = new LinkedList<int[]>();
//world was null when destroyed by explosion
if(world==null||pos==null){
WizardryLogger.logError("world or pos was null for TED at "+world+" "+pos);
return;
}
if(world.getBlockState(pos.north()).getBlock() == WizardryRegistry.dust_placed){
TileEntityDustPlaced ted = (TileEntityDustPlaced)world.getTileEntity(pos.north());
if(ted!=null){
for(int i=0;i<COLS;i++){
int id=getSlotIDfromPosition(0, i);
int otherSlot=id+((ROWS-1)*COLS);
if(!contents[0][i].isEmpty()){
if(dustsMatch(contents[0][i], ted.getStackInSlot(otherSlot))){
result.add(new int[]{0,i,DustRegistry.getDustFromItemStack(contents[0][i]).getPlacedColor(contents[0][i]),EnumFacing.NORTH.getIndex()});
}
}
}
}
}
if(world.getBlockState(pos.south()).getBlock() == WizardryRegistry.dust_placed){
TileEntityDustPlaced ted = (TileEntityDustPlaced)world.getTileEntity(pos.south());
if(ted!=null){
for(int i=0;i<COLS;i++){
int id=getSlotIDfromPosition(ROWS-1, i);
int otherSlot=id-((ROWS-1)*COLS);
if(!contents[ROWS-1][i].isEmpty()){
if(dustsMatch(contents[ROWS-1][i], ted.getStackInSlot(otherSlot))){
result.add(new int[]{ROWS-1,i,DustRegistry.getDustFromItemStack(contents[ROWS-1][i]).getPlacedColor(contents[ROWS-1][i]),EnumFacing.SOUTH.getIndex()});
}
}
}
}
}
if(world.getBlockState(pos.west()).getBlock() == WizardryRegistry.dust_placed){
TileEntityDustPlaced ted = (TileEntityDustPlaced)world.getTileEntity(pos.west());
if(ted!=null){
for(int i=0;i<ROWS;i++){
int id=getSlotIDfromPosition(i,0);
int otherSlot=id+(COLS-1);
if(!contents[i][0].isEmpty()){
if(dustsMatch(contents[i][0], ted.getStackInSlot(otherSlot))){
result.add(new int[]{i,0,DustRegistry.getDustFromItemStack(contents[i][0]).getPlacedColor(contents[i][0]),EnumFacing.WEST.getIndex()});
}
}
}
}
}
if(world.getBlockState(pos.east()).getBlock() == WizardryRegistry.dust_placed){
TileEntityDustPlaced ted = (TileEntityDustPlaced)world.getTileEntity(pos.east());
if(ted!=null){
for(int i=0;i<ROWS;i++){
int id=getSlotIDfromPosition(i,COLS-1);
int otherSlot=id-(COLS-1);
if(!contents[i][COLS-1].isEmpty()){
if(dustsMatch(contents[i][COLS-1], ted.getStackInSlot(otherSlot))){//should be true OK
result.add(new int[]{i,COLS-1,DustRegistry.getDustFromItemStack(contents[i][COLS-1]).getPlacedColor(contents[i][COLS-1]),EnumFacing.EAST.getIndex()});
}
}
}
}
}
externalConnectors=result;
}
private boolean dustsMatch(ItemStack stack1, ItemStack stack2){
if(!stack1.isEmpty() && stack1.getItem()instanceof IDust){
IDust dust1 = DustRegistry.getDustFromItemStack(stack1);
return dust1.shouldConnect(stack1, stack2);
}
if(!stack2.isEmpty() && stack2.getItem() instanceof IDust){
IDust dust2 = DustRegistry.getDustFromItemStack(stack2);
return dust2.shouldConnect(stack2, stack1);
}
return false;//if not at least one is a non-null IDust, should not connect.
}
@Override
public boolean shouldRenderInPass(int pass) {
return pass==1;
}
@SideOnly(Side.CLIENT)
@Override
public double getMaxRenderDistanceSquared() {
return 32*32;
};
// @SideOnly(Side.CLIENT)
// @Override
// public net.minecraft.util.AxisAlignedBB getRenderBoundingBox() {
// return INFINITE_EXTENT_AABB;
// };
@Override
public String getName() {
return References.modid+".DustPlaced";
}
@Override
public boolean hasCustomName() {
return true;
}
@Override
public ITextComponent getDisplayName() {
//see TileEntityDustDye
return this.hasCustomName() ? new TextComponentString(this.getName()) : new TextComponentTranslation(this.getName(), new Object[0]);
}
@Override
public int getSizeInventory() {
return ROWS*COLS;
}
@Override
public ItemStack getStackInSlot(int index) {
int[] coords=getPositionFromSlotID(index);
return contents[coords[0]][coords[1]];
}
/** returns true if there are no more itemStacks in**/
public boolean isEmpty(){
for(int i=0;i<contents.length;i++){
for(int j=0;j<contents[i].length;j++){
if(!contents[i][j].isEmpty())return false;
}
}
return true;
}
/** returns true if this block is part of a rune**/
public boolean isInRune(){
return rune!=null;
}
@Override
public ItemStack decrStackSize(int index, int count) {
int[] co=getPositionFromSlotID(index);
if (!this.contents[co[0]][co[1]].isEmpty())
{
ItemStack itemstack;
if (this.contents[co[0]][co[1]].getCount() <= count)
{
itemstack = this.contents[co[0]][co[1]];
this.contents[co[0]][co[1]] = null;
return itemstack;
}
else
{
itemstack = this.contents[co[0]][co[1]].splitStack(count);
if (this.contents[co[0]][co[1]].getCount() == 0)
{
this.contents[co[0]][co[1]] = null;
}
return itemstack;
}
}
else
{
return null;
}
}
@Override
public ItemStack removeStackFromSlot(int index) {
ItemStack stack = getStackInSlot(index);
if (!stack.isEmpty()) {
setInventorySlotContents(index, ItemStack.EMPTY);
}
return stack;
}
@Override
public void setInventorySlotContents(int index, ItemStack stack) {
int[] co=getPositionFromSlotID(index);
contents[co[0]][co[1]]=stack;
if (!stack.isEmpty() && stack.getCount() > getInventoryStackLimit()) {
stack.setCount(getInventoryStackLimit());
}
//update the rendering stuff
updateCenterColors();
updateInternalConnectors();
updateExternalConnectors();
//update neighbors
//world.notifyNeighborsOfStateChange(getPos(), getBlockType()); NOT working FSR
updateNeighborConnectors();
}
public void updateNeighborConnectors(){
updateNeighborConnectors(getWorld(), getPos());
}
public static void updateNeighborConnectors(World worldIn, BlockPos pos){
for(EnumFacing dir : EnumFacing.HORIZONTALS){
TileEntity te =worldIn.getTileEntity(pos.offset(dir));
if(te instanceof TileEntityDustPlaced){
((TileEntityDustPlaced)te).updateExternalConnectors();
}
}
}
@Override
public int getInventoryStackLimit() {
// only 1 item per slot (does this even do anything?)
return 1;
}
@Override
public boolean isUsableByPlayer(EntityPlayer player) {
// Handled with onBlockActivated?
return false;
}
//Next 2 methods are an attempt to sync stuff
/* (non-Javadoc)
* @see net.minecraft.tileentity.TileEntity#getDescriptionPacket()
*/
@Override
public SPacketUpdateTileEntity getUpdatePacket() {
//this didn't seem to sync properly - getUpdateTag seems to do it, but it would be wise to ask
//what botania does
NBTTagCompound tagCompound = new NBTTagCompound();
this.writeToNBT(tagCompound);
return new SPacketUpdateTileEntity(pos, -999, tagCompound);
}
/* (non-Javadoc)
* @see net.minecraft.tileentity.TileEntity#onDataPacket(net.minecraft.network.NetworkManager, net.minecraft.network.play.server.S35PacketUpdateTileEntity)
*/
@Override
public void onDataPacket(NetworkManager net, SPacketUpdateTileEntity pkt) {
super.onDataPacket(net, pkt);
this.readFromNBT(pkt.getNbtCompound());
// //also update the rendering
updateCenterColors();
updateInternalConnectors();
updateExternalConnectors();
updateNeighborConnectors();
//world.notifyBlockOfStateChange(getPos(), getBlockType());
//world.notifyNeighborsOfStateChange(getPos(), getBlockType());
}
/* (non-Javadoc)
* @see net.minecraft.tileentity.TileEntity#getUpdateTag()
*/
@Override
public NBTTagCompound getUpdateTag() {
return writeToNBT(super.getUpdateTag());
}
@Override
public void openInventory(EntityPlayer player) {
// not using this
}
@Override
public void closeInventory(EntityPlayer player) {
//not using this
}
@Override
public boolean isItemValidForSlot(int index, ItemStack stack) {
// only let dust in
return stack.getItem() instanceof IDust;
}
@Override
public void readFromNBT(NBTTagCompound tagCompound){
super.readFromNBT(tagCompound);
this.clear();//Necessary because null stacks don't get saved to NBT, causing removed items to stay on the client until reload
NBTTagList tagList = tagCompound.getTagList("Inventory",10);
for (int i = 0; i < tagList.tagCount(); i++) {
NBTTagCompound tag = tagList.getCompoundTagAt(i);
byte slot = tag.getByte("Slot");
if (slot >= 0 && slot < getSizeInventory()) {
int[] coords = getPositionFromSlotID(slot);
contents[coords[0]][coords[1]] = new ItemStack(tag);
}
}
if(!tagCompound.getBoolean("inRune"))this.rune=null;
}
@Override
public NBTTagCompound writeToNBT(NBTTagCompound tagCompound) {
super.writeToNBT(tagCompound);
NBTTagList itemList = new NBTTagList();
for (int i = 0; i < getSizeInventory(); i++) {
ItemStack stack = getStackInSlot(i);
if (!stack.isEmpty()) {
NBTTagCompound tag = new NBTTagCompound();
tag.setByte("Slot", (byte) i);
stack.writeToNBT(tag);
itemList.appendTag(tag);
}
}
tagCompound.setTag("Inventory", itemList);
tagCompound.setBoolean("inRune", isInRune());
return tagCompound;
}
//NOT using the following field methods
@Override
public int getField(int id) {
return 0;
}
@Override
public void setField(int id, int value) {
}
@Override
public int getFieldCount() {
return 0;
}
@Override
public void clear() {
// let's just do the same thing as inventoryBasic
for(int i=0;i<contents.length;i++){
for(int j=0;j<contents[i].length;j++){
contents[i][j]=ItemStack.EMPTY;
}
}
}
}