Skip to content

Commit

Permalink
New Block: Machine Interface (#5921)
Browse files Browse the repository at this point in the history
* Add the machine interface block
It's very barebones rn, but it's a proof of concept.

* Change ArcFurnaceInputHandler to not subclass InsertOnlyInventory, because that makes querying the items inside it impossible

* Test implementation of the machine interface on the arc furnace

* Add a barebones GUI for the machine interface

* Add dynamic scaling for the machine interface GUI

* Add delete button for rows

* Allow comparator output for machine interface

* Abstract comparator based conditions into a helper method

* Add a full list of checks for the Arc Furnace to serve as an example implementation for custom checks

* Remove useless class parameters when building condition options

* Change MachineInterfaceConfig from record to class to make it mutable

* Rename option class

* Refactor row of buttons into its own reusable class

* Perform widget addition with consumer

* Reposition elements in the GUI

* Add a safe method for accessing configurations
Needed for calculating the initial width of the GUI

* Make GUI provide a helpful message when not attached

* Overhaul textures for the machine interface block

* Make buttons in GUI brighter

* Add a recipe for the machine interface

* Add a basic check for machines being active

* Change order of operations in the GUI to set the fallback to the widest possible row

* Change order of buttons in machine interface, putting colour up front

* Add minimum width for select boxes

* Rename class to GuiSelectBox

* Overhaul select box visuals

* Improve uniform width setting for select boxes

* Consider mouseX for highlighting in the select box, which now allows closing the box without making a choice

* Fix capability reference persisting longer than intended

* Add an option generator function to MultiblockProcessor

* Support the machine interface on Squeezer and Fermenter

Add a utility method for BlockPos based capabilities

* Add machine interface support on the crusher and metal press

* Support machine interface on refinery

* Support machine interface on sawmill

* Support machine interface on the bottling machine

* Support machine interface on the mixer

* Support machine interface on the diesel generator and excavator

* Support machine interface on the automatic workbench

* Allow machine interface to pass a signal through to the multiblock

* Fix MachineInterfaceScreen being rendered too dark

Throw out un-needed methods for background rendering

* Add a manual entry for the machine interface

* Overhaul manual entry
  • Loading branch information
BluSunrize committed May 1, 2024
1 parent 5c020e2 commit 385b21d
Show file tree
Hide file tree
Showing 53 changed files with 1,592 additions and 67 deletions.
1 change: 1 addition & 0 deletions src/api/java/blusunrize/immersiveengineering/api/Lib.java
Expand Up @@ -61,6 +61,7 @@ public class Lib
public static final String GUIID_Sorter = "sorter";
public static final String GUIID_ItemBatcher = "item_batcher";
public static final String GUIID_LogicUnit = "logic_unit";
public static final String GUIID_MachineInterface = "machineinterface";
public static final String GUIID_Squeezer = "squeezer";
public static final String GUIID_Fermenter = "fermenter";
public static final String GUIID_Refinery = "refinery";
Expand Down
Expand Up @@ -63,6 +63,15 @@ interface CapabilityRegistrar<State>
{
<T> void register(BlockCapability<T, @Nullable Direction> capability, CapabilityGetter<T, State> getter);

default <T> void registerAtBlockPos(
BlockCapability<T, @Nullable Direction> capability,
BlockPos atPosition,
Function<State, T> getter
)
{
register(capability, (state, position) -> Objects.equals(position.posInMultiblock(), atPosition)?getter.apply(state): null);
}

default <T> void registerAt(
BlockCapability<T, @Nullable Direction> capability,
CapabilityPosition atPosition,
Expand Down
@@ -0,0 +1,168 @@
/*
* BluSunrize
* Copyright (c) 2024
*
* This code is licensed under "Blu's License of Common Sense"
* Details can be found in the license file in the root folder of this project
*/

package blusunrize.immersiveengineering.api.tool;

import blusunrize.immersiveengineering.api.IEApi;
import blusunrize.immersiveengineering.api.Lib;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.capabilities.BlockCapability;
import net.neoforged.neoforge.energy.IEnergyStorage;
import net.neoforged.neoforge.fluids.FluidStack;
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
import net.neoforged.neoforge.items.IItemHandler;
import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BooleanSupplier;
import java.util.function.Predicate;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;

public class MachineInterfaceHandler
{
public static ResourceLocation BASIC_ACTIVE = new ResourceLocation(Lib.MODID, "basic/active");
public static ResourceLocation BASIC_ITEM_IN = new ResourceLocation(Lib.MODID, "basic/item_input");
public static ResourceLocation BASIC_ITEM_OUT = new ResourceLocation(Lib.MODID, "basic/item_output");
public static ResourceLocation BASIC_FLUID_IN = new ResourceLocation(Lib.MODID, "basic/fluid_input");
public static ResourceLocation BASIC_FLUID_OUT = new ResourceLocation(Lib.MODID, "basic/fluid_output");
public static ResourceLocation BASIC_ENERGY = new ResourceLocation(Lib.MODID, "basic/energy_storage");

private static final Map<ResourceLocation, CheckOption<?>[]> CONDITION_REGISTRY = new HashMap<>();

public static void register(ResourceLocation key, CheckOption<?>... options)
{
CONDITION_REGISTRY.put(key, options);
}

public static void copyOptions(ResourceLocation newKey, ResourceLocation oldKey)
{
CONDITION_REGISTRY.put(newKey, CONDITION_REGISTRY.get(oldKey));
}

static
{
// Active, yes/no
register(BASIC_ACTIVE,
new CheckOption<BooleanSupplier>(new ResourceLocation(Lib.MODID, "yes"), value -> value.getAsBoolean()?15: 0),
new CheckOption<BooleanSupplier>(new ResourceLocation(Lib.MODID, "no"), value -> value.getAsBoolean()?0: 15)
);
// Items
register(BASIC_ITEM_IN, buildComparativeConditions(MachineInterfaceHandler::getInventoryFill));
copyOptions(BASIC_ITEM_OUT, BASIC_ITEM_IN);
// Fluids
register(BASIC_FLUID_IN, buildComparativeConditions(MachineInterfaceHandler::getTankFill));
copyOptions(BASIC_FLUID_OUT, BASIC_FLUID_IN);
// Energy
register(BASIC_ENERGY, buildComparativeConditions(MachineInterfaceHandler::getEnergyFill));
}

@SuppressWarnings("unchecked")
public static <T> CheckOption<T>[] buildComparativeConditions(ToDoubleFunction<T> tdf)
{
return (CheckOption<T>[])new CheckOption<?>[]{
CheckOption.doubleCondition(new ResourceLocation(Lib.MODID, "comparator"), tdf),
CheckOption.<T>booleanCondition(new ResourceLocation(Lib.MODID, "empty"), h -> tdf.applyAsDouble(h) <= 0),
CheckOption.<T>booleanCondition(new ResourceLocation(Lib.MODID, "quarter"), h -> tdf.applyAsDouble(h) > 0.25),
CheckOption.<T>booleanCondition(new ResourceLocation(Lib.MODID, "half"), h -> tdf.applyAsDouble(h) > 0.5),
CheckOption.<T>booleanCondition(new ResourceLocation(Lib.MODID, "three_quarter"), h -> tdf.applyAsDouble(h) > 0.75),
CheckOption.<T>booleanCondition(new ResourceLocation(Lib.MODID, "full"), h -> tdf.applyAsDouble(h) >= 1)
};
}

private static float getInventoryFill(IItemHandler itemHandler)
{
float f = 0;
// This is a bit of a nasty workaround to deal with our own WrappingItemHandler
// We use its isItemValid check to rule out slots not covered by the wrapping
int validSlots = 0;
for(int iSlot = 0; iSlot < itemHandler.getSlots(); iSlot++)
{
ItemStack itemstack = itemHandler.getStackInSlot(iSlot);
if(itemHandler.isItemValid(iSlot, itemstack))
{
if(!itemstack.isEmpty())
f += itemstack.getCount()/(float)Math.min(itemHandler.getSlotLimit(iSlot), itemstack.getMaxStackSize());
validSlots++;
}
}
f /= (float)validSlots;
return f;
}

private static float getTankFill(IFluidHandler fluidHandler)
{
float f = 0;
for(int iTank = 0; iTank < fluidHandler.getTanks(); iTank++)
{
FluidStack fluid = fluidHandler.getFluidInTank(iTank);
if(!fluid.isEmpty())
f += fluid.getAmount()/(float)fluidHandler.getTankCapacity(iTank);
}
f /= (float)fluidHandler.getTanks();
return f;
}

private static float getEnergyFill(IEnergyStorage energyStorage)
{
return energyStorage.getEnergyStored()/(float)energyStorage.getMaxEnergyStored();
}

public record CheckOption<T>(ResourceLocation name, ToIntFunction<T> condition)
{
// helper method to turn boolean conditions into comparator signals
public static <T> CheckOption<T> booleanCondition(ResourceLocation name, final Predicate<T> predicate)
{
return new CheckOption<>(name, value -> predicate.test(value)?15: 0);
}

// helper method to turn double conditions into comparator signals
public static <T> CheckOption<T> doubleCondition(ResourceLocation name, final ToDoubleFunction<T> toDouble)
{
return new CheckOption<>(name, value -> Mth.ceil(Math.max(toDouble.applyAsDouble(value), 0)*15));
}

public Component getName()
{
return Component.translatable("gui."+name.getNamespace()+".config.machine_interface.option."+name.getPath());
}

public int getValue(T input)
{
return condition.applyAsInt(input);
}
}

public record MachineCheckImplementation<T>(T instance, ResourceLocation key, CheckOption<T>[] options)
{
@SuppressWarnings("unchecked")
public MachineCheckImplementation(T instance, ResourceLocation key)
{
this(instance, key, (CheckOption<T>[])CONDITION_REGISTRY.get(key));
}

public Component getName()
{
return Component.translatable("gui."+key.getNamespace()+".config.machine_interface.check."+key.getPath().replaceAll("/", "."));
}
}

public interface IMachineInterfaceConnection
{
BlockCapability<IMachineInterfaceConnection, @Nullable Direction> CAPABILITY = BlockCapability.createSided(
IEApi.ieLoc("machine_interface_connection"), IMachineInterfaceConnection.class
);

MachineCheckImplementation<?>[] getAvailableChecks();
}
}
Expand Up @@ -364,6 +364,15 @@ protected void registerStatesAndModels()
.layer(solid(), translucent())
.end()
);
{
ModelFile machineInterfaceModel = models().cubeBottomTop("machine_interface",
modLoc("block/wooden_device/machine_interface"),
modLoc("block/wooden_device/machine_interface_back"),
modLoc("block/wooden_device/machine_interface_front")
);
createRotatedBlock(WoodenDevices.MACHINE_INTERFACE, machineInterfaceModel, IEProperties.FACING_HORIZONTAL, ImmutableList.of(), -90, 0);
itemModel(WoodenDevices.MACHINE_INTERFACE, machineInterfaceModel);
}

createHorizontalRotatedBlock(Cloth.STRIP_CURTAIN,
state -> new ExistingModelFile(rl(
Expand Down
Expand Up @@ -139,6 +139,15 @@ private void woodenDevices(RecipeOutput out)
.define('c', Ingredients.CIRCUIT_BOARD)
.unlockedBy("has_"+toPath(Ingredients.CIRCUIT_BOARD), has(Ingredients.CIRCUIT_BOARD))
.save(out, toRL(toPath(WoodenDevices.LOGIC_UNIT)));
shapedMisc(WoodenDevices.MACHINE_INTERFACE)
.pattern("aea")
.pattern("wcw")
.define('w', IETags.getItemTag(IETags.treatedWood))
.define('a', IETags.aluminumWire)
.define('c', Ingredients.CIRCUIT_BOARD)
.define('e', Ingredients.COMPONENT_ELECTRONIC_ADV)
.unlockedBy("has_"+toPath(Ingredients.CIRCUIT_BOARD), has(Ingredients.CIRCUIT_BOARD))
.save(out, toRL(toPath(WoodenDevices.MACHINE_INTERFACE)));

shapedMisc(WoodenDevices.TURNTABLE)
.pattern("iwi")
Expand Down
Expand Up @@ -337,6 +337,7 @@ private void registerAxeMineable()
WoodenDevices.WATERMILL,
WoodenDevices.TREATED_WALLMOUNT,
WoodenDevices.LOGIC_UNIT,
WoodenDevices.MACHINE_INTERFACE,
WoodenDecoration.TREATED_FENCE,
WoodenDecoration.TREATED_SCAFFOLDING,
WoodenDecoration.TREATED_POST,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 385b21d

Please sign in to comment.