/**
*
* Inspired/ported from GregTech 6 under the LGPL license
*
* Copyright (c) 2020 GregTech-6 Team
*
* This file is part of GregTech.
*
* GregTech is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* GregTech is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along with GregTech. If not, see
* .
*/
package gregtech.common.tileentities.machines.long_distance;
import static gregtech.api.enums.Textures.BlockIcons.MACHINE_CASINGS;
import static mcp.mobius.waila.api.SpecialChars.BLUE;
import static mcp.mobius.waila.api.SpecialChars.GOLD;
import static mcp.mobius.waila.api.SpecialChars.GREEN;
import static mcp.mobius.waila.api.SpecialChars.RED;
import static mcp.mobius.waila.api.SpecialChars.RESET;
import static mcp.mobius.waila.api.SpecialChars.YELLOW;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
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.nbt.NBTTagCompound;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ChunkCoordinates;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import gregtech.api.GregTechAPI;
import gregtech.api.interfaces.ITexture;
import gregtech.api.interfaces.metatileentity.IMetaTileEntity;
import gregtech.api.interfaces.tileentity.IGregTechTileEntity;
import gregtech.api.items.BlockLongDistancePipe;
import gregtech.api.metatileentity.BaseMetaTileEntity;
import gregtech.api.metatileentity.implementations.MTEBasicHullNonElectric;
import gregtech.api.util.GTUtility;
import mcp.mobius.waila.api.IWailaConfigHandler;
import mcp.mobius.waila.api.IWailaDataAccessor;
public abstract class MTELongDistancePipelineBase extends MTEBasicHullNonElectric {
protected static final int INPUT_INDEX = 0;
protected static final int OUTPUT_INDEX = 1;
protected static final int SIDE_UP_DOWN_INDEX = 2;
protected static final int SIDE_LEFT_RIGHT_INDEX = 3;
public static int minimalDistancePoints = 64;
protected MTELongDistancePipelineBase mTarget = null;
// these two are updated by machine block update thread, so must be volatile
protected volatile MTELongDistancePipelineBase mSender = null;
protected volatile ChunkCoordinates mTargetPos = null;
protected MTELongDistancePipelineBase mTooCloseTarget = null, mTooCloseSender = null;
public MTELongDistancePipelineBase(int aID, String aName, String aNameRegional, int aTier, String aDescription) {
super(aID, aName, aNameRegional, aTier, aDescription);
}
public MTELongDistancePipelineBase(String aName, int aTier, String aDescription, ITexture[][][] aTextures) {
super(aName, aTier, aDescription, aTextures);
}
@Override
public String[] getDescription() {
return new String[] { "Only one Input and Output are allowed per pipeline",
"Only Input and Output have to be chunkloaded", "Transfer rate is solely limited by input rate",
"Minimum distance: " + minimalDistancePoints + " blocks" };
}
@Override
public void saveNBTData(NBTTagCompound aNBT) {
super.saveNBTData(aNBT);
if (mTargetPos != null && mTarget != this) {
aNBT.setBoolean("target", true);
aNBT.setInteger("target.x", mTargetPos.posX);
aNBT.setInteger("target.y", mTargetPos.posY);
aNBT.setInteger("target.z", mTargetPos.posZ);
}
}
@Override
public void loadNBTData(NBTTagCompound aNBT) {
super.loadNBTData(aNBT);
if (aNBT.hasKey("target")) {
mTargetPos = new ChunkCoordinates(
aNBT.getInteger("target.x"),
aNBT.getInteger("target.y"),
aNBT.getInteger("target.z"));
if (getDistanceToSelf(mTargetPos) < minimalDistancePoints) mTargetPos = null;
}
}
public boolean isSameClass(MTELongDistancePipelineBase other) {
return false;
}
@Override
public boolean onRightclick(IGregTechTileEntity aBaseMetaTileEntity, EntityPlayer aPlayer) {
if (aBaseMetaTileEntity.isClientSide()) return true;
ItemStack tCurrentItem = aPlayer.inventory.getCurrentItem();
if (tCurrentItem != null) {
if (GTUtility.isStackInList(tCurrentItem, GregTechAPI.sSoftHammerList)) {
scanPipes();
return true;
}
}
return false;
}
public boolean isDead() {
return getBaseMetaTileEntity() == null || getBaseMetaTileEntity().isDead();
}
public boolean checkTarget() {
final IGregTechTileEntity gt_tile = getBaseMetaTileEntity();
if (gt_tile == null || !gt_tile.isAllowedToWork() || gt_tile.isClientSide()) return false;
World world = gt_tile.getWorld();
if (world == null) return false;
if (mTargetPos == null) {
// We don't have a target position, scan the pipes
scanPipes();
} else if (mTarget == null || mTarget.isDead()) {
// We don't have a target, or it's dead. Try checking the target position
mTarget = null;
if (world.blockExists(mTargetPos.posX, mTargetPos.posY, mTargetPos.posZ)) {
// Only check if the target position is loaded
TileEntity te = world.getTileEntity(mTargetPos.posX, mTargetPos.posY, mTargetPos.posZ);
final IMetaTileEntity tMeta;
if (te instanceof BaseMetaTileEntity
&& ((tMeta = ((BaseMetaTileEntity) te).getMetaTileEntity()) instanceof MTELongDistancePipelineBase)
&& isSameClass((MTELongDistancePipelineBase) tMeta)) {
// It's the right type!
mTarget = (MTELongDistancePipelineBase) tMeta;
} else if (te != null) {
// It isn't the right type, kill the target position
mTargetPos = null;
}
}
}
if (mTooCloseTarget != null && mTooCloseTarget.mSender == null) mTooCloseTarget.mTooCloseSender = this;
if (mTooCloseSender != null && (mTooCloseSender.isDead() || mTooCloseSender.mTarget != null))
mTooCloseSender = null;
if (mTarget == null || mTarget == this) return false;
if (mTarget.mSender == null || mTarget.mSender.isDead()
|| mTarget.mSender.mTarget == null
|| mTarget.mSender.mTarget.isDead()) {
mTarget.mSender = this;
mTarget.mTooCloseSender = null;
}
return mTarget.mSender == this;
}
@Override
public ArrayList getSpecialDebugInfo(IGregTechTileEntity aBaseMetaTileEntity, EntityPlayer aPlayer,
int aLogLevel, ArrayList aList) {
if (mSender != null && !mSender.isDead() && mSender.mTarget == this) {
final ChunkCoordinates coords = mSender.getCoords();
aList.addAll(
Arrays.asList(
"Is Pipeline Output",
"Pipeline Input is at: X: " + coords.posX + " Y: " + coords.posY + " Z: " + coords.posZ));
} else {
aList.addAll(
Arrays.asList(
checkTarget() ? "Is connected to Pipeline Output" : "Pipeline Output is not connected/chunkloaded",
"Pipeline Output should be around: X: " + mTargetPos.posX
+ " Y: "
+ mTargetPos.posY
+ " Z: "
+ mTargetPos.posZ));
}
return aList;
}
// What meta should the pipes for this pipeline have
public abstract int getPipeMeta();
protected void scanPipes() {
if (mSender != null && !mSender.isDead() && mSender.mTarget == this) return;
// Check if we need to scan anything
final IGregTechTileEntity gtTile = getBaseMetaTileEntity();
if (gtTile == null) return;
final World world = gtTile.getWorld();
if (world == null) return;
mTargetPos = getCoords();
mTarget = this;
mSender = null;
// Start scanning from the output side
Block aBlock = gtTile.getBlockAtSide(gtTile.getBackFacing());
if (aBlock instanceof BlockLongDistancePipe) {
byte aMetaData = gtTile.getMetaIDAtSide(gtTile.getBackFacing());
if (aMetaData != getPipeMeta()) return;
HashSet tVisited = new HashSet<>(Collections.singletonList(getCoords())),
tWires = new HashSet<>();
Queue tQueue = new LinkedList<>(
Collections.singletonList(getFacingOffset(gtTile, gtTile.getBackFacing())));
while (!tQueue.isEmpty()) {
final ChunkCoordinates aCoords = tQueue.poll();
if (world.getBlock(aCoords.posX, aCoords.posY, aCoords.posZ) == aBlock
&& world.getBlockMetadata(aCoords.posX, aCoords.posY, aCoords.posZ) == aMetaData) {
// We've got another pipe/wire block
// TODO: Make sure it's the right type of pipe/wire via meta
ChunkCoordinates tCoords;
tWires.add(aCoords);
// For each direction, if we haven't already visited that coordinate, add it to the end of the
// queue
if (tVisited.add(tCoords = new ChunkCoordinates(aCoords.posX + 1, aCoords.posY, aCoords.posZ)))
tQueue.add(tCoords);
if (tVisited.add(tCoords = new ChunkCoordinates(aCoords.posX - 1, aCoords.posY, aCoords.posZ)))
tQueue.add(tCoords);
if (tVisited.add(tCoords = new ChunkCoordinates(aCoords.posX, aCoords.posY + 1, aCoords.posZ)))
tQueue.add(tCoords);
if (tVisited.add(tCoords = new ChunkCoordinates(aCoords.posX, aCoords.posY - 1, aCoords.posZ)))
tQueue.add(tCoords);
if (tVisited.add(tCoords = new ChunkCoordinates(aCoords.posX, aCoords.posY, aCoords.posZ + 1)))
tQueue.add(tCoords);
if (tVisited.add(tCoords = new ChunkCoordinates(aCoords.posX, aCoords.posY, aCoords.posZ - 1)))
tQueue.add(tCoords);
} else {
// It's not a block - let's see if it's a tile entity
TileEntity tTileEntity = world.getTileEntity(aCoords.posX, aCoords.posY, aCoords.posZ);
if (tTileEntity != gtTile && tTileEntity instanceof BaseMetaTileEntity
&& ((BaseMetaTileEntity) tTileEntity)
.getMetaTileEntity() instanceof MTELongDistancePipelineBase tGtTile) {
if (isSameClass(tGtTile) && tWires.contains(
tGtTile.getFacingOffset(
(BaseMetaTileEntity) tTileEntity,
((BaseMetaTileEntity) tTileEntity).getFrontFacing()))) {
// If it's the same class, and we've scanned a wire in front of it (the input side), we've
// found our target
// still need to check if it's distant enough
int distance = getDistanceToSelf(aCoords);
if (distance > minimalDistancePoints) {
mTarget = tGtTile;
mTargetPos = tGtTile.getCoords();
mTooCloseTarget = null;
return;
} else {
if (mTooCloseTarget == null) {
mTooCloseTarget = tGtTile;
}
}
}
// Remove this block from the visited because we might end up back here from another wire that
// IS connected to the
// input side
tVisited.remove(aCoords);
}
}
}
}
}
protected int getDistanceToSelf(ChunkCoordinates aCoords) {
return Math.abs(getBaseMetaTileEntity().getXCoord() - aCoords.posX)
+ Math.abs(getBaseMetaTileEntity().getYCoord() - aCoords.posY) / 2
+ Math.abs(getBaseMetaTileEntity().getZCoord() - aCoords.posZ);
}
public ChunkCoordinates getFacingOffset(IGregTechTileEntity gt_tile, ForgeDirection side) {
return new ChunkCoordinates(
gt_tile.getOffsetX(side, 1),
gt_tile.getOffsetY(side, 1),
gt_tile.getOffsetZ(side, 1));
}
public ChunkCoordinates getCoords() {
final IGregTechTileEntity gt_tile = getBaseMetaTileEntity();
return new ChunkCoordinates(gt_tile.getXCoord(), gt_tile.getYCoord(), gt_tile.getZCoord());
}
@Override
public void onMachineBlockUpdate() {
mTargetPos = null;
mSender = null;
}
@Override
public boolean shouldTriggerBlockUpdate() {
return true;
}
abstract public ITexture[] getTextureOverlays();
@Override
public ITexture[][][] getTextureSet(ITexture[] aTextures) {
ITexture[][][] rTextures = new ITexture[4][17][];
ITexture[] overlays = getTextureOverlays();
for (int i = 0; i < rTextures[0].length; i++) {
rTextures[INPUT_INDEX][i] = new ITexture[] { MACHINE_CASINGS[mTier][i], overlays[INPUT_INDEX] };
rTextures[OUTPUT_INDEX][i] = new ITexture[] { MACHINE_CASINGS[mTier][i], overlays[OUTPUT_INDEX] };
rTextures[SIDE_UP_DOWN_INDEX][i] = new ITexture[] { MACHINE_CASINGS[mTier][i],
overlays[SIDE_UP_DOWN_INDEX] };
rTextures[SIDE_LEFT_RIGHT_INDEX][i] = new ITexture[] { MACHINE_CASINGS[mTier][i],
overlays[SIDE_LEFT_RIGHT_INDEX] };
}
return rTextures;
}
@Override
public ITexture[] getTexture(IGregTechTileEntity baseMetaTileEntity, ForgeDirection sideDirection,
ForgeDirection facingDirection, int colorIndex, boolean active, boolean redstoneLevel) {
colorIndex += 1;
if (sideDirection == facingDirection) return mTextures[INPUT_INDEX][colorIndex];
else if (sideDirection == facingDirection.getOpposite()) return mTextures[OUTPUT_INDEX][colorIndex];
else {
switch (facingDirection) {
case UP, DOWN -> {
return mTextures[SIDE_UP_DOWN_INDEX][colorIndex];
}
case NORTH -> {
switch (sideDirection) {
case DOWN, UP -> {
return mTextures[SIDE_UP_DOWN_INDEX][colorIndex];
}
case EAST, WEST -> {
return mTextures[SIDE_LEFT_RIGHT_INDEX][colorIndex];
}
default -> {}
}
}
case SOUTH -> {
switch (sideDirection) {
case DOWN, UP -> {
return mTextures[SIDE_UP_DOWN_INDEX][colorIndex];
}
case EAST, WEST -> {
return mTextures[SIDE_LEFT_RIGHT_INDEX][colorIndex];
}
default -> {}
}
}
case EAST, WEST -> {
return mTextures[SIDE_LEFT_RIGHT_INDEX][colorIndex];
}
default -> {}
}
}
return mTextures[INPUT_INDEX][colorIndex]; // dummy
}
@Override
public void getWailaBody(ItemStack itemStack, List currentTip, IWailaDataAccessor accessor,
IWailaConfigHandler config) {
final ForgeDirection facing = getBaseMetaTileEntity().getFrontFacing();
final ForgeDirection side = accessor.getSide();
final NBTTagCompound tag = accessor.getNBTData();
final boolean hasInput = tag.getBoolean("hasInput");
final boolean hasInputTooClose = tag.getBoolean("hasInputTooClose");
final boolean hasOutput = tag.getBoolean("hasOutput");
final boolean hasOutputTooClose = tag.getBoolean("hasOutputTooClose");
if (side == facing) currentTip.add(GOLD + "Pipeline Input" + RESET);
else if (side == facing.getOpposite()) currentTip.add(BLUE + "Pipeline Output" + RESET);
else currentTip.add("Pipeline Side");
if (!hasInput && !hasInputTooClose && !hasOutput && !hasOutputTooClose) {
currentTip.add(YELLOW + "Not connected" + RESET);
}
if (hasInput) currentTip.add(GREEN + "Connected to " + GOLD + "Input" + RESET);
else if (hasInputTooClose) currentTip.add(RED + "Connected Input too close" + RESET);
else if (hasOutput) currentTip.add(GREEN + "Connected to " + BLUE + "Output" + RESET);
else if (hasOutputTooClose) currentTip.add(RED + "Connected Output too close" + RESET);
super.getWailaBody(itemStack, currentTip, accessor, config);
}
@Override
public void getWailaNBTData(EntityPlayerMP player, TileEntity tile, NBTTagCompound tag, World world, int x, int y,
int z) {
super.getWailaNBTData(player, tile, tag, world, x, y, z);
tag.setBoolean("hasInput", mSender != null);
tag.setBoolean("hasInputTooClose", mTooCloseSender != null);
tag.setBoolean("hasOutput", mTarget != null && mTarget != this);
tag.setBoolean("hasOutputTooClose", mTooCloseTarget != null);
}
}