diff options
-rw-r--r-- | .gitignore | 22 | ||||
-rw-r--r-- | README.md | 10 | ||||
-rw-r--r-- | build.gradle | 85 | ||||
-rw-r--r-- | gradle/wrapper/gradle-wrapper.jar | bin | 0 -> 52271 bytes | |||
-rw-r--r-- | gradle/wrapper/gradle-wrapper.properties | 6 | ||||
-rw-r--r-- | gradlew | 164 | ||||
-rw-r--r-- | gradlew.bat | 90 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowmoonication/Cowmoonication.java | 63 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowmoonication/Friends.java | 55 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowmoonication/Utils.java | 57 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowmoonication/command/MooCommand.java | 115 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowmoonication/config/MooConfig.java | 113 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowmoonication/config/MooGuiConfig.java | 39 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowmoonication/config/MooGuiFactory.java | 29 | ||||
-rw-r--r-- | src/main/java/eu/olli/cowmoonication/listener/ChatListener.java | 124 | ||||
-rw-r--r-- | src/main/resources/assets/cowmoonication/lang/en_us.lang | 1 | ||||
-rw-r--r-- | src/main/resources/mcmod.info | 13 |
17 files changed, 985 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c770e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +eclipse +run @@ -1,2 +1,10 @@ # Cowmoonication -A client-side only Forge mod by providing various things related to communication. +A client-side only Forge mod by [Cow](https://namemc.com/profile/Cow) providing various things related to communication. + +Completed features: +*Base command is `/moo`* +* Toggle to hide all join/leave notifications +* 'Best friends' list to limit the amount of join and leave notifications +* Auto-replace `/r` with `/msg <latest username>` +* Copy chat components via <kbd>ALT</kbd> + <kbd>right click</kbd> +* Change guiScale to any value diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3d002c6 --- /dev/null +++ b/build.gradle @@ -0,0 +1,85 @@ + +// For those who want the bleeding edge +buildscript { + repositories { + jcenter() + maven { + name = "forge" + url = "http://files.minecraftforge.net/maven" + } + } + dependencies { + classpath 'net.minecraftforge.gradle:ForgeGradle:2.1-SNAPSHOT' + } +} +apply plugin: 'net.minecraftforge.gradle.forge' + +/* +// for people who want stable - not yet functional for MC 1.8.8 - we require the forgegradle 2.1 snapshot +plugins { + id "net.minecraftforge.gradle.forge" version "2.0.2" +} +*/ +version = "0.1" +group= "eu.olli.cowmoonication" // http://maven.apache.org/guides/mini/guide-naming-conventions.html +archivesBaseName = "Cowmoonication" + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +minecraft { + version = "1.8.9-11.15.1.1722" + 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 allways work. + // simply re-run your setup task after changing the mappings to update your workspace. + mappings = "stable_20" + // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. +} + +dependencies { + // you may put jars on which you depend on in ./libs + // or you may define them like so.. + //compile "some.group:artifact:version:classifier" + //compile "some.group:artifact:version" + + // real examples + //compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env + //compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env + + // the 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime. + //provided 'com.mod-buildcraft:buildcraft:6.0.8:dev' + + // the deobf configurations: 'deobfCompile' and 'deobfProvided' are the same as the normal compile and provided, + // except that these dependencies get remapped to your current MCP mappings + //deobfCompile 'com.mod-buildcraft:buildcraft:6.0.8:dev' + //deobfProvided 'com.mod-buildcraft:buildcraft:6.0.8:dev' + + // for more info... + // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html + // http://www.gradle.org/docs/current/userguide/dependency_management.html + +} + +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' + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 0000000..30d399d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..fb87029 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Feb 26 22:53:05 CET 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/eu/olli/cowmoonication/Cowmoonication.java b/src/main/java/eu/olli/cowmoonication/Cowmoonication.java new file mode 100644 index 0000000..d07876c --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/Cowmoonication.java @@ -0,0 +1,63 @@ +package eu.olli.cowmoonication; + +import eu.olli.cowmoonication.command.MooCommand; +import eu.olli.cowmoonication.config.MooConfig; +import eu.olli.cowmoonication.listener.ChatListener; +import net.minecraftforge.client.ClientCommandHandler; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventHandler; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import org.apache.logging.log4j.Logger; + +import java.io.File; + +@Mod(modid = Cowmoonication.MODID, version = Cowmoonication.VERSION, clientSideOnly = true, guiFactory = "eu.olli." + Cowmoonication.MODID + ".config.MooGuiFactory") +public class Cowmoonication { + public static final String MODID = "cowmoonication"; + public static final String VERSION = "1.0"; + private MooConfig config; + private Friends friends; + private Utils utils; + private Logger logger; + + @Mod.EventHandler + public void preInit(FMLPreInitializationEvent e) { + logger = e.getModLog(); + + File modDir = new File(e.getModConfigurationDirectory(), MODID + File.separatorChar); + if (!modDir.exists()) { + modDir.mkdirs(); + } + + friends = new Friends(this); + config = new MooConfig(new Configuration(new File(modDir, MODID + ".cfg")), this); + } + + @EventHandler + public void init(FMLInitializationEvent e) { + utils = new Utils(this); + + MinecraftForge.EVENT_BUS.register(new ChatListener(this)); + ClientCommandHandler.instance.registerCommand(new MooCommand(this)); + } + + public MooConfig getConfig() { + return config; + } + + public Friends getFriends() { + return friends; + } + + public Utils getUtils() { + return utils; + } + + public Logger getLogger() { + return logger; + } + +} diff --git a/src/main/java/eu/olli/cowmoonication/Friends.java b/src/main/java/eu/olli/cowmoonication/Friends.java new file mode 100644 index 0000000..0cbe517 --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/Friends.java @@ -0,0 +1,55 @@ +package eu.olli.cowmoonication; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; + +public class Friends { + private final Cowmoonication main; + private Set<String> bestFriends = new HashSet<>(); + + public Friends(Cowmoonication main) { + this.main = main; + } + + public boolean addBestFriend(String name, boolean save) { + if (name.isEmpty()) { + return false; + } + boolean added = bestFriends.add(name); + if (added && save) { + saveBestFriends(); + } + return added; + } + + public boolean addBestFriend(String name) { + return addBestFriend(name, false); + } + + public boolean removeBestFriend(String name) { + boolean removed = bestFriends.remove(name); + if (removed) { + saveBestFriends(); + } + return removed; + } + + public boolean isBestFriend(String playerName) { + return bestFriends.contains(playerName); + } + + private void saveBestFriends() { + + } + + public Set<String> getBestFriends() { + return new TreeSet<>(bestFriends); + } + + public void syncFriends(String[] bestFriends) { + this.bestFriends = new HashSet<>(); + Collections.addAll(this.bestFriends, bestFriends); + } +} diff --git a/src/main/java/eu/olli/cowmoonication/Utils.java b/src/main/java/eu/olli/cowmoonication/Utils.java new file mode 100644 index 0000000..ac3167c --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/Utils.java @@ -0,0 +1,57 @@ +package eu.olli.cowmoonication; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.IChatComponent; +import net.minecraftforge.client.event.ClientChatReceivedEvent; +import net.minecraftforge.common.MinecraftForge; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Utils { + public static final Pattern VALID_USERNAME = Pattern.compile("^[\\w]{1,16}$"); + private static final Pattern USELESS_JSON_CONTENT_PATTERN = Pattern.compile("\"[A-Za-z]+\":false,?"); + private final Cowmoonication main; + private String[] aboveChatMessage; + private long aboveChatMessageExpiration; + + public Utils(Cowmoonication main) { + this.main = main; + } + + public void sendMessage(String text) { + sendMessage(new ChatComponentText(text)); + } + + public void sendMessage(IChatComponent chatComponent) { + ClientChatReceivedEvent event = new ClientChatReceivedEvent((byte) 1, chatComponent); + MinecraftForge.EVENT_BUS.post(event); + if (!event.isCanceled()) { + Minecraft.getMinecraft().thePlayer.addChatMessage(event.message); + } + } + + public void sendAboveChatMessage(String... text) { + aboveChatMessage = text; + aboveChatMessageExpiration = Minecraft.getSystemTime() + 5000; + } + + public String[] getAboveChatMessage() { + if (aboveChatMessageExpiration < Minecraft.getSystemTime()) { + // message expired + aboveChatMessage = null; + } + return aboveChatMessage; + } + + public boolean isValidMcName(String username) { + return VALID_USERNAME.matcher(username).matches(); + } + + public String cleanChatComponent(IChatComponent chatComponent) { + String component = IChatComponent.Serializer.componentToJson(chatComponent); + Matcher jsonMatcher = USELESS_JSON_CONTENT_PATTERN.matcher(component); + return jsonMatcher.replaceAll(""); + } +} diff --git a/src/main/java/eu/olli/cowmoonication/command/MooCommand.java b/src/main/java/eu/olli/cowmoonication/command/MooCommand.java new file mode 100644 index 0000000..ca4722c --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/command/MooCommand.java @@ -0,0 +1,115 @@ +package eu.olli.cowmoonication.command; + +import eu.olli.cowmoonication.Cowmoonication; +import eu.olli.cowmoonication.config.MooConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.command.CommandBase; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.util.BlockPos; +import net.minecraft.util.ChatComponentTranslation; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.MathHelper; + +import java.util.List; +import java.util.Set; + +public class MooCommand extends CommandBase { + private final Cowmoonication main; + + public MooCommand(Cowmoonication main) { + this.main = main; + } + + @Override + public void processCommand(ICommandSender sender, String[] args) throws CommandException { + if (args.length == 0) { + main.getUtils().sendMessage(new ChatComponentTranslation(getCommandUsage(sender))); + return; + } + if (args.length == 2 && args[0].equalsIgnoreCase("add")) { + main.getUtils().sendMessage(EnumChatFormatting.RED + "Edit the best friends list via ESC > Mod Options > Cowmoonication > Config > bestFriends."); + // TODO replace with a proper command + // handleBestFriendAdd(args[1]); + } else if (args.length == 2 && args[0].equalsIgnoreCase("remove") && main.getUtils().isValidMcName(args[1])) { + main.getUtils().sendMessage(EnumChatFormatting.RED + "Edit the best friends list via ESC > Mod Options > Cowmoonication > Config > bestFriends."); + // TODO replace with a proper command + // handleBestFriendRemove(args[1]); + } else if (args[0].equalsIgnoreCase("list")) { + handleListBestFriends(); + } else if (args[0].equalsIgnoreCase("toggle")) { + main.getConfig().toggleNotifications(); + main.getUtils().sendMessage(EnumChatFormatting.GREEN + "Switched all non-best friend login/logout notifications " + (MooConfig.filterFriendNotifications ? EnumChatFormatting.DARK_GREEN + "off" : EnumChatFormatting.DARK_RED + "on")); + } else if (args[0].equalsIgnoreCase("guiscale")) { + int currentGuiScale = (Minecraft.getMinecraft()).gameSettings.guiScale; + if (args.length == 1) { + main.getUtils().sendMessage(EnumChatFormatting.GREEN + "Current GUI scale: " + EnumChatFormatting.DARK_GREEN + currentGuiScale); + } else { + int scale = Math.min(10, MathHelper.parseIntWithDefault(args[1], 6)); + Minecraft.getMinecraft().gameSettings.guiScale = scale; + main.getUtils().sendMessage(EnumChatFormatting.GREEN + "New GUI scale: " + EnumChatFormatting.DARK_GREEN + scale + EnumChatFormatting.GREEN + " (previous: " + EnumChatFormatting.DARK_GREEN + currentGuiScale + EnumChatFormatting.GREEN + ")"); + } + } else { + main.getUtils().sendMessage(new ChatComponentTranslation(getCommandUsage(sender))); + } + } + + private void handleBestFriendAdd(String username) { + if (!main.getUtils().isValidMcName(username)) { + main.getUtils().sendMessage(EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + "? This... doesn't look like a valid username."); + return; + } + + // TODO Add check if 'best friend' is on normal friend list + boolean added = main.getFriends().addBestFriend(username, true); + if (added) { + main.getUtils().sendMessage(EnumChatFormatting.GREEN + "Added " + EnumChatFormatting.DARK_GREEN + username + EnumChatFormatting.GREEN + " as best friend."); + } else { + main.getUtils().sendMessage(EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + " is a best friend already."); + } + } + + private void handleBestFriendRemove(String username) { + if (!main.getUtils().isValidMcName(username)) { + main.getUtils().sendMessage(EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + "? This... doesn't look like a valid username."); + return; + } + + boolean removed = main.getFriends().removeBestFriend(username); + if (removed) { + main.getUtils().sendMessage(EnumChatFormatting.GREEN + "Removed " + EnumChatFormatting.DARK_GREEN + username + EnumChatFormatting.GREEN + " from best friends list."); + } else { + main.getUtils().sendMessage(EnumChatFormatting.DARK_RED + username + EnumChatFormatting.RED + " isn't a best friend."); + } + } + + private void handleListBestFriends() { + Set<String> bestFriends = main.getFriends().getBestFriends(); + + // TODO show fancy gui with list of best friends (maybe just the mod's settings?) + main.getUtils().sendMessage(EnumChatFormatting.GREEN + "Best friends: " + String.join(", ", bestFriends)); + } + + @Override + public String getCommandName() { + return "moo"; + } + + @Override + public String getCommandUsage(ICommandSender sender) { + return Cowmoonication.MODID + ":command.moo.usage"; + } + + @Override + public int getRequiredPermissionLevel() { + return 0; + } + + @Override + public List<String> addTabCompletionOptions(ICommandSender sender, String[] args, BlockPos pos) { + if (args.length == 1) { + return getListOfStringsMatchingLastWord(args, "add", "remove", "list", "toggle", "guiscale"); + } + return null; + } +} diff --git a/src/main/java/eu/olli/cowmoonication/config/MooConfig.java b/src/main/java/eu/olli/cowmoonication/config/MooConfig.java new file mode 100644 index 0000000..6165e54 --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/config/MooConfig.java @@ -0,0 +1,113 @@ +package eu.olli.cowmoonication.config; + +import eu.olli.cowmoonication.Cowmoonication; +import eu.olli.cowmoonication.Utils; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; +import net.minecraftforge.fml.client.event.ConfigChangedEvent; +import net.minecraftforge.fml.common.eventhandler.EventPriority; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.util.ArrayList; +import java.util.List; + +public class MooConfig { + public static boolean filterFriendNotifications; + private static String[] bestFriends; + private static Configuration cfg = null; + private final Cowmoonication main; + + public MooConfig(Configuration configuration, Cowmoonication main) { + this.main = main; + cfg = configuration; + initConfig(); + } + + public static Configuration getConfig() { + return cfg; + } + + private void initConfig() { + syncFromFile(); + main.getFriends().syncFriends(bestFriends); + MinecraftForge.EVENT_BUS.register(new ConfigEventHandler()); + } + + /** + * Load the configuration values from the configuration file + */ + private void syncFromFile() { + syncConfig(true, true); + } + + /** + * Save the GUI-altered values to disk + */ + private void syncFromGUI() { + syncConfig(false, true); + } + + /** + * Save the Configuration variables (fields) to disk + */ + private void syncFromFields() { + syncConfig(false, false); + } + + /** + * Synchronise the three copies of the data + * 1) loadConfigFromFile && readFieldsFromConfig -> initialise everything from the disk file + * 2) !loadConfigFromFile && readFieldsFromConfig -> copy everything from the config file (altered by GUI) + * 3) !loadConfigFromFile && !readFieldsFromConfig -> copy everything from the native fields + * + * @param loadConfigFromFile if true, load the config field from the configuration file on disk + * @param readFieldsFromConfig if true, reload the member variables from the config field + */ + private void syncConfig(boolean loadConfigFromFile, boolean readFieldsFromConfig) { + if (loadConfigFromFile) { + cfg.load(); + } + + final boolean FILTER_FRIEND_NOTIFICATIONS = true; + Property propFilterFriendNotify = cfg.get(Configuration.CATEGORY_CLIENT, "filterFriendNotifications", FILTER_FRIEND_NOTIFICATIONS, "Set to false to receive all login/logout messages, set to true to only get notifications of 'best friends' joining/leaving"); + + final String[] BEST_FRIENDS_DEFAULT_VALUE = new String[]{"Cow"}; + Property propBestFriends = cfg.get(Configuration.CATEGORY_CLIENT, "bestFriends", BEST_FRIENDS_DEFAULT_VALUE, "List of best friends: receive login/logout notifications from them"); + propBestFriends.setValidationPattern(Utils.VALID_USERNAME); + + List<String> propOrderGeneral = new ArrayList<>(); + propOrderGeneral.add(propFilterFriendNotify.getName()); + propOrderGeneral.add(propBestFriends.getName()); + cfg.setCategoryPropertyOrder(Configuration.CATEGORY_CLIENT, propOrderGeneral); + + if (readFieldsFromConfig) { + filterFriendNotifications = propFilterFriendNotify.getBoolean(FILTER_FRIEND_NOTIFICATIONS); + bestFriends = propBestFriends.getStringList(); + } + + propFilterFriendNotify.set(filterFriendNotifications); + propBestFriends.set(bestFriends); + + if (cfg.hasChanged()) { + cfg.save(); + } + if (propBestFriends.hasChanged()) { + main.getFriends().syncFriends(bestFriends); + } + } + + public void toggleNotifications() { + filterFriendNotifications = !filterFriendNotifications; + syncFromFields(); + } + + public class ConfigEventHandler { + @SubscribeEvent(priority = EventPriority.NORMAL) + public void onEvent(ConfigChangedEvent.OnConfigChangedEvent e) { + if (Cowmoonication.MODID.equals(e.modID)) { + syncFromGUI(); + } + } + } +} diff --git a/src/main/java/eu/olli/cowmoonication/config/MooGuiConfig.java b/src/main/java/eu/olli/cowmoonication/config/MooGuiConfig.java new file mode 100644 index 0000000..e7b7862 --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/config/MooGuiConfig.java @@ -0,0 +1,39 @@ +package eu.olli.cowmoonication.config; + +import eu.olli.cowmoonication.Cowmoonication; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.common.config.ConfigElement; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.fml.client.config.GuiConfig; + +public class MooGuiConfig extends GuiConfig { + public MooGuiConfig(GuiScreen parent) { + super(parent, + new ConfigElement(MooConfig.getConfig().getCategory(Configuration.CATEGORY_CLIENT)).getChildElements(), + Cowmoonication.MODID, + false, + false, + "Configuration for Cowmoonication"); + titleLine2 = MooConfig.getConfig().getConfigFile().getAbsolutePath(); + } + + @Override + public void initGui() { + super.initGui(); + // optional: add buttons and initialize fields + } + + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + super.drawScreen(mouseX, mouseY, partialTicks); + // optional: create animations, draw additional elements, etc. + } + + @Override + protected void actionPerformed(GuiButton button) { + super.actionPerformed(button); + // optional: process any additional buttons added in initGui + } +} diff --git a/src/main/java/eu/olli/cowmoonication/config/MooGuiFactory.java b/src/main/java/eu/olli/cowmoonication/config/MooGuiFactory.java new file mode 100644 index 0000000..dbdb139 --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/config/MooGuiFactory.java @@ -0,0 +1,29 @@ +package eu.olli.cowmoonication.config; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.fml.client.IModGuiFactory; + +import java.util.Set; + +public class MooGuiFactory implements IModGuiFactory { + @Override + public void initialize(Minecraft minecraftInstance) { + + } + + @Override + public Class<? extends GuiScreen> mainConfigGuiClass() { + return MooGuiConfig.class; + } + + @Override + public Set<RuntimeOptionCategoryElement> runtimeGuiCategories() { + return null; + } + + @Override + public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element) { + return null; + } +} diff --git a/src/main/java/eu/olli/cowmoonication/listener/ChatListener.java b/src/main/java/eu/olli/cowmoonication/listener/ChatListener.java new file mode 100644 index 0000000..83b3890 --- /dev/null +++ b/src/main/java/eu/olli/cowmoonication/listener/ChatListener.java @@ -0,0 +1,124 @@ +package eu.olli.cowmoonication.listener; + +import eu.olli.cowmoonication.Cowmoonication; +import eu.olli.cowmoonication.config.MooConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiChat; +import net.minecraft.client.gui.GuiNewChat; +import net.minecraft.util.EnumChatFormatting; +import net.minecraft.util.IChatComponent; +import net.minecraftforge.client.event.ClientChatReceivedEvent; +import net.minecraftforge.client.event.GuiOpenEvent; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.apache.commons.lang3.CharUtils; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +import java.awt.*; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.StringSelection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ChatListener { + private static final Pattern PRIVATE_MESSAGE_RECEIVED_PATTERN = Pattern.compile("^From (?:\\[.*?] )?(\\w+): "); + private final Cowmoonication main; + private String lastTypedChars = ""; + private String lastPMSender; + + public ChatListener(Cowmoonication main) { + this.main = main; + } + + @SubscribeEvent + public void onLogInOutMessage(ClientChatReceivedEvent e) { + if (e.type != 2 && MooConfig.filterFriendNotifications) { // normal chat or system msg + String text = e.message.getUnformattedText(); + if (text.endsWith(" joined.") || text.endsWith(" left.") // Hypixel + || text.endsWith(" joined the game") || text.endsWith(" left the game.")) { // Spigot + // TODO maybe check which server thePlayer is on and check for logout pattern accordingly + int nameEnd = text.indexOf(" joined"); + if (nameEnd == -1) { + nameEnd = text.indexOf(" left"); + } + boolean isBestFriend = main.getFriends().isBestFriend(text.substring(0, nameEnd)); + if (!isBestFriend) { + e.setCanceled(true); + } + } + } + } + + @SubscribeEvent + public void onClickOnChat(GuiScreenEvent.MouseInputEvent.Pre e) { + if (e.gui instanceof GuiChat) { + if (!Mouse.getEventButtonState() && Mouse.getEventButton() == 1 && Keyboard.isKeyDown(Keyboard.KEY_LMENU)) { // alt key pressed and right mouse button being released + IChatComponent chatComponent = Minecraft.getMinecraft().ingameGUI.getChatGUI().getChatComponent(Mouse.getX(), Mouse.getY()); + if (chatComponent != null) { + String chatData = main.getUtils().cleanChatComponent(chatComponent); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(new StringSelection(chatData), null); + main.getUtils().sendAboveChatMessage(EnumChatFormatting.YELLOW + "Copied chat component to clipboard:", "" + EnumChatFormatting.BOLD + EnumChatFormatting.GOLD + "\u276E" + EnumChatFormatting.RESET + chatComponent.getUnformattedText() + EnumChatFormatting.BOLD + EnumChatFormatting.GOLD + "\u276F"); + } + } + } + } + + @SubscribeEvent + public void onReplyToMsg(GuiScreenEvent.KeyboardInputEvent.Pre e) { + // TODO Switch to more reliable way: GuiTextField#writeText on GuiChat#inputField (protected field) via reflections [using "Open Command"-key isn't detected currently] + if (lastPMSender != null && e.gui instanceof GuiChat && lastTypedChars.length() < 3 && Keyboard.getEventKeyState()) { + char eventCharacter = Keyboard.getEventCharacter(); + if (!CharUtils.isAsciiControl(eventCharacter)) { + lastTypedChars += eventCharacter; + if (lastTypedChars.equalsIgnoreCase("/r ")) { + // replace /r with /msg <last user> + main.getUtils().sendAboveChatMessage("Sending message to " + lastPMSender + "!"); + Minecraft.getMinecraft().displayGuiScreen(new GuiChat("/msg " + lastPMSender + " ")); + } + } else if (Keyboard.getEventKey() == Keyboard.KEY_BACK) { // Backspace + lastTypedChars = lastTypedChars.substring(0, Math.max(lastTypedChars.length() - 1, 0)); + } + } + } + + @SubscribeEvent + public void onChatOpen(GuiOpenEvent e) { + if (e.gui instanceof GuiChat) { + lastTypedChars = ""; + } + } + + @SubscribeEvent + public void onPrivateMsgReceive(ClientChatReceivedEvent e) { + if (e.type != 2) { + Matcher matcher = PRIVATE_MESSAGE_RECEIVED_PATTERN.matcher(e.message.getUnformattedText()); + if (matcher.find()) { + this.lastPMSender = matcher.group(1); + } + } + } + + @SubscribeEvent + public void onRenderChatGui(RenderGameOverlayEvent.Chat e) { + if (e.type == RenderGameOverlayEvent.ElementType.CHAT) { + // render message above chat box + String[] aboveChatMessage = main.getUtils().getAboveChatMessage(); + if (aboveChatMessage != null) { + float chatHeightFocused = Minecraft.getMinecraft().gameSettings.chatHeightFocused; + float chatScale = Minecraft.getMinecraft().gameSettings.chatScale; + int chatBoxHeight = (int) (GuiNewChat.calculateChatboxHeight(chatHeightFocused) * chatScale); + + int defaultTextY = e.resolution.getScaledHeight() - chatBoxHeight - 30; + + for (int i = 0; i < aboveChatMessage.length; i++) { + String msg = aboveChatMessage[i]; + int textY = defaultTextY - (aboveChatMessage.length - i) * (Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT + 1); + Minecraft.getMinecraft().fontRendererObj.drawStringWithShadow(msg, 2, textY, 0xffffff); + } + } + } + } +} diff --git a/src/main/resources/assets/cowmoonication/lang/en_us.lang b/src/main/resources/assets/cowmoonication/lang/en_us.lang new file mode 100644 index 0000000..b7e77d4 --- /dev/null +++ b/src/main/resources/assets/cowmoonication/lang/en_us.lang @@ -0,0 +1 @@ +cowmoonication:command.moo.usage=/moo diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info new file mode 100644 index 0000000..e6c8f6e --- /dev/null +++ b/src/main/resources/mcmod.info @@ -0,0 +1,13 @@ +[{ + "modid": "cowmoonication", + "name": "Cowmoonication", + "description": "Adding various things related to communication.", + "version": "${version}", + "mcversion": "${mcversion}", + "url": "https://github.com/cow-mc/Cowmoonication", + "updateUrl": "", + "authorList": ["Cow"], + "logoFile": "", + "screenshots": [], + "dependencies": [] +}] |