296 lines
11 KiB
Java

package com.hbm.handler;
import com.hbm.blocks.ModBlocks;
import com.hbm.blocks.network.BlockOpenComputersCablePaintable;
import com.hbm.inventory.RecipesCommon;
import com.hbm.inventory.fluid.FluidType;
import com.hbm.inventory.fluid.Fluids;
import com.hbm.lib.RefStrings;
import com.hbm.main.MainRegistry;
import com.hbm.util.ItemStackUtil;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.Optional;
import li.cil.oc.api.Items;
import li.cil.oc.api.fs.FileSystem;
import li.cil.oc.api.machine.Arguments;
import li.cil.oc.api.machine.Context;
import li.cil.oc.api.network.*;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.oredict.OreDictionary;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Callable;
import static com.hbm.main.CraftingManager.addShapelessAuto;
import static li.cil.oc.api.FileSystem.asReadOnly;
import static li.cil.oc.api.FileSystem.fromClass;
/**
* General handler for OpenComputers compatibility.
* @author BallOfEnergy (Microwave)
*/
public class CompatHandler {
/**
* Used for converting a steam type to an integer (compression levels).
* @param type Steam type.
* @return Object[] array containing an int with the "compression level"
*/
public static Object[] steamTypeToInt(FluidType type) {
final int typeId = type.getID();
if(typeId == Fluids.HOTSTEAM.getID()) {
return new Object[]{1};
} else if(typeId == Fluids.SUPERHOTSTEAM.getID()) {
return new Object[]{2};
} else if(typeId == Fluids.ULTRAHOTSTEAM.getID()) {
return new Object[]{3};
}
return new Object[] {0};
}
/**
* Used for converting a compression level to a steam type.
* @param arg Steam compression level.
* @return FluidType of the steam type based on the compression level.
*/
public static FluidType intToSteamType(int arg) {
switch(arg) {
case(1):
return Fluids.HOTSTEAM;
case(2):
return Fluids.SUPERHOTSTEAM;
case(3):
return Fluids.ULTRAHOTSTEAM;
default:
return Fluids.STEAM;
}
}
/**
* Allows for easy creation of read-only filesystems. Primarily for floppy disks.
* (Though maybe reading directly from VOTV drives as filesystems could be implemented. :3)
**/
protected static class ReadOnlyFileSystem implements Callable<FileSystem> {
private final String name;
ReadOnlyFileSystem(String name) {
this.name = name;
}
@Override
@Optional.Method(modid = "OpenComputers")
public li.cil.oc.api.fs.FileSystem call() throws Exception {
return asReadOnly(fromClass(MainRegistry.class, RefStrings.MODID, "disks/" + FloppyDisk.sanitizeName(name)));
}
}
// Floppy disk class.
public static class FloppyDisk {
// Specifies the callable ReadOnlyFileSystem to allow OC to access the floppy.
protected final ReadOnlyFileSystem fs;
// Specifies the color of the floppy disk (0-16 colors defined by OC).
public final Byte color;
// Set after loading the disk; allows for adding a recipe to the item.
public ItemStack item;
FloppyDisk(String name, int color) {
this.fs = new ReadOnlyFileSystem(FloppyDisk.sanitizeName(name));
this.color = (byte) color;
}
// Disk names will be sanitized before the FileSystem is created.
// This only affects the location/directory, not the display name.
// (Prevents filesystems from breaking/crashing due to having file separators, wildcards, etc.)
public static String sanitizeName(String input) {
return input.toLowerCase().replaceAll("\\W", "");
}
}
/**
* Simple enum for mapping OC color ordinals to a nicer format for adding new disks.
*/
public enum OCColors {
BLACK(0x444444, "dyeBlack"),
RED(0xB3312C, "dyeRed"),
GREEN(0x339911, "dyeGreen"),
BROWN(0x51301A, "dyeBrown"),
BLUE(0x6666FF, "dyeBlue"),
PURPLE(0x7B2FBE, "dyePurple"),
CYAN(0x66FFFF, "dyeCyan"),
LIGHTGRAY(0xABABAB, "dyeLightGray"),
GRAY(0x666666, "dyeGray"),
PINK(0xD88198, "dyePink"),
LIME(0x66FF66, "dyeLime"),
YELLOW(0xFFFF66, "dyeYellow"),
LIGHTBLUE(0xAAAAFF, "dyeLightBlue"),
MAGENTA(0xC354CD, "dyeMagenta"),
ORANGE(0xEB8844, "dyeOrange"),
WHITE(0xF0F0F0, "dyeWhite"),
NONE(0x0, "");
private final int color;
private final String dictName;
OCColors(int color, String dictName) {
this.color = color;
this.dictName = dictName;
}
public int getColor() {
return color;
}
public static OCColors fromInt(int intColor) {
for (OCColors iColor : OCColors.values()) {
if (intColor == iColor.getColor())
return iColor;
}
return OCColors.NONE;
}
public static OCColors fromDye(ItemStack stack) {
List<String> oreNames = ItemStackUtil.getOreDictNames(stack);
for(String dict : oreNames) {
if(!(dict.length() > 3) || !dict.startsWith("dye"))
continue;
for (OCColors color : OCColors.values()) {
if(!color.dictName.equals(dict))
continue;
return color;
}
}
return OCColors.NONE;
}
}
// Where all disks are stored with their name and `FloppyDisk` class.
public static HashMap<String, FloppyDisk> disks = new HashMap<>();
/**
* Called in the FML PostLoad stage, after the OC API loads.
* <br>
* Loads various parts of OC compatibility.
*/
public static void init() {
if(Loader.isModLoaded("OpenComputers")) {
/*
For anyone wanting to add their own floppy disks,
read the README found in assets.hbm.disks.
*/
// Idea/Code by instantnootles
disks.put("PWRangler", new FloppyDisk("PWRangler", OCColors.CYAN.ordinal()));
// begin registering disks
Logger logger = LogManager.getLogger("HBM");
logger.info("Loading OpenComputers disks...");
if(disks.isEmpty()) {
logger.info("No disks registered; see com.hbm.handler.CompatHandler.disks");
return;
}
disks.forEach((s, disk) -> {
// Test if the disk path even exists.
FileSystem fs = fromClass(MainRegistry.class, RefStrings.MODID, "disks/" + disk.fs.name);
if (fs == null) { // Disk path does NOT exist, and it should not be loaded.
logger.error("Error loading disk: {} at /assets/" + RefStrings.MODID + "/disks/{}", s, disk.fs.name);
logger.error("This is likely due to the path to the disk being non-existent.");
} else { // Disk path DOES exist, and it should be loaded.
disk.item = Items.registerFloppy(s, disk.color, disk.fs); // The big part, actually registering the floppies!
logger.info("Registered disk: {} at /assets/" + RefStrings.MODID + "/disks/{}", s, disk.fs.name);
}
});
logger.info("OpenComputers disks registered.");
// OC disk recipes!
List<ItemStack> floppyDisks = new RecipesCommon.OreDictStack("oc:floppy").toStacks();
if(!floppyDisks.isEmpty()) { //check that floppy disks even exist in oredict.
// Recipes must be initialized here, since if they were initialized in `CraftingManager` then the disk item would not be created yet.
addShapelessAuto(disks.get("PWRangler").item, "oc:floppy", new ItemStack(ModBlocks.pwr_casing));
logger.info("OpenComputers disk recipe added for PWRangler.");
} else {
logger.info("OpenComputers floppy disk oredict not found, recipes cannot be loaded!");
}
// boom, OC disks loaded
logger.info("OpenComputers disks loaded.");
}
}
// Null component name, default to this if broken to avoid NullPointerExceptions.
public static final String nullComponent = "ntm_null";
/**
* This is an interface made specifically for adding OC compatibility to NTM machines. The {@link li.cil.oc.api.network.SimpleComponent} interface must also be implemented in the TE.
* <br>
* This interface is not required to be defined as an optional interface, though the {@link li.cil.oc.api.network.SimpleComponent} interface must be declared as an optional interface.
* <br>
* Pseudo multiblocks will automatically receive compatibility with their ports by proxying their `methods()` and `invoke()` functions. This is the only time they need to be defined.
*
**/
@Optional.InterfaceList({
@Optional.Interface(iface = "li.cil.oc.api.network.SimpleComponent", modid = "OpenComputers"),
@Optional.Interface(iface = "li.cil.oc.api.network.SidedComponent", modid = "OpenComputers"),
@Optional.Interface(iface = "li.cil.oc.api.network.ManagedPeripheral", modid = "OpenComputers"),
})
@SimpleComponent.SkipInjection // make sure OC doesn't inject this shit into the interface and crash
public interface OCComponent extends SimpleComponent, SidedComponent, ManagedPeripheral {
/**
* Must be overridden in the implemented TE, or it will default to "ntm_null".
* <br>
* Dictates the component name exposed to the computer.
* @return String
*/
@Override
@Optional.Method(modid = "OpenComputers")
default String getComponentName() {
return nullComponent;
}
/**
* Tells OC which sides of the block cables should connect to.
* @param side Side to check
* @return If the side should be able to connect.
*/
@Override
@Optional.Method(modid = "OpenComputers")
default boolean canConnectNode(ForgeDirection side) {
return !this.getComponentName().equals(nullComponent);
}
/**
* Standard methods array from {@link li.cil.oc.api.network.ManagedPeripheral} extending {@link li.cil.oc.api.network.SimpleComponent}.
* @return Array of methods to expose to the computer.
*/
@Override
@Optional.Method(modid = "OpenComputers")
default String[] methods() {return new String[0];}
/**
* Standard invoke function from {@link li.cil.oc.api.network.ManagedPeripheral} extending {@link li.cil.oc.api.network.SimpleComponent}.
* @return Data to the computer as a return from the function.
*/
@Override
@Optional.Method(modid = "OpenComputers")
default Object[] invoke(String method, Context context, Arguments args) throws Exception {return null;}
}
}