mirror of
https://github.com/Creators-of-Create/Create.git
synced 2025-03-04 06:44:40 +01:00
Merge pull request #3 from Creators-of-Create/jay/mc1.20.1/mounted-storage-refactor
Mounted Storage Refactor
This commit is contained in:
commit
f83b8523a6
87 changed files with 3352 additions and 1617 deletions
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"values": [
|
||||
"minecraft:chest",
|
||||
"minecraft:trapped_chest"
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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"))
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -128,6 +128,7 @@ public class Create {
|
|||
AllPackets.registerPackets();
|
||||
AllFeatures.register(modEventBus);
|
||||
AllPlacementModifiers.register(modEventBus);
|
||||
AllMountedStorageTypes.register();
|
||||
|
||||
AllConfigs.register(modLoadingContext);
|
||||
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -549,5 +549,4 @@ public class BuilderTransformers {
|
|||
c::get, 2))
|
||||
.simpleItem();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
115
src/main/java/com/simibubi/create/foundation/item/ItemSlots.java
Normal file
115
src/main/java/com/simibubi/create/foundation/item/ItemSlots.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue