Files
NotEnoughItems/src/main/java/codechicken/nei/ClientHandler.java
T

513 lines
20 KiB
Java

package codechicken.nei;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiSelectWorld;
import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.resources.IResource;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.event.GuiOpenEvent;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import codechicken.core.ClassDiscoverer;
import codechicken.core.ClientUtils;
import codechicken.lib.packet.PacketCustom;
import codechicken.nei.api.API;
import codechicken.nei.api.GuiInfo;
import codechicken.nei.api.IConfigureNEI;
import codechicken.nei.api.ItemInfo;
import codechicken.nei.bookmark.tree.GuiCraftingTree;
import codechicken.nei.guihook.GuiContainerManager;
import codechicken.nei.recipe.GuiCraftingRecipe;
import codechicken.nei.recipe.GuiUsageRecipe;
import codechicken.nei.recipe.IRecipeHandler;
import codechicken.nei.recipe.RecipeHandlerRef;
import codechicken.nei.recipe.RecipeItemInputHandler;
import codechicken.nei.recipe.StackInfo;
import codechicken.nei.recipe.debug.DebugHandlerWidget;
import cpw.mods.fml.client.CustomModLoadingErrorDisplayException;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModClassLoader;
import cpw.mods.fml.common.discovery.ASMDataTable;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent;
import cpw.mods.fml.common.gameevent.TickEvent.Phase;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
public class ClientHandler {
private static ClientHandler instance;
private final ArrayList<EntityItem> SMPmagneticItems = new ArrayList<>();
private static final String HANDLER_ORDERING = "handlerordering.csv";
public static final List<String> DEFAULT_CONFIGS = ImmutableList.of(
"serialhandlers.cfg",
"heighthackhandlers.cfg",
"hiddenhandlers.cfg",
"enableautofocus.cfg",
"guicraftablesblacklist.cfg",
"defaultbookmarkcontainers.cfg",
"hiddenitems.cfg",
"guidfilters.cfg",
"informationpages.cfg",
"collapsibleitems.cfg",
"recipeidblacklist.cfg",
"handlers.csv",
"catalysts.csv",
HANDLER_ORDERING);
private World lastworld;
public void addSMPMagneticItem(int i, World world) {
WorldClient cworld = (WorldClient) world;
Entity e = cworld.getEntityByID(i);
if (!(e instanceof EntityItem)) {
return;
}
SMPmagneticItems.add((EntityItem) e);
}
private void updateMagnetMode(World world, EntityPlayerSP player) {
if (!NEIClientConfig.getMagnetMode()) return;
float distancexz = 16;
float distancey = 8;
double maxspeedxz = 0.5;
double maxspeedy = 0.5;
double speedxz = 0.05;
double speedy = 0.07;
List<EntityItem> items;
if (world.isRemote) {
items = SMPmagneticItems;
} else {
items = world.getEntitiesWithinAABB(
EntityItem.class,
player.boundingBox.expand(distancexz, distancey, distancexz));
}
for (Iterator<EntityItem> iterator = items.iterator(); iterator.hasNext();) {
EntityItem item = iterator.next();
if (item.delayBeforeCanPickup > 0) continue;
if (item.isDead && world.isRemote) iterator.remove();
if (!NEIClientUtils.canItemFitInInventory(player, item.getEntityItem())) continue;
double dx = player.posX - item.posX;
double dy = player.posY + player.getEyeHeight() - item.posY;
double dz = player.posZ - item.posZ;
double absxz = Math.sqrt(dx * dx + dz * dz);
double absy = Math.abs(dy);
if (absxz > distancexz) {
continue;
}
if (absxz > 1) {
dx /= absxz;
dz /= absxz;
}
if (absy > 1) {
dy /= absy;
}
double vx = item.motionX + speedxz * dx;
double vy = item.motionY + speedy * dy;
double vz = item.motionZ + speedxz * dz;
double absvxz = Math.sqrt(vx * vx + vz * vz);
double absvy = Math.abs(vy);
double rationspeedxz = absvxz / maxspeedxz;
if (rationspeedxz > 1) {
vx /= rationspeedxz;
vz /= rationspeedxz;
}
double rationspeedy = absvy / maxspeedy;
if (rationspeedy > 1) {
vy /= rationspeedy;
}
if (absvxz < 0.2 && absxz < 0.2 && world.isRemote) {
item.setDead();
}
item.setVelocity(vx, vy, vz);
}
}
public static void preInit() {
loadSerialHandlers();
loadHiddenItems();
loadHeightHackHandlers();
loadHiddenHandlers();
loadEnableAutoFocus();
loadGuiCraftablesBlacklist();
loadDefaultBookmarkContainers();
ItemInfo.preInit();
StackInfo.loadGuidFilters();
}
private static URL getDefaultResource(String resource) {
final String folder = resource.substring(resource.lastIndexOf(".") + 1);
return ClientHandler.class.getResource("/assets/nei/" + folder + "/" + resource);
}
public static boolean exportDefaultConfig(String resource) {
final File file = new File(NEIClientConfig.configDir, resource);
if (file.exists() && !NEIClientUtils.backupFile(file)) return false;
if (HANDLER_ORDERING.equals(resource)) {
return writeDefaultHandlerOrdering();
}
final URL url = getDefaultResource(resource);
if (url == null) return false;
try {
FileUtils.copyURLToFile(url, file);
NEIClientConfig.logger.info("Exported default '{}' to {}", resource, file);
return true;
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to export default '{}' to file {}", resource, file, e);
return false;
}
}
public static boolean loadSettingsResource(String resource, Consumer<Stream<String>> callback) {
final ResourceLocation location = new ResourceLocation("nei", resource);
try {
final IResource res = Minecraft.getMinecraft().getResourceManager().getResource(location);
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(res.getInputStream(), StandardCharsets.UTF_8))) {
NEIClientConfig.logger.info("Loading '{}' from jar {}", resource, location);
callback.accept(
IOUtils.readLines(reader).stream()
.map(line -> line.contains("#") ? line.substring(0, line.indexOf('#')) : line)
.map(String::trim).filter(line -> !line.isEmpty()));
return true;
}
} catch (Exception io) {
NEIClientConfig.logger.error("Failed to load '{}' file {}", resource, location);
}
return false;
}
public static boolean loadSettingsFile(String resource, Consumer<Stream<String>> callback) {
final File file = new File(NEIClientConfig.configDir, resource);
final boolean fromJar = !file.exists();
try {
final URL url = fromJar ? getDefaultResource(resource) : file.toURI().toURL();
if (url == null) return false;
try (InputStreamReader reader = new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)) {
NEIClientConfig.logger.info("Loading '{}' from {}", resource, fromJar ? "JAR" : file);
callback.accept(
IOUtils.readLines(reader).stream()
.map(line -> line.contains("#") ? line.substring(0, line.indexOf('#')) : line)
.map(String::trim).filter(line -> !line.isEmpty()));
return true;
}
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to load '{}' file {}", resource, file, e);
}
return false;
}
public static void loadSerialHandlers() {
loadSettingsFile(
"serialhandlers.cfg",
lines -> NEIClientConfig.serialHandlers = lines.collect(Collectors.toCollection(HashSet::new)));
}
public static void loadHeightHackHandlers() {
loadSettingsFile(
"heighthackhandlers.cfg",
lines -> NEIClientConfig.heightHackHandlerRegex = lines.map(Pattern::compile)
.collect(Collectors.toCollection(HashSet::new)));
}
public static void loadHiddenHandlers() {
loadSettingsFile(
"hiddenhandlers.cfg",
lines -> NEIClientConfig.hiddenHandlers = lines.collect(Collectors.toCollection(HashSet::new)));
}
public static void loadEnableAutoFocus() {
loadSettingsFile(
"enableautofocus.cfg",
lines -> AutoFocusWidget.enableAutoFocusPrefixes = lines.collect(Collectors.toList()));
}
public static void loadGuiCraftablesBlacklist() {
loadSettingsFile(
"guicraftablesblacklist.cfg",
lines -> ItemCraftablesPanel.guiBlacklist = lines.collect(Collectors.toList()));
}
public static void loadDefaultBookmarkContainers() {
loadSettingsFile("defaultbookmarkcontainers.cfg", lines -> {
for (String clazz : lines.collect(Collectors.toList())) {
try {
API.registerBookmarkContainerHandler(
(Class<? extends GuiContainer>) Class.forName(clazz),
new DefaultBookmarkContainerHandler());
} catch (Exception e) {
NEIClientConfig.logger.error("Failed to load class '{}' for defaultbookmarkcontainers.cfg", clazz);
}
}
});
}
public static void loadHiddenItems() {
loadSettingsFile("hiddenitems.cfg", lines -> lines.forEach(API::hideItem));
}
public static void load() {
instance = new ClientHandler();
PacketCustom.assignHandler(NEICPH.channel, new NEICPH());
FMLCommonHandler.instance().bus().register(instance);
MinecraftForge.EVENT_BUS.register(instance);
MinecraftForge.EVENT_BUS.register(new GuiCraftingTree.EventHandler());
API.registerHighlightHandler(new DefaultHighlightHandler(), ItemInfo.Layout.HEADER);
HUDRenderer.load();
WorldOverlayRenderer.load();
}
public static void postInit() {
GuiContainerManager.registerReloadResourceListener();
}
public static void loadHandlerOrdering() {
loadSettingsFile(
HANDLER_ORDERING,
lines -> lines.map(line -> line.split(",")).filter(parts -> parts.length == 2).forEach(parts -> {
String handlerId = parts[0];
int ordering = Integer.parseInt(parts[1]);
NEIClientConfig.handlerOrdering.put(handlerId, ordering);
}));
}
private static boolean writeDefaultHandlerOrdering() {
final File file = new File(NEIClientConfig.configDir, HANDLER_ORDERING);
final List<String> lines = Lists.newArrayList(
"# Each line in this file should either be a comment (starts with '#') or an ordering.",
"# Ordering lines are <handler ID>,<ordering number>.",
"# Handlers will be sorted in order of number ascending, so smaller numbers first.",
"# Any handlers that are missing from this file keep their own order, 0 for most of them.",
"# Negative numbers are fine.");
final Map<String, Integer> orders = new TreeMap<>();
final List<IRecipeHandler> handlers = new ArrayList<>();
handlers.addAll(GuiCraftingRecipe.craftinghandlers);
handlers.addAll(GuiCraftingRecipe.serialCraftingHandlers);
handlers.addAll(GuiUsageRecipe.usagehandlers);
handlers.addAll(GuiUsageRecipe.serialUsageHandlers);
for (IRecipeHandler handler : handlers) {
orders.putIfAbsent(
NEIClientConfig.HANDLER_ID_FUNCTION.apply(handler),
NEIClientConfig.getHandlerOrder(handler));
}
orders.forEach((handlerId, order) -> lines.add(handlerId + "," + order));
try {
NEIClientConfig.logger.info("Creating default '{}' {}", HANDLER_ORDERING, file);
FileUtils.writeLines(file, StandardCharsets.UTF_8.name(), lines, "\n");
return true;
} catch (IOException e) {
NEIClientConfig.logger.error("Failed to save default '{}' to file {}", HANDLER_ORDERING, file, e);
return false;
}
}
public static void loadPluginsList() {
final boolean jarjar = Launch.blackboard.getOrDefault("jarjar.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE;
if (!jarjar) {
// Do the old style class discovery
final ClassDiscoverer classDiscoverer = new ClassDiscoverer(
test -> test.startsWith("NEI") && test.endsWith("Config.class"),
IConfigureNEI.class);
NEIClientConfig.pluginsList.addAll(classDiscoverer.findClasses());
NEIClientConfig.logger
.info("Loading NEI Plugins via ClassDiscoverer: " + NEIClientConfig.pluginsList.size());
} else {
// If JarJar's loaded, the ASMDataTable now includes interfaces in addition to Annotations, so use that
// instead Includes a small change in behavior of no longer requiring the class to start with NEI and end
// with Config; This could easily be changed if desired, but the previous behavior was likely to speed
// things up by limiting the scope when looking at all of the jars and classes in the classpath.
final ASMDataTable dataTable = NEIModContainer.getAsmDataTable();
final ModClassLoader modClassLoader = (ModClassLoader) Loader.instance().getModClassLoader();
for (ASMDataTable.ASMData asmData : dataTable.getAll(IConfigureNEI.class.getName().replace('.', '/'))) {
final String className = asmData.getClassName().replace('/', '.');
try {
NEIClientConfig.pluginsList.add(Class.forName(className, true, modClassLoader));
} catch (ClassNotFoundException e) {
NEIClientConfig.logger.error("Failed to load plugin class {}", className, e);
}
}
NEIClientConfig.logger.info("Loading NEI Plugins via JarJar: " + NEIClientConfig.pluginsList.size());
}
}
@SubscribeEvent
public void tickEvent(TickEvent.ClientTickEvent event) {
if (event.phase == Phase.END) return;
Minecraft mc = Minecraft.getMinecraft();
if (mc.theWorld != null) {
loadWorld(mc.theWorld, false);
if (!NEIClientConfig.isEnabled()) return;
/*
* NEI plugins are loaded in a separate thread. Make sure loading them has finished otherwise a CME might
* occur in KeyManager if a plugin calls API.addKeyBind() during initialization.
*/
if (NEIClientConfig.isLoaded()) KeyManager.tickKeyStates();
NEIController.updateUnlimitedItems(mc.thePlayer.inventory);
if (mc.currentScreen == null) NEIController.processCreativeCycling(mc.thePlayer.inventory);
updateMagnetMode(mc.theWorld, mc.thePlayer);
} else {
lastworld = null;
}
}
@SubscribeEvent
public void onGuiScreen(GuiOpenEvent event) {
if (event.gui instanceof GuiSelectWorld) {
NEIClientConfig.reloadSaves();
} else if (event.gui == null) {
/* prevent WorldClient reference being held in the Gui */
NEIController.manager = null;
GuiInfo.clearGuiHandlers();
}
}
@SubscribeEvent
public void tickEvent(TickEvent.RenderTickEvent event) {
if (event.phase == Phase.END && NEIClientConfig.isEnabled()) HUDRenderer.renderOverlay();
}
@SubscribeEvent
public void renderLastEvent(RenderWorldLastEvent event) {
if (NEIClientConfig.isEnabled()) WorldOverlayRenderer.render(event.partialTicks);
}
@SideOnly(Side.CLIENT)
@SubscribeEvent
public void onWorldUnload(WorldEvent.Unload event) {
Minecraft mc = Minecraft.getMinecraft();
if (event.world == mc.theWorld) {
NEIClientConfig.unloadWorld();
ItemMobSpawner.clearEntityReferences();
RecipeItemInputHandler.instance.clearTooltipCache();
RecipeHandlerRef.clearCache();
ItemPanels.itemPanel.craftablesPanel.clearGuiReferences();
DebugHandlerWidget.instance.clearGuiReferences();
NEIController.manager = null;
}
}
public void loadWorld(World world, boolean fromServer) {
if (world != lastworld) {
SMPmagneticItems.clear();
if (!NEIClientConfig.getBooleanSetting("world.overlays.lock")) {
WorldOverlayRenderer.reset();
}
if (!fromServer) {
NEIClientConfig.setHasSMPCounterPart(false);
NEIClientConfig.setInternalEnabled(false);
if (!Minecraft.getMinecraft().isSingleplayer()) // wait for server to initiate in singleplayer
NEIClientConfig.loadWorld("remote/" + ClientUtils.getServerIP().replace(':', '~'));
}
lastworld = world;
}
}
public static ClientHandler instance() {
return instance;
}
public static RuntimeException throwCME(final String message) {
final GuiScreen errorGui = new GuiErrorScreen(null, null) {
@Override
public void handleMouseInput() {}
@Override
public void handleKeyboardInput() {}
@Override
public void drawScreen(int par1, int par2, float par3) {
drawDefaultBackground();
String[] s_msg = message.split("\n");
for (int i = 0; i < s_msg.length; ++i)
drawCenteredString(fontRendererObj, s_msg[i], width / 2, height / 3 + 12 * i, 0xFFFFFFFF);
}
};
CustomModLoadingErrorDisplayException e = new CustomModLoadingErrorDisplayException() {
private static final long serialVersionUID = -5593387489666663375L;
@Override
public void initGui(GuiErrorScreen errorScreen, FontRenderer fontRenderer) {
Minecraft.getMinecraft().displayGuiScreen(errorGui);
}
@Override
public void drawScreen(GuiErrorScreen errorScreen, FontRenderer fontRenderer, int mouseRelX, int mouseRelY,
float tickTime) {}
};
throw e;
}
}