diff options
Diffstat (limited to 'src/main/java/eu')
8 files changed, 595 insertions, 0 deletions
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); + } + } + } + } +} |