Merge pull request #3 from Creators-of-Create/jay/mc1.20.1/mounted-storage-refactor

Mounted Storage Refactor
This commit is contained in:
simibubi 2025-01-26 10:15:40 +01:00 committed by GitHub
commit f83b8523a6
Failed to generate hash of commit
87 changed files with 3352 additions and 1617 deletions

View file

@ -0,0 +1,6 @@
{
"values": [
"minecraft:chest",
"minecraft:trapped_chest"
]
}

View file

@ -0,0 +1,22 @@
{
"values": [
"minecraft:barrel",
"minecraft:shulker_box",
"minecraft:white_shulker_box",
"minecraft:orange_shulker_box",
"minecraft:magenta_shulker_box",
"minecraft:light_blue_shulker_box",
"minecraft:yellow_shulker_box",
"minecraft:lime_shulker_box",
"minecraft:pink_shulker_box",
"minecraft:gray_shulker_box",
"minecraft:light_gray_shulker_box",
"minecraft:cyan_shulker_box",
"minecraft:purple_shulker_box",
"minecraft:blue_shulker_box",
"minecraft:brown_shulker_box",
"minecraft:green_shulker_box",
"minecraft:red_shulker_box",
"minecraft:black_shulker_box"
]
}

View file

@ -3,6 +3,8 @@ package com.simibubi.create;
import static com.simibubi.create.AllInteractionBehaviours.interactionBehaviour;
import static com.simibubi.create.AllMovementBehaviours.movementBehaviour;
import static com.simibubi.create.Create.REGISTRATE;
import static com.simibubi.create.api.contraption.storage.MountedStorageTypeRegistry.mountedFluidStorage;
import static com.simibubi.create.api.contraption.storage.MountedStorageTypeRegistry.mountedItemStorage;
import static com.simibubi.create.content.redstone.displayLink.AllDisplayBehaviours.assignDataBehaviour;
import static com.simibubi.create.foundation.data.BlockStateGen.axisBlock;
import static com.simibubi.create.foundation.data.BlockStateGen.simpleCubeAll;
@ -105,6 +107,7 @@ import com.simibubi.create.content.fluids.tank.FluidTankBlock;
import com.simibubi.create.content.fluids.tank.FluidTankGenerator;
import com.simibubi.create.content.fluids.tank.FluidTankItem;
import com.simibubi.create.content.fluids.tank.FluidTankModel;
import com.simibubi.create.content.fluids.tank.FluidTankMovementBehavior;
import com.simibubi.create.content.kinetics.BlockStressDefaults;
import com.simibubi.create.content.kinetics.belt.BeltBlock;
import com.simibubi.create.content.kinetics.belt.BeltGenerator;
@ -329,6 +332,7 @@ import net.minecraft.world.level.storage.loot.predicates.ExplosionCondition;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.providers.nbt.ContextNbtProvider;
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
import net.minecraftforge.client.model.generators.ConfiguredModel;
import net.minecraftforge.client.model.generators.ModelFile;
import net.minecraftforge.common.Tags;
@ -796,6 +800,7 @@ public class AllBlocks {
.blockstate((c, p) -> p.simpleBlock(c.getEntry(), AssetLookup.partialBaseModel(c, p)))
.onRegister(assignDataBehaviour(new ItemNameDisplaySource(), "combine_item_names"))
.onRegister(interactionBehaviour(new MountedDepotInteractionBehaviour()))
.transform(mountedItemStorage(AllMountedStorageTypes.DEPOT))
.item()
.transform(customItemModel("_", "block"))
.register();
@ -994,6 +999,8 @@ public class AllBlocks {
.blockstate(new FluidTankGenerator()::generate)
.onRegister(CreateRegistrate.blockModel(() -> FluidTankModel::standard))
.onRegister(assignDataBehaviour(new BoilerDisplaySource(), "boiler_status"))
.transform(mountedFluidStorage(AllMountedStorageTypes.FLUID_TANK))
.onRegister(movementBehaviour(new FluidTankMovementBehavior()))
.addLayer(() -> RenderType::cutoutMipped)
.item(FluidTankItem::new)
.model(AssetLookup.customBlockItemModel("_", "block_single_window"))
@ -1009,6 +1016,7 @@ public class AllBlocks {
.tag(AllBlockTags.SAFE_NBT.tag)
.blockstate(new FluidTankGenerator("creative_")::generate)
.onRegister(CreateRegistrate.blockModel(() -> FluidTankModel::creative))
.transform(mountedFluidStorage(AllMountedStorageTypes.CREATIVE_FLUID_TANK))
.addLayer(() -> RenderType::cutoutMipped)
.item(FluidTankItem::new)
.properties(p -> p.rarity(Rarity.EPIC))
@ -1822,6 +1830,7 @@ public class AllBlocks {
REGISTRATE.block("creative_crate", CreativeCrateBlock::new)
.transform(BuilderTransformers.crate("creative"))
.properties(p -> p.mapColor(MapColor.COLOR_PURPLE))
.transform(mountedItemStorage(AllMountedStorageTypes.CREATIVE_CRATE))
.register();
public static final BlockEntry<ItemVaultBlock> ITEM_VAULT = REGISTRATE.block("item_vault", ItemVaultBlock::new)
@ -1836,6 +1845,7 @@ public class AllBlocks {
.rotationY(s.getValue(ItemVaultBlock.HORIZONTAL_AXIS) == Axis.X ? 90 : 0)
.build()))
.onRegister(connectedTextures(ItemVaultCTBehaviour::new))
.transform(mountedItemStorage(AllMountedStorageTypes.VAULT))
.item(ItemVaultItem::new)
.build()
.register();
@ -2242,6 +2252,7 @@ public class AllBlocks {
.texture("0", p.modLoc("block/toolbox/" + colourName)));
})
.onRegisterAfter(Registries.ITEM, v -> ItemDescription.useKey(v, "block.create.toolbox"))
.transform(mountedItemStorage(AllMountedStorageTypes.TOOLBOX))
.tag(AllBlockTags.TOOLBOXES.tag)
.item(UncontainableBlockItem::new)
.model((c, p) -> p.withExistingParent(colourName + "_toolbox", p.modLoc("block/toolbox/item"))

View file

@ -0,0 +1,57 @@
package com.simibubi.create;
import static com.simibubi.create.Create.REGISTRATE;
import java.util.function.Supplier;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageType;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.contraption.storage.item.chest.ChestMountedStorageType;
import com.simibubi.create.api.contraption.storage.item.simple.SimpleMountedStorageType;
import com.simibubi.create.content.contraptions.behaviour.dispenser.storage.DispenserMountedStorageType;
import com.simibubi.create.content.equipment.toolbox.ToolboxMountedStorageType;
import com.simibubi.create.content.fluids.tank.storage.FluidTankMountedStorageType;
import com.simibubi.create.content.fluids.tank.storage.creative.CreativeFluidTankMountedStorageType;
import com.simibubi.create.content.logistics.crate.CreativeCrateMountedStorageType;
import com.simibubi.create.content.logistics.depot.storage.DepotMountedStorageType;
import com.simibubi.create.content.logistics.vault.ItemVaultMountedStorageType;
import com.simibubi.create.impl.contraption.storage.FallbackMountedStorageType;
import com.tterrag.registrate.util.entry.RegistryEntry;
import net.minecraft.world.level.block.Blocks;
public class AllMountedStorageTypes {
// fallback is special, provider is registered on lookup creation so it's always last
public static final RegistryEntry<FallbackMountedStorageType> FALLBACK = simpleItem("fallback", FallbackMountedStorageType::new);
// registrations for these are handled by the blocks, not the types
public static final RegistryEntry<CreativeCrateMountedStorageType> CREATIVE_CRATE = simpleItem("creative_crate", CreativeCrateMountedStorageType::new);
public static final RegistryEntry<ItemVaultMountedStorageType> VAULT = simpleItem("vault", ItemVaultMountedStorageType::new);
public static final RegistryEntry<ToolboxMountedStorageType> TOOLBOX = simpleItem("toolbox", ToolboxMountedStorageType::new);
public static final RegistryEntry<DepotMountedStorageType> DEPOT = simpleItem("depot", DepotMountedStorageType::new);
public static final RegistryEntry<FluidTankMountedStorageType> FLUID_TANK = simpleFluid("fluid_tank", FluidTankMountedStorageType::new);
public static final RegistryEntry<CreativeFluidTankMountedStorageType> CREATIVE_FLUID_TANK = simpleFluid("creative_fluid_tank", CreativeFluidTankMountedStorageType::new);
// these are for external blocks, register associations here
public static final RegistryEntry<SimpleMountedStorageType.Impl> SIMPLE = REGISTRATE.mountedItemStorage("simple", SimpleMountedStorageType.Impl::new)
.registerTo(AllTags.AllBlockTags.SIMPLE_MOUNTED_STORAGE.tag)
.register();
public static final RegistryEntry<ChestMountedStorageType> CHEST = REGISTRATE.mountedItemStorage("chest", ChestMountedStorageType::new)
.registerTo(AllTags.AllBlockTags.CHEST_MOUNTED_STORAGE.tag)
.register();
public static final RegistryEntry<DispenserMountedStorageType> DISPENSER = REGISTRATE.mountedItemStorage("dispenser", DispenserMountedStorageType::new)
.registerTo(Blocks.DISPENSER)
.registerTo(Blocks.DROPPER)
.register();
private static <T extends MountedItemStorageType<?>> RegistryEntry<T> simpleItem(String name, Supplier<T> supplier) {
return REGISTRATE.mountedItemStorage(name, supplier).register();
}
private static <T extends MountedFluidStorageType<?>> RegistryEntry<T> simpleFluid(String name, Supplier<T> supplier) {
return REGISTRATE.mountedFluidStorage(name, supplier).register();
}
public static void register() {
}
}

View file

@ -16,6 +16,7 @@ import com.simibubi.create.content.contraptions.ContraptionColliderLockPacket.Co
import com.simibubi.create.content.contraptions.ContraptionDisassemblyPacket;
import com.simibubi.create.content.contraptions.ContraptionRelocationPacket;
import com.simibubi.create.content.contraptions.ContraptionStallPacket;
import com.simibubi.create.content.contraptions.MountedStorageSyncPacket;
import com.simibubi.create.content.contraptions.TrainCollisionPacket;
import com.simibubi.create.content.contraptions.actors.contraptionControls.ContraptionDisableActorPacket;
import com.simibubi.create.content.contraptions.actors.trainControls.ControlsInputPacket;
@ -30,9 +31,7 @@ import com.simibubi.create.content.contraptions.glue.SuperGlueSelectionPacket;
import com.simibubi.create.content.contraptions.minecart.CouplingCreationPacket;
import com.simibubi.create.content.contraptions.minecart.capability.MinecartControllerUpdatePacket;
import com.simibubi.create.content.contraptions.sync.ClientMotionPacket;
import com.simibubi.create.content.contraptions.sync.ContraptionFluidPacket;
import com.simibubi.create.content.contraptions.sync.ContraptionInteractionPacket;
import com.simibubi.create.content.contraptions.sync.ContraptionItemPacket;
import com.simibubi.create.content.contraptions.sync.ContraptionSeatMappingPacket;
import com.simibubi.create.content.contraptions.sync.LimbSwingUpdatePacket;
import com.simibubi.create.content.contraptions.wrench.RadialWrenchMenuSubmitPacket;
@ -123,6 +122,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkEvent.Context;
import net.minecraftforge.network.NetworkRegistry;
@ -218,8 +218,7 @@ public enum AllPackets {
LIMBSWING_UPDATE(LimbSwingUpdatePacket.class, LimbSwingUpdatePacket::new, PLAY_TO_CLIENT),
MINECART_CONTROLLER(MinecartControllerUpdatePacket.class, MinecartControllerUpdatePacket::new, PLAY_TO_CLIENT),
FLUID_SPLASH(FluidSplashPacket.class, FluidSplashPacket::new, PLAY_TO_CLIENT),
CONTRAPTION_FLUID(ContraptionFluidPacket.class, ContraptionFluidPacket::new, PLAY_TO_CLIENT),
CONTRAPTION_ITEM(ContraptionItemPacket.class, ContraptionItemPacket::new, PLAY_TO_CLIENT),
MOUNTED_STORAGE_SYNC(MountedStorageSyncPacket.class, MountedStorageSyncPacket::new, PLAY_TO_CLIENT),
GANTRY_UPDATE(GantryContraptionUpdatePacket.class, GantryContraptionUpdatePacket::new, PLAY_TO_CLIENT),
BLOCK_HIGHLIGHT(HighlightPacket.class, HighlightPacket::new, PLAY_TO_CLIENT),
TUNNEL_FLAP(TunnelFlapPacket.class, TunnelFlapPacket::new, PLAY_TO_CLIENT),

View file

@ -82,7 +82,6 @@ public class AllTags {
BRITTLE,
CASING,
CONTRAPTION_INVENTORY_DENY,
COPYCAT_ALLOW,
COPYCAT_DENY,
FAN_PROCESSING_CATALYSTS_BLASTING(MOD, "fan_processing_catalysts/blasting"),
@ -105,6 +104,9 @@ public class AllTags {
VALVE_HANDLES,
WINDMILL_SAILS,
WRENCH_PICKUP,
CHEST_MOUNTED_STORAGE,
SIMPLE_MOUNTED_STORAGE,
FALLBACK_MOUNTED_STORAGE_BLACKLIST,
ROOTS,
CORALS,

View file

@ -128,6 +128,7 @@ public class Create {
AllPackets.registerPackets();
AllFeatures.register(modEventBus);
AllPlacementModifiers.register(modEventBus);
AllMountedStorageTypes.register();
AllConfigs.register(modLoadingContext);

View file

@ -0,0 +1,65 @@
package com.simibubi.create.api.contraption.storage;
import com.simibubi.create.Create;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageType;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.lookup.BlockLookup;
import com.simibubi.create.impl.contraption.storage.MountedStorageTypeRegistryImpl;
import com.tterrag.registrate.builders.BlockBuilder;
import com.tterrag.registrate.util.entry.RegistryEntry;
import com.tterrag.registrate.util.nullness.NonNullUnaryOperator;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.registries.IForgeRegistry;
public class MountedStorageTypeRegistry {
public static final ResourceKey<Registry<MountedItemStorageType<?>>> ITEMS = ResourceKey.createRegistryKey(
Create.asResource("mounted_item_storage_type")
);
public static final ResourceKey<Registry<MountedFluidStorageType<?>>> FLUIDS = ResourceKey.createRegistryKey(
Create.asResource("mounted_fluid_storage_type")
);
/**
* Lookup used for finding the item storage type associated with a block.
* @see BlockLookup
*/
public static final BlockLookup<MountedItemStorageType<?>> ITEM_LOOKUP = MountedStorageTypeRegistryImpl.ITEM_LOOKUP;
/**
* Lookup used for finding the fluid storage type associated with a block.
* @see BlockLookup
*/
public static final BlockLookup<MountedFluidStorageType<?>> FLUID_LOOKUP = MountedStorageTypeRegistryImpl.FLUID_LOOKUP;
/**
* @throws NullPointerException if called before registry registration
*/
public static IForgeRegistry<MountedItemStorageType<?>> getItemsRegistry() {
return MountedStorageTypeRegistryImpl.getItemsRegistry();
}
/**
* @throws NullPointerException if called before registry registration
*/
public static IForgeRegistry<MountedFluidStorageType<?>> getFluidsRegistry() {
return MountedStorageTypeRegistryImpl.getFluidsRegistry();
}
/**
* Utility for use with Registrate builders. Creates a builder transformer
* that will register the given MountedItemStorageType to a block when ready.
*/
public static <B extends Block, P> NonNullUnaryOperator<BlockBuilder<B, P>> mountedItemStorage(RegistryEntry<? extends MountedItemStorageType<?>> type) {
return builder -> builder.onRegisterAfter(ITEMS, block -> ITEM_LOOKUP.register(block, type.get()));
}
/**
* Utility for use with Registrate builders. Creates a builder transformer
* that will register the given MountedFluidStorageType to a block when ready.
*/
public static <B extends Block, P> NonNullUnaryOperator<BlockBuilder<B, P>> mountedFluidStorage(RegistryEntry<? extends MountedFluidStorageType<?>> type) {
return builder -> builder.onRegisterAfter(ITEMS, block -> FLUID_LOOKUP.register(block, type.get()));
}
}

View file

@ -0,0 +1,25 @@
package com.simibubi.create.api.contraption.storage;
import com.simibubi.create.content.contraptions.Contraption;
import net.minecraft.core.BlockPos;
/**
* Optional interface for mounted storage that is synced with the client.
*/
public interface SyncedMountedStorage {
/**
* @return true if this storage needs to be synced.
*/
boolean isDirty();
/**
* Called after this storage has been synced.
*/
void markClean();
/**
* Called on the client side after this storage has been synced from the server.
*/
void afterSync(Contraption contraption, BlockPos localPos);
}

View file

@ -0,0 +1,31 @@
package com.simibubi.create.api.contraption.storage.fluid;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import com.mojang.serialization.Codec;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.fluids.capability.IFluidHandler;
public abstract class MountedFluidStorage implements IFluidHandler {
public static final Codec<MountedFluidStorage> CODEC = MountedFluidStorageType.CODEC.dispatch(
storage -> storage.type, type -> type.codec
);
public final MountedFluidStorageType<? extends MountedFluidStorage> type;
protected MountedFluidStorage(MountedFluidStorageType<?> type) {
this.type = Objects.requireNonNull(type);
}
/**
* Un-mount this storage back into the world. The expected storage type of the target
* block has already been checked to make sure it matches this storage's type.
*/
public abstract void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be);
}

View file

@ -0,0 +1,27 @@
package com.simibubi.create.api.contraption.storage.fluid;
import org.jetbrains.annotations.Nullable;
import com.mojang.serialization.Codec;
import com.simibubi.create.api.contraption.storage.MountedStorageTypeRegistry;
import net.minecraft.core.BlockPos;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public abstract class MountedFluidStorageType<T extends MountedFluidStorage> {
public static final Codec<MountedFluidStorageType<?>> CODEC = ExtraCodecs.lazyInitializedCodec(
() -> MountedStorageTypeRegistry.getFluidsRegistry().getCodec()
);
public final Codec<? extends T> codec;
protected MountedFluidStorageType(Codec<? extends T> codec) {
this.codec = codec;
}
@Nullable
public abstract T mount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be);
}

View file

@ -0,0 +1,20 @@
package com.simibubi.create.api.contraption.storage.fluid;
import com.google.common.collect.ImmutableMap;
import com.simibubi.create.foundation.fluid.CombinedTankWrapper;
import net.minecraft.core.BlockPos;
import net.minecraftforge.fluids.capability.IFluidHandler;
/**
* Wrapper around many MountedFluidStorages, providing access to all of them as one storage.
* They can still be accessed individually through the map.
*/
public class MountedFluidStorageWrapper extends CombinedTankWrapper {
public final ImmutableMap<BlockPos, MountedFluidStorage> storages;
public MountedFluidStorageWrapper(ImmutableMap<BlockPos, MountedFluidStorage> storages) {
super(storages.values().toArray(IFluidHandler[]::new));
this.storages = storages;
}
}

View file

@ -0,0 +1,56 @@
package com.simibubi.create.api.contraption.storage.fluid;
import org.jetbrains.annotations.NotNull;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
/**
* Partial implementation of a MountedFluidStorage that wraps a fluid handler.
*/
public abstract class WrapperMountedFluidStorage<T extends IFluidHandler> extends MountedFluidStorage {
protected final T wrapped;
protected WrapperMountedFluidStorage(MountedFluidStorageType<?> type, T wrapped) {
super(type);
this.wrapped = wrapped;
}
@Override
public int getTanks() {
return this.wrapped.getTanks();
}
@Override
@NotNull
public FluidStack getFluidInTank(int tank) {
return this.wrapped.getFluidInTank(tank);
}
@Override
public int getTankCapacity(int tank) {
return this.wrapped.getTankCapacity(tank);
}
@Override
public boolean isFluidValid(int tank, @NotNull FluidStack stack) {
return this.wrapped.isFluidValid(tank, stack);
}
@Override
public int fill(FluidStack resource, FluidAction action) {
return this.wrapped.fill(resource, action);
}
@Override
@NotNull
public FluidStack drain(FluidStack resource, FluidAction action) {
return this.wrapped.drain(resource, action);
}
@Override
@NotNull
public FluidStack drain(int maxDrain, FluidAction action) {
return this.wrapped.drain(maxDrain, action);
}
}

View file

@ -0,0 +1,36 @@
package com.simibubi.create.api.contraption.storage.fluid.registrate;
import com.simibubi.create.api.contraption.storage.MountedStorageTypeRegistry;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageType;
import com.tterrag.registrate.AbstractRegistrate;
import com.tterrag.registrate.builders.AbstractBuilder;
import com.tterrag.registrate.builders.BuilderCallback;
import com.tterrag.registrate.util.nullness.NonnullType;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.block.Block;
public class MountedFluidStorageTypeBuilder<T extends MountedFluidStorageType<?>, P> extends AbstractBuilder<MountedFluidStorageType<?>, T, P, MountedFluidStorageTypeBuilder<T, P>> {
private final T type;
public MountedFluidStorageTypeBuilder(AbstractRegistrate<?> owner, P parent, String name, BuilderCallback callback, T type) {
super(owner, parent, name, callback, MountedStorageTypeRegistry.FLUIDS);
this.type = type;
}
public MountedFluidStorageTypeBuilder<T, P> registerTo(Block block) {
MountedStorageTypeRegistry.FLUID_LOOKUP.register(block, this.type);
return this;
}
public MountedFluidStorageTypeBuilder<T, P> registerTo(TagKey<Block> tag) {
MountedStorageTypeRegistry.FLUID_LOOKUP.registerTag(tag, this.type);
return this;
}
@Override
@NonnullType
protected T createEntry() {
return this.type;
}
}

View file

@ -0,0 +1,152 @@
package com.simibubi.create.api.contraption.storage.item;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import com.mojang.serialization.Codec;
import com.simibubi.create.api.contraption.storage.item.menu.MountedStorageMenus;
import com.simibubi.create.content.contraptions.Contraption;
import com.simibubi.create.content.contraptions.MountedStorageManager;
import com.simibubi.create.content.contraptions.behaviour.MovementBehaviour;
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
import com.simibubi.create.foundation.utility.CreateLang;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.items.IItemHandlerModifiable;
public abstract class MountedItemStorage implements IItemHandlerModifiable {
public static final Codec<MountedItemStorage> CODEC = MountedItemStorageType.CODEC.dispatch(
storage -> storage.type, type -> type.codec
);
public final MountedItemStorageType<? extends MountedItemStorage> type;
protected MountedItemStorage(MountedItemStorageType<?> type) {
this.type = Objects.requireNonNull(type);
}
/**
* Un-mount this storage back into the world. The expected storage type of the target
* block has already been checked to make sure it matches this storage's type.
*/
public abstract void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be);
/**
* Internal mounted storages are not exposed to the larger contraption inventory.
* They are only for internal use, such as access from a {@link MovementBehaviour}.
* Internal storages are still accessible through {@link MovementContext#getItemStorage()}
* as well as {@link MountedStorageManager#getAllItemStorages()}.
* A storage being internal implies that it does not provide fuel either.
* This is only called once on assembly.
*/
public boolean isInternal() {
return false;
}
/**
* Contraptions may search storage for fuel, such as for powering furnace minecarts
* and trains. Return false if this storage should
*/
public boolean providesFuel() {
return true;
}
/**
* Handle a player clicking on this mounted storage. This is always called on the server.
* The default implementation will try to open a generic GUI for standard inventories.
* For this to work, this storage must have 1-6 complete rows of 9 slots.
* @return true if the interaction was successful
*/
public boolean handleInteraction(ServerPlayer player, Contraption contraption, StructureBlockInfo info) {
ServerLevel level = player.serverLevel();
BlockPos localPos = info.pos();
Vec3 localPosVec = Vec3.atCenterOf(localPos);
Predicate<Player> stillValid = p -> {
Vec3 currentPos = contraption.entity.toGlobalVector(localPosVec, 0);
return this.isMenuValid(player, contraption, currentPos);
};
Component menuName = this.getMenuName(info, contraption);
IItemHandlerModifiable handler = this.getHandlerForMenu(info, contraption);
Consumer<Player> onClose = p -> {
Vec3 newPos = contraption.entity.toGlobalVector(localPosVec, 0);
this.playClosingSound(level, newPos);
};
OptionalInt id = player.openMenu(this.createMenuProvider(menuName, handler, stillValid, onClose));
if (id.isPresent()) {
Vec3 globalPos = contraption.entity.toGlobalVector(localPosVec, 0);
this.playOpeningSound(level, globalPos);
return true;
} else {
return false;
}
}
/**
* Get the item handler that will be used by this storage's menu. This is useful for
* handling multi-blocks, such as double chests.
*/
protected IItemHandlerModifiable getHandlerForMenu(StructureBlockInfo info, Contraption contraption) {
return this;
}
/**
* @param player the player who opened the menu
* @param pos the center of this storage in-world
* @return true if a GUI opened for this storage is still valid
*/
protected boolean isMenuValid(ServerPlayer player, Contraption contraption, Vec3 pos) {
return contraption.entity.isAlive() && player.distanceToSqr(pos) < (8 * 8);
}
/**
* @return the title to be shown in the GUI when this storage is opened
*/
protected Component getMenuName(StructureBlockInfo info, Contraption contraption) {
MutableComponent blockName = info.state().getBlock().getName();
return CreateLang.translateDirect("contraptions.moving_container", blockName);
}
/**
* @return a MenuProvider that provides the menu players will see when opening this storage
*/
@Nullable
protected MenuProvider createMenuProvider(Component name, IItemHandlerModifiable handler,
Predicate<Player> stillValid, Consumer<Player> onClose) {
return MountedStorageMenus.createGeneric(name, handler, stillValid, onClose);
}
/**
* Play the sound made by opening this storage's GUI.
*/
protected void playOpeningSound(ServerLevel level, Vec3 pos) {
level.playSound(
null, BlockPos.containing(pos),
SoundEvents.BARREL_OPEN, SoundSource.BLOCKS,
0.75f, 1f
);
}
/**
* Play the sound made by closing this storage's GUI.
*/
protected void playClosingSound(ServerLevel level, Vec3 pos) {
}
}

View file

@ -0,0 +1,27 @@
package com.simibubi.create.api.contraption.storage.item;
import org.jetbrains.annotations.Nullable;
import com.mojang.serialization.Codec;
import com.simibubi.create.api.contraption.storage.MountedStorageTypeRegistry;
import net.minecraft.core.BlockPos;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public abstract class MountedItemStorageType<T extends MountedItemStorage> {
public static final Codec<MountedItemStorageType<?>> CODEC = ExtraCodecs.lazyInitializedCodec(
() -> MountedStorageTypeRegistry.getItemsRegistry().getCodec()
);
public final Codec<? extends T> codec;
protected MountedItemStorageType(Codec<? extends T> codec) {
this.codec = codec;
}
@Nullable
public abstract T mount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be);
}

View file

@ -0,0 +1,20 @@
package com.simibubi.create.api.contraption.storage.item;
import com.google.common.collect.ImmutableMap;
import net.minecraft.core.BlockPos;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.wrapper.CombinedInvWrapper;
/**
* Wrapper around many MountedItemStorages, providing access to all of them as one storage.
* They can still be accessed individually through the map.
*/
public class MountedItemStorageWrapper extends CombinedInvWrapper {
public final ImmutableMap<BlockPos, MountedItemStorage> storages;
public MountedItemStorageWrapper(ImmutableMap<BlockPos, MountedItemStorage> storages) {
super(storages.values().toArray(IItemHandlerModifiable[]::new));
this.storages = storages;
}
}

View file

@ -0,0 +1,66 @@
package com.simibubi.create.api.contraption.storage.item;
import org.jetbrains.annotations.NotNull;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.ItemStackHandler;
/**
* Partial implementation of a MountedItemStorage that wraps an item handler.
*/
public abstract class WrapperMountedItemStorage<T extends IItemHandlerModifiable> extends MountedItemStorage {
protected final T wrapped;
protected WrapperMountedItemStorage(MountedItemStorageType<?> type, T wrapped) {
super(type);
this.wrapped = wrapped;
}
@Override
public void setStackInSlot(int slot, @NotNull ItemStack stack) {
this.wrapped.setStackInSlot(slot, stack);
}
@Override
public int getSlots() {
return this.wrapped.getSlots();
}
@Override
@NotNull
public ItemStack getStackInSlot(int slot) {
return this.wrapped.getStackInSlot(slot);
}
@Override
@NotNull
public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) {
return this.wrapped.insertItem(slot, stack, simulate);
}
@Override
@NotNull
public ItemStack extractItem(int slot, int amount, boolean simulate) {
return this.wrapped.extractItem(slot, amount, simulate);
}
@Override
public int getSlotLimit(int slot) {
return this.wrapped.getSlotLimit(slot);
}
@Override
public boolean isItemValid(int slot, @NotNull ItemStack stack) {
return this.wrapped.isItemValid(slot, stack);
}
public static ItemStackHandler copyToItemStackHandler(IItemHandler handler) {
ItemStackHandler copy = new ItemStackHandler(handler.getSlots());
for (int i = 0; i < handler.getSlots(); i++) {
copy.setStackInSlot(i, handler.getStackInSlot(i).copy());
}
return copy;
}
}

View file

@ -0,0 +1,111 @@
package com.simibubi.create.api.contraption.storage.item.chest;
import org.jetbrains.annotations.Nullable;
import com.mojang.serialization.Codec;
import com.simibubi.create.AllMountedStorageTypes;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorage;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.contraption.storage.item.simple.SimpleMountedStorage;
import com.simibubi.create.content.contraptions.Contraption;
import com.simibubi.create.foundation.item.ItemHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.Container;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.ChestType;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.wrapper.CombinedInvWrapper;
import net.minecraftforge.items.wrapper.InvWrapper;
/**
* Mounted storage that handles opening a combined GUI for double chests.
*/
public class ChestMountedStorage extends SimpleMountedStorage {
public static final Codec<ChestMountedStorage> CODEC = SimpleMountedStorage.codec(ChestMountedStorage::new);
protected ChestMountedStorage(MountedItemStorageType<?> type, IItemHandler handler) {
super(type, handler);
}
public ChestMountedStorage(IItemHandler handler) {
this(AllMountedStorageTypes.CHEST.get(), handler);
}
@Override
public void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
// the capability will include both sides of chests, but mounted storage is 1:1
if (be instanceof Container container && this.getSlots() == container.getContainerSize()) {
ItemHelper.copyContents(this, new InvWrapper(container));
}
}
@Override
protected IItemHandlerModifiable getHandlerForMenu(StructureBlockInfo info, Contraption contraption) {
BlockState state = info.state();
ChestType type = state.getValue(ChestBlock.TYPE);
if (type == ChestType.SINGLE)
return this;
Direction facing = state.getValue(ChestBlock.FACING);
Direction connectedDirection = ChestBlock.getConnectedDirection(state);
BlockPos otherHalfPos = info.pos().relative(connectedDirection);
MountedItemStorage otherHalf = this.getOtherHalf(contraption, otherHalfPos, state.getBlock(), facing, type);
if (otherHalf == null)
return this;
if (type == ChestType.RIGHT) {
return new CombinedInvWrapper(this, otherHalf);
} else {
return new CombinedInvWrapper(otherHalf, this);
}
}
@Nullable
protected MountedItemStorage getOtherHalf(Contraption contraption, BlockPos localPos, Block block,
Direction thisFacing, ChestType thisType) {
StructureBlockInfo info = contraption.getBlocks().get(localPos);
if (info == null)
return null;
BlockState state = info.state();
if (!state.is(block))
return null;
Direction facing = state.getValue(ChestBlock.FACING);
ChestType type = state.getValue(ChestBlock.TYPE);
return facing == thisFacing && type == thisType.getOpposite()
? contraption.getStorage().getMountedItems().storages.get(localPos)
: null;
}
@Override
protected void playOpeningSound(ServerLevel level, Vec3 pos) {
level.playSound(
null, BlockPos.containing(pos),
SoundEvents.CHEST_OPEN, SoundSource.BLOCKS,
0.75f, 1f
);
}
@Override
protected void playClosingSound(ServerLevel level, Vec3 pos) {
level.playSound(
null, BlockPos.containing(pos),
SoundEvents.CHEST_CLOSE, SoundSource.BLOCKS,
0.75f, 1f
);
}
}

