double it and give it to the next person

- fluid mounted storage
- handling for old data format
- a few more docs
This commit is contained in:
TropheusJ 2025-01-24 23:07:07 -05:00
parent 5c0b012357
commit 595263e0df
25 changed files with 597 additions and 71 deletions

View file

@ -3,6 +3,7 @@ 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;
@ -989,6 +990,7 @@ public class AllBlocks {
.blockstate(new FluidTankGenerator()::generate)
.onRegister(CreateRegistrate.blockModel(() -> FluidTankModel::standard))
.onRegister(assignDataBehaviour(new BoilerDisplaySource(), "boiler_status"))
.transform(mountedFluidStorage(AllMountedStorageTypes.FLUID_TANK))
.addLayer(() -> RenderType::cutoutMipped)
.item(FluidTankItem::new)
.model(AssetLookup.customBlockItemModel("_", "block_single_window"))
@ -1004,6 +1006,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))

View file

@ -4,29 +4,31 @@ 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.api.contraption.storage.item.simple.SimpleMountedStorageType;
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 = simple("fallback", FallbackMountedStorageType::new);
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 = simple("creative_crate", CreativeCrateMountedStorageType::new);
public static final RegistryEntry<ItemVaultMountedStorageType> VAULT = simple("vault", ItemVaultMountedStorageType::new);
public static final RegistryEntry<ToolboxMountedStorageType> TOOLBOX = simple("toolbox", ToolboxMountedStorageType::new);
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<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)
@ -40,10 +42,14 @@ public class AllMountedStorageTypes {
.registerTo(Blocks.DROPPER)
.register();
private static <T extends MountedItemStorageType<?>> RegistryEntry<T> simple(String name, Supplier<T> supplier) {
private static <T extends MountedItemStorageType<?>> RegistryEntry<T> simpleItem(String name, Supplier<T> supplier) {
return REGISTRATE.mountedItemStorage(name, supplier).register();
}
private static <T extends MountedFluidStorageType<?>> RegistryEntry<T> simpleFluid(String name, Supplier<T> supplier) {
return REGISTRATE.mountedFluidStorage(name, supplier).register();
}
public static void register() {
}
}

View file

@ -1,6 +1,7 @@
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;
@ -8,22 +9,29 @@ import com.tterrag.registrate.builders.BlockBuilder;
import com.tterrag.registrate.util.entry.RegistryEntry;
import com.tterrag.registrate.util.nullness.NonNullUnaryOperator;
import net.minecraftforge.registries.IForgeRegistry;
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
@ -32,6 +40,13 @@ public class MountedStorageTypeRegistry {
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.
@ -39,4 +54,12 @@ public class MountedStorageTypeRegistry {
public static <B extends Block, P> NonNullUnaryOperator<BlockBuilder<B, P>> mountedItemStorage(RegistryEntry<? extends MountedItemStorageType<?>> type) {
return builder -> builder.onRegisterAfter(ITEMS, block -> ITEM_LOOKUP.register(block, type.get()));
}
/**
* Utility for use with Registrate builders. Creates a builder transformer
* that will register the given MountedFluidStorageType to a block when ready.
*/
public static <B extends Block, P> NonNullUnaryOperator<BlockBuilder<B, P>> mountedFluidStorage(RegistryEntry<? extends MountedFluidStorageType<?>> type) {
return builder -> builder.onRegisterAfter(ITEMS, block -> FLUID_LOOKUP.register(block, type.get()));
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,11 +2,14 @@ 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;
import net.minecraft.core.BlockPos;
/**
* 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;

View file

@ -7,6 +7,9 @@ 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;

View file

@ -24,7 +24,6 @@ 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.fml.common.Mod;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.wrapper.CombinedInvWrapper;
@ -33,7 +32,6 @@ import net.minecraftforge.items.wrapper.InvWrapper;
/**
* Mounted storage that handles opening a combined GUI for double chests.
*/
@Mod.EventBusSubscriber
public class ChestMountedStorage extends SimpleMountedStorage {
public static final Codec<ChestMountedStorage> CODEC = SimpleMountedStorage.codec(ChestMountedStorage::new);

View file

@ -1,10 +1,10 @@
package com.simibubi.create.api.lookup;
import com.simibubi.create.impl.lookup.BlockLookupImpl;
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;
@ -14,7 +14,10 @@ import net.minecraft.world.level.block.state.BlockState;
* 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.
* <br>
* <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.
*/

View file

@ -9,11 +9,20 @@ import org.jetbrains.annotations.Nullable;
import com.google.common.collect.ImmutableMap;
import com.mojang.datafixers.util.Pair;
import com.simibubi.create.Create;
import com.simibubi.create.api.contraption.storage.MountedStorageTypeRegistry;
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.foundation.fluid.CombinedTankWrapper;
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.vault.ItemVaultMountedStorage;
import com.simibubi.create.impl.contraption.storage.FallbackMountedStorage;
import net.createmod.catnip.utility.NBTHelper;
import net.minecraft.core.BlockPos;
@ -24,23 +33,27 @@ import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
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.fluids.capability.IFluidHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraftforge.items.wrapper.CombinedInvWrapper;
public class MountedStorageManager {
// builders used during assembly, null afterward
private ImmutableMap.Builder<BlockPos, MountedItemStorage> itemsBuilder;
private ImmutableMap.Builder<BlockPos, MountedFluidStorage> fluidsBuilder;
// built data structures after assembly, null before
protected ImmutableMap<BlockPos, MountedItemStorage> allItemStorages;
// different from allItemStorages, does not contain internal ones
protected MountedItemStorageWrapper items;
@Nullable
protected MountedItemStorageWrapper fuelItems;
protected MountedFluidStorageWrapper fluids;
private List<IItemHandlerModifiable> externalHandlers;
protected CombinedInvWrapper allItems;
@ -67,6 +80,10 @@ public class MountedStorageManager {
this.allItemStorages, storage -> !storage.isInternal() && storage.providesFuel()
);
this.fuelItems = fuelMap.isEmpty() ? null : new MountedItemStorageWrapper(fuelMap);
ImmutableMap<BlockPos, MountedFluidStorage> fluids = this.fluidsBuilder.build();
this.fluids = new MountedFluidStorageWrapper(fluids);
this.fluidsBuilder = null;
}
private boolean isInitialized() {
@ -83,30 +100,48 @@ public class MountedStorageManager {
this.allItemStorages = null;
this.items = null;
this.fuelItems = null;
this.fluids = null;
this.externalHandlers = new ArrayList<>();
this.allItems = null;
this.itemsBuilder = ImmutableMap.builder();
this.fluidsBuilder = ImmutableMap.builder();
}
public void addBlock(Level level, BlockState state, BlockPos globalPos, BlockPos localPos, @Nullable BlockEntity be) {
MountedItemStorageType<?> type = MountedStorageTypeRegistry.ITEM_LOOKUP.find(state);
if (type == null)
return;
MountedItemStorageType<?> itemType = MountedStorageTypeRegistry.ITEM_LOOKUP.find(state);
if (itemType != null) {
MountedItemStorage storage = itemType.mount(level, state, globalPos, be);
if (storage != null) {
this.itemsBuilder.put(localPos, storage);
}
}
MountedItemStorage storage = type.mount(level, state, globalPos, be);
if (storage != null) {
this.itemsBuilder.put(localPos, storage);
MountedFluidStorageType<?> fluidType = MountedStorageTypeRegistry.FLUID_LOOKUP.find(state);
if (fluidType != null) {
MountedFluidStorage storage = fluidType.mount(level, state, globalPos, be);
if (storage != null) {
this.fluidsBuilder.put(localPos, storage);
}
}
}
public void unmount(Level level, StructureBlockInfo info, BlockPos globalPos, @Nullable BlockEntity be) {
BlockPos localPos = info.pos();
MountedItemStorage storage = this.getAllItemStorages().get(localPos);
if (storage != null) {
BlockState state = info.state();
BlockState state = info.state();
MountedItemStorage itemStorage = this.getAllItemStorages().get(localPos);
if (itemStorage != null) {
MountedItemStorageType<?> expectedType = MountedStorageTypeRegistry.ITEM_LOOKUP.find(state);
if (storage.type == expectedType) {
storage.unmount(level, state, globalPos, be);
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);
}
}
}
@ -114,35 +149,62 @@ public class MountedStorageManager {
public void read(CompoundTag nbt) {
this.reset();
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)
// .or(() -> Optional.ofNullable(parseLegacy(data)))
.ifPresent(storage -> this.itemsBuilder.put(pos, storage));
});
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.itemsBuilder.put(pos, storage));
});
// NBTHelper.iterateCompoundList(nbt.getList("FluidStorage", Tag.TAG_COMPOUND), c -> fluidStorage
// .put(NbtUtils.readBlockPos(c.getCompound("Pos")), MountedFluidStorage.deserialize(c.getCompound("Data"))));
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.fluidsBuilder.put(pos, storage));
});
this.readLegacy(nbt);
} 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();
}
public void write(CompoundTag nbt, boolean clientPacket) {
ListTag items = new ListTag();
nbt.put("items", items);
if (!this.getAllItemStorages().isEmpty()) {
ListTag items = new ListTag();
this.getAllItemStorages().forEach(
(pos, storage) -> 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);
})
);
nbt.put("items", items);
}
this.getAllItemStorages().forEach(
(pos, storage) -> 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 (!this.getFluids().storages.isEmpty()) {
ListTag fluids = new ListTag();
this.getFluids().storages.forEach(
(pos, storage) -> 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);
})
);
nbt.put("fluids", fluids);
}
}
public void attachExternal(IItemHandlerModifiable externalStorage) {
@ -198,8 +260,9 @@ public class MountedStorageManager {
return this.allItems;
}
public IFluidHandler getFluids() {
return new CombinedTankWrapper();
public MountedFluidStorageWrapper getFluids() {
this.assertInitialized();
return this.fluids;
}
public boolean handlePlayerStorageInteraction(Contraption contraption, Player player, BlockPos localPos) {
@ -217,6 +280,39 @@ public class MountedStorageManager {
}
}
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");
// note: Synced is ignored, since all are synced
if (data.contains("Toolbox")) {
this.itemsBuilder.put(pos, ToolboxMountedStorage.fromLegacy(data));
} else if (data.contains("NoFuel")) {
this.itemsBuilder.put(pos, ItemVaultMountedStorage.fromLegacy(data));
} else if (data.contains("Bottomless")) {
ItemStack supplied = ItemStack.of(data.getCompound("ProvidedStack"));
this.itemsBuilder.put(pos, new CreativeCrateMountedStorage(supplied));
} else {
// we can create a fallback storage safely, it will be validated before unmounting
ItemStackHandler handler = new ItemStackHandler();
handler.deserializeNBT(data);
this.itemsBuilder.put(pos, new FallbackMountedStorage(handler));
}
});
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.fluidsBuilder.put(pos, CreativeFluidTankMountedStorage.fromLegacy(data));
} else {
this.fluidsBuilder.put(pos, FluidTankMountedStorage.fromLegacy(data));
}
});
}
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) -> {

View file

@ -10,6 +10,7 @@ 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;
@ -50,4 +51,10 @@ public class ToolboxMountedStorage extends WrapperMountedItemStorage<ToolboxInve
copy.filters = toolbox.inventory.filters.stream().map(ItemStack::copy).toList();
return new ToolboxMountedStorage(copy);
}
public static ToolboxMountedStorage fromLegacy(CompoundTag nbt) {
ToolboxInventory inv = new ToolboxInventory(null);
inv.deserializeNBT(nbt);
return new ToolboxMountedStorage(inv);
}
}

