/*
* ******************************************************************************
* Copyright 2011-2015 CovertJaguar
*
* This work (the API) is licensed under the "MIT" License, see LICENSE.md for details.
* ***************************************************************************
*/
package mods.railcraft.api.signals;
import com.google.common.collect.MapMaker;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import mods.railcraft.api.core.WorldCoordinate;
import net.minecraft.block.Block;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.*;
/**
* @author CovertJaguar <http://www.railcraft.info>
*/
public abstract class AbstractPair {
protected static final Random rand = new Random();
private static final boolean IS_BUKKIT;
static {
boolean foundBukkit;
try {
foundBukkit = Class.forName("org.spigotmc.SpigotConfig") != null;
} catch (ClassNotFoundException er) {
foundBukkit = false;
}
IS_BUKKIT = foundBukkit;
}
private static final int SAFE_TIME = 32;
private static final int PAIR_CHECK_INTERVAL = 16;
public final TileEntity tile;
public final String locTag;
public final int maxPairings;
protected final Deque<WorldCoordinate> pairings = new LinkedList<WorldCoordinate>();
protected final Set<WorldCoordinate> invalidPairings = new HashSet<WorldCoordinate>();
private final Collection<WorldCoordinate> safePairings = Collections.unmodifiableCollection(pairings);
private final Set<WorldCoordinate> pairingsToTest = new HashSet<WorldCoordinate>();
private final Set<WorldCoordinate> pairingsToTestNext = new HashSet<WorldCoordinate>();
private final Map<WorldCoordinate, TileEntity> tileCache = new MapMaker().weakValues().makeMap();
private WorldCoordinate coords;
private boolean isBeingPaired;
private int update = rand.nextInt();
private int ticksExisted;
private boolean needsInit = true;
private String name;
public AbstractPair(String locTag, TileEntity tile, int maxPairings) {
this.tile = tile;
this.maxPairings = maxPairings;
this.locTag = locTag;
}
public String getName() {
return this.name;
}
public void setName(String name) {
if (name == null || this.name == null || !this.name.equals(name)) {
this.name = name;
this.informPairsOfNameChange();
}
}
public void informPairsOfNameChange() {
}
public void onPairNameChange(WorldCoordinate coords, String name) {
}
protected boolean isLoaded() {
return ticksExisted >= SAFE_TIME;
}
protected void addPairing(WorldCoordinate other) {
pairings.remove(other);
pairings.add(other);
while (pairings.size() > getMaxPairings()) {
pairings.remove();
}
SignalTools.packetBuilder.sendPairPacketUpdate(this);
}
public void clearPairing(WorldCoordinate other) {
invalidPairings.add(other);
}
public void endPairing() {
isBeingPaired = false;
}
public void tickClient() {
if (needsInit) {
needsInit = false;
SignalTools.packetBuilder.sendPairPacketRequest(this);
}
}
public void tickServer() {
update++;
if (!isLoaded())
ticksExisted++;
else if (update % PAIR_CHECK_INTERVAL == 0)
validatePairings();
}
protected void validatePairings() {
if (!pairingsToTestNext.isEmpty()) {
pairingsToTestNext.retainAll(pairings);
for (WorldCoordinate coord : pairingsToTestNext) {
int x = coord.x;
int y = coord.y;
int z = coord.z;
World world = tile.getWorldObj();
if (!world.blockExists(x, y, z))
continue;
Block block = world.getBlock(x, y, z);
int meta = world.getBlockMetadata(x, y, z);
if (!block.hasTileEntity(meta)) {
clearPairing(coord);
continue;
}
TileEntity target = world.getTileEntity(x, y, z);
if (target != null && !isValidPair(coord, target))
clearPairing(coord);
}
pairingsToTestNext.clear();
}
cleanPairings();
for (WorldCoordinate coord : pairings) {
getPairAt(coord);
}
pairingsToTestNext.addAll(pairingsToTest);
pairingsToTest.clear();
}
public void cleanPairings() {
if (invalidPairings.isEmpty())
return;
boolean changed = pairings.removeAll(invalidPairings);
invalidPairings.clear();
if (changed)
SignalTools.packetBuilder.sendPairPacketUpdate(this);
}
protected TileEntity getPairAt(WorldCoordinate coord) {
if (!pairings.contains(coord))
return null;
int x = coord.x;
int y = coord.y;
int z = coord.z;
boolean useCache;
try {
useCache = !IS_BUKKIT && getCoords().isInSameChunk(coord);
} catch (Throwable er) {
useCache = false;
}
if (useCache) {
TileEntity cacheTarget = tileCache.get(coord);
if (cacheTarget != null) {
if (cacheTarget.isInvalid() || cacheTarget.xCoord != x || cacheTarget.yCoord != y || cacheTarget.zCoord != z)
tileCache.remove(coord);
else if (isValidPair(coord, cacheTarget))
return cacheTarget;
}
}
if (y < 0) {
clearPairing(coord);
return null;
}
World world = tile.getWorldObj();
if (!world.blockExists(x, y, z))
return null;
Block block = world.getBlock(x, y, z);
int meta = world.getBlockMetadata(x, y, z);
if (!block.hasTileEntity(meta)) {
pairingsToTest.add(coord);
return null;
}
TileEntity target = world.getTileEntity(x, y, z);
if (target != null && !isValidPair(coord, target)) {
pairingsToTest.add(coord);
return null;
}
if (useCache && target != null) {
tileCache.put(coord, target);
}
return target;
}
public boolean isValidPair(WorldCoordinate otherCoord, TileEntity otherTile) {
return false;
}
public WorldCoordinate getCoords() {
if (coords == null)
coords = new WorldCoordinate(tile.getWorldObj().provider.dimensionId, tile.xCoord, tile.yCoord, tile.zCoord);
return coords;
}
public String getLocalizationTag() {
return locTag;
}
public int getMaxPairings() {
return maxPairings;
}
public int getNumPairs() {
return pairings.size();
}
public boolean isPaired() {
return !pairings.isEmpty();
}
public Collection<WorldCoordinate> getPairs() {
return safePairings;
}
public TileEntity getTile() {
return tile;
}
public void startPairing() {
isBeingPaired = true;
}
public boolean isBeingPaired() {
return isBeingPaired;
}
public boolean isPairedWith(WorldCoordinate other) {
return pairings.contains(other);
}
protected abstract String getTagName();
public final void writeToNBT(NBTTagCompound data) {
NBTTagCompound tag = new NBTTagCompound();
saveNBT(tag);
data.setTag(getTagName(), tag);
}
protected void saveNBT(NBTTagCompound data) {
NBTTagList list = new NBTTagList();
for (WorldCoordinate c : pairings) {
NBTTagCompound tag = new NBTTagCompound();
tag.setIntArray("coords", new int[]{c.dimension, c.x, c.y, c.z});
list.appendTag(tag);
}
data.setTag("pairings", list);
if (this.name != null) {
data.setString("name", this.name);
}
}
public final void readFromNBT(NBTTagCompound data) {
NBTTagCompound tag = data.getCompoundTag(getTagName());
loadNBT(tag);
}
protected void loadNBT(NBTTagCompound data) {
NBTTagList list = data.getTagList("pairings", 10);
for (byte entry = 0; entry < list.tagCount(); entry++) {
NBTTagCompound tag = list.getCompoundTagAt(entry);
int[] c = tag.getIntArray("coords");
pairings.add(new WorldCoordinate(c[0], c[1], c[2], c[3]));
}
this.name = data.getString("name");
if (this.name.isEmpty()) {
this.name = null;
}
}
public void writePacketData(DataOutputStream data) throws IOException {
data.writeUTF(this.name != null ? this.name : "");
}
public void readPacketData(DataInputStream data) throws IOException {
this.name = data.readUTF();
if (this.name.isEmpty()) {
this.name = null;
}
}
@SideOnly(Side.CLIENT)
public void addPair(int x, int y, int z) {
pairings.add(new WorldCoordinate(tile.getWorldObj().provider.dimensionId, x, y, z));
}
@SideOnly(Side.CLIENT)
public void removePair(int x, int y, int z) {
pairings.remove(new WorldCoordinate(tile.getWorldObj().provider.dimensionId, x, y, z));
}
public void clearPairings() {
pairings.clear();
if (!tile.getWorldObj().isRemote)
SignalTools.packetBuilder.sendPairPacketUpdate(this);
}
}