package codechicken.nei; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; 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.function.BiConsumer; 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.IOUtils; import com.google.common.collect.Lists; import codechicken.core.ClassDiscoverer; import codechicken.core.ClientUtils; import codechicken.core.GuiModListScroll; import codechicken.lib.packet.PacketCustom; import codechicken.nei.api.API; import codechicken.nei.api.IConfigureNEI; import codechicken.nei.api.ItemInfo; import codechicken.nei.guihook.GuiContainerManager; import codechicken.nei.recipe.GuiRecipeTab; import codechicken.nei.recipe.StackInfo; 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 SMPmagneticItems = new ArrayList<>(); 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 items; if (world.isRemote) { items = SMPmagneticItems; } else { items = world.getEntitiesWithinAABB( EntityItem.class, player.boundingBox.expand(distancexz, distancey, distancexz)); } for (Iterator 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(); } public static void loadSettingsFile(String resource, Consumer> callback) { loadSettingsFile(resource, (file, writer) -> { String folder = resource.substring(resource.lastIndexOf(".") + 1); URL defaultResource = ClientHandler.class.getResource("/assets/nei/" + folder + "/" + resource); if (defaultResource != null) { try { IOUtils.copy(defaultResource.openStream(), writer); } catch (IOException e) {} } }, callback); } public static boolean loadSettingsResource(String resource, Consumer> 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, BiConsumer createDefault, Consumer> callback) { File file = new File(NEIClientConfig.configDir, resource); if (!file.exists() && createDefault != null) { try (FileWriter writer = new FileWriter(file)) { NEIClientConfig.logger.info("Creating default '{}' {}", resource, file); createDefault.accept(file, writer); } catch (IOException e) { NEIClientConfig.logger.error("Failed to save default '{}' to file {}", resource, file, e); } } if (file.exists()) { try (FileReader reader = new FileReader(file)) { NEIClientConfig.logger.info("Loading '{}' file {}", resource, 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) 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(); GuiModListScroll.register("NotEnoughItems"); PacketCustom.assignHandler(NEICPH.channel, new NEICPH()); FMLCommonHandler.instance().bus().register(instance); MinecraftForge.EVENT_BUS.register(instance); API.registerHighlightHandler(new DefaultHighlightHandler(), ItemInfo.Layout.HEADER); HUDRenderer.load(); WorldOverlayRenderer.load(); } public static void postInit() { GuiContainerManager.registerReloadResourceListener(); } public static void loadHandlerOrdering() { final String COMMA_DELIMITER = ","; final String[] defaultHandlerOrdering = { "# Each line in this file should either be a comment (starts with '#') or an ordering.", "# Ordering lines are ,.", "# Handlers will be sorted in order of number ascending, so smaller numbers first.", "# Any handlers that are missing from this file will be assigned to 0.", "# Negative numbers are fine.", "# If you delete this file, it will be regenerated with all registered handler IDs.", }; loadSettingsFile("handlerordering.csv", (file, writer) -> { List toWrite = Lists.newArrayList(defaultHandlerOrdering); GuiRecipeTab.handlerMap.keySet().stream().sorted() .forEach(handlerId -> toWrite.add(String.format("%s" + COMMA_DELIMITER + "0", handlerId))); try { IOUtils.writeLines(toWrite, "\n", writer); } catch (IOException e) {} }, lines -> lines.map(line -> line.split(COMMA_DELIMITER)).filter(parts -> parts.length == 2).forEach(parts -> { String handlerId = parts[0]; int ordering = Integer.parseInt(parts[1]); NEIClientConfig.handlerOrdering.put(handlerId, ordering); })); } 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; } } @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(); } } 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; } }