Files
ForgeMultipart/src/main/scala/codechicken/multipart/TileMultipart.java
T
2026-09-08 22:58:56 +02:00

1021 lines
41 KiB
Java

package codechicken.multipart;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Vec3;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import codechicken.lib.data.MCDataOutput;
import codechicken.lib.packet.PacketCustom;
import codechicken.lib.raytracer.ExtendedMOP;
import codechicken.lib.vec.BlockCoord;
import codechicken.lib.vec.Cuboid6;
import codechicken.lib.vec.Vector3;
import codechicken.lib.world.IChunkLoadTile;
import codechicken.multipart.handler.MultipartCompatiblity;
import codechicken.multipart.handler.MultipartProxy;
import codechicken.multipart.handler.MultipartSPH;
import scala.Function1;
import scala.Tuple2;
import scala.collection.Iterator;
import scala.collection.JavaConversions;
import scala.collection.Seq;
import scala.runtime.AbstractFunction1;
import scala.runtime.BoxedUnit;
public class TileMultipart extends TileEntity implements IChunkLoadTile {
/** List of parts in this tile space. */
private Seq<TMultiPart> partList = emptyParts();
private boolean doesTick = false;
/**
* @deprecated Use {@link #jPartList()} for Java collection access. Retained as the virtual storage accessor for
* existing subclasses and binaries.
*/
@Deprecated
public Seq<TMultiPart> partList() {
return partList;
}
/**
* @deprecated Use {@link #setPartList(List)} for Java storage assignment. This legacy hook retains the exact
* supplied sequence, including mutable storage or null; existing overrides remain supported.
*/
@Deprecated
public void partList_$eq(Seq<TMultiPart> parts) {
partList = parts;
}
/**
* Assigns a shallow, immutable copy of the supplied list through the legacy {@link #partList_$eq(Seq)} hook.
* Preserves order, duplicate entries and part identities. Later list edits do not affect the stored sequence.
*
* <p>
* This is low-level storage assignment for reconstruction. It does not bind parts, rebuild trait caches, change
* ticking or send notifications. Use {@link #loadPartList(Collection)} to load a correctly prepared composite tile;
* use {@link #addPart(World, BlockCoord, TMultiPart)} / {@link #remPart(TMultiPart)} for normal world changes.
* Existing setter overrides can customize storage. Internal writes still call the legacy hook directly.
*
* @param parts list to copy; null preserves the legacy unset-state sentinel, and null entries are not validated
*/
public void setPartList(List<TMultiPart> parts) {
partList_$eq(parts == null ? null : toSeq(parts));
}
/**
* Internal FMP composite-transition hook: copies trait state, then rebinds the stored parts. Not a consumer entry
* point.
*/
public void from(TileMultipart that) {
copyFrom(that);
loadFrom(that);
}
/**
* Internal FMP composite-transition hook; do not call directly from consumers.
*
* This method should be used for copying all the data from the fields in that container tile. This method will be
* automatically generated on java tile traits with fields if it is not overridden.
*/
public void copyFrom(TileMultipart that) {
partList_$eq(that.partList());
doesTick = that.doesTick;
}
/**
* Internal FMP composite-transition hook: rebinds this tile's stored parts and restores ticking after state copy.
* For consumer reconstruction, use {@link #loadPartList(Collection)} on a prepared composite instead.
*/
public void loadFrom(TileMultipart that) {
Iterator<TMultiPart> iterator = partList().iterator();
while (iterator.hasNext()) {
iterator.next().bind(this);
}
if (doesTick) {
doesTick = false;
setTicking(true);
}
}
/** Overidden in TSlottedTile when a part that goes in a slot is added. */
public TMultiPart partMap(int slot) {
return null;
}
/**
* Returns an ordered Java view of the sequence returned by {@link #partList()} at this call. Includes detached
* parts; replacing the tile's sequence does not update an existing view. Normally the sequence is immutable, but a
* mutable sequence supplied through the legacy setter remains live through this view.
*
* <p>
* Treat the view as read-only: list edits do not perform multipart binding, notifications or synchronization. Use
* {@link #addPart(World, BlockCoord, TMultiPart)} and {@link #remPart(TMultiPart)} for world changes. Copy into an
* {@link ArrayList} when independent list storage is needed; the parts themselves remain shared.
*
* @return a view of the captured sequence, with its original order and part identities
*/
public List<TMultiPart> jPartList() {
return JavaConversions.seqAsJavaList(partList());
}
/**
* Visits parts in sequence order, skipping those whose {@link TMultiPart#tile()} is null when reached. Delegates
* through the legacy {@link #operate(Function1)} hook, so existing overrides still control traversal.
*
* <p>
* The default hook captures {@link #partList()} once. Normal additions publish a new sequence and are not visited
* by the current call; parts detached by an earlier callback are skipped. A part rebound to another tile is still
* visited. Legacy mutable sequences retain their own iterator behavior and must not be structurally edited during
* traversal. Each nested call captures its own sequence. Callback exceptions propagate immediately, stopping the
* current traversal.
*
* <p>
* Call on the game thread. Lifecycle callbacks continue to dispatch through {@code operate}, not through overrides
* of this convenience method. With the default hook, a null callback fails only when a bound part is visited.
*
* @param consumer action to apply to each visited part
*/
public void forEachPart(Consumer<TMultiPart> consumer) {
operate(action(consumer));
}
@Override
public boolean canUpdate() {
return doesTick;
}
/**
* Legacy traversal hook, also used by multipart lifecycle callbacks.
*
* @deprecated Call {@link #forEachPart(Consumer)} from Java. Existing overrides remain supported and intercept both
* that API and lifecycle callbacks; keep such overrides here while this compatibility hook is retained.
*/
@Deprecated
// Direct list traversal avoids measured iterator/wrapper allocations; the setter also accepts other Seq types.
@SuppressWarnings("unchecked")
public void operate(Function1<TMultiPart, BoxedUnit> f) {
Seq<TMultiPart> current = partList();
if (!(current instanceof scala.collection.immutable.List)) {
scala.collection.Iterator<TMultiPart> iterator = current.iterator();
while (iterator.hasNext()) {
applyIfBound(f, iterator.next());
}
return;
}
scala.collection.immutable.List<TMultiPart> list = (scala.collection.immutable.List<TMultiPart>) current;
while (!list.isEmpty()) {
TMultiPart p = list.head();
list = (scala.collection.immutable.List<TMultiPart>) list.tail();
applyIfBound(f, p);
}
}
private static void applyIfBound(Function1<TMultiPart, BoxedUnit> f, TMultiPart p) {
if (p.tile() != null) {
f.apply(p);
}
}
@Override
public void updateEntity() {
operate(action(TMultiPart::update));
}
@Override
public void onChunkUnload() {
operate(action(TMultiPart::onChunkUnload));
}
@Override
public void onChunkLoad() {
operate(action(TMultiPart::onChunkLoad));
}
/** Internal FMP validity update that bypasses multipart lifecycle callbacks. Not a consumer entry point. */
public final void setValid(boolean b) {
if (b) {
super.validate();
} else {
super.invalidate();
}
}
public void onMoved() {
operate(action(TMultiPart::onMoved));
}
@Override
public void invalidate() {
if (!isInvalid()) {
super.invalidate();
if (worldObj != null) {
Iterator<TMultiPart> iterator = partList().iterator();
while (iterator.hasNext()) {
iterator.next().onWorldSeparate();
}
if (worldObj.isRemote) {
TileCache.remove(this);
}
}
}
}
@Override
public void validate() {
super.validate();
if (worldObj != null && worldObj.isRemote) {
TileCache.add(this);
}
}
/**
* Called by parts when they have changed in some form that affects the world. Notifies neighbor blocks, the world
* and parts that share this host and recalculates lighting.
*/
public void notifyPartChange(TMultiPart part) {
internalPartChange(part);
worldObj.markBlockForUpdate(xCoord, yCoord, zCoord);
worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord, MultipartProxy.block());
worldObj.func_147451_t(xCoord, yCoord, zCoord);
}
/**
* Supported consumer API for local part-change notifications, used by ProjectRed. Calls
* {@link TMultiPart#onPartChanged(TMultiPart)} through the retained {@link #operate(Function1)} hook. Its base
* traversal captures the current list/order and skips parts whose tile binding is null at callback time; a non-null
* binding to another tile still qualifies. Parts equal to {@code part} (using {@code part.equals(p)}) are excluded;
* null broadcasts to all eligible parts. Callback failures propagate and stop traversal.
*
* This method does not mark the tile dirty, send updates or notify neighboring blocks/lighting. Keep the caller's
* synchronization and external-notification policy, or use {@link #notifyPartChange(TMultiPart)} when world
* notifications are needed as well.
*/
public void internalPartChange(TMultiPart part) {
operate(action(p -> {
if (part == null ? p != null : !part.equals(p)) {
p.onPartChanged(part);
}
}));
}
/** Notifies all parts not in the passed collection of a change from all the parts in the collection. */
public void multiPartChange(Collection<TMultiPart> parts) {
operate(action(p -> {
if (!parts.contains(p)) {
for (TMultiPart changed : parts) {
p.onPartChanged(changed);
}
}
}));
}
/** Notifies neighboring blocks that this tile has changed. */
public void notifyTileChange() {
worldObj.func_147453_f(xCoord, yCoord, zCoord, MultipartProxy.block());
}
public void onNeighborBlockChange() {
operate(action(TMultiPart::onNeighborChanged));
}
/** Blank implementation, overriden by TTileChangeTile. */
public void onNeighborTileChange(int tileX, int tileY, int tileZ) {}
/** Blank implementation, overriden by TTileChangeTile. */
public boolean getWeakChanges() {
return false;
}
@SuppressWarnings("unchecked")
public int getLightValue() {
if (partList().isEmpty()) {
return 0;
}
int max = 0;
Seq<TMultiPart> current = partList();
if (!(current instanceof scala.collection.immutable.List)) {
Iterator<TMultiPart> iterator = current.iterator();
while (iterator.hasNext()) {
max = Math.max(max, iterator.next().getLightValue());
}
return max;
}
scala.collection.immutable.List<TMultiPart> list = (scala.collection.immutable.List<TMultiPart>) current;
while (!list.isEmpty()) {
max = Math.max(max, list.head().getLightValue());
list = (scala.collection.immutable.List<TMultiPart>) list.tail();
}
return max;
}
public float getExplosionResistance(Entity entity) {
Seq<TMultiPart> current = partList();
if (current.isEmpty()) {
// Matches the reference, where max on an empty view throws.
throw new UnsupportedOperationException("empty.max");
}
float max = Float.NEGATIVE_INFINITY;
Iterator<TMultiPart> iterator = current.iterator();
while (iterator.hasNext()) {
max = Math.max(max, iterator.next().explosionResistance(entity));
}
return max;
}
/** Callback for parts to mark the chunk as needs saving. */
@Override
public void markDirty() {
worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this);
}
/** Mark this block space for a render update. */
public void markRender() {
worldObj.func_147479_m(xCoord, yCoord, zCoord);
}
/** Stable class-call target for the flag getter generated by the TileMultipartClient runtime interface. */
public boolean hasDynamicParts() {
return false;
}
/** Client render hook overridden by generated tiles carrying TileMultipartClient. */
public boolean renderStatic(IBlockAccess world, Vector3 position, RenderBlocks renderer) {
return false;
}
/** Client render hook overridden by generated tiles carrying TileMultipartClient. */
public void renderDynamic(Vector3 position, float frame, int pass) {}
/** Client particle hook overridden by generated tiles carrying TRandomDisplayTickTile. */
public void randomDisplayTick(Random random) {}
/** Helper function for calling a second level notify on a side (eg indirect power from a lever). */
public void notifyNeighborChange(int side) {
BlockCoord pos = new BlockCoord(this).offset(side);
worldObj.notifyBlocksOfNeighborChange(pos.x, pos.y, pos.z, MultipartProxy.block());
}
public boolean isSolid(int side) {
TMultiPart part = partMap(side);
return part instanceof TFacePart && ((TFacePart) part).solid(side);
}
public boolean canPlaceTorchOnTop() {
Iterator<TMultiPart> iterator = partList().iterator();
while (iterator.hasNext()) {
if (iterator.next().canPlaceTorchOnTop()) {
return true;
}
}
return isSolid(1);
}
private void setTicking(boolean tick) {
if (doesTick == tick) {
return;
}
doesTick = tick;
if (worldObj != null && worldObj.getTileEntity(xCoord, yCoord, zCoord) == this) {
if (tick) {
worldObj.addTileEntity(this);
} else {
worldObj.func_147457_a(this);
}
}
}
/** Returns true if part can be added to this space. */
public boolean canAddPart(TMultiPart part) {
return compatibilityAllows(worldObj, xCoord, yCoord, zCoord) && !partList().contains(part)
&& occlusionTest(partList(), part);
}
/**
* Returns true if opart can be replaced with npart (note opart and npart may be the exact same object).
* <p>
* This function should be used for testing if a part can change it's shape (eg. rotation, expansion, cable
* connection). For example, to test whether a cable part can connect to it's neighbor: set the cable part's
* bounding boxes as if the connection is established, call canReplacePart(part, part), and if it succeeds perform
* the connection, else revert the bounding box.
*/
public boolean canReplacePart(TMultiPart opart, TMultiPart npart) {
List<TMultiPart> olist = new ArrayList<>();
Iterator<TMultiPart> iterator = partList().iterator();
while (iterator.hasNext()) {
TMultiPart part = iterator.next();
if (part == null ? opart != null : !part.equals(opart)) {
olist.add(part);
}
}
Seq<TMultiPart> others = toSeq(olist);
if (others.contains(npart)) {
return false;
}
return occlusionTest(others, npart);
}
/**
* Tests a candidate against a shallow snapshot of the supplied parts through the legacy
* {@link #occlusionTest(Seq, TMultiPart)} hook, including generated partial-occlusion overrides. Preserves
* collection iteration order, duplicates and part identities; changes to the caller's collection during callbacks
* do not change this query's sequence.
*
* <p>
* The default hook calls each existing part's occlusion test first, then the candidate's reverse test, stopping
* immediately on rejection or an exception. It includes detached parts and does not exclude the candidate itself.
* Part objects remain shared, so callback changes to their state are visible. Existing overrides may add checks.
*
* <p>
* This tests the supplied geometry, not full placement validity: it does not select tile capabilities, check
* placement permissions or slot occupancy, bind parts or change stored parts. Use the appropriate composite tile
* for the candidate's capabilities; use {@link #canAddPart(TMultiPart)} /
* {@link #canReplacePart(TMultiPart, TMultiPart)} for normal tile checks. Those methods still dispatch directly
* through the legacy hook.
*
* @param parts parts to copy before dispatch; null fails before calling the legacy hook
* @param candidate part to test; no eager null check, so the default hook accepts null against an empty collection
* @return true when all applicable occlusion checks allow the candidate
*/
public boolean testOcclusion(Collection<? extends TMultiPart> parts, TMultiPart candidate) {
return occlusionTest(toSeq(new ArrayList<>(parts)), candidate);
}
/**
* Returns true if parts do not occlude npart.
*
* @deprecated Call {@link #testOcclusion(Collection, TMultiPart)} from Java. Retained as the virtual hook for
* existing subclasses, generated traits and tile placement/replacement checks; its sequence behavior is
* unchanged.
*/
@Deprecated
public boolean occlusionTest(Seq<TMultiPart> parts, TMultiPart npart) {
for (TMultiPart part : JavaConversions.seqAsJavaList(parts)) {
if (!part.occlusionTest(npart) || !npart.occlusionTest(part)) {
return false;
}
}
return true;
}
/** Get the write stream for updates to part. */
public MCDataOutput getWriteStream(TMultiPart part) {
return writeStream().writeByte(partList().indexOf(part));
}
private MCDataOutput writeStream() {
return MultipartSPH.getTileStream(worldObj, new BlockCoord(this));
}
/**
* Internal FMP placement lifecycle and synchronization hook. Consumers use
* {@link #addPart(World, BlockCoord, TMultiPart)} so composite selection and world installation also run.
*/
public void addPart_impl(TMultiPart part) {
if (!worldObj.isRemote) {
writeAddPart(part);
}
addPart_do(part);
part.onAdded();
partAdded(part);
notifyPartChange(part);
notifyTileChange();
markDirty();
markRender();
}
/** Internal FMP add-part packet writer; not a consumer entry point. Use the public placement API. */
public void writeAddPart(TMultiPart part) {
MCDataOutput stream = writeStream().writeByte(253);
MultiPartRegistry.writePartID(stream, part);
part.writeDesc(stream);
}
/**
* Internal FMP storage/cache/binding step, without the complete placement lifecycle. Consumers use
* {@link #addPart(World, BlockCoord, TMultiPart)} or {@link #loadPartList(Collection)} for prepared reconstruction.
*/
public void addPart_do(TMultiPart part) {
if (partList().size() >= 250) {
throw new AssertionError(
"assertion failed: Tried to add more than 250 parts to the one tile. You're doing it wrong");
}
List<TMultiPart> next = mutablePartsSnapshot();
next.add(part);
partList_$eq(toSeq(next));
bindPart(part);
part.bind(this);
if (!doesTick && part.doesTick()) {
setTicking(true);
}
}
/**
* Supported advanced consumer API and trait override hook for adding a part to this tile's capability caches. The
* base implementation is empty; generated traits supply cache behavior. Does not insert the part into the part
* list, change {@link TMultiPart#tile()}, run placement callbacks or send notifications.
*
* OpenComputers uses this to refresh a changed slot mask after clearing that part's old slot entries. Rebinding
* alone does not clear obsolete slots, and other traits may append cache entries on every call; this is not a
* general-purpose idempotent refresh. Only use it with known capability/cache semantics. For placement use
* {@link #addPart(World, BlockCoord, TMultiPart)}; for full reconstruction use {@link #loadPartList(Collection)}.
*/
public void bindPart(TMultiPart part) {}
/**
* Refreshes the generated slot cache after an already stored part changes its {@link TSlottedPart#getSlotMask()}.
* Generated slotted tiles remove every cached entry equal to {@code part}, then dispatch
* {@link #bindPart(TMultiPart)} once to add its current slots. A tile without the slotted capability does nothing.
*
* <p>
* This does not validate placement, insert or bind the part, change the stored list, send notifications, mark the
* tile dirty/renderable or schedule updates. Call only after the part has validated its new shape and updated its
* mask, on the owning world thread. The part normally remains stored and bound to this tile; no eager validation is
* added. Other generated bind hooks also run, so callers must retain their existing cache-specific assumptions and
* perform their own notifications afterward.
*/
public void refreshPartSlots(TMultiPart part) {}
/** Internal FMP placement callback for trait overrides; do not call directly from consumers. */
public void partAdded(TMultiPart part) {}
/**
* Removes part from this tile. Note that due to the operation sync, the part may not be removed until the call
* stack has been passed to all other parts in the space.
*/
public TileMultipart remPart(TMultiPart part) {
if (worldObj.isRemote) {
throw new AssertionError("assertion failed: Cannot remove multi parts from a client tile");
}
return remPart_impl(part);
}
/** Internal FMP removal/synchronization hook. Consumers use {@link #remPart(TMultiPart)} on the server. */
public TileMultipart remPart_impl(TMultiPart part) {
remPart_do(part, !worldObj.isRemote);
if (!isInvalid()) {
TileMultipart tile = MultipartGenerator$.MODULE$.partRemoved(this);
tile.notifyPartChange(part);
tile.markDirty();
tile.markRender();
return tile;
}
return null;
}
private int remPart_do(TMultiPart part, boolean sendPacket) {
int r = partList().indexOf(part);
if (r < 0) {
throw new IllegalArgumentException("Tried to remove a non-existant part");
}
// The reference read an unused removedLightValue here; the virtual call is observable, so keep it.
part.getLightValue();
part.preRemove();
List<TMultiPart> current = mutablePartsSnapshot();
current.removeIf(p -> p == null ? part == null : p.equals(part));
partList_$eq(toSeq(current));
if (sendPacket) {
writeStream().writeByte(254).writeByte(r);
}
partRemoved(part, r);
part.onRemoved();
part.tile_$eq(null);
if (partList().isEmpty()) {
worldObj.setBlockToAir(xCoord, yCoord, zCoord);
} else if (part.doesTick() && doesTick) {
boolean ntick = false;
Iterator<TMultiPart> iterator = partList().iterator();
while (iterator.hasNext()) {
ntick |= iterator.next().doesTick();
}
if (!ntick) {
setTicking(false);
}
}
return r;
}
/** Internal FMP cache-removal callback for trait overrides; do not call directly from consumers. */
public void partRemoved(TMultiPart part, int p) {}
/**
* Loads parts into an already prepared composite tile through the legacy
* {@link #loadParts(scala.collection.Iterable)} hook. Clears trait caches, then stores and binds each input part in
* collection iteration order. The collection is viewed during the call, not copied in advance; do not structurally
* modify it from callbacks.
*
* <p>
* With a world, the default hook calls {@code onWorldJoin} on the client and then
* {@link #notifyPartChange(TMultiPart)} with null on either side. Without a world it only loads/binds. It does not
* generate capabilities, remove/unbind old parts, call {@code onAdded}, reset the existing ticking flag or send a
* complete description packet. Use {@link MultipartHelper#createTileFromParts(Iterable)} when constructing a new
* server tile from parts.
*
* <p>
* Call on the game thread. Loading is not atomic: failures propagate with cleared or partially loaded state. The
* default hook clears before reading the collection, so a null collection also fails after clearing. Existing
* legacy overrides still control loading; internal reconstruction continues to call that hook directly.
*
* @param parts parts to load; iteration order, duplicates and callback failure behavior are retained
*/
public void loadPartList(Collection<TMultiPart> parts) {
loadParts(JavaConversions.iterableAsScalaIterable(parts));
}
/**
* @deprecated Call {@link #loadPartList(Collection)} from Java. Retained for legacy overrides, compiled callers and
* exact Scala-parameter reflection; behavior is unchanged.
*/
@Deprecated
public void loadParts(scala.collection.Iterable<TMultiPart> parts) {
clearParts();
for (TMultiPart p : JavaConversions.asJavaIterable(parts)) {
addPart_do(p);
}
if (worldObj != null) {
if (worldObj.isRemote) {
operate(action(TMultiPart::onWorldJoin));
}
notifyPartChange(null);
}
}
/** Remove all parts from internal cache. Provided for trait overrides, do not call externally. */
public void clearParts() {
partList_$eq(emptyParts());
}
/** Writes the description of this tile, and all parts composing it, to packet. */
public void writeDesc(MCDataOutput packet) {
packet.writeByte(partList().size());
Iterator<TMultiPart> iterator = partList().iterator();
while (iterator.hasNext()) {
TMultiPart part = iterator.next();
MultiPartRegistry.writePartID(packet, part);
part.writeDesc(packet);
}
}
/** Perform a raytrace returning all intersecting parts sorted nearest to farthest. */
public Iterable<ExtendedMOP> rayTraceAll(Vec3 start, Vec3 end) {
List<ExtendedMOP> list = new ArrayList<>();
Iterator<TMultiPart> iterator = partList().iterator();
int i = 0;
while (iterator.hasNext()) {
Object mop = iterator.next().collisionRayTrace(start, end);
if (mop instanceof ExtendedMOP) {
ExtendedMOP extended = (ExtendedMOP) mop;
extended.data = new Tuple2<>(i, extended.data);
list.add(extended);
}
i++;
}
Collections.sort(list);
return list;
}
/** Perform a raytrace returning the nearest intersecting part. */
public ExtendedMOP collisionRayTrace(Vec3 start, Vec3 end) {
for (ExtendedMOP mop : rayTraceAll(start, end)) {
return mop;
}
return null;
}
/** Drop and remove part at index (internal mining callback). */
public void harvestPart(int index, ExtendedMOP hit, EntityPlayer player) {
TMultiPart part = partList().apply(index);
if (part != null) {
part.harvest(hit, player);
}
}
/** Utility function for dropping items around the center of this space. */
public void dropItems(Iterable<ItemStack> items) {
Vector3 pos = Vector3.fromTileEntityCenter(this);
for (ItemStack item : items) {
dropItem(item, worldObj, pos);
}
}
@Override
public final void writeToNBT(NBTTagCompound tag) {
super.writeToNBT(tag);
NBTTagList taglist = new NBTTagList();
Iterator<TMultiPart> iterator = partList().iterator();
while (iterator.hasNext()) {
TMultiPart part = iterator.next();
NBTTagCompound parttag = new NBTTagCompound();
parttag.setString("id", part.getType());
part.save(parttag);
taglist.appendTag(parttag);
}
tag.setTag("parts", taglist);
}
/** Internal callback. */
public void onEntityCollision(Entity entity) {
operate(action(p -> p.onEntityCollision(entity)));
}
/** Internal callback, overriden in TRedstoneTile. */
public int strongPowerLevel(int side) {
return 0;
}
/** Internal callback, overriden in TRedstoneTile. */
public int weakPowerLevel(int side) {
return 0;
}
/** Internal callback, overriden in TRedstoneTile. */
public boolean canConnectRedstone(int side) {
return false;
}
/** Mutable snapshot used only while publishing a replacement Seq. */
private List<TMultiPart> mutablePartsSnapshot() {
return new ArrayList<>(JavaConversions.seqAsJavaList(partList()));
}
private static Seq<TMultiPart> toSeq(List<TMultiPart> parts) {
return JavaConversions.asScalaBuffer(parts).toList();
}
private static Seq<TMultiPart> emptyParts() {
return toSeq(Collections.emptyList());
}
/** Wraps a Java action as the Scala function operate takes, so overrides of operate still see every call. */
private static Function1<TMultiPart, BoxedUnit> action(Consumer<TMultiPart> action) {
return new AbstractFunction1<TMultiPart, BoxedUnit>() {
@Override
public BoxedUnit apply(TMultiPart part) {
action.accept(part);
return BoxedUnit.UNIT;
}
};
}
@SuppressWarnings("unchecked")
private static boolean compatibilityAllows(World world, int x, int y, int z) {
return (Boolean) MultipartCompatiblity.canAddPart()
.apply(world, Integer.valueOf(x), Integer.valueOf(y), Integer.valueOf(z));
}
// Static helpers, formerly the TileMultipart companion object.
private static int renderID = -1;
/**
* Returns FMP's global block-render registration ID. Initially -1; reading it does not initialize the client
* renderer or allocate an ID. This is shared state, not a per-tile or persistent part ID.
*/
public static int getRenderID() {
return renderID();
}
/**
* Assigns the global render ID, preserving all integer values including -1. Intended for controlled render setup;
* this only changes the stored value and does not allocate an ID, register a handler or update Forge's mapping.
* FMP's client renderer still performs its own allocation when first initialized. Use on the initialization/game
* thread, not as a per-tile rendering control.
*/
public static void setRenderID(int value) {
renderID_$eq(value);
}
/** @deprecated Use {@link #getRenderID()}. Retained for existing static/companion callers. */
@Deprecated
public static int renderID() {
return renderID;
}
/** @deprecated Use {@link #setRenderID(int)}. Retained with unchanged shared-state behavior. */
@Deprecated
public static void renderID_$eq(int value) {
renderID = value;
}
/**
* Gets an existing tile or converted placeholder, or null. Conversion does not install the tile in the world. Use
* {@link #getOrConvertTileResult(World, BlockCoord)} when the conversion flag is also needed.
*/
public static TileMultipart getOrConvertTile(World world, BlockCoord pos) {
return getOrConvertTile2(world, pos)._1();
}
/**
* Looks up an existing tile, otherwise asks registered block converters for a multipart representation. An existing
* tile is returned by identity with {@link TileConversionResult#isConverted()} false. A successful conversion
* returns a fresh, uninstalled placeholder with the flag true; no match returns a null tile and false.
*
* <p>
* A placeholder uses the world's client/server side and has its coordinates, world and converted part bound. This
* lookup does not replace the block, install the tile or invoke the part's placement/conversion callbacks. Repeated
* lookups can construct different placeholders. Converter callbacks run normally and their exceptions propagate;
* this is not a side-effect-free query. Use on the appropriate world thread after FMP initialization.
*
* <p>
* For ordinary placement, use {@link #canPlacePart(World, BlockCoord, TMultiPart)} followed by
* {@link #addPart(World, BlockCoord, TMultiPart)} and retain the tile returned by the addition. Do not install this
* placeholder directly. Use {@link #getTile(World, BlockCoord)} if conversion should not be attempted.
*
* @return a non-null result recording this lookup; its tile reference may be null
*/
public static TileConversionResult getOrConvertTileResult(World world, BlockCoord pos) {
Tuple2<TileMultipart, Object> result = getOrConvertTile2(world, pos);
return new TileConversionResult(result._1(), (Boolean) result._2());
}
/**
* Gets a multipart tile instance at pos, converting if necessary. Note converted tiles are merely a structure
* formality, they do not actually exist in world until they are required to by the addition of another multipart to
* their space.
*
* @return the tile or null if there was none, and true if the tile is a result of a conversion
* @deprecated Use {@link #getOrConvertTileResult(World, BlockCoord)} for a Java result with named accessors.
*/
@Deprecated
public static Tuple2<TileMultipart, Object> getOrConvertTile2(World world, BlockCoord pos) {
TileEntity t = world.getTileEntity(pos.x, pos.y, pos.z);
if (t instanceof TileMultipart) {
return new Tuple2<>((TileMultipart) t, Boolean.FALSE);
}
TMultiPart p = MultiPartRegistry.convertBlock(world, pos, world.getBlock(pos.x, pos.y, pos.z));
if (p != null) {
TileMultipart tile = MultipartGenerator$.MODULE$
.generateCompositeTile(null, toSeq(Collections.singletonList(p)), world.isRemote);
tile.xCoord = pos.x;
tile.yCoord = pos.y;
tile.zCoord = pos.z;
tile.setWorldObj(world);
tile.addPart_do(p);
return new Tuple2<>(tile, Boolean.TRUE);
}
return new Tuple2<>(null, Boolean.FALSE);
}
/** Gets the multipart tile instance at pos, or null if it doesn't exist or is not a multipart tile. */
public static TileMultipart getTile(World world, BlockCoord pos) {
TileEntity t = world.getTileEntity(pos.x, pos.y, pos.z);
return t instanceof TileMultipart ? (TileMultipart) t : null;
}
public static boolean checkNoEntityCollision(World world, BlockCoord pos, TMultiPart part) {
for (Cuboid6 b : part.getCollisionBoxes()) {
if (!world.checkNoEntityCollision(b.toAABB().offset(pos.x, pos.y, pos.z))) {
return false;
}
}
return true;
}
/**
* Returns whether part can be added to the space at pos. Will do conversions as necessary. This function is the
* recommended way to add parts to the world.
*/
public static boolean canPlacePart(World world, BlockCoord pos, TMultiPart part) {
if (!checkNoEntityCollision(world, pos, part)) {
return false;
}
TileMultipart t = getOrConvertTile(world, pos);
if (t != null) {
return t.canAddPart(part);
} else if (!compatibilityAllows(world, pos.x, pos.y, pos.z)) {
return false;
}
return replaceable(world, pos);
}
/** Returns if the block at pos is replaceable (air, vines etc). */
public static boolean replaceable(World world, BlockCoord pos) {
net.minecraft.block.Block block = world.getBlock(pos.x, pos.y, pos.z);
return block.isAir(world, pos.x, pos.y, pos.z) || block.isReplaceable(world, pos.x, pos.y, pos.z);
}
/**
* Adds a part to a block space. canPlacePart should always be called first. The addition of parts on the client is
* handled internally.
*/
public static TileMultipart addPart(World world, BlockCoord pos, TMultiPart part) {
if (world.isRemote) {
throw new AssertionError("assertion failed: Cannot add multi parts to a client tile.");
}
return MultipartGenerator$.MODULE$.addPart(world, pos, part);
}
/** Constructs this tile and its parts from a desc packet. */
public static void handleDescPacket(World world, BlockCoord pos, PacketCustom packet) {
int nparts = packet.readUByte();
List<TMultiPart> parts = new ArrayList<>();
for (int i = 0; i < nparts; i++) {
TMultiPart part = MultiPartRegistry.readPart(packet);
part.readDesc(packet);
parts.add(part);
}
if (parts.isEmpty()) {
return;
}
TileEntity t = world.getTileEntity(pos.x, pos.y, pos.z);
TileMultipart tilemp = MultipartGenerator$.MODULE$.generateCompositeTile(t, toSeq(parts), true);
if (tilemp != t) {
world.setBlock(pos.x, pos.y, pos.z, MultipartProxy.block());
MultipartGenerator.silentAddTile(world, pos, tilemp);
}
tilemp.loadParts(toSeq(parts));
tilemp.notifyTileChange();
tilemp.markRender();
}
/** Handles an update packet, addition, removal and otherwise. */
public static void handlePacket(BlockCoord pos, World world, int i, PacketCustom packet) {
if (i == 253) {
TMultiPart part = MultiPartRegistry.readPart(packet);
part.readDesc(packet);
MultipartGenerator$.MODULE$.addPart(world, pos, part);
} else if (i == 254) {
// The reference resolves the tile separately for the receiver and the argument.
TileMultipart receiver = TileCache.findTile(world, pos);
TMultiPart removed = TileCache.findTile(world, pos).partList().apply(packet.readUByte());
receiver.remPart_impl(removed);
} else {
TileCache.findTile(world, pos).partList().apply(i).read(packet);
}
}
/** Creates this tile from an NBT tag. */
public static TileMultipart createFromNBT(NBTTagCompound tag) {
NBTTagList partList = tag.getTagList("parts", 10);
List<TMultiPart> parts = new ArrayList<>();
for (int i = 0; i < partList.tagCount(); i++) {
NBTTagCompound partTag = partList.getCompoundTagAt(i);
String partID = partTag.getString("id");
TMultiPart part = MultiPartRegistry.loadPart(partID, partTag);
if (part != null) {
part.load(partTag);
parts.add(part);
}
}
if (parts.isEmpty()) {
return null;
}
TileMultipart tmb = MultipartGenerator$.MODULE$.generateCompositeTile(null, toSeq(parts), false);
tmb.readFromNBT(tag);
tmb.loadParts(toSeq(parts));
return tmb;
}
/** Drops an item around pos. */
public static void dropItem(ItemStack stack, World world, Vector3 pos) {
EntityItem item = new EntityItem(world, pos.x, pos.y, pos.z, stack);
item.motionX = world.rand.nextGaussian() * 0.05;
item.motionY = world.rand.nextGaussian() * 0.05 + 0.2;
item.motionZ = world.rand.nextGaussian() * 0.05;
item.delayBeforeCanPickup = 10;
world.spawnEntityInWorld(item);
}
}