Files
Applied-Energistics-2/src/main/java/appeng/tile/AEBaseTileEntity.java
T
2020-08-24 01:06:26 +02:00

461 lines
14 KiB
Java

/*
* This file is part of Applied Energistics 2.
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
*
* Applied Energistics 2 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.
*
* Applied Energistics 2 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 Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.tile;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import io.netty.buffer.Unpooled;
import net.minecraft.block.BlockState;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.PacketBuffer;
import net.minecraft.network.play.server.SUpdateTileEntityPacket;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent;
import net.minecraft.world.World;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.items.IItemHandler;
import appeng.api.implementations.tiles.ISegmentedInventory;
import appeng.api.util.ICommonTile;
import appeng.api.util.IConfigManager;
import appeng.api.util.IConfigurableObject;
import appeng.api.util.IOrientable;
import appeng.block.AEBaseTileBlock;
import appeng.client.render.model.AEModelData;
import appeng.core.AELog;
import appeng.core.features.IStackSrc;
import appeng.helpers.ICustomNameObject;
import appeng.helpers.IPriorityHost;
import appeng.hooks.TickHandler;
import appeng.tile.inventory.AppEngInternalAEInventory;
import appeng.util.Platform;
import appeng.util.SettingsFrom;
public class AEBaseTileEntity extends TileEntity implements IOrientable, ICommonTile, ICustomNameObject {
private static final ThreadLocal<WeakReference<AEBaseTileEntity>> DROP_NO_ITEMS = new ThreadLocal<>();
private static final Map<Class<? extends TileEntity>, IStackSrc> ITEM_STACKS = new HashMap<>();
private int renderFragment = 0;
@Nullable
private String customName;
private Direction forward = Direction.NORTH;
private Direction up = Direction.UP;
private boolean markDirtyQueued = false;
public AEBaseTileEntity(TileEntityType<?> tileEntityTypeIn) {
super(tileEntityTypeIn);
}
public static void registerTileItem(final Class<? extends TileEntity> c, final IStackSrc wat) {
ITEM_STACKS.put(c, wat);
}
public boolean dropItems() {
final WeakReference<AEBaseTileEntity> what = DROP_NO_ITEMS.get();
return what == null || what.get() != this;
}
public boolean notLoaded() {
return !this.world.isBlockLoaded(this.pos);
}
@Nonnull
public TileEntity getTile() {
return this;
}
@Nullable
protected ItemStack getItemFromTile(final Object obj) {
final IStackSrc src = ITEM_STACKS.get(obj.getClass());
if (src == null) {
return ItemStack.EMPTY;
}
return src.stack(1);
}
@Override
public void read(BlockState blockState, final CompoundNBT data) {
super.read(blockState, data);
if (data.contains("customName")) {
this.customName = data.getString("customName");
} else {
this.customName = null;
}
try {
if (this.canBeRotated()) {
this.forward = Direction.valueOf(data.getString("forward"));
this.up = Direction.valueOf(data.getString("up"));
}
} catch (final IllegalArgumentException ignored) {
}
}
@Override
public CompoundNBT write(final CompoundNBT data) {
super.write(data);
if (this.canBeRotated()) {
data.putString("forward", this.getForward().name());
data.putString("up", this.getUp().name());
}
if (this.customName != null) {
data.putString("customName", this.customName);
}
return data;
}
@Override
public SUpdateTileEntityPacket getUpdatePacket() {
return new SUpdateTileEntityPacket(this.pos, 64, this.getUpdateTag());
}
@Override
public void onDataPacket(final NetworkManager net, final SUpdateTileEntityPacket pkt) {
// / pkt.actionType
if (pkt.getTileEntityType() == 64) {
this.handleUpdateTag(null, pkt.getNbtCompound());
}
}
public void onReady() {
}
/**
* This builds a tag with the actual data that should be sent to the client for
* update syncs. If the tile entity doesn't need update syncs, it returns null.
*/
private CompoundNBT writeUpdateData() {
final CompoundNBT data = new CompoundNBT();
final PacketBuffer stream = new PacketBuffer(Unpooled.buffer());
try {
this.writeToStream(stream);
if (stream.readableBytes() == 0) {
return null;
}
} catch (final Throwable t) {
AELog.debug(t);
}
stream.capacity(stream.readableBytes());
data.putByteArray("X", stream.array());
return data;
}
private boolean readUpdateData(PacketBuffer stream) {
boolean output = false;
try {
this.renderFragment = 100;
output = this.readFromStream(stream);
if ((this.renderFragment & 1) == 1) {
output = true;
}
this.renderFragment = 0;
} catch (final Throwable t) {
AELog.debug(t);
}
return output;
}
/**
* Handles tile entites that are being sent to the client as part of a full
* chunk.
*/
@Override
public CompoundNBT getUpdateTag() {
final CompoundNBT data = this.writeUpdateData();
if (data == null) {
return new CompoundNBT();
}
data.putInt("x", this.pos.getX());
data.putInt("y", this.pos.getY());
data.putInt("z", this.pos.getZ());
return data;
}
/**
* Handles tile entites that are being received by the client as part of a full
* chunk.
*/
@Override
public void handleUpdateTag(BlockState state, CompoundNBT tag) {
final PacketBuffer stream = new PacketBuffer(Unpooled.copiedBuffer(tag.getByteArray("X")));
if (this.readUpdateData(stream)) {
this.markForUpdate();
}
}
protected boolean readFromStream(final PacketBuffer data) throws IOException {
if (this.canBeRotated()) {
final Direction old_Forward = this.forward;
final Direction old_Up = this.up;
final byte orientation = data.readByte();
this.forward = Direction.values()[orientation & 0x7];
this.up = Direction.values()[orientation >> 3];
return this.forward != old_Forward || this.up != old_Up;
}
return false;
}
protected void writeToStream(final PacketBuffer data) throws IOException {
if (this.canBeRotated()) {
final byte orientation = (byte) ((this.up.ordinal() << 3) | this.forward.ordinal());
data.writeByte(orientation);
}
}
public void markForUpdate() {
if (this.renderFragment > 0) {
this.renderFragment |= 1;
} else {
// TODO: Optimize Network Load
if (this.world != null) {
this.requestModelDataUpdate();
boolean alreadyUpdated = false;
// Let the block update it's own state with our internal state changes
BlockState currentState = getBlockState();
if (currentState.getBlock() instanceof AEBaseTileBlock) {
AEBaseTileBlock<?> tileBlock = (AEBaseTileBlock<?>) currentState.getBlock();
BlockState newState = tileBlock.getTileEntityBlockState(currentState, this);
if (currentState != newState) {
AELog.blockUpdate(this.pos, currentState, newState, this);
this.world.setBlockState(pos, newState);
alreadyUpdated = true;
}
}
if (!alreadyUpdated) {
this.world.notifyBlockUpdate(this.pos, currentState, currentState, 1);
}
}
}
}
/**
* By default all blocks can have orientation, this handles saving, and loading,
* as well as synchronization.
*
* @return true if tile can be rotated
*/
@Override
public boolean canBeRotated() {
return true;
}
@Override
public Direction getForward() {
return this.forward;
}
@Override
public Direction getUp() {
return this.up;
}
@Override
public void setOrientation(final Direction inForward, final Direction inUp) {
this.forward = inForward;
this.up = inUp;
this.markForUpdate();
Platform.notifyBlocksOfNeighbors(this.world, this.pos);
this.saveChanges();
}
public void onPlacement(BlockItemUseContext context) {
ItemStack stack = context.getItem();
if (stack.hasTag()) {
this.uploadSettings(SettingsFrom.DISMANTLE_ITEM, stack.getTag());
}
}
/**
* depending on the from, different settings will be accepted, don't call this
* with null
*
* @param from source of settings
* @param compound compound of source
*/
public void uploadSettings(final SettingsFrom from, final CompoundNBT compound) {
if (this instanceof IConfigurableObject) {
final IConfigManager cm = ((IConfigurableObject) this).getConfigManager();
if (cm != null) {
cm.readFromNBT(compound);
}
}
if (this instanceof IPriorityHost) {
final IPriorityHost pHost = (IPriorityHost) this;
pHost.setPriority(compound.getInt("priority"));
}
if (this instanceof ISegmentedInventory) {
final IItemHandler inv = ((ISegmentedInventory) this).getInventoryByName("config");
if (inv instanceof AppEngInternalAEInventory) {
final AppEngInternalAEInventory target = (AppEngInternalAEInventory) inv;
final AppEngInternalAEInventory tmp = new AppEngInternalAEInventory(null, target.getSlots());
tmp.readFromNBT(compound, "config");
for (int x = 0; x < tmp.getSlots(); x++) {
target.setStackInSlot(x, tmp.getStackInSlot(x));
}
}
}
}
/**
* returns the contents of the tile entity, into the world, defaults to dropping
* everything in the inventory.
*
* @param w world
* @param pos block position
* @param drops drops of tile entity
*/
@Override
public void getDrops(final World w, final BlockPos pos, final List<ItemStack> drops) {
}
public void getNoDrops(final World w, final BlockPos pos, final List<ItemStack> drops) {
}
/**
* null means nothing to store...
*
* @param from source of settings
*
* @return compound of source
*/
public CompoundNBT downloadSettings(final SettingsFrom from) {
final CompoundNBT output = new CompoundNBT();
if (this.hasCustomInventoryName()) {
final CompoundNBT dsp = new CompoundNBT();
dsp.putString("Name", this.customName);
output.put("display", dsp);
}
if (this instanceof IConfigurableObject) {
final IConfigManager cm = ((IConfigurableObject) this).getConfigManager();
if (cm != null) {
cm.writeToNBT(output);
}
}
if (this instanceof IPriorityHost) {
final IPriorityHost pHost = (IPriorityHost) this;
output.putInt("priority", pHost.getPriority());
}
if (this instanceof ISegmentedInventory) {
final IItemHandler inv = ((ISegmentedInventory) this).getInventoryByName("config");
if (inv instanceof AppEngInternalAEInventory) {
((AppEngInternalAEInventory) inv).writeToNBT(output, "config");
}
}
return output.isEmpty() ? null : output;
}
@Override
public ITextComponent getCustomInventoryName() {
return new StringTextComponent(
this.hasCustomInventoryName() ? this.customName : this.getClass().getSimpleName());
}
@Override
public boolean hasCustomInventoryName() {
return this.customName != null && !this.customName.isEmpty();
}
public void securityBreak() {
this.world.destroyBlock(this.pos, true);
this.disableDrops();
}
/**
* Checks if this tile entity is remote (we are running on the logical client
* side).
*/
public boolean isRemote() {
World world = getWorld();
return world == null || world.isRemote();
}
public void disableDrops() {
DROP_NO_ITEMS.set(new WeakReference<>(this));
}
public void saveChanges() {
if (this.world != null) {
this.world.markChunkDirty(this.pos, this);
if (!this.markDirtyQueued) {
TickHandler.instance().addCallable(null, this::markDirtyAtEndOfTick);
this.markDirtyQueued = true;
}
}
}
private Object markDirtyAtEndOfTick(final World w) {
this.markDirty();
this.markDirtyQueued = false;
return null;
}
public void setName(final String name) {
this.customName = name;
}
@Nonnull
@Override
public IModelData getModelData() {
return new AEModelData(up, forward);
}
}