View file

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

View file

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

View file

@ -0,0 +1,55 @@
package com.simibubi.create.content.fluids.tank.storage;
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.FluidTankBlockEntity;
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.fluids.capability.templates.FluidTank;
public class FluidTankMountedStorage extends WrapperMountedFluidStorage<FluidTank> {
public static final Codec<FluidTankMountedStorage> CODEC = CreateCodecs.FLUID_TANK.xmap(
FluidTankMountedStorage::new, storage -> storage.wrapped
);
protected FluidTankMountedStorage(MountedFluidStorageType<?> type, FluidTank wrapped) {
super(type, wrapped);
}
protected FluidTankMountedStorage(FluidTank wrapped) {
this(AllMountedStorageTypes.FLUID_TANK.get(), wrapped);
}
@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 static FluidTankMountedStorage fromTank(FluidTankBlockEntity tank) {
// tank has update callbacks, make an isolated copy
FluidTank inventory = tank.getTankInventory();
FluidTank copy = new FluidTank(inventory.getCapacity());
copy.setFluid(inventory.getFluid());
return new FluidTankMountedStorage(copy);
}
public static FluidTankMountedStorage fromLegacy(CompoundTag nbt) {
int capacity = nbt.getInt("Capacity");
FluidTank tank = new FluidTank(capacity);
tank.readFromNBT(nbt);
return new FluidTankMountedStorage(tank);
}
}

