From 961d1fe4b75a8b474456856004985971da1bbbf7 Mon Sep 17 00:00:00 2001 From: 90 Date: Mon, 29 Apr 2024 16:18:13 +0100 Subject: [PATCH] Begin work on ME EMC Interface FIXME: screen won't fucking open for some reason --- build.gradle.kts | 3 +- .../java/gripe/_90/appliede/AppliedE.java | 78 +++- .../_90/appliede/iface/EMCInterfaceBlock.java | 46 ++ .../iface/EMCInterfaceBlockEntity.java | 102 ++++ .../_90/appliede/iface/EMCInterfaceLogic.java | 435 ++++++++++++++++++ .../appliede/iface/EMCInterfaceLogicHost.java | 47 ++ .../_90/appliede/iface/EMCInterfaceMenu.java | 55 +++ .../_90/appliede/iface/EMCInterfacePart.java | 148 ++++++ .../appliede/iface/EMCInterfaceScreen.java | 68 +++ .../gripe/_90/appliede/key/EMCRenderer.java | 5 +- .../resources/META-INF/accesstransformer.cfg | 1 + .../ae2/screens/appliede/emc_interface.json | 111 +++++ .../appliede/blockstates/emc_interface.json | 7 + .../resources/assets/appliede/lang/en_us.json | 2 + .../appliede/models/block/emc_interface.json | 6 + .../models/item/cable_emc_interface.json | 6 + .../appliede/models/item/emc_interface.json | 3 + .../appliede/models/part/emc_interface.json | 6 + .../appliede/textures/block/emc_interface.png | Bin 0 -> 404 bytes .../data/appliede/recipes/emc_interface.json | 23 + .../appliede/recipes/emc_interface_block.json | 12 + .../appliede/recipes/emc_interface_part.json | 12 + 22 files changed, 1158 insertions(+), 18 deletions(-) create mode 100644 src/main/java/gripe/_90/appliede/iface/EMCInterfaceBlock.java create mode 100644 src/main/java/gripe/_90/appliede/iface/EMCInterfaceBlockEntity.java create mode 100644 src/main/java/gripe/_90/appliede/iface/EMCInterfaceLogic.java create mode 100644 src/main/java/gripe/_90/appliede/iface/EMCInterfaceLogicHost.java create mode 100644 src/main/java/gripe/_90/appliede/iface/EMCInterfaceMenu.java create mode 100644 src/main/java/gripe/_90/appliede/iface/EMCInterfacePart.java create mode 100644 src/main/java/gripe/_90/appliede/iface/EMCInterfaceScreen.java create mode 100644 src/main/resources/META-INF/accesstransformer.cfg create mode 100644 src/main/resources/assets/ae2/screens/appliede/emc_interface.json create mode 100644 src/main/resources/assets/appliede/blockstates/emc_interface.json create mode 100644 src/main/resources/assets/appliede/models/block/emc_interface.json create mode 100644 src/main/resources/assets/appliede/models/item/cable_emc_interface.json create mode 100644 src/main/resources/assets/appliede/models/item/emc_interface.json create mode 100644 src/main/resources/assets/appliede/models/part/emc_interface.json create mode 100644 src/main/resources/assets/appliede/textures/block/emc_interface.png create mode 100644 src/main/resources/data/appliede/recipes/emc_interface.json create mode 100644 src/main/resources/data/appliede/recipes/emc_interface_block.json create mode 100644 src/main/resources/data/appliede/recipes/emc_interface_part.json diff --git a/build.gradle.kts b/build.gradle.kts index 8b41afb..a450c4e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -36,11 +36,12 @@ repositories { minecraft { mappings("official", "1.20.1") copyIdeResources.set(true) + accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg")) runs { configureEach { workingDirectory(file("run")) - property("forge.logging.console.level", "info") + property("forge.logging.console.level", "debug") mods { create(modId) { diff --git a/src/main/java/gripe/_90/appliede/AppliedE.java b/src/main/java/gripe/_90/appliede/AppliedE.java index fdaf9d5..8378b30 100644 --- a/src/main/java/gripe/_90/appliede/AppliedE.java +++ b/src/main/java/gripe/_90/appliede/AppliedE.java @@ -1,13 +1,17 @@ package gripe._90.appliede; import java.math.BigInteger; +import java.util.function.Function; -import net.minecraft.Util; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.loading.FMLEnvironment; @@ -16,15 +20,24 @@ import net.minecraftforge.registries.RegistryObject; import appeng.api.networking.GridServices; +import appeng.api.parts.IPart; +import appeng.api.parts.IPartItem; import appeng.api.parts.PartModels; +import appeng.blockentity.AEBaseBlockEntity; +import appeng.core.AppEng; import appeng.items.parts.PartItem; import appeng.items.parts.PartModelsHelper; +import gripe._90.appliede.iface.EMCInterfaceBlock; +import gripe._90.appliede.iface.EMCInterfaceBlockEntity; +import gripe._90.appliede.iface.EMCInterfaceMenu; +import gripe._90.appliede.iface.EMCInterfacePart; +import gripe._90.appliede.iface.EMCInterfaceScreen; import gripe._90.appliede.key.EMCKeyType; import gripe._90.appliede.key.EMCRenderer; import gripe._90.appliede.module.EMCModulePart; import gripe._90.appliede.module.KnowledgeService; -import gripe._90.appliede.pattern.TransmutationPatternItem; +import gripe._90.appliede.module.TransmutationPatternItem; @Mod(AppliedE.MODID) public final class AppliedE { @@ -37,35 +50,70 @@ public static ResourceLocation id(String path) { public static final BigInteger TIER_LIMIT = BigInteger.valueOf((long) Math.pow(2, 42)); private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID); + private static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); + private static final DeferredRegister> BE_TYPES = + DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, MODID); + private static final DeferredRegister> MENU_TYPES = + DeferredRegister.create(ForgeRegistries.MENU_TYPES, AppEng.MOD_ID); private static final DeferredRegister TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID); - public static final RegistryObject EMC_MODULE = ITEMS.register( - "emc_module", - () -> Util.make(() -> { - PartModels.registerModels(PartModelsHelper.createModels(EMCModulePart.class)); - return new PartItem<>(new Item.Properties(), EMCModulePart.class, EMCModulePart::new); - })); + public static final RegistryObject EMC_MODULE = + ITEMS.register("emc_module", () -> part(EMCModulePart.class, EMCModulePart::new)); public static final RegistryObject TRANSMUTATION_PATTERN = ITEMS.register("transmutation_pattern", TransmutationPatternItem::new); - @SuppressWarnings("unused") - private static final RegistryObject TAB = TABS.register(MODID, () -> CreativeModeTab.builder() - .title(Component.translatable("mod." + MODID)) - .icon(() -> EMC_MODULE.get().getDefaultInstance()) - .displayItems((params, output) -> output.accept(EMC_MODULE.get())) - .build()); + public static final RegistryObject EMC_INTERFACE = BLOCKS.register("emc_interface", () -> { + var block = new EMCInterfaceBlock(); + ITEMS.register("emc_interface", () -> new BlockItem(block, new Item.Properties())); + return block; + }); + public static final RegistryObject CABLE_EMC_INTERFACE = + ITEMS.register("cable_emc_interface", () -> part(EMCInterfacePart.class, EMCInterfacePart::new)); + + @SuppressWarnings("DataFlowIssue") + public static final RegistryObject> EMC_INTERFACE_BE = + BE_TYPES.register("emc_interface", () -> { + var type = BlockEntityType.Builder.of(EMCInterfaceBlockEntity::new, EMC_INTERFACE.get()) + .build(null); + EMC_INTERFACE.get().setBlockEntity(EMCInterfaceBlockEntity.class, type, null, null); + AEBaseBlockEntity.registerBlockEntityItem( + type, EMC_INTERFACE.get().asItem()); + return type; + }); + + static { + MENU_TYPES.register("emc_interface", () -> EMCInterfaceMenu.TYPE); + TABS.register(MODID, () -> CreativeModeTab.builder() + .title(Component.translatable("mod." + MODID)) + .icon(() -> EMC_MODULE.get().getDefaultInstance()) + .displayItems((params, output) -> { + output.accept(EMC_MODULE.get()); + output.accept(EMC_INTERFACE.get()); + output.accept(CABLE_EMC_INTERFACE.get()); + }) + .build()); + } public AppliedE() { var bus = FMLJavaModLoadingContext.get().getModEventBus(); ITEMS.register(bus); + BLOCKS.register(bus); + MENU_TYPES.register(bus); + BE_TYPES.register(bus); TABS.register(bus); bus.addListener(EMCKeyType::register); GridServices.register(KnowledgeService.class, KnowledgeService.class); if (FMLEnvironment.dist.isClient()) { - EMCRenderer.register(); + bus.addListener(EMCRenderer::register); + bus.addListener(EMCInterfaceScreen::register); } } + + private static

