package com.infinityraider.agricraft.tiles.irrigation; import com.infinityraider.agricraft.api.irrigation.IConnectable; import com.infinityraider.agricraft.api.irrigation.IIrrigationComponent; import com.infinityraider.agricraft.reference.AgriCraftConfig; import com.infinityraider.agricraft.network.MessageSyncFluidLevel; import com.infinityraider.agricraft.reference.Constants; import com.infinityraider.agricraft.tiles.TileEntityCustomWood; import com.infinityraider.infinitylib.utility.debug.IDebuggable; import net.minecraft.nbt.NBTTagCompound; import com.agricraft.agricore.core.AgriCore; import net.minecraftforge.fml.common.network.NetworkRegistry; import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.SideOnly; import java.util.List; import net.minecraft.util.EnumFacing; import net.minecraft.util.ITickable; import com.infinityraider.agricraft.reference.AgriNBT; import com.infinityraider.agricraft.api.irrigation.IrrigationConnectionType; import com.infinityraider.infinitylib.utility.WorldHelper; import java.util.function.Consumer; import net.minecraft.block.state.IBlockState; public class TileEntityChannel extends TileEntityCustomWood implements ITickable, IIrrigationComponent, IDebuggable { private final IIrrigationComponent[] neighbours = new IIrrigationComponent[4]; protected static final int NEIGHBOUR_CHECK_DELAY = 1024; protected int ticksSinceNeighbourCheck = NEIGHBOUR_CHECK_DELAY; // Might want to move this to a static import class... protected static final int MIN = 5; protected static final int MAX = 12; protected static final int HEIGHT = MAX - MIN; protected static final int DISCRETE_MAX = 16; protected static final int ABSOLUTE_MAX = AgriCraftConfig.channelCapacity; protected static final float DISCRETE_FACTOR = (float) DISCRETE_MAX / (float) ABSOLUTE_MAX; private int lvl; private int lastDiscreteLvl = 0; //this saves the data on the tile entity @Override protected final void writeNBT(NBTTagCompound tag) { if (this.lvl > 0) { tag.setInteger(AgriNBT.LEVEL, this.lvl); } writeChannelNBT(tag); } void writeChannelNBT(NBTTagCompound tag) { } //this loads the saved data for the tile entity @Override protected final void readNBT(NBTTagCompound tag) { if (tag.hasKey(AgriNBT.LEVEL)) { this.lvl = tag.getInteger(AgriNBT.LEVEL); } else { this.lvl = 0; } readChannelNBT(tag); } void readChannelNBT(NBTTagCompound tag) { } @Override public int getFluidAmount(int y) { return this.lvl; } @Override public int getCapacity() { return ABSOLUTE_MAX; } @Override public void setFluidLevel(int lvl) { if (lvl >= 0 && lvl <= ABSOLUTE_MAX && lvl != this.lvl) { this.lvl = lvl; syncFluidLevel(); } } @Override public int acceptFluid(int y, int amount, boolean partial) { if (!worldObj.isRemote && amount >= 0 && this.canAcceptFluid(0, amount, partial)) { int room = this.getCapacity() - this.getFluidAmount(0); if (room >= amount) { this.setFluidLevel(this.getFluidAmount(0) + amount); amount = 0; } else if (room > 0) { this.setFluidLevel(this.getCapacity()); amount = amount - room; } } return amount; } @Override public boolean canConnectTo(EnumFacing side, IConnectable component) { return (component instanceof TileEntityCustomWood) && this.isSameMaterial((TileEntityCustomWood) component); } @Override public boolean canAcceptFluid(int y, int amount, boolean partial) { if (this.lvl + amount >= this.getCapacity()) { return true; } else { return partial; } } @Override public final void checkConnections() { for (int i = 0; i < EnumFacing.HORIZONTALS.length; i++) { final EnumFacing dir = EnumFacing.HORIZONTALS[i]; neighbours[i] = WorldHelper.getTile(worldObj, pos.offset(dir), IIrrigationComponent.class) .filter(n -> n.canConnectTo(dir.getOpposite(), this) || this.canConnectTo(dir, n)) .orElse(null); } } @Override public int getFluidHeight() { return (int) getFluidHeight(getFluidAmount(0)); } @Override public float getFluidHeight(int lvl) { return MIN + HEIGHT * ((float) lvl) / (ABSOLUTE_MAX); } public boolean hasNeighbor(EnumFacing direction) { final int dir = direction.getHorizontalIndex(); return dir >= 0 && neighbours[dir] != null; } public IIrrigationComponent getNeighbor(EnumFacing direction) { final int dir = direction.getHorizontalIndex(); return dir >= 0 ? neighbours[dir] : null; } @Override public IrrigationConnectionType getConnectionType(EnumFacing side) { if (this.hasNeighbor(side)) { return IrrigationConnectionType.PRIMARY; } else { return IrrigationConnectionType.NONE; } } //updates the tile entity every tick @Override public void update() { if (++ticksSinceNeighbourCheck > NEIGHBOUR_CHECK_DELAY) { checkConnections(); ticksSinceNeighbourCheck = 0; } if (!this.worldObj.isRemote) { //calculate total fluid lvl and capacity int totalLvl = 0; int nr = 1; int updatedLevel = this.getFluidAmount(0); for (IIrrigationComponent component : neighbours) { if (component == null) { continue; } //neighbour is a channel: add its volume to the total and increase the COUNT if (component instanceof TileEntityChannel) { if (!(component instanceof TileEntityChannelValve && ((TileEntityChannelValve) component).isPowered())) { totalLvl = totalLvl + ((TileEntityChannel) component).lvl; nr++; } } //neighbour is a tank: calculate the fluid levels of the tank and the channel else { TileEntityTank tank = (TileEntityTank) component; int Y = tank.getYPosition(); float y_c = Constants.WHOLE * Y + this.getFluidHeight(updatedLevel); //initial channel water Y1 float y_t = tank.getFluidHeight(); //initial tank water Y1 float y1 = (float) MIN + Constants.WHOLE * Y; //minimum Y1 of the channel float y2 = (float) MAX + Constants.WHOLE * Y; //maximum Y1 of the channel int V_tot = tank.getFluidAmount(0) + updatedLevel; if (y_c != y_t) { //total volume is below the channel connection if (y_t <= y1) { updatedLevel = 0; tank.setFluidLevel(V_tot); } //total volume is above the channel connection else if (y_t >= y2) { updatedLevel = ABSOLUTE_MAX; tank.setFluidLevel(V_tot - ABSOLUTE_MAX); } //total volume is between channel connection top and bottom else { //some parameters int tankYSize = tank.getMultiBlockData().sizeY(); int C = tank.getCapacity(); //calculate the Y1 corresponding to the total volume: Y1 = f(V_tot), V_tank = f(Y1), V_channel = f(Y1) float enumerator = (V_tot) + ((ABSOLUTE_MAX * y1) / (y2 - y1) + ((float) 2 * C) / (Constants.WHOLE * tankYSize - 2)); float denominator = ((ABSOLUTE_MAX) / (y2 - y1) + ((float) C) / ((float) (Constants.WHOLE * tankYSize - 2))); float y = enumerator / denominator; //convert the Y1 to volumes int channelVolume = (int) Math.floor(ABSOLUTE_MAX * (y - y1) / (y2 - y1)); int tankVolume = (int) Math.ceil(C * (y - 2) / (Constants.WHOLE * tankYSize - 2)); updatedLevel = channelVolume; tank.setFluidLevel(tankVolume); } } } } // Handle Sprinklers TileEntitySprinkler spr = WorldHelper.getTile(worldObj, this.pos.add(0, 1, 0), TileEntitySprinkler.class).orElse(null); if (spr != null) { updatedLevel = spr.acceptFluid(1000, updatedLevel, true); } //equalize water LEVEL over all neighbouring channels totalLvl = totalLvl + updatedLevel; int rest = totalLvl % nr; int newLvl = totalLvl / nr; if (nr > 1) { //set fluid levels for (IIrrigationComponent component : neighbours) { //TODO: cleanup if (component instanceof TileEntityChannel) { if (!(component instanceof TileEntityChannelValve && ((TileEntityChannelValve) component).isPowered())) { final int olvl = rest == 0 ? newLvl : newLvl + 1; rest = rest == 0 ? 0 : rest - 1; component.setFluidLevel(olvl); } } } } this.setFluidLevel(newLvl + rest); } } @Override public void syncFluidLevel() { if (!this.worldObj.isRemote) { int newDiscreteLvl = getDiscreteFluidLevel(); if (newDiscreteLvl != lastDiscreteLvl) { lastDiscreteLvl = newDiscreteLvl; NetworkRegistry.TargetPoint point = new NetworkRegistry.TargetPoint(this.worldObj.provider.getDimension(), this.xCoord(), this.yCoord(), this.zCoord(), 64); new MessageSyncFluidLevel(this.lvl, this.getPos()).sendToAllAround(point); } } } /** * Maps the current fluid LEVEL into the integer interval [0, 16] * * @return The discrete fluid level. */ public int getDiscreteFluidLevel() { int discreteFluidLevel = Math.round(DISCRETE_FACTOR * lvl); if (discreteFluidLevel == 0 && lvl > 0) { discreteFluidLevel = 1; } return discreteFluidLevel; } @Override public void addServerDebugInfo(Consumer<String> consumer) { consumer.accept("CHANNEL:"); super.addServerDebugInfo(consumer); consumer.accept(" - FluidLevel: " + this.getFluidAmount(0) + "/" + ABSOLUTE_MAX); consumer.accept(" - FluidHeight: " + this.getFluidHeight()); consumer.accept(" - Connections: "); for (EnumFacing dir : EnumFacing.values()) { if (this.hasNeighbor(dir)) { consumer.accept(" - " + dir.name()); } } } @Override public void addClientDebugInfo(Consumer<String> consumer) { consumer.accept("CHANNEL:"); super.addClientDebugInfo(consumer); consumer.accept(" - FluidLevel: " + this.getFluidAmount(0) + "/" + ABSOLUTE_MAX); consumer.accept(" - FluidHeight: " + this.getFluidHeight()); consumer.accept(" - Connections: "); for (EnumFacing dir : EnumFacing.values()) { if (this.hasNeighbor(dir)) { consumer.accept(" - " + dir.name()); } } } @Override @SideOnly(Side.CLIENT) @SuppressWarnings("unchecked") public void addDisplayInfo(List information) { //Required call to super. super.addDisplayInfo(information); information.add(AgriCore.getTranslator().translate("agricraft_tooltip.waterLevel") + ": " + this.getFluidAmount(0) + "/" + ABSOLUTE_MAX); } protected IBlockState getStateChannel(IBlockState state) { return state; } }