View file

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

View file

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

View file

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

View file

@ -9,6 +9,7 @@ import com.simibubi.create.api.contraption.storage.item.WrapperMountedItemStorag
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;
@ -43,4 +44,10 @@ public class ItemVaultMountedStorage extends WrapperMountedItemStorage<ItemStack
// Vault inventories have a world-affecting onContentsChanged, copy to a safe one
return new ItemVaultMountedStorage(copyToItemStackHandler(vault.getInventoryOfBlock()));
}
public static ItemVaultMountedStorage fromLegacy(CompoundTag nbt) {
ItemStackHandler handler = new ItemStackHandler();
handler.deserializeNBT(nbt);
return new ItemVaultMountedStorage(handler);
}
}

View file

@ -10,13 +10,13 @@ import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import com.simibubi.create.api.contraption.storage.item.MountedItemStorageType;
import com.simibubi.create.api.contraption.storage.item.registrate.MountedItemStorageTypeBuilder;
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;
@ -148,6 +148,10 @@ public class CreateRegistrate extends AbstractRegistrate<CreateRegistrate> {
return this.entry(name, callback -> new MountedItemStorageTypeBuilder<>(this, this, name, callback, supplier.get()));
}
public <T extends MountedFluidStorageType<?>> MountedFluidStorageTypeBuilder<T, CreateRegistrate> mountedFluidStorage(String name, Supplier<T> supplier) {
return this.entry(name, callback -> new MountedFluidStorageTypeBuilder<>(this, this, name, callback, supplier.get()));
}
/* Palettes */
public <T extends Block> BlockBuilder<T, CreateRegistrate> paletteStoneBlock(String name,

View file

@ -11,11 +11,14 @@ import com.simibubi.create.foundation.utility.CreateCodecs;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
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),