View file

@ -0,0 +1,25 @@
package com.simibubi.create.api.contraption.storage.item.chest;
import com.simibubi.create.api.contraption.storage.item.simple.SimpleMountedStorage;
import com.simibubi.create.api.contraption.storage.item.simple.SimpleMountedStorageType;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.wrapper.InvWrapper;
import net.minecraft.world.Container;
import net.minecraft.world.level.block.entity.BlockEntity;
public class ChestMountedStorageType extends SimpleMountedStorageType<ChestMountedStorage> {
public ChestMountedStorageType() {
super(ChestMountedStorage.CODEC);
}
@Override
protected IItemHandler getHandler(BlockEntity be) {
return be instanceof Container container ? new InvWrapper(container) : null;
}
@Override
protected SimpleMountedStorage createStorage(IItemHandler handler) {
return new ChestMountedStorage(handler);
}
}

View file

@ -0,0 +1,52 @@
package com.simibubi.create.api.contraption.storage.item.menu;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import net.minecraft.network.chat.Component;
import net.minecraft.world.Container;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.DispenserMenu;
import net.minecraft.world.inventory.MenuConstructor;
import net.minecraft.world.inventory.MenuType;
import net.minecraftforge.items.IItemHandlerModifiable;
import org.jetbrains.annotations.Nullable;
/**
* Methods for creating generic menus usable by mounted storages.
*/
public class MountedStorageMenus {
public static final List<MenuType<?>> GENERIC_CHEST_MENUS = List.of(
MenuType.GENERIC_9x1, MenuType.GENERIC_9x2, MenuType.GENERIC_9x3,
MenuType.GENERIC_9x4, MenuType.GENERIC_9x5, MenuType.GENERIC_9x6
);
@Nullable
public static MenuProvider createGeneric(Component menuName, IItemHandlerModifiable handler,
Predicate<Player> stillValid, Consumer<Player> onClose) {
int rows = handler.getSlots() / 9;
if (rows < 1 || rows > 6)
return null;
MenuType<?> type = GENERIC_CHEST_MENUS.get(rows - 1);
Container wrapper = new StorageInteractionWrapper(handler, stillValid, onClose);
MenuConstructor constructor = (id, inv, player) -> new ChestMenu(type, id, inv, wrapper, rows);
return new SimpleMenuProvider(constructor, menuName);
}
@Nullable
public static MenuProvider createGeneric9x9(Component name, IItemHandlerModifiable handler,
Predicate<Player> stillValid, Consumer<Player> onClose) {
if (handler.getSlots() != 9)
return null;
Container wrapper = new StorageInteractionWrapper(handler, stillValid, onClose);
MenuConstructor constructor = (id, inv, player) -> new DispenserMenu(id, inv, wrapper);
return new SimpleMenuProvider(constructor, name);
}
}

View file

@ -0,0 +1,35 @@
package com.simibubi.create.api.contraption.storage.item.menu;
import java.util.function.Consumer;
import java.util.function.Predicate;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.wrapper.RecipeWrapper;
import net.minecraft.world.entity.player.Player;
public class StorageInteractionWrapper extends RecipeWrapper {
private final Predicate<Player> stillValid;
private final Consumer<Player> onClose;
public StorageInteractionWrapper(IItemHandlerModifiable inv, Predicate<Player> stillValid, Consumer<Player> onClose) {
super(inv);
this.stillValid = stillValid;
this.onClose = onClose;
}
@Override
public boolean stillValid(Player player) {
return this.stillValid.test(player);
}
@Override
public int getMaxStackSize() {
return 64;
}
@Override
public void stopOpen(Player player) {
this.onClose.accept(player);
}
}

View file

@ -0,0 +1,36 @@
package com.simibubi.create.api.contraption.storage.item.registrate;
import com.simibubi.create.api.contraption.storage.MountedStorageTypeRegistry;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.tterrag.registrate.AbstractRegistrate;
import com.tterrag.registrate.builders.AbstractBuilder;
import com.tterrag.registrate.builders.BuilderCallback;
import com.tterrag.registrate.util.nullness.NonnullType;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.block.Block;
public class MountedItemStorageTypeBuilder<T extends MountedItemStorageType<?>, P> extends AbstractBuilder<MountedItemStorageType<?>, T, P, MountedItemStorageTypeBuilder<T, P>> {
private final T type;
public MountedItemStorageTypeBuilder(AbstractRegistrate<?> owner, P parent, String name, BuilderCallback callback, T type) {
super(owner, parent, name, callback, MountedStorageTypeRegistry.ITEMS);
this.type = type;
}
public MountedItemStorageTypeBuilder<T, P> registerTo(Block block) {
MountedStorageTypeRegistry.ITEM_LOOKUP.register(block, this.type);
return this;
}
public MountedItemStorageTypeBuilder<T, P> registerTo(TagKey<Block> tag) {
MountedStorageTypeRegistry.ITEM_LOOKUP.registerTag(tag, this.type);
return this;
}
@Override
@NonnullType
protected T createEntry() {
return this.type;
}
}

View file

@ -0,0 +1,76 @@
package com.simibubi.create.api.contraption.storage.item.simple;
import java.util.Optional;
import java.util.function.Function;
import com.mojang.serialization.Codec;
import com.simibubi.create.AllMountedStorageTypes;
import com.simibubi.create.AllTags;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.contraption.storage.item.WrapperMountedItemStorage;
import com.simibubi.create.foundation.utility.CreateCodecs;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.ItemStackHandler;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
/**
* Widely-applicable mounted storage implementation.
* Gets an item handler from the mounted block, copies it to an ItemStackHandler,
* and then copies the inventory back to the target when unmounting.
* All blocks for which this mounted storage is registered must provide an
* {@link IItemHandlerModifiable} to {@link ForgeCapabilities#ITEM_HANDLER}.
* <br>
* To use this implementation, either register {@link AllMountedStorageTypes#SIMPLE} to your block
* manually, or add your block to the {@link AllTags.AllBlockTags#SIMPLE_MOUNTED_STORAGE} tag.
* It is also possible to extend this class to create your own implementation.
*/
public class SimpleMountedStorage extends WrapperMountedItemStorage<ItemStackHandler> {
public static final Codec<SimpleMountedStorage> CODEC = codec(SimpleMountedStorage::new);
public SimpleMountedStorage(MountedItemStorageType<?> type, IItemHandler handler) {
super(type, copyToItemStackHandler(handler));
}
public SimpleMountedStorage(IItemHandler handler) {
this(AllMountedStorageTypes.SIMPLE.get(), handler);
}
@Override
public void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
if (be == null)
return;
be.getCapability(ForgeCapabilities.ITEM_HANDLER).resolve().flatMap(this::validate).ifPresent(handler -> {
for (int i = 0; i < handler.getSlots(); i++) {
handler.setStackInSlot(i, this.getStackInSlot(i));
}
});
}
/**
* Make sure the targeted handler is valid for copying items back into.
* It is highly recommended to call super in overrides.
*/
protected Optional<IItemHandlerModifiable> validate(IItemHandler handler) {
if (handler.getSlots() == this.getSlots() && handler instanceof IItemHandlerModifiable modifiable) {
return Optional.of(modifiable);
} else {
return Optional.empty();
}
}
public static <T extends SimpleMountedStorage> Codec<T> codec(Function<IItemHandler, T> factory) {
return CreateCodecs.ITEM_STACK_HANDLER.xmap(factory, storage -> storage.wrapped);
}
}

View file

@ -0,0 +1,49 @@
package com.simibubi.create.api.contraption.storage.item.simple;
import java.util.Optional;
import com.mojang.serialization.Codec;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public abstract class SimpleMountedStorageType<T extends SimpleMountedStorage> extends MountedItemStorageType<SimpleMountedStorage> {
protected SimpleMountedStorageType(Codec<T> codec) {
super(codec);
}
@Override
@Nullable
public SimpleMountedStorage mount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
return Optional.ofNullable(be)
.map(this::getHandler)
.map(this::createStorage)
.orElse(null);
}
protected IItemHandler getHandler(BlockEntity be) {
IItemHandler handler = be.getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null);
// make sure the handler is modifiable so new contents can be moved over on disassembly
return handler instanceof IItemHandlerModifiable modifiable ? modifiable : null;
}
protected SimpleMountedStorage createStorage(IItemHandler handler) {
return new SimpleMountedStorage(this, handler);
}
public static final class Impl extends SimpleMountedStorageType<SimpleMountedStorage> {
public Impl() {
super(SimpleMountedStorage.CODEC);
}
}
}

View file

@ -0,0 +1,66 @@
package com.simibubi.create.api.lookup;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.impl.lookup.BlockLookupImpl;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
/**
* Lookup for objects provided by blocks. Values can either be registered directly
* or found lazily through providers. Providers are only queried once per block.
* If they return a value, that value is cached. If they don't, that block is recorded
* as not having a corresponding value.
* <p>
* Provided values are reset on resource reloads and will be re-queried and re-cached the
* next time a block is queried.
* <p>
* All providers are expected to be registered synchronously during game init.
* Adding new ones late is not supported.
*/
@ApiStatus.NonExtendable
public interface BlockLookup<T> {
@Nullable
T find(Block block);
/**
* Shortcut to avoid calling getBlock() on a BlockState.
*/
@Nullable
T find(BlockState state);
/**
* Register a value to one block.
*/
void register(Block block, T value);
/**
* Register a value to all entries of a tag.
*/
void registerTag(TagKey<Block> tag, T value);
/**
* Register a new provider that will be queried.
* Providers are queried in reverse-registration order.
*/
void registerProvider(Provider<T> provider);
static <T> BlockLookup<T> create() {
return new BlockLookupImpl<>();
}
static <T> BlockLookup<T> create(Provider<T> initialProvider) {
BlockLookup<T> lookup = new BlockLookupImpl<>();
lookup.registerProvider(initialProvider);
return lookup;
}
@FunctionalInterface
interface Provider<T> {
@Nullable
T get(Block block);
}
}

