diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index 42d5d8d73..ae8bc074f 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -1,15 +1,22 @@ package ru.dbotthepony.mc.otm; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.InterModComms; +import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.event.lifecycle.InterModEnqueueEvent; import net.minecraftforge.fml.event.lifecycle.InterModProcessEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fmlserverevents.FMLServerStartedEvent; +import net.minecraftforge.fmlserverevents.FMLServerStoppingEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import ru.dbotthepony.mc.otm.block.entity.BlockEntityBlackHole; @@ -25,7 +32,12 @@ import ru.dbotthepony.mc.otm.matter.MatterGrid; import ru.dbotthepony.mc.otm.matter.MatterRegistry; import ru.dbotthepony.mc.otm.network.MatteryNetworking; -import java.util.UUID; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; // The value here should match an entry in the META-INF/mods.toml file @@ -39,6 +51,67 @@ public class OverdriveThatMatters { public static CreativeModeTab CREATIVE_TAB; + private static final WeakHashMap>> tick_until = new WeakHashMap<>(); + private static final WeakHashMap>> tick_once = new WeakHashMap<>(); + + @SubscribeEvent + public void onServerStarting(FMLServerStartedEvent event) { + tick_until.clear(); + } + + @SubscribeEvent + public void onServerStopping(FMLServerStoppingEvent event) { + tick_until.clear(); + } + + @SubscribeEvent + public void onPreTick(TickEvent.WorldTickEvent event) { + if (event.phase != TickEvent.Phase.START || event.side != LogicalSide.SERVER) + return; + + var set = tick_until.get(event.world); + + if (set != null) { + ArrayList> invalid = new ArrayList<>(); + + for (var ticker : set) { + if (ticker.get()) { + invalid.add(ticker); + } + } + + if (invalid.size() != 0) { + for (Supplier cell : invalid) { + set.remove(cell); + } + } + + if (set.size() == 0) { + tick_until.remove(event.world); + } + } + + var list = tick_once.get(event.world); + + if (list != null) { + ArrayList> invalid = new ArrayList<>(); + + for (var ticker : list) { + ticker.accept(event.world); + } + + tick_once.remove(event.world); + } + } + + public static void tickUntil(Level level, Supplier ticker) { + tick_until.computeIfAbsent(level, (k) -> new HashSet<>()).add(ticker); + } + + public static void tickOnce(Level level, Consumer ticker) { + tick_once.computeIfAbsent(level, (k) -> new ArrayList<>()).add(ticker); + } + public OverdriveThatMatters() { // Register the setup method for modloading FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup); diff --git a/src/main/java/ru/dbotthepony/mc/otm/Registry.java b/src/main/java/ru/dbotthepony/mc/otm/Registry.java index 6ee1d26b7..413a533be 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/Registry.java +++ b/src/main/java/ru/dbotthepony/mc/otm/Registry.java @@ -107,6 +107,8 @@ public class Registry { public static final ResourceLocation MATTER_REPLICATOR = new ResourceLocation(OverdriveThatMatters.MOD_ID, "matter_replicator"); public static final ResourceLocation MATTER_BOTTLER = new ResourceLocation(OverdriveThatMatters.MOD_ID, "matter_bottler"); public static final ResourceLocation DRIVE_VIEWER = new ResourceLocation(OverdriveThatMatters.MOD_ID, "drive_viewer"); + public static final ResourceLocation DRIVE_RACK = new ResourceLocation(OverdriveThatMatters.MOD_ID, "drive_rack"); + public static final ResourceLocation ITEM_MONITOR = new ResourceLocation(OverdriveThatMatters.MOD_ID, "item_monitor"); public static final ResourceLocation BLACK_HOLE = new ResourceLocation(OverdriveThatMatters.MOD_ID, "black_hole"); public static final ResourceLocation CARGO_CRATE = new ResourceLocation(OverdriveThatMatters.MOD_ID, "cargo_crate"); @@ -238,6 +240,8 @@ public class Registry { public static final BlockMatterBottler MATTER_BOTTLER = new BlockMatterBottler(); public static final BlockDriveViewer DRIVE_VIEWER = new BlockDriveViewer(); public static final BlockCargoCrate CARGO_CRATE = new BlockCargoCrate(); + public static final BlockDriveRack DRIVE_RACK = new BlockDriveRack(); + public static final BlockItemMonitor ITEM_MONITOR = new BlockItemMonitor(); public static final BlockBlackHole BLACK_HOLE = new BlockBlackHole(); @@ -313,6 +317,8 @@ public class Registry { DRIVE_VIEWER.setRegistryName(Names.DRIVE_VIEWER); BLACK_HOLE.setRegistryName(Names.BLACK_HOLE); CARGO_CRATE.setRegistryName(Names.CARGO_CRATE); + DRIVE_RACK.setRegistryName(Names.DRIVE_RACK); + ITEM_MONITOR.setRegistryName(Names.ITEM_MONITOR); TRITANIUM_BLOCK.setRegistryName(Names.TRITANIUM_BLOCK); TRITANIUM_STRIPED_BLOCK.setRegistryName(Names.TRITANIUM_STRIPED_BLOCK); @@ -340,6 +346,8 @@ public class Registry { event.getRegistry().register(TRITANIUM_ORE); event.getRegistry().register(DEEPSLATE_TRITANIUM_ORE); event.getRegistry().register(TRITANIUM_RAW_BLOCK); + event.getRegistry().register(DRIVE_RACK); + event.getRegistry().register(ITEM_MONITOR); for (var crate : CRATES) { event.getRegistry().register(crate); @@ -365,6 +373,8 @@ public class Registry { public static final Item TRITANIUM_ORE = new BlockItem(Blocks.TRITANIUM_ORE, new Item.Properties().stacksTo(64).tab(OverdriveThatMatters.CREATIVE_TAB)); public static final Item DEEPSLATE_TRITANIUM_ORE = new BlockItem(Blocks.DEEPSLATE_TRITANIUM_ORE, new Item.Properties().stacksTo(64).tab(OverdriveThatMatters.CREATIVE_TAB)); public static final Item TRITANIUM_RAW_BLOCK = new BlockItem(Blocks.TRITANIUM_RAW_BLOCK, new Item.Properties().stacksTo(64).tab(OverdriveThatMatters.CREATIVE_TAB)); + public static final Item DRIVE_RACK = new BlockItem(Blocks.DRIVE_RACK, new Item.Properties().stacksTo(64).tab(OverdriveThatMatters.CREATIVE_TAB)); + public static final Item ITEM_MONITOR = new BlockItem(Blocks.ITEM_MONITOR, new Item.Properties().stacksTo(64).tab(OverdriveThatMatters.CREATIVE_TAB)); public static final Item TRITANIUM_ORE_CLUMP = new Item(new Item.Properties().stacksTo(64).tab(OverdriveThatMatters.CREATIVE_TAB)); public static final Item TRITANIUM_INGOT = new Item(new Item.Properties().stacksTo(64).tab(OverdriveThatMatters.CREATIVE_TAB)); @@ -436,6 +446,8 @@ public class Registry { DRIVE_VIEWER.setRegistryName(Names.DRIVE_VIEWER); BLACK_HOLE.setRegistryName(Names.BLACK_HOLE); CARGO_CRATE.setRegistryName(Names.CARGO_CRATE); + DRIVE_RACK.setRegistryName(Names.DRIVE_RACK); + ITEM_MONITOR.setRegistryName(Names.ITEM_MONITOR); TRITANIUM_ORE.setRegistryName(Names.TRITANIUM_ORE); DEEPSLATE_TRITANIUM_ORE.setRegistryName(Names.DEEPSLATE_TRITANIUM_ORE); @@ -500,6 +512,8 @@ public class Registry { event.getRegistry().register(DRIVE_VIEWER); event.getRegistry().register(BLACK_HOLE); event.getRegistry().register(CARGO_CRATE); + event.getRegistry().register(DRIVE_RACK); + event.getRegistry().register(ITEM_MONITOR); event.getRegistry().register(TRITANIUM_ORE); event.getRegistry().register(DEEPSLATE_TRITANIUM_ORE); @@ -571,6 +585,8 @@ public class Registry { public static final BlockEntityType DRIVE_VIEWER = BlockEntityType.Builder.of(BlockEntityDriveViewer::new, Blocks.DRIVE_VIEWER).build(null); public static final BlockEntityType BLACK_HOLE = BlockEntityType.Builder.of(BlockEntityBlackHole::new, Blocks.BLACK_HOLE).build(null); public static final BlockEntityType CARGO_CRATE = BlockEntityType.Builder.of(BlockEntityCargoCrate::new, Blocks.CARGO_CRATE).build(null); + public static final BlockEntityType DRIVE_RACK = BlockEntityType.Builder.of(BlockEntityDriveRack::new, Blocks.DRIVE_RACK).build(null); + public static final BlockEntityType ITEM_MONITOR = BlockEntityType.Builder.of(BlockEntityItemMonitor::new, Blocks.ITEM_MONITOR).build(null); static { ANDROID_STATION.setRegistryName(Names.ANDROID_STATION); @@ -586,6 +602,8 @@ public class Registry { DRIVE_VIEWER.setRegistryName(Names.DRIVE_VIEWER); BLACK_HOLE.setRegistryName(Names.BLACK_HOLE); CARGO_CRATE.setRegistryName(Names.CARGO_CRATE); + DRIVE_RACK.setRegistryName(Names.DRIVE_RACK); + ITEM_MONITOR.setRegistryName(Names.ITEM_MONITOR); } @SubscribeEvent @@ -603,6 +621,8 @@ public class Registry { event.getRegistry().register(DRIVE_VIEWER); event.getRegistry().register(BLACK_HOLE); event.getRegistry().register(CARGO_CRATE); + event.getRegistry().register(DRIVE_RACK); + event.getRegistry().register(ITEM_MONITOR); // OverdriveThatMatters.LOGGER.info("Registered block entities"); } @@ -846,6 +866,8 @@ public class Registry { public static final MenuType MATTER_BOTTLER = new MenuType<>(MatterBottlerMenu::new); public static final MenuType DRIVE_VIEWER = new MenuType<>(DriveViewerMenu::new); public static final MenuType CARGO_CRATE = new MenuType<>(CargoCrateMenu::new); + public static final MenuType DRIVE_RACK = new MenuType<>(DriveRackMenu::new); + public static final MenuType ITEM_MONITOR = new MenuType<>(ItemMonitorMenu::new); static { ANDROID_STATION.setRegistryName(Names.ANDROID_STATION); @@ -859,6 +881,8 @@ public class Registry { MATTER_BOTTLER.setRegistryName(Names.MATTER_BOTTLER); DRIVE_VIEWER.setRegistryName(Names.DRIVE_VIEWER); CARGO_CRATE.setRegistryName(Names.CARGO_CRATE); + DRIVE_RACK.setRegistryName(Names.DRIVE_RACK); + ITEM_MONITOR.setRegistryName(Names.ITEM_MONITOR); } @SubscribeEvent @@ -874,6 +898,8 @@ public class Registry { event.getRegistry().register(MATTER_BOTTLER); event.getRegistry().register(DRIVE_VIEWER); event.getRegistry().register(CARGO_CRATE); + event.getRegistry().register(DRIVE_RACK); + event.getRegistry().register(ITEM_MONITOR); // OverdriveThatMatters.LOGGER.info("Registered menus"); } @@ -891,6 +917,8 @@ public class Registry { MenuScreens.register(MATTER_BOTTLER, MatterBottlerScreen::new); MenuScreens.register(DRIVE_VIEWER, DriveViewerScreen::new); MenuScreens.register(CARGO_CRATE, CargoCrateScreen::new); + MenuScreens.register(DRIVE_RACK, DriveRackScreen::new); + MenuScreens.register(ITEM_MONITOR, ItemMonitorScreen::new); // OverdriveThatMatters.LOGGER.info("Registered screens"); } diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/BlockDriveRack.java b/src/main/java/ru/dbotthepony/mc/otm/block/BlockDriveRack.java new file mode 100644 index 000000000..0714c1dff --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/block/BlockDriveRack.java @@ -0,0 +1,17 @@ +package ru.dbotthepony.mc.otm.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import ru.dbotthepony.mc.otm.block.entity.BlockEntityDriveRack; + +import javax.annotation.Nullable; + +public class BlockDriveRack extends BlockMatteryRotatable implements EntityBlock { + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return new BlockEntityDriveRack(blockPos, blockState); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/BlockItemMonitor.java b/src/main/java/ru/dbotthepony/mc/otm/block/BlockItemMonitor.java new file mode 100644 index 000000000..891f6563d --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/block/BlockItemMonitor.java @@ -0,0 +1,17 @@ +package ru.dbotthepony.mc.otm.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import ru.dbotthepony.mc.otm.block.entity.BlockEntityItemMonitor; + +import javax.annotation.Nullable; + +public class BlockItemMonitor extends BlockMatteryRotatable implements EntityBlock { + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return new BlockEntityItemMonitor(blockPos, blockState); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityDriveRack.java b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityDriveRack.java new file mode 100644 index 000000000..4afd60783 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityDriveRack.java @@ -0,0 +1,117 @@ +package ru.dbotthepony.mc.otm.block.entity; + +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import ru.dbotthepony.mc.otm.Registry; +import ru.dbotthepony.mc.otm.capability.AbstractStorageGridCell; +import ru.dbotthepony.mc.otm.capability.IStorageGridCell; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.capability.MatteryMachineEnergyStorage; +import ru.dbotthepony.mc.otm.container.MatteryContainer; +import ru.dbotthepony.mc.otm.menu.DriveRackMenu; +import ru.dbotthepony.mc.otm.storage.IStorageIdentity; +import ru.dbotthepony.mc.otm.storage.ItemStackObject; +import ru.dbotthepony.mc.otm.storage.StorageItemView; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import java.math.BigDecimal; +import java.util.List; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public class BlockEntityDriveRack extends BlockEntityMatteryPowered { + public BlockEntityDriveRack(BlockPos p_155229_, BlockState p_155230_) { + super(Registry.BlockEntities.DRIVE_RACK, p_155229_, p_155230_); + energy = new MatteryMachineEnergyStorage(this, MatteryMachineEnergyStorage.MachineType.WORKER, new BigDecimal(80_000)); + } + + public final MatteryContainer drives = new MatteryContainer(this::setChanged, 4) { + @Override + public void setChanged(int slot, ItemStack new_state, ItemStack old_state) { + super.setChanged(slot, new_state, old_state); + + /*old_state.getCapability(MatteryCapability.DRIVE).ifPresent((drive) -> { + drive.removeListenerAuto(cell.computeIfAbsent(ItemStackObject.class, StorageItemView::new)); + }); + + new_state.getCapability(MatteryCapability.DRIVE).ifPresent((drive) -> { + drive.addListenerAuto(cell.computeIfAbsent(ItemStackObject.class, StorageItemView::new)); + });*/ + + old_state.getCapability(MatteryCapability.DRIVE).ifPresent(cell::removeStorageComponent); + new_state.getCapability(MatteryCapability.DRIVE).ifPresent(cell::addStorageComponent); + } + }; + + public final AbstractStorageGridCell cell = new AbstractStorageGridCell(); + + @Override + public void load(CompoundTag nbt) { + super.load(nbt); + drives.deserializeNBT(nbt.getCompound("drives")); + } + + @Override + public CompoundTag save(CompoundTag nbt) { + nbt.put("drives", drives.serializeNBT()); + return super.save(nbt); + } + + @Override + public void setLevel(Level p_155231_) { + super.setLevel(p_155231_); + + cell.scheduleDiscoverNeighbours(p_155231_, getBlockPos()); + } + + private static final TranslatableComponent NAME = new TranslatableComponent("block.overdrive_that_matters.drive_rack"); + + @Override + protected Component getDefaultDisplayName() { + return NAME; + } + + @Nullable + @Override + public AbstractContainerMenu createMenu(int containerID, Inventory inventory, Player ply) { + return new DriveRackMenu(containerID, inventory, this); + } + + @Nonnull + @Override + public LazyOptional getCapability(@Nonnull Capability cap, @Nullable Direction side) { + if (cap == MatteryCapability.STORAGE_CELL) { + return cell.get().cast(); + } + + return super.getCapability(cap, side); + } + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + cell.invalidate(); + } + + @Override + public void reviveCaps() { + super.reviveCaps(); + cell.revive(); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityItemMonitor.java b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityItemMonitor.java new file mode 100644 index 000000000..c1d80bd4a --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityItemMonitor.java @@ -0,0 +1,74 @@ +package ru.dbotthepony.mc.otm.block.entity; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import ru.dbotthepony.mc.otm.Registry; +import ru.dbotthepony.mc.otm.capability.AbstractStorageGridCell; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.capability.MatteryMachineEnergyStorage; +import ru.dbotthepony.mc.otm.menu.ItemMonitorMenu; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.math.BigDecimal; + +public class BlockEntityItemMonitor extends BlockEntityMatteryPowered { + public BlockEntityItemMonitor(BlockPos p_155229_, BlockState p_155230_) { + super(Registry.BlockEntities.ITEM_MONITOR, p_155229_, p_155230_); + energy = new MatteryMachineEnergyStorage(this, MatteryMachineEnergyStorage.MachineType.WORKER, new BigDecimal(80_000)); + } + + public final AbstractStorageGridCell cell = new AbstractStorageGridCell(); + + private static final TranslatableComponent NAME = new TranslatableComponent("block.overdrive_that_matters.item_storage_viewer"); + + @Override + protected Component getDefaultDisplayName() { + return NAME; + } + + @Nullable + @Override + public AbstractContainerMenu createMenu(int containerID, Inventory inventory, Player ply) { + return new ItemMonitorMenu(containerID, inventory, this); + } + + @Override + public void setLevel(Level p_155231_) { + super.setLevel(p_155231_); + + cell.scheduleDiscoverNeighbours(p_155231_, getBlockPos()); + } + + @Nonnull + @Override + public LazyOptional getCapability(@Nonnull Capability cap, @Nullable Direction side) { + if (cap == MatteryCapability.STORAGE_CELL) { + return cell.get().cast(); + } + + return super.getCapability(cap, side); + } + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + cell.invalidate(); + } + + @Override + public void reviveCaps() { + super.reviveCaps(); + cell.revive(); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterReplicator.java b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterReplicator.java index 9e2852d8e..693e375d6 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterReplicator.java +++ b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterReplicator.java @@ -72,7 +72,7 @@ public class BlockEntityMatterReplicator extends BlockEntityMatteryWorker implem @Nonnull @Override protected MachineJobStatus onJobFinish(MachineJob job) { - if (!regular_slots.addItem(job.stack()).isEmpty()) { + if (!regular_slots.fullyAddItem(job.stack())) { return new MachineJobStatus(false, 20); } diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/AbstractStorageGridCell.java b/src/main/java/ru/dbotthepony/mc/otm/capability/AbstractStorageGridCell.java new file mode 100644 index 000000000..e6f5c285d --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/AbstractStorageGridCell.java @@ -0,0 +1,101 @@ +package ru.dbotthepony.mc.otm.capability; + +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraftforge.common.util.LazyOptional; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import ru.dbotthepony.mc.otm.storage.IStorageIdentity; +import ru.dbotthepony.mc.otm.storage.IStorageObject; +import ru.dbotthepony.mc.otm.storage.StorageGrid; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public class AbstractStorageGridCell implements IStorageGridCell { + private LazyOptional resolver = LazyOptional.of(() -> this); + private boolean valid = true; + protected final ArrayList> components = new ArrayList<>(); + + @Override + public List> getComponents() { + return Collections.unmodifiableList(components); + } + + public > U computeIfAbsent(Class identity, Supplier provider) { + for (var component : components) { + if (component.storageIdentity() == identity) { + return (U) component; + } + } + + var factory = provider.get(); + addStorageComponent(factory); + return factory; + } + + public void addStorageComponent(IStorageIdentity component) { + for (var component1 : components) { + if (component == component1 || component1.storageIdentity() == component.storageIdentity()) { + return; + } + } + + detachStorage(); + components.add(component); + attachStorage(); + } + + public void removeStorageComponent(IStorageIdentity component) { + for (var component1 : components) { + if (component == component1 || component1.storageIdentity() == component.storageIdentity()) { + detachStorage(); + components.remove(component1); + attachStorage(); + } + } + } + + public void invalidate() { + if (!valid) + return; + + valid = false; + resolver.invalidate(); + } + + public void revive() { + if (valid) + return; + + valid = true; + resolver = LazyOptional.of(() -> this); + } + + public LazyOptional get() { + return valid ? resolver : LazyOptional.empty(); + } + + protected StorageGrid storage_grid; + + @Nullable + @Override + public StorageGrid getStorageGrid() { + return storage_grid; + } + + @Override + public void setStorageGrid(StorageGrid grid) { + OverdriveThatMatters.LOGGER.info("Set grid {} {}", this, grid); + storage_grid = grid; + } + + @Override + public boolean isValidStorageCell() { + return valid; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/IStorageGridCell.java b/src/main/java/ru/dbotthepony/mc/otm/capability/IStorageGridCell.java new file mode 100644 index 000000000..a1eaa36c8 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/IStorageGridCell.java @@ -0,0 +1,126 @@ +package ru.dbotthepony.mc.otm.capability; + +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import ru.dbotthepony.mc.otm.storage.IStorageIdentity; +import ru.dbotthepony.mc.otm.storage.StorageGrid; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.List; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public interface IStorageGridCell { + /** + * Return all providers that this cell can store. Or nothing, if it is not a storage. + * + * If you don't know what you are going to store, return empty list, then, when you make up your + * mind by either player tweaking settings or something, + * call helper method detachStorage(); + * add whatever going to end up in getComponents(); + * add helper method attachStorage(); + * + * Failing to do it in this order will cause IllegalStateException + * + * @return a list of all providers, inheriting IStorageIdentity, or an empty list + */ + List> getComponents(); + + default List> getComponentsForView() { + return getComponents(); + } + + default List> getComponentsForInterfaces() { + return getComponents(); + } + + default List> getComponentsForExporters() { + return getComponents(); + } + + default void detachStorage() { + var grid = getStorageGrid(); + + if (grid != null) { + grid.detach(this); + } + } + + default void attachStorage() { + var grid = getStorageGrid(); + + if (grid != null) { + grid.attach(this); + } + } + + @Nullable + StorageGrid getStorageGrid(); + void setStorageGrid(@Nullable StorageGrid grid); + boolean isValidStorageCell(); + + default void scheduleDiscoverNeighbours(Level level, BlockPos pos) { + if (level instanceof ServerLevel) + StorageGrid.scheduleDiscoverNeighbours(this, level, pos); + } + + default boolean connectOrCreateStorageGrid(Level level, BlockPos pos) { + return connectOrCreateStorageGrid(level, pos, false); + } + + default void onNeighbourStorageCell(Level level, BlockPos pos, Direction direction, IStorageGridCell cell) { + + } + + default boolean connectOrCreateStorageGrid(Level level, BlockPos pos, boolean force) { + OverdriveThatMatters.LOGGER.info("Discover {}", this); + if (getStorageGrid() != null && !force) + return true; + + boolean full_discovery = true; + + for (Direction direction : Direction.values()) { + BlockPos offset = pos.offset(direction.getNormal()); + + // level.getBlockEntity can drink big cup of deadlocks + LevelChunk get_chunk = level.getChunkSource().getChunkNow(SectionPos.blockToSectionCoord(offset.getX()), SectionPos.blockToSectionCoord(offset.getZ())); + + if (get_chunk == null) { + full_discovery = false; + continue; + } + + BlockEntity get_entity = get_chunk.getBlockEntity(offset); + + if (get_entity != null && get_entity.getCapability(MatteryCapability.STORAGE_CELL, direction.getOpposite()).isPresent()) { + var cell = get_entity.getCapability(MatteryCapability.STORAGE_CELL, direction.getOpposite()).resolve().get(); + var grid = cell.getStorageGrid(); + + if (grid != null && grid != getStorageGrid()) { + if (getStorageGrid() == null) { + grid.add(this); + } else { + grid.merge(getStorageGrid()); + } + } + + if (cell.isValidStorageCell()) + onNeighbourStorageCell(level, pos, direction, cell); + } + } + + if (getStorageGrid() == null) { + new StorageGrid().add(this); + } + + return full_discovery; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java index b90d216fa..0b54e8934 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java @@ -32,6 +32,9 @@ public class MatteryCapability { @CapabilityInject(IMatteryDrive.class) public static Capability DRIVE = null; + @CapabilityInject(IStorageGridCell.class) + public static Capability STORAGE_CELL = null; + public static void register() { CapabilityManager.INSTANCE.register(IAndroidCapability.class); CapabilityManager.INSTANCE.register(IMatteryEnergyStorage.class); @@ -40,6 +43,7 @@ public class MatteryCapability { CapabilityManager.INSTANCE.register(IMatterGridCell.class); CapabilityManager.INSTANCE.register(IMatterTaskProvider.class); CapabilityManager.INSTANCE.register(IMatteryDrive.class); + CapabilityManager.INSTANCE.register(IStorageGridCell.class); } public static final MathContext ROUND_RULES = new MathContext(32, RoundingMode.HALF_DOWN); diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/drive/IMatteryDrive.java b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/IMatteryDrive.java index a221ba496..c793d42af 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/drive/IMatteryDrive.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/IMatteryDrive.java @@ -6,6 +6,7 @@ import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraftforge.common.util.INBTSerializable; import ru.dbotthepony.mc.otm.menu.DriveViewerMenu; +import ru.dbotthepony.mc.otm.storage.*; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -16,7 +17,7 @@ import java.util.function.Predicate; @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault -public interface IMatteryDrive { +public interface IMatteryDrive extends IStorageComponent { record StoredStack(ItemStack stack, UUID id) {}; List getItems(); @@ -39,9 +40,6 @@ public interface IMatteryDrive { ItemStack getItem(UUID id); - void addListener(IItemViewListener listener); - void removeListener(IItemViewListener listener); - int getStoredCount(); int getCapacity(); diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/drive/MatteryDrive.java b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/MatteryDrive.java index cc51667cb..4eb9012a0 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/drive/MatteryDrive.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/drive/MatteryDrive.java @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.capability.drive; +import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; @@ -9,11 +10,19 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraftforge.registries.RegistryManager; import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import ru.dbotthepony.mc.otm.storage.IStorageListener; +import ru.dbotthepony.mc.otm.storage.IStorageTuple; +import ru.dbotthepony.mc.otm.storage.ItemStackObject; +import ru.dbotthepony.mc.otm.storage.StorageTuple; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import java.math.BigDecimal; import java.util.*; +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault public class MatteryDrive implements IMatteryDrive { protected final HashMap> items = new HashMap<>(); protected final HashMap items_by_id = new HashMap<>(); @@ -115,33 +124,6 @@ public class MatteryDrive implements IMatteryDrive { return build_list; } - protected final ArrayList listeners = new ArrayList<>(); - - @Override - public void addListener(IItemViewListener listener) { - if (listeners.contains(listener)) - return; - - listeners.add(listener); - - for (var list : this.items.values()) { - for (var state : list) { - listener.addViewItem(state.stack(), state.id()); - } - } - } - - @Override - public void removeListener(IItemViewListener listener) { - if (listeners.remove(listener)) { - for (var list : this.items.values()) { - for (var state : list) { - listener.removeViewItem(state.id()); - } - } - } - } - @Nonnull @Override public ItemStack getItem(UUID id) { @@ -165,8 +147,8 @@ public class MatteryDrive implements IMatteryDrive { state.stack().grow(max_insert); stored += max_insert; - for (var listener : listeners) { - listener.changeViewItem(state.id(), state.stack().getCount()); + for (var listener : listeners2) { + listener.changeObject(state.id(), new BigDecimal(state.stack().getCount())); } markDirty(); @@ -192,8 +174,8 @@ public class MatteryDrive implements IMatteryDrive { listing.add(state); items_by_id.put(state.id(), state); - for (var listener : listeners) { - listener.addViewItem(state.stack(), state.id()); + for (var listener : listeners2) { + listener.addObject(new ItemStackObject(state.stack()), state.id(), this); } markDirty(); @@ -204,6 +186,12 @@ public class MatteryDrive implements IMatteryDrive { return copy_item; } + @Override + public ItemStackObject insertObject(ItemStackObject obj, boolean simulate) { + var get = insertItem(obj.stack(), simulate); + return get.isEmpty() ? ItemStackObject.EMPTY : new ItemStackObject(get); + } + @Nonnull @Override public ItemStack extractItem(UUID id, int amount, boolean obey_stack_size, boolean simulate) { @@ -229,8 +217,8 @@ public class MatteryDrive implements IMatteryDrive { listing.remove(get); different_stacks--; - for (var listener : listeners) { - listener.removeViewItem(get.id()); + for (var listener : listeners2) { + listener.removeObject(get.id()); } if (listing.size() == 0) { @@ -242,8 +230,8 @@ public class MatteryDrive implements IMatteryDrive { get.stack().shrink(extract); if (get.stack().getCount() != 0) { - for (var listener : listeners) { - listener.changeViewItem(get.id(), get.stack().getCount()); + for (var listener : listeners2) { + listener.changeObject(get.id(), new BigDecimal(get.stack().getCount())); } } @@ -334,4 +322,51 @@ public class MatteryDrive implements IMatteryDrive { } } } + + @Override + public Class storageIdentity() { + return ItemStackObject.class; + } + + @Override + public ItemStackObject getStoredObject(UUID id) { + var get = getItem(id); + return get.isEmpty() ? ItemStackObject.EMPTY : new ItemStackObject(get); + } + + @Override + public ItemStackObject extractObject(UUID id, BigDecimal amount, boolean simulate) { + var get = extractItem(id, amount.intValue(), simulate); + return get.isEmpty() ? ItemStackObject.EMPTY : new ItemStackObject(get); + } + + @Override + public List> getStorageObjects() { + int amount = 0; + + for (var listing : items.values()) + amount += listing.size(); + + var list = new ArrayList>(amount); + + for (var listing : items.values()) { + for (var stack : listing) { + list.add(new StorageTuple<>(stack.id(), new ItemStackObject(stack.stack()))); + } + } + + return list; + } + + protected final HashSet> listeners2 = new HashSet<>(); + + @Override + public boolean addListener(IStorageListener listener) { + return listeners2.add(listener); + } + + @Override + public boolean removeListener(IStorageListener listener) { + return listeners2.remove(listener); + } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/client/screen/DriveRackScreen.java b/src/main/java/ru/dbotthepony/mc/otm/client/screen/DriveRackScreen.java new file mode 100644 index 000000000..c945eed03 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/client/screen/DriveRackScreen.java @@ -0,0 +1,33 @@ +package ru.dbotthepony.mc.otm.client.screen; + +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +import ru.dbotthepony.mc.otm.client.screen.panels.FlexGridPanel; +import ru.dbotthepony.mc.otm.client.screen.panels.SlotPanel; +import ru.dbotthepony.mc.otm.menu.DriveRackMenu; +import ru.dbotthepony.mc.otm.menu.slot.MatterySlot; +import ru.dbotthepony.mc.otm.menu.widget.GaugeWidget; + +import java.util.List; + +public class DriveRackScreen extends MatteryScreen implements MatteryScreen.IMatteryScreenGaugeGetter, MatteryScreen.IMatteryScreenBatteryGetter, MatteryScreen.IMatteryScreenGridBased { + public DriveRackScreen(DriveRackMenu menu, Inventory inventory, Component title) { + super(menu, inventory, title); + } + + @Override + public List getGauges() { + return List.of(menu.battery_widget); + } + + @Override + public List getBatterySlots() { + return List.of(menu.battery_slot); + } + + @Override + public void createGridPanels(FlexGridPanel grid) { + for (var slot : menu.drives) + new SlotPanel<>(this, grid, slot); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/client/screen/DriveViewerScreen.java b/src/main/java/ru/dbotthepony/mc/otm/client/screen/DriveViewerScreen.java index 35594b7ea..3678532c7 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/client/screen/DriveViewerScreen.java +++ b/src/main/java/ru/dbotthepony/mc/otm/client/screen/DriveViewerScreen.java @@ -9,6 +9,7 @@ import net.minecraft.world.inventory.ClickType; import net.minecraft.world.item.ItemStack; import ru.dbotthepony.mc.otm.item.ItemPortableCondensationDrive; import ru.dbotthepony.mc.otm.menu.DriveViewerMenu; +import ru.dbotthepony.mc.otm.menu.data.InteractPacket; import ru.dbotthepony.mc.otm.menu.slot.MatterySlot; import ru.dbotthepony.mc.otm.menu.widget.GaugeWidget; import ru.dbotthepony.mc.otm.network.MatteryNetworking; @@ -116,12 +117,8 @@ public class DriveViewerScreen extends MatteryScreen implements @Override protected boolean mouseClickedInner(double mouse_x, double mouse_y, int mouse_click_type) { int findex = index + scroll_bar.getScroll(GRID_WIDTH); - var list = menu.view.getItems(); - var action = mouse_click_type == InputConstants.MOUSE_BUTTON_LEFT ? ClickAction.PRIMARY : ClickAction.SECONDARY; + menu.view.mouseClick(findex, mouse_click_type); - var type = mouse_click_type == InputConstants.MOUSE_BUTTON_MIDDLE ? ClickType.CLONE : hasShiftDown() ? ClickType.QUICK_MOVE : ClickType.PICKUP; - - MatteryNetworking.send(new DriveViewerMenu.InteractPacket(menu.containerId, findex >= list.size() ? -1 : list.get(findex).id(), type, action)); return true; } }; @@ -163,7 +160,7 @@ public class DriveViewerScreen extends MatteryScreen implements @Override protected boolean mouseReleasedInner(double mouse_x, double mouse_y, int flag) { if (clicking) - MatteryNetworking.send(new DriveViewerMenu.FilterSetPacket(menu.containerId, index, menu.getCarried())); + MatteryNetworking.send(null, new DriveViewerMenu.FilterSetPacket(menu.containerId, index, menu.getCarried())); clicking = false; @@ -197,7 +194,7 @@ public class DriveViewerScreen extends MatteryScreen implements var filter = menu.getFilter(); if (filter != null) { - MatteryNetworking.send(new DriveViewerMenu.FilterSwitchPacket(menu.containerId, DriveViewerMenu.FilterSwitch.MATCH_NBT, !filter.match_nbt)); + MatteryNetworking.send(null, new DriveViewerMenu.FilterSwitchPacket(menu.containerId, DriveViewerMenu.FilterSwitch.MATCH_NBT, !filter.match_nbt)); disableFor(20); } } @@ -225,7 +222,7 @@ public class DriveViewerScreen extends MatteryScreen implements var filter = menu.getFilter(); if (filter != null) { - MatteryNetworking.send(new DriveViewerMenu.FilterSwitchPacket(menu.containerId, DriveViewerMenu.FilterSwitch.MATCH_TAG, !filter.match_tag)); + MatteryNetworking.send(null, new DriveViewerMenu.FilterSwitchPacket(menu.containerId, DriveViewerMenu.FilterSwitch.MATCH_TAG, !filter.match_tag)); disableFor(20); } } @@ -253,7 +250,7 @@ public class DriveViewerScreen extends MatteryScreen implements var filter = menu.getFilter(); if (filter != null) { - MatteryNetworking.send(new DriveViewerMenu.FilterSwitchPacket(menu.containerId, DriveViewerMenu.FilterSwitch.BLACKLIST, !filter.blacklist)); + MatteryNetworking.send(null, new DriveViewerMenu.FilterSwitchPacket(menu.containerId, DriveViewerMenu.FilterSwitch.BLACKLIST, !filter.blacklist)); disableFor(20); } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/client/screen/ItemMonitorScreen.java b/src/main/java/ru/dbotthepony/mc/otm/client/screen/ItemMonitorScreen.java new file mode 100644 index 000000000..4e3f4fa5d --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/client/screen/ItemMonitorScreen.java @@ -0,0 +1,97 @@ +package ru.dbotthepony.mc.otm.client.screen; + +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.ClickAction; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.item.ItemStack; +import ru.dbotthepony.mc.otm.client.screen.panels.*; +import ru.dbotthepony.mc.otm.item.ItemPortableCondensationDrive; +import ru.dbotthepony.mc.otm.menu.DriveViewerMenu; +import ru.dbotthepony.mc.otm.menu.ItemMonitorMenu; +import ru.dbotthepony.mc.otm.menu.data.InteractPacket; +import ru.dbotthepony.mc.otm.menu.slot.MatterySlot; +import ru.dbotthepony.mc.otm.menu.widget.GaugeWidget; +import ru.dbotthepony.mc.otm.network.MatteryNetworking; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class ItemMonitorScreen extends MatteryScreen implements MatteryScreen.IMatteryScreenGaugeGetter, MatteryScreen.IMatteryScreenBatteryGetter { + public static final int FRAME_WIDTH = 210; + public static final int FRAME_HEIGHT = 110; + + public static final int GRID_WIDTH = 9; + public static final int GRID_HEIGHT = 5; + + public ItemMonitorScreen(ItemMonitorMenu menu, Inventory inventory, Component title) { + super(menu, inventory, title); + } + + @Override + public List getGauges() { + return List.of(menu.battery_widget); + } + + @Override + public List getBatterySlots() { + return List.of(menu.battery_slot); + } + + @Nullable + @Override + protected FramePanel makeMainFrame() { + var frame = new FramePanel(this, null, 0, 0, FRAME_WIDTH, FRAME_HEIGHT, getTitle()); + + autoAttachToFrame(frame); + + var grid = new GridPanel(this, frame, 0, 0, 0, 0, GRID_WIDTH, GRID_HEIGHT); + grid.setDock(Dock.FILL); + grid.setDockMargin(2, 2, 2, 2); + + var scroll_bar = new ScrollBarPanel(this, frame, 0, 0, 0); + scroll_bar.setDock(Dock.RIGHT); + scroll_bar.setupRowMultiplier(() -> { + return menu.view.getItems().size() / GRID_WIDTH; + }); + + for (int i = 0; i < GRID_WIDTH * GRID_HEIGHT; i++) { + final int index = i; + + new AbstractSlotPanel(this, grid, 0, 0) { + @Nonnull + @Override + protected ItemStack getItemStack() { + int findex = index + scroll_bar.getScroll(menu.view.getItems().size() / GRID_WIDTH); + + var list = menu.view.getItems(); + + if (findex >= list.size()) { + return ItemStack.EMPTY; + } + + return list.get(findex).stack(); + } + + @Override + protected boolean mouseScrolledInner(double mouse_x, double mouse_y, double scroll) { + return scroll_bar.mouseScrolledInner(mouse_x, mouse_y, scroll); + } + + @Override + protected boolean mouseClickedInner(double mouse_x, double mouse_y, int mouse_click_type) { + int findex = index + scroll_bar.getScroll(GRID_WIDTH); + menu.view.mouseClick(findex, mouse_click_type); + + return true; + } + }; + } + + return frame; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainer.java b/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainer.java index e54cf7382..184122ee0 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainer.java +++ b/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainer.java @@ -1,20 +1,40 @@ package ru.dbotthepony.mc.otm.container; +import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; -import net.minecraft.world.SimpleContainer; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.items.IItemHandler; -import javax.annotation.Nonnull; import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Arrays; +import java.util.Iterator; import java.util.function.Consumer; -public class MatteryContainer extends SimpleContainer { +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public class MatteryContainer implements Container, Iterable { protected final Runnable watcher; protected int ignore_change_notifications = 0; + protected final int size; + protected final ItemStack[] slots; + protected final ItemStack[] tracked_slots; + + public MatteryContainer(Runnable watcher, int size) { + this.size = size; + + slots = new ItemStack[this.size]; + tracked_slots = new ItemStack[this.size]; + Arrays.fill(slots, ItemStack.EMPTY); + Arrays.fill(tracked_slots, ItemStack.EMPTY); + + this.watcher = watcher; + } public void startIgnore() { ignore_change_notifications++; @@ -24,11 +44,6 @@ public class MatteryContainer extends SimpleContainer { ignore_change_notifications--; } - public MatteryContainer(Runnable watcher, int i) { - super(i); - this.watcher = watcher; - } - public MatteryContainer of(@Nullable CompoundTag tag) { if (tag == null) return this; @@ -55,10 +70,13 @@ public class MatteryContainer extends SimpleContainer { // нам не интересен размер - if (tag.get("items") instanceof ListTag list) - for (int i = 0; i < Math.min(list.size(), getContainerSize()); i++) - if (list.get(i) instanceof CompoundTag get_tag) + if (tag.get("items") instanceof ListTag list) { + for (int i = 0; i < Math.min(list.size(), getContainerSize()); i++) { + if (list.get(i) instanceof CompoundTag get_tag) { setItem(i, ItemStack.of(get_tag)); + } + } + } ignore_change_notifications--; setChanged(); @@ -83,11 +101,6 @@ public class MatteryContainer extends SimpleContainer { } } - public MatteryContainer(Runnable watcher, ItemStack... p_19152_) { - super(p_19152_); - this.watcher = watcher; - } - public ContainerIteratorItemStack stackIterator() { return new ContainerIteratorItemStack(this); } @@ -104,17 +117,9 @@ public class MatteryContainer extends SimpleContainer { capabilityIterator(cap).consume(consumer); } - @Override - public void setChanged() { - if (ignore_change_notifications > 0) - return; - - super.setChanged(); - watcher.run(); - } - public void setChanged(int slot, ItemStack new_state, ItemStack old_state) { - setChanged(); + if (ignore_change_notifications == 0) + watcher.run(); } public CompoundTag serializeNBT() { @@ -157,16 +162,6 @@ public class MatteryContainer extends SimpleContainer { return false; } - @FunctionalInterface - public interface MatteryContainerInsertValidator { - boolean apply(int slot, @Nonnull ItemStack stack); - } - - @FunctionalInterface - public interface MatteryContainerExtractValidator { - boolean apply(int slot, int amount, @Nonnull ItemStack stack); - } - protected MatteryContainerHandler handler; public IItemHandler handler(MatteryContainerInsertValidator insert_validator, MatteryContainerExtractValidator extract_validator) { @@ -194,114 +189,165 @@ public class MatteryContainer extends SimpleContainer { return getMaxStackSize(); } - static class MatteryContainerHandler implements IItemHandler { - private final MatteryContainer container; - private final MatteryContainerInsertValidator insert_validator; - private final MatteryContainerExtractValidator extract_validator; + public ItemStack addItem(ItemStack stack, int start, int end, boolean simulate) { + if (stack.isEmpty() || start < 0 || end > size || start >= end) + return ItemStack.EMPTY; - MatteryContainerHandler(MatteryContainer container, MatteryContainerInsertValidator insert_validator, MatteryContainerExtractValidator extract_validator) { - this.container = container; - this.insert_validator = insert_validator; - this.extract_validator = extract_validator; - } + var copy = stack.copy(); - MatteryContainerHandler(MatteryContainer container) { - this(container, (slot, stack) -> true, (slot, amount, stack) -> true); - } + // двигаем в одинаковые слоты + for (int slot = start; slot < end; slot++) { + if (ItemStack.isSameItemSameTags(slots[slot], copy)) { + var this_stack = slots[slot]; + int slot_limit = Math.min(getMaxStackSize(slot), this_stack.getMaxStackSize()); - MatteryContainerHandler(MatteryContainer container, MatteryContainerInsertValidator insert_validator) { - this(container, insert_validator, (slot, amount, stack) -> true); - } + if (this_stack.getCount() < slot_limit) { + int new_count = Math.min(this_stack.getCount() + copy.getCount(), slot_limit); + int diff = new_count - this_stack.getCount(); - @Override - public int getSlots() { - return container.getContainerSize(); - } - - @Nonnull - @Override - public ItemStack getStackInSlot(int slot) { - return container.getItem(slot); - } - - @Nonnull - @Override - public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) { - if (!insert_validator.apply(slot, stack)) - return stack; - - ItemStack self_stack = container.getItem(slot); - - if (self_stack.isEmpty()) { - if (!simulate) { - var copy = stack.copy(); - container.setChanged(slot, copy, ItemStack.EMPTY); - container.setItem(slot, copy); - } - - return ItemStack.EMPTY; - } else if (self_stack.isStackable() && self_stack.getMaxStackSize() > self_stack.getCount() && ItemStack.isSameItemSameTags(self_stack, stack)) { - int new_count = Math.min(self_stack.getMaxStackSize(), self_stack.getCount() + stack.getCount()); - int diff = new_count - self_stack.getCount(); - - if (diff != 0) { if (!simulate) { - ItemStack copy_self = self_stack.copy(); - self_stack.grow(diff); - container.setChanged(slot, self_stack, copy_self); + var old = this_stack.copy(); + this_stack.setCount(new_count); + tracked_slots[slot] = this_stack.copy(); + setChanged(slot, this_stack, old); } - ItemStack copy = stack.copy(); copy.shrink(diff); + + if (copy.isEmpty()) { + return copy; + } + } + } + } + + // двигаем в пустые слоты + for (int slot = start; slot < end; slot++) { + if (slots[slot].isEmpty()) { + int diff = Math.min(copy.getCount(), Math.min(getMaxStackSize(slot), copy.getMaxStackSize())); + + if (!simulate) { + var put_copy = copy.copy(); + put_copy.setCount(diff); + tracked_slots[slot] = put_copy.copy(); + setChanged(slot, put_copy, ItemStack.EMPTY); + } + + copy.shrink(diff); + + if (copy.isEmpty()) { return copy; } } - - return stack; } - @Nonnull - @Override - public ItemStack extractItem(int slot, int amount, boolean simulate) { - if (amount == 0) - return ItemStack.EMPTY; + return copy; + } - if (amount < 0) - throw new IllegalArgumentException("Can not extract negative amount of items"); + public ItemStack addItem(ItemStack stack, boolean simulate) { + return addItem(stack, 0, size, simulate); + } - ItemStack self_stack = container.getItem(slot); + public ItemStack addItem(ItemStack stack) { + return addItem(stack, 0, size, false); + } - if (self_stack.isEmpty()) - return ItemStack.EMPTY; + public boolean fullyAddItem(ItemStack stack) { + if (!addItem(stack, 0, size, true).isEmpty()) { + return false; + } - if (!extract_validator.apply(slot, amount, self_stack)) - return ItemStack.EMPTY; + return addItem(stack, 0, size, false).isEmpty(); + } - int minimal = Math.min(amount, self_stack.getCount()); - ItemStack copy = self_stack.copy(); - copy.setCount(minimal); + public boolean fullyAddItem(ItemStack stack, int start, int end) { + if (!addItem(stack, start, end, true).isEmpty()) { + return false; + } - if (!simulate) { - ItemStack copy_self = self_stack.copy(); - self_stack.shrink(minimal); - container.setChanged(slot, self_stack, copy_self); + return addItem(stack, start, end, false).isEmpty(); + } + + @Override + public int getContainerSize() { + return size; + } + + @Override + public boolean isEmpty() { + for (var stack : slots) + if (!stack.isEmpty()) + return false; + + return true; + } + + @Override + public ItemStack getItem(int slot) { + return slots[slot]; + } + + @Override + public ItemStack removeItem(int slot, int amount) { + if (amount <= 0 || slot < 0 || slot >= size || slots[slot].isEmpty()) + return ItemStack.EMPTY; + + var old = slots[slot].copy(); + var split = slots[slot].split(amount); + tracked_slots[slot] = slots[slot].copy(); + setChanged(slot, slots[slot].isEmpty() ? ItemStack.EMPTY : slots[slot], old); + + return split; + } + + @Override + public ItemStack removeItemNoUpdate(int slot) { + var old = slots[slot]; + slots[slot] = ItemStack.EMPTY; + tracked_slots[slot] = ItemStack.EMPTY; + return old; + } + + @Override + public void setItem(int slot, ItemStack stack) { + if (slots[slot].isEmpty() && stack.isEmpty() || stack == slots[slot]) + return; + + var old = slots[slot]; + slots[slot] = stack; + tracked_slots[slot] = stack.copy(); + setChanged(slot, stack, old); + } + + @Override + public void setChanged() { + for (int slot = 0; slot < size; slot++) { + if (!ItemStack.isSameItemSameTags(slots[slot], tracked_slots[slot])) { + setChanged(slot, slots[slot], tracked_slots[slot]); + tracked_slots[slot] = slots[slot].copy(); + // mojang соси))0)0))0)))))0) } - - return copy; - } - - @Override - public int getSlotLimit(int slot) { - return container.getMaxStackSize(); - } - - @Override - public boolean isItemValid(int slot, @Nonnull ItemStack stack) { - return insert_validator.apply(slot, stack); - } - - MatteryContainerHandler cloneOf(MatteryContainer container) { - return new MatteryContainerHandler(container, insert_validator, extract_validator); } } + + @Override + public boolean stillValid(Player player) { + return true; + } + + @Override + public void clearContent() { + for (int slot = 0; slot < size; slot++) { + if (!slots[slot].isEmpty()) { + setChanged(slot, ItemStack.EMPTY, slots[slot]); + } + } + + Arrays.fill(tracked_slots, ItemStack.EMPTY); + } + + @Override + public Iterator iterator() { + return Arrays.stream(slots).iterator(); + } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainerExtractValidator.java b/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainerExtractValidator.java new file mode 100644 index 000000000..38e27110c --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainerExtractValidator.java @@ -0,0 +1,10 @@ +package ru.dbotthepony.mc.otm.container; + +import net.minecraft.world.item.ItemStack; + +import javax.annotation.Nonnull; + +@FunctionalInterface +public interface MatteryContainerExtractValidator { + boolean apply(int slot, int amount, @Nonnull ItemStack stack); +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainerHandler.java b/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainerHandler.java new file mode 100644 index 000000000..2a70337b4 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainerHandler.java @@ -0,0 +1,117 @@ +package ru.dbotthepony.mc.otm.container; + +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; + +class MatteryContainerHandler implements IItemHandler { + private final MatteryContainer container; + private final MatteryContainerInsertValidator insert_validator; + private final MatteryContainerExtractValidator extract_validator; + + MatteryContainerHandler(MatteryContainer container, MatteryContainerInsertValidator insert_validator, MatteryContainerExtractValidator extract_validator) { + this.container = container; + this.insert_validator = insert_validator; + this.extract_validator = extract_validator; + } + + MatteryContainerHandler(MatteryContainer container) { + this(container, (slot, stack) -> true, (slot, amount, stack) -> true); + } + + MatteryContainerHandler(MatteryContainer container, MatteryContainerInsertValidator insert_validator) { + this(container, insert_validator, (slot, amount, stack) -> true); + } + + @Override + public int getSlots() { + return container.getContainerSize(); + } + + @Nonnull + @Override + public ItemStack getStackInSlot(int slot) { + return container.getItem(slot); + } + + @Nonnull + @Override + public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) { + if (!insert_validator.apply(slot, stack)) + return stack; + + ItemStack self_stack = container.getItem(slot); + + if (self_stack.isEmpty()) { + if (!simulate) { + var copy = stack.copy(); + container.setChanged(slot, copy, ItemStack.EMPTY); + container.setItem(slot, copy); + } + + return ItemStack.EMPTY; + } else if (self_stack.isStackable() && self_stack.getMaxStackSize() > self_stack.getCount() && ItemStack.isSameItemSameTags(self_stack, stack)) { + int new_count = Math.min(self_stack.getMaxStackSize(), self_stack.getCount() + stack.getCount()); + int diff = new_count - self_stack.getCount(); + + if (diff != 0) { + if (!simulate) { + ItemStack copy_self = self_stack.copy(); + self_stack.grow(diff); + container.setChanged(slot, self_stack, copy_self); + } + + ItemStack copy = stack.copy(); + copy.shrink(diff); + return copy; + } + } + + return stack; + } + + @Nonnull + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + if (amount == 0) + return ItemStack.EMPTY; + + if (amount < 0) + throw new IllegalArgumentException("Can not extract negative amount of items"); + + ItemStack self_stack = container.getItem(slot); + + if (self_stack.isEmpty()) + return ItemStack.EMPTY; + + if (!extract_validator.apply(slot, amount, self_stack)) + return ItemStack.EMPTY; + + int minimal = Math.min(amount, self_stack.getCount()); + ItemStack copy = self_stack.copy(); + copy.setCount(minimal); + + if (!simulate) { + ItemStack copy_self = self_stack.copy(); + self_stack.shrink(minimal); + container.setChanged(slot, self_stack, copy_self); + } + + return copy; + } + + @Override + public int getSlotLimit(int slot) { + return container.getMaxStackSize(); + } + + @Override + public boolean isItemValid(int slot, @Nonnull ItemStack stack) { + return insert_validator.apply(slot, stack); + } + + MatteryContainerHandler cloneOf(MatteryContainer container) { + return new MatteryContainerHandler(container, insert_validator, extract_validator); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainerInsertValidator.java b/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainerInsertValidator.java new file mode 100644 index 000000000..450409916 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/container/MatteryContainerInsertValidator.java @@ -0,0 +1,10 @@ +package ru.dbotthepony.mc.otm.container; + +import net.minecraft.world.item.ItemStack; + +import javax.annotation.Nonnull; + +@FunctionalInterface +public interface MatteryContainerInsertValidator { + boolean apply(int slot, @Nonnull ItemStack stack); +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/matter/MatterGrid.java b/src/main/java/ru/dbotthepony/mc/otm/matter/MatterGrid.java index 33e3219b9..7f8dbd9db 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/matter/MatterGrid.java +++ b/src/main/java/ru/dbotthepony/mc/otm/matter/MatterGrid.java @@ -4,18 +4,13 @@ import com.google.common.collect.ImmutableList; import net.minecraft.core.BlockPos; import net.minecraft.world.item.Item; import net.minecraft.world.level.Level; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fmlserverevents.FMLServerStartedEvent; -import net.minecraftforge.fmlserverevents.FMLServerStoppingEvent; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; import ru.dbotthepony.mc.otm.capability.*; import javax.annotation.Nullable; import java.math.BigDecimal; import java.util.*; -import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.function.Supplier; public class MatterGrid implements IMatterGridListener { public static final Set NETWORKS = new HashSet<>(); @@ -23,48 +18,8 @@ public class MatterGrid implements IMatterGridListener { private final Set cells = new HashSet<>(); private final Set listeners = new HashSet<>(); - private static final WeakHashMap>> discovering_neighbours = new WeakHashMap<>(); - public static void scheduleDiscoverNeighbours(IMatterGridCell cell, BlockPos pos, Level level) { - discovering_neighbours.computeIfAbsent(level, (key) -> new HashSet<>()).add(() -> !cell.isValidMatterCell() || cell.connectOrCreateMatterGrid(pos, level, true)); - } - - @SubscribeEvent - public void onServerStarting(FMLServerStartedEvent event) { - discovering_neighbours.clear(); - } - - @SubscribeEvent - public void onServerStopping(FMLServerStoppingEvent event) { - discovering_neighbours.clear(); - } - - @SubscribeEvent - public static void discoverNeighbours(TickEvent.WorldTickEvent event) { - if (event.phase != TickEvent.Phase.START) - return; - - var set = discovering_neighbours.get(event.world); - - if (set != null) { - ArrayList> invalid = new ArrayList<>(); - - for (Supplier cell : set) { - if (cell.get()) { - invalid.add(cell); - } - } - - if (invalid.size() != 0) { - for (Supplier cell : invalid) { - set.remove(cell); - } - } - - if (set.size() == 0) { - discovering_neighbours.remove(event.world); - } - } + OverdriveThatMatters.tickUntil(level, () -> !cell.isValidMatterCell() || cell.connectOrCreateMatterGrid(pos, level, true)); } public MatterGrid() { diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/DriveRackMenu.java b/src/main/java/ru/dbotthepony/mc/otm/menu/DriveRackMenu.java new file mode 100644 index 000000000..d5a919b5e --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/DriveRackMenu.java @@ -0,0 +1,51 @@ +package ru.dbotthepony.mc.otm.menu; + +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.entity.BlockEntity; +import ru.dbotthepony.mc.otm.Registry; +import ru.dbotthepony.mc.otm.block.entity.BlockEntityDriveRack; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.menu.slot.MatterySlot; + +import javax.annotation.Nullable; + +public class DriveRackMenu extends PoweredMatteryMenu { + public DriveRackMenu(int p_38852_, Inventory inventory) { + this(p_38852_, inventory, null); + } + + public final MatterySlot[] drives = new MatterySlot[4]; + + public DriveRackMenu(int p_38852_, Inventory inventory, BlockEntityDriveRack tile) { + super(Registry.Menus.DRIVE_RACK, p_38852_, inventory, tile); + + var container = tile != null ? tile.drives : new SimpleContainer(4); + + for (int i = 0; i < container.getContainerSize(); i++) { + drives[i] = new MatterySlot(container, i) { + @Override + public boolean mayPlace(ItemStack p_40231_) { + return p_40231_.getCapability(MatteryCapability.DRIVE).isPresent(); + } + }; + + addSlot(drives[i]); + } + + addBatterySlot(); + addInventorySlots(); + } + + @Override + protected int getWorkingSlotStart() { + return 0; + } + + @Override + protected int getWorkingSlotEnd() { + return 5; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/DriveViewerMenu.java b/src/main/java/ru/dbotthepony/mc/otm/menu/DriveViewerMenu.java index 291e80ee5..dacd5bd3c 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/DriveViewerMenu.java +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/DriveViewerMenu.java @@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.menu; import net.minecraft.client.Minecraft; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.SimpleContainer; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; @@ -12,28 +11,30 @@ import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraftforge.energy.CapabilityEnergy; import net.minecraftforge.fmllegacy.network.NetworkEvent; -import net.minecraftforge.fmllegacy.network.PacketDistributor; import ru.dbotthepony.mc.otm.Registry; import ru.dbotthepony.mc.otm.block.entity.BlockEntityDriveViewer; import ru.dbotthepony.mc.otm.capability.MatteryCapability; -import ru.dbotthepony.mc.otm.capability.drive.IItemViewListener; import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive; import ru.dbotthepony.mc.otm.item.ItemPortableCondensationDrive; +import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewSupplier; +import ru.dbotthepony.mc.otm.menu.data.InteractPacket; +import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView; import ru.dbotthepony.mc.otm.menu.slot.MatterySlot; import ru.dbotthepony.mc.otm.network.MatteryNetworking; import ru.dbotthepony.mc.otm.network.SetCarriedPacket; import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.UUID; import java.util.function.Supplier; -public class DriveViewerMenu extends PoweredMatteryMenu { - public NetworkedItemView view; +public class DriveViewerMenu extends PoweredMatteryMenu implements INetworkedItemViewSupplier { + public final NetworkedItemView view; public MatterySlot drive_slot; - protected IMatteryDrive last_drive; + public IMatteryDrive last_drive; + + @Override + public NetworkedItemView getNetworkedItemView() { + return view; + } public DriveViewerMenu(int containerID, Inventory inventory) { this(containerID, inventory, null); @@ -51,9 +52,27 @@ public class DriveViewerMenu extends PoweredMatteryMenu { } }; - addSlot(drive_slot); - view = new NetworkedItemView(); + view = new NetworkedItemView(inventory.player, this, tile == null) { + @Override + public int calculateIOAmount(int desired_amount) { + if (tile != null) + return tile.getIOItemCount(desired_amount, true); + return super.calculateIOAmount(desired_amount); + } + + @Override + public void doneIOAmount(int done_amount) { + if (tile != null) { + tile.getIOItemCount(done_amount, false); + return; + } + + super.doneIOAmount(done_amount); + } + }; + + addSlot(drive_slot); addBatterySlot(); addInventorySlots(); } @@ -66,35 +85,18 @@ public class DriveViewerMenu extends PoweredMatteryMenu { var get = ((BlockEntityDriveViewer) tile).drive_slot.getItem(0); if (get.isEmpty()) { - if (last_drive != null) { - last_drive.removeListener(view); - view.clear(); - } - last_drive = null; } else { var get_cap = get.getCapability(MatteryCapability.DRIVE).resolve(); if (get_cap.isEmpty()) { - if (last_drive != null) { - last_drive.removeListener(view); - view.clear(); - } - last_drive = null; } else { - if (last_drive != get_cap.get()) { - if (last_drive != null) - last_drive.removeListener(view); - - view.clear(); - get_cap.get().addListener(view); - } - last_drive = get_cap.get(); } } + view.setComponent(last_drive); view.network(); } } @@ -119,109 +121,6 @@ public class DriveViewerMenu extends PoweredMatteryMenu { } } - /** - * Creates a virtual, slotless container for Player's interaction with. - */ - public class NetworkedItemView implements IItemViewListener { - public record NetworkedItem(int id, ItemStack stack, UUID id_upstream) { - public NetworkedItem(int id, ItemStack stack) { - this(id, stack, null); - } - } - - protected int next_stack_id = 0; - - protected final HashMap state = new HashMap<>(); - protected final HashMap upstream_state = new HashMap<>(); - protected final ArrayList backlog = new ArrayList<>(); - - private List view_cache; - - public void clearCache() { - view_cache = null; - } - - public List getItems() { - if (view_cache != null) - return view_cache; - - return view_cache = List.copyOf(state.values()); - } - - @Override - public void addViewItem(ItemStack stack, UUID id_upstream) { - if (upstream_state.containsKey(id_upstream)) - throw new IllegalStateException("Already tracking ItemStack with upstream id " + id_upstream + "!"); - - var state = new NetworkedItem(next_stack_id++, stack.copy(), id_upstream); - this.state.put(state.id, state); - this.upstream_state.put(id_upstream, state); - - if (tile != null) { - backlog.add(new StackAddPacket(containerId, state.id, stack)); - } - - clearCache(); - } - - @Override - public void changeViewItem(UUID id_upstream, int new_count) { - var get = upstream_state.get(id_upstream); - - if (get == null) - throw new IllegalStateException("Unknown ItemStack with upstream id " + id_upstream + "!"); - - get.stack.setCount(new_count); - - if (tile != null) { - backlog.add(new StackChangePacket(containerId, get.id, new_count)); - } - - clearCache(); - } - - @Override - public void removeViewItem(UUID id_upstream) { - var get = upstream_state.get(id_upstream); - - if (get == null) - throw new IllegalStateException("Unknown ItemStack with upstream id " + id_upstream + "!"); - - upstream_state.remove(id_upstream); - state.remove(get.id); - - if (tile != null) { - backlog.add(new StackRemovePacket(containerId, get.id)); - } - - clearCache(); - } - - public void clear() { - clearCache(); - upstream_state.clear(); - state.clear(); - - if (tile != null) { - backlog.clear(); - backlog.add(new ClearPacket(containerId)); - } - } - - public void network() { - if (tile == null) - throw new IllegalStateException("Not a server"); - - var consumer = PacketDistributor.PLAYER.with(() -> (ServerPlayer) ply); - - for (var packet : backlog) { - MatteryNetworking.CHANNEL.send(consumer, packet); - } - - backlog.clear(); - } - } - @Override public ItemStack quickMoveStack(Player ply, int slot_index) { var slot = slots.get(slot_index); @@ -276,283 +175,6 @@ public class DriveViewerMenu extends PoweredMatteryMenu { return copy; } - public record InteractPacket(int id, int stack_id, ClickType click, ClickAction action) { - public void write(FriendlyByteBuf buffer) { - buffer.writeInt(id); - buffer.writeInt(stack_id); - buffer.writeEnum(click); - buffer.writeEnum(action); - } - - public static InteractPacket read(FriendlyByteBuf buffer) { - var id = buffer.readInt(); - var stack = buffer.readInt(); - var click = buffer.readEnum(ClickType.class); - var action = buffer.readEnum(ClickAction.class); - return new InteractPacket(id, stack, click, action); - } - - public void play(Supplier context) { - context.get().setPacketHandled(true); - context.get().enqueueWork(() -> { - var ply = context.get().getSender(); - - if (ply.containerMenu instanceof DriveViewerMenu menu) { - var view = menu.containerId == id ? menu.view : null; - - if (view == null) { - return; - } - - ply.resetLastActionTime(); - - if (menu.last_drive == null) - return; - - var tile = (BlockEntityDriveViewer) menu.tile; - - if (click == ClickType.QUICK_MOVE && stack_id > -1) { - if (!tile.canIOItems()) - return; - - var get_state = view.state.get(stack_id); - - if (get_state == null) { - return; - } - - int amount = tile.getIOItemCount(action == ClickAction.PRIMARY ? get_state.stack.getMaxStackSize() : Math.max(1, get_state.stack.getMaxStackSize() / 2), true); - var extracted = menu.last_drive.extractItem(get_state.id_upstream, amount, true); - - if (!extracted.isEmpty()) { - var move = menu.quickMoveToInventory(extracted, false); - - if (move.remaining().getCount() != extracted.getCount()) { - menu.last_drive.extractItem(get_state.id_upstream, extracted.getCount() - move.remaining().getCount(), false); - tile.getIOItemCount(extracted.getCount() - move.remaining().getCount(), false); - } - } - - return; - } - - if (click == ClickType.CLONE) { - if (stack_id < 0 || !ply.getAbilities().instabuild) - return; - - var get_state = view.state.get(stack_id); - - if (get_state == null) { - return; - } - - var copy = get_state.stack.copy(); - copy.setCount(Math.min(copy.getCount(), copy.getMaxStackSize())); - - menu.setCarried(copy); - MatteryNetworking.send(ply, new SetCarriedPacket(menu.getCarried())); - menu.setRemoteCarried(menu.getCarried().copy()); - return; - } - - if (!tile.canIOItems()) - return; - - if (!menu.getCarried().isEmpty() && click != ClickType.QUICK_MOVE) { - // try to put - if (action == ClickAction.PRIMARY) { - var carried = menu.getCarried(); - int amount = tile.getIOItemCount(carried.getCount(), true); - - if (amount == carried.getCount()) { - var leftover = menu.last_drive.insertItem(menu.getCarried(), false); - menu.setCarried(leftover); - tile.getIOItemCount(amount - leftover.getCount(), false); - - MatteryNetworking.send(ply, new SetCarriedPacket(menu.getCarried())); - menu.setRemoteCarried(menu.getCarried().copy()); - } else if (amount != 0) { - var copy = carried.copy(); - copy.setCount(amount); - - var leftover = menu.last_drive.insertItem(copy, false); - tile.getIOItemCount(amount - leftover.getCount(), false); - - leftover.setCount(carried.getCount() - amount + leftover.getCount()); - menu.setCarried(leftover); - - MatteryNetworking.send(ply, new SetCarriedPacket(menu.getCarried())); - menu.setRemoteCarried(menu.getCarried().copy()); - } - } else { - var copy = menu.getCarried().copy(); - copy.setCount(1); - - if (tile.getIOItemCount(1, true) == 1 && menu.last_drive.insertItem(copy, false).isEmpty()) { - menu.getCarried().shrink(1); - tile.getIOItemCount(1, false); - MatteryNetworking.send(ply, new SetCarriedPacket(menu.getCarried())); - menu.setRemoteCarried(menu.getCarried().copy()); - } - } - } else if (stack_id > -1) { - var get_state = view.state.get(stack_id); - - if (get_state == null) { - return; - } - - int amount = tile.getIOItemCount(action == ClickAction.PRIMARY ? get_state.stack.getMaxStackSize() : Math.max(1, get_state.stack.getMaxStackSize() / 2), true); - - var extracted = menu.last_drive.extractItem(get_state.id_upstream, amount, false); - - tile.getIOItemCount(extracted.getCount(), false); - menu.setCarried(extracted); - MatteryNetworking.send(ply, new SetCarriedPacket(menu.getCarried())); - menu.setRemoteCarried(menu.getCarried().copy()); - } - } - }); - } - } - - public record ClearPacket(int id) { - public void write(FriendlyByteBuf buffer) { - buffer.writeInt(id); - } - - public static ClearPacket read(FriendlyByteBuf buffer) { - return new ClearPacket(buffer.readInt()); - } - - public void play(Supplier context) { - context.get().setPacketHandled(true); - context.get().enqueueWork(() -> { - var get = Minecraft.getInstance().player.containerMenu instanceof DriveViewerMenu menu && menu.containerId == id ? menu.view : null; - - if (get == null) { - throw new IllegalStateException("No such item tracker with id " + id); - } - - get.clear(); - }); - } - } - - public record StackAddPacket(int id, int stack_id, ItemStack stack) { - public void write(FriendlyByteBuf buffer) { - buffer.writeInt(id); - buffer.writeInt(stack_id); - buffer.writeRegistryId(stack.getItem()); - buffer.writeInt(stack.getCount()); - buffer.writeBoolean(stack.getTag() != null); - - if (stack.getTag() != null) - buffer.writeNbt(stack.getShareTag()); - } - - public static StackAddPacket read(FriendlyByteBuf buffer) { - var id = buffer.readInt(); - var stack = buffer.readInt(); - var item = buffer.readRegistryIdSafe(Item.class); - var count = buffer.readInt(); - - var state = new ItemStack(item, count); - - if (buffer.readBoolean()) - state.readShareTag(buffer.readNbt()); - - return new StackAddPacket(id, stack, state); - } - - public void play(Supplier context) { - context.get().setPacketHandled(true); - context.get().enqueueWork(() -> { - var get = Minecraft.getInstance().player.containerMenu instanceof DriveViewerMenu menu && menu.containerId == id ? menu.view : null; - - if (get == null) { - throw new IllegalStateException("No such item tracker with id " + id); - } - - if (get.state.containsKey(stack_id)) { - throw new IllegalStateException("Item tracker " + id + " already has stack with id of " + stack_id); - } - - get.state.put(stack_id, new NetworkedItemView.NetworkedItem(stack_id, stack)); - get.clearCache(); - }); - } - } - - public record StackChangePacket(int id, int stack_id, int new_count) { - public void write(FriendlyByteBuf buffer) { - buffer.writeInt(id); - buffer.writeInt(stack_id); - buffer.writeInt(new_count); - } - - public static StackChangePacket read(FriendlyByteBuf buffer) { - var id = buffer.readInt(); - var stack_id = buffer.readInt(); - var new_count = buffer.readInt(); - - return new StackChangePacket(id, stack_id, new_count); - } - - public void play(Supplier context) { - context.get().setPacketHandled(true); - context.get().enqueueWork(() -> { - var get = Minecraft.getInstance().player.containerMenu instanceof DriveViewerMenu menu && menu.containerId == id ? menu.view : null; - - if (get == null) { - throw new IllegalStateException("No such item tracker with id " + id); - } - - var get_state = get.state.get(stack_id); - - if (get_state == null) { - throw new IllegalStateException("Item tracker " + id + " has no stack with id of " + stack_id); - } - - get_state.stack.setCount(new_count); - get.clearCache(); - }); - } - } - - public record StackRemovePacket(int id, int stack_id) { - public void write(FriendlyByteBuf buffer) { - buffer.writeInt(id); - buffer.writeInt(stack_id); - } - - public static StackRemovePacket read(FriendlyByteBuf buffer) { - var id = buffer.readInt(); - var stack_id = buffer.readInt(); - - return new StackRemovePacket(id, stack_id); - } - - public void play(Supplier context) { - context.get().setPacketHandled(true); - context.get().enqueueWork(() -> { - var get = Minecraft.getInstance().player.containerMenu instanceof DriveViewerMenu menu && menu.containerId == id ? menu.view : null; - - if (get == null) { - throw new IllegalStateException("No such item tracker with id " + id); - } - - var get_state = get.state.remove(stack_id); - - if (get_state == null) { - throw new IllegalStateException("Item tracker " + id + " has no stack with id of " + stack_id); - } - - get.clearCache(); - }); - } - } - public enum FilterSwitch { MATCH_NBT, MATCH_TAG, diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/ItemMonitorMenu.java b/src/main/java/ru/dbotthepony/mc/otm/menu/ItemMonitorMenu.java new file mode 100644 index 000000000..0e6acfe18 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/ItemMonitorMenu.java @@ -0,0 +1,58 @@ +package ru.dbotthepony.mc.otm.menu; + +import net.minecraft.world.entity.player.Inventory; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import ru.dbotthepony.mc.otm.Registry; +import ru.dbotthepony.mc.otm.block.entity.BlockEntityDriveRack; +import ru.dbotthepony.mc.otm.block.entity.BlockEntityItemMonitor; +import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewSupplier; +import ru.dbotthepony.mc.otm.menu.data.InteractPacket; +import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView; +import ru.dbotthepony.mc.otm.storage.ItemStackObject; + +import javax.annotation.Nonnull; + +public class ItemMonitorMenu extends PoweredMatteryMenu implements INetworkedItemViewSupplier { + public final NetworkedItemView view; + + @Nonnull + @Override + public NetworkedItemView getNetworkedItemView() { + return view; + } + + public ItemMonitorMenu(int p_38852_, Inventory inventory) { + this(p_38852_, inventory, null); + } + + public ItemMonitorMenu(int p_38852_, Inventory inventory, BlockEntityItemMonitor tile) { + super(Registry.Menus.ITEM_MONITOR, p_38852_, inventory, tile); + + view = new NetworkedItemView(inventory.player, this, tile == null); + + if (tile != null) { + OverdriveThatMatters.LOGGER.info("{}", tile.cell.getStorageGrid().global_item_view.getCombinedItemList()); + view.setComponent(tile.cell.getStorageGrid().getVirtualComponent(ItemStackObject.class)); + } + + addBatterySlot(); + addInventorySlots(); + } + + @Override + public void broadcastChanges() { + super.broadcastChanges(); + + view.network(); + } + + @Override + protected int getWorkingSlotStart() { + return 0; + } + + @Override + protected int getWorkingSlotEnd() { + return 1; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/MatteryMenu.java b/src/main/java/ru/dbotthepony/mc/otm/menu/MatteryMenu.java index ab3ca77ce..3a3471eb6 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/MatteryMenu.java +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/MatteryMenu.java @@ -18,10 +18,10 @@ import java.util.Set; import java.util.function.Consumer; public abstract class MatteryMenu extends AbstractContainerMenu { - protected BlockEntity tile; + public final BlockEntity tile; - protected Inventory inventory; - protected Player ply; + public final Inventory inventory; + public final Player ply; public final ArrayList mattery_widgets = new ArrayList<>(); public final ArrayList inventory_slots = new ArrayList<>(); @@ -43,13 +43,13 @@ public abstract class MatteryMenu extends AbstractContainerMenu { } protected MatteryMenu(@Nullable MenuType p_38851_, int p_38852_, Inventory inventory) { - super(p_38851_, p_38852_); - this.inventory = inventory; - this.ply = inventory.player; + this(p_38851_, p_38852_, inventory, null); } protected MatteryMenu(@Nullable MenuType p_38851_, int p_38852_, Inventory inventory, BlockEntity tile) { - this(p_38851_, p_38852_, inventory); + super(p_38851_, p_38852_); + this.inventory = inventory; + this.ply = inventory.player; this.tile = tile; } @@ -157,22 +157,6 @@ public abstract class MatteryMenu extends AbstractContainerMenu { abstract protected int getWorkingSlotStart(); abstract protected int getWorkingSlotEnd(); - protected void notifySlotsWatchCurrentStack() { - for (var slot : slots) { - if (slot instanceof MatterySlot slot1) { - slot1.watchCurrentStack(); - } - } - } - - protected void notifySlotsUnwatchCurrentStack() { - for (var slot : slots) { - if (slot instanceof MatterySlot slot1) { - slot1.unwatchCurrentStack(); - } - } - } - // This method receive Player interactor and slot_index where Shift + Right click occurred // It shall return item stack that got moved @Override @@ -193,7 +177,6 @@ public abstract class MatteryMenu extends AbstractContainerMenu { ItemStack moved = ItemStack.EMPTY; Slot get_slot = this.slots.get(slot_index); - notifySlotsWatchCurrentStack(); if (get_slot.hasItem()) { ItemStack slot_item = get_slot.getItem(); @@ -203,12 +186,10 @@ public abstract class MatteryMenu extends AbstractContainerMenu { // Moving FROM machine TO inventory if (!moveItemStackTo(slot_item, inventory_slot_index_start, inventory_slot_index_end + 1, false)) { - notifySlotsUnwatchCurrentStack(); return ItemStack.EMPTY; } } else if (!moveItemStackTo(slot_item, start, end, false)) { // Moving FROM inventory TO machine - notifySlotsUnwatchCurrentStack(); return ItemStack.EMPTY; } @@ -217,11 +198,8 @@ public abstract class MatteryMenu extends AbstractContainerMenu { } else { get_slot.setChanged(); } - - notifySlotsWatchCurrentStack(); } - notifySlotsUnwatchCurrentStack(); return moved; } diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/PoweredMatteryMenu.java b/src/main/java/ru/dbotthepony/mc/otm/menu/PoweredMatteryMenu.java index bb8b40ac2..809150d23 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/PoweredMatteryMenu.java +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/PoweredMatteryMenu.java @@ -12,7 +12,7 @@ import ru.dbotthepony.mc.otm.menu.widget.BatteryLevelWidget; import javax.annotation.Nullable; abstract public class PoweredMatteryMenu extends MatteryMenu { - protected BlockEntityMatteryPowered tile; + public final BlockEntityMatteryPowered tile; public BatteryLevelWidget battery_widget; public BatterySlot battery_slot; diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/data/ClearPacket.java b/src/main/java/ru/dbotthepony/mc/otm/menu/data/ClearPacket.java new file mode 100644 index 000000000..d7297dc06 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/data/ClearPacket.java @@ -0,0 +1,31 @@ +package ru.dbotthepony.mc.otm.menu.data; + +import net.minecraft.client.Minecraft; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.fmllegacy.network.NetworkEvent; +import ru.dbotthepony.mc.otm.menu.DriveViewerMenu; + +import java.util.function.Supplier; + +public record ClearPacket(int id) { + public void write(FriendlyByteBuf buffer) { + buffer.writeInt(id); + } + + public static ClearPacket read(FriendlyByteBuf buffer) { + return new ClearPacket(buffer.readInt()); + } + + public void play(Supplier context) { + context.get().setPacketHandled(true); + context.get().enqueueWork(() -> { + var get = Minecraft.getInstance().player.containerMenu instanceof INetworkedItemViewSupplier supplier && Minecraft.getInstance().player.containerMenu.containerId == id ? supplier.getNetworkedItemView() : null; + + if (get == null) { + throw new IllegalStateException("No such item tracker with id " + id); + } + + get.clear(); + }); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/data/INetworkedItemViewSupplier.java b/src/main/java/ru/dbotthepony/mc/otm/menu/data/INetworkedItemViewSupplier.java new file mode 100644 index 000000000..ddebaac78 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/data/INetworkedItemViewSupplier.java @@ -0,0 +1,11 @@ +package ru.dbotthepony.mc.otm.menu.data; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public interface INetworkedItemViewSupplier { + NetworkedItemView getNetworkedItemView(); +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/data/InteractPacket.java b/src/main/java/ru/dbotthepony/mc/otm/menu/data/InteractPacket.java new file mode 100644 index 000000000..5fdfc8e7c --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/data/InteractPacket.java @@ -0,0 +1,41 @@ +package ru.dbotthepony.mc.otm.menu.data; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.inventory.ClickAction; +import net.minecraft.world.inventory.ClickType; +import net.minecraftforge.fmllegacy.network.NetworkEvent; +import ru.dbotthepony.mc.otm.block.entity.BlockEntityDriveViewer; +import ru.dbotthepony.mc.otm.menu.DriveViewerMenu; +import ru.dbotthepony.mc.otm.network.MatteryNetworking; +import ru.dbotthepony.mc.otm.network.SetCarriedPacket; + +import java.util.function.Supplier; + +public record InteractPacket(int id, int stack_id, ClickType click, ClickAction action) { + public void write(FriendlyByteBuf buffer) { + buffer.writeInt(id); + buffer.writeInt(stack_id); + buffer.writeEnum(click); + buffer.writeEnum(action); + } + + public static InteractPacket read(FriendlyByteBuf buffer) { + var id = buffer.readInt(); + var stack = buffer.readInt(); + var click = buffer.readEnum(ClickType.class); + var action = buffer.readEnum(ClickAction.class); + return new InteractPacket(id, stack, click, action); + } + + public void play(Supplier context) { + context.get().setPacketHandled(true); + context.get().enqueueWork(() -> { + var ply = context.get().getSender(); + + if (ply.containerMenu instanceof INetworkedItemViewSupplier supplier && ply.containerMenu.containerId == id) { + ply.resetLastActionTime(); + supplier.getNetworkedItemView().playerInteract(this); + } + }); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.java b/src/main/java/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.java new file mode 100644 index 000000000..6d8047ccf --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/data/NetworkedItemView.java @@ -0,0 +1,288 @@ +package ru.dbotthepony.mc.otm.menu.data; + +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.ClickAction; +import net.minecraft.world.inventory.ClickType; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fmllegacy.network.PacketDistributor; +import ru.dbotthepony.mc.otm.menu.MatteryMenu; +import ru.dbotthepony.mc.otm.network.MatteryNetworking; +import ru.dbotthepony.mc.otm.network.SetCarriedPacket; +import ru.dbotthepony.mc.otm.storage.*; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +import static net.minecraft.client.gui.screens.Screen.hasShiftDown; + +/** + * Creates a virtual, slotless container for Player's interaction with. + */ + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public class NetworkedItemView implements IStorageListener { + public final boolean remote; + public final MatteryMenu menu; + public final Player ply; + + protected int next_stack_id = 0; + + protected final HashMap state = new HashMap<>(); + protected final HashMap upstream_state = new HashMap<>(); + protected final ArrayList backlog = new ArrayList<>(); + + @Nullable + public NetworkedItem get(int id) { + return state.get(id); + } + + private List view_cache; + + protected IStorageComponent provider; + + public NetworkedItemView(Player ply, MatteryMenu menu, boolean remote) { + this.remote = remote; + this.menu = menu; + this.ply = ply; + } + + public void mouseClick(int index, int mouse_click_type) { + var list = getItems(); + var action = mouse_click_type == InputConstants.MOUSE_BUTTON_LEFT ? ClickAction.PRIMARY : ClickAction.SECONDARY; + var type = mouse_click_type == InputConstants.MOUSE_BUTTON_MIDDLE ? ClickType.CLONE : hasShiftDown() ? ClickType.QUICK_MOVE : ClickType.PICKUP; + + MatteryNetworking.send(null, new InteractPacket(menu.containerId, index >= list.size() ? -1 : list.get(index).id(), type, action)); + } + + public void setComponent(@Nullable IStorageComponent provider) { + if (provider == this.provider) + return; + + if (this.provider != null) + this.provider.removeListenerAuto(this); + + this.provider = provider; + + if (provider != null) + provider.addListenerAuto(this); + } + + public record NetworkedItem(int id, ItemStack stack, @Nullable UUID id_upstream) { + public NetworkedItem(int id, ItemStack stack) { + this(id, stack, null); + } + } + + public void clearCache() { + view_cache = null; + } + + public List getItems() { + if (view_cache != null) + return view_cache; + + return view_cache = List.copyOf(state.values()); + } + + @Override + public void addObject(ItemStackObject stack, UUID id, IStorageView provider) { + addObject(stack.stack(), id); + } + + @Override + public void changeObject(UUID id, BigDecimal new_count) { + changeObject(id, new_count.intValue()); + } + + @Override + public void removeObject(UUID id) { + var get = upstream_state.get(id); + + if (get == null) + throw new IllegalStateException("Unknown ItemStack with upstream id " + id + "!"); + + upstream_state.remove(id); + state.remove(get.id); + + if (!remote) { + backlog.add(new StackRemovePacket(menu.containerId, get.id)); + } + + clearCache(); + } + + public void addObject(ItemStack stack, UUID id_upstream) { + if (upstream_state.containsKey(id_upstream)) + throw new IllegalStateException("Already tracking ItemStack with upstream id " + id_upstream + "!"); + + var state = new NetworkedItem(next_stack_id++, stack.copy(), id_upstream); + this.state.put(state.id, state); + this.upstream_state.put(id_upstream, state); + + if (!remote) { + backlog.add(new StackAddPacket(menu.containerId, state.id, stack)); + } + + clearCache(); + } + + public void changeObject(UUID id_upstream, int new_count) { + var get = upstream_state.get(id_upstream); + + if (get == null) + throw new IllegalStateException("Unknown ItemStack with upstream id " + id_upstream + "!"); + + get.stack.setCount(new_count); + + if (!remote) { + backlog.add(new StackChangePacket(menu.containerId, get.id, new_count)); + } + + clearCache(); + } + + public void clear() { + clearCache(); + upstream_state.clear(); + state.clear(); + + if (!remote) { + backlog.clear(); + backlog.add(new ClearPacket(menu.containerId)); + } + } + + public void network() { + if (remote) + throw new IllegalStateException("Not a server"); + + var consumer = PacketDistributor.PLAYER.with(() -> (ServerPlayer) ply); + + for (var packet : backlog) { + MatteryNetworking.CHANNEL.send(consumer, packet); + } + + backlog.clear(); + } + + public int calculateIOAmount(int desired_amount) { + return desired_amount; + } + + public void doneIOAmount(int done_amount) { + + } + + public void playerInteract(InteractPacket packet) { + if (provider == null) + return; + + var click = packet.click(); + var action = packet.action(); + var stack_id = packet.stack_id(); + + if (click == ClickType.CLONE) { + if (stack_id < 0 || !ply.getAbilities().instabuild) + return; + + var get_state = get(stack_id); + + if (get_state == null) { + return; + } + + var copy = get_state.stack().copy(); + copy.setCount(Math.min(copy.getCount(), copy.getMaxStackSize())); + + ply.containerMenu.setCarried(copy); + MatteryNetworking.send((ServerPlayer) ply, new SetCarriedPacket(ply.containerMenu.getCarried())); + ply.containerMenu.setRemoteCarried(ply.containerMenu.getCarried().copy()); + return; + } + + if (click == ClickType.QUICK_MOVE && stack_id > -1) { + var get_state = get(stack_id); + + if (get_state == null) { + return; + } + + int amount = calculateIOAmount(action == ClickAction.PRIMARY ? get_state.stack().getMaxStackSize() : Math.max(1, get_state.stack().getMaxStackSize() / 2)); + var extracted = provider.extractObject(get_state.id_upstream(), new BigDecimal(amount), true); + + if (!extracted.isEmpty()) { + var move = menu.quickMoveToInventory(extracted.stack(), false); + + if (move.remaining().getCount() != extracted.stack().getCount()) { + provider.extractObject(get_state.id_upstream(), new BigDecimal(extracted.stack().getCount() - move.remaining().getCount()), false); + doneIOAmount(extracted.stack().getCount() - move.remaining().getCount()); + } + } + + return; + } + + if (!menu.getCarried().isEmpty() && click != ClickType.QUICK_MOVE) { + // try to put + if (action == ClickAction.PRIMARY) { + var carried = menu.getCarried(); + int amount = calculateIOAmount(carried.getCount()); + + if (amount == carried.getCount()) { + var leftover = provider.insertObject(new ItemStackObject(menu.getCarried()), false); + menu.setCarried(leftover.stack()); + doneIOAmount(amount - leftover.stack().getCount()); + + MatteryNetworking.send((ServerPlayer) ply, new SetCarriedPacket(menu.getCarried())); + menu.setRemoteCarried(menu.getCarried().copy()); + } else if (amount != 0) { + var copy = carried.copy(); + copy.setCount(amount); + + var leftover = provider.insertObject(new ItemStackObject(copy), false); + doneIOAmount(amount - leftover.stack().getCount()); + + leftover.setCount(carried.getCount() - amount + leftover.getCountInt()); + menu.setCarried(leftover.stack()); + + MatteryNetworking.send((ServerPlayer) ply, new SetCarriedPacket(menu.getCarried())); + menu.setRemoteCarried(menu.getCarried().copy()); + } + } else { + var copy = menu.getCarried().copy(); + copy.setCount(1); + + if (calculateIOAmount(1) == 1 && provider.insertObject(new ItemStackObject(copy), false).isEmpty()) { + menu.getCarried().shrink(1); + doneIOAmount(1); + MatteryNetworking.send((ServerPlayer) ply, new SetCarriedPacket(menu.getCarried())); + menu.setRemoteCarried(menu.getCarried().copy()); + } + } + } else if (stack_id > -1) { + var get_state = get(stack_id); + + if (get_state == null) { + return; + } + + int amount = calculateIOAmount(action == ClickAction.PRIMARY ? get_state.stack().getMaxStackSize() : Math.max(1, get_state.stack().getMaxStackSize() / 2)); + + var extracted = provider.extractObject(get_state.id_upstream(), new BigDecimal(amount), false); + + doneIOAmount(extracted.getCountInt()); + menu.setCarried(extracted.stack()); + MatteryNetworking.send((ServerPlayer) ply, new SetCarriedPacket(menu.getCarried())); + menu.setRemoteCarried(menu.getCarried().copy()); + } + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackAddPacket.java b/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackAddPacket.java new file mode 100644 index 000000000..22fe9402e --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackAddPacket.java @@ -0,0 +1,55 @@ +package ru.dbotthepony.mc.otm.menu.data; + +import net.minecraft.client.Minecraft; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.fmllegacy.network.NetworkEvent; +import ru.dbotthepony.mc.otm.menu.DriveViewerMenu; + +import java.util.function.Supplier; + +public record StackAddPacket(int id, int stack_id, ItemStack stack) { + public void write(FriendlyByteBuf buffer) { + buffer.writeInt(id); + buffer.writeInt(stack_id); + buffer.writeRegistryId(stack.getItem()); + buffer.writeInt(stack.getCount()); + buffer.writeBoolean(stack.getTag() != null); + + if (stack.getTag() != null) + buffer.writeNbt(stack.getShareTag()); + } + + public static StackAddPacket read(FriendlyByteBuf buffer) { + var id = buffer.readInt(); + var stack = buffer.readInt(); + var item = buffer.readRegistryIdSafe(Item.class); + var count = buffer.readInt(); + + var state = new ItemStack(item, count); + + if (buffer.readBoolean()) + state.readShareTag(buffer.readNbt()); + + return new StackAddPacket(id, stack, state); + } + + public void play(Supplier context) { + context.get().setPacketHandled(true); + context.get().enqueueWork(() -> { + var get = Minecraft.getInstance().player.containerMenu instanceof INetworkedItemViewSupplier supplier && Minecraft.getInstance().player.containerMenu.containerId == id ? supplier.getNetworkedItemView() : null; + + if (get == null) { + throw new IllegalStateException("No such item tracker with id " + id); + } + + if (get.state.containsKey(stack_id)) { + throw new IllegalStateException("Item tracker " + id + " already has stack with id of " + stack_id); + } + + get.state.put(stack_id, new NetworkedItemView.NetworkedItem(stack_id, stack)); + get.clearCache(); + }); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackChangePacket.java b/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackChangePacket.java new file mode 100644 index 000000000..49c61337a --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackChangePacket.java @@ -0,0 +1,44 @@ +package ru.dbotthepony.mc.otm.menu.data; + +import net.minecraft.client.Minecraft; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.fmllegacy.network.NetworkEvent; +import ru.dbotthepony.mc.otm.menu.DriveViewerMenu; + +import java.util.function.Supplier; + +public record StackChangePacket(int id, int stack_id, int new_count) { + public void write(FriendlyByteBuf buffer) { + buffer.writeInt(id); + buffer.writeInt(stack_id); + buffer.writeInt(new_count); + } + + public static StackChangePacket read(FriendlyByteBuf buffer) { + var id = buffer.readInt(); + var stack_id = buffer.readInt(); + var new_count = buffer.readInt(); + + return new StackChangePacket(id, stack_id, new_count); + } + + public void play(Supplier context) { + context.get().setPacketHandled(true); + context.get().enqueueWork(() -> { + var get = Minecraft.getInstance().player.containerMenu instanceof INetworkedItemViewSupplier supplier && Minecraft.getInstance().player.containerMenu.containerId == id ? supplier.getNetworkedItemView() : null; + + if (get == null) { + throw new IllegalStateException("No such item tracker with id " + id); + } + + var get_state = get.state.get(stack_id); + + if (get_state == null) { + throw new IllegalStateException("Item tracker " + id + " has no stack with id of " + stack_id); + } + + get_state.stack().setCount(new_count); + get.clearCache(); + }); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackRemovePacket.java b/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackRemovePacket.java new file mode 100644 index 000000000..1f169d6d6 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/data/StackRemovePacket.java @@ -0,0 +1,41 @@ +package ru.dbotthepony.mc.otm.menu.data; + +import net.minecraft.client.Minecraft; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.fmllegacy.network.NetworkEvent; +import ru.dbotthepony.mc.otm.menu.DriveViewerMenu; + +import java.util.function.Supplier; + +public record StackRemovePacket(int id, int stack_id) { + public void write(FriendlyByteBuf buffer) { + buffer.writeInt(id); + buffer.writeInt(stack_id); + } + + public static StackRemovePacket read(FriendlyByteBuf buffer) { + var id = buffer.readInt(); + var stack_id = buffer.readInt(); + + return new StackRemovePacket(id, stack_id); + } + + public void play(Supplier context) { + context.get().setPacketHandled(true); + context.get().enqueueWork(() -> { + var get = Minecraft.getInstance().player.containerMenu instanceof INetworkedItemViewSupplier supplier && Minecraft.getInstance().player.containerMenu.containerId == id ? supplier.getNetworkedItemView() : null; + + if (get == null) { + throw new IllegalStateException("No such item tracker with id " + id); + } + + var get_state = get.state.remove(stack_id); + + if (get_state == null) { + throw new IllegalStateException("Item tracker " + id + " has no stack with id of " + stack_id); + } + + get.clearCache(); + }); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/slot/MatterySlot.java b/src/main/java/ru/dbotthepony/mc/otm/menu/slot/MatterySlot.java index 540dff3dc..df606e1a8 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/slot/MatterySlot.java +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/slot/MatterySlot.java @@ -30,56 +30,4 @@ public class MatterySlot extends Slot { public MatterySlot(Container p_40223_, int index) { this(p_40223_, index, 0, 0, true); } - - private ItemStack last_stack; - private ItemStack watching_stack; - - public void watchCurrentStack() { - watching_stack = super.getItem().copy(); - } - - public void unwatchCurrentStack() { - watching_stack = null; - } - - @Override - public ItemStack getItem() { - var get = super.getItem(); - last_stack = get.copy(); - return get; - } - - @Override - public void setChanged() { - if (container instanceof MatteryContainer container1) { - ItemStack old = watching_stack != null ? watching_stack.copy() : last_stack.copy(); - container1.setChanged(getSlotIndex(), super.getItem(), old); - } else { - super.setChanged(); - } - } - - @Override - public void set(ItemStack p_40240_) { - if (container instanceof MatteryContainer container1) { - ItemStack old = watching_stack != null ? watching_stack.copy() : container1.getItem(getSlotIndex()).copy(); - container1.startIgnore(); - container1.setItem(getSlotIndex(), p_40240_); - container1.stopIgnore(); - container1.setChanged(getSlotIndex(), p_40240_, old); - } else { - super.set(p_40240_); - } - } - - @Override - public void onTake(Player p_150645_, ItemStack p_150646_) { - if (container instanceof MatteryContainer container1) { - ItemStack old = watching_stack != null ? watching_stack.copy() : last_stack.copy(); - container1.setChanged(getSlotIndex(), p_150646_.copy(), old); - last_stack = ItemStack.EMPTY; - } else { - super.onTake(p_150645_, p_150646_); - } - } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java b/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java index 1f387364c..d614ef10a 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java +++ b/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java @@ -7,8 +7,8 @@ import net.minecraftforge.fmllegacy.network.NetworkRegistry; import net.minecraftforge.fmllegacy.network.PacketDistributor; import net.minecraftforge.fmllegacy.network.simple.SimpleChannel; import ru.dbotthepony.mc.otm.OverdriveThatMatters; -import ru.dbotthepony.mc.otm.item.ItemPortableCondensationDrive; import ru.dbotthepony.mc.otm.menu.DriveViewerMenu; +import ru.dbotthepony.mc.otm.menu.data.*; import ru.dbotthepony.mc.otm.network.android.*; import java.util.Optional; @@ -26,6 +26,14 @@ public class MatteryNetworking { ); public static void send(ServerPlayer ply, Object ...messages) { + if (ply == null) { + for (var message : messages) { + CHANNEL.sendToServer(message); + } + + return; + } + var supplier = PacketDistributor.PLAYER.with(() -> ply); for (var message : messages) { @@ -33,12 +41,6 @@ public class MatteryNetworking { } } - public static void send(Object ...messages) { - for (var message : messages) { - CHANNEL.sendToServer(message); - } - } - public static void register() { CHANNEL.registerMessage( next_network_id++, @@ -150,46 +152,46 @@ public class MatteryNetworking { CHANNEL.registerMessage( next_network_id++, - DriveViewerMenu.ClearPacket.class, - DriveViewerMenu.ClearPacket::write, - DriveViewerMenu.ClearPacket::read, - DriveViewerMenu.ClearPacket::play, + ClearPacket.class, + ClearPacket::write, + ClearPacket::read, + ClearPacket::play, Optional.of(NetworkDirection.PLAY_TO_CLIENT) ); CHANNEL.registerMessage( next_network_id++, - DriveViewerMenu.StackAddPacket.class, - DriveViewerMenu.StackAddPacket::write, - DriveViewerMenu.StackAddPacket::read, - DriveViewerMenu.StackAddPacket::play, + StackAddPacket.class, + StackAddPacket::write, + StackAddPacket::read, + StackAddPacket::play, Optional.of(NetworkDirection.PLAY_TO_CLIENT) ); CHANNEL.registerMessage( next_network_id++, - DriveViewerMenu.StackChangePacket.class, - DriveViewerMenu.StackChangePacket::write, - DriveViewerMenu.StackChangePacket::read, - DriveViewerMenu.StackChangePacket::play, + StackChangePacket.class, + StackChangePacket::write, + StackChangePacket::read, + StackChangePacket::play, Optional.of(NetworkDirection.PLAY_TO_CLIENT) ); CHANNEL.registerMessage( next_network_id++, - DriveViewerMenu.StackRemovePacket.class, - DriveViewerMenu.StackRemovePacket::write, - DriveViewerMenu.StackRemovePacket::read, - DriveViewerMenu.StackRemovePacket::play, + StackRemovePacket.class, + StackRemovePacket::write, + StackRemovePacket::read, + StackRemovePacket::play, Optional.of(NetworkDirection.PLAY_TO_CLIENT) ); CHANNEL.registerMessage( next_network_id++, - DriveViewerMenu.InteractPacket.class, - DriveViewerMenu.InteractPacket::write, - DriveViewerMenu.InteractPacket::read, - DriveViewerMenu.InteractPacket::play, + InteractPacket.class, + InteractPacket::write, + InteractPacket::read, + InteractPacket::play, Optional.of(NetworkDirection.PLAY_TO_SERVER) ); diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageComponent.java b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageComponent.java new file mode 100644 index 000000000..92bf12656 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageComponent.java @@ -0,0 +1,12 @@ +package ru.dbotthepony.mc.otm.storage; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.List; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public interface IStorageComponent extends IStorageView, IStorageConsumer { + +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageConsumer.java b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageConsumer.java new file mode 100644 index 000000000..7f3d71316 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageConsumer.java @@ -0,0 +1,11 @@ +package ru.dbotthepony.mc.otm.storage; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public interface IStorageConsumer extends IStorageIdentity { + T insertObject(T obj, boolean simulate); +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageIdentity.java b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageIdentity.java new file mode 100644 index 000000000..b70efca84 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageIdentity.java @@ -0,0 +1,11 @@ +package ru.dbotthepony.mc.otm.storage; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public interface IStorageIdentity { + Class storageIdentity(); +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageListener.java b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageListener.java new file mode 100644 index 000000000..682b1b76d --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageListener.java @@ -0,0 +1,16 @@ +package ru.dbotthepony.mc.otm.storage; + +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.math.BigDecimal; +import java.util.UUID; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public interface IStorageListener { + void addObject(T stack, UUID id, IStorageView provider); + void changeObject(UUID id, BigDecimal new_count); + void removeObject(UUID id); +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageObject.java b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageObject.java new file mode 100644 index 000000000..cfbf71482 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageObject.java @@ -0,0 +1,17 @@ +package ru.dbotthepony.mc.otm.storage; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.math.BigDecimal; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public interface IStorageObject { + IStorageObject copy(); + + void setCount(BigDecimal value); + BigDecimal getCount(); + + boolean isEmpty(); +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageTrigger.java b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageTrigger.java new file mode 100644 index 000000000..9bd04557c --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageTrigger.java @@ -0,0 +1,12 @@ +package ru.dbotthepony.mc.otm.storage; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public interface IStorageTrigger extends IStorageIdentity { + boolean addListener(IStorageListener listener); + boolean removeListener(IStorageListener listener); +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageTuple.java b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageTuple.java new file mode 100644 index 000000000..b37e03178 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageTuple.java @@ -0,0 +1,8 @@ +package ru.dbotthepony.mc.otm.storage; + +import java.util.UUID; + +public interface IStorageTuple { + UUID id(); + T object(); +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageView.java b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageView.java new file mode 100644 index 000000000..d77cb8e79 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/IStorageView.java @@ -0,0 +1,66 @@ +package ru.dbotthepony.mc.otm.storage; + + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.math.BigDecimal; +import java.util.List; +import java.util.UUID; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public interface IStorageView extends IStorageTrigger { + /** + * @param id identifier of object + * @return stored object (not a copy). Do not edit it. + */ + T getStoredObject(UUID id); + + /** + * @param id identifier of object to extract + * @param amount amount of units to extract + * @param simulate whenever to simulate the action or not + * @return copy of object, with amount of units actually extracted + */ + T extractObject(UUID id, BigDecimal amount, boolean simulate); + + /** + * Designed for views, for extraction with less computation overhead caused by + * copying object extracted + * + * @param id identifier of object to extract + * @param amount desired amount to extract + * @param simulate whenever to simulate the action or not + * @return amount extracted + */ + default BigDecimal extractObjectCount(UUID id, BigDecimal amount, boolean simulate) { + return extractObject(id, amount, simulate).getCount(); + } + + List> getStorageObjects(); + + default boolean addListenerAuto(IStorageListener listener) { + if (addListener(listener)) { + for (var stack : getStorageObjects()) { + listener.addObject(stack.object(), stack.id(), this); + } + + return true; + } + + return false; + } + + default boolean removeListenerAuto(IStorageListener listener) { + if (removeListener(listener)) { + for (var stack : getStorageObjects()) { + listener.removeObject(stack.id()); + } + + return true; + } + + return false; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/ItemStackObject.java b/src/main/java/ru/dbotthepony/mc/otm/storage/ItemStackObject.java new file mode 100644 index 000000000..fa2143eec --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/ItemStackObject.java @@ -0,0 +1,41 @@ +package ru.dbotthepony.mc.otm.storage; + +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.math.BigDecimal; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public record ItemStackObject(ItemStack stack) implements IStorageObject { + public static final ItemStackObject EMPTY = new ItemStackObject(ItemStack.EMPTY); + + @Override + public IStorageObject copy() { + return new ItemStackObject(stack.copy()); + } + + @Override + public void setCount(BigDecimal value) { + stack.setCount(Math.max(value.intValue(), 0)); + } + + public void setCount(int value) { + stack.setCount(Math.max(value, 0)); + } + + @Override + public BigDecimal getCount() { + return new BigDecimal(stack.getCount()); + } + + public int getCountInt() { + return stack.getCount(); + } + + @Override + public boolean isEmpty() { + return stack.isEmpty(); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/StorageGrid.java b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageGrid.java new file mode 100644 index 000000000..96521b02e --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageGrid.java @@ -0,0 +1,221 @@ +package ru.dbotthepony.mc.otm.storage; + +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraftforge.common.util.LazyOptional; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import ru.dbotthepony.mc.otm.capability.IStorageGridCell; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import java.math.BigDecimal; +import java.util.*; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public class StorageGrid { + public final StorageItemView global_item_view = new StorageItemView(); + private final HashSet cells = new HashSet<>(); + + private final HashMap, IStorageView> views = new HashMap<>(); + private final HashMap, ArrayList>> consumers = new HashMap<>(); + private final HashMap, IStorageComponent> virtual_components = new HashMap<>(); + + public StorageGrid() { + views.put(ItemStackObject.class, global_item_view); + } + + @SuppressWarnings("unchecked") + public T insertObject(Class type, T object, boolean simulate) { + var leftover = object; + + for (var consumer : consumers.computeIfAbsent(type, (k) -> new ArrayList<>())) { + leftover = ((IStorageConsumer) consumer).insertObject(leftover, simulate); + + if (leftover.isEmpty()) { + return leftover; + } + } + + return leftover; + } + + /** + * @param type Class, representing identity of stored object + * @param The identity itself + * @return a virtual IStorageComponent, or null if storage grid contain no view for provided type + */ + @SuppressWarnings("unchecked") + @Nullable + public IStorageComponent getVirtualComponent(final Class type) { + return (IStorageComponent) virtual_components.computeIfAbsent(type, (k) -> { + final var get_view = (IStorageView) views.get(type); + + if (get_view == null) + return null; + + return new IStorageComponent() { + @Override + public T insertObject(T obj, boolean simulate) { + return StorageGrid.this.insertObject(type, obj, simulate); + } + + @Override + public T getStoredObject(UUID id) { + return get_view.getStoredObject(id); + } + + @Override + public T extractObject(UUID id, BigDecimal amount, boolean simulate) { + return get_view.extractObject(id, amount, simulate); + } + + @Override + public List> getStorageObjects() { + return get_view.getStorageObjects(); + } + + @Override + public Class storageIdentity() { + return type; + } + + @Override + public boolean addListener(IStorageListener listener) { + return get_view.addListener(listener); + } + + @Override + public boolean removeListener(IStorageListener listener) { + return get_view.removeListener(listener); + } + }; + }); + } + + public int size() { + return cells.size(); + } + + @SuppressWarnings("unchecked") + public void add(IStorageIdentity identity) { + final var view = (IStorageView) views.get(identity.storageIdentity()); + + if (view != null && identity instanceof IStorageView provider) { + ((IStorageView) provider).addListenerAuto((IStorageListener) view); + } else if (view != null && identity instanceof IStorageTrigger provider) { + ((IStorageTrigger) provider).addListener((IStorageListener) view); + } + + if (identity instanceof IStorageConsumer provider) { + consumers.computeIfAbsent(identity.storageIdentity(), (k) -> new ArrayList<>()).add(provider); + } + } + + public boolean add(LazyOptional cell) { + if (!cell.isPresent()) + return false; + + var resolved = cell.resolve(); + + if (resolved.isPresent()) { + final var value = resolved.get(); + + if (add(value)) { + cell.addListener((lazy) -> remove(value)); + return true; + } + } + + return false; + } + + public boolean add(IStorageGridCell cell) { + if (cells.add(cell)) { + cell.setStorageGrid(this); + + for (var identity : cell.getComponents()) { + add(identity); + } + + return true; + } + + return false; + } + + @SuppressWarnings("unchecked") + public void remove(IStorageIdentity identity) { + final var view = (IStorageView) views.get(identity.storageIdentity()); + + if (view != null && identity instanceof IStorageView provider) { + ((IStorageView) provider).removeListenerAuto((IStorageListener) view); + } else if (view != null && identity instanceof IStorageTrigger provider) { + ((IStorageTrigger) provider).removeListener((IStorageListener) view); + } + + if (identity instanceof IStorageConsumer provider) { + consumers.computeIfAbsent(identity.storageIdentity(), (k) -> new ArrayList<>()).remove(provider); + } + } + + public boolean remove(IStorageGridCell cell) { + if (cells.remove(cell)) { + cell.setStorageGrid(null); + + for (var identity : cell.getComponents()) { + remove(identity); + } + + return true; + } + + return false; + } + + public void detach(IStorageGridCell cell) { + if (cells.contains(cell)) { + for (var identity : cell.getComponents()) { + remove(identity); + } + } + } + + public void attach(IStorageGridCell cell) { + if (cells.contains(cell)) { + for (var identity : cell.getComponents()) { + add(identity); + } + } + } + + public StorageGrid merge(StorageGrid other) { + if (other == this) + return this; + + if (size() < other.size()) { + var copy = List.copyOf(cells); + + for (var cell : copy) { + remove(cell); + other.add(cell); + } + + return other; + } else { + var copy = List.copyOf(other.cells); + + for (var cell : copy) { + other.remove(cell); + add(cell); + } + + return this; + } + } + + public static void scheduleDiscoverNeighbours(IStorageGridCell cell, Level level, BlockPos pos) { + OverdriveThatMatters.tickUntil(level, () -> !cell.isValidStorageCell() || cell.connectOrCreateStorageGrid(level, pos, true)); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/StorageItemView.java b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageItemView.java new file mode 100644 index 000000000..f85b74b28 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageItemView.java @@ -0,0 +1,278 @@ +package ru.dbotthepony.mc.otm.storage; + +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.math.BigDecimal; +import java.util.*; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +public class StorageItemView implements IStorageListener, IStorageView { + public record LocalTuple(ItemStackObject object, UUID id, List tuples) implements IStorageTuple { + public static final LocalTuple EMPTY = new LocalTuple(ItemStackObject.EMPTY, new UUID(0, 0), List.of()); + + @Override + public boolean equals(Object obj) { + return obj instanceof LocalTuple tuple && tuple.id.equals(id) || obj instanceof UUID id && this.id.equals(id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + } + + @Override + public Class storageIdentity() { + return ItemStackObject.class; + } + + public record RemoteTuple(ItemStack stack, UUID remote_id, IStorageView provider, LocalTuple local) { + public static final RemoteTuple EMPTY = new RemoteTuple(ItemStack.EMPTY, new UUID(0, 0), new IStorageView<>() { + @Override + public Class storageIdentity() { + return ItemStackObject.class; + } + + @Override + public boolean addListener(IStorageListener listener) { + return false; + } + + @Override + public boolean removeListener(IStorageListener listener) { + return false; + } + + @Override + public ItemStackObject getStoredObject(UUID id) { + return ItemStackObject.EMPTY; + } + + @Override + public ItemStackObject extractObject(UUID id, BigDecimal amount, boolean simulate) { + return ItemStackObject.EMPTY; + } + + @Override + public List> getStorageObjects() { + return List.of(); + } + }, LocalTuple.EMPTY); + + public ItemStackObject extract(BigDecimal amount, boolean simulate) { + return provider.extractObject(remote_id, amount, simulate); + } + + public BigDecimal extractCount(BigDecimal amount, boolean simulate) { + return provider.extractObjectCount(remote_id, amount, simulate); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof RemoteTuple tuple && tuple.remote_id.equals(remote_id) || obj instanceof UUID id && id.equals(remote_id); + } + + @Override + public int hashCode() { + return remote_id.hashCode(); + } + } + + // удаленный UUID -> Кортеж + private final HashMap indexed_by_remote_uuid = new HashMap<>(); + + // локальный UUID -> Локальный кортеж + private final HashMap indexed_by_local_uuid = new HashMap<>(); + + // Item -> Список Локальных кортежей + private final HashMap> indexed_by_item = new HashMap<>(); + + public List getCopyRawItemList() { + final var list = new ArrayList(indexed_by_remote_uuid.size()); + + for (var state : indexed_by_remote_uuid.values()) + list.add(state.stack.copy()); + + return list; + } + + public List getRawItemList() { + final var list = new ArrayList(indexed_by_remote_uuid.size()); + + for (var state : indexed_by_remote_uuid.values()) + list.add(state.stack); + + return list; + } + + @Override + public List> getStorageObjects() { + int capacity = 0; + + for (var list : indexed_by_item.values()) + capacity += list.size(); + + final var output = new ArrayList>(capacity); + + for (var listing : indexed_by_item.values()) + output.addAll(listing); + + return output; + } + + public List getCombinedItemList() { + int capacity = 0; + + for (var list : indexed_by_item.values()) + capacity += list.size(); + + final var output = new ArrayList(capacity); + + for (var listing : indexed_by_item.values()) + for (var combined : listing) + output.add(combined.object.stack()); + + return output; + } + + @Override + public void addObject(ItemStackObject _stack, UUID id, IStorageView provider) { + if (indexed_by_remote_uuid.containsKey(id)) { + throw new IllegalStateException("Already tracking tuple with id " + id); + } + + var stack = _stack.stack(); + + var items = indexed_by_item.computeIfAbsent(stack.getItem(), (k) -> new ArrayList<>()); + LocalTuple local_tuple = null; + + for (var item : items) { + if (ItemStack.tagMatches(item.object.stack(), stack)) { + item.object.stack().grow(stack.getCount()); + local_tuple = item; + break; + } + } + + boolean added = local_tuple == null; + + if (added) { + local_tuple = new LocalTuple((ItemStackObject) _stack.copy(), UUID.randomUUID(), new ArrayList<>(1)); + items.add(local_tuple); + indexed_by_local_uuid.put(local_tuple.id, local_tuple); + } + + var tuple = new RemoteTuple(stack.copy(), id, provider, local_tuple); + local_tuple.tuples.add(tuple); + indexed_by_remote_uuid.put(id, tuple); + + if (added) { + for (var listener : listeners) { + listener.addObject(local_tuple.object, local_tuple.id, this); + } + } else { + for (var listener : listeners) { + listener.changeObject(local_tuple.id, local_tuple.object.getCount()); + } + } + } + + @Override + public void changeObject(UUID id, BigDecimal new_count) { + assert new_count.compareTo(BigDecimal.ZERO) > 0; + var tuple = indexed_by_remote_uuid.get(id); + + if (tuple == null) + throw new IllegalStateException("No such tuple with id " + id); + + var delta = new_count.intValue() - tuple.stack.getCount(); + tuple.stack.setCount(new_count.intValue()); + tuple.local.object.stack().grow(delta); + + for (var listener : listeners) { + listener.changeObject(tuple.local.id, tuple.local.object.getCount()); + } + } + + @Override + public void removeObject(UUID id) { + var tuple = indexed_by_remote_uuid.get(id); + + if (tuple == null) + throw new IllegalStateException("No such tuple with id " + id); + + var item = tuple.local.object.stack().getItem(); + tuple.local.object.stack().shrink(tuple.stack.getCount()); + tuple.local.tuples.remove(tuple); + + indexed_by_remote_uuid.remove(id); + + if (tuple.local.object.stack().getCount() <= 0 || tuple.local.tuples.size() == 0) { + assert tuple.local.object.stack().getCount() == tuple.local.tuples.size(); + + indexed_by_local_uuid.remove(tuple.local.id); + indexed_by_item.get(item).remove(tuple.local); + + for (var listener : listeners) { + listener.removeObject(tuple.local.id); + } + } + } + + private final Set> listeners = new HashSet<>(); + + @Override + public boolean addListener(IStorageListener listener) { + return listeners.add(listener); + } + + @Override + public boolean removeListener(IStorageListener listener) { + return listeners.remove(listener); + } + + @Override + public ItemStackObject getStoredObject(UUID id) { + return indexed_by_local_uuid.getOrDefault(id, LocalTuple.EMPTY).object; + } + + @Override + public ItemStackObject extractObject(UUID id, BigDecimal _amount, boolean simulate) { + var tuple = indexed_by_local_uuid.get(id); + + if (tuple == null) + return ItemStackObject.EMPTY; + + int amount = _amount.intValue(); + + if (amount == 0) + return ItemStackObject.EMPTY; + + if (amount == -1) + amount = tuple.object.stack().getMaxStackSize(); + + int extract = Math.min(amount, tuple.object.stack().getCount()); + int extracted = 0; + + var copy = tuple.object.stack().copy(); + + for (var remote_tuple : tuple.tuples) { + extracted += remote_tuple.extractCount(new BigDecimal(extract - extracted), simulate).intValue(); + + if (extracted >= extract) + break; + } + + if (extracted > 0) { + copy.setCount(extracted); + return new ItemStackObject(copy); + } + + return ItemStackObject.EMPTY; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/storage/StorageTuple.java b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageTuple.java new file mode 100644 index 000000000..f7859c475 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/storage/StorageTuple.java @@ -0,0 +1,6 @@ +package ru.dbotthepony.mc.otm.storage; + +import java.util.UUID; + +public record StorageTuple(UUID id, T object) implements IStorageTuple { +}