aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/de/cowtipper
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/cowtipper')
-rw-r--r--src/main/java/de/cowtipper/cowlection/Cowlection.java5
-rw-r--r--src/main/java/de/cowtipper/cowlection/command/MooCommand.java29
-rw-r--r--src/main/java/de/cowtipper/cowlection/config/MooConfig.java21
-rw-r--r--src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java77
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/ImageUtils.java23
-rw-r--r--src/main/java/de/cowtipper/cowlection/util/Utils.java61
6 files changed, 137 insertions, 79 deletions
diff --git a/src/main/java/de/cowtipper/cowlection/Cowlection.java b/src/main/java/de/cowtipper/cowlection/Cowlection.java
index 23ff2a8..2d7a988 100644
--- a/src/main/java/de/cowtipper/cowlection/Cowlection.java
+++ b/src/main/java/de/cowtipper/cowlection/Cowlection.java
@@ -80,8 +80,9 @@ public class Cowlection {
ClientCommandHandler.instance.registerCommand(new TabCompletableCommand(this, tabCompletableNamesCommand));
}
// key bindings
- keyBindings = new KeyBinding[1];
- keyBindings[0] = new KeyBinding("key.cowlection.moo", Keyboard.KEY_M, "key.cowlection.category");
+ keyBindings = new KeyBinding[2];
+ keyBindings[0] = new KeyBinding("key.cowlection.moo", Keyboard.KEY_NONE, "key.cowlection.category");
+ keyBindings[1] = new KeyBinding("key.cowlection.waila", Keyboard.KEY_NONE, "key.cowlection.category");
for (KeyBinding keyBinding : keyBindings) {
ClientRegistry.registerKeyBinding(keyBinding);
diff --git a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java
index ceff45d..2849666 100644
--- a/src/main/java/de/cowtipper/cowlection/command/MooCommand.java
+++ b/src/main/java/de/cowtipper/cowlection/command/MooCommand.java
@@ -822,8 +822,7 @@ public class MooCommand extends CommandBase {
NBTTagCompound relevantNbt = tldrInfo(nbt, showAllInfo);
BlockPos skullPos = skull.getPos();
relevantNbt.setTag("__position", new NBTTagIntArray(new int[]{skullPos.getX(), skullPos.getY(), skullPos.getZ()}));
- GuiScreen.setClipboardString(GsonUtils.toJson(relevantNbt, true));
- main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Copied skull data to clipboard.");
+ Utils.copyToClipboardOrSaveAsFile("skull data", "skull", relevantNbt, true);
return;
}
} else if (te instanceof TileEntitySign) {
@@ -833,8 +832,7 @@ public class MooCommand extends CommandBase {
nbt.setString("Text" + (lineNr + 1), sign.signText[lineNr].getFormattedText());
nbt.setString("TextUnformatted" + (lineNr + 1), sign.signText[lineNr].getUnformattedText());
}
- GuiScreen.setClipboardString(GsonUtils.toJson(nbt, true));
- main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Copied sign data to clipboard.");
+ Utils.copyToClipboardOrSaveAsFile("sign data", "sign", nbt, true);
return;
} else if (te instanceof TileEntityBanner) {
List<String> possiblePatterns = Arrays.asList("b", "bl", "bo", "br", "bri", "bs", "bt", "bts", "cbo", "cr", "cre", "cs", "dls", "drs", "flo", "gra", "hh", "ld", "ls", "mc", "moj", "mr", "ms", "rd", "rs", "sc", "sku", "ss", "tl", "tr", "ts", "tt", "tts", "vh", "lud", "rud", "gru", "hhb", "vhr");
@@ -866,22 +864,18 @@ public class MooCommand extends CommandBase {
if (entity instanceof EntityArmorStand) {
// looking at non-invisible armor stand (e.g. Minion)
EntityArmorStand armorStand = (EntityArmorStand) entity;
- copyEntityInfoToClipboard(armorStand, showAllInfo);
- main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Copied armor stand '" + armorStand.getName() + EnumChatFormatting.GREEN + "' to clipboard.");
+ copyEntityInfoToClipboard("armor stand '" + armorStand.getName() + EnumChatFormatting.GREEN + "'", "armorstand", armorStand, showAllInfo);
return;
} else if (entity instanceof EntityOtherPlayerMP) {
// looking at NPC or another player
EntityOtherPlayerMP otherPlayer = (EntityOtherPlayerMP) entity;
- copyEntityInfoToClipboard(otherPlayer, showAllInfo);
- main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Copied player/npc '" + otherPlayer.getDisplayNameString() + EnumChatFormatting.GREEN + "' to clipboard.");
+ copyEntityInfoToClipboard("player/npc '" + otherPlayer.getDisplayNameString() + EnumChatFormatting.GREEN + "'", "npc_" + otherPlayer.getDisplayNameString(), otherPlayer, showAllInfo);
return;
} else if (entity instanceof EntityItemFrame) {
EntityItemFrame itemFrame = (EntityItemFrame) entity;
- copyEntityInfoToClipboard(itemFrame, showAllInfo);
ItemStack displayedItem = itemFrame.getDisplayedItem();
if (displayedItem != null) {
-
NBTTagCompound nbt = new NBTTagCompound();
if (displayedItem.getItem() == Items.filled_map) {
// filled map
@@ -897,14 +891,12 @@ public class MooCommand extends CommandBase {
} else {
displayedItem.writeToNBT(nbt);
}
- GuiScreen.setClipboardString(GsonUtils.toJson(nbt, true));
- main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Copied item in item frame '" + displayedItem.getDisplayName() + EnumChatFormatting.GREEN + "' to clipboard.");
+ Utils.copyToClipboardOrSaveAsFile("item in item frame '" + displayedItem.getDisplayName() + EnumChatFormatting.GREEN + "'", "itemframe-item_" + displayedItem.getDisplayName(), nbt, true);
return;
}
} else if (entity instanceof EntityLiving) {
EntityLiving living = (EntityLiving) entity;
- copyEntityInfoToClipboard(living, showAllInfo);
- main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Copied mob '" + living.getName() + EnumChatFormatting.GREEN + "' to clipboard.");
+ copyEntityInfoToClipboard("mob '" + living.getName() + EnumChatFormatting.GREEN + "'", "mob_" + living.getName(), living, showAllInfo);
return;
}
break;
@@ -934,8 +926,7 @@ public class MooCommand extends CommandBase {
entities.appendTag(relevantNbt);
}
- GuiScreen.setClipboardString(GsonUtils.toJson(entities, true));
- main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Copied " + nearbyEntities.size() + " nearby entities to clipboard.");
+ Utils.copyToClipboardOrSaveAsFile(nearbyEntities.size() + " nearby entities", "entities", entities, true);
} else {
main.getChatHelper().sendMessage(EnumChatFormatting.RED, "You stare into the void... and see nothing of interest. " + EnumChatFormatting.GRAY + "Try looking at: NPCs, mobs, armor stands, placed skulls, banners, signs, dropped items, item in item frames, or maps on a wall.");
}
@@ -988,9 +979,9 @@ public class MooCommand extends CommandBase {
return relevantNbt;
}
- private void copyEntityInfoToClipboard(Entity entity, boolean showAllInfo) {
+ private void copyEntityInfoToClipboard(String what, String fileName, Entity entity, boolean showAllInfo) {
NBTTagCompound relevantNbt = extractEntityInfo(entity, showAllInfo);
- GuiScreen.setClipboardString(GsonUtils.toJson(relevantNbt, true));
+ Utils.copyToClipboardOrSaveAsFile(what, fileName, relevantNbt, true);
}
private NBTTagCompound tldrInfo(NBTTagCompound nbt, boolean showAllInfo) {
@@ -1143,7 +1134,7 @@ public class MooCommand extends CommandBase {
.appendSibling(createCmdHelpEntry("stalkskyblock", "Get info of player's SkyBlock stats §d§l⚷"))
.appendSibling(createCmdHelpEntry("analyzeChests", "Analyze chests' contents and evaluate potential Bazaar value"))
.appendSibling(createCmdHelpEntry("analyzeIsland", "Analyze a SkyBlock private island (inspect minions)"))
- .appendSibling(createCmdHelpEntry("waila", "Copy the 'thing' you're looking at"))
+ .appendSibling(createCmdHelpEntry("waila", "Copy the 'thing' you're looking at (optional keybinding: Minecraft controls > Cowlection)"))
.appendSibling(createCmdHelpEntry("dungeon", "SkyBlock Dungeons: display current dungeon performance"))
.appendSibling(createCmdHelpEntry("dungeon party", "SkyBlock Dungeons: Shows armor and dungeon info about current party members " + EnumChatFormatting.GRAY + "(alias: " + EnumChatFormatting.WHITE + "/" + getCommandName() + " dp" + EnumChatFormatting.GRAY + ") §d§l⚷"))
.appendSibling(createCmdHelpSection(3, "Miscellaneous"))
diff --git a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java
index 5006953..ea980d0 100644
--- a/src/main/java/de/cowtipper/cowlection/config/MooConfig.java
+++ b/src/main/java/de/cowtipper/cowlection/config/MooConfig.java
@@ -55,6 +55,7 @@ public class MooConfig {
public static boolean fixReplyCmd;
public static boolean enableCopyInventory;
private static String wailaLevelOfDetail;
+ private static String copyOrSaveWailaAndInventoryData;
public static String[] tabCompletableNamesCommands;
private static final String CATEGORY_LOGS_SEARCH = "logssearch";
public static String[] logsDirs;
@@ -250,14 +251,13 @@ public class MooConfig {
// Sub-Category: Cowlection config gui
MooConfigCategory.SubCategory subCat = configCat.addSubCategory("Cowlection config gui");
- subCat.addExplanations("Display of the explanations for each sub-section:",
- " ‣ " + EnumChatFormatting.YELLOW + "as tooltip ①" + EnumChatFormatting.DARK_GRAY + "⬛" + EnumChatFormatting.RESET + " = tooltip when hovering over sub-category heading (with darkened background)",
- " ‣ " + EnumChatFormatting.YELLOW + "as tooltip ②" + EnumChatFormatting.WHITE + "⬛" + EnumChatFormatting.RESET + " = tooltip when hovering over sub-category heading (no extra background)",
+ subCat.addExplanations("Display of the explanations for each sub-section marked with §2❢§r",
+ " ‣ " + EnumChatFormatting.YELLOW + "as tooltip" + EnumChatFormatting.RESET + " = tooltip when hovering over sub-category heading",
" ‣ " + EnumChatFormatting.YELLOW + "as text" + EnumChatFormatting.RESET + " = below each sub-category heading",
" ‣ " + EnumChatFormatting.YELLOW + "hidden" + EnumChatFormatting.RESET + " = ");
Property propConfigGuiExplanations = subCat.addConfigEntry(cfg.get(configCat.getConfigName(),
- "configGuiExplanations", "tooltip ① §0⬛", "Display config settings explanations",
- new String[]{"as tooltip ①§0⬛", "as tooltip ②§f⬛", "as text", "hidden"}));
+ "configGuiExplanations", "as tooltip", "Display config settings explanations",
+ new String[]{"as tooltip", "as text", "hidden"}));
// (not visible in config gui: has opened config? or: knows how to move the dungeon overlay?)
@@ -284,6 +284,8 @@ public class MooConfig {
"enableCopyInventory", false, "Enable copy inventory with CTRL + C?"));
Property propWailaLevelOfDetail = subCat.addConfigEntry(cfg.get(configCat.getConfigName(),
"wailaLevelOfDetail", "main info", "Level of detail of /moo waila", new String[]{"main info", "all info"}));
+ Property propCopyOrSaveWailaAndInventoryData = subCat.addConfigEntry(cfg.get(configCat.getConfigName(),
+ "copyOrSaveWailaAndInventoryData", "to clipboard", "Copy or save waila and inventory data?", new String[]{"to clipboard", "save file"}));
// Sub-Category: Tab-completable names in commands
subCat = configCat.addSubCategory("Tab-completable usernames");
@@ -636,6 +638,7 @@ public class MooConfig {
fixReplyCmd = propFixReplyCmd.getBoolean();
enableCopyInventory = propEnableCopyInventory.getBoolean();
wailaLevelOfDetail = propWailaLevelOfDetail.getString();
+ copyOrSaveWailaAndInventoryData = propCopyOrSaveWailaAndInventoryData.getString();
tabCompletableNamesCommands = propTabCompletableNamesCommands.getStringList();
logsDirs = propLogsDirs.getStringList();
defaultStartDate = propDefaultStartDate.getString().trim();
@@ -718,6 +721,7 @@ public class MooConfig {
propFixReplyCmd.set(fixReplyCmd);
propEnableCopyInventory.set(enableCopyInventory);
propWailaLevelOfDetail.set(wailaLevelOfDetail);
+ propCopyOrSaveWailaAndInventoryData.set(copyOrSaveWailaAndInventoryData);
propTabCompletableNamesCommands.set(tabCompletableNamesCommands);
propLogsDirs.set(logsDirs);
propDefaultStartDate.set(defaultStartDate);
@@ -886,6 +890,10 @@ public class MooConfig {
return "all info".equals(wailaLevelOfDetail);
}
+ public static boolean copyWailaAndInventoryDataToClipboard() {
+ return "to clipboard".equals(copyOrSaveWailaAndInventoryData);
+ }
+
// Category: Notifications
/**
@@ -1100,7 +1108,7 @@ public class MooConfig {
case "always":
return ALWAYS;
case "as a tooltip":
- case "as tooltip ②§f⬛":
+ case "as tooltip":
return TOOLTIP;
case "in chat":
case "as text":
@@ -1112,7 +1120,6 @@ public class MooConfig {
return DISABLED;
case "on SkyBlock":
case "key press":
- case "as tooltip ①§0⬛":
case "a letter":
return SPECIAL;
default:
diff --git a/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java b/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java
index 5e1ad0b..5b5a4e6 100644
--- a/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java
+++ b/src/main/java/de/cowtipper/cowlection/listener/PlayerListener.java
@@ -6,25 +6,25 @@ import de.cowtipper.cowlection.config.MooConfig;
import de.cowtipper.cowlection.event.ApiErrorEvent;
import de.cowtipper.cowlection.listener.skyblock.DungeonsListener;
import de.cowtipper.cowlection.listener.skyblock.SkyBlockListener;
-import de.cowtipper.cowlection.util.AbortableRunnable;
-import de.cowtipper.cowlection.util.GsonUtils;
-import de.cowtipper.cowlection.util.MooChatComponent;
-import de.cowtipper.cowlection.util.TickDelay;
+import de.cowtipper.cowlection.util.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiChat;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.inventory.GuiChest;
+import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.gui.inventory.GuiInventory;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.ContainerChest;
import net.minecraft.inventory.IInventory;
+import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.scoreboard.ScoreObjective;
import net.minecraft.util.EnumChatFormatting;
+import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerSetSpawnEvent;
@@ -55,41 +55,58 @@ public class PlayerListener {
if (mc.currentScreen == null && mc.gameSettings.chatVisibility != EntityPlayer.EnumChatVisibility.HIDDEN) {
mc.displayGuiScreen(new GuiChat("/moo "));
}
+ } else if (keyBindings[1].isPressed()) {
+ Minecraft mc = Minecraft.getMinecraft();
+ if (mc.currentScreen == null) {
+ ClientCommandHandler.instance.executeCommand(mc.thePlayer, "/moo waila");
+ }
}
}
@SubscribeEvent
public void onKeyboardInput(GuiScreenEvent.KeyboardInputEvent.Pre e) {
if (MooConfig.enableCopyInventory && Keyboard.getEventKeyState() && Keyboard.getEventKey() == Keyboard.KEY_C && GuiScreen.isCtrlKeyDown()) {
- // ctrl + C
- IInventory inventory;
- String inventoryName;
- if (e.gui instanceof GuiChest) {
- // some kind of chest
- ContainerChest chestContainer = (ContainerChest) ((GuiChest) e.gui).inventorySlots;
- inventory = chestContainer.getLowerChestInventory();
- inventoryName = (inventory.hasCustomName() ? EnumChatFormatting.getTextWithoutFormattingCodes(inventory.getDisplayName().getUnformattedTextForChat()) : inventory.getName());
- } else if (e.gui instanceof GuiInventory) {
- // player inventory
- inventory = Minecraft.getMinecraft().thePlayer.inventory;
- inventoryName = "Player inventory";
+ if (GuiScreen.isShiftKeyDown()) {
+ // ctrl + shift + C
+ if (e.gui instanceof GuiContainer) {
+ Slot slotUnderMouse = GuiHelper.getSlotUnderMouse((GuiContainer) e.gui);
+ if (slotUnderMouse != null && slotUnderMouse.getHasStack()) {
+ ItemStack itemUnderMouse = slotUnderMouse.getStack();
+ NBTTagCompound itemNbt = new NBTTagCompound();
+ itemUnderMouse.writeToNBT(itemNbt);
+ Utils.copyToClipboardOrSaveAsFile(itemUnderMouse.getDisplayName() + EnumChatFormatting.RESET + EnumChatFormatting.GREEN, "item_" + itemUnderMouse.getDisplayName(), itemNbt, false);
+ }
+ }
} else {
- // another gui, abort!
- return;
- }
- NBTTagList items = new NBTTagList();
- for (int slot = 0; slot < inventory.getSizeInventory(); slot++) {
- ItemStack item = inventory.getStackInSlot(slot);
- if (item != null) {
- // slot + item
- NBTTagCompound tag = new NBTTagCompound();
- tag.setByte("Slot", (byte) slot);
- item.writeToNBT(tag);
- items.appendTag(tag);
+ // ctrl + C
+ IInventory inventory;
+ String inventoryName;
+ if (e.gui instanceof GuiChest) {
+ // some kind of chest
+ ContainerChest chestContainer = (ContainerChest) ((GuiChest) e.gui).inventorySlots;
+ inventory = chestContainer.getLowerChestInventory();
+ inventoryName = (inventory.hasCustomName() ? EnumChatFormatting.getTextWithoutFormattingCodes(inventory.getDisplayName().getUnformattedTextForChat()) : inventory.getName());
+ } else if (e.gui instanceof GuiInventory) {
+ // player inventory
+ inventory = Minecraft.getMinecraft().thePlayer.inventory;
+ inventoryName = "Player inventory";
+ } else {
+ // another gui, abort!
+ return;
+ }
+ NBTTagList items = new NBTTagList();
+ for (int slot = 0; slot < inventory.getSizeInventory(); slot++) {
+ ItemStack item = inventory.getStackInSlot(slot);
+ if (item != null) {
+ // slot + item
+ NBTTagCompound tag = new NBTTagCompound();
+ tag.setByte("Slot", (byte) slot);
+ item.writeToNBT(tag);
+ items.appendTag(tag);
+ }
}
+ Utils.copyToClipboardOrSaveAsFile(items.tagCount() + " items from '" + inventoryName + "'", "inventory_" + inventoryName, items, false);
}
- GuiScreen.setClipboardString(GsonUtils.toJson(items));
- main.getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Copied " + items.tagCount() + " items from '" + inventoryName + "' to clipboard!");
}
}
diff --git a/src/main/java/de/cowtipper/cowlection/util/ImageUtils.java b/src/main/java/de/cowtipper/cowlection/util/ImageUtils.java
index 1d6820f..f96b439 100644
--- a/src/main/java/de/cowtipper/cowlection/util/ImageUtils.java
+++ b/src/main/java/de/cowtipper/cowlection/util/ImageUtils.java
@@ -13,8 +13,6 @@ import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
public class ImageUtils {
public static int getTierFromTexture(String minionSkinId) {
@@ -131,12 +129,10 @@ public class ImageUtils {
}
}
try {
- File cowlectionImagePath = new File(Minecraft.getMinecraft().mcDataDir, "cowlection_images");
- if (!cowlectionImagePath.exists() && !cowlectionImagePath.mkdirs()) {
- // dir didn't exist and couldn't be created
+ File imageFile = Utils.getTimestampedFileForDirectory("map", "png");
+ if(imageFile == null) {
return null;
}
- File imageFile = getTimestampedPngFileForDirectory(cowlectionImagePath, "map");
ImageIO.write(image, "png", imageFile);
return imageFile.getCanonicalFile();
} catch (IOException e) {
@@ -179,19 +175,4 @@ public class ImageUtils {
return new Color((shadeMul * c.getRed()) / 255, (shadeMul * c.getGreen()) / 255, (shadeMul * c.getBlue()) / 255, c.getAlpha());
}
- /**
- * Based on ScreenShotHelper#getTimestampedPNGFileForDirectory
- */
- private static File getTimestampedPngFileForDirectory(File directory, String prefix) {
- String currentDateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"));
- int i = 1;
-
- while (true) {
- File timestampedFile = new File(directory, prefix + "_" + currentDateTime + (i == 1 ? "" : "_" + i) + ".png");
- if (!timestampedFile.exists()) {
- return timestampedFile;
- }
- ++i;
- }
- }
}
diff --git a/src/main/java/de/cowtipper/cowlection/util/Utils.java b/src/main/java/de/cowtipper/cowlection/util/Utils.java
index 4b5c73c..dccf911 100644
--- a/src/main/java/de/cowtipper/cowlection/util/Utils.java
+++ b/src/main/java/de/cowtipper/cowlection/util/Utils.java
@@ -1,6 +1,11 @@
package de.cowtipper.cowlection.util;
import com.mojang.realmsclient.util.Pair;
+import de.cowtipper.cowlection.Cowlection;
+import de.cowtipper.cowlection.config.MooConfig;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiScreen;
+import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumChatFormatting;
import org.apache.commons.lang3.StringUtils;
@@ -10,9 +15,14 @@ import org.apache.commons.lang3.time.DurationFormatUtils;
import java.io.File;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
@@ -365,4 +375,55 @@ public final class Utils {
hit = sb.indexOf(search);
}
}
+
+ public static void copyToClipboardOrSaveAsFile(String what, String fileName, NBTBase data, boolean sortData) {
+ String nbt = GsonUtils.toJson(data, sortData);
+ if (MooConfig.copyWailaAndInventoryDataToClipboard()) {
+ GuiScreen.setClipboardString(nbt);
+ Cowlection.getInstance().getChatHelper().sendMessage(EnumChatFormatting.GREEN, "Copied " + what + " to clipboard.");
+ } else {
+ try {
+ File target = getTimestampedFileForDirectory(fileName, "json");
+ if (target == null) {
+ return;
+ }
+ Files.write(target.toPath(), nbt.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE_NEW);
+
+ File targetNormalized = target.getCanonicalFile();
+
+ Cowlection.getInstance().getChatHelper().sendMessage(new MooChatComponent("Saved " + what).green()
+ .appendSibling(new MooChatComponent(" [open file]").gold().setOpenFile(targetNormalized))
+ .appendSibling(new MooChatComponent(" [open folder]").darkAqua().setOpenFile(targetNormalized.getParentFile())));
+ } catch (IOException | UnsupportedOperationException e) {
+ e.printStackTrace();
+ Cowlection.getInstance().getChatHelper().sendMessage(EnumChatFormatting.RED, "Couldn't save " + what + ": " + e.toString());
+ }
+ }
+ }
+
+ /**
+ * Based on ScreenShotHelper#getTimestampedPNGFileForDirectory
+ */
+ static File getTimestampedFileForDirectory(String suffix, String fileType) {
+ File cowlectionOutPath = new File(Minecraft.getMinecraft().mcDataDir, Cowlection.MODID.toLowerCase() + "_out");
+ if (!cowlectionOutPath.exists() && !cowlectionOutPath.mkdirs()) {
+ // dir didn't exist and couldn't be created
+ return null;
+ }
+
+ String currentDateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"));
+ String sanitizedSuffix = StringUtils.replaceEach(EnumChatFormatting.getTextWithoutFormattingCodes(suffix).trim(),
+ // replacement characters from https://stackoverflow.com/a/61448658
+ new String[]{"\\", "/", ":", "*", "?", "\"", "<", ">", "|"},
+ new String[]{"⧵", "∕", "∶", "∗", "?", "“", "‹", "›", "∣"});
+ String baseName = currentDateTime + "_" + sanitizedSuffix;
+ int i = 1;
+ while (true) {
+ File timestampedFile = new File(cowlectionOutPath, baseName + (i == 1 ? "" : "_" + i) + "." + fileType);
+ if (!timestampedFile.exists()) {
+ return timestampedFile;
+ }
+ ++i;
+ }
+ }
}