Item part(Class

partClass, Function, P> factory) { + PartModels.registerModels(PartModelsHelper.createModels(partClass)); + return new PartItem<>(new Item.Properties(), partClass, factory); + } } diff --git a/src/main/java/gripe/_90/appliede/iface/EMCInterfaceBlock.java b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceBlock.java new file mode 100644 index 0000000..8977853 --- /dev/null +++ b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceBlock.java @@ -0,0 +1,46 @@ +package gripe._90.appliede.iface; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; + +import appeng.block.AEBaseEntityBlock; +import appeng.menu.locator.MenuLocators; +import appeng.util.InteractionUtil; + +public class EMCInterfaceBlock extends AEBaseEntityBlock { + public EMCInterfaceBlock() { + super(metalProps()); + } + + @Override + public InteractionResult onActivated( + Level level, + BlockPos pos, + Player player, + InteractionHand hand, + @Nullable ItemStack heldItem, + BlockHitResult hit) { + if (InteractionUtil.isInAlternateUseMode(player)) { + return InteractionResult.PASS; + } + + var be = this.getBlockEntity(level, pos); + + if (be != null) { + if (!level.isClientSide()) { + be.openMenu(player, MenuLocators.forBlockEntity(be)); + } + + return InteractionResult.sidedSuccess(level.isClientSide()); + } + + return InteractionResult.PASS; + } +} diff --git a/src/main/java/gripe/_90/appliede/iface/EMCInterfaceBlockEntity.java b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceBlockEntity.java new file mode 100644 index 0000000..8951e0b --- /dev/null +++ b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceBlockEntity.java @@ -0,0 +1,102 @@ +package gripe._90.appliede.iface; + +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +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 appeng.api.networking.GridHelper; +import appeng.api.networking.IGridNode; +import appeng.api.networking.IGridNodeListener; +import appeng.api.networking.IManagedGridNode; +import appeng.blockentity.grid.AENetworkBlockEntity; +import appeng.me.helpers.BlockEntityNodeListener; + +import gripe._90.appliede.AppliedE; + +public class EMCInterfaceBlockEntity extends AENetworkBlockEntity implements EMCInterfaceLogicHost { + private static final IGridNodeListener NODE_LISTENER = new BlockEntityNodeListener<>() { + @Override + public void onGridChanged(EMCInterfaceBlockEntity nodeOwner, IGridNode node) { + nodeOwner.logic.notifyNeighbours(); + } + }; + + private final EMCInterfaceLogic logic = createLogic(); + + public EMCInterfaceBlockEntity(BlockPos pos, BlockState blockState) { + super(AppliedE.EMC_INTERFACE_BE.get(), pos, blockState); + } + + protected EMCInterfaceLogic createLogic() { + return new EMCInterfaceLogic(getMainNode(), this); + } + + @Override + protected IManagedGridNode createMainNode() { + return GridHelper.createManagedNode(this, NODE_LISTENER); + } + + @Override + public EMCInterfaceLogic getInterfaceLogic() { + return logic; + } + + @Override + public void onMainNodeStateChanged(IGridNodeListener.State reason) { + if (getMainNode().hasGridBooted()) { + logic.notifyNeighbours(); + } + } + + @Override + public void saveAdditional(CompoundTag data) { + super.saveAdditional(data); + logic.writeToNBT(data); + } + + @Override + public void loadTag(CompoundTag data) { + super.loadTag(data); + logic.readFromNBT(data); + } + + @Override + public void addAdditionalDrops(Level level, BlockPos pos, List drops) { + super.addAdditionalDrops(level, pos, drops); + logic.addDrops(drops); + } + + @Override + public void clearContent() { + super.clearContent(); + getStorage().clear(); + } + + @Override + public ItemStack getMainMenuIcon() { + return AppliedE.EMC_INTERFACE.get().asItem().getDefaultInstance(); + } + + @NotNull + @Override + public LazyOptional getCapability(@NotNull Capability cap, @Nullable Direction side) { + var capability = logic.getCapability(cap); + return capability.isPresent() ? capability : super.getCapability(cap, side); + } + + @Override + public void invalidateCaps() { + super.invalidateCaps(); + logic.invalidateCaps(); + } +} diff --git a/src/main/java/gripe/_90/appliede/iface/EMCInterfaceLogic.java b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceLogic.java new file mode 100644 index 0000000..c2e505c --- /dev/null +++ b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceLogic.java @@ -0,0 +1,435 @@ +package gripe._90.appliede.iface; + +import java.math.BigInteger; +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; + +import appeng.api.behaviors.GenericInternalInventory; +import appeng.api.config.Actionable; +import appeng.api.config.PowerMultiplier; +import appeng.api.networking.GridFlags; +import appeng.api.networking.IGrid; +import appeng.api.networking.IGridNode; +import appeng.api.networking.IManagedGridNode; +import appeng.api.networking.security.IActionHost; +import appeng.api.networking.security.IActionSource; +import appeng.api.networking.ticking.IGridTickable; +import appeng.api.networking.ticking.TickRateModulation; +import appeng.api.networking.ticking.TickingRequest; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.api.storage.MEStorage; +import appeng.capabilities.Capabilities; +import appeng.me.storage.DelegatingMEInventory; +import appeng.util.ConfigInventory; +import appeng.util.Platform; + +import gripe._90.appliede.key.EMCKey; +import gripe._90.appliede.module.KnowledgeService; + +import moze_intel.projecte.api.proxy.IEMCProxy; + +@SuppressWarnings("UnstableApiUsage") +public class EMCInterfaceLogic implements IActionHost, IGridTickable { + protected final EMCInterfaceLogicHost host; + protected final IManagedGridNode mainNode; + + @Nullable + private MEStorage localInvHandler; + + private final ConfigInventory config; + private final ConfigInventory storage; + + protected final IActionSource requestSource = new RequestSource(); + private final GenericStack[] plannedWork; + private int priority = 0; + + private final LazyOptional storageHolder; + private final LazyOptional localInvHolder; + + public EMCInterfaceLogic(IManagedGridNode node, EMCInterfaceLogicHost host) { + this(node, host, 9); + } + + public EMCInterfaceLogic(IManagedGridNode node, EMCInterfaceLogicHost host, int slots) { + this.host = host; + config = ConfigInventory.configStacks(AEItemKey.filter(), slots, this::onConfigRowChanged, false); + storage = ConfigInventory.storage(slots, this::onStorageChanged); + + mainNode = node.setFlags(GridFlags.REQUIRE_CHANNEL).addService(IGridTickable.class, this); + plannedWork = new GenericStack[slots]; + + config.useRegisteredCapacities(); + storage.useRegisteredCapacities(); + + storageHolder = LazyOptional.of(() -> storage); + localInvHolder = LazyOptional.of(this::getInventory); + } + + public ConfigInventory getConfig() { + return config; + } + + public ConfigInventory getStorage() { + return storage; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + host.saveChanges(); + } + + public void readFromNBT(CompoundTag tag) { + config.readFromChildTag(tag, "config"); + storage.readFromChildTag(tag, "storage"); + priority = tag.getInt("priority"); + + updatePlan(); + notifyNeighbours(); + } + + public void writeToNBT(CompoundTag tag) { + config.writeToChildTag(tag, "config"); + storage.writeToChildTag(tag, "storage"); + tag.putInt("priority", priority); + } + + private MEStorage getInventory() { + if (localInvHandler == null) { + localInvHandler = new Inventory(); + } + + return localInvHandler; + } + + @Nullable + @Override + public IGridNode getActionableNode() { + return mainNode.getNode(); + } + + @Override + public TickingRequest getTickingRequest(IGridNode node) { + return new TickingRequest(5, 120, !hasWorkToDo(), true); + } + + @Override + public TickRateModulation tickingRequest(IGridNode node, int ticksSinceLastCall) { + if (!mainNode.isActive()) { + return TickRateModulation.SLEEP; + } + + var couldDoWork = false; + + for (var i = 0; i < plannedWork.length; i++) { + var work = plannedWork[i]; + + if (work != null) { + couldDoWork = tryUsePlan(i, work.what(), (int) work.amount()) || couldDoWork; + + if (couldDoWork) { + updatePlan(i); + } + } + } + + return hasWorkToDo() + ? couldDoWork ? TickRateModulation.URGENT : TickRateModulation.SLOWER + : TickRateModulation.SLEEP; + } + + private boolean hasWorkToDo() { + for (var requiredWork : plannedWork) { + if (requiredWork != null) { + return true; + } + } + + return false; + } + + private void updatePlan() { + var hadWork = hasWorkToDo(); + + for (var i = 0; i < config.size(); i++) { + updatePlan(i); + } + + var hasWork = hasWorkToDo(); + + if (hadWork != hasWork) { + mainNode.ifPresent((grid, node) -> { + if (hasWork) { + grid.getTickManager().alertDevice(node); + } else { + grid.getTickManager().sleepDevice(node); + } + }); + } + } + + private void updatePlan(int slot) { + var req = config.getStack(slot); + var stored = storage.getStack(slot); + + if (req == null && stored != null) { + plannedWork[slot] = new GenericStack(stored.what(), -stored.amount()); + } else if (req != null) { + if (stored == null) { + plannedWork[slot] = req; + } else if (req.what().equals(stored.what())) { + plannedWork[slot] = req.amount() != stored.amount() + ? new GenericStack(req.what(), req.amount() - stored.amount()) + : null; + } else { + plannedWork[slot] = new GenericStack(stored.what(), -stored.amount()); + } + } else { + plannedWork[slot] = null; + } + } + + private boolean tryUsePlan(int slot, AEKey what, int amount) { + if (!(what instanceof AEItemKey itemKey)) { + return false; + } + + var grid = mainNode.getGrid(); + + if (grid == null) { + return false; + } + + if (amount < 0) { + amount = -amount; + var inSlot = storage.getStack(slot); + + if (!what.matches(inSlot) || inSlot.amount() < amount) { + return true; + } + + var itemEmc = BigInteger.valueOf(IEMCProxy.INSTANCE.getValue(itemKey.getItem())); + var totalEmc = itemEmc.multiply(BigInteger.valueOf(amount)); + var insertedItems = 0; + + while (totalEmc.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { + var toDeposit = clampedLong(totalEmc); + var energyToExpend = PowerMultiplier.CONFIG.multiply(toDeposit); + var availablePower = grid.getEnergyService() + .extractAEPower(energyToExpend, Actionable.SIMULATE, PowerMultiplier.CONFIG); + + if (availablePower < energyToExpend) { + break; + } + + grid.getEnergyService().extractAEPower(energyToExpend, Actionable.MODULATE, PowerMultiplier.CONFIG); + grid.getService(KnowledgeService.class) + .getStorage() + .insert(EMCKey.base(), toDeposit, Actionable.MODULATE, requestSource); + + var deposited = BigInteger.valueOf(toDeposit); + insertedItems += (int) deposited.divide(itemEmc).longValue(); + totalEmc = totalEmc.subtract(deposited).add(deposited.remainder(itemEmc)); + } + + if (insertedItems > 0) { + storage.extract(slot, what, insertedItems, Actionable.MODULATE); + return true; + } + } + + if (amount > 0) { + return storage.insert(slot, what, amount, Actionable.SIMULATE) != amount + || acquireFromNetwork(grid, slot, what, amount); + } + + return false; + } + + private boolean acquireFromNetwork(IGrid grid, int slot, AEKey what, long amount) { + if (!(what instanceof AEItemKey itemKey)) { + return false; + } + + var emcStorage = grid.getService(KnowledgeService.class).getStorage(); + + var itemEmc = BigInteger.valueOf(IEMCProxy.INSTANCE.getValue(itemKey.getItem())); + var totalEmc = itemEmc.multiply(BigInteger.valueOf(amount)); + var acquiredItems = 0L; + + while (totalEmc.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { + var toWithdraw = clampedLong(totalEmc); + var canWithdraw = emcStorage.extract(EMCKey.base(), toWithdraw, Actionable.SIMULATE, requestSource); + + if (canWithdraw < toWithdraw) { + break; + } + + var energyToExpend = PowerMultiplier.CONFIG.multiply(toWithdraw); + var availablePower = + grid.getEnergyService().extractAEPower(energyToExpend, Actionable.SIMULATE, PowerMultiplier.CONFIG); + + if (availablePower < energyToExpend) { + break; + } + + grid.getEnergyService().extractAEPower(energyToExpend, Actionable.MODULATE, PowerMultiplier.CONFIG); + emcStorage.extract(EMCKey.base(), toWithdraw, Actionable.MODULATE, requestSource); + + var withdrawn = BigInteger.valueOf(toWithdraw); + acquiredItems += withdrawn.divide(itemEmc).longValue(); + totalEmc = totalEmc.subtract(withdrawn).add(withdrawn.remainder(itemEmc)); + } + + if (acquiredItems > 0) { + var inserted = storage.insert(slot, what, acquiredItems, Actionable.MODULATE); + + if (inserted < acquiredItems) { + throw new IllegalStateException("Bad attempt at managing inventory. Voided items: " + inserted); + } + + return true; + } else { + return false; + } + } + + private long clampedLong(BigInteger toClamp) { + return toClamp.min(BigInteger.valueOf(Long.MAX_VALUE)).longValue(); + } + + private void onConfigRowChanged() { + host.saveChanges(); + updatePlan(); + notifyNeighbours(); + } + + private void onStorageChanged() { + host.saveChanges(); + updatePlan(); + } + + void notifyNeighbours() { + mainNode.ifPresent((grid, node) -> { + if (node.isActive()) { + grid.getTickManager().wakeDevice(node); + } + }); + + var be = host.getBlockEntity(); + + if (be != null && be.getLevel() != null) { + Platform.notifyBlocksOfNeighbors(be.getLevel(), be.getBlockPos()); + } + } + + public void addDrops(List drops) { + for (var i = 0; i < storage.size(); i++) { + var stack = storage.getStack(i); + + if (stack != null) { + stack.what() + .addDrops( + stack.amount(), + drops, + host.getBlockEntity().getLevel(), + host.getBlockEntity().getBlockPos()); + } + } + } + + public LazyOptional getCapability(Capability cap) { + if (cap == Capabilities.GENERIC_INTERNAL_INV) { + return storageHolder.cast(); + } else if (cap == Capabilities.STORAGE) { + return localInvHolder.cast(); + } else { + return LazyOptional.empty(); + } + } + + public void invalidateCaps() { + storageHolder.invalidate(); + localInvHolder.invalidate(); + } + + private class Inventory extends DelegatingMEInventory { + private Inventory() { + super(storage); + } + + @Override + public long insert(AEKey what, long amount, Actionable mode, IActionSource source) { + return getRequestInterfacePriority(source).isPresent() && isSameGrid(source) + ? 0 + : super.insert(what, amount, mode, source); + } + + @Override + public long extract(AEKey what, long amount, Actionable mode, IActionSource source) { + var requestPriority = getRequestInterfacePriority(source); + return requestPriority.isPresent() && requestPriority.getAsInt() <= getPriority() && isSameGrid(source) + ? 0 + : super.extract(what, amount, mode, source); + } + + private OptionalInt getRequestInterfacePriority(IActionSource source) { + return source.context(RequestContext.class) + .map(ctx -> OptionalInt.of(ctx.getPriority())) + .orElseGet(OptionalInt::empty); + } + + private boolean isSameGrid(IActionSource source) { + return source.machine() + .map(IActionHost::getActionableNode) + .map(IGridNode::getGrid) + .orElse(null) + == mainNode.getGrid(); + } + + @Override + public Component getDescription() { + return host.getMainMenuIcon().getHoverName(); + } + } + + private class RequestSource implements IActionSource { + private final RequestContext context = new RequestContext(); + + @Override + public Optional player() { + return Optional.empty(); + } + + @Override + public Optional machine() { + return Optional.of(EMCInterfaceLogic.this); + } + + @Override + public Optional context(Class key) { + return key == RequestContext.class ? Optional.of(key.cast(context)) : Optional.empty(); + } + } + + private class RequestContext { + public int getPriority() { + return priority; + } + } +} diff --git a/src/main/java/gripe/_90/appliede/iface/EMCInterfaceLogicHost.java b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceLogicHost.java new file mode 100644 index 0000000..2739405 --- /dev/null +++ b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceLogicHost.java @@ -0,0 +1,47 @@ +package gripe._90.appliede.iface; + +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.entity.BlockEntity; + +import appeng.helpers.IConfigInvHost; +import appeng.helpers.IPriorityHost; +import appeng.helpers.externalstorage.GenericStackInv; +import appeng.menu.ISubMenu; +import appeng.menu.MenuOpener; +import appeng.menu.locator.MenuLocator; + +public interface EMCInterfaceLogicHost extends IPriorityHost, IConfigInvHost { + BlockEntity getBlockEntity(); + + void saveChanges(); + + EMCInterfaceLogic getInterfaceLogic(); + + @Override + default int getPriority() { + return getInterfaceLogic().getPriority(); + } + + @Override + default void setPriority(int priority) { + getInterfaceLogic().setPriority(priority); + } + + @Override + default GenericStackInv getConfig() { + return getInterfaceLogic().getConfig(); + } + + default GenericStackInv getStorage() { + return getInterfaceLogic().getStorage(); + } + + default void openMenu(Player player, MenuLocator locator) { + MenuOpener.open(EMCInterfaceMenu.TYPE, player, locator); + } + + @Override + default void returnToMainMenu(Player player, ISubMenu subMenu) { + MenuOpener.returnTo(EMCInterfaceMenu.TYPE, player, subMenu.getLocator()); + } +} diff --git a/src/main/java/gripe/_90/appliede/iface/EMCInterfaceMenu.java b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceMenu.java new file mode 100644 index 0000000..09e3a76 --- /dev/null +++ b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceMenu.java @@ -0,0 +1,55 @@ +package gripe._90.appliede.iface; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.MenuType; + +import appeng.menu.AEBaseMenu; +import appeng.menu.SlotSemantics; +import appeng.menu.implementations.InterfaceMenu; +import appeng.menu.implementations.MenuTypeBuilder; +import appeng.menu.implementations.SetStockAmountMenu; +import appeng.menu.slot.AppEngSlot; +import appeng.menu.slot.FakeSlot; + +public class EMCInterfaceMenu extends AEBaseMenu { + private static final String ACTION_OPEN_SET_AMOUNT = InterfaceMenu.ACTION_OPEN_SET_AMOUNT; + + public static final MenuType TYPE = MenuTypeBuilder.create( + EMCInterfaceMenu::new, EMCInterfaceLogicHost.class) + .build("emc_interface"); + + private final EMCInterfaceLogicHost host; + + public EMCInterfaceMenu(MenuType menuType, int id, Inventory playerInventory, EMCInterfaceLogicHost host) { + super(menuType, id, playerInventory, host); + this.host = host; + + registerClientAction(ACTION_OPEN_SET_AMOUNT, Integer.class, this::openSetAmountMenu); + + var logic = host.getInterfaceLogic(); + var config = logic.getConfig().createMenuWrapper(); + var storage = logic.getStorage().createMenuWrapper(); + + for (var i = 0; i < config.size(); i++) { + addSlot(new FakeSlot(config, i), SlotSemantics.CONFIG); + } + + for (var i = 0; i < storage.size(); i++) { + addSlot(new AppEngSlot(storage, i), SlotSemantics.STORAGE); + } + } + + public void openSetAmountMenu(int configSlot) { + if (isClientSide()) { + sendClientAction(ACTION_OPEN_SET_AMOUNT, configSlot); + } else { + var stack = host.getConfig().getStack(configSlot); + + if (stack != null) { + SetStockAmountMenu.open( + (ServerPlayer) getPlayer(), getLocator(), configSlot, stack.what(), (int) stack.amount()); + } + } + } +} diff --git a/src/main/java/gripe/_90/appliede/iface/EMCInterfacePart.java b/src/main/java/gripe/_90/appliede/iface/EMCInterfacePart.java new file mode 100644 index 0000000..937bf54 --- /dev/null +++ b/src/main/java/gripe/_90/appliede/iface/EMCInterfacePart.java @@ -0,0 +1,148 @@ +package gripe._90.appliede.iface; + +import java.util.List; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.util.LazyOptional; + +import appeng.api.networking.GridHelper; +import appeng.api.networking.IGridNode; +import appeng.api.networking.IGridNodeListener; +import appeng.api.networking.IManagedGridNode; +import appeng.api.parts.IPartCollisionHelper; +import appeng.api.parts.IPartItem; +import appeng.api.parts.IPartModel; +import appeng.api.util.AECableType; +import appeng.core.AppEng; +import appeng.items.parts.PartModels; +import appeng.menu.locator.MenuLocators; +import appeng.parts.AEBasePart; +import appeng.parts.PartModel; + +import gripe._90.appliede.AppliedE; + +public class EMCInterfacePart extends AEBasePart implements EMCInterfaceLogicHost { + private static final IGridNodeListener NODE_LISTENER = new AEBasePart.NodeListener<>() { + @Override + public void onGridChanged(EMCInterfacePart nodeOwner, IGridNode node) { + nodeOwner.getInterfaceLogic().notifyNeighbours(); + } + }; + + private static final ResourceLocation MODEL_BASE = AppliedE.id("part/emc_interface"); + + @PartModels + private static final PartModel MODELS_OFF = new PartModel(MODEL_BASE, AppEng.makeId("part/interface_off")); + + @PartModels + private static final PartModel MODELS_ON = new PartModel(MODEL_BASE, AppEng.makeId("part/interface_on")); + + @PartModels + private static final PartModel MODELS_HAS_CHANNEL = + new PartModel(MODEL_BASE, AppEng.makeId("part/interface_has_channel")); + + private final EMCInterfaceLogic logic = createLogic(); + + public EMCInterfacePart(IPartItem partItem) { + super(partItem); + } + + protected EMCInterfaceLogic createLogic() { + return new EMCInterfaceLogic(getMainNode(), this); + } + + @Override + protected IManagedGridNode createMainNode() { + return GridHelper.createManagedNode(this, NODE_LISTENER); + } + + @Override + public EMCInterfaceLogic getInterfaceLogic() { + return logic; + } + + @Override + protected void onMainNodeStateChanged(IGridNodeListener.State reason) { + super.onMainNodeStateChanged(reason); + + if (getMainNode().hasGridBooted()) { + logic.notifyNeighbours(); + } + } + + @Override + public void readFromNBT(CompoundTag data) { + super.readFromNBT(data); + logic.readFromNBT(data); + } + + @Override + public void writeToNBT(CompoundTag data) { + super.writeToNBT(data); + logic.writeToNBT(data); + } + + @Override + public void addAdditionalDrops(List drops, boolean wrenched) { + super.addAdditionalDrops(drops, wrenched); + logic.addDrops(drops); + } + + @Override + public void clearContent() { + super.clearContent(); + getStorage().clear(); + } + + @Override + public void saveChanges() { + getHost().markForSave(); + } + + @Override + public boolean onPartActivate(Player player, InteractionHand hand, Vec3 pos) { + if (!player.getCommandSenderWorld().isClientSide()) { + openMenu(player, MenuLocators.forPart(this)); + } + + return true; + } + + @Override + public void getBoxes(IPartCollisionHelper bch) { + bch.addBox(2, 2, 14, 14, 14, 16); + bch.addBox(5, 5, 12, 11, 11, 14); + } + + @Override + public float getCableConnectionLength(AECableType cable) { + return 4; + } + + @Override + public IPartModel getStaticModels() { + if (this.isActive() && this.isPowered()) { + return MODELS_HAS_CHANNEL; + } else if (this.isPowered()) { + return MODELS_ON; + } else { + return MODELS_OFF; + } + } + + @Override + public ItemStack getMainMenuIcon() { + return new ItemStack(getPartItem()); + } + + @Override + public LazyOptional getCapability(Capability cap) { + return logic.getCapability(cap); + } +} diff --git a/src/main/java/gripe/_90/appliede/iface/EMCInterfaceScreen.java b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceScreen.java new file mode 100644 index 0000000..585b5c6 --- /dev/null +++ b/src/main/java/gripe/_90/appliede/iface/EMCInterfaceScreen.java @@ -0,0 +1,68 @@ +package gripe._90.appliede.iface; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.client.gui.components.Button; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; + +import appeng.client.gui.AEBaseScreen; +import appeng.client.gui.Icon; +import appeng.client.gui.style.ScreenStyle; +import appeng.client.gui.widgets.IconButton; +import appeng.core.localization.ButtonToolTips; +import appeng.init.client.InitScreens; +import appeng.menu.SlotSemantics; + +public class EMCInterfaceScreen extends AEBaseScreen { + private final List