diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36367f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# eclipse +eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +classes +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +#Netbeans +.nb-gradle +.nb-gradle-properties + +# other +run +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..9580941 --- /dev/null +++ b/build.gradle @@ -0,0 +1,77 @@ +buildscript { + repositories { + jcenter() + maven { + name = "forge" + url = "https://files.minecraftforge.net/maven" + } + + flatDir dirs: 'libs' + } + dependencies { + classpath "net.minecraftforge.gradle:ForgeGradle:2.1-SNAPSHOT" + } +} + +apply plugin: "net.minecraftforge.gradle.forge" + +version = modVersion +group = modGroup +archivesBaseName = modBaseName + +sourceCompatibility = targetCompatibility = 1.8 +compileJava.options.encoding = 'UTF-8' + +minecraft { + version = project.forgeVersion + runDir = "run" + + // the mappings can be changed at any time, and must be in the following format. + // snapshot_YYYYMMDD snapshot are built nightly. + // stable_# stables are built at the discretion of the MCP team. + // Use non-default mappings at your own risk. they may not always work. + // simply re-run your setup task after changing the mappings to update your workspace. + mappings = project.mcpVersion + makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. +} + +dependencies { + provided files("libs/modcore.jar") +} + +task moveResources { + doLast { + ant.move file: "${buildDir}/resources/main", + todir: "${buildDir}/classes/java" + } +} + +moveResources.dependsOn processResources +classes.dependsOn moveResources + +processResources { + // this will ensure that this task is redone when the versions change. + inputs.property "version", project.version + inputs.property "mcversion", project.minecraft.version + + // replace stuff in mcmod.info, nothing else + from(sourceSets.main.resources.srcDirs) { + include "mcmod.info" + + // replace version and mcversion + expand "version": project.version, "mcversion": project.minecraft.version + } + + // copy everything else, thats not the mcmod.info + from(sourceSets.main.resources.srcDirs) { + exclude "mcmod.info" + } +} + +jar { + manifest { + attributes "FMLCorePlugin": "club.sk1er.uhcstars.tweaker.UHCStarsTweaker", + "ModSide": "CLIENT", + "FMLCorePluginContainsFMLMod": "Yes, yes it does" + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..ca20431 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,11 @@ +modGroup=club.sk1er +modVersion=2.0 +modBaseName=UHC Stars +forgeVersion=1.8.9-11.15.1.2318-1.8.9 +mcpVersion=stable_22 + +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.configureoncommand=true +org.gradle.parallel.threads=4 +org.gradle.jvmargs=-Xmx6G \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0179f2c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jan 11 10:31:12 EST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/libs/modcore.jar b/libs/modcore.jar new file mode 100644 index 0000000..9d51af4 Binary files /dev/null and b/libs/modcore.jar differ diff --git a/sk1er_mod.properties b/sk1er_mod.properties index 4705f70..b7b1ee9 100644 --- a/sk1er_mod.properties +++ b/sk1er_mod.properties @@ -1,4 +1,4 @@ mod_id=Sk1er-UHCstars display_name=Hypixel Stars Mod -not_complete=false +not_complete=false hide=false diff --git a/src/main/java/club/sk1er/modcore/ModCoreInstaller.java b/src/main/java/club/sk1er/modcore/ModCoreInstaller.java new file mode 100644 index 0000000..ecf3720 --- /dev/null +++ b/src/main/java/club/sk1er/modcore/ModCoreInstaller.java @@ -0,0 +1,460 @@ +package club.sk1er.modcore; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.launchwrapper.LaunchClassLoader; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; + +import javax.swing.JFrame; +import javax.swing.JProgressBar; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridLayout; +import java.awt.TextArea; +import java.awt.Toolkit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/* + Created by Sk1er for use in all mods. Install under exact package name each time. + */ +public class ModCoreInstaller { + + + private static final String VERSION_URL = "https://api.sk1er.club/modcore_versions"; + private static final String className = "club.sk1er.mods.core.ModCore"; + private static boolean errored = false; + private static String error; + private static File dataDir = null; + private static boolean isRunningModCore = false; + + public static boolean isIsRunningModCore() { + return isRunningModCore; + } + + private static boolean isInitalized() { + try { + LinkedHashSet objects = new LinkedHashSet<>(); + objects.add(className); + Launch.classLoader.clearNegativeEntries(objects); + Field invalidClasses = LaunchClassLoader.class.getDeclaredField("invalidClasses"); + invalidClasses.setAccessible(true); + Object obj = invalidClasses.get(ModCoreInstaller.class.getClassLoader()); + ((Set) obj).remove(className); + return Class.forName("club.sk1er.mods.core.ModCore") != null; + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException ignored) { + ignored.printStackTrace(); + } + return false; + } + + public static boolean isErrored() { + return errored; + } + + public static String getError() { + return error; + } + + private static void bail(String error) { + errored = true; + ModCoreInstaller.error = error; + } + + private static JsonHolder readFile(File in) { + try { + return new JsonHolder(FileUtils.readFileToString(in)); + } catch (IOException ignored) { + + } + return new JsonHolder(); + } + + public static void initializeModCore(File gameDir) { + if (!isIsRunningModCore()) { + return; + } + try { + Class modCore = Class.forName(className); + Method instanceMethod = modCore.getMethod("getInstance"); + Method initialize = modCore.getMethod("initialize", File.class); + Object modCoreObject = instanceMethod.invoke(null); + initialize.invoke(modCoreObject, gameDir); + System.out.println("Loaded ModCore Successfully"); + return; + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + System.out.println("Did NOT ModCore Successfully"); + } + + public static int initialize(File gameDir, String minecraftVersion) { + if (isInitalized()) return -1; + dataDir = new File(gameDir, "modcore"); + if (!dataDir.exists()) { + if (!dataDir.mkdirs()) { + bail("Unable to create necessary files"); + return 1; + } + } + JsonHolder jsonHolder = fetchJSON(VERSION_URL); + String latestRemote = jsonHolder.optString(minecraftVersion); + boolean failed = jsonHolder.getKeys().size() == 0 || (jsonHolder.has("success") && !jsonHolder.optBoolean("success")); + + File metadataFile = new File(dataDir, "metadata.json"); + JsonHolder localMetadata = readFile(metadataFile); + if (failed) latestRemote = localMetadata.optString(minecraftVersion); + File modcoreFile = new File(dataDir, "Sk1er Modcore-" + latestRemote + " (" + minecraftVersion + ").jar"); + + if (!modcoreFile.exists() || !localMetadata.optString(minecraftVersion).equalsIgnoreCase(latestRemote) && !failed) { + //File does not exist, or is out of date, download it + File old = new File(dataDir, "Sk1er Modcore-" + localMetadata.optString(minecraftVersion) + " (" + minecraftVersion + ").jar"); + if (old.exists()) old.delete(); + + if (!download("https://static.sk1er.club/repo/mods/modcore/" + latestRemote + "/" + minecraftVersion + "/ModCore-" + latestRemote + " (" + minecraftVersion + ").jar", latestRemote, modcoreFile, minecraftVersion, localMetadata)) { + bail("Unable to download"); + return 2; + } + + } + + addToClasspath(modcoreFile); + + if (!isInitalized()) { + bail("Something went wrong and it did not add the jar to the class path. Local file exists? " + modcoreFile.exists()); + return 3; + } + isRunningModCore = true; + return 0; + } + + + public static void addToClasspath(File file) { + try { + URL url = file.toURI().toURL(); + + ClassLoader classLoader = ModCoreInstaller.class.getClassLoader(); + Method method = classLoader.getClass().getDeclaredMethod("addURL", URL.class); + method.setAccessible(true); + method.invoke(classLoader, url); + } catch (Exception e) { + throw new RuntimeException("Unexpected exception", e); + } + } + + private static boolean download(String url, String version, File file, String mcver, JsonHolder versionData) { + url = url.replace(" ", "%20"); + System.out.println("Downloading ModCore " + " version " + version + " from: " + url); + JFrame frame = new JFrame("ModCore Initializer"); + JProgressBar bar = new JProgressBar(); + TextArea comp = new TextArea("", 1, 1, TextArea.SCROLLBARS_NONE); + frame.getContentPane().add(comp); + frame.getContentPane().add(bar); + GridLayout manager = new GridLayout(); + frame.setLayout(manager); + manager.setColumns(1); + manager.setRows(2); + comp.setText("Downloading Sk1er ModCore Library Version " + version + " for Minecraft " + mcver); + comp.setSize(399, 80); + comp.setEditable(false); + Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + + + Dimension preferredSize = new Dimension(400, 225); + bar.setSize(preferredSize); + frame.setSize(preferredSize); + frame.setResizable(false); + bar.setBorderPainted(true); + bar.setMinimum(0); + bar.setStringPainted(true); + frame.setVisible(true); + frame.setLocation(dim.width / 2 - frame.getSize().width / 2, dim.height / 2 - frame.getSize().height / 2); + Font font = bar.getFont(); + bar.setFont(new Font(font.getName(), font.getStyle(), font.getSize() * 4)); + comp.setFont(new Font(font.getName(), font.getStyle(), font.getSize() * 2)); + + try { + + URL u = new URL(url); + HttpURLConnection connection = (HttpURLConnection) u.openConnection(); + connection.setRequestMethod("GET"); + connection.setUseCaches(true); + connection.addRequestProperty("User-Agent", "Mozilla/4.76 (Sk1er Modcore Initializer)"); + connection.setReadTimeout(15000); + connection.setConnectTimeout(15000); + connection.setDoOutput(true); + InputStream is = connection.getInputStream(); + int contentLength = connection.getContentLength(); + FileOutputStream outputStream = new FileOutputStream(file); + byte[] buffer = new byte[1024]; + System.out.println("MAX: " + contentLength); + bar.setMaximum(contentLength); + int read; + bar.setValue(0); + while ((read = is.read(buffer)) > 0) { + outputStream.write(buffer, 0, read); + bar.setValue(bar.getValue() + 1024); + } + outputStream.close(); + FileUtils.write(new File(dataDir, "metadata.json"), versionData.put(mcver, version).toString()); + } catch (Exception e) { + e.printStackTrace(); + frame.dispose(); + return false; + } + frame.dispose(); + return true; + } + + public static JsonHolder fetchJSON(String url) { + return new JsonHolder(fetchString(url)); + } + + public static String fetchString(String url) { + url = url.replace(" ", "%20"); + System.out.println("Fetching " + url); + try { + URL u = new URL(url); + HttpURLConnection connection = (HttpURLConnection) u.openConnection(); + connection.setRequestMethod("GET"); + connection.setUseCaches(true); + connection.addRequestProperty("User-Agent", "Mozilla/4.76 (Sk1er ModCore)"); + connection.setReadTimeout(15000); + connection.setConnectTimeout(15000); + connection.setDoOutput(true); + InputStream is = connection.getInputStream(); + return IOUtils.toString(is, Charset.defaultCharset()); + } catch (Exception e) { + e.printStackTrace(); + } + return "Failed to fetch"; + } + + + //Added because we need to use before ModCore is loaded + static class JsonHolder { + private JsonObject object; + + public JsonHolder(JsonObject object) { + this.object = object; + } + + public JsonHolder(String raw) { + if (raw == null) + object = new JsonObject(); + else + try { + this.object = new JsonParser().parse(raw).getAsJsonObject(); + } catch (Exception e) { + this.object = new JsonObject(); + e.printStackTrace(); + } + } + + public JsonHolder() { + this(new JsonObject()); + } + + @Override + public String toString() { + if (object != null) + return object.toString(); + return "{}"; + } + + public JsonHolder put(String key, boolean value) { + object.addProperty(key, value); + return this; + } + + public void mergeNotOverride(JsonHolder merge) { + merge(merge, false); + } + + public void mergeOverride(JsonHolder merge) { + merge(merge, true); + } + + public void merge(JsonHolder merge, boolean override) { + JsonObject object = merge.getObject(); + for (String s : merge.getKeys()) { + if (override || !this.has(s)) + put(s, object.get(s)); + } + } + + private void put(String s, JsonElement element) { + this.object.add(s, element); + } + + public JsonHolder put(String key, String value) { + object.addProperty(key, value); + return this; + } + + public JsonHolder put(String key, int value) { + object.addProperty(key, value); + return this; + } + + public JsonHolder put(String key, double value) { + object.addProperty(key, value); + return this; + } + + public JsonHolder put(String key, long value) { + object.addProperty(key, value); + return this; + } + + private JsonHolder defaultOptJSONObject(String key, JsonObject fallBack) { + try { + return new JsonHolder(object.get(key).getAsJsonObject()); + } catch (Exception e) { + return new JsonHolder(fallBack); + } + } + + public JsonArray defaultOptJSONArray(String key, JsonArray fallback) { + try { + return object.get(key).getAsJsonArray(); + } catch (Exception e) { + return fallback; + } + } + + public JsonArray optJSONArray(String key) { + return defaultOptJSONArray(key, new JsonArray()); + } + + + public boolean has(String key) { + return object.has(key); + } + + public long optLong(String key, long fallback) { + try { + return object.get(key).getAsLong(); + } catch (Exception e) { + return fallback; + } + } + + public long optLong(String key) { + return optLong(key, 0); + } + + public boolean optBoolean(String key, boolean fallback) { + try { + return object.get(key).getAsBoolean(); + } catch (Exception e) { + return fallback; + } + } + + public boolean optBoolean(String key) { + return optBoolean(key, false); + } + + public JsonObject optActualJSONObject(String key) { + try { + return object.get(key).getAsJsonObject(); + } catch (Exception e) { + return new JsonObject(); + } + } + + public JsonHolder optJSONObject(String key) { + return defaultOptJSONObject(key, new JsonObject()); + } + + + public int optInt(String key, int fallBack) { + try { + return object.get(key).getAsInt(); + } catch (Exception e) { + return fallBack; + } + } + + public int optInt(String key) { + return optInt(key, 0); + } + + + public String defaultOptString(String key, String fallBack) { + try { + return object.get(key).getAsString(); + } catch (Exception e) { + return fallBack; + } + } + + public String optString(String key) { + return defaultOptString(key, ""); + } + + + public double optDouble(String key, double fallBack) { + try { + return object.get(key).getAsDouble(); + } catch (Exception e) { + return fallBack; + } + } + + public List getKeys() { + List tmp = new ArrayList<>(); + object.entrySet().forEach(e -> tmp.add(e.getKey())); + return tmp; + } + + public double optDouble(String key) { + return optDouble(key, 0.0); + } + + + public JsonObject getObject() { + return object; + } + + public boolean isNull(String key) { + return object.has(key) && object.get(key).isJsonNull(); + } + + public JsonHolder put(String values, JsonHolder values1) { + return put(values, values1.getObject()); + } + + public JsonHolder put(String values, JsonObject object) { + this.object.add(values, object); + return this; + } + + public void put(String blacklisted, JsonArray jsonElements) { + this.object.add(blacklisted, jsonElements); + } + + public void remove(String header) { + object.remove(header); + } + } + + +} \ No newline at end of file diff --git a/src/main/java/club/sk1er/uhcstars/UHCStars.java b/src/main/java/club/sk1er/uhcstars/UHCStars.java new file mode 100644 index 0000000..76e3c35 --- /dev/null +++ b/src/main/java/club/sk1er/uhcstars/UHCStars.java @@ -0,0 +1,19 @@ +package club.sk1er.uhcstars; + +import club.sk1er.modcore.ModCoreInstaller; +import club.sk1er.uhcstars.command.StarsCommand; +import net.minecraft.client.Minecraft; +import net.minecraftforge.client.ClientCommandHandler; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventHandler; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; + +@Mod(modid = "Sk1er-UHCstars", name = "UHC Stars", version = "2.0") +public class UHCStars { + + @EventHandler + public void init(FMLInitializationEvent event) { + ModCoreInstaller.initializeModCore(Minecraft.getMinecraft().mcDataDir); + ClientCommandHandler.instance.registerCommand(new StarsCommand()); + } +} diff --git a/src/main/java/club/sk1er/uhcstars/command/StarsCommand.java b/src/main/java/club/sk1er/uhcstars/command/StarsCommand.java new file mode 100644 index 0000000..ee8d491 --- /dev/null +++ b/src/main/java/club/sk1er/uhcstars/command/StarsCommand.java @@ -0,0 +1,86 @@ +package club.sk1er.uhcstars.command; + +import club.sk1er.mods.core.util.JsonHolder; +import club.sk1er.mods.core.util.MinecraftUtils; +import club.sk1er.mods.core.util.Multithreading; +import club.sk1er.mods.core.util.WebUtil; +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.util.EnumChatFormatting; + +public class StarsCommand extends CommandBase { + + private final int[] scores = {10, 50, 150, 250, 500, 750, 1000, 2500, 5000}; + + /** Gets the name of the command */ + @Override + public String getCommandName() { + return "stars"; + } + + /** + * Gets the usage string for the command. + * + * @param sender + */ + @Override + public String getCommandUsage(ICommandSender sender) { + return "/stars "; + } + + /** + * Callback when the command is invoked + * + * @param sender + * @param args + */ + @Override + public void processCommand(ICommandSender sender, String[] args) throws CommandException { + if (args.length == 1) { + Multithreading.runAsync( + () -> { + try { + JsonHolder object = WebUtil.fetchJSON("https://api.sk1er.club/player/" + args[0]); + if (!object.optBoolean("success")) { + MinecraftUtils.sendMessage(EnumChatFormatting.RED + "Player not found!"); + } else { + JsonHolder uhc = + object.optJSONObject("player").optJSONObject("stats").optJSONObject("UHC"); + int score = uhc.optInt("score"); + MinecraftUtils.sendMessage(EnumChatFormatting.YELLOW + "[UHC Stars] ", + EnumChatFormatting.BLUE + + object.optJSONObject("player").optString("displayname") + + " is a " + + getStars(score) + + " star!"); + } + } catch (Exception e) { + MinecraftUtils.sendMessage(EnumChatFormatting.RED + "Player not found!"); + } + }); + } else { + MinecraftUtils.sendMessage(EnumChatFormatting.RED + getCommandUsage(sender)); + } + } + + @Override + public int getRequiredPermissionLevel() { + return -1; + } + + private int getStars(int score) { + int stars = 1; + + for (int i : scores) { + if (score < i) { + break; + } + + score -= i; + ++stars; + } + + return stars + score / 3000; + } +} diff --git a/src/main/java/club/sk1er/uhcstars/tweaker/UHCStarsTweaker.java b/src/main/java/club/sk1er/uhcstars/tweaker/UHCStarsTweaker.java new file mode 100644 index 0000000..0ea5928 --- /dev/null +++ b/src/main/java/club/sk1er/uhcstars/tweaker/UHCStarsTweaker.java @@ -0,0 +1,80 @@ +package club.sk1er.uhcstars.tweaker; + +import club.sk1er.modcore.ModCoreInstaller; +import java.util.Map; +import net.minecraft.launchwrapper.Launch; +import net.minecraftforge.common.ForgeVersion; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.MCVersion; + +@MCVersion(ForgeVersion.mcVersion) +public class UHCStarsTweaker implements IFMLLoadingPlugin { + + /** + * Return a list of classes that implements the IClassTransformer interface + * + * @return a list of classes that implements the IClassTransformer interface + */ + @Override + public String[] getASMTransformerClass() { + int initialize = ModCoreInstaller.initialize(Launch.minecraftHome, "1.8.9"); + + if (ModCoreInstaller.isErrored() || initialize != 0 && initialize != -1) { + // Technically wouldn't happen in simulated installed but is important for actual impl + System.out.println( + "Failed to load Sk1er Modcore - " + initialize + " - " + ModCoreInstaller.getError()); + } + + // If true the classes are loaded + if (ModCoreInstaller.isIsRunningModCore()) { + return new String[] {"club.sk1er.mods.core.forge.ClassTransformer"}; + } + + return new String[] {}; + } + + /** + * Return a class name that implements "ModContainer" for injection into the mod list The + * "getName" function should return a name that other mods can, if need be, depend on. Trivially, + * this modcontainer will be loaded before all regular mod containers, which means it will be + * forced to be "immutable" - not susceptible to normal sorting behaviour. All other mod + * behaviours are available however- this container can receive and handle normal loading events + */ + @Override + public String getModContainerClass() { + return null; + } + + /** + * Return the class name of an implementor of "IFMLCallHook", that will be run, in the main + * thread, to perform any additional setup this coremod may require. It will be run + * prior to Minecraft starting, so it CANNOT operate on minecraft itself. The + * game will deliberately crash if this code is detected to trigger a minecraft class loading + * (TODO: implement crash ;) ) + */ + @Override + public String getSetupClass() { + return null; + } + + /** + * Inject coremod data into this coremod This data includes: "mcLocation" : the location of the + * minecraft directory, "coremodList" : the list of coremods "coremodLocation" : the file this + * coremod loaded from, + * + * @param data + */ + @Override + public void injectData(Map data) {} + + /** + * Return an optional access transformer class for this coremod. It will be injected post-deobf so + * ensure your ATs conform to the new srgnames scheme. + * + * @return the name of an access transformer class or null if none is provided + */ + @Override + public String getAccessTransformerClass() { + return null; + } +} diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info new file mode 100644 index 0000000..df7b6b9 --- /dev/null +++ b/src/main/resources/mcmod.info @@ -0,0 +1,18 @@ +[ + { + "modid": "Sk1er-UHCstars", + "name": "UHC Stars", + "description": "Get the star number of a player while in a UHC game.", + "version": "${version}", + "mcversion": "${mcversion}", + "url": "", + "updateUrl": "", + "authorList": [ + "Sk1er LLC" + ], + "credits": "", + "logoFile": "", + "screenshots": [], + "dependencies": [] + } +] \ No newline at end of file