View file

@ -124,10 +124,6 @@ import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraftforge.client.model.data.ModelData;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.wrapper.CombinedInvWrapper;
import net.minecraftforge.registries.GameData;
public abstract class Contraption {
@ -267,7 +263,7 @@ public abstract class Contraption {
stabilizedSubContraptions.put(movedContraption.getUUID(), new BlockFace(toLocalPos(pos), face));
}
storage.createHandlers();
storage.initialize();
gatherBBsOffThread();
}
@ -427,7 +423,7 @@ public abstract class Contraption {
frontier.add(offsetPos);
}
addBlock(pos, capture(world, pos));
addBlock(world, pos, capture(world, pos));
if (blocks.size() <= AllConfigs.server().kinetics.maxBlocksMoved.get())
return true;
else
@ -566,7 +562,7 @@ public abstract class Contraption {
frontier.add(ropePos);
break;
}
addBlock(ropePos, capture(world, ropePos));
addBlock(world, ropePos, capture(world, ropePos));
}
}
@ -638,24 +634,25 @@ public abstract class Contraption {
return Pair.of(new StructureBlockInfo(pos, blockstate, compoundnbt), blockEntity);
}
protected void addBlock(BlockPos pos, Pair<StructureBlockInfo, BlockEntity> pair) {
protected void addBlock(Level level, BlockPos pos, Pair<StructureBlockInfo, BlockEntity> pair) {
StructureBlockInfo captured = pair.getKey();
BlockPos localPos = pos.subtract(anchor);
StructureBlockInfo structureBlockInfo = new StructureBlockInfo(localPos, captured.state(), captured.nbt());
BlockState state = captured.state();
StructureBlockInfo structureBlockInfo = new StructureBlockInfo(localPos, state, captured.nbt());
if (blocks.put(localPos, structureBlockInfo) != null)
return;
bounds = bounds.minmax(new AABB(localPos));
BlockEntity be = pair.getValue();
storage.addBlock(localPos, be);
storage.addBlock(level, state, pos, localPos, be);
captureMultiblock(localPos, structureBlockInfo, be);
if (AllMovementBehaviours.getBehaviour(captured.state()) != null)
if (AllMovementBehaviours.getBehaviour(state) != null)
actors.add(MutablePair.of(structureBlockInfo, null));
MovingInteractionBehaviour interactionBehaviour = AllInteractionBehaviours.getBehaviour(captured.state());
MovingInteractionBehaviour interactionBehaviour = AllInteractionBehaviours.getBehaviour(state);
if (interactionBehaviour != null)
interactors.put(localPos, interactionBehaviour);
@ -735,6 +732,8 @@ public abstract class Contraption {
});
});
storage.read(nbt, spawnData, this);
actors.clear();
nbt.getList("Actors", Tag.TAG_COMPOUND)
.forEach(c -> {
@ -776,8 +775,6 @@ public abstract class Contraption {
interactors.put(pos, behaviour);
});
storage.read(nbt, presentBlockEntities, spawnData);
if (nbt.contains("BoundsFront"))
bounds = NBTHelper.readAABB(nbt.getList("BoundsFront", Tag.TAG_FLOAT));
@ -827,8 +824,8 @@ public abstract class Contraption {
superglueNBT.add(c);
}
}
(spawnPacket ? getStorageForSpawnPacket() : storage).write(nbt, spawnPacket);
writeStorage(nbt, spawnPacket);
ListTag interactorNBT = new ListTag();
for (BlockPos pos : interactors.keySet()) {
@ -870,9 +867,9 @@ public abstract class Contraption {
return nbt;
}
protected MountedStorageManager getStorageForSpawnPacket() {
return storage;
public void writeStorage(CompoundTag nbt, boolean spawnPacket) {
storage.write(nbt, spawnPacket);
}
private CompoundTag writeBlocksCompound() {
@ -974,8 +971,6 @@ public abstract class Contraption {
}
public void removeBlocksFromWorld(Level world, BlockPos offset) {
storage.removeStorageFromWorld();
glueToRemove.forEach(glue -> {
superglue.add(glue.getBoundingBox()
.move(Vec3.atLowerCornerOf(offset.offset(anchor))
@ -1157,9 +1152,10 @@ public abstract class Contraption {
}
blockEntity.load(tag);
storage.addStorageToWorld(block, blockEntity);
}
storage.unmount(world, block, targetPos, blockEntity);
if (blockEntity != null) {
transform.apply(blockEntity);
}
@ -1180,8 +1176,6 @@ public abstract class Contraption {
if (!world.isClientSide)
world.addFreshEntity(new SuperGlueEntity(world, box));
}
storage.clear();
}
protected void translateMultiblockControllers(StructureTransform transform) {
@ -1449,20 +1443,8 @@ public abstract class Contraption {
return maxDistSq;
}
public IItemHandlerModifiable getSharedInventory() {
return storage.getItems();
}
public IItemHandlerModifiable getSharedFuelInventory() {
return storage.getFuelItems();
}
public IFluidHandler getSharedFluidTanks() {
return storage.getFluids();
}
public MountedStorageManager getStorageManager() {
return storage;
public MountedStorageManager getStorage() {
return this.storage;
}
public RenderedBlocks getRenderedBlocks() {
@ -1487,36 +1469,8 @@ public abstract class Contraption {
return simplifiedEntityColliders;
}
public void handleContraptionFluidPacket(BlockPos localPos, FluidStack containedFluid) {
storage.updateContainedFluid(localPos, containedFluid);
}
public void handleContraptionItemPacket(BlockPos localPos, List<ItemStack> containedItems) {
storage.updateContainedItem(localPos, containedItems);
}
public static class ContraptionInvWrapper extends CombinedInvWrapper {
protected final boolean isExternal;
public ContraptionInvWrapper(boolean isExternal, IItemHandlerModifiable... itemHandler) {
super(itemHandler);
this.isExternal = isExternal;
}
public ContraptionInvWrapper(IItemHandlerModifiable... itemHandler) {
this(false, itemHandler);
}
public boolean isSlotExternal(int slot) {
if (isExternal)
return true;
IItemHandlerModifiable handler = getHandlerFromIndex(getIndexForSlot(slot));
return handler instanceof ContraptionInvWrapper && ((ContraptionInvWrapper) handler).isSlotExternal(slot);
}
}
public void tickStorage(AbstractContraptionEntity entity) {
storage.entityTick(entity);
getStorage().tick(entity);
}
public boolean containsBlockBreakers() {

View file

@ -1,174 +0,0 @@
package com.simibubi.create.content.contraptions;
import com.simibubi.create.AllPackets;
import com.simibubi.create.content.contraptions.sync.ContraptionFluidPacket;
import com.simibubi.create.content.fluids.tank.CreativeFluidTankBlockEntity;
import com.simibubi.create.content.fluids.tank.CreativeFluidTankBlockEntity.CreativeSmartFluidTank;
import com.simibubi.create.content.fluids.tank.FluidTankBlockEntity;
import com.simibubi.create.foundation.fluid.SmartFluidTank;
import net.createmod.catnip.animation.LerpedFloat;
import net.createmod.catnip.animation.LerpedFloat.Chaser;
import net.createmod.catnip.nbt.NBTHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.network.PacketDistributor;
public class MountedFluidStorage {
SmartFluidTank tank;
private boolean valid;
private BlockEntity blockEntity;
private int packetCooldown = 0;
private boolean sendPacket = false;
public static boolean canUseAsStorage(BlockEntity be) {
if (be instanceof FluidTankBlockEntity)
return ((FluidTankBlockEntity) be).isController();
return false;
}
public MountedFluidStorage(BlockEntity be) {
assignBlockEntity(be);
}
public void assignBlockEntity(BlockEntity be) {
this.blockEntity = be;
tank = createMountedTank(be);
}
private SmartFluidTank createMountedTank(BlockEntity be) {
if (be instanceof CreativeFluidTankBlockEntity)
return new CreativeSmartFluidTank(
((FluidTankBlockEntity) be).getTotalTankSize() * FluidTankBlockEntity.getCapacityMultiplier(), $ -> {
});
if (be instanceof FluidTankBlockEntity)
return new SmartFluidTank(
((FluidTankBlockEntity) be).getTotalTankSize() * FluidTankBlockEntity.getCapacityMultiplier(),
this::onFluidStackChanged);
return null;
}
public void tick(Entity entity, BlockPos pos, boolean isRemote) {
if (!isRemote) {
if (packetCooldown > 0)
packetCooldown--;
else if (sendPacket) {
sendPacket = false;
AllPackets.getChannel().send(PacketDistributor.TRACKING_ENTITY.with(() -> entity),
new ContraptionFluidPacket(entity.getId(), pos, tank.getFluid()));
packetCooldown = 8;
}
return;
}
if (!(blockEntity instanceof FluidTankBlockEntity))
return;
FluidTankBlockEntity tank = (FluidTankBlockEntity) blockEntity;
tank.getFluidLevel()
.tickChaser();
}
public void updateFluid(FluidStack fluid) {
tank.setFluid(fluid);
if (!(blockEntity instanceof FluidTankBlockEntity))
return;
float fillState = tank.getFluidAmount() / (float) tank.getCapacity();
FluidTankBlockEntity tank = (FluidTankBlockEntity) blockEntity;
if (tank.getFluidLevel() == null)
tank.setFluidLevel(LerpedFloat.linear()
.startWithValue(fillState));
tank.getFluidLevel()
.chase(fillState, 0.5, Chaser.EXP);
IFluidTank tankInventory = tank.getTankInventory();
if (tankInventory instanceof SmartFluidTank)
((SmartFluidTank) tankInventory).setFluid(fluid);
}
public void removeStorageFromWorld() {
valid = false;
if (blockEntity == null)
return;
IFluidHandler teHandler = blockEntity.getCapability(ForgeCapabilities.FLUID_HANDLER)
.orElse(null);
if (!(teHandler instanceof SmartFluidTank))
return;
SmartFluidTank smartTank = (SmartFluidTank) teHandler;
tank.setFluid(smartTank.getFluid());
sendPacket = false;
valid = true;
}
private void onFluidStackChanged(FluidStack fs) {
sendPacket = true;
}
public void addStorageToWorld(BlockEntity be) {
if (tank instanceof CreativeSmartFluidTank)
return;
LazyOptional<IFluidHandler> capability = be.getCapability(ForgeCapabilities.FLUID_HANDLER);
IFluidHandler teHandler = capability.orElse(null);
if (!(teHandler instanceof SmartFluidTank))
return;
SmartFluidTank inv = (SmartFluidTank) teHandler;
inv.setFluid(tank.getFluid()
.copy());
}
public IFluidHandler getFluidHandler() {
return tank;
}
public CompoundTag serialize() {
if (!valid)
return null;
CompoundTag tag = tank.writeToNBT(new CompoundTag());
tag.putInt("Capacity", tank.getCapacity());
if (tank instanceof CreativeSmartFluidTank) {
NBTHelper.putMarker(tag, "Bottomless");
tag.put("ProvidedStack", tank.getFluid()
.writeToNBT(new CompoundTag()));
}
return tag;
}
public static MountedFluidStorage deserialize(CompoundTag nbt) {
MountedFluidStorage storage = new MountedFluidStorage(null);
if (nbt == null)
return storage;
int capacity = nbt.getInt("Capacity");
storage.tank = new SmartFluidTank(capacity, storage::onFluidStackChanged);
storage.valid = true;
if (nbt.contains("Bottomless")) {
FluidStack providedStack = FluidStack.loadFluidStackFromNBT(nbt.getCompound("ProvidedStack"));
CreativeSmartFluidTank creativeSmartFluidTank = new CreativeSmartFluidTank(capacity, $ -> {
});
creativeSmartFluidTank.setContainedFluid(providedStack);
storage.tank = creativeSmartFluidTank;
return storage;
}
storage.tank.readFromNBT(nbt);
return storage;
}
public boolean isValid() {
return valid;
}
}

View file

@ -1,297 +0,0 @@
package com.simibubi.create.content.contraptions;
import java.util.List;
import com.simibubi.create.AllBlockEntityTypes;
import com.simibubi.create.AllPackets;
import com.simibubi.create.AllTags.AllBlockTags;
import com.simibubi.create.content.contraptions.sync.ContraptionItemPacket;
import com.simibubi.create.content.equipment.toolbox.ToolboxInventory;
import com.simibubi.create.content.kinetics.belt.transport.TransportedItemStack;
import com.simibubi.create.content.kinetics.crafter.MechanicalCrafterBlockEntity;
import com.simibubi.create.content.logistics.crate.BottomlessItemHandler;
import com.simibubi.create.content.logistics.depot.DepotBehaviour;
import com.simibubi.create.content.logistics.depot.DepotBlockEntity;
import com.simibubi.create.content.logistics.vault.ItemVaultBlockEntity;
import com.simibubi.create.content.processing.recipe.ProcessingInventory;
import net.createmod.catnip.nbt.NBTHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.ContainerHelper;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BarrelBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.ChestBlockEntity;
import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.registries.ForgeRegistries;
public class MountedStorage {
private static final ItemStackHandler dummyHandler = new ItemStackHandler();
ItemStackHandler handler;
boolean noFuel;
boolean valid;
private int packetCooldown = 0;
private boolean sendPacket = false;
BlockEntity blockEntity;
public static boolean canUseAsStorage(BlockEntity be) {
if (be == null)
return false;
if (be instanceof MechanicalCrafterBlockEntity)
return false;
if (AllBlockEntityTypes.CREATIVE_CRATE.is(be))
return true;
if (be instanceof ShulkerBoxBlockEntity)
return true;
if (be instanceof ChestBlockEntity)
return true;
if (be instanceof BarrelBlockEntity)
return true;
if (be instanceof ItemVaultBlockEntity)
return true;
if (be instanceof DepotBlockEntity)
return true;
try {
LazyOptional<IItemHandler> capability = be.getCapability(ForgeCapabilities.ITEM_HANDLER);
IItemHandler handler = capability.orElse(null);
if (handler instanceof ItemStackHandler)
return !(handler instanceof ProcessingInventory);
return canUseModdedInventory(be, handler);
} catch (Exception e) {
return false;
}
}
public static boolean canUseModdedInventory(BlockEntity be, IItemHandler handler) {
if (!(handler instanceof IItemHandlerModifiable validItemHandler))
return false;
BlockState blockState = be.getBlockState();
if (AllBlockTags.CONTRAPTION_INVENTORY_DENY.matches(blockState))
return false;
// There doesn't appear to be much of a standard for tagging chests/barrels
String blockId = ForgeRegistries.BLOCKS.getKey(blockState.getBlock())
.getPath();
if (blockId.contains("ender"))
return false;
return blockId.endsWith("_chest") || blockId.endsWith("_barrel");
}
public MountedStorage(BlockEntity be) {
this.blockEntity = be;
handler = dummyHandler;
noFuel = be instanceof ItemVaultBlockEntity;
}
public void removeStorageFromWorld() {
valid = false;
sendPacket = false;
if (blockEntity == null)
return;
if (blockEntity instanceof DepotBlockEntity depot) {
handler = new SyncedMountedItemStackHandler(1);
handler.setStackInSlot(0, depot.getHeldItem());
valid = true;
return;
}
if (blockEntity instanceof ChestBlockEntity) {
CompoundTag tag = blockEntity.saveWithFullMetadata();
if (tag.contains("LootTable", 8))
return;
handler = new ItemStackHandler(((ChestBlockEntity) blockEntity).getContainerSize());
NonNullList<ItemStack> items = NonNullList.withSize(handler.getSlots(), ItemStack.EMPTY);
ContainerHelper.loadAllItems(tag, items);
for (int i = 0; i < items.size(); i++)
handler.setStackInSlot(i, items.get(i));
valid = true;
return;
}
IItemHandler beHandler = blockEntity.getCapability(ForgeCapabilities.ITEM_HANDLER)
.orElse(dummyHandler);
if (beHandler == dummyHandler)
return;
// multiblock vaults need to provide individual invs
if (blockEntity instanceof ItemVaultBlockEntity) {
handler = ((ItemVaultBlockEntity) blockEntity).getInventoryOfBlock();
valid = true;
return;
}
// be uses ItemStackHandler
if (beHandler instanceof ItemStackHandler) {
handler = (ItemStackHandler) beHandler;
valid = true;
return;
}
// serialization not accessible -> fill into a serializable handler
if (beHandler instanceof IItemHandlerModifiable) {
IItemHandlerModifiable inv = (IItemHandlerModifiable) beHandler;
handler = new ItemStackHandler(beHandler.getSlots());
for (int slot = 0; slot < handler.getSlots(); slot++) {
handler.setStackInSlot(slot, inv.getStackInSlot(slot));
inv.setStackInSlot(slot, ItemStack.EMPTY);
}
valid = true;
return;
}
}
public void addStorageToWorld(BlockEntity be) {
// FIXME: More dynamic mounted storage in .4
if (handler instanceof BottomlessItemHandler)
return;
if (be instanceof DepotBlockEntity depot) {
if (handler.getSlots() > 0)
depot.getBehaviour(DepotBehaviour.TYPE)
.setCenteredHeldItem(new TransportedItemStack(handler.getStackInSlot(0)));
return;
}
if (be instanceof ChestBlockEntity) {
CompoundTag tag = be.saveWithFullMetadata();
tag.remove("Items");
NonNullList<ItemStack> items = NonNullList.withSize(handler.getSlots(), ItemStack.EMPTY);
for (int i = 0; i < items.size(); i++)
items.set(i, handler.getStackInSlot(i));
ContainerHelper.saveAllItems(tag, items);
be.load(tag);
return;
}
if (be instanceof ItemVaultBlockEntity) {
((ItemVaultBlockEntity) be).applyInventoryToBlock(handler);
return;
}
LazyOptional<IItemHandler> capability = be.getCapability(ForgeCapabilities.ITEM_HANDLER);
IItemHandler teHandler = capability.orElse(null);
if (!(teHandler instanceof IItemHandlerModifiable))
return;
IItemHandlerModifiable inv = (IItemHandlerModifiable) teHandler;
for (int slot = 0; slot < Math.min(inv.getSlots(), handler.getSlots()); slot++)
inv.setStackInSlot(slot, handler.getStackInSlot(slot));
}
public void tick(Entity entity, BlockPos pos, boolean isRemote) {
if (isRemote)
return;
if (packetCooldown > 0) {
packetCooldown--;
return;
}
if (sendPacket) {
sendPacket = false;
AllPackets.getChannel()
.send(PacketDistributor.TRACKING_ENTITY.with(() -> entity),
new ContraptionItemPacket(entity.getId(), pos, handler));
packetCooldown = 8;
}
}
public void updateItems(List<ItemStack> containedItems) {
for (int i = 0; i < Math.min(containedItems.size(), handler.getSlots()); i++)
handler.setStackInSlot(i, containedItems.get(i));
if (blockEntity instanceof DepotBlockEntity depot)
depot.setHeldItem(handler.getStackInSlot(0));
}
public IItemHandlerModifiable getItemHandler() {
return handler;
}
public CompoundTag serialize() {
if (!valid)
return null;
CompoundTag tag = handler.serializeNBT();
if (noFuel)
NBTHelper.putMarker(tag, "NoFuel");
if (handler instanceof ToolboxInventory)
NBTHelper.putMarker(tag, "Toolbox");
if (needsSync())
NBTHelper.putMarker(tag, "Synced");
if (!(handler instanceof BottomlessItemHandler))
return tag;
NBTHelper.putMarker(tag, "Bottomless");
tag.put("ProvidedStack", handler.getStackInSlot(0)
.serializeNBT());
return tag;
}
public static MountedStorage deserialize(CompoundTag nbt) {
MountedStorage storage = new MountedStorage(null);
storage.handler = new ItemStackHandler();
if (nbt == null)
return storage;
if (nbt.contains("Toolbox"))
storage.handler = new ToolboxInventory(null);
if (nbt.contains("Synced"))
storage.handler = storage.new SyncedMountedItemStackHandler(1);
storage.valid = true;
storage.noFuel = nbt.contains("NoFuel");
if (nbt.contains("Bottomless")) {
ItemStack providedStack = ItemStack.of(nbt.getCompound("ProvidedStack"));
storage.handler = new BottomlessItemHandler(() -> providedStack);
return storage;
}
storage.handler.deserializeNBT(nbt);
return storage;
}
public boolean isValid() {
return valid;
}
public boolean canUseForFuel() {
return !noFuel;
}
public boolean needsSync() {
return handler instanceof SyncedMountedItemStackHandler;
}
public class SyncedMountedItemStackHandler extends ItemStackHandler {
public SyncedMountedItemStackHandler(int i) {
super(i);
}
@Override
protected void onContentsChanged(int slot) {
sendPacket = true;
}
}
}

View file

@ -1,68 +0,0 @@
package com.simibubi.create.content.contraptions;
import java.util.List;
import java.util.function.Supplier;
import com.google.common.collect.ImmutableList;
import com.simibubi.create.foundation.utility.CreateLang;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Mth;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.ChestMenu;
import net.minecraft.world.inventory.MenuType;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.wrapper.RecipeWrapper;
public class MountedStorageInteraction {
public static final List<MenuType<?>> menus = ImmutableList.of(MenuType.GENERIC_9x1, MenuType.GENERIC_9x2,
MenuType.GENERIC_9x3, MenuType.GENERIC_9x4, MenuType.GENERIC_9x5, MenuType.GENERIC_9x6);
public static MenuProvider createMenuProvider(Component displayName, IItemHandlerModifiable handler,
int slotCount, Supplier<Boolean> stillValid) {
int rows = Mth.clamp(slotCount / 9, 1, 6);
MenuType<?> menuType = menus.get(rows - 1);
Component menuName = CreateLang.translateDirect("contraptions.moving_container", displayName);
return new MenuProvider() {
@Override
public AbstractContainerMenu createMenu(int pContainerId, Inventory pPlayerInventory, Player pPlayer) {
return new ChestMenu(menuType, pContainerId, pPlayerInventory, new StorageInteractionContainer(handler, stillValid),
rows);
}
@Override
public Component getDisplayName() {
return menuName;
}
};
}
public static class StorageInteractionContainer extends RecipeWrapper {
private Supplier<Boolean> stillValid;
public StorageInteractionContainer(IItemHandlerModifiable inv, Supplier<Boolean> stillValid) {
super(inv);
this.stillValid = stillValid;
}
@Override
public boolean stillValid(Player player) {
return stillValid.get();
}
@Override
public int getMaxStackSize() {
return 64;
}
}
}

View file

@ -1,289 +1,474 @@
package com.simibubi.create.content.contraptions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.Set;
import java.util.function.Predicate;
import com.simibubi.create.content.contraptions.Contraption.ContraptionInvWrapper;
import com.simibubi.create.content.fluids.tank.FluidTankBlockEntity;
import com.simibubi.create.content.logistics.depot.DepotBlockEntity;
import com.simibubi.create.foundation.fluid.CombinedTankWrapper;
import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.mojang.datafixers.util.Pair;
import com.simibubi.create.AllPackets;
import com.simibubi.create.Create;
import com.simibubi.create.api.contraption.storage.MountedStorageTypeRegistry;
import com.simibubi.create.api.contraption.storage.SyncedMountedStorage;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorage;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageType;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageWrapper;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorage;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageWrapper;
import com.simibubi.create.content.equipment.toolbox.ToolboxMountedStorage;
import com.simibubi.create.content.fluids.tank.storage.FluidTankMountedStorage;
import com.simibubi.create.content.fluids.tank.storage.creative.CreativeFluidTankMountedStorage;
import com.simibubi.create.content.logistics.crate.CreativeCrateMountedStorage;
import com.simibubi.create.content.logistics.depot.storage.DepotMountedStorage;
import com.simibubi.create.content.logistics.vault.ItemVaultMountedStorage;
import com.simibubi.create.impl.contraption.storage.FallbackMountedStorage;
import net.createmod.catnip.lang.Components;
import net.createmod.catnip.nbt.NBTHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.ChestBlock;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.properties.ChestType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
import net.minecraftforge.fluids.capability.templates.FluidTank;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.items.wrapper.CombinedInvWrapper;
import net.minecraftforge.network.PacketDistributor;
public class MountedStorageManager {
// builders used during assembly, null afterward
// ImmutableMap.Builder is not used because it will throw with duplicate keys, not override them
private Map<BlockPos, MountedItemStorage> itemsBuilder;
private Map<BlockPos, MountedFluidStorage> fluidsBuilder;
private Map<BlockPos, SyncedMountedStorage> syncedItemsBuilder;
private Map<BlockPos, SyncedMountedStorage> syncedFluidsBuilder;
protected ContraptionInvWrapper inventory;
protected ContraptionInvWrapper fuelInventory;
protected CombinedTankWrapper fluidInventory;
protected Map<BlockPos, MountedStorage> storage;
protected Map<BlockPos, MountedFluidStorage> fluidStorage;
// built data structures after assembly, null before
private ImmutableMap<BlockPos, MountedItemStorage> allItemStorages;
// different from allItemStorages, does not contain internal ones
protected MountedItemStorageWrapper items;
@Nullable
protected MountedItemStorageWrapper fuelItems;
protected MountedFluidStorageWrapper fluids;
private ImmutableMap<BlockPos, SyncedMountedStorage> syncedItems;
private ImmutableMap<BlockPos, SyncedMountedStorage> syncedFluids;
private List<IItemHandlerModifiable> externalHandlers;
private CombinedInvWrapper allItems;
// ticks until storage can sync again
private int syncCooldown;
// client-side: not all storages are synced, this determines which interactions are valid
private Set<BlockPos> interactablePositions;
public MountedStorageManager() {
storage = new TreeMap<>();
fluidStorage = new TreeMap<>();
this.reset();
}
public void entityTick(AbstractContraptionEntity entity) {
boolean isClientSide = entity.level().isClientSide;
storage.forEach((pos, mfs) -> mfs.tick(entity, pos, isClientSide));
fluidStorage.forEach((pos, mfs) -> mfs.tick(entity, pos, isClientSide));
}
public void createHandlers() {
Collection<MountedStorage> itemHandlers = storage.values();
inventory = wrapItems(itemHandlers.stream()
.map(MountedStorage::getItemHandler)
.toList(), false);
fuelInventory = wrapItems(itemHandlers.stream()
.filter(MountedStorage::canUseForFuel)
.map(MountedStorage::getItemHandler)
.toList(), true);
fluidInventory = wrapFluids(fluidStorage.values()
.stream()
.map(MountedFluidStorage::getFluidHandler)
.collect(Collectors.toList()));
}
protected ContraptionInvWrapper wrapItems(Collection<IItemHandlerModifiable> list, boolean fuel) {
return new ContraptionInvWrapper(Arrays.copyOf(list.toArray(), list.size(), IItemHandlerModifiable[].class));
}
protected CombinedTankWrapper wrapFluids(Collection<IFluidHandler> list) {
return new CombinedTankWrapper(Arrays.copyOf(list.toArray(), list.size(), IFluidHandler[].class));
}
public void addBlock(BlockPos localPos, BlockEntity be) {
if (be != null && MountedStorage.canUseAsStorage(be))
storage.put(localPos, new MountedStorage(be));
if (be != null && MountedFluidStorage.canUseAsStorage(be))
fluidStorage.put(localPos, new MountedFluidStorage(be));
}
public void read(CompoundTag nbt, Map<BlockPos, BlockEntity> presentBlockEntities, boolean clientPacket) {
storage.clear();
NBTHelper.iterateCompoundList(nbt.getList("Storage", Tag.TAG_COMPOUND), c -> storage
.put(NbtUtils.readBlockPos(c.getCompound("Pos")), MountedStorage.deserialize(c.getCompound("Data"))));
fluidStorage.clear();
NBTHelper.iterateCompoundList(nbt.getList("FluidStorage", Tag.TAG_COMPOUND), c -> fluidStorage
.put(NbtUtils.readBlockPos(c.getCompound("Pos")), MountedFluidStorage.deserialize(c.getCompound("Data"))));
if (clientPacket && presentBlockEntities != null)
bindTanksAndDepots(presentBlockEntities);
List<IItemHandlerModifiable> handlers = new ArrayList<>();
List<IItemHandlerModifiable> fuelHandlers = new ArrayList<>();
for (MountedStorage mountedStorage : storage.values()) {
IItemHandlerModifiable itemHandler = mountedStorage.getItemHandler();
handlers.add(itemHandler);
if (mountedStorage.canUseForFuel())
fuelHandlers.add(itemHandler);
public void initialize() {
if (this.isInitialized()) {
throw new IllegalStateException("Mounted storage has already been initialized");
}
inventory = wrapItems(handlers, false);
fuelInventory = wrapItems(fuelHandlers, true);
fluidInventory = wrapFluids(fluidStorage.values()
.stream()
.map(MountedFluidStorage::getFluidHandler)
.toList());
this.allItemStorages = ImmutableMap.copyOf(this.itemsBuilder);
this.items = new MountedItemStorageWrapper(subMap(
this.allItemStorages, storage -> !storage.isInternal()
));
this.allItems = this.items;
this.itemsBuilder = null;
ImmutableMap<BlockPos, MountedItemStorage> fuelMap = subMap(
this.allItemStorages, storage -> !storage.isInternal() && storage.providesFuel()
);
this.fuelItems = fuelMap.isEmpty() ? null : new MountedItemStorageWrapper(fuelMap);
ImmutableMap<BlockPos, MountedFluidStorage> fluids = ImmutableMap.copyOf(this.fluidsBuilder);
this.fluids = new MountedFluidStorageWrapper(fluids);
this.fluidsBuilder = null;
this.syncedItems = ImmutableMap.copyOf(this.syncedItemsBuilder);
this.syncedItemsBuilder = null;
this.syncedFluids = ImmutableMap.copyOf(this.syncedFluidsBuilder);
this.syncedFluidsBuilder = null;
}
public void bindTanksAndDepots(Map<BlockPos, BlockEntity> presentBlockEntities) {
for (Entry<BlockPos, MountedStorage> entry : storage.entrySet()) {
BlockEntity blockEntity = presentBlockEntities.get(entry.getKey());
if (!(blockEntity instanceof DepotBlockEntity depot))
return;
depot.setHeldItem(entry.getValue().handler.getStackInSlot(0));
entry.getValue().blockEntity = depot;
}
for (Entry<BlockPos, MountedFluidStorage> entry : fluidStorage.entrySet()) {
BlockEntity blockEntity = presentBlockEntities.get(entry.getKey());
if (!(blockEntity instanceof FluidTankBlockEntity tank))
return;
IFluidTank tankInventory = tank.getTankInventory();
MountedFluidStorage mfs = entry.getValue();
if (tankInventory instanceof FluidTank)
((FluidTank) tankInventory).setFluid(mfs.tank.getFluid());
tank.getFluidLevel()
.startWithValue(tank.getFillState());
mfs.assignBlockEntity(tank);
private boolean isInitialized() {
return this.itemsBuilder == null;
}
private void assertInitialized() {
if (!this.isInitialized()) {
throw new IllegalStateException("MountedStorageManager is uninitialized");
}
}
public void write(CompoundTag nbt, boolean clientPacket) {
ListTag storageNBT = new ListTag();
for (BlockPos pos : storage.keySet()) {
CompoundTag c = new CompoundTag();
MountedStorage mountedStorage = storage.get(pos);
if (!mountedStorage.isValid())
continue;
if (clientPacket && !mountedStorage.needsSync())
continue;
c.put("Pos", NbtUtils.writeBlockPos(pos));
c.put("Data", mountedStorage.serialize());
storageNBT.add(c);
}
ListTag fluidStorageNBT = new ListTag();
for (BlockPos pos : fluidStorage.keySet()) {
CompoundTag c = new CompoundTag();
MountedFluidStorage mountedStorage = fluidStorage.get(pos);
if (!mountedStorage.isValid())
continue;
c.put("Pos", NbtUtils.writeBlockPos(pos));
c.put("Data", mountedStorage.serialize());
fluidStorageNBT.add(c);
}
nbt.put("Storage", storageNBT);
nbt.put("FluidStorage", fluidStorageNBT);
protected void reset() {
this.allItemStorages = null;
this.items = null;
this.fuelItems = null;
this.fluids = null;
this.externalHandlers = new ArrayList<>();
this.allItems = null;
this.itemsBuilder = new HashMap<>();
this.fluidsBuilder = new HashMap<>();
this.syncedItemsBuilder = new HashMap<>();
this.syncedFluidsBuilder = new HashMap<>();
// interactablePositions intentionally not reset
}
public void removeStorageFromWorld() {
storage.values()
.forEach(MountedStorage::removeStorageFromWorld);
fluidStorage.values()
.forEach(MountedFluidStorage::removeStorageFromWorld);
}
public void addStorageToWorld(StructureBlockInfo block, BlockEntity blockEntity) {
if (storage.containsKey(block.pos())) {
MountedStorage mountedStorage = storage.get(block.pos());
if (mountedStorage.isValid())
mountedStorage.addStorageToWorld(blockEntity);
}
if (fluidStorage.containsKey(block.pos())) {
MountedFluidStorage mountedStorage = fluidStorage.get(block.pos());
if (mountedStorage.isValid())
mountedStorage.addStorageToWorld(blockEntity);
}
}
public void clear() {
for (int i = 0; i < inventory.getSlots(); i++)
if (!inventory.isSlotExternal(i))
inventory.setStackInSlot(i, ItemStack.EMPTY);
for (int i = 0; i < fluidInventory.getTanks(); i++)
fluidInventory.drain(fluidInventory.getFluidInTank(i), FluidAction.EXECUTE);
}
public void updateContainedFluid(BlockPos localPos, FluidStack containedFluid) {
MountedFluidStorage mountedFluidStorage = fluidStorage.get(localPos);
if (mountedFluidStorage != null)
mountedFluidStorage.updateFluid(containedFluid);
}
public void updateContainedItem(BlockPos localPos, List<ItemStack> containedItems) {
MountedStorage mountedStorage = storage.get(localPos);
if (mountedStorage != null)
mountedStorage.updateItems(containedItems);
}
public void attachExternal(IItemHandlerModifiable externalStorage) {
inventory = new ContraptionInvWrapper(externalStorage, inventory);
fuelInventory = new ContraptionInvWrapper(externalStorage, fuelInventory);
}
public IItemHandlerModifiable getItems() {
return inventory;
}
public IItemHandlerModifiable getFuelItems() {
return fuelInventory;
}
public IFluidHandler getFluids() {
return fluidInventory;
}
public Map<BlockPos, MountedStorage> getMountedItemStorage() {
return storage;
}
public Map<BlockPos, MountedFluidStorage> getMountedFluidStorage() {
return fluidStorage;
}
public boolean handlePlayerStorageInteraction(Contraption contraption, Player player, BlockPos localPos) {
if (player.level().isClientSide()) {
BlockEntity localBE = contraption.presentBlockEntities.get(localPos);
return MountedStorage.canUseAsStorage(localBE);
}
MountedStorageManager storageManager = contraption.getStorageForSpawnPacket();
MountedStorage storage = storageManager.storage.get(localPos);
if (storage == null || storage.getItemHandler() == null)
return false;
IItemHandlerModifiable handler = storage.getItemHandler();
StructureBlockInfo info = contraption.getBlocks()
.get(localPos);
if (info != null && info.state().hasProperty(ChestBlock.TYPE)) {
ChestType chestType = info.state().getValue(ChestBlock.TYPE);
Direction facing = info.state().getOptionalValue(ChestBlock.FACING)
.orElse(Direction.SOUTH);
Direction connectedDirection =
chestType == ChestType.LEFT ? facing.getClockWise() : facing.getCounterClockWise();
if (chestType != ChestType.SINGLE) {
MountedStorage storage2 = storageManager.storage.get(localPos.relative(connectedDirection));
if (storage2 != null && storage2.getItemHandler() != null)
handler = chestType == ChestType.RIGHT ? new CombinedInvWrapper(handler, storage2.getItemHandler())
: new CombinedInvWrapper(storage2.getItemHandler(), handler);
public void addBlock(Level level, BlockState state, BlockPos globalPos, BlockPos localPos, @Nullable BlockEntity be) {
MountedItemStorageType<?> itemType = MountedStorageTypeRegistry.ITEM_LOOKUP.find(state);
if (itemType != null) {
MountedItemStorage storage = itemType.mount(level, state, globalPos, be);
if (storage != null) {
this.addStorage(storage, localPos);
}
}
int slotCount = handler.getSlots();
if (slotCount == 0)
return false;
if (slotCount % 9 != 0)
return false;
Supplier<Boolean> stillValid = () -> contraption.entity.isAlive()
&& player.distanceToSqr(contraption.entity.toGlobalVector(Vec3.atCenterOf(localPos), 0)) < 64;
Component name = info != null ? info.state().getBlock()
.getName() : Components.literal("Container");
player.openMenu(MountedStorageInteraction.createMenuProvider(name, handler, slotCount, stillValid));
Vec3 soundPos = contraption.entity.toGlobalVector(Vec3.atCenterOf(localPos), 0);
player.level().playSound(null, BlockPos.containing(soundPos), SoundEvents.BARREL_OPEN, SoundSource.BLOCKS, 0.75f, 1f);
return true;
MountedFluidStorageType<?> fluidType = MountedStorageTypeRegistry.FLUID_LOOKUP.find(state);
if (fluidType != null) {
MountedFluidStorage storage = fluidType.mount(level, state, globalPos, be);
if (storage != null) {
this.addStorage(storage, localPos);
}
}
}
public void unmount(Level level, StructureBlockInfo info, BlockPos globalPos, @Nullable BlockEntity be) {
BlockPos localPos = info.pos();
BlockState state = info.state();
MountedItemStorage itemStorage = this.getAllItemStorages().get(localPos);
if (itemStorage != null) {
MountedItemStorageType<?> expectedType = MountedStorageTypeRegistry.ITEM_LOOKUP.find(state);
if (itemStorage.type == expectedType) {
itemStorage.unmount(level, state, globalPos, be);
}
}
MountedFluidStorage fluidStorage = this.getFluids().storages.get(localPos);
if (fluidStorage != null) {
MountedFluidStorageType<?> expectedType = MountedStorageTypeRegistry.FLUID_LOOKUP.find(state);
if (fluidStorage.type == expectedType) {
fluidStorage.unmount(level, state, globalPos, be);
}
}
}
public void tick(AbstractContraptionEntity entity) {
if (this.syncCooldown > 0) {
this.syncCooldown--;
return;
}
Map<BlockPos, MountedItemStorage> items = new HashMap<>();
Map<BlockPos, MountedFluidStorage> fluids = new HashMap<>();
this.syncedItems.forEach((pos, storage) -> {
if (storage.isDirty()) {
items.put(pos, (MountedItemStorage) storage);
storage.markClean();
}
});
this.syncedFluids.forEach((pos, storage) -> {
if (storage.isDirty()) {
fluids.put(pos, (MountedFluidStorage) storage);
storage.markClean();
}
});
if (!items.isEmpty() || !fluids.isEmpty()) {
MountedStorageSyncPacket packet = new MountedStorageSyncPacket(entity.getId(), items, fluids);
AllPackets.getChannel().send(PacketDistributor.TRACKING_ENTITY.with(() -> entity), packet);
this.syncCooldown = 8;
}
}
public void handleSync(MountedStorageSyncPacket packet, AbstractContraptionEntity entity) {
// packet only contains changed storages, grab existing ones before resetting
ImmutableMap<BlockPos, MountedItemStorage> items = this.getAllItemStorages();
MountedFluidStorageWrapper fluids = this.getFluids();
this.reset();
// track freshly synced storages
Map<SyncedMountedStorage, BlockPos> syncedStorages = new IdentityHashMap<>();
try {
// re-add existing ones
this.itemsBuilder.putAll(items);
this.fluidsBuilder.putAll(fluids.storages);
// add newly synced ones, overriding existing ones if present
packet.items.forEach((pos, storage) -> {
this.itemsBuilder.put(pos, storage);
syncedStorages.put((SyncedMountedStorage) storage, pos);
});
packet.fluids.forEach((pos, storage) -> {
this.fluidsBuilder.put(pos, storage);
syncedStorages.put((SyncedMountedStorage) storage, pos);
});
} catch (Throwable t) {
// an exception will leave the manager in an invalid state
Create.LOGGER.error("An error occurred while syncing a MountedStorageManager", t);
}
this.initialize();
// call all afterSync methods
Contraption contraption = entity.getContraption();
syncedStorages.forEach((storage, pos) -> storage.afterSync(contraption, pos));
}
// contraption is provided on the client for initial afterSync storage callbacks
public void read(CompoundTag nbt, boolean clientPacket, @Nullable Contraption contraption) {
this.reset();
try {
NBTHelper.iterateCompoundList(nbt.getList("items", Tag.TAG_COMPOUND), tag -> {
BlockPos pos = NbtUtils.readBlockPos(tag.getCompound("pos"));
CompoundTag data = tag.getCompound("storage");
MountedItemStorage.CODEC.decode(NbtOps.INSTANCE, data)
.result()
.map(Pair::getFirst)
.ifPresent(storage -> this.addStorage(storage, pos));
});
NBTHelper.iterateCompoundList(nbt.getList("fluids", Tag.TAG_COMPOUND), tag -> {
BlockPos pos = NbtUtils.readBlockPos(tag.getCompound("pos"));
CompoundTag data = tag.getCompound("storage");
MountedFluidStorage.CODEC.decode(NbtOps.INSTANCE, data)
.result()
.map(Pair::getFirst)
.ifPresent(storage -> this.addStorage(storage, pos));
});
this.readLegacy(nbt);
if (nbt.contains("interactable_positions")) {
this.interactablePositions = new HashSet<>();
NBTHelper.iterateCompoundList(nbt.getList("interactable_positions", Tag.TAG_COMPOUND), tag -> {
BlockPos pos = NbtUtils.readBlockPos(tag);
this.interactablePositions.add(pos);
});
}
} catch (Throwable t) {
Create.LOGGER.error("Error deserializing mounted storage", t);
// an exception will leave the manager in an invalid state, initialize must be called
}
this.initialize();
// for client sync, run initial afterSync callbacks
if (!clientPacket || contraption == null)
return;
this.getAllItemStorages().forEach((pos, storage) -> {
if (storage instanceof SyncedMountedStorage synced) {
synced.afterSync(contraption, pos);
}
});
this.getFluids().storages.forEach((pos, storage) -> {
if (storage instanceof SyncedMountedStorage synced) {
synced.afterSync(contraption, pos);
}
});
}
public void write(CompoundTag nbt, boolean clientPacket) {
ListTag items = new ListTag();
this.getAllItemStorages().forEach((pos, storage) -> {
if (!clientPacket || storage instanceof SyncedMountedStorage) {
MountedItemStorage.CODEC.encodeStart(NbtOps.INSTANCE, storage).result().ifPresent(encoded -> {
CompoundTag tag = new CompoundTag();
tag.put("pos", NbtUtils.writeBlockPos(pos));
tag.put("storage", encoded);
items.add(tag);
});
}
}
);
if (!items.isEmpty()) {
nbt.put("items", items);
}
ListTag fluids = new ListTag();
this.getFluids().storages.forEach((pos, storage) -> {
if (!clientPacket || storage instanceof SyncedMountedStorage) {
MountedFluidStorage.CODEC.encodeStart(NbtOps.INSTANCE, storage).result().ifPresent(encoded -> {
CompoundTag tag = new CompoundTag();
tag.put("pos", NbtUtils.writeBlockPos(pos));
tag.put("storage", encoded);
fluids.add(tag);
});
}
}
);
if (!fluids.isEmpty()) {
nbt.put("fluids", fluids);
}
if (clientPacket) {
// let the client know of all non-synced ones too
SetView<BlockPos> positions = Sets.union(this.getAllItemStorages().keySet(), this.getFluids().storages.keySet());
ListTag list = new ListTag();
for (BlockPos pos : positions) {
list.add(NbtUtils.writeBlockPos(pos));
}
nbt.put("interactable_positions", list);
}
}
public void attachExternal(IItemHandlerModifiable externalStorage) {
this.externalHandlers.add(externalStorage);
IItemHandlerModifiable[] all = new IItemHandlerModifiable[this.externalHandlers.size() + 1];
all[0] = this.items;
for (int i = 0; i < this.externalHandlers.size(); i++) {
all[i + 1] = this.externalHandlers.get(i);
}
this.allItems = new CombinedInvWrapper(all);
}
/**
* The primary way to access a contraption's inventory. Includes all
* non-internal mounted storages as well as all external storage.
*/
public CombinedInvWrapper getAllItems() {
this.assertInitialized();
return this.allItems;
}
/**
* Gets a map of all MountedItemStorages in the contraption, irrelevant of them
* being internal or providing fuel.
* @see MountedItemStorage#isInternal()
* @see MountedItemStorage#providesFuel()
*/
public ImmutableMap<BlockPos, MountedItemStorage> getAllItemStorages() {
this.assertInitialized();
return this.allItemStorages;
}
/**
* Gets an item handler wrapping all non-internal mounted storages. This is not
* the whole contraption inventory as it does not include external storages.
* Most often, you want {@link #getAllItems()}, which does.
*/
public MountedItemStorageWrapper getMountedItems() {
this.assertInitialized();
return this.items;
}
/**
* Gets an item handler wrapping all non-internal mounted storages that provide fuel.
* May be null if none are present.
*/
@Nullable
public MountedItemStorageWrapper getFuelItems() {
this.assertInitialized();
return this.fuelItems;
}
/**
* Gets a fluid handler wrapping all mounted fluid storages.
*/
public MountedFluidStorageWrapper getFluids() {
this.assertInitialized();
return this.fluids;
}
public boolean handlePlayerStorageInteraction(Contraption contraption, Player player, BlockPos localPos) {
if (!(player instanceof ServerPlayer serverPlayer)) {
return this.interactablePositions != null && this.interactablePositions.contains(localPos);
}
StructureBlockInfo info = contraption.getBlocks().get(localPos);
if (info == null)
return false;
MountedStorageManager storageManager = contraption.getStorage();
MountedItemStorage storage = storageManager.getAllItemStorages().get(localPos);
if (storage != null) {
return storage.handleInteraction(serverPlayer, contraption, info);
} else {
return false;
}
}
private void readLegacy(CompoundTag nbt) {
NBTHelper.iterateCompoundList(nbt.getList("Storage", Tag.TAG_COMPOUND), tag -> {
BlockPos pos = NbtUtils.readBlockPos(tag.getCompound("Pos"));
CompoundTag data = tag.getCompound("Data");
if (data.contains("Toolbox")) {
this.addStorage(ToolboxMountedStorage.fromLegacy(data), pos);
} else if (data.contains("NoFuel")) {
this.addStorage(ItemVaultMountedStorage.fromLegacy(data), pos);
} else if (data.contains("Bottomless")) {
ItemStack supplied = ItemStack.of(data.getCompound("ProvidedStack"));
this.addStorage(new CreativeCrateMountedStorage(supplied), pos);
} else if (data.contains("Synced")) {
this.addStorage(DepotMountedStorage.fromLegacy(data), pos);
} else {
// we can create a fallback storage safely, it will be validated before unmounting
ItemStackHandler handler = new ItemStackHandler();
handler.deserializeNBT(data);
this.addStorage(new FallbackMountedStorage(handler), pos);
}
});
NBTHelper.iterateCompoundList(nbt.getList("FluidStorage", Tag.TAG_COMPOUND), tag -> {
BlockPos pos = NbtUtils.readBlockPos(tag.getCompound("Pos"));
CompoundTag data = tag.getCompound("Data");
if (data.contains("Bottomless")) {
this.addStorage(CreativeFluidTankMountedStorage.fromLegacy(data), pos);
} else {
this.addStorage(FluidTankMountedStorage.fromLegacy(data), pos);
}
});
}
private void addStorage(MountedItemStorage storage, BlockPos pos) {
this.itemsBuilder.put(pos, storage);
if (storage instanceof SyncedMountedStorage synced)
this.syncedItemsBuilder.put(pos, synced);
}
private void addStorage(MountedFluidStorage storage, BlockPos pos) {
this.fluidsBuilder.put(pos, storage);
if (storage instanceof SyncedMountedStorage synced)
this.syncedFluidsBuilder.put(pos, synced);
}
private static <K, V> ImmutableMap<K, V> subMap(Map<K, V> map, Predicate<V> predicate) {
ImmutableMap.Builder<K, V> builder = ImmutableMap.builder();
map.forEach((key, value) -> {
if (predicate.test(value)) {
builder.put(key, value);
}
});
return builder.build();
}
}

View file

@ -0,0 +1,63 @@
package com.simibubi.create.content.contraptions;
import java.util.Map;
import com.mojang.serialization.Codec;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorage;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorage;
import com.simibubi.create.foundation.networking.SimplePacketBase;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.NbtOps;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.network.NetworkEvent.Context;
public class MountedStorageSyncPacket extends SimplePacketBase {
public final int contraptionId;
public final Map<BlockPos, MountedItemStorage> items;
public final Map<BlockPos, MountedFluidStorage> fluids;
public MountedStorageSyncPacket(int contraptionId, Map<BlockPos, MountedItemStorage> items, Map<BlockPos, MountedFluidStorage> fluids) {
this.contraptionId = contraptionId;
this.items = items;
this.fluids = fluids;
}
public MountedStorageSyncPacket(FriendlyByteBuf buf) {
this.contraptionId = buf.readVarInt();
this.items = read(buf, MountedItemStorage.CODEC);
this.fluids = read(buf, MountedFluidStorage.CODEC);
}
@Override
public void write(FriendlyByteBuf buffer) {
buffer.writeVarInt(this.contraptionId);
write(buffer, this.items, MountedItemStorage.CODEC);
write(buffer, this.fluids, MountedFluidStorage.CODEC);
}
@SuppressWarnings("deprecation")
private static <T> void write(FriendlyByteBuf buf, Map<BlockPos, T> map, Codec<T> codec) {
buf.writeMap(map, FriendlyByteBuf::writeBlockPos, (b, t) -> b.writeWithCodec(NbtOps.INSTANCE, codec, t));
}
@SuppressWarnings("deprecation")
private static <T> Map<BlockPos, T> read(FriendlyByteBuf buf, Codec<T> codec) {
return buf.readMap(FriendlyByteBuf::readBlockPos, (b) -> b.readWithCodec(NbtOps.INSTANCE, codec));
}
@Override
public boolean handle(Context context) {
context.enqueueWork(() -> {
Entity entity = Minecraft.getInstance().level.getEntity(this.contraptionId);
if (!(entity instanceof AbstractContraptionEntity contraption))
return;
contraption.getContraption().getStorage().handleSync(this, contraption);
});
return true;
}
}

View file

@ -9,6 +9,7 @@ import javax.annotation.Nullable;
import com.mojang.blaze3d.vertex.PoseStack;
import com.simibubi.create.AllEntityTypes;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageWrapper;
import com.simibubi.create.content.contraptions.bearing.StabilizedContraption;
import com.simibubi.create.content.contraptions.minecart.MinecartSim2020;
import com.simibubi.create.content.contraptions.minecart.capability.CapabilityMinecartController;
@ -435,9 +436,12 @@ public class OrientedContraptionEntity extends AbstractContraptionEntity {
.normalize()
.scale(1));
if (fuel < 5 && contraption != null) {
ItemStack coal = ItemHelper.extract(contraption.getSharedInventory(), FUEL_ITEMS, 1, false);
if (!coal.isEmpty())
fuel += 3600;
MountedItemStorageWrapper fuelItems = contraption.getStorage().getFuelItems();
if (fuelItems != null) {
ItemStack coal = ItemHelper.extract(fuelItems, FUEL_ITEMS, 1, false);
if (!coal.isEmpty())
fuel += 3600;
}
}
if (fuel != fuelBefore || pushX != 0 || pushZ != 0) {

View file

@ -24,7 +24,7 @@ public class PortableFluidInterfaceBlockEntity extends PortableStorageInterfaceB
@Override
public void startTransferringTo(Contraption contraption, float distance) {
LazyOptional<IFluidHandler> oldcap = capability;
capability = LazyOptional.of(() -> new InterfaceFluidHandler(contraption.getSharedFluidTanks()));
capability = LazyOptional.of(() -> new InterfaceFluidHandler(contraption.getStorage().getFluids()));
oldcap.invalidate();
super.startTransferringTo(contraption, distance);
}

View file

@ -25,7 +25,7 @@ public class PortableItemInterfaceBlockEntity extends PortableStorageInterfaceBl
@Override
public void startTransferringTo(Contraption contraption, float distance) {
LazyOptional<IItemHandlerModifiable> oldCap = capability;
capability = LazyOptional.of(() -> new InterfaceItemHandler(contraption.getSharedInventory()));
capability = LazyOptional.of(() -> new InterfaceItemHandler(contraption.getStorage().getAllItems()));
oldCap.invalidate();
super.startTransferringTo(contraption, distance);
}

View file

@ -197,7 +197,7 @@ public class RollerMovementBehaviour extends BlockBreakingMovementBehaviour {
if (!getStateToPaveWith(context).isAir()) {
FilterItemStack filter = context.getFilterFromBE();
if (!ItemHelper
.extract(context.contraption.getSharedInventory(),
.extract(context.contraption.getStorage().getAllItems(),
stack -> filter.test(context.world, stack), 1, true)
.isEmpty())
startingY = 0;
@ -476,7 +476,7 @@ public class RollerMovementBehaviour extends BlockBreakingMovementBehaviour {
return PaveResult.FAIL;
FilterItemStack filter = context.getFilterFromBE();
ItemStack held = ItemHelper.extract(context.contraption.getSharedInventory(),
ItemStack held = ItemHelper.extract(context.contraption.getStorage().getAllItems(),
stack -> filter.test(context.world, stack), 1, false);
if (held.isEmpty())
return PaveResult.FAIL;

View file

@ -57,11 +57,11 @@ public class BearingContraption extends Contraption {
}
@Override
public void addBlock(BlockPos pos, Pair<StructureBlockInfo, BlockEntity> capture) {
public void addBlock(Level level, BlockPos pos, Pair<StructureBlockInfo, BlockEntity> capture) {
BlockPos localPos = pos.subtract(anchor);
if (!getBlocks().containsKey(localPos) && AllBlockTags.WINDMILL_SAILS.matches(getSailBlock(capture)))
sailBlocks++;
super.addBlock(pos, capture);
super.addBlock(level, pos, capture);
}
private BlockState getSailBlock(Pair<StructureBlockInfo, BlockEntity> capture) {

View file

@ -53,7 +53,7 @@ public interface MovementBehaviour {
default void dropItem(MovementContext context, ItemStack stack) {
ItemStack remainder;
if (AllConfigs.server().kinetics.moveItemsToStorage.get())
remainder = ItemHandlerHelper.insertItem(context.contraption.getSharedInventory(), stack, false);
remainder = ItemHandlerHelper.insertItem(context.contraption.getStorage().getAllItems(), stack, false);
else
remainder = stack;
if (remainder.isEmpty())

View file

@ -1,7 +1,13 @@
package com.simibubi.create.content.contraptions.behaviour;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import org.jetbrains.annotations.Nullable;
import com.google.common.base.Suppliers;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorage;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorage;
import com.simibubi.create.content.contraptions.Contraption;
import com.simibubi.create.content.logistics.filter.FilterItemStack;
@ -35,6 +41,9 @@ public class MovementContext {
private FilterItemStack filter;
private final Supplier<MountedItemStorage> itemStorage;
private final Supplier<MountedFluidStorage> fluidStorage;
public MovementContext(Level world, StructureBlockInfo info, Contraption contraption) {
this.world = world;
this.state = info.state();
@ -51,6 +60,8 @@ public class MovementContext {
data = new CompoundTag();
stall = false;
filter = null;
this.itemStorage = Suppliers.memoize(() -> contraption.getStorage().getAllItemStorages().get(this.localPos));
this.fluidStorage = Suppliers.memoize(() -> contraption.getStorage().getFluids().storages.get(this.localPos));
}
public float getAnimationSpeed() {
@ -94,4 +105,13 @@ public class MovementContext {
return filter = FilterItemStack.of(blockEntityData.getCompound("Filter"));
}
@Nullable
public MountedItemStorage getItemStorage() {
return this.itemStorage.get();
}
@Nullable
public MountedFluidStorage getFluidStorage() {
return this.fluidStorage.get();
}
}

View file

@ -1,11 +1,18 @@
package com.simibubi.create.content.contraptions.behaviour.dispenser;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
import com.simibubi.create.foundation.mixin.accessor.DispenserBlockAccessor;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.core.BlockSource;
import net.minecraft.core.Direction;
import net.minecraft.core.dispenser.AbstractProjectileDispenseBehavior;
import net.minecraft.core.dispenser.DefaultDispenseItemBehavior;
@ -14,11 +21,13 @@ import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.Vec3;
public class DispenserMovementBehaviour extends DropperMovementBehaviour {
private static final HashMap<Item, IMovedDispenseItemBehaviour> MOVED_DISPENSE_ITEM_BEHAVIOURS = new HashMap<>();
private static final HashMap<Item, IMovedDispenseItemBehaviour> MOVED_PROJECTILE_DISPENSE_BEHAVIOURS = new HashMap<>();
private static final Map<Item, IMovedDispenseItemBehaviour> movedDispenseItemBehaviors = new HashMap<>();
private static final Set<Item> blacklist = new HashSet<>();
private static boolean spawnEggsRegistered = false;
public static void gatherMovedDispenseItemBehaviours() {
@ -27,7 +36,7 @@ public class DispenserMovementBehaviour extends DropperMovementBehaviour {
public static void registerMovedDispenseItemBehaviour(Item item,
IMovedDispenseItemBehaviour movedDispenseItemBehaviour) {
MOVED_DISPENSE_ITEM_BEHAVIOURS.put(item, movedDispenseItemBehaviour);
movedDispenseItemBehaviors.put(item, movedDispenseItemBehaviour);
}
public static DispenseItemBehavior getDispenseMethod(ItemStack itemstack) {
@ -35,55 +44,64 @@ public class DispenserMovementBehaviour extends DropperMovementBehaviour {
}
@Override
protected void activate(MovementContext context, BlockPos pos) {
protected IMovedDispenseItemBehaviour getDispenseBehavior(MovementContext context, BlockPos pos, ItemStack stack) {
if (!spawnEggsRegistered) {
spawnEggsRegistered = true;
IMovedDispenseItemBehaviour.initSpawnEggs();
}
DispenseItemLocation location = getDispenseLocation(context);
if (location.isEmpty()) {
context.world.levelEvent(1001, pos, 0);
} else {
ItemStack itemStack = getItemStackAt(location, context);
// Special dispense item behaviour for moving contraptions
if (MOVED_DISPENSE_ITEM_BEHAVIOURS.containsKey(itemStack.getItem())) {
setItemStackAt(location, MOVED_DISPENSE_ITEM_BEHAVIOURS.get(itemStack.getItem()).dispense(itemStack, context, pos), context);
return;
}
ItemStack backup = itemStack.copy();
// If none is there, try vanilla registry
try {
if (MOVED_PROJECTILE_DISPENSE_BEHAVIOURS.containsKey(itemStack.getItem())) {
setItemStackAt(location, MOVED_PROJECTILE_DISPENSE_BEHAVIOURS.get(itemStack.getItem()).dispense(itemStack, context, pos), context);
return;
}
DispenseItemBehavior behavior = getDispenseMethod(itemStack);
if (behavior instanceof AbstractProjectileDispenseBehavior) { // Projectile behaviours can be converted most of the time
IMovedDispenseItemBehaviour movedBehaviour = MovedProjectileDispenserBehaviour.of((AbstractProjectileDispenseBehavior) behavior);
setItemStackAt(location, movedBehaviour.dispense(itemStack, context, pos), context);
MOVED_PROJECTILE_DISPENSE_BEHAVIOURS.put(itemStack.getItem(), movedBehaviour); // buffer conversion if successful
return;
}
Vec3 facingVec = Vec3.atLowerCornerOf(context.state.getValue(DispenserBlock.FACING).getNormal());
facingVec = context.rotation.apply(facingVec);
facingVec.normalize();
Direction clostestFacing = Direction.getNearest(facingVec.x, facingVec.y, facingVec.z);
ContraptionBlockSource blockSource = new ContraptionBlockSource(context, pos, clostestFacing);
if (behavior.getClass() != DefaultDispenseItemBehavior.class) { // There is a dispense item behaviour registered for the vanilla dispenser
setItemStackAt(location, behavior.dispense(blockSource, itemStack), context);
return;
}
} catch (NullPointerException ignored) {
itemStack = backup; // Something went wrong with the BE being null in ContraptionBlockSource, reset the stack
}
setItemStackAt(location, DEFAULT_BEHAVIOUR.dispense(itemStack, context, pos), context); // the default: launch the item
Item item = stack.getItem();
// return registered/cached behavior if present
if (movedDispenseItemBehaviors.containsKey(item)) {
return movedDispenseItemBehaviors.get(item);
}
// if there isn't one, try to create one from a vanilla behavior
if (blacklist.contains(item)) {
// unless it's been blacklisted, which means a behavior was created already and errored
return MovedDefaultDispenseItemBehaviour.INSTANCE;
}
DispenseItemBehavior behavior = getDispenseMethod(stack);
// no behavior or default, use the moved default
if (behavior == null || behavior.getClass() == DefaultDispenseItemBehavior.class)
return MovedDefaultDispenseItemBehaviour.INSTANCE;
// projectile-specific behaviors are pretty straightforward to convert
if (behavior instanceof AbstractProjectileDispenseBehavior projectile) {
IMovedDispenseItemBehaviour movedBehaviour = MovedProjectileDispenserBehaviour.of(projectile);
// cache it for later
registerMovedDispenseItemBehaviour(item, movedBehaviour);
return movedBehaviour;
}
// other behaviors are more convoluted due to BlockSource providing a BlockEntity.
Vec3 normal = getRotatedFacingNormal(context);
Direction nearestFacing = Direction.getNearest(normal.x, normal.y, normal.z);
ContraptionBlockSource source = new ContraptionBlockSource(context, pos, nearestFacing);
IMovedDispenseItemBehaviour movedBehavior = new FallbackMovedDispenseBehavior(item, behavior, source);
registerMovedDispenseItemBehaviour(item, movedBehavior);
return movedBehavior;
}
private static Vec3 getRotatedFacingNormal(MovementContext ctx) {
Direction facing = ctx.state.getValue(DispenserBlock.FACING);
Vec3 normal = Vec3.atLowerCornerOf(facing.getNormal());
return ctx.rotation.apply(normal);
}
private record FallbackMovedDispenseBehavior(Item item, DispenseItemBehavior wrapped, BlockSource source) implements IMovedDispenseItemBehaviour {
@Override
public ItemStack dispense(ItemStack stack, MovementContext context, BlockPos pos) {
ItemStack backup = stack.copy();
try {
return this.wrapped.dispense(this.source, stack);
} catch (NullPointerException ignored) {
// error due to lack of a BlockEntity. Un-register self to avoid continuing to fail
movedDispenseItemBehaviors.remove(this.item);
blacklist.add(this.item);
return backup;
}
}
}
}

View file

@ -1,132 +1,88 @@
package com.simibubi.create.content.contraptions.behaviour.dispenser;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorage;
import com.simibubi.create.content.contraptions.behaviour.MovementBehaviour;
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
import com.simibubi.create.foundation.item.ItemHelper;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.NonNullList;
import net.minecraft.world.ContainerHelper;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.LevelEvent;
import net.minecraftforge.items.IItemHandler;
public class DropperMovementBehaviour implements MovementBehaviour {
protected static final MovedDefaultDispenseItemBehaviour DEFAULT_BEHAVIOUR =
new MovedDefaultDispenseItemBehaviour();
private static final Random RNG = new Random();
protected void activate(MovementContext context, BlockPos pos) {
DispenseItemLocation location = getDispenseLocation(context);
if (location.isEmpty()) {
context.world.levelEvent(1001, pos, 0);
} else {
setItemStackAt(location, DEFAULT_BEHAVIOUR.dispense(getItemStackAt(location, context), context, pos),
context);
}
}
@Override
public void visitNewPosition(MovementContext context, BlockPos pos) {
if (context.world.isClientSide)
return;
collectItems(context);
activate(context, pos);
}
private void collectItems(MovementContext context) {
getStacks(context).stream()
.filter(itemStack -> !itemStack.isEmpty() && itemStack.getItem() != Items.AIR
&& itemStack.getMaxStackSize() > itemStack.getCount())
.forEach(itemStack -> itemStack.grow(ItemHelper
.extract(context.contraption.getSharedInventory(), (otherItemStack) -> ItemStack.isSameItemSameTags(itemStack, otherItemStack),
ItemHelper.ExtractionCountMode.UPTO, itemStack.getMaxStackSize() - itemStack.getCount(), false)
.getCount()));
}
private void updateTemporaryData(MovementContext context) {
if (!(context.temporaryData instanceof NonNullList) && context.world != null) {
NonNullList<ItemStack> stacks = NonNullList.withSize(getInvSize(), ItemStack.EMPTY);
ContainerHelper.loadAllItems(context.blockEntityData, stacks);
context.temporaryData = stacks;
}
}
@SuppressWarnings("unchecked")
private NonNullList<ItemStack> getStacks(MovementContext context) {
updateTemporaryData(context);
return (NonNullList<ItemStack>) context.temporaryData;
}
private ArrayList<DispenseItemLocation> getUseableLocations(MovementContext context) {
ArrayList<DispenseItemLocation> useable = new ArrayList<>();
for (int slot = 0; slot < getInvSize(); slot++) {
DispenseItemLocation location = new DispenseItemLocation(true, slot);
ItemStack testStack = getItemStackAt(location, context);
if (testStack == null || testStack.isEmpty())
continue;
if (testStack.getMaxStackSize() == 1) {
location = new DispenseItemLocation(false, ItemHelper.findFirstMatchingSlotIndex(
context.contraption.getSharedInventory(), ItemHelper.sameItemPredicate(testStack)));
if (!getItemStackAt(location, context).isEmpty())
useable.add(location);
} else if (testStack.getCount() >= 2)
useable.add(location);
}
return useable;
}
@Override
public void writeExtraData(MovementContext context) {
NonNullList<ItemStack> stacks = getStacks(context);
if (stacks == null)
MountedItemStorage storage = context.getItemStorage();
if (storage == null)
return;
ContainerHelper.saveAllItems(context.blockEntityData, stacks);
int slot = getSlot(storage, context.world.random, context.contraption.getStorage().getAllItems());
if (slot == -1) {
// all slots empty
failDispense(context, pos);
return;
}
// copy because dispense behaviors will modify it directly
ItemStack stack = storage.getStackInSlot(slot).copy();
IMovedDispenseItemBehaviour behavior = getDispenseBehavior(context, pos, stack);
ItemStack remainder = behavior.dispense(stack, context, pos);
storage.setStackInSlot(slot, remainder);
}
@Override
public void stopMoving(MovementContext context) {
MovementBehaviour.super.stopMoving(context);
writeExtraData(context);
protected IMovedDispenseItemBehaviour getDispenseBehavior(MovementContext context, BlockPos pos, ItemStack stack) {
return MovedDefaultDispenseItemBehaviour.INSTANCE;
}
protected DispenseItemLocation getDispenseLocation(MovementContext context) {
int i = -1;
int j = 1;
List<DispenseItemLocation> useableLocations = getUseableLocations(context);
for (int k = 0; k < useableLocations.size(); ++k) {
if (RNG.nextInt(j++) == 0) {
i = k;
/**
* Finds a dispensable slot. Empty slots are skipped and nearly-empty slots are topped off.
*/
private static int getSlot(MountedItemStorage storage, RandomSource random, IItemHandler contraptionInventory) {
IntList filledSlots = new IntArrayList();
for (int i = 0; i < storage.getSlots(); i++) {
ItemStack stack = storage.getStackInSlot(i);
if (stack.isEmpty())
continue;
if (stack.getCount() == 1 && stack.getMaxStackSize() != 1) {
stack = tryTopOff(stack, contraptionInventory);
if (stack == null) {
continue;
}
}
filledSlots.add(i);
}
if (i < 0)
return DispenseItemLocation.NONE;
else
return useableLocations.get(i);
return switch (filledSlots.size()) {
case 0 -> -1;
case 1 -> filledSlots.getInt(0);
default -> Util.getRandom(filledSlots, random);
};
}
protected ItemStack getItemStackAt(DispenseItemLocation location, MovementContext context) {
if (location.isInternal()) {
return getStacks(context).get(location.getSlot());
} else {
return context.contraption.getSharedInventory()
.getStackInSlot(location.getSlot());
}
@Nullable
private static ItemStack tryTopOff(ItemStack stack, IItemHandler from) {
Predicate<ItemStack> test = otherStack -> ItemStack.isSameItemSameTags(stack, otherStack);
int needed = stack.getMaxStackSize() - stack.getCount();
ItemStack extracted = ItemHelper.extract(from, test, ItemHelper.ExtractionCountMode.UPTO, needed, false);
return extracted.isEmpty() ? null : stack.copyWithCount(stack.getCount() + extracted.getCount());
}
protected void setItemStackAt(DispenseItemLocation location, ItemStack stack, MovementContext context) {
if (location.isInternal()) {
getStacks(context).set(location.getSlot(), stack);
} else {
context.contraption.getSharedInventory()
.setStackInSlot(location.getSlot(), stack);
}
}
private static int getInvSize() {
return 9;
private static void failDispense(MovementContext ctx, BlockPos pos) {
ctx.world.levelEvent(LevelEvent.SOUND_DISPENSER_FAIL, pos, 0);
}
}

View file

@ -13,9 +13,10 @@ import net.minecraft.world.level.block.DispenserBlock;
import net.minecraft.world.level.block.entity.HopperBlockEntity;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.items.ItemHandlerHelper;
import net.minecraftforge.items.wrapper.CombinedInvWrapper;
public class MovedDefaultDispenseItemBehaviour implements IMovedDispenseItemBehaviour {
private static final MovedDefaultDispenseItemBehaviour DEFAULT_INSTANCE = new MovedDefaultDispenseItemBehaviour();
public static final MovedDefaultDispenseItemBehaviour INSTANCE = new MovedDefaultDispenseItemBehaviour();
public static void doDispense(Level p_82486_0_, ItemStack p_82486_1_, int p_82486_2_, Vec3 facing,
BlockPos p_82486_4_, MovementContext context) {
@ -98,10 +99,21 @@ public class MovedDefaultDispenseItemBehaviour implements IMovedDispenseItemBeha
protected ItemStack placeItemInInventory(ItemStack consumedFrom, ItemStack output, MovementContext context,
BlockPos pos, Vec3 facing) {
consumedFrom.shrink(1);
ItemStack remainder =
ItemHandlerHelper.insertItem(context.contraption.getSharedInventory(), output.copy(), false);
if (!remainder.isEmpty())
DEFAULT_INSTANCE.dispenseStack(output, context, pos, facing);
ItemStack toInsert = output.copy();
// try inserting into own inventory first
ItemStack remainder = ItemHandlerHelper.insertItem(context.getItemStorage(), toInsert, false);
if (!remainder.isEmpty()) {
// next, try the whole contraption inventory
// note that this contains the dispenser inventory. That's fine.
CombinedInvWrapper contraption = context.contraption.getStorage().getAllItems();
ItemStack newRemainder = ItemHandlerHelper.insertItem(contraption, remainder, false);
if (!newRemainder.isEmpty()) {
// if there's *still* something left, dispense into world
INSTANCE.dispenseStack(remainder, context, pos, facing);
}
}
return consumedFrom;
}
}

View file

@ -0,0 +1,49 @@
package com.simibubi.create.content.contraptions.behaviour.dispenser.storage;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import com.mojang.serialization.Codec;
import com.simibubi.create.AllMountedStorageTypes;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.contraption.storage.item.menu.MountedStorageMenus;
import com.simibubi.create.api.contraption.storage.item.simple.SimpleMountedStorage;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
public class DispenserMountedStorage extends SimpleMountedStorage {
public static final Codec<DispenserMountedStorage> CODEC = SimpleMountedStorage.codec(DispenserMountedStorage::new);
protected DispenserMountedStorage(MountedItemStorageType<?> type, IItemHandler handler) {
super(type, handler);
}
public DispenserMountedStorage(IItemHandler handler) {
this(AllMountedStorageTypes.DISPENSER.get(), handler);
}
@Override
public boolean isInternal() {
return true;
}
@Override
@Nullable
protected MenuProvider createMenuProvider(Component name, IItemHandlerModifiable handler,
Predicate<Player> stillValid, Consumer<Player> onClose) {
return MountedStorageMenus.createGeneric9x9(name, handler, stillValid, onClose);
}
@Override
protected void playOpeningSound(ServerLevel level, Vec3 pos) {
// dispensers are silent
}
}

View file

@ -0,0 +1,17 @@
package com.simibubi.create.content.contraptions.behaviour.dispenser.storage;
import com.simibubi.create.api.contraption.storage.item.simple.SimpleMountedStorage;
import com.simibubi.create.api.contraption.storage.item.simple.SimpleMountedStorageType;
import net.minecraftforge.items.IItemHandler;
public class DispenserMountedStorageType extends SimpleMountedStorageType<DispenserMountedStorage> {
public DispenserMountedStorageType() {
super(DispenserMountedStorage.CODEC);
}
@Override
protected SimpleMountedStorage createStorage(IItemHandler handler) {
return new DispenserMountedStorage(handler);
}
}

View file

@ -1,21 +1,18 @@
package com.simibubi.create.content.contraptions.minecart;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import com.simibubi.create.content.contraptions.Contraption.ContraptionInvWrapper;
import com.simibubi.create.content.contraptions.MountedStorageManager;
import com.simibubi.create.foundation.fluid.CombinedTankWrapper;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageWrapper;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageWrapper;
import com.simibubi.create.content.contraptions.Contraption;
import com.simibubi.create.content.contraptions.MountedStorageManager;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
public class TrainCargoManager extends MountedStorageManager {
@ -28,20 +25,13 @@ public class TrainCargoManager extends MountedStorageManager {
}
@Override
public void createHandlers() {
super.createHandlers();
}
@Override
protected ContraptionInvWrapper wrapItems(Collection<IItemHandlerModifiable> list, boolean fuel) {
if (fuel)
return super.wrapItems(list, fuel);
return new CargoInvWrapper(Arrays.copyOf(list.toArray(), list.size(), IItemHandlerModifiable[].class));
}
@Override
protected CombinedTankWrapper wrapFluids(Collection<IFluidHandler> list) {
return new CargoTankWrapper(Arrays.copyOf(list.toArray(), list.size(), IFluidHandler[].class));
public void initialize() {
super.initialize();
this.items = new CargoInvWrapper(this.items);
if (this.fuelItems != null) {
this.fuelItems = new CargoInvWrapper(this.fuelItems);
}
this.fluids = new CargoTankWrapper(this.fluids);
}
@Override
@ -51,36 +41,35 @@ public class TrainCargoManager extends MountedStorageManager {
}
@Override
public void read(CompoundTag nbt, Map<BlockPos, BlockEntity> presentBlockEntities, boolean clientPacket) {
super.read(nbt, presentBlockEntities, clientPacket);
public void read(CompoundTag nbt, boolean clientPacket, @Nullable Contraption contraption) {
super.read(nbt, clientPacket, contraption);
ticksSinceLastExchange = nbt.getInt("TicksSinceLastExchange");
}
public void resetIdleCargoTracker() {
ticksSinceLastExchange = 0;
}
public void tickIdleCargoTracker() {
ticksSinceLastExchange++;
}
public int getTicksSinceLastExchange() {
return ticksSinceLastExchange;
}
public int getVersion() {
return version.get();
}
void changeDetected() {
version.incrementAndGet();
resetIdleCargoTracker();
}
class CargoInvWrapper extends ContraptionInvWrapper {
public CargoInvWrapper(IItemHandlerModifiable... itemHandler) {
super(false, itemHandler);
class CargoInvWrapper extends MountedItemStorageWrapper {
CargoInvWrapper(MountedItemStorageWrapper wrapped) {
super(wrapped.storages);
}
@Override
@ -108,10 +97,9 @@ public class TrainCargoManager extends MountedStorageManager {
}
class CargoTankWrapper extends CombinedTankWrapper {
public CargoTankWrapper(IFluidHandler... fluidHandler) {
super(fluidHandler);
class CargoTankWrapper extends MountedFluidStorageWrapper {
CargoTankWrapper(MountedFluidStorageWrapper wrapped) {
super(wrapped.storages);
}
@Override
@ -137,7 +125,7 @@ public class TrainCargoManager extends MountedStorageManager {
changeDetected();
return drained;
}
}
}

View file

@ -60,7 +60,7 @@ public class MountedContraption extends Contraption {
return false;
Axis axis = state.getValue(RAIL_SHAPE) == RailShape.EAST_WEST ? Axis.X : Axis.Z;
addBlock(pos, Pair.of(new StructureBlockInfo(pos, AllBlocks.MINECART_ANCHOR.getDefaultState()
addBlock(world, pos, Pair.of(new StructureBlockInfo(pos, AllBlocks.MINECART_ANCHOR.getDefaultState()
.setValue(BlockStateProperties.HORIZONTAL_AXIS, axis), null), null));
if (blocks.size() == 1)
@ -154,7 +154,7 @@ public class MountedContraption extends Contraption {
public void addExtraInventories(Entity cart) {
if (cart instanceof Container container)
storage.attachExternal(new ContraptionInvWrapper(true, new InvWrapper(container)));
storage.attachExternal(new InvWrapper(container));
}
}

View file

@ -187,8 +187,8 @@ public class PistonContraption extends TranslatingContraption {
}
@Override
public void addBlock(BlockPos pos, Pair<StructureBlockInfo, BlockEntity> capture) {
super.addBlock(pos.relative(orientation, -initialExtensionProgress), capture);
public void addBlock(Level level, BlockPos pos, Pair<StructureBlockInfo, BlockEntity> capture) {
super.addBlock(level, pos.relative(orientation, -initialExtensionProgress), capture);
}
@Override

View file

@ -1,49 +0,0 @@
package com.simibubi.create.content.contraptions.sync;
import com.simibubi.create.content.contraptions.AbstractContraptionEntity;
import com.simibubi.create.foundation.networking.SimplePacketBase;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.network.NetworkEvent.Context;
public class ContraptionFluidPacket extends SimplePacketBase {
private int entityId;
private BlockPos localPos;
private FluidStack containedFluid;
public ContraptionFluidPacket(int entityId, BlockPos localPos, FluidStack containedFluid) {
this.entityId = entityId;
this.localPos = localPos;
this.containedFluid = containedFluid;
}
public ContraptionFluidPacket(FriendlyByteBuf buffer) {
entityId = buffer.readInt();
localPos = buffer.readBlockPos();
containedFluid = FluidStack.readFromPacket(buffer);
}
@Override
public void write(FriendlyByteBuf buffer) {
buffer.writeInt(entityId);
buffer.writeBlockPos(localPos);
containedFluid.writeToPacket(buffer);
}
@Override
public boolean handle(Context context) {
context.enqueueWork(() -> {
Entity entityByID = Minecraft.getInstance().level.getEntity(entityId);
if (!(entityByID instanceof AbstractContraptionEntity))
return;
AbstractContraptionEntity contraptionEntity = (AbstractContraptionEntity) entityByID;
contraptionEntity.getContraption().handleContraptionFluidPacket(localPos, containedFluid);
});
return true;
}
}

View file

@ -1,60 +0,0 @@
package com.simibubi.create.content.contraptions.sync;
import java.util.ArrayList;
import java.util.List;
import com.simibubi.create.content.contraptions.AbstractContraptionEntity;
import com.simibubi.create.foundation.networking.SimplePacketBase;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.network.NetworkEvent.Context;
public class ContraptionItemPacket extends SimplePacketBase {
private int entityId;
private BlockPos localPos;
private List<ItemStack> containedItems;
public ContraptionItemPacket(int entityId, BlockPos localPos, ItemStackHandler handler) {
this.entityId = entityId;
this.localPos = localPos;
this.containedItems = new ArrayList<>(handler.getSlots());
for (int i = 0; i < handler.getSlots(); i++)
containedItems.add(handler.getStackInSlot(i));
}
public ContraptionItemPacket(FriendlyByteBuf buffer) {
entityId = buffer.readInt();
localPos = buffer.readBlockPos();
int count = buffer.readVarInt();
containedItems = new ArrayList<>();
for (int i = 0; i < count; i++)
containedItems.add(buffer.readItem());
}
@Override
public void write(FriendlyByteBuf buffer) {
buffer.writeInt(entityId);
buffer.writeBlockPos(localPos);
buffer.writeVarInt(containedItems.size());
for (ItemStack stack : containedItems)
buffer.writeItem(stack);
}
@Override
public boolean handle(Context context) {
context.enqueueWork(() -> {
Entity entityByID = Minecraft.getInstance().level.getEntity(entityId);
if (!(entityByID instanceof AbstractContraptionEntity))
return;
AbstractContraptionEntity contraptionEntity = (AbstractContraptionEntity) entityByID;
contraptionEntity.getContraption().handleContraptionItemPacket(localPos, containedItems);
});
return true;
}
}

View file

@ -6,7 +6,10 @@ import java.util.function.Consumer;
import javax.annotation.Nonnull;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.simibubi.create.AllItems;
import com.simibubi.create.foundation.item.ItemSlots;
import net.createmod.catnip.nbt.NBTHelper;
import net.minecraft.nbt.CompoundTag;
@ -16,6 +19,10 @@ import net.minecraftforge.items.ItemHandlerHelper;
import net.minecraftforge.items.ItemStackHandler;
public class ToolboxInventory extends ItemStackHandler {
public static final Codec<ToolboxInventory> CODEC = RecordCodecBuilder.create(instance -> instance.group(
ItemSlots.maxSizeCodec(8).fieldOf("items").forGetter(ItemSlots::fromHandler),
ItemStack.CODEC.listOf().fieldOf("filters").forGetter(toolbox -> toolbox.filters)
).apply(instance, ToolboxInventory::deserialize));
public static final int STACKS_PER_COMPARTMENT = 4;
List<ItemStack> filters;
@ -212,4 +219,11 @@ public class ToolboxInventory extends ItemStackHandler {
blockEntity.notifyUpdate();
}
private static ToolboxInventory deserialize(ItemSlots slots, List<ItemStack> filters) {
ToolboxInventory inventory = new ToolboxInventory(null);
slots.forEach(inventory::setStackInSlot);
inventory.filters = filters;
return inventory;
}
}

View file

@ -0,0 +1,60 @@
package com.simibubi.create.content.equipment.toolbox;
import org.jetbrains.annotations.Nullable;
import com.mojang.serialization.Codec;
import com.simibubi.create.AllMountedStorageTypes;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.contraption.storage.item.WrapperMountedItemStorage;
import com.simibubi.create.content.contraptions.Contraption;
import com.simibubi.create.foundation.item.ItemHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
public class ToolboxMountedStorage extends WrapperMountedItemStorage<ToolboxInventory> {
public static final Codec<ToolboxMountedStorage> CODEC = ToolboxInventory.CODEC.xmap(
ToolboxMountedStorage::new, storage -> storage.wrapped
);
protected ToolboxMountedStorage(MountedItemStorageType<?> type, ToolboxInventory wrapped) {
super(type, wrapped);
}
protected ToolboxMountedStorage(ToolboxInventory wrapped) {
this(AllMountedStorageTypes.TOOLBOX.get(), wrapped);
}
@Override
public void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
if (be instanceof ToolboxBlockEntity toolbox) {
ItemHelper.copyContents(this, toolbox.inventory);
}
}
@Override
public boolean handleInteraction(ServerPlayer player, Contraption contraption, StructureBlockInfo info) {
// The default impl will fail anyway, might as well cancel trying
return false;
}
public static ToolboxMountedStorage fromToolbox(ToolboxBlockEntity toolbox) {
// the inventory will send updates to the block entity, make an isolated copy to avoid that
ToolboxInventory copy = new ToolboxInventory(null);
ItemHelper.copyContents(toolbox.inventory, copy);
copy.filters = toolbox.inventory.filters.stream().map(ItemStack::copy).toList();
return new ToolboxMountedStorage(copy);
}
public static ToolboxMountedStorage fromLegacy(CompoundTag nbt) {
ToolboxInventory inv = new ToolboxInventory(null);
inv.deserializeNBT(nbt);
return new ToolboxMountedStorage(inv);
}
}

View file

@ -0,0 +1,22 @@
package com.simibubi.create.content.equipment.toolbox;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public class ToolboxMountedStorageType extends MountedItemStorageType<ToolboxMountedStorage> {
public ToolboxMountedStorageType() {
super(ToolboxMountedStorage.CODEC);
}
@Override
@Nullable
public ToolboxMountedStorage mount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
return be instanceof ToolboxBlockEntity toolbox ? ToolboxMountedStorage.fromToolbox(toolbox) : null;
}
}

View file

@ -3,13 +3,17 @@ package com.simibubi.create.content.fluids.tank;
import java.util.List;
import java.util.function.Consumer;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.simibubi.create.foundation.fluid.SmartFluidTank;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.templates.FluidTank;
public class CreativeFluidTankBlockEntity extends FluidTankBlockEntity {
@ -21,26 +25,34 @@ public class CreativeFluidTankBlockEntity extends FluidTankBlockEntity {
protected SmartFluidTank createInventory() {
return new CreativeSmartFluidTank(getCapacityMultiplier(), this::onFluidStackChanged);
}
@Override
public boolean addToGoggleTooltip(List<Component> tooltip, boolean isPlayerSneaking) {
return false;
}
public static class CreativeSmartFluidTank extends SmartFluidTank {
public static final Codec<CreativeSmartFluidTank> CODEC = RecordCodecBuilder.create(i -> i.group(
FluidStack.CODEC.fieldOf("fluid").forGetter(FluidTank::getFluid),
ExtraCodecs.NON_NEGATIVE_INT.fieldOf("capacity").forGetter(FluidTank::getCapacity)
).apply(i, (fluid, capacity) -> {
CreativeSmartFluidTank tank = new CreativeSmartFluidTank(capacity, $ -> {});
tank.setFluid(fluid);
return tank;
}));
public CreativeSmartFluidTank(int capacity, Consumer<FluidStack> updateCallback) {
super(capacity, updateCallback);
}
@Override
public int getFluidAmount() {
return getFluid().isEmpty() ? 0 : getTankCapacity(0);
}
public void setContainedFluid(FluidStack fluidStack) {
fluid = fluidStack.copy();
if (!fluidStack.isEmpty())
if (!fluidStack.isEmpty())
fluid.setAmount(getTankCapacity(0));
onContentsChanged();
}

View file

@ -509,7 +509,7 @@ public class FluidTankBlockEntity extends SmartBlockEntity implements IHaveGoggl
registerAwardables(behaviours, AllAdvancements.STEAM_ENGINE_MAXED, AllAdvancements.PIPE_ORGAN);
}
public IFluidTank getTankInventory() {
public FluidTank getTankInventory() {
return tankInventory;
}

View file

@ -0,0 +1,24 @@
package com.simibubi.create.content.fluids.tank;
import com.simibubi.create.content.contraptions.behaviour.MovementBehaviour;
import com.simibubi.create.content.contraptions.behaviour.MovementContext;
import net.minecraft.world.level.block.entity.BlockEntity;
// The fluid level needs to be ticked to animate smoothly
public class FluidTankMovementBehavior implements MovementBehaviour {
@Override
public boolean mustTickWhileDisabled() {
return true;
}
@Override
public void tick(MovementContext context) {
if (context.world.isClientSide) {
BlockEntity be = context.contraption.presentBlockEntities.get(context.localPos);
if (be instanceof FluidTankBlockEntity tank) {
tank.getFluidLevel().tickChaser();
}
}
}
}

View file

@ -0,0 +1,110 @@
package com.simibubi.create.content.fluids.tank.storage;
import org.jetbrains.annotations.Nullable;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.simibubi.create.AllMountedStorageTypes;
import com.simibubi.create.api.contraption.storage.SyncedMountedStorage;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageType;
import com.simibubi.create.api.contraption.storage.fluid.WrapperMountedFluidStorage;
import com.simibubi.create.content.contraptions.Contraption;
import com.simibubi.create.content.fluids.tank.FluidTankBlockEntity;
import com.simibubi.create.content.fluids.tank.storage.FluidTankMountedStorage.Handler;
import net.createmod.catnip.animation.LerpedFloat;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.templates.FluidTank;
public class FluidTankMountedStorage extends WrapperMountedFluidStorage<Handler> implements SyncedMountedStorage {
public static final Codec<FluidTankMountedStorage> CODEC = RecordCodecBuilder.create(i -> i.group(
ExtraCodecs.NON_NEGATIVE_INT.fieldOf("capacity").forGetter(FluidTankMountedStorage::getCapacity),
FluidStack.CODEC.fieldOf("fluid").forGetter(FluidTankMountedStorage::getFluid)
).apply(i, FluidTankMountedStorage::new));
private boolean dirty;
protected FluidTankMountedStorage(MountedFluidStorageType<?> type, int capacity, FluidStack stack) {
super(type, new Handler(capacity, stack));
this.wrapped.onChange = () -> this.dirty = true;
}
protected FluidTankMountedStorage(int capacity, FluidStack stack) {
this(AllMountedStorageTypes.FLUID_TANK.get(), capacity, stack);
}
@Override
public void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
if (be instanceof FluidTankBlockEntity tank && tank.isController()) {
FluidTank inventory = tank.getTankInventory();
// capacity shouldn't change, leave it
inventory.setFluid(this.wrapped.getFluid());
}
}
public FluidStack getFluid() {
return this.wrapped.getFluid();
}
public int getCapacity() {
return this.wrapped.getCapacity();
}
@Override
public boolean isDirty() {
return this.dirty;
}
@Override
public void markClean() {
this.dirty = false;
}
@Override
public void afterSync(Contraption contraption, BlockPos localPos) {
BlockEntity be = contraption.presentBlockEntities.get(localPos);
if (!(be instanceof FluidTankBlockEntity tank))
return;
FluidTank inv = tank.getTankInventory();
inv.setFluid(this.getFluid());
float fillLevel = inv.getFluidAmount() / (float) inv.getCapacity();
if (tank.getFluidLevel() == null) {
tank.setFluidLevel(LerpedFloat.linear().startWithValue(fillLevel));
}
tank.getFluidLevel().chase(fillLevel, 0.5, LerpedFloat.Chaser.EXP);
}
public static FluidTankMountedStorage fromTank(FluidTankBlockEntity tank) {
// tank has update callbacks, make an isolated copy
FluidTank inventory = tank.getTankInventory();
return new FluidTankMountedStorage(inventory.getCapacity(), inventory.getFluid().copy());
}
public static FluidTankMountedStorage fromLegacy(CompoundTag nbt) {
int capacity = nbt.getInt("Capacity");
FluidStack fluid = FluidStack.loadFluidStackFromNBT(nbt);
return new FluidTankMountedStorage(capacity, fluid);
}
public static final class Handler extends FluidTank {
private Runnable onChange = () -> {};
public Handler(int capacity, FluidStack stack) {
super(capacity);
this.setFluid(stack);
}
@Override
protected void onContentsChanged() {
this.onChange.run();
}
}
}

View file

@ -0,0 +1,27 @@
package com.simibubi.create.content.fluids.tank.storage;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageType;
import com.simibubi.create.content.fluids.tank.FluidTankBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public class FluidTankMountedStorageType extends MountedFluidStorageType<FluidTankMountedStorage> {
public FluidTankMountedStorageType() {
super(FluidTankMountedStorage.CODEC);
}
@Override
@Nullable
public FluidTankMountedStorage mount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
if (be instanceof FluidTankBlockEntity tank && tank.isController()) {
return FluidTankMountedStorage.fromTank(tank);
}
return null;
}
}

View file

@ -0,0 +1,53 @@
package com.simibubi.create.content.fluids.tank.storage.creative;
import org.jetbrains.annotations.Nullable;
import com.mojang.serialization.Codec;
import com.simibubi.create.AllMountedStorageTypes;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageType;
import com.simibubi.create.api.contraption.storage.fluid.WrapperMountedFluidStorage;
import com.simibubi.create.content.fluids.tank.CreativeFluidTankBlockEntity;
import com.simibubi.create.content.fluids.tank.CreativeFluidTankBlockEntity.CreativeSmartFluidTank;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.templates.FluidTank;
public class CreativeFluidTankMountedStorage extends WrapperMountedFluidStorage<CreativeSmartFluidTank> {
public static final Codec<CreativeFluidTankMountedStorage> CODEC = CreativeSmartFluidTank.CODEC.xmap(
CreativeFluidTankMountedStorage::new, storage -> storage.wrapped
);
protected CreativeFluidTankMountedStorage(MountedFluidStorageType<?> type, CreativeSmartFluidTank tank) {
super(type, tank);
}
protected CreativeFluidTankMountedStorage(CreativeSmartFluidTank tank) {
this(AllMountedStorageTypes.CREATIVE_FLUID_TANK.get(), tank);
}
@Override
public void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
// no need to do anything, supplied stack can't change while mounted
}
public static CreativeFluidTankMountedStorage fromTank(CreativeFluidTankBlockEntity tank) {
// make an isolated copy
FluidTank inv = tank.getTankInventory();
CreativeSmartFluidTank copy = new CreativeSmartFluidTank(inv.getCapacity(), $ -> {});
copy.setContainedFluid(inv.getFluid());
return new CreativeFluidTankMountedStorage(copy);
}
public static CreativeFluidTankMountedStorage fromLegacy(CompoundTag nbt) {
int capacity = nbt.getInt("Capacity");
FluidStack fluid = FluidStack.loadFluidStackFromNBT(nbt.getCompound("ProvidedStack"));
CreativeSmartFluidTank tank = new CreativeSmartFluidTank(capacity, $ -> {});
tank.setContainedFluid(fluid);
return new CreativeFluidTankMountedStorage(tank);
}
}

View file

@ -0,0 +1,27 @@
package com.simibubi.create.content.fluids.tank.storage.creative;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageType;
import com.simibubi.create.content.fluids.tank.CreativeFluidTankBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public class CreativeFluidTankMountedStorageType extends MountedFluidStorageType<CreativeFluidTankMountedStorage> {
public CreativeFluidTankMountedStorageType() {
super(CreativeFluidTankMountedStorage.CODEC);
}
@Override
@Nullable
public CreativeFluidTankMountedStorage mount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
if (be instanceof CreativeFluidTankBlockEntity tank) {
return CreativeFluidTankMountedStorage.fromTank(tank);
}
return null;
}
}

View file

@ -144,7 +144,7 @@ public class DeployerMovementBehaviour implements MovementBehaviour {
ItemStack contextStack = requiredItems.isEmpty() ? ItemStack.EMPTY : requiredItems.get(0).stack;
if (!context.contraption.hasUniversalCreativeCrate) {
IItemHandler itemHandler = context.contraption.getSharedInventory();
IItemHandler itemHandler = context.contraption.getStorage().getAllItems();
for (ItemRequirement.StackRequirement required : requiredItems) {
ItemStack stack = ItemHelper
.extract(itemHandler, required::matches, ExtractionCountMode.EXACTLY,
@ -233,7 +233,7 @@ public class DeployerMovementBehaviour implements MovementBehaviour {
FilterItemStack filter = context.getFilterFromBE();
if (AllItems.SCHEMATIC.isIn(filter.item()))
return;
ItemStack held = ItemHelper.extract(context.contraption.getSharedInventory(),
ItemStack held = ItemHelper.extract(context.contraption.getStorage().getAllItems(),
stack -> filter.test(context.world, stack), 1, false);
player.setItemInHand(InteractionHand.MAIN_HAND, held);
}

View file

@ -82,7 +82,7 @@ public class SawMovementBehaviour extends BlockBreakingMovementBehaviour {
}
public void dropItemFromCutTree(MovementContext context, BlockPos pos, ItemStack stack) {
ItemStack remainder = ItemHandlerHelper.insertItem(context.contraption.getSharedInventory(), stack, false);
ItemStack remainder = ItemHandlerHelper.insertItem(context.contraption.getStorage().getAllItems(), stack, false);
if (remainder.isEmpty())
return;

View file

@ -0,0 +1,82 @@
package com.simibubi.create.content.logistics.crate;
import com.mojang.serialization.Codec;
import com.simibubi.create.AllMountedStorageTypes;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorage;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public class CreativeCrateMountedStorage extends MountedItemStorage {
public static final Codec<CreativeCrateMountedStorage> CODEC = ItemStack.CODEC.xmap(
CreativeCrateMountedStorage::new, storage -> storage.suppliedStack
);
private final ItemStack suppliedStack;
private final ItemStack cachedStackInSlot;
protected CreativeCrateMountedStorage(MountedItemStorageType<?> type, ItemStack suppliedStack) {
super(type);
this.suppliedStack = suppliedStack;
this.cachedStackInSlot = suppliedStack.copyWithCount(suppliedStack.getMaxStackSize());
}
public CreativeCrateMountedStorage(ItemStack suppliedStack) {
this(AllMountedStorageTypes.CREATIVE_CRATE.get(), suppliedStack);
}
@Override
public void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
// no need to do anything here, the supplied item can't change while mounted
}
@Override
public int getSlots() {
return 2; // 0 holds the supplied stack endlessly, 1 is always empty to accept
}
@Override
@NotNull
public ItemStack getStackInSlot(int slot) {
return slot == 0 ? this.cachedStackInSlot : ItemStack.EMPTY;
}
@Override
public void setStackInSlot(int slot, @NotNull ItemStack stack) {
}
@Override
@NotNull
public ItemStack insertItem(int slot, @NotNull ItemStack stack, boolean simulate) {
return ItemStack.EMPTY; // no remainder, accept any input
}
@Override
@NotNull
public ItemStack extractItem(int slot, int amount, boolean simulate) {
if (slot == 0 && !this.suppliedStack.isEmpty()) {
int count = Math.min(amount, this.suppliedStack.getMaxStackSize());
return this.suppliedStack.copyWithCount(count);
}
return ItemStack.EMPTY;
}
@Override
public int getSlotLimit(int slot) {
return 64;
}
@Override
public boolean isItemValid(int slot, @NotNull ItemStack stack) {
return true;
}
}

View file

@ -0,0 +1,28 @@
package com.simibubi.create.content.logistics.crate;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public class CreativeCrateMountedStorageType extends MountedItemStorageType<CreativeCrateMountedStorage> {
public CreativeCrateMountedStorageType() {
super(CreativeCrateMountedStorage.CODEC);
}
@Override
@Nullable
public CreativeCrateMountedStorage mount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
if (be instanceof CreativeCrateBlockEntity crate) {
ItemStack supplied = crate.filtering.getFilter();
return new CreativeCrateMountedStorage(supplied);
}
return null;
}
}

View file

@ -1,17 +1,17 @@
package com.simibubi.create.content.logistics.depot;
import com.simibubi.create.AllSoundEvents;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorage;
import com.simibubi.create.content.contraptions.AbstractContraptionEntity;
import com.simibubi.create.content.contraptions.MountedStorage;
import com.simibubi.create.content.contraptions.MountedStorageManager;
import com.simibubi.create.content.contraptions.behaviour.MovingInteractionBehaviour;
import com.simibubi.create.content.logistics.depot.storage.DepotMountedStorage;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.items.IItemHandlerModifiable;
public class MountedDepotInteractionBehaviour extends MovingInteractionBehaviour {
@ -24,23 +24,17 @@ public class MountedDepotInteractionBehaviour extends MovingInteractionBehaviour
if (player.level().isClientSide)
return true;
MountedStorageManager storageManager = contraptionEntity.getContraption()
.getStorageManager();
if (storageManager == null)
MountedStorageManager manager = contraptionEntity.getContraption().getStorage();
MountedItemStorage storage = manager.getAllItemStorages().get(localPos);
if (!(storage instanceof DepotMountedStorage depot))
return false;
MountedStorage mountedStorage = storageManager.getMountedItemStorage()
.get(localPos);
if (mountedStorage == null)
return false;
IItemHandlerModifiable itemHandler = mountedStorage.getItemHandler();
ItemStack itemOnDepot = itemHandler.getStackInSlot(0);
ItemStack itemOnDepot = depot.getItem();
if (itemOnDepot.isEmpty() && itemInHand.isEmpty())
return true;
itemHandler.setStackInSlot(0, itemInHand.copy());
depot.setItem(itemInHand.copy());
player.setItemInHand(activeHand, itemOnDepot.copy());
AllSoundEvents.DEPOT_PLOP.playOnServer(player.level(),
BlockPos.containing(contraptionEntity.toGlobalVector(Vec3.atCenterOf(localPos), 0)));

View file

@ -0,0 +1,109 @@
package com.simibubi.create.content.logistics.depot.storage;
import org.jetbrains.annotations.Nullable;
import com.mojang.serialization.Codec;
import com.simibubi.create.AllMountedStorageTypes;
import com.simibubi.create.api.contraption.storage.SyncedMountedStorage;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.contraption.storage.item.WrapperMountedItemStorage;
import com.simibubi.create.content.contraptions.Contraption;
import com.simibubi.create.content.logistics.depot.DepotBlockEntity;
import com.simibubi.create.content.logistics.depot.storage.DepotMountedStorage.Handler;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraftforge.items.ItemStackHandler;
public class DepotMountedStorage extends WrapperMountedItemStorage<Handler> implements SyncedMountedStorage {
public static final Codec<DepotMountedStorage> CODEC = ItemStack.CODEC.xmap(
DepotMountedStorage::new, DepotMountedStorage::getItem
);
private boolean dirty;
protected DepotMountedStorage(ItemStack stack) {
this(AllMountedStorageTypes.DEPOT.get(), stack);
}
protected DepotMountedStorage(MountedItemStorageType<?> type, ItemStack stack) {
super(type, new Handler(stack));
this.wrapped.onChange = () -> this.dirty = true;
}
@Override
public void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
if (be instanceof DepotBlockEntity depot) {
depot.setHeldItem(this.getStackInSlot(0));
}
}
@Override
public boolean handleInteraction(ServerPlayer player, Contraption contraption, StructureBlockInfo info) {
// interaction is handled in the Interaction Behavior, swaps items with the player
return false;
}
@Override
public boolean isDirty() {
return this.dirty;
}
@Override
public void markClean() {
this.dirty = false;
}
@Override
public void afterSync(Contraption contraption, BlockPos localPos) {
BlockEntity be = contraption.presentBlockEntities.get(localPos);
if (be instanceof DepotBlockEntity depot) {
depot.setHeldItem(this.getItem());
}
}
public void setItem(ItemStack stack) {
this.setStackInSlot(0, stack);
}
public ItemStack getItem() {
return this.getStackInSlot(0);
}
public static DepotMountedStorage fromDepot(DepotBlockEntity depot) {
ItemStack held = depot.getHeldItem();
return new DepotMountedStorage(held.copy());
}
public static DepotMountedStorage fromLegacy(CompoundTag nbt) {
ItemStackHandler handler = new ItemStackHandler();
handler.deserializeNBT(nbt);
if (handler.getSlots() == 1) {
ItemStack stack = handler.getStackInSlot(0);
return new DepotMountedStorage(stack);
} else {
return new DepotMountedStorage(ItemStack.EMPTY);
}
}
public static final class Handler extends ItemStackHandler {
private Runnable onChange = () -> {};
private Handler(ItemStack stack) {
super(1);
this.setStackInSlot(0, stack);
}
@Override
protected void onContentsChanged(int slot) {
this.onChange.run();
}
}
}

View file

@ -0,0 +1,27 @@
package com.simibubi.create.content.logistics.depot.storage;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.content.logistics.depot.DepotBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public class DepotMountedStorageType extends MountedItemStorageType<DepotMountedStorage> {
public DepotMountedStorageType() {
super(DepotMountedStorage.CODEC);
}
@Override
@Nullable
public DepotMountedStorage mount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
if (be instanceof DepotBlockEntity depot) {
return DepotMountedStorage.fromDepot(depot);
}
return null;
}
}

View file

@ -76,7 +76,7 @@ public class FunnelMovementBehaviour implements MovementBehaviour {
boolean upTo = context.blockEntityData.getBoolean("UpTo");
filterAmount = hasFilter ? filterAmount : 1;
ItemStack extract = ItemHelper.extract(context.contraption.getSharedInventory(),
ItemStack extract = ItemHelper.extract(context.contraption.getStorage().getAllItems(),
s -> filter.test(world, s),
upTo ? ItemHelper.ExtractionCountMode.UPTO : ItemHelper.ExtractionCountMode.EXACTLY, filterAmount, false);
@ -105,7 +105,7 @@ public class FunnelMovementBehaviour implements MovementBehaviour {
if (!filter.test(context.world, toInsert))
continue;
ItemStack remainder =
ItemHandlerHelper.insertItemStacked(context.contraption.getSharedInventory(), toInsert, false);
ItemHandlerHelper.insertItemStacked(context.contraption.getStorage().getAllItems(), toInsert, false);
if (remainder.getCount() == toInsert.getCount())
continue;
if (remainder.isEmpty()) {

View file

@ -0,0 +1,53 @@
package com.simibubi.create.content.logistics.vault;
import org.jetbrains.annotations.Nullable;
import com.mojang.serialization.Codec;
import com.simibubi.create.AllMountedStorageTypes;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.contraption.storage.item.WrapperMountedItemStorage;
import com.simibubi.create.foundation.utility.CreateCodecs;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.items.ItemStackHandler;
public class ItemVaultMountedStorage extends WrapperMountedItemStorage<ItemStackHandler> {
public static final Codec<ItemVaultMountedStorage> CODEC = CreateCodecs.ITEM_STACK_HANDLER.xmap(
ItemVaultMountedStorage::new, storage -> storage.wrapped
);
protected ItemVaultMountedStorage(MountedItemStorageType<?> type, ItemStackHandler handler) {
super(type, handler);
}
protected ItemVaultMountedStorage(ItemStackHandler handler) {
this(AllMountedStorageTypes.VAULT.get(), handler);
}
@Override
public void unmount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
if (be instanceof ItemVaultBlockEntity vault) {
vault.applyInventoryToBlock(this.wrapped);
}
}
@Override
public boolean providesFuel() {
return false;
}
public static ItemVaultMountedStorage fromVault(ItemVaultBlockEntity vault) {
// Vault inventories have a world-affecting onContentsChanged, copy to a safe one
return new ItemVaultMountedStorage(copyToItemStackHandler(vault.getInventoryOfBlock()));
}
public static ItemVaultMountedStorage fromLegacy(CompoundTag nbt) {
ItemStackHandler handler = new ItemStackHandler();
handler.deserializeNBT(nbt);
return new ItemVaultMountedStorage(handler);
}
}

View file

@ -0,0 +1,22 @@
package com.simibubi.create.content.logistics.vault;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public class ItemVaultMountedStorageType extends MountedItemStorageType<ItemVaultMountedStorage> {
public ItemVaultMountedStorageType() {
super(ItemVaultMountedStorage.CODEC);
}
@Override
@Nullable
public ItemVaultMountedStorage mount(Level level, BlockState state, BlockPos pos, @Nullable BlockEntity be) {
return be instanceof ItemVaultBlockEntity vault ? ItemVaultMountedStorage.fromVault(vault) : null;
}
}

View file

@ -30,9 +30,9 @@ import com.simibubi.create.foundation.advancement.AllAdvancements;
import net.createmod.catnip.data.Couple;
import net.createmod.catnip.data.Iterate;
import net.createmod.catnip.nbt.NBTHelper;
import net.createmod.catnip.data.Pair;
import net.createmod.catnip.math.VecHelper;
import net.createmod.catnip.nbt.NBTHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
@ -46,6 +46,7 @@ import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.DistExecutor;
@ -647,7 +648,7 @@ public class Carriage {
public void read(CompoundTag tag) {
cutoff = tag.getFloat("Cutoff");
discardTicks = tag.getInt("DiscardTicks");
storage.read(tag, null, false);
storage.read(tag, false, null);
if (tag.contains("Pivot"))
pivot = TrackNodeLocation.read(tag.getCompound("Pivot"), null);
if (positionAnchor != null)

View file

@ -10,7 +10,6 @@ import java.util.Optional;
import org.apache.commons.lang3.tuple.Pair;
import com.simibubi.create.AllBlocks;
import com.simibubi.create.content.contraptions.AbstractContraptionEntity;
import com.simibubi.create.content.contraptions.AssemblyException;
import com.simibubi.create.content.contraptions.Contraption;
import com.simibubi.create.content.contraptions.ContraptionType;
@ -39,11 +38,6 @@ import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate.StructureBlockInfo;
import net.minecraft.world.phys.AABB;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.IFluidHandler;
import net.minecraftforge.fluids.capability.templates.FluidTank;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.ItemStackHandler;
public class CarriageContraption extends Contraption {
@ -67,8 +61,12 @@ public class CarriageContraption extends Contraption {
public int portalCutoffMin;
public int portalCutoffMax;
static final IItemHandlerModifiable fallbackItems = new ItemStackHandler();
static final IFluidHandler fallbackFluids = new FluidTank(0);
static final MountedStorageManager fallbackStorage;
static {
fallbackStorage = new MountedStorageManager();
fallbackStorage.initialize();
}
public CarriageContraption() {
conductorSeats = new HashMap<>();
@ -123,7 +121,8 @@ public class CarriageContraption extends Contraption {
StructureBlockInfo info = blocks.get(controlsPos);
if (!AllBlocks.TRAIN_CONTROLS.has(info.state()))
return false;
return info.state().getValue(ControlsBlock.FACING) == direction.getOpposite();
return info.state()
.getValue(ControlsBlock.FACING) == direction.getOpposite();
}
public void swapStorageAfterAssembly(CarriageContraptionEntity cce) {
@ -228,17 +227,12 @@ public class CarriageContraption extends Contraption {
return false;
}
@Override
protected MountedStorageManager getStorageForSpawnPacket() {
return storageProxy;
}
@Override
public ContraptionType getType() {
return ContraptionType.CARRIAGE;
}
public Direction getAssemblyDirection() {
public Direction getAssemblyDirection() {
return assemblyDirection;
}
@ -321,30 +315,16 @@ public class CarriageContraption extends Contraption {
}
@Override
public IItemHandlerModifiable getSharedInventory() {
return storageProxy == null ? fallbackItems : storageProxy.getItems();
public MountedStorageManager getStorage() {
return storageProxy == null ? fallbackStorage : storageProxy;
}
@Override
public IFluidHandler getSharedFluidTanks() {
return storageProxy == null ? fallbackFluids : storageProxy.getFluids();
}
public void handleContraptionFluidPacket(BlockPos localPos, FluidStack containedFluid) {
storage.updateContainedFluid(localPos, containedFluid);
}
@Override
public MountedStorageManager getStorageManager() {
return storageProxy;
}
@Override
public void tickStorage(AbstractContraptionEntity entity) {
if (entity.level().isClientSide)
storage.entityTick(entity);
else if (storageProxy != null)
storageProxy.entityTick(entity);
public void writeStorage(CompoundTag nbt, boolean spawnPacket) {
if (!spawnPacket)
return;
if (storageProxy != null)
storageProxy.write(nbt, spawnPacket);
}
}

View file

@ -218,7 +218,7 @@ public class Train {
if (shouldActivate)
break;
IItemHandlerModifiable inv = carriage.storage.getItems();
IItemHandlerModifiable inv = carriage.storage.getAllItems();
if (inv != null) {
for (int slot = 0; slot < inv.getSlots(); slot++) {
if (shouldActivate)

View file

@ -45,7 +45,7 @@ public class ItemThresholdCondition extends CargoThresholdCondition {
int foundItems = 0;
for (Carriage carriage : train.carriages) {
IItemHandlerModifiable items = carriage.storage.getItems();
IItemHandlerModifiable items = carriage.storage.getAllItems();
for (int i = 0; i < items.getSlots(); i++) {
ItemStack stackInSlot = items.getStackInSlot(i);
if (!stack.test(level, stackInSlot))

View file

@ -27,6 +27,7 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.items.IItemHandlerModifiable;
public class DeliverPackagesInstruction extends ScheduleInstruction {
@ -78,7 +79,7 @@ public class DeliverPackagesInstruction extends ScheduleInstruction {
}
for (Carriage carriage : train.carriages) {
IItemHandlerModifiable carriageInventory = carriage.storage.getItems();
IItemHandlerModifiable carriageInventory = carriage.storage.getAllItems();
if (carriageInventory == null)
continue;

View file

@ -182,7 +182,7 @@ public class GlobalStation extends SingleBlockEntityEdgePoint {
.getLevel(getBlockEntityDimension());
}
IItemHandlerModifiable carriageInventory = carriage.storage.getItems();
IItemHandlerModifiable carriageInventory = carriage.storage.getAllItems();
if (carriageInventory == null)
continue;

View file

@ -549,5 +549,4 @@ public class BuilderTransformers {
c::get, 2))
.simpleItem();
}
}

View file

@ -13,6 +13,10 @@ import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import com.simibubi.create.CreateClient;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageType;
import com.simibubi.create.api.contraption.storage.fluid.registrate.MountedFluidStorageTypeBuilder;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.contraption.storage.item.registrate.MountedItemStorageTypeBuilder;
import com.simibubi.create.content.decoration.encasing.CasingConnectivity;
import com.simibubi.create.content.fluids.VirtualFluid;
import com.simibubi.create.foundation.block.connected.CTModel;
@ -140,6 +144,14 @@ public class CreateRegistrate extends AbstractRegistrate<CreateRegistrate> {
});
}
public <T extends MountedItemStorageType<?>> MountedItemStorageTypeBuilder<T, CreateRegistrate> mountedItemStorage(String name, Supplier<T> supplier) {
return this.entry(name, callback -> new MountedItemStorageTypeBuilder<>(this, this, name, callback, supplier.get()));
}
public <T extends MountedFluidStorageType<?>> MountedFluidStorageTypeBuilder<T, CreateRegistrate> mountedFluidStorage(String name, Supplier<T> supplier) {
return this.entry(name, callback -> new MountedFluidStorageTypeBuilder<>(this, this, name, callback, supplier.get()));
}
/* Palettes */
public <T extends Block> BlockBuilder<T, CreateRegistrate> paletteStoneBlock(String name,

View file

@ -7,6 +7,8 @@ import java.util.function.Predicate;
import javax.annotation.Nullable;
import net.minecraftforge.items.IItemHandlerModifiable;
import org.apache.commons.lang3.mutable.MutableInt;
import com.simibubi.create.content.logistics.box.PackageEntity;
@ -309,4 +311,13 @@ public class ItemHelper {
return remainder;
}
public static void copyContents(IItemHandler from, IItemHandlerModifiable to) {
if (from.getSlots() != to.getSlots()) {
throw new IllegalArgumentException("Slot count mismatch");
}
for (int i = 0; i < from.getSlots(); i++) {
to.setStackInSlot(i, from.getStackInSlot(i).copy());
}
}
}

View file

@ -0,0 +1,115 @@
package com.simibubi.create.foundation.item;
import java.util.HashMap;
import java.util.Map;
import java.util.function.IntFunction;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.simibubi.create.foundation.utility.CreateCodecs;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
/**
* Utility class representing non-empty slots in an item inventory.
*/
public class ItemSlots {
public static final Codec<ItemSlots> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.unboundedMap(CreateCodecs.boundedIntStr(0), ItemStack.CODEC).fieldOf("items").forGetter(ItemSlots::toBoxedMap),
ExtraCodecs.NON_NEGATIVE_INT.fieldOf("size").forGetter(ItemSlots::getSize)
).apply(instance, ItemSlots::deserialize));
private final Int2ObjectMap<ItemStack> map;
private int size;
public ItemSlots() {
this.map = new Int2ObjectOpenHashMap<>();
this.size = 0;
}
public void set(int slot, ItemStack stack) {
if (slot < 0) {
throw new IllegalArgumentException("Slot must be positive");
} else if (!stack.isEmpty()) {
this.map.put(slot, stack);
this.size = Math.max(this.size, slot + 1);
}
}
public int getSize() {
return this.size;
}
public void setSize(int size) {
if (size <= this.getHighestSlot()) {
throw new IllegalStateException("cannot set size to below the highest slot");
}
this.size = size;
}
public void forEach(SlotConsumer consumer) {
for (Int2ObjectMap.Entry<ItemStack> entry : this.map.int2ObjectEntrySet()) {
consumer.accept(entry.getIntKey(), entry.getValue());
}
}
private int getHighestSlot() {
return this.map.keySet().intStream().max().orElse(-1);
}
public <T extends IItemHandlerModifiable> T toHandler(IntFunction<T> factory) {
T handler = factory.apply(this.size);
this.forEach(handler::setStackInSlot);
return handler;
}
public static ItemSlots fromHandler(IItemHandler handler) {
ItemSlots slots = new ItemSlots();
slots.setSize(handler.getSlots());
for (int i = 0; i < handler.getSlots(); i++) {
ItemStack stack = handler.getStackInSlot(i);
if (!stack.isEmpty()) {
slots.set(i, stack.copy());
}
}
return slots;
}
public Map<Integer, ItemStack> toBoxedMap() {
Map<Integer, ItemStack> map = new HashMap<>();
this.forEach(map::put);
return map;
}
public static ItemSlots fromBoxedMap(Map<Integer, ItemStack> map) {
ItemSlots slots = new ItemSlots();
map.forEach(slots::set);
return slots;
}
public static Codec<ItemSlots> maxSizeCodec(int maxSize) {
return ExtraCodecs.validate(
CODEC,
slots -> slots.size <= maxSize
? DataResult.success(slots)
: DataResult.error(() -> "Slots above maximum of " + maxSize)
);
}
private static ItemSlots deserialize(Map<Integer, ItemStack> map, int size) {
ItemSlots slots = fromBoxedMap(map);
slots.setSize(size);
return slots;
}
@FunctionalInterface
public interface SlotConsumer {
void accept(int slot, ItemStack stack);
}
}

View file

@ -0,0 +1,33 @@
package com.simibubi.create.foundation.utility;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.simibubi.create.foundation.item.ItemSlots;
import net.minecraft.util.ExtraCodecs;
import net.minecraftforge.items.ItemStackHandler;
public class CreateCodecs {
public static final Codec<Integer> INT_STR = Codec.STRING.comapFlatMap(
string -> {
try {
return DataResult.success(Integer.parseInt(string));
} catch (NumberFormatException ignored) {
return DataResult.error(() -> "Not an integer: " + string);
}
},
String::valueOf
);
public static final Codec<ItemStackHandler> ITEM_STACK_HANDLER = ItemSlots.CODEC.xmap(
slots -> slots.toHandler(ItemStackHandler::new), ItemSlots::fromHandler
);
public static Codec<Integer> boundedIntStr(int min) {
return ExtraCodecs.validate(
INT_STR,
i -> i >= min ? DataResult.success(i) : DataResult.error(() -> "Value under minimum of " + min)
);
}
}

View file

@ -0,0 +1,33 @@
package com.simibubi.create.impl.contraption.storage;
import java.util.Optional;
import com.mojang.serialization.Codec;
import com.simibubi.create.AllMountedStorageTypes;
import com.simibubi.create.api.contraption.storage.item.simple.SimpleMountedStorage;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.ItemStackHandler;
/**
* A fallback mounted storage impl that will try to be used when no type is
* registered for a block. This requires that the mounted block provide an item handler
* whose class is exactly {@link ItemStackHandler}.
*/
public class FallbackMountedStorage extends SimpleMountedStorage {
public static final Codec<FallbackMountedStorage> CODEC = SimpleMountedStorage.codec(FallbackMountedStorage::new);
public FallbackMountedStorage(IItemHandler handler) {
super(AllMountedStorageTypes.FALLBACK.get(), handler);
}
@Override
protected Optional<IItemHandlerModifiable> validate(IItemHandler handler) {
return super.validate(handler).filter(FallbackMountedStorage::isValid);
}
public static boolean isValid(IItemHandler handler) {
return handler.getClass() == ItemStackHandler.class;
}
}

View file

@ -0,0 +1,21 @@
package com.simibubi.create.impl.contraption.storage;
import com.simibubi.create.api.contraption.storage.item.simple.SimpleMountedStorageType;
import net.minecraftforge.items.IItemHandler;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public class FallbackMountedStorageType extends SimpleMountedStorageType<FallbackMountedStorage> {
public FallbackMountedStorageType() {
super(FallbackMountedStorage.CODEC);
}
@Override
protected IItemHandler getHandler(BlockEntity be) {
IItemHandler handler = super.getHandler(be);
return handler != null && FallbackMountedStorage.isValid(handler) ? handler : null;
}
}

View file

@ -0,0 +1,57 @@
package com.simibubi.create.impl.contraption.storage;
import java.util.Objects;
import org.jetbrains.annotations.ApiStatus;
import com.simibubi.create.AllMountedStorageTypes;
import com.simibubi.create.AllTags;
import com.simibubi.create.api.contraption.storage.MountedStorageTypeRegistry;
import com.simibubi.create.api.contraption.storage.fluid.MountedFluidStorageType;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.lookup.BlockLookup;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.registries.IForgeRegistry;
import net.minecraftforge.registries.NewRegistryEvent;
import net.minecraftforge.registries.RegistryBuilder;
@ApiStatus.Internal
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
public class MountedStorageTypeRegistryImpl {
public static final BlockLookup<MountedItemStorageType<?>> ITEM_LOOKUP = BlockLookup.create(MountedStorageTypeRegistryImpl::itemFallback);
public static final BlockLookup<MountedFluidStorageType<?>> FLUID_LOOKUP = BlockLookup.create();
private static IForgeRegistry<MountedItemStorageType<?>> itemsRegistry;
private static IForgeRegistry<MountedFluidStorageType<?>> fluidsRegistry;
public static IForgeRegistry<MountedItemStorageType<?>> getItemsRegistry() {
return Objects.requireNonNull(itemsRegistry, "Registry accessed too early");
}
public static IForgeRegistry<MountedFluidStorageType<?>> getFluidsRegistry() {
return Objects.requireNonNull(fluidsRegistry, "Registry accessed too early");
}
@SubscribeEvent
public static void registerRegistries(NewRegistryEvent event) {
event.create(
new RegistryBuilder<MountedItemStorageType<?>>()
.setName(MountedStorageTypeRegistry.ITEMS.location()),
registry -> itemsRegistry = registry
);
event.create(
new RegistryBuilder<MountedFluidStorageType<?>>()
.setName(MountedStorageTypeRegistry.FLUIDS.location()),
registry -> fluidsRegistry = registry
);
}
private static MountedItemStorageType<?> itemFallback(Block block) {
return AllTags.AllBlockTags.FALLBACK_MOUNTED_STORAGE_BLACKLIST.matches(block)
? null
: AllMountedStorageTypes.FALLBACK.get();
}
}

View file

@ -0,0 +1,103 @@
package com.simibubi.create.impl.lookup;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.simibubi.create.api.lookup.BlockLookup;
import net.minecraftforge.event.TagsUpdatedEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
@ApiStatus.Internal
@Mod.EventBusSubscriber
public class BlockLookupImpl<T> implements BlockLookup<T> {
private static final List<BlockLookupImpl<?>> allLookups = new ArrayList<>();
private final Map<Block, T> map;
private final Map<Block, T> providedValues;
private final Set<Block> providedNull;
private final List<Provider<T>> providers;
public BlockLookupImpl() {
this.map = new IdentityHashMap<>();
this.providedValues = new IdentityHashMap<>();
this.providedNull = new HashSet<>();
this.providers = new ArrayList<>();
allLookups.add(this);
}
@Nullable
@Override
public T find(BlockState state) {
return this.find(state.getBlock());
}
@Nullable
@Override
public T find(Block block) {
T registered = this.map.get(block);
if (registered != null)
return registered;
if (this.providedNull.contains(block))
return null;
return this.providedValues.computeIfAbsent(block, $ -> {
for (Provider<T> provider : this.providers) {
T value = provider.get(block);
if (value != null) {
return value;
}
}
this.providedNull.add(block);
return null;
});
}
@Override
public void register(Block block, T value) {
this.map.put(block, value);
}
@Override
public void registerTag(TagKey<Block> tag, T value) {
this.registerProvider(new TagProvider<>(tag, value));
}
@Override
public void registerProvider(Provider<T> provider) {
this.providers.add(0, provider);
}
@SubscribeEvent
public static void onTagsReloaded(TagsUpdatedEvent event) {
if (event.getUpdateCause() == TagsUpdatedEvent.UpdateCause.SERVER_DATA_LOAD) {
for (BlockLookupImpl<?> lookup : allLookups) {
lookup.providedValues.clear();
lookup.providedNull.clear();
}
}
}
private record TagProvider<T>(TagKey<Block> tag, T value) implements Provider<T> {
@Override
@Nullable
@SuppressWarnings("deprecation")
public T get(Block block) {
return block.builtInRegistryHolder().is(this.tag) ? this.value : null;
}
}
}

View file

@ -109,6 +109,18 @@ public class CreateRegistrateTags {
.addTag(BlockTags.PRESSURE_PLATES)
.addTag(BlockTags.RAILS);
// tags aren't used here because the implementations of modded entries are unknown
prov.tag(AllBlockTags.CHEST_MOUNTED_STORAGE.tag).add(
Blocks.CHEST, Blocks.TRAPPED_CHEST
);
prov.tag(AllBlockTags.SIMPLE_MOUNTED_STORAGE.tag).add(
Blocks.BARREL, Blocks.SHULKER_BOX,
Blocks.WHITE_SHULKER_BOX, Blocks.ORANGE_SHULKER_BOX, Blocks.MAGENTA_SHULKER_BOX, Blocks.LIGHT_BLUE_SHULKER_BOX,
Blocks.YELLOW_SHULKER_BOX, Blocks.LIME_SHULKER_BOX, Blocks.PINK_SHULKER_BOX, Blocks.GRAY_SHULKER_BOX,
Blocks.LIGHT_GRAY_SHULKER_BOX, Blocks.CYAN_SHULKER_BOX, Blocks.PURPLE_SHULKER_BOX, Blocks.BLUE_SHULKER_BOX,
Blocks.BROWN_SHULKER_BOX, Blocks.GREEN_SHULKER_BOX, Blocks.RED_SHULKER_BOX, Blocks.BLACK_SHULKER_BOX
);
prov.tag(AllBlockTags.ROOTS.tag)
.add(Blocks.MANGROVE_ROOTS);