From 3b313ec162fa2fe56a963bae6e685b23d39ab731 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Tue, 17 Aug 2021 13:35:11 +0700 Subject: [PATCH] Pattern monitor and Matter replicator! --- .../java/ru/dbotthepony/mc/otm/Registry.java | 28 ++ .../mc/otm/block/BlockMatterPanel.java | 115 +++++ .../mc/otm/block/BlockMatterReplicator.java | 28 ++ .../entity/BlockEntityMatterDecomposer.java | 6 +- .../block/entity/BlockEntityMatterPanel.java | 199 ++++++++ .../entity/BlockEntityMatterReplicator.java | 241 ++++++++++ .../entity/BlockEntityMatterScanner.java | 12 +- .../BlockEntityMatteryPoweredWorker.java | 61 +-- .../mc/otm/capability/IMatterGridCell.java | 9 + .../otm/capability/IMatterTaskProvider.java | 52 +++ .../mc/otm/capability/IPatternStorage.java | 25 ++ .../mc/otm/capability/MatterTask.java | 77 ++++ .../mc/otm/capability/MatteryCapability.java | 4 + .../dbotthepony/mc/otm/matter/MatterGrid.java | 51 ++- .../mc/otm/menu/MatterDecomposerMenu.java | 2 +- .../mc/otm/menu/MatterPanelMenu.java | 95 ++++ .../mc/otm/menu/MatterReplicatorMenu.java | 52 +++ .../mc/otm/menu/MatterScannerMenu.java | 2 +- .../dbotthepony/mc/otm/menu/MatteryMenu.java | 9 +- .../mc/otm/menu/widget/AbstractWidget.java | 8 + ...ientPatternStateSendListPacketHandler.java | 29 ++ .../mc/otm/network/MatteryNetworking.java | 18 + .../PatternReplicationRequestPacket.java | 45 ++ .../network/PatternStateSendListPacket.java | 62 +++ .../mc/otm/screen/MatterPanelScreen.java | 424 ++++++++++++++++++ .../mc/otm/screen/MatterReplicatorScreen.java | 11 + .../mc/otm/screen/MatteryScreen.java | 34 +- .../blockstates/matter_panel.json | 27 ++ .../overdrive_that_matters/lang/en_us.json | 13 +- .../models/block/matter_panel.json | 17 + .../textures/gui/android_station.png | Bin 9606 -> 9576 bytes .../textures/gui/android_station.xcf | Bin 21095 -> 21075 bytes .../textures/gui/generic_machine.png | Bin 9230 -> 9200 bytes .../textures/gui/generic_machine.xcf | Bin 19852 -> 19832 bytes .../textures/gui/matter_panel.png | Bin 0 -> 11226 bytes .../textures/gui/matter_panel.xcf | Bin 0 -> 37782 bytes 36 files changed, 1697 insertions(+), 59 deletions(-) create mode 100644 src/main/java/ru/dbotthepony/mc/otm/block/BlockMatterPanel.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/block/BlockMatterReplicator.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterPanel.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterReplicator.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/capability/IMatterTaskProvider.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/capability/MatterTask.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/menu/MatterPanelMenu.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/menu/MatterReplicatorMenu.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/network/ClientPatternStateSendListPacketHandler.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/network/PatternReplicationRequestPacket.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/network/PatternStateSendListPacket.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/screen/MatterPanelScreen.java create mode 100644 src/main/java/ru/dbotthepony/mc/otm/screen/MatterReplicatorScreen.java create mode 100644 src/main/resources/assets/overdrive_that_matters/blockstates/matter_panel.json create mode 100644 src/main/resources/assets/overdrive_that_matters/models/block/matter_panel.json create mode 100644 src/main/resources/assets/overdrive_that_matters/textures/gui/matter_panel.png create mode 100644 src/main/resources/assets/overdrive_that_matters/textures/gui/matter_panel.xcf diff --git a/src/main/java/ru/dbotthepony/mc/otm/Registry.java b/src/main/java/ru/dbotthepony/mc/otm/Registry.java index 71b1ae4d7..065049d4f 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/Registry.java +++ b/src/main/java/ru/dbotthepony/mc/otm/Registry.java @@ -39,6 +39,8 @@ public class Registry { public static final ResourceLocation MATTER_CABLE = new ResourceLocation(OverdriveThatMatters.MOD_ID, "matter_cable"); public static final ResourceLocation PATTERN_STORAGE = new ResourceLocation(OverdriveThatMatters.MOD_ID, "pattern_storage"); public static final ResourceLocation MATTER_SCANNER = new ResourceLocation(OverdriveThatMatters.MOD_ID, "matter_scanner"); + public static final ResourceLocation MATTER_PANEL = new ResourceLocation(OverdriveThatMatters.MOD_ID, "matter_panel"); + public static final ResourceLocation MATTER_REPLICATOR = new ResourceLocation(OverdriveThatMatters.MOD_ID, "matter_replicator"); public static final ResourceLocation ANDROID_CAPABILITY = new ResourceLocation(OverdriveThatMatters.MOD_ID, "android_capability"); @@ -66,6 +68,8 @@ public class Registry { public static final Block MATTER_CABLE = new BlockMatterCable(); public static final Block PATTERN_STORAGE = new BlockPatternStorage(); public static final Block MATTER_SCANNER = new BlockMatterScanner(); + public static final Block MATTER_PANEL = new BlockMatterPanel(); + public static final Block MATTER_REPLICATOR = new BlockMatterReplicator(); static { ANDROID_STATION.setRegistryName(Names.ANDROID_STATION); @@ -75,6 +79,8 @@ public class Registry { MATTER_CABLE.setRegistryName(Names.MATTER_CABLE); PATTERN_STORAGE.setRegistryName(Names.PATTERN_STORAGE); MATTER_SCANNER.setRegistryName(Names.MATTER_SCANNER); + MATTER_PANEL.setRegistryName(Names.MATTER_PANEL); + MATTER_REPLICATOR.setRegistryName(Names.MATTER_REPLICATOR); } public static void register(final RegistryEvent.Register event) { @@ -85,6 +91,8 @@ public class Registry { event.getRegistry().register(MATTER_CABLE); event.getRegistry().register(PATTERN_STORAGE); event.getRegistry().register(MATTER_SCANNER); + event.getRegistry().register(MATTER_PANEL); + event.getRegistry().register(MATTER_REPLICATOR); // OverdriveThatMatters.LOGGER.info("Registered blocks"); } @@ -98,6 +106,8 @@ public class Registry { public static final Item MATTER_CABLE = new BlockItem(Blocks.MATTER_CABLE, new Item.Properties().stacksTo(64).tab(CreativeModeTab.TAB_MISC)); public static final Item PATTERN_STORAGE = new BlockItem(Blocks.PATTERN_STORAGE, new Item.Properties().stacksTo(64).tab(CreativeModeTab.TAB_MISC)); public static final Item MATTER_SCANNER = new BlockItem(Blocks.MATTER_SCANNER, new Item.Properties().stacksTo(64).tab(CreativeModeTab.TAB_MISC)); + public static final Item MATTER_PANEL = new BlockItem(Blocks.MATTER_PANEL, new Item.Properties().stacksTo(64).tab(CreativeModeTab.TAB_MISC)); + public static final Item MATTER_REPLICATOR = new BlockItem(Blocks.MATTER_REPLICATOR, new Item.Properties().stacksTo(64).tab(CreativeModeTab.TAB_MISC)); public static final ItemPill PILL_ANDROID = new ItemPill(ItemPill.PillType.BECOME_ANDROID); public static final ItemPill PILL_HUMANE = new ItemPill(ItemPill.PillType.BECOME_HUMANE); @@ -122,6 +132,8 @@ public class Registry { MATTER_CABLE.setRegistryName(Names.MATTER_CABLE); PATTERN_STORAGE.setRegistryName(Names.PATTERN_STORAGE); MATTER_SCANNER.setRegistryName(Names.MATTER_SCANNER); + MATTER_PANEL.setRegistryName(Names.MATTER_PANEL); + MATTER_REPLICATOR.setRegistryName(Names.MATTER_REPLICATOR); PILL_ANDROID.setRegistryName(Names.PILL_ANDROID); PILL_HUMANE.setRegistryName(Names.PILL_HUMANE); @@ -146,6 +158,8 @@ public class Registry { event.getRegistry().register(MATTER_CABLE); event.getRegistry().register(PATTERN_STORAGE); event.getRegistry().register(MATTER_SCANNER); + event.getRegistry().register(MATTER_PANEL); + event.getRegistry().register(MATTER_REPLICATOR); event.getRegistry().register(PILL_ANDROID); event.getRegistry().register(PILL_HUMANE); @@ -173,6 +187,8 @@ public class Registry { public static final BlockEntityType MATTER_CABLE = BlockEntityType.Builder.of(BlockEntityMatterCable::new, Blocks.MATTER_CABLE).build(null); public static final BlockEntityType PATTERN_STORAGE = BlockEntityType.Builder.of(BlockEntityPatternStorage::new, Blocks.PATTERN_STORAGE).build(null); public static final BlockEntityType MATTER_SCANNER = BlockEntityType.Builder.of(BlockEntityMatterScanner::new, Blocks.MATTER_SCANNER).build(null); + public static final BlockEntityType MATTER_PANEL = BlockEntityType.Builder.of(BlockEntityMatterPanel::new, Blocks.MATTER_PANEL).build(null); + public static final BlockEntityType MATTER_REPLICATOR = BlockEntityType.Builder.of(BlockEntityMatterReplicator::new, Blocks.MATTER_REPLICATOR).build(null); static { ANDROID_STATION.setRegistryName(Names.ANDROID_STATION); @@ -182,6 +198,8 @@ public class Registry { MATTER_CABLE.setRegistryName(Names.MATTER_CABLE); PATTERN_STORAGE.setRegistryName(Names.PATTERN_STORAGE); MATTER_SCANNER.setRegistryName(Names.MATTER_SCANNER); + MATTER_PANEL.setRegistryName(Names.MATTER_PANEL); + MATTER_REPLICATOR.setRegistryName(Names.MATTER_REPLICATOR); } public static void register(final RegistryEvent.Register> event) { @@ -192,6 +210,8 @@ public class Registry { event.getRegistry().register(MATTER_CABLE); event.getRegistry().register(PATTERN_STORAGE); event.getRegistry().register(MATTER_SCANNER); + event.getRegistry().register(MATTER_PANEL); + event.getRegistry().register(MATTER_REPLICATOR); // OverdriveThatMatters.LOGGER.info("Registered block entities"); } @@ -204,6 +224,8 @@ public class Registry { public static final MenuType MATTER_CAPACITOR_BANK = new MenuType<>(MatterCapacitorBankMenu::new); public static final MenuType PATTERN_STORAGE = new MenuType<>(PatternStorageMenu::new); public static final MenuType MATTER_SCANNER = new MenuType<>(MatterScannerMenu::new); + public static final MenuType MATTER_PANEL = new MenuType<>(MatterPanelMenu::new); + public static final MenuType MATTER_REPLICATOR = new MenuType<>(MatterReplicatorMenu::new); static { ANDROID_STATION.setRegistryName(Names.ANDROID_STATION); @@ -212,6 +234,8 @@ public class Registry { MATTER_CAPACITOR_BANK.setRegistryName(Names.MATTER_CAPACITOR_BANK); PATTERN_STORAGE.setRegistryName(Names.PATTERN_STORAGE); MATTER_SCANNER.setRegistryName(Names.MATTER_SCANNER); + MATTER_PANEL.setRegistryName(Names.MATTER_PANEL); + MATTER_REPLICATOR.setRegistryName(Names.MATTER_REPLICATOR); } public static void register(final RegistryEvent.Register> event) { @@ -221,6 +245,8 @@ public class Registry { event.getRegistry().register(MATTER_CAPACITOR_BANK); event.getRegistry().register(PATTERN_STORAGE); event.getRegistry().register(MATTER_SCANNER); + event.getRegistry().register(MATTER_PANEL); + event.getRegistry().register(MATTER_REPLICATOR); // OverdriveThatMatters.LOGGER.info("Registered menus"); } @@ -232,6 +258,8 @@ public class Registry { MenuScreens.register(MATTER_CAPACITOR_BANK, MatterCapacitorBankScreen::new); MenuScreens.register(PATTERN_STORAGE, PatternStorageScreen::new); MenuScreens.register(MATTER_SCANNER, MatterScannerScreen::new); + MenuScreens.register(MATTER_PANEL, MatterPanelScreen::new); + MenuScreens.register(MATTER_REPLICATOR, MatterReplicatorScreen::new); // OverdriveThatMatters.LOGGER.info("Registered screens"); } diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/BlockMatterPanel.java b/src/main/java/ru/dbotthepony/mc/otm/block/BlockMatterPanel.java new file mode 100644 index 000000000..f2ed74eb3 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/block/BlockMatterPanel.java @@ -0,0 +1,115 @@ +package ru.dbotthepony.mc.otm.block; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import ru.dbotthepony.mc.otm.block.entity.BlockEntityMatterPanel; + +import javax.annotation.Nullable; + +public class BlockMatterPanel extends BlockMattery implements EntityBlock { + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return new BlockEntityMatterPanel(blockPos, blockState); + } + + public static final EnumProperty FACING = EnumProperty.create( + "facing", + Direction.class); + + private ImmutableMap SHAPES; + + public BlockMatterPanel() { + super(); + + registerDefaultState(this.getStateDefinition().any().setValue(FACING, Direction.SOUTH)); + + SHAPES = getShapeForEachState(blockstate -> { + return switch (blockstate.getValue(FACING)) { + case NORTH -> Shapes.box( + 0.0d, + 0.0d, + 0.625d, + + 1.0d, + 1.0d, + 1.0d + ); + case EAST -> Shapes.box( + 0.0d, + 0.0d, + 0.0d, + + 0.375d, + 1.0d, + 1.0d + ); + case WEST -> Shapes.box( + 0.625d, + 0.0d, + 0.0d, + + 1.0d, + 1.0d, + 1.0d + ); + case DOWN -> Shapes.box( + 0.0d, + 0.625d, + 0.0d, + + 1.0d, + 1.0d, + 1.0d + ); + case UP -> Shapes.box( + 0.0d, + 0.0d, + 0.0d, + + 1.0d, + 0.375d, + 1.0d + ); + default -> Shapes.box( + 0.0d, + 0.0d, + 0.0d, + + 1.0d, + 1.0d, + 0.375d + ); + }; + }); + } + + @Override + public VoxelShape getShape(BlockState p_60555_, BlockGetter p_60556_, BlockPos p_60557_, CollisionContext p_60558_) { + return SHAPES.get(p_60555_); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(FACING); + } + + @Nullable + @Override + public BlockState getStateForPlacement(BlockPlaceContext context) { + return this.defaultBlockState().setValue(FACING, context.getClickedFace()); + } + +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/BlockMatterReplicator.java b/src/main/java/ru/dbotthepony/mc/otm/block/BlockMatterReplicator.java new file mode 100644 index 000000000..f4f0c20f1 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/block/BlockMatterReplicator.java @@ -0,0 +1,28 @@ +package ru.dbotthepony.mc.otm.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import ru.dbotthepony.mc.otm.Registry; +import ru.dbotthepony.mc.otm.block.entity.BlockEntityMatterReplicator; +import ru.dbotthepony.mc.otm.block.entity.BlockEntityMatteryPoweredWorker; + +import javax.annotation.Nullable; + +public class BlockMatterReplicator extends BlockMatteryRotatable implements EntityBlock { + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) { + return new BlockEntityMatterReplicator(blockPos, blockState); + } + + @Nullable + @Override + public BlockEntityTicker getTicker(Level p_153212_, BlockState p_153213_, BlockEntityType p_153214_) { + return p_153212_.isClientSide || p_153214_ != Registry.BlockEntities.MATTER_REPLICATOR ? null : BlockEntityMatteryPoweredWorker::basicTicker; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterDecomposer.java b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterDecomposer.java index 86c2cdcde..0fac88ffc 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterDecomposer.java +++ b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterDecomposer.java @@ -115,10 +115,10 @@ public class BlockEntityMatterDecomposer extends BlockEntityMatteryPoweredWorker @Nonnull @Override - protected MachineJobFinish onJobFinish(MachineJob job) { + protected MachineJobStatus onJobFinish(MachineJob job) { BigDecimal matter_value = MatterRegistry.getMatterValue(job.stack()); matter.receiveMatterInner(matter_value, false); - return new MachineJobFinish(); + return new MachineJobStatus(); } @Nullable @@ -133,7 +133,7 @@ public class BlockEntityMatterDecomposer extends BlockEntityMatteryPoweredWorker if (!matter_value.equals(BigDecimal.ZERO) && matter.canReceiveAll(matter_value)) { stack.shrink(1); - return new MachineJob(copy, matter_value.doubleValue() * 35000d); + return new MachineJob(copy, matter_value.doubleValue() * 12_500d); } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterPanel.java b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterPanel.java new file mode 100644 index 000000000..9bb998965 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterPanel.java @@ -0,0 +1,199 @@ +package ru.dbotthepony.mc.otm.block.entity; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +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.Item; +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.IMatterGridCell; +import ru.dbotthepony.mc.otm.capability.IMatterTaskProvider; +import ru.dbotthepony.mc.otm.capability.MatterTask; +import ru.dbotthepony.mc.otm.capability.MatteryCapability; +import ru.dbotthepony.mc.otm.matter.MatterGrid; +import ru.dbotthepony.mc.otm.menu.MatterPanelMenu; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.stream.Collectors; + +public class BlockEntityMatterPanel extends BlockEntityMattery implements IMatterGridCell, IMatterTaskProvider { + public BlockEntityMatterPanel(BlockPos p_155229_, BlockState p_155230_) { + super(Registry.BlockEntities.MATTER_PANEL, p_155229_, p_155230_); + } + + private static final TranslatableComponent NAME = new TranslatableComponent("block.overdrive_that_matters.matter_panel"); + + @Override + protected Component getDefaultDisplayName() { + return NAME; + } + + @Nullable + @Override + public AbstractContainerMenu createMenu(int containerID, Inventory inventory, Player ply) { + return new MatterPanelMenu(containerID, inventory, this); + } + + private boolean valid = true; + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + valid = false; + } + + @Override + public void reviveCaps() { + super.reviveCaps(); + valid = true; + } + + private final LazyOptional resolver = LazyOptional.of(() -> this); + + @Nonnull + @Override + public LazyOptional getCapability(@Nonnull Capability cap, @Nullable Direction side) { + if (valid && (cap == MatteryCapability.MATTER_CELL || cap == MatteryCapability.TASK)) + return resolver.cast(); + + return super.getCapability(cap, side); + } + + private MatterGrid grid; + + @Override + public void setLevel(Level p_155231_) { + super.setLevel(p_155231_); + + if (grid == null) + scheduleDiscoverNeighbours(getBlockPos(), p_155231_); + } + + @Nullable + @Override + public MatterGrid getMatterGrid() { + return grid; + } + + @Override + public boolean isValidMatterCell() { + return valid; + } + + @Override + public void setMatterGrid(MatterGrid grid) { + this.grid = grid; + } + + @Nullable + @Override + public IMatterTaskProvider getTaskProvider() { + return this; + } + + private final HashMap tasks = new HashMap<>(); + + @Nonnull + @Override + public Collection getTasks() { + return tasks.values().stream().filter(task -> task.required() > 0).collect(Collectors.toList()); + } + + @Nonnull + @Override + public Collection getAllTasks() { + return List.copyOf(tasks.values()); + } + + @Nullable + @Override + public MatterTask allocateTask(boolean simulate) { + for (var entry : tasks.entrySet()) { + if (entry.getValue().required() > 0) { + if (!simulate) { + tasks.put(entry.getKey(), entry.getValue().shrinkRequired(1)); + } + + return entry.getValue(); + } + } + + return null; + } + + @Override + public void notifyTaskCompletion(MatterTask task) { + var get_task = tasks.get(task.id()); + + if (get_task == null) { + return; + } + + get_task = get_task.shrinkInProgress(1); + + if (get_task.required() == 0 && get_task.in_progress() == 0) { + tasks.remove(task.id()); + } else { + tasks.put(task.id(), get_task); + } + } + + @Override + public CompoundTag save(CompoundTag nbt) { + ListTag list = new ListTag(); + + for (MatterTask task : tasks.values()) { + list.add(task.serializeNBT()); + } + + nbt.put("tasks", list); + + return super.save(nbt); + } + + @Override + public void load(CompoundTag nbt) { + super.load(nbt); + tasks.clear(); + + ListTag list = nbt.getList("tasks", Tag.TAG_COMPOUND); + + for (Tag tag : list) { + MatterTask task = MatterTask.deserializeNBT(tag); + + if (task != null) { + tasks.put(task.id(), task); + } + } + } + + @Nullable + @Override + public MatterTask getTask(UUID id) { + return tasks.get(id); + } + + public MatterTask addTask(Item item, int how_much) { + var task = new MatterTask(UUID.randomUUID(), item, 0, 0, how_much); + tasks.put(task.id(), task); + return task; + } + + @Override + public void dropAllTasks() { + tasks.clear(); + } +} 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 new file mode 100644 index 000000000..be97b963a --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterReplicator.java @@ -0,0 +1,241 @@ +package ru.dbotthepony.mc.otm.block.entity; + +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.state.BlockState; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.CapabilityItemHandler; +import net.minecraftforge.items.IItemHandler; +import ru.dbotthepony.mc.otm.Registry; +import ru.dbotthepony.mc.otm.capability.*; +import ru.dbotthepony.mc.otm.container.MatteryContainer; +import ru.dbotthepony.mc.otm.matter.MatterGrid; +import ru.dbotthepony.mc.otm.matter.MatterRegistry; +import ru.dbotthepony.mc.otm.menu.MatterReplicatorMenu; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.math.BigDecimal; + +public class BlockEntityMatterReplicator extends BlockEntityMatteryPoweredWorker implements IMatterGridCell { + public BlockEntityMatterReplicator(BlockPos p_155229_, BlockState p_155230_) { + super(Registry.BlockEntities.MATTER_REPLICATOR, p_155229_, p_155230_); + energy = new MatteryMachineEnergyStorage(this, MatteryMachineEnergyStorage.MachineType.WORKER, new BigDecimal(200_000), new BigDecimal(4000), new BigDecimal(4000)); + } + + public final MatterHandlerCapability matter = new MatterHandlerCapability(this::setChanged, IMatterHandler.MatterDirection.RECEIVE, new BigDecimal(2)); + + // обычные запросы + public final MatteryContainer regular_slots = new MatteryContainer(this::setChanged, 3); + + // запросы от matter replicator interface + public final MatteryContainer reserved_slots = new MatteryContainer(this::setChanged, 3); + + private final LazyOptional resolve_handler = LazyOptional.of(() -> regular_slots.handler( + (slot, stack) -> false + )); + + private static final TranslatableComponent NAME = new TranslatableComponent("block.overdrive_that_matters.matter_replicator"); + + private static final BigDecimal BASE_CONSUMPTION = new BigDecimal(400); + + @Override + protected Component getDefaultDisplayName() { + return NAME; + } + + @Nullable + @Override + public AbstractContainerMenu createMenu(int containerID, Inventory inventory, Player ply) { + return new MatterReplicatorMenu(containerID, inventory, this); + } + + @Nonnull + @Override + protected BigDecimal getBaseConsumption() { + return BASE_CONSUMPTION; + } + + @Nonnull + @Override + protected MachineJobStatus onJobFinish(MachineJob job) { + if (!regular_slots.addItem(job.stack()).isEmpty()) { + if (grid != null) { + grid.notifyTaskCompletion(MatterTask.deserializeNBT(job.data().get("task"))); + } + + return new MachineJobStatus(false); + } + + return new MachineJobStatus(); + } + + @Override + public void notifyTasksChanged() { + is_idling = false; + } + + private static final double TICKS_PER_MTU = 20_000d; + private static final BigDecimal TICKS_PER_MTU_BD = new BigDecimal(20_000); + private static final double MTU_PER_TICK = 1d / 20_000d; + private static final BigDecimal MTU_PER_TICK_BD = BigDecimal.ONE.divide(TICKS_PER_MTU_BD, MatteryCapability.ROUND_RULES); + + @Override + public void setLevel(Level p_155231_) { + super.setLevel(p_155231_); + + if (grid == null) + scheduleDiscoverNeighbours(getBlockPos(), p_155231_); + } + + @Nullable + @Override + protected MachineJob getNextJob() { + if (grid == null) + return null; + + MatterTask task = grid.allocateTask(false); + + if (task == null) + return null; + + ItemStack stack = task.stack(1); + + // ???????? + if (!MatterRegistry.hasMatterValue(stack)) + return null; + + MachineJob job = new MachineJob(stack, MatterRegistry.getMatterValue(stack).doubleValue() * TICKS_PER_MTU); + + job.data().put("task", task.serializeNBT()); + + return job; + } + + private static final BigDecimal DRAIN_MULT = new BigDecimal(200); + + @Override + protected MachineJobStatus onWorkTick(WorkTickContext context) { + BigDecimal drain_per_tick = MatterRegistry.getMatterValue(context.job().stack().getItem()).multiply(MTU_PER_TICK_BD, MatteryCapability.ROUND_RULES).multiply(context.work_speed(), MatteryCapability.ROUND_RULES); + + if (matter.extractMatterInner(drain_per_tick, true).compareTo(drain_per_tick) < 0) { + // в машине недостаточно материи + if (grid == null) + return new MachineJobStatus(false, 20); + + if (drain_per_tick.compareTo(matter.getMaxStoredMatter()) > 0) { + // в тик требуется больше материи, чем её может хранить репликатор + BigDecimal to_extract = drain_per_tick.subtract(matter.extractMatterInner(drain_per_tick, true)); + BigDecimal drain = grid.extractMatter(to_extract, true); + + if (drain.compareTo(to_extract) < 0) { + // недостаточно материи в сети + return new MachineJobStatus(false, 200); + } + + // достаточно материи в сети + внутри машины + matter.extractMatterInner(drain_per_tick, false); + grid.extractMatter(drain, false); + + return new MachineJobStatus(); + } else { + // в тик требуется меньше материи, чем её может хранить репликатор + // примем из сети недостающее количество бака материи, или 200 тиков репликации, что меньше + BigDecimal to_extract = matter.getMissingMatter().min(drain_per_tick.multiply(DRAIN_MULT)); + BigDecimal drain = grid.extractMatter(to_extract, true); + + if (drain.compareTo(BigDecimal.ZERO) == 0) { + // в сети нет материи + return new MachineJobStatus(false, 200); + } + + BigDecimal received = matter.receiveMatterOuter(drain, false); + grid.extractMatter(received, false); + + // получили материю, проверяем возможность работы + if (matter.extractMatterInner(drain_per_tick, false).compareTo(drain_per_tick) >= 0) { + return new MachineJobStatus(); + } else { + // :( + return new MachineJobStatus(false, 200); + } + } + } + + // в машине достаточно материи + + matter.extractMatterInner(drain_per_tick, false); + return new MachineJobStatus(); + } + + @Override + public CompoundTag save(CompoundTag nbt) { + nbt.put("regular_slots", regular_slots.serializeNBT()); + nbt.put("reserved_slots", reserved_slots.serializeNBT()); + return super.save(nbt); + } + + @Override + public void load(CompoundTag nbt) { + super.load(nbt); + regular_slots.deserializeNBT(nbt.get("regular_slots")); + reserved_slots.deserializeNBT(nbt.get("reserved_slots")); + } + + private boolean valid = true; + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + valid = false; + } + + @Override + public void reviveCaps() { + super.reviveCaps(); + valid = true; + } + + private final LazyOptional resolver_self = LazyOptional.of(() -> this); + + @Nonnull + @Override + public LazyOptional getCapability(@Nonnull Capability cap, @Nullable Direction side) { + if (valid) { + if (cap == MatteryCapability.MATTER_CELL) + return resolver_self.cast(); + + if (cap == CapabilityItemHandler.ITEM_HANDLER_CAPABILITY) + return resolve_handler.cast(); + } + + return super.getCapability(cap, side); + } + + private MatterGrid grid; + + @Nullable + @Override + public MatterGrid getMatterGrid() { + return grid; + } + + @Override + public boolean isValidMatterCell() { + return valid; + } + + @Override + public void setMatterGrid(MatterGrid grid) { + this.grid = grid; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterScanner.java b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterScanner.java index c62d556f3..b9187249e 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterScanner.java +++ b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatterScanner.java @@ -115,14 +115,14 @@ public class BlockEntityMatterScanner extends BlockEntityMatteryPoweredWorker im @Nonnull @Override - protected MachineJobFinish onJobFinish(MachineJob job) { + protected MachineJobStatus onJobFinish(MachineJob job) { if (grid == null) - return new MachineJobFinish(false, 100); + return new MachineJobStatus(false, 100); ItemStack stack = job.stack(); if (stack.isEmpty() || !MatterRegistry.hasMatterValue(stack)) - return new MachineJobFinish(); + return new MachineJobStatus(); Collection get_state = grid.findPattern(stack.getItem()); @@ -148,10 +148,10 @@ public class BlockEntityMatterScanner extends BlockEntityMatteryPoweredWorker im } if (grid.insertPattern(new_state, false, false)) { - return new MachineJobFinish(); + return new MachineJobStatus(); } - return new MachineJobFinish(false, 200); + return new MachineJobStatus(false, 200); } @Nullable @@ -189,7 +189,7 @@ public class BlockEntityMatterScanner extends BlockEntityMatteryPoweredWorker im copy.setCount(1); stack.shrink(1); input_slot.setChanged(); - return new MachineJob(copy, MatterRegistry.getMatterValue(copy).doubleValue() * 80000d); + return new MachineJob(copy, MatterRegistry.getMatterValue(copy).doubleValue() * 35_000d); } return null; diff --git a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatteryPoweredWorker.java b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatteryPoweredWorker.java index 3b546a21a..7eec020f7 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatteryPoweredWorker.java +++ b/src/main/java/ru/dbotthepony/mc/otm/block/entity/BlockEntityMatteryPoweredWorker.java @@ -24,16 +24,16 @@ abstract public class BlockEntityMatteryPoweredWorker extends BlockEntityMattery @Nonnull protected abstract BigDecimal getBaseConsumption(); - public record WorkTickContext(MachineJob job, BigDecimal required_power, BigDecimal extracted_power, double work_speed) { + public record WorkTickContext(MachineJob job, BigDecimal required_power, BigDecimal extracted_power, BigDecimal work_speed) { } - public record MachineJobFinish(boolean finished, int throttle) { - public MachineJobFinish() { + public record MachineJobStatus(boolean valid, int throttle) { + public MachineJobStatus() { this(true, 0); } - public MachineJobFinish(boolean finished) { + public MachineJobStatus(boolean finished) { this(finished, 0); } } @@ -90,7 +90,7 @@ abstract public class BlockEntityMatteryPoweredWorker extends BlockEntityMattery } protected double work_ticks = 0; - protected int throttle_job_finish = 0; + protected int throttle_ticks = 0; protected MachineJob current_job; // if is_idling is true, then workerLoop() does nothing @@ -101,8 +101,8 @@ abstract public class BlockEntityMatteryPoweredWorker extends BlockEntityMattery return current_job; } - public boolean cantFinishJob() { - return throttle_job_finish > 0; + public boolean cantProcessJob() { + return throttle_ticks > 0; } @Override @@ -141,23 +141,23 @@ abstract public class BlockEntityMatteryPoweredWorker extends BlockEntityMattery * waiting on conditions to be met */ @Nonnull - abstract protected MachineJobFinish onJobFinish(MachineJob job); + abstract protected MachineJobStatus onJobFinish(MachineJob job); /** * @param context context for current job * @return whenever machine can perform it */ - protected boolean onWorkTick(WorkTickContext context) { - return true; + protected MachineJobStatus onWorkTick(WorkTickContext context) { + return new MachineJobStatus(); } protected void workerLoop() { - if (is_idling) { + if (throttle_ticks > 0) { + throttle_ticks--; return; } - if (throttle_job_finish > 0) { - throttle_job_finish--; + if (is_idling) { return; } @@ -180,12 +180,16 @@ abstract public class BlockEntityMatteryPoweredWorker extends BlockEntityMattery BigDecimal required_power = consumptionPerWork().multiply(current_job.power_consumption_multiplier); BigDecimal extracted_power = energy.extractEnergyInner(required_power, true); - double work_speed = extracted_power.divide(required_power, MatteryCapability.ROUND_RULES).doubleValue(); + BigDecimal work_speed = extracted_power.divide(required_power, MatteryCapability.ROUND_RULES); - if (!onWorkTick(new WorkTickContext(current_job, required_power, extracted_power, work_speed))) + MachineJobStatus status = onWorkTick(new WorkTickContext(current_job, required_power, extracted_power, work_speed)); + + if (!status.valid) { + throttle_ticks += status.throttle; return; + } - double new_work_ticks = work_speed + work_ticks; + double new_work_ticks = work_speed.doubleValue() + work_ticks; if (new_work_ticks > current_job.ticks_processing_time) { work_ticks = current_job.ticks_processing_time; @@ -195,41 +199,46 @@ abstract public class BlockEntityMatteryPoweredWorker extends BlockEntityMattery energy.extractEnergyInner(extracted_power, false); if (work_ticks >= current_job.ticks_processing_time) { - MachineJobFinish status = onJobFinish(current_job); + MachineJobStatus finish = onJobFinish(current_job); - if (status.finished) { + if (finish.valid) { current_job = null; work_ticks = 0d; } else { - throttle_job_finish += status.throttle; + throttle_ticks += finish.throttle; } } } } else { - if (!onWorkTick(new WorkTickContext(current_job, BigDecimal.ZERO, BigDecimal.ZERO, 1d))) + + MachineJobStatus status = onWorkTick(new WorkTickContext(current_job, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ONE)); + + if (!status.valid) { + throttle_ticks += status.throttle; return; + } work_ticks += 1d; if (work_ticks >= current_job.ticks_processing_time) { - MachineJobFinish status = onJobFinish(current_job); + MachineJobStatus finish = onJobFinish(current_job); - if (status.finished) { + if (finish.valid) { current_job = null; work_ticks = 0d; } else { - throttle_job_finish += status.throttle; + throttle_ticks += finish.throttle; } } } } else { - MachineJobFinish status = onJobFinish(current_job); + MachineJobStatus finish = onJobFinish(current_job); - if (status.finished) { + if (finish.valid) { current_job = null; work_ticks = 0d; } else { - throttle_job_finish += status.throttle; + throttle_ticks += finish.throttle; } } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/IMatterGridCell.java b/src/main/java/ru/dbotthepony/mc/otm/capability/IMatterGridCell.java index d978e902f..1b511ef2a 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/IMatterGridCell.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/IMatterGridCell.java @@ -24,6 +24,11 @@ public interface IMatterGridCell { return null; } + @Nullable + default IMatterTaskProvider getTaskProvider() { + return null; + } + boolean isValidMatterCell(); void setMatterGrid(MatterGrid grid); @@ -32,6 +37,10 @@ public interface IMatterGridCell { } + default void notifyTasksChanged() { + + } + default void scheduleDiscoverNeighbours(BlockPos pos, Level level) { MatterGrid.scheduleDiscoverNeighbours(this, pos, level); } diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/IMatterTaskProvider.java b/src/main/java/ru/dbotthepony/mc/otm/capability/IMatterTaskProvider.java new file mode 100644 index 000000000..51c2939fa --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/IMatterTaskProvider.java @@ -0,0 +1,52 @@ +package ru.dbotthepony.mc.otm.capability; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.UUID; + +public interface IMatterTaskProvider { + /** + * @return immutable collection of tasks that can be allocated by a worker + */ + @Nonnull + Collection getTasks(); + + /** + * @return immutable collection of all stored tasks, even fully allocated by workers + */ + @Nonnull + Collection getAllTasks(); + + /** + * Allocates (marks as work-in-progress) a task + * by incrementing it's in_progress by 1 + * and shrinking required by 1 + * + * If required == 0, it should not be returned by this method + * @param simulate whenever to change internal state + * @return MatterTask that should be performed, or null of no tasks available + */ + @Nullable + MatterTask allocateTask(boolean simulate); + + /** + * Notify about task completion. If this provider indeed contain this task, it should + * shrink in_progress by 1 + * If in_progress == 0 and required == 0, it should discard the task + * @param task task being completed. this method should ignore tasks that are not owned by it. + */ + void notifyTaskCompletion(MatterTask task); + + /** + * @param id uuid of task + * @return MatterTask that this capability holds with this id, or null + */ + @Nullable + MatterTask getTask(UUID id); + + /** + * Destroys all tasks this capability contains + */ + void dropAllTasks(); +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/IPatternStorage.java b/src/main/java/ru/dbotthepony/mc/otm/capability/IPatternStorage.java index 6a37567f9..b6a46a986 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/IPatternStorage.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/IPatternStorage.java @@ -1,8 +1,12 @@ package ru.dbotthepony.mc.otm.capability; +import net.minecraft.network.FriendlyByteBuf; import net.minecraft.world.item.Item; +import net.minecraftforge.registries.ForgeRegistry; +import net.minecraftforge.registries.RegistryManager; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.Collection; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -17,17 +21,38 @@ public interface IPatternStorage { public boolean equals(PatternState state) { return state.item == this.item && state.research_percent == this.research_percent; } + + public void write(FriendlyByteBuf buffer) { + buffer.writeInt(((ForgeRegistry) RegistryManager.ACTIVE.getRegistry(Item.class)).getID(item)); + buffer.writeDouble(research_percent); + } + + @Nullable + public static PatternState read(FriendlyByteBuf buffer) { + int item = buffer.readInt(); + double percent = buffer.readDouble(); + + Item get_item = ((ForgeRegistry) RegistryManager.ACTIVE.getRegistry(Item.class)).getValue(item); + + if (get_item == null) + return null; + + return new PatternState(get_item, percent); + } } /** * @return unmodifiable collection of stored patterns */ + @Nonnull Collection getStoredPatterns(); + @Nonnull default Collection findPattern(Item item) { return findPattern((item2) -> item.equals(item2.item)); } + @Nonnull default Collection findPattern(Predicate predicate) { return getStoredPatterns().stream().filter(predicate).collect(Collectors.toList()); } diff --git a/src/main/java/ru/dbotthepony/mc/otm/capability/MatterTask.java b/src/main/java/ru/dbotthepony/mc/otm/capability/MatterTask.java new file mode 100644 index 000000000..4dca37f80 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/MatterTask.java @@ -0,0 +1,77 @@ +package ru.dbotthepony.mc.otm.capability; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.common.util.INBTSerializable; +import net.minecraftforge.registries.RegistryManager; + +import javax.annotation.Nullable; +import java.util.Objects; +import java.util.UUID; + +public record MatterTask(UUID id, Item item, int in_progress, int finished, int required) { + public MatterTask(UUID id, Item item, int in_progress, int finished, int required) { + this.id = id; + this.item = item; + this.in_progress = Math.max(0, in_progress); + this.finished = Math.max(0, finished); + this.required = Math.max(0, required); + } + + public ItemStack stack() { + return new ItemStack(item, 1); + } + + public ItemStack stack(int amount) { + return new ItemStack(item, amount); + } + + public int total() { + return in_progress + finished + required; + } + + public MatterTask shrinkRequired(int amount) { + return new MatterTask(id, item, in_progress + amount, finished, required - amount); + } + + public MatterTask shrinkInProgress(int amount) { + return new MatterTask(id, item, in_progress - amount, finished + amount, required); + } + + public CompoundTag serializeNBT() { + CompoundTag tag = new CompoundTag(); + + tag.putLong("id_l", id.getLeastSignificantBits()); + tag.putLong("id_u", id.getMostSignificantBits()); + tag.putString("item", Objects.requireNonNull(item.getRegistryName()).toString()); + tag.putInt("in_progress", in_progress); + tag.putInt("finished", finished); + tag.putInt("required", required); + + return tag; + } + + @Nullable + public static MatterTask deserializeNBT(@Nullable Tag nbt) { + if (nbt == null) + return null; + + if (nbt instanceof CompoundTag tag) { + Item get_item = RegistryManager.ACTIVE.getRegistry(Item.class).getValue(new ResourceLocation(tag.getString("item"))); + + if (get_item != null) + return new MatterTask( + new UUID(tag.getLong("id_u"), tag.getLong("id_l")), + get_item, + tag.getInt("in_progress"), + tag.getInt("finished"), + tag.getInt("required") + ); + } + + return null; + } +} 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 f39b22d78..620ea8f73 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java +++ b/src/main/java/ru/dbotthepony/mc/otm/capability/MatteryCapability.java @@ -25,12 +25,16 @@ public class MatteryCapability { @CapabilityInject(IPatternStorage.class) public static Capability PATTERN = null; + @CapabilityInject(IMatterTaskProvider.class) + public static Capability TASK = null; + public static void register() { CapabilityManager.INSTANCE.register(IAndroidCapability.class); CapabilityManager.INSTANCE.register(IMatteryEnergyStorage.class); CapabilityManager.INSTANCE.register(IMatterHandler.class); CapabilityManager.INSTANCE.register(IPatternStorage.class); CapabilityManager.INSTANCE.register(IMatterGridCell.class); + CapabilityManager.INSTANCE.register(IMatterTaskProvider.class); } public static final MathContext ROUND_RULES = new MathContext(32, RoundingMode.HALF_DOWN); 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 26b064d61..30c187a7e 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/matter/MatterGrid.java +++ b/src/main/java/ru/dbotthepony/mc/otm/matter/MatterGrid.java @@ -8,9 +8,7 @@ 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.capability.IMatterGridCell; -import ru.dbotthepony.mc.otm.capability.IMatterHandler; -import ru.dbotthepony.mc.otm.capability.IPatternStorage; +import ru.dbotthepony.mc.otm.capability.*; import javax.annotation.Nullable; import java.math.BigDecimal; @@ -283,6 +281,50 @@ public class MatterGrid { return null; } + public boolean hasPatternState(IPatternStorage.PatternState state) { + for (IMatterGridCell cell : cells) { + IPatternStorage storage = cell.getPatternStorage(); + + if (storage != null) { + if (storage.hasExactPattern(state)) { + return true; + } + } + } + + return false; + } + + @Nullable + public MatterTask allocateTask(boolean simulate) { + for (IMatterGridCell cell : cells) { + IMatterTaskProvider tasks = cell.getTaskProvider(); + + if (tasks != null) { + MatterTask allocated = tasks.allocateTask(simulate); + + if (allocated != null) { + return allocated; + } + } + } + + return null; + } + + @Nullable + public MatterTask notifyTaskCompletion(MatterTask task) { + for (IMatterGridCell cell : cells) { + IMatterTaskProvider tasks = cell.getTaskProvider(); + + if (tasks != null) { + tasks.notifyTaskCompletion(task); + } + } + + return null; + } + public void notifyPatternsChanged() { for (IMatterGridCell cell : cells) cell.notifyPatternsChanged(); @@ -305,6 +347,9 @@ public class MatterGrid { cells.remove(entity); entity.setMatterGrid(null); + if (cells.size() == 0) + NETWORKS.remove(this); + // OverdriveThatMatters.LOGGER.debug("Untracking {} in {}. Tracking {} in total", entity, this, entities.size()); } diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/MatterDecomposerMenu.java b/src/main/java/ru/dbotthepony/mc/otm/menu/MatterDecomposerMenu.java index 3f5c93572..0795ce7ac 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/MatterDecomposerMenu.java +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/MatterDecomposerMenu.java @@ -43,7 +43,7 @@ public class MatterDecomposerMenu extends PoweredMatteryMenu { progress_widget = new ProgressGaugeWidget<>(this, 61 + 18 + 3, 36); } else { matter_widget = new MatterLevelWidget<>(this, 22, 14, tile.getCapability(MatteryCapability.MATTER).resolve().get()); - progress_widget = new ProgressGaugeWidget<>(this, 61 + 18 + 3, 36, () -> (float) tile.getWorkProgress(), tile::cantFinishJob); + progress_widget = new ProgressGaugeWidget<>(this, 61 + 18 + 3, 36, () -> (float) tile.getWorkProgress(), tile::cantProcessJob); } addBatterySlot(14); diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/MatterPanelMenu.java b/src/main/java/ru/dbotthepony/mc/otm/menu/MatterPanelMenu.java new file mode 100644 index 000000000..e71879bf2 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/MatterPanelMenu.java @@ -0,0 +1,95 @@ +package ru.dbotthepony.mc.otm.menu; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraftforge.fmllegacy.network.PacketDistributor; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import ru.dbotthepony.mc.otm.Registry; +import ru.dbotthepony.mc.otm.block.entity.BlockEntityMatterPanel; +import ru.dbotthepony.mc.otm.capability.IPatternStorage; +import ru.dbotthepony.mc.otm.network.MatteryNetworking; +import ru.dbotthepony.mc.otm.network.PatternStateSendListPacket; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class MatterPanelMenu extends MatteryMenu { + public MatterPanelMenu(int p_38852_, Inventory inventory) { + this(p_38852_, inventory, null); + } + + public MatterPanelMenu(int p_38852_, Inventory inventory, BlockEntityMatterPanel tile) { + super(Registry.Menus.MATTER_PANEL, p_38852_, inventory, tile); + } + + @Override + public void broadcastFullState() { + super.broadcastFullState(); + sendPatternsToClient(); + } + + private boolean initial_send = false; + + @Override + public void broadcastChanges() { + super.broadcastChanges(); + + if (!initial_send) + sendPatternsToClient(); + } + + public ArrayList patterns = new ArrayList<>(); + public int changeset = 0; + + public void networkStates(List list) { + changeset++; + patterns.clear(); + patterns.addAll(list); + } + + public void requestReplication(ServerPlayer ply, IPatternStorage.PatternState state, int how_much) { + if (tile == null) + return; + + var grid = ((BlockEntityMatterPanel) tile).getMatterGrid(); + + if (grid == null) + return; + + if (!grid.hasPatternState(state)) { + OverdriveThatMatters.LOGGER.error("Received replication request from {} of {}, but can't find it nowhere", ply, state); + return; + } + + OverdriveThatMatters.LOGGER.debug("add task: {}", ((BlockEntityMatterPanel) tile).addTask(state.item(), how_much)); + } + + public void sendPatternsToClient() { + if (inventory.player.level.isClientSide) { + initial_send = true; + return; + } + + if (tile != null) { + var grid = ((BlockEntityMatterPanel) tile).getMatterGrid(); + + if (grid != null) { + initial_send = true; + MatteryNetworking.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) inventory.player), new PatternStateSendListPacket(containerId, grid.getStoredPatterns())); + } + } + } + + @Override + protected int getWorkingSlotStart() { + return 0; + } + + @Override + protected int getWorkingSlotEnd() { + return 0; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/MatterReplicatorMenu.java b/src/main/java/ru/dbotthepony/mc/otm/menu/MatterReplicatorMenu.java new file mode 100644 index 000000000..100024184 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/MatterReplicatorMenu.java @@ -0,0 +1,52 @@ +package ru.dbotthepony.mc.otm.menu; + +import net.minecraft.world.Container; +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.BlockEntityMatterReplicator; +import ru.dbotthepony.mc.otm.matter.MatterRegistry; +import ru.dbotthepony.mc.otm.menu.MatteryMenu; +import ru.dbotthepony.mc.otm.menu.slot.BatterySlot; +import ru.dbotthepony.mc.otm.menu.slot.MachineOutputSlot; +import ru.dbotthepony.mc.otm.menu.slot.SlotAutoRenderable; +import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget; + +import javax.annotation.Nullable; + +public class MatterReplicatorMenu extends PoweredMatteryMenu { + public MatterReplicatorMenu(int p_38852_, Inventory inventory) { + this(p_38852_, inventory, null); + } + + public MatterReplicatorMenu(int p_38852_, Inventory inventory, BlockEntityMatterReplicator tile) { + super(Registry.Menus.MATTER_REPLICATOR, p_38852_, inventory, tile); + + Container container = tile != null ? tile.regular_slots : new SimpleContainer(3); + + for (int i = 0; i < container.getContainerSize(); i++) + addSlot(new MachineOutputSlot(container, i, 64 + 18 * i, 38, true, false)); + + if (tile != null) { + new ProgressGaugeWidget<>(this, 38, 38, () -> (float) tile.getWorkProgress(), tile::cantProcessJob); + } else { + new ProgressGaugeWidget<>(this, 38, 38); + } + + addBatterySlot(); + addInventorySlots(); + } + + @Override + protected int getWorkingSlotStart() { + return 0; + } + + @Override + protected int getWorkingSlotEnd() { + return 7; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/MatterScannerMenu.java b/src/main/java/ru/dbotthepony/mc/otm/menu/MatterScannerMenu.java index b756d1e64..5d6305d68 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/MatterScannerMenu.java +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/MatterScannerMenu.java @@ -28,7 +28,7 @@ public class MatterScannerMenu extends PoweredMatteryMenu { }); if (tile != null) { - new ProgressGaugeWidget<>(this, 88, 38, () -> (float) tile.getWorkProgress(), tile::cantFinishJob); + new ProgressGaugeWidget<>(this, 88, 38, () -> (float) tile.getWorkProgress(), tile::cantProcessJob); } else { new ProgressGaugeWidget<>(this, 88, 38); } 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 6a5059f3e..f502386b8 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/MatteryMenu.java +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/MatteryMenu.java @@ -117,6 +117,13 @@ public abstract class MatteryMenu extends AbstractContainerMenu { // if TRUE, iteration order is end_slot_index -> start_slot_index // if FALSE iteration order is start_slot_index -> end_slot_index + int start = getWorkingSlotStart(); + int end = getWorkingSlotEnd(); + + if (start == end) { + return ItemStack.EMPTY; + } + ItemStack moved = ItemStack.EMPTY; Slot get_slot = this.slots.get(slot_index); @@ -130,7 +137,7 @@ public abstract class MatteryMenu extends AbstractContainerMenu { if (!moveItemStackTo(slot_item, inventory_slot_index_start, inventory_slot_index_end + 1, false)) { return ItemStack.EMPTY; } - } else if (!moveItemStackTo(slot_item, getWorkingSlotStart(), getWorkingSlotEnd(), false)) { + } else if (!moveItemStackTo(slot_item, start, end, false)) { // Moving FROM inventory TO machine return ItemStack.EMPTY; } diff --git a/src/main/java/ru/dbotthepony/mc/otm/menu/widget/AbstractWidget.java b/src/main/java/ru/dbotthepony/mc/otm/menu/widget/AbstractWidget.java index 90f3e5904..89568a971 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/menu/widget/AbstractWidget.java +++ b/src/main/java/ru/dbotthepony/mc/otm/menu/widget/AbstractWidget.java @@ -11,6 +11,14 @@ import javax.annotation.Nonnull; import java.util.concurrent.Callable; import java.util.function.Consumer; +/** + * This widget class implement *shared* code + * meaning these widgets are created inside Menu, not inside Screen + * and these widgets are supposed to move data between server to client + * + * this is why this is different from Minecraft's clientside only widgets + * @param menu type + */ public abstract class AbstractWidget { public static final ResourceLocation TEXTURE = new ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/widgets.png"); diff --git a/src/main/java/ru/dbotthepony/mc/otm/network/ClientPatternStateSendListPacketHandler.java b/src/main/java/ru/dbotthepony/mc/otm/network/ClientPatternStateSendListPacketHandler.java new file mode 100644 index 000000000..42fd6275b --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/network/ClientPatternStateSendListPacketHandler.java @@ -0,0 +1,29 @@ +package ru.dbotthepony.mc.otm.network; + +import net.minecraft.client.Minecraft; +import net.minecraftforge.fmllegacy.network.NetworkEvent; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import ru.dbotthepony.mc.otm.menu.MatterPanelMenu; + +import java.util.function.Supplier; + +public class ClientPatternStateSendListPacketHandler { + public static void handlePacket(PatternStateSendListPacket packet, Supplier context) { + if (Minecraft.getInstance().player.containerMenu == null) { + OverdriveThatMatters.LOGGER.error("Receive pattern state list for {}, but nothing is open!", packet.container_id); + return; + } + + if (Minecraft.getInstance().player.containerMenu.containerId != packet.container_id) { + OverdriveThatMatters.LOGGER.error("Receive pattern state list for {}, but {} is open! ({})", packet.container_id, Minecraft.getInstance().player.containerMenu.containerId, Minecraft.getInstance().player.containerMenu); + return; + } + + if (!(Minecraft.getInstance().player.containerMenu instanceof MatterPanelMenu menu)) { + OverdriveThatMatters.LOGGER.error("Receive pattern state list for {}, but it is wrong type!", packet.container_id); + return; + } + + menu.networkStates(packet.states); + } +} 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 d0ff337a5..cf568bff3 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java +++ b/src/main/java/ru/dbotthepony/mc/otm/network/MatteryNetworking.java @@ -29,5 +29,23 @@ public class MatteryNetworking { AndroidCapabilityChangePacket::handleMessage, Optional.of(NetworkDirection.PLAY_TO_CLIENT) ); + + CHANNEL.registerMessage( + next_network_id++, + PatternStateSendListPacket.class, + PatternStateSendListPacket::encodeNetwork, + PatternStateSendListPacket::decodeNetwork, + PatternStateSendListPacket::handleMessage, + Optional.of(NetworkDirection.PLAY_TO_CLIENT) + ); + + CHANNEL.registerMessage( + next_network_id++, + PatternReplicationRequestPacket.class, + PatternReplicationRequestPacket::encodeNetwork, + PatternReplicationRequestPacket::decodeNetwork, + PatternReplicationRequestPacket::handleMessage, + Optional.of(NetworkDirection.PLAY_TO_SERVER) + ); } } diff --git a/src/main/java/ru/dbotthepony/mc/otm/network/PatternReplicationRequestPacket.java b/src/main/java/ru/dbotthepony/mc/otm/network/PatternReplicationRequestPacket.java new file mode 100644 index 000000000..bb2819626 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/network/PatternReplicationRequestPacket.java @@ -0,0 +1,45 @@ +package ru.dbotthepony.mc.otm.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fmllegacy.network.NetworkEvent; +import ru.dbotthepony.mc.otm.capability.IPatternStorage; +import ru.dbotthepony.mc.otm.menu.MatterPanelMenu; + +import java.util.function.Supplier; + +public record PatternReplicationRequestPacket(IPatternStorage.PatternState state, int how_much) { + public PatternReplicationRequestPacket(IPatternStorage.PatternState state, int how_much) { + this.state = state; + this.how_much = Math.max(1, Math.min(99999, how_much)); + } + + public void encodeNetwork(FriendlyByteBuf buffer) { + state.write(buffer); + buffer.writeInt(how_much); + } + + public void handleMessage(Supplier context) { + context.get().setPacketHandled(true); + + context.get().enqueueWork(() -> { + if (state == null) + return; + + var ply = context.get().getSender(); + + if (ply.containerMenu == null) + return; + + if (!(ply.containerMenu instanceof MatterPanelMenu)) + return; + + ((MatterPanelMenu) ply.containerMenu).requestReplication(ply, state, how_much); + }); + } + + public static PatternReplicationRequestPacket decodeNetwork(FriendlyByteBuf buffer) { + return new PatternReplicationRequestPacket(IPatternStorage.PatternState.read(buffer), buffer.readInt()); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/network/PatternStateSendListPacket.java b/src/main/java/ru/dbotthepony/mc/otm/network/PatternStateSendListPacket.java new file mode 100644 index 000000000..ef9a43779 --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/network/PatternStateSendListPacket.java @@ -0,0 +1,62 @@ +package ru.dbotthepony.mc.otm.network; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fmllegacy.network.NetworkEvent; +import ru.dbotthepony.mc.otm.capability.IPatternStorage; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Supplier; + +public class PatternStateSendListPacket { + public PatternStateSendListPacket() { + + } + + public PatternStateSendListPacket(Collection states) { + this.states.addAll(states); + } + + public PatternStateSendListPacket(int container_id, Collection states) { + this(states); + this.container_id = container_id; + } + + public int container_id; + public ArrayList states = new ArrayList<>(); + + public void encodeNetwork(FriendlyByteBuf buffer) { + buffer.writeInt(container_id); + buffer.writeInt(states.size()); + + for (var state : states) { + state.write(buffer); + } + } + + public void handleMessage(Supplier context) { + context.get().setPacketHandled(true); + + context.get().enqueueWork(() -> { + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ClientPatternStateSendListPacketHandler.handlePacket(this, context)); + }); + } + + public static PatternStateSendListPacket decodeNetwork(FriendlyByteBuf buffer) { + var packet = new PatternStateSendListPacket(); + packet.container_id = buffer.readInt(); + int size = buffer.readInt(); + + for (int i = 0; i < size; i++) { + var state = IPatternStorage.PatternState.read(buffer); + + if (state != null) { + packet.states.add(state); + } + } + + return packet; + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/screen/MatterPanelScreen.java b/src/main/java/ru/dbotthepony/mc/otm/screen/MatterPanelScreen.java new file mode 100644 index 000000000..17103485a --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/screen/MatterPanelScreen.java @@ -0,0 +1,424 @@ +package ru.dbotthepony.mc.otm.screen; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import ru.dbotthepony.mc.otm.OverdriveThatMatters; +import ru.dbotthepony.mc.otm.capability.IPatternStorage; +import ru.dbotthepony.mc.otm.menu.MatterPanelMenu; +import ru.dbotthepony.mc.otm.network.MatteryNetworking; +import ru.dbotthepony.mc.otm.network.PatternReplicationRequestPacket; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; + +import static org.lwjgl.opengl.GL11.GL_ALWAYS; +import static org.lwjgl.opengl.GL11.GL_LESS; + +public class MatterPanelScreen extends MatteryScreen { + private static final int modal_width = 213; + private static final int modal_height = 110; + + protected static final ResourceLocation CONTAINER = new ResourceLocation(OverdriveThatMatters.MOD_ID, "textures/gui/matter_panel.png"); + + protected ResourceLocation CONTAINER_BACKGROUND() { + return CONTAINER; + } + + public MatterPanelScreen(MatterPanelMenu p_97741_, Inventory p_97742_, Component p_97743_) { + super(p_97741_, p_97742_, p_97743_); + + imageWidth = 176; + imageHeight = 187; + + titleLabelY = 5; + } + + private IPatternStorage.PatternState open_pattern; + private EditBox input_amount; + private Button inc_8; + private Button inc_64; + private Button inc_256; + + private Button dec_8; + private Button dec_64; + private Button dec_256; + + private Button send; + private Button cancel; + + private IPatternStorage.PatternState hovered_pattern; + + private double dragger_position = 0; + + @Override + protected void init() { + super.init(); + + int x = (width - modal_width) / 2; + int y = (height - modal_height) / 2; + + int top_level = 15; + int button_width = 40; + int right = 5 + button_width; + int box_width = modal_width - 5 - 5 - 30; + + inc_8 = new Button(x + modal_width - right, y + top_level, button_width, 20, new TranslatableComponent("otm.container.matter_panel.increase_by", 8), (btn) -> onChangeAmountPress(8)); + right += 2 + button_width; + + inc_64 = new Button(x + modal_width - right, y + top_level, button_width, 20, new TranslatableComponent("otm.container.matter_panel.increase_by", 64), (btn) -> onChangeAmountPress(64)); + right += 2 + button_width; + + inc_256 = new Button(x + modal_width - right, y + top_level, button_width, 20, new TranslatableComponent("otm.container.matter_panel.increase_by", 256), (btn) -> onChangeAmountPress(256)); + right = 5 + button_width; + + top_level += 24; + + input_amount = new EditBox(font, x + modal_width - 5 - box_width, y + top_level, box_width, 16, new TranslatableComponent("otm.container.matter_panel.number_input")); + input_amount.setMaxLength(6); + input_amount.setValue("1"); + input_amount.visible = false; + + top_level += 20; + + dec_8 = new Button(x + modal_width - right, y + top_level, button_width, 20, new TranslatableComponent("otm.container.matter_panel.decrease_by", 8), (btn) -> onChangeAmountPress(-8)); + right += 2 + button_width; + + dec_64 = new Button(x + modal_width - right, y + top_level, button_width, 20, new TranslatableComponent("otm.container.matter_panel.decrease_by", 64), (btn) -> onChangeAmountPress(-64)); + right += 2 + button_width; + + dec_256 = new Button(x + modal_width - right, y + top_level, button_width, 20, new TranslatableComponent("otm.container.matter_panel.decrease_by", 256), (btn) -> onChangeAmountPress(-256)); + + top_level += 24; + right = 5 + button_width; + + cancel = new Button(x + modal_width - right, y + top_level, button_width, 20, new TranslatableComponent("otm.container.matter_panel.cancel"), (btn) -> onCancel()); + right += 2 + button_width; + + send = new Button(x + modal_width - right, y + top_level, button_width, 20, new TranslatableComponent("otm.container.matter_panel.send"), (btn) -> onSend()); + + inc_8.visible = false; + inc_64.visible = false; + inc_256.visible = false; + + dec_8.visible = false; + dec_64.visible = false; + dec_256.visible = false; + + send.visible = false; + cancel.visible = false; + + addWidget(input_amount); + addWidget(inc_8); + addWidget(inc_64); + addWidget(inc_256); + addWidget(dec_8); + addWidget(dec_64); + addWidget(dec_256); + addWidget(send); + addWidget(cancel); + } + + private void onChangeAmountPress(int amount) { + int value = 1; + + try { + value = Integer.parseInt(input_amount.getValue()); + } catch (Exception ignored) { + + } + + input_amount.setValue(Integer.toString(Math.max(1, Math.min(99999, value + amount)))); + } + + private void onSend() { + int value = 1; + + try { + value = Integer.parseInt(input_amount.getValue()); + } catch (Exception ignored) { + + } + + MatteryNetworking.CHANNEL.sendToServer(new PatternReplicationRequestPacket(open_pattern, value)); + open_pattern = null; + } + + private void onCancel() { + open_pattern = null; + } + + @Override + protected void containerTick() { + super.containerTick(); + + input_amount.tick(); + } + + @Override + public void resize(Minecraft p_96575_, int p_96576_, int p_96577_) { + var input_amount = this.input_amount; + super.resize(p_96575_, p_96576_, p_96577_); + if (input_amount != null) { + this.input_amount.setValue(input_amount.getValue()); + } + } + + @Override + protected void renderForeground(PoseStack stack, int mouseX, int mouseY, float p_98421_) { + if (open_pattern != null) { + RenderSystem.disableDepthTest(); + input_amount.render(stack, mouseX, mouseY, p_98421_); + input_amount.visible = true; + inc_8.render(stack, mouseX, mouseY, p_98421_); + inc_8.visible = true; + inc_64.render(stack, mouseX, mouseY, p_98421_); + inc_64.visible = true; + inc_256.render(stack, mouseX, mouseY, p_98421_); + inc_256.visible = true; + + dec_8.visible = true; + dec_64.visible = true; + dec_256.visible = true; + + send.visible = true; + cancel.visible = true; + + dec_8.render(stack, mouseX, mouseY, p_98421_); + dec_64.render(stack, mouseX, mouseY, p_98421_); + dec_256.render(stack, mouseX, mouseY, p_98421_); + dec_8.render(stack, mouseX, mouseY, p_98421_); + + send.render(stack, mouseX, mouseY, p_98421_); + cancel.render(stack, mouseX, mouseY, p_98421_); + } else { + input_amount.visible = false; + inc_8.visible = false; + inc_64.visible = false; + inc_256.visible = false; + + send.visible = false; + cancel.visible = false; + + dec_8.visible = false; + dec_64.visible = false; + dec_256.visible = false; + } + } + + @Override + protected void renderLabels(PoseStack p_97808_, int p_97809_, int p_97810_) {} + + @Nonnull + @Override + public List getTooltipFromItem(ItemStack p_96556_) { + List get_list = super.getTooltipFromItem(p_96556_); + + if (hovered_pattern != null) { + get_list.add(new TranslatableComponent("otm.item.pattern.research", String.format("%.2f", hovered_pattern.research_percent() * 100d)).withStyle(ChatFormatting.AQUA)); + } + + return get_list; + } + + @Override + protected void renderTooltip(PoseStack pose, int mouseX, int mouseY) { + if (open_pattern != null) { + int x = (width - modal_width) / 2 + 7; + int y = input_amount.y + 1; + + if (mouseX >= x && mouseX <= x + 16 && mouseY >= y && mouseY <= y + 16) { + this.renderTooltip(pose, new ItemStack(open_pattern.item(), 1), mouseX, mouseY); + return; + } + } else if (hovered_pattern != null) { + this.renderTooltip(pose, new ItemStack(hovered_pattern.item(), 1), mouseX, mouseY); + return; + } + + super.renderTooltip(pose, mouseX, mouseY); + } + + private void openPattern(IPatternStorage.PatternState state) { + open_pattern = state; + input_amount.setValue("1"); + scrolling = false; + } + + @Override + public boolean mouseClicked(double mouse_x, double mouse_y, int p_97750_) { + if (hovered_pattern != null && open_pattern == null) { + openPattern(hovered_pattern); + return true; + } + + double div_scroll = menu.patterns.size() < 1 ? 1 : menu.patterns.size() - 1; + + int scroll_x = leftPos + 157; + int scroll_y = topPos + 20 + (int) ((dragger_position / maxScroll()) * 145); + + if (maxScroll() > 0 && mouse_x >= scroll_x && scroll_x + 12 >= mouse_x && mouse_y >= scroll_y && scroll_y + 15 >= mouse_y) { + scrolling = true; + } else { + scrolling = false; + } + + return super.mouseClicked(mouse_x, mouse_y, p_97750_); + } + + @Override + public boolean mouseReleased(double p_97812_, double p_97813_, int p_97814_) { + scrolling = false; + return super.mouseReleased(p_97812_, p_97813_, p_97814_); + } + + @Override + public boolean mouseScrolled(double mouse_x, double mouse_y, double scroll) { + if (open_pattern != null) { + return true; + } + + if (super.mouseScrolled(mouse_x, mouse_y, scroll)) { + return true; + } + + dragger_position = Math.max(0, Math.min(maxScroll(), dragger_position - scroll)); + + return true; + } + + @Override + public boolean mouseDragged(double mouse_x, double mouse_y, int flag, double drag_x, double drag_y) { + if (scrolling && maxScroll() > 0) { + if (mouse_y < topPos + 20) { + dragger_position = 0; + } else if (mouse_y > topPos + 20 + 160) { + dragger_position = maxScroll(); + } else { + dragger_position = maxScroll() * (mouse_y - topPos - 20) / 160d; + } + } + + return super.mouseDragged(mouse_x, mouse_y, flag, drag_x, drag_y); + } + + private boolean scrolling = false; + + private int maxScroll() { + if (menu.patterns.size() < 16) { + return 0; + } + + return (int) Math.floor(menu.patterns.size() / 8d - 2d); + } + + @Override + protected void renderBg(PoseStack pose, float p_97788_, int mouseX, int mouseY) { + super.renderBg(pose, p_97788_, mouseX, mouseY); + + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.setShaderTexture(0, CONTAINER_BACKGROUND()); + + if (maxScroll() > 0) { + if (scrolling) { + blit(pose, leftPos + 157, topPos + 20 + (int) ((dragger_position / maxScroll()) * 145), 188, 0, 12, 15); + } else { + blit(pose, leftPos + 157, topPos + 20 + (int) ((dragger_position / maxScroll()) * 145), 176, 0, 12, 15); + } + } else { + blit(pose, leftPos + 157, topPos + 20, 176, 0, 12, 15); + } + + this.font.draw(pose, this.title, (float) (leftPos + this.titleLabelX), (float) (topPos + this.titleLabelY), 4210752); + + hovered_pattern = null; + + int cells_width = 8; + int cells_height = 9; + + int render_x = leftPos + 10; + int render_y = topPos + 20; + int cell_x = 0; + int cell_y = 0; + + RenderSystem.enableDepthTest(); + + for (int index = ((int) dragger_position) * 9; index < menu.patterns.size(); index++) { + var state = menu.patterns.get(index); + var itemstack = new ItemStack(state.item(), 1); + + // player, itemstack, x, y, ?????????? + this.itemRenderer.renderAndDecorateItem(this.minecraft.player, itemstack, render_x, render_y, -1); + // font, itemstack, x, y, string + this.itemRenderer.renderGuiItemDecorations(this.font, itemstack, render_x, render_y, null); + + if (hovered_pattern == null && mouseX >= render_x && mouseX <= render_x + 16 && mouseY >= render_y && mouseY <= render_y + 16) { + hovered_pattern = state; + } + + cell_x++; + render_x += 18; + + if (cell_x >= cells_width) { + render_x = leftPos + 10; + render_y += 18; + cell_x = 0; + cell_y++; + + if (cell_y >= cells_height) + break; + } + } + + if (open_pattern != null) { + RenderSystem.depthFunc(GL_ALWAYS); + + this.renderBackground(pose); + + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.setShaderTexture(0, CONTAINER_BACKGROUND()); + // PoseStack, x, y, image_start_x, image_start_y, rect_size_x, rect_size_y + + int x = (width - modal_width) / 2; + int y = (height - modal_height) / 2; + + // заголовок + int dy = 0; + this.blit(pose, x, y, 0, 188, modal_width, 4); + dy += 4; + + for (; dy < modal_height - 10; dy += 5) + this.blit(pose, x, y + dy, 0, 192, modal_width, 5); + + this.blit(pose, x, y + dy, 0, 197, modal_width, 4); + + this.blit(pose, x + 6, input_amount.y, 176, 15, 18, 18); + + var itemstack = new ItemStack(open_pattern.item(), 1); + + RenderSystem.enableDepthTest(); + // player, itemstack, x, y, ?????????? + this.itemRenderer.renderAndDecorateItem(this.minecraft.player, itemstack, x + 7, input_amount.y + 1, 100); + // font, itemstack, x, y, string + this.itemRenderer.renderGuiItemDecorations(this.font, itemstack, 0, 0, null); + + this.font.draw(pose, new TranslatableComponent("otm.container.matter_panel.label"), (float) (x + 6), (float) (y + 5), 4210752); + this.font.draw(pose, new TranslatableComponent("otm.item.pattern.line", itemstack.getDisplayName(), String.format("%.2f", open_pattern.research_percent() * 100d)), (float) (x + 6), (float) (cancel.y), ChatFormatting.AQUA.getColor()); + + RenderSystem.depthFunc(GL_LESS); + } + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/screen/MatterReplicatorScreen.java b/src/main/java/ru/dbotthepony/mc/otm/screen/MatterReplicatorScreen.java new file mode 100644 index 000000000..1929d24fd --- /dev/null +++ b/src/main/java/ru/dbotthepony/mc/otm/screen/MatterReplicatorScreen.java @@ -0,0 +1,11 @@ +package ru.dbotthepony.mc.otm.screen; + +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +import ru.dbotthepony.mc.otm.menu.MatterReplicatorMenu; + +public class MatterReplicatorScreen extends MatteryScreen { + public MatterReplicatorScreen(MatterReplicatorMenu p_97741_, Inventory p_97742_, Component p_97743_) { + super(p_97741_, p_97742_, p_97743_); + } +} diff --git a/src/main/java/ru/dbotthepony/mc/otm/screen/MatteryScreen.java b/src/main/java/ru/dbotthepony/mc/otm/screen/MatteryScreen.java index f7b7f1453..5d43d0435 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/screen/MatteryScreen.java +++ b/src/main/java/ru/dbotthepony/mc/otm/screen/MatteryScreen.java @@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.screen; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.gui.components.Widget; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.renderer.GameRenderer; import net.minecraft.network.chat.Component; @@ -24,9 +25,6 @@ public class MatteryScreen extends AbstractContainerScree return CONTAINER_BASE; } - protected int render_x; - protected int render_y; - public MatteryScreen(T p_97741_, Inventory p_97742_, Component p_97743_) { super(p_97741_, p_97742_, p_97743_); @@ -43,7 +41,7 @@ public class MatteryScreen extends AbstractContainerScree if (this.hoveredSlot == null && menu.getCarried().isEmpty()) { for (AbstractWidget widget : menu.widget_list) { - if (widget.renderTooltip(this, pose, render_x, render_y, mouseX, mouseY)) { + if (widget.renderTooltip(this, pose, leftPos, topPos, mouseX, mouseY)) { return; } } @@ -51,30 +49,32 @@ public class MatteryScreen extends AbstractContainerScree } @Override - public void render(PoseStack p_98418_, int mouseX, int mouseY, float p_98421_) { - render_x = (width - imageWidth) / 2; - render_y = (height - imageHeight) / 2; - + public void render(PoseStack pose, int mouseX, int mouseY, float p_98421_) { for (AbstractWidget widget : menu.widget_list) { - widget.updateIsHovered(render_x, render_y, mouseX, mouseY); + widget.updateIsHovered(leftPos, topPos, mouseX, mouseY); } - this.renderBackground(p_98418_); - super.render(p_98418_, mouseX, mouseY, p_98421_); - this.renderTooltip(p_98418_, mouseX, mouseY); + this.renderBackground(pose); + super.render(pose, mouseX, mouseY, p_98421_); + this.renderForeground(pose, mouseX, mouseY, p_98421_); + this.renderTooltip(pose, mouseX, mouseY); } protected void renderRegularSlot(PoseStack pose, int x, int y) { - this.blit(pose, render_x + x - 1, render_y + y - 1, 0, 96, 18, 18); + this.blit(pose, leftPos + x - 1, topPos + y - 1, 0, 96, 18, 18); } protected void renderOutputSlot(PoseStack pose, int x, int y) { - this.blit(pose, render_x + x - 5, render_y + y - 5, 18, 96, 26, 26); + this.blit(pose, leftPos + x - 5, topPos + y - 5, 18, 96, 26, 26); } protected void renderBatterySlot(PoseStack pose, int x, int y) { renderRegularSlot(pose, x, y); - this.blit(pose, render_x + x - 1, render_y + y - 1, 0, 114, 18, 18); + this.blit(pose, leftPos + x - 1, topPos + y - 1, 0, 114, 18, 18); + } + + protected void renderForeground(PoseStack stack, int mouseX, int mouseY, float p_98421_) { + } @Override @@ -83,7 +83,7 @@ public class MatteryScreen extends AbstractContainerScree RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); RenderSystem.setShaderTexture(0, CONTAINER_BACKGROUND()); // PoseStack, x, y, image_start_x, image_start_y, rect_size_x, rect_size_y - this.blit(pose, render_x, render_y, 0, 0, this.imageWidth, this.imageHeight); + this.blit(pose, leftPos, topPos, 0, 0, this.imageWidth, this.imageHeight); RenderSystem.setShader(GameRenderer::getPositionTexShader); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); @@ -106,7 +106,7 @@ public class MatteryScreen extends AbstractContainerScree } for (AbstractWidget widget : menu.widget_list) { - widget.renderBackground(this, pose, render_x, render_y, mouseX, mouseY); + widget.renderBackground(this, pose, leftPos, topPos, mouseX, mouseY); } } } diff --git a/src/main/resources/assets/overdrive_that_matters/blockstates/matter_panel.json b/src/main/resources/assets/overdrive_that_matters/blockstates/matter_panel.json new file mode 100644 index 000000000..91d2d79bd --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/blockstates/matter_panel.json @@ -0,0 +1,27 @@ +{ + "variants": { + "facing=south": { + "model": "overdrive_that_matters:block/matter_panel" + }, + "facing=up": { + "model": "overdrive_that_matters:block/matter_panel", + "x": 90 + }, + "facing=down": { + "model": "overdrive_that_matters:block/matter_panel", + "x": 180 + }, + "facing=west": { + "model": "overdrive_that_matters:block/matter_panel", + "y": 90 + }, + "facing=north": { + "model": "overdrive_that_matters:block/matter_panel", + "y": 180 + }, + "facing=east": { + "model": "overdrive_that_matters:block/matter_panel", + "y": 270 + } + } +} diff --git a/src/main/resources/assets/overdrive_that_matters/lang/en_us.json b/src/main/resources/assets/overdrive_that_matters/lang/en_us.json index 328f86afb..cb77dd556 100644 --- a/src/main/resources/assets/overdrive_that_matters/lang/en_us.json +++ b/src/main/resources/assets/overdrive_that_matters/lang/en_us.json @@ -7,7 +7,7 @@ "otm.gui.power.name": "MtE", "otm.gui.progress_widget": "Progress: %s%%", - "otm.gui.progress_widget_stuck": "The machine can't finish it's work, check configuration", + "otm.gui.progress_widget_stuck": "The machine can not work, check configuration", "otm.gui.matter.percentage_level": "Matter level: %s%%", "otm.gui.matter.format": "Matter: %s", @@ -21,6 +21,7 @@ "otm.item.pattern.stored": "Stored patterns: %s / %s", "otm.item.pattern.line": "%s [%s%%]", + "otm.item.pattern.research": "Researched: %s%%", "otm.item.matter.infinite": "Stored matter: Infinity / Infinity", "otm.item.matter.normal": "Stored matter: %s / %s", @@ -54,6 +55,16 @@ "block.overdrive_that_matters.matter_cable": "Matter network cable", "block.overdrive_that_matters.pattern_storage": "Pattern storage", "block.overdrive_that_matters.matter_scanner": "Matter scanner", + "block.overdrive_that_matters.matter_panel": "Pattern monitor", + "block.overdrive_that_matters.matter_replicator": "Matter replicator", + + "otm.container.matter_panel.number_input": "Input replication task count", + + "otm.container.matter_panel.increase_by": "+%s", + "otm.container.matter_panel.decrease_by": "-%s", + "otm.container.matter_panel.send": "Send", + "otm.container.matter_panel.cancel": "Cancel", + "otm.container.matter_panel.label": "Replication request", "item.overdrive_that_matters.pill_android": "Android pill", "item.overdrive_that_matters.pill_humane": "Humane pill", diff --git a/src/main/resources/assets/overdrive_that_matters/models/block/matter_panel.json b/src/main/resources/assets/overdrive_that_matters/models/block/matter_panel.json new file mode 100644 index 000000000..8b5bccd0a --- /dev/null +++ b/src/main/resources/assets/overdrive_that_matters/models/block/matter_panel.json @@ -0,0 +1,17 @@ +{ + "parent": "block/block", + "elements": [ + { + "from": [ 0, 0, 0 ], + "to": [ 16, 16, 6 ], + "faces": { + "down": { "texture": "#side" }, + "up": { "texture": "#side" }, + "north": { "texture": "#back" }, + "south": { "texture": "#front" }, + "west": { "texture": "#side" }, + "east": { "texture": "#side" } + } + } + ] +} diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/android_station.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/android_station.png index b920ebcc008ef239de570dc99fd6f437bc28e40f..debc0e4f0a857cb84096ae6852dff53dc91a72d5 100644 GIT binary patch delta 5330 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 X>{{M8&imMC00000NkvXXu0mjfu_y?U diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/android_station.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/android_station.xcf index 798fb99900d2075cf322dfdd5764f5819b39b271..334dac1b80ae7a587ba8a81e2f0d3e281689170b 100644 GIT binary patch delta 104 zcmaF9gz@qc#tD&(atot4IjtGMAjo}kGH0BX{T~R|-5x>*zJ}7jA#@Nol$M6l+7Oz7 YVRIq(8>Y?Ad6#HvxVw%BULs9j=Hp<=73ky^FdqGna?O>5MyJxZ!0+yv9nn_9I$3@om&iNcQcDp3hh|f8&R&ly$xG>WiLK`~5us zFj>}-{XZOM=hw?kK_Oc~_vE54l{GPX@>R`L-DcB)y!#}AtB1P0L-qwng>5URAPq zUwOIZY3lZDSYmzVW2^mNA4lecZstQ6YR;OP-B4mQKaXc7kV1c$y>`%YIT)TW@musC z{1rSbKBASdub8dwNW#jG-K;Lbvk6yc`NzAVllHxh!qLVxfWOs5`E(LvODU~-?LF^I zoekXR1}t_M-_|h%;3}M(mMQ4;dSzP?;9kC#F`L`CytjnA6=?y(*m|>Skt)qjaeCM_ zYN#3TZI=(TMs$up!edrg6!mp8?y|bqEpawAkk+TEF9X;D-yZ}bht}~-;nZUFpP3jH zJ9rtt@i^C<&B9j9I*P^gIbA|Y3HVm{%8td7L9>ii$fL}i;gHtz5auBQGI9Zje9n&@ zf?Vzge7yuQSwwMMDUY7M5d=lir+!F7`4}F$!PYW(igS?tlJ&G&!iI+ZA2SS}mbH<5 zGjJ_!#9FwyY0oh=9&YxqT5Pw)us0u1AVphGrSeB&Rn%C5=q0wEkB@-I;xe6b9j9io zmijK=d4wujew3DZl+<-6S^Ccfy?quCqB4|ucl&MLlKPOm!4avzOGKE;YYgrR^|0$x zPOLqNy@Cw&NIU?28lMRgF;u0O`95f(XACx8tDtOF=_&hRcjd?y-`tl}{=QCb z%q923geAt^{x4q^bq+_jxXnTkDyndn$K8u=BIUwbb*D~<`-6N`Ywg+_nh@^rkn>Nb z$pfAZ5T#;+y7E42Zco{xT-&;Z(RxZ!zcZi1-cB9TqITAmB-@*?SlL(jOU^wq+2>1W$ zfUs%HElBvp-@Ui0-->VSdt*XuBrbScQy}_C)%8$8Qy4Z>tuH(h=C0J>j(sM(4R+lB zIbpw(;w*$r+T58)W`ZKBoszl{2p8F5AtP|7bYdetT5W43?CYlGRAZMq+Y;Dj8C;#{oMW)jCBVZ z6nlOx+!wyOev83fN1e5tYbPwO_f9kOAAh>isR{2NbPJ@2pSM+r=zyYSc9K=M*{XLc ztIR!$%cQ7?xVm#*5^=vF|OCHjhVoWiPl&|JMDkvgo!WMd9hEH2EnEh@l_)F*B8@ zLmGzLfLG0_mktd}+7SQ^s*D_cISh{`C@WfRn*!a;xX-oyD2q7ME3LxFpPCU(Mzz}S z%=@2OT4ky86dJx}p{i*&DLEtF8r`l(u@8wZ0#*JdOUNF9m@+h9*I*UMf6s5@nxWK? z*n{kZehJeC&$g87{*x)J)4(6AAcuJWW|r%O;pwIK(Azk${8Hm<)Mt8p(p@JT!7FZ_+rMKi&B<&4=E63#;$JOPkQ>Ia_ zbY0}1OfDoSleYiBgGFULxB#4bwmWvL6J^huk-n1Px|J@;-v<3y_b8jkBCssuA`becW4C+{W+EhC9C-fR;( z{8BD%zyF5Cf~GK{#K`*iA{_HvoVU9&9~8ez2VqxW0Uw!cKdE;_TYf}c5inu&3P1$5 zl~E69_Z_0p3-xBfIiD*68IbmjiFv9{)2sKdu)X_%&F|}!vl<+WnKeh3ofL$e)kODI z&KRVRh-Nm`{PH*g@J990&`}Zt&m@HD;?9|@eq0xd0_|* zOl4y+-V+q~6OC_{hWx^nWGs#?9?x7P>bqe?hsK&g_ahyTR5js!`uq3M zIKt>96*Ju^^5xNF>V`14;nOXtN81N?B;xDkKJsthH>G@^yK$Y0wo%SIcp%R0t~?>e z5IN^)W=|GtB^y(xFja{7Hg)N*DrpU7DCwTFmK+gy{g>$y*@%LOZu9v|m++;BJJ&HW zvS4v7xtq$pMMD-O^lqal?5L~md|brB^ODtkf6tgKmcV!-H||x zPZX_|226J8J~K1xBpMNJ4VP?n#lPYe@*`8DTO(j2zrc|zXMCD1zL&Z0*S8C~;CyCG zSV>3I5oD?$hC)4M)M*Axp{<^C0==25x6m=s<=oAM)-;YHZYy&ZoJ*IP(L@_c>0E9b zZs_Uscz|%+Np7HleCF`7o!<5@pytq zRB)Xye9iXLaC#%L$X{DF|E~U|EAl) zYc-rbWw8}AKf5&~LFtzvI<&#K%Kz0|jLHUMb5Qt)GwEf%2Voqd%5-i;`ZR+gh|0t; z=-(*`&Kpg)8A(<=8o!|5FSr|h4J58Lp2!iBSn(6v{Yl8%P?atbG_9;&B}GnpBP8R& zfvPu?l+#{&r!7`*xK1dq+;V(>*t{(IwtA)p&R}7zn7K=x^ZjSN@&S>iWr5FOx?a;F710)9-UA&74sCQu>_HSQDMd^%`|3Ja<_p&UG`N`8#3D!O&yyvk z2Lc(~&3_Unjpgm?8#fuLtj6Ob-!#9B{%bORQqI_jrN|9!p=;do(MeO8qIB+ zLQwaJI9vTQt9-ynoM01op+yy{x4npcq^fi?fZ?0Et#Ic2Z|A)+Fh!4kEHXfnM}BZ1 zxK8~ZX8vWkzxX~VmJeOX`G}8EGhOPS+2V67&6(zX#qXSVSnQ2WEZ;qUwL5{(YRG9> zWSZHU&R=tNjo`bMI2}3nF(GY3I!s9(D=4d|lwvOy=8)c1c=#q2ttSzRDhEQ{k!^HB zNRDnLXvw|z(GdWV$vu{6%Ewj{JrUr`NaWG&B)@+4P)k9Oj)jg9>j10|_CK`u!&SE+ z%(dw4KZ_dPcrTPg9eAQRW?v&DmqZ_!_>Hkj->qE=b1QgCkQI5882t?jX8QKwm5hS! zi`o}Dtw+xiSxPGsn9n*;){NZks-A?+d23UPx0Scm!5PSD6cX9fXjSFvnc$CM!}~he z92=JQck{Hqi`(R8li-rR&h`&2uv4FCeQ`f4sPDheaRKTwV($zzf=R5ZD`aMKj+l3DayTnl0+?1IV@N!`TH9RR$ItXni7aSaDJz%A$S+!Hy1{}g3C}kRG$46qw(*Co0^=PVwCGA`-4YN?;f@Bz1MX_O$32z7B6mp=T9h9 z7ms&zoi843Y7VnCvdTZ5{`C`Ve4SKBrpVl*HtiI%e6gFABYsjh$L0;w(di2Qb;~-l zZ7rH{lCmc=pv`Ha^OD8-E`Y_aAEdokpY^slCzY-r>KgXz zF{zdrZ-{&iw*PUZm<@h3s8&WzSf9}J6 z0NK<>{dp`5Owz(#jh-dMb*Kw!j`b9g9P4S1iGD^+m_9UUnVubd--_}*3%wcpMb{7U zn2z*5*)F)(>HW^O!ZP6P3a+NIc21w@8*<*g4#&kPTWoT3tH^ zDO&8GjwftXeE7Mk8o157m!Li|h515V9sW{FX+$R$Fa1N(>00yiVDsJIGh^E(wGlW` zKc#k*F130b&45+t;d5vYL4w-nw?kKqKPHd-sAh_kv-k0_0l~)wCpSayw(e|Jrn6s;&@bJv;!j$SP^2Mr-tdcQgw zroZ{~bt`M{n??Vz@Yz6%v&m783Go5@OH}3syuQxedKa%(|NrIL<%7Vl7>Q|ASnkY1Og@w76Us%xF~=?V4%kf9UU1>Po%G% zC&E$li9FZ8M8w6#K}Hhh2$OUY|HnAmfk6-$Od4co2Xh2Dh(jHu9l`csI|;=9QRw-& zIJs zieuEFkb;ZBWF*98BqeI(nVypoJ+(i0klG9cqNwiyC4tO5f&c&^iRKd}c;MXbLZ=H_ zkv@DGYFGAC%H5$;LHNn`=oel-^4V}w_0`y=0Lqq*hMSU@w=3%R8)DYTlH~hxYA*A# zubvyxMnr}-;v2Jra^eAWw+iwq%B}VbFImo2g^P0d;H6YS8}@L@42ZngsvT^nwTo=pdXWbI?6ON@HFt;^2>a)$$W^0aJJNyLwoW zT7PE9)>u;wI3K3kaFa-~_x+QX=5+^2K?#&zyA%DT__xVFgGe^V9S>ru;%Hc&Wdyz$ zP|lc-uBgYQ4Ig^X3H>iE%id>V`(Ly>G0xwe`1HSFxxK?YL<<+!$F#q4Blj8EFNl(} zio97CqN&1r&*eIHG#WO&7*ngc)lxgSNf>ATQep$jSgf8VzX$v)O;z0|mCClS{sU}c BB}o7P delta 5298 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 Ef}e-}Jpcdz diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/generic_machine.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/generic_machine.xcf index a7143751674cc32b39e6d77a3967e621e891acd3..bdfaeeb65e4ebad15d107a3466949fe4df52f0b3 100644 GIT binary patch delta 95 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 bH}KWD;D9f(EC8;mFSLvRpHQ?3Ou+sRg;7&! literal 0 HcmV?d00001