View file

@ -2,11 +2,13 @@ package com.simibubi.create.foundation.utility;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.simibubi.create.foundation.item.ItemSlots;
import net.minecraftforge.items.ItemStackHandler;
import net.minecraft.util.ExtraCodecs;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.capability.templates.FluidTank;
import net.minecraftforge.items.ItemStackHandler;
public class CreateCodecs {
public static final Codec<Integer> INT_STR = Codec.STRING.comapFlatMap(
@ -24,6 +26,18 @@ public class CreateCodecs {
slots -> slots.toHandler(ItemStackHandler::new), ItemSlots::fromHandler
);
/**
* Codec for a simple FluidTank with no validator support.
*/
public static final Codec<FluidTank> FLUID_TANK = 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) -> {
FluidTank tank = new FluidTank(capacity);
tank.setFluid(fluid);
return tank;
}));
public static Codec<Integer> boundedIntStr(int min) {
return ExtraCodecs.validate(
INT_STR,

View file

@ -7,6 +7,7 @@ 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;
@ -21,13 +22,19 @@ import net.minecraftforge.registries.RegistryBuilder;
@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(
@ -35,9 +42,14 @@ public class MountedStorageTypeRegistryImpl {
.setName(MountedStorageTypeRegistry.ITEMS.location()),
registry -> itemsRegistry = registry
);
event.create(
new RegistryBuilder<MountedFluidStorageType<?>>()
.setName(MountedStorageTypeRegistry.FLUIDS.location()),
registry -> fluidsRegistry = registry
);
}
public static MountedItemStorageType<?> itemFallback(Block block) {
private static MountedItemStorageType<?> itemFallback(Block block) {
return AllTags.AllBlockTags.FALLBACK_MOUNTED_STORAGE_BLACKLIST.matches(block)
? null
: AllMountedStorageTypes.FALLBACK.get();