diff --git a/src/main/java/io/github/homchom/recode/mod/commands/CommandHandler.java b/src/main/java/io/github/homchom/recode/mod/commands/CommandHandler.java index fddbf4a7..90597969 100644 --- a/src/main/java/io/github/homchom/recode/mod/commands/CommandHandler.java +++ b/src/main/java/io/github/homchom/recode/mod/commands/CommandHandler.java @@ -44,7 +44,8 @@ public static void load(CommandDispatcher dispatcher, new TitleCommand(), new SubTitleCommand(), new ActionbarCommand(), - new CalcCommand() + new CalcCommand(), + new CopyValueCommand() ); if (Config.getBoolean("dfCommands")) { diff --git a/src/main/java/io/github/homchom/recode/mod/commands/impl/text/CopyValueCommand.kt b/src/main/java/io/github/homchom/recode/mod/commands/impl/text/CopyValueCommand.kt new file mode 100644 index 00000000..425ceb94 --- /dev/null +++ b/src/main/java/io/github/homchom/recode/mod/commands/impl/text/CopyValueCommand.kt @@ -0,0 +1,190 @@ +package io.github.homchom.recode.mod.commands.impl.text + +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import com.mojang.brigadier.CommandDispatcher +import io.github.homchom.recode.mod.commands.Command +import io.github.homchom.recode.mod.commands.arguments.ArgBuilder +import io.github.homchom.recode.mod.config.Config +import io.github.homchom.recode.sys.player.chat.ChatType +import io.github.homchom.recode.sys.player.chat.ChatUtil +import io.github.homchom.recode.util.item.getDFValueItemData +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource +import net.minecraft.client.Minecraft +import net.minecraft.commands.CommandBuildContext +import net.minecraft.world.item.Items + +private const val COMMAND_NAME = "copyval" +private const val MUST_HOLD_AIR_ITEM = "You need to hold an item that is not air." +private const val MUST_USE_VALUE_ITEM = "You need to hold a value item, such as a location." +private const val INVALID_VALUE_ITEM_DATA = "The held item's value data is malformed. ('data' tag invalid)" +private const val INVALID_VALUE_ITEM_ID = "The held item's value data is malformed. ('id' tag invalid)" +private const val UNSUPPORTED_VALUE_ITEM = "This type of value item is unsupported for this command." +private const val SUCCESSFULLY_COPIED = "Value copied to clipboard!" + +// Numbers are stored as strings normally, this checks if a number string is a literal value or not +private val NUMBER_LITERAL_REGEX = Regex("\\d+(?:.\\d{0,3})?|.\\d{1,3}") + +/** + * This command will copy text to your clipboard based on the held DF Value. + * The value will be converted into text using a user-defined format in the mod config. + * This is primarily meant to be used for Text -> DiamondFire languages, allowing easy copying of locations in particular. + */ +class CopyValueCommand : Command() { + override fun register( + mc: Minecraft, + cd: CommandDispatcher, + context: CommandBuildContext + ) { + cd.register(ArgBuilder.literal(COMMAND_NAME).executes cmd@{ _ -> + val heldItem = mc.player!!.mainHandItem + + // Disallow air items entirely + if (heldItem.item == Items.AIR) { + ChatUtil.sendMessage(MUST_HOLD_AIR_ITEM, ChatType.FAIL) + return@cmd -1 + } + + // Get valueitem data or error if it doesn't exist + val compound = heldItem.getDFValueItemData() + if (compound == null) { + ChatUtil.sendMessage(MUST_USE_VALUE_ITEM, ChatType.FAIL) + return@cmd -1 + } + + // Technically .asString can return "12" and whatnot for a number tag, but that doesn't matter in this case. + val id = compound.get("id")?.asJsonPrimitive?.asString + val result = when (id) { + // Location item + "loc" -> { + val dataObject = (compound.get("data") as? JsonObject)?.get("loc") as? JsonObject + val x = (dataObject?.get("x") as? JsonPrimitive)?.asDouble + val y = (dataObject?.get("y") as? JsonPrimitive)?.asDouble + val z = (dataObject?.get("z") as? JsonPrimitive)?.asDouble + val pitch = (dataObject?.get("pitch") as? JsonPrimitive)?.asDouble + val yaw = (dataObject?.get("yaw") as? JsonPrimitive)?.asDouble + + if (dataObject != null && x != null && y != null && z != null && pitch != null && yaw != null) { + val expression = Config.getString("itemTextFormatLocation") ?: "[%f, %f, %f, %f, %f]" + expression.format(x, y, z, pitch, yaw) + } else { + null + } + } + + // Vector item + "vec" -> { + val dataObject = compound.get("data") as? JsonObject + val x = (dataObject?.get("x") as? JsonPrimitive)?.asDouble + val y = (dataObject?.get("y") as? JsonPrimitive)?.asDouble + val z = (dataObject?.get("z") as? JsonPrimitive)?.asDouble + + if (dataObject != null && x != null && y != null && z != null) { + val expression = Config.getString("itemTextFormatVector") ?: "<%f, %f, %f>" + expression.format(x, y, z) + } else { + null + } + } + + // String + "txt" -> { + val dataObject = compound.get("data") as? JsonObject + val name = (dataObject?.get("name") as? JsonPrimitive)?.asString + + if (dataObject != null && name != null) { + // TODO Could be replaced with a more powerful thing? (Such as a list of regex post-process replacement rules? Feels overkill) + val delimiter = Config.getString("itemTextFormatStringDelimiter") ?: "\"" + val escape = Config.getString("itemTextFormatStringEscape") ?: "\\" + val expression = Config.getString("itemTextFormatString") ?: "\"%s\"" + + if (delimiter.isNotEmpty() && escape.isNotEmpty()) { + val newName = name + .replace(escape, "$escape$escape") + .replace(delimiter, "$escape$delimiter") + + expression.format(newName) + } else { + expression.format(name) + } + } else { + null + } + } + + // Styled Text + "comp" -> { + val dataObject = compound.get("data") as? JsonObject + val name = (dataObject?.get("name") as? JsonPrimitive)?.asString + + if (dataObject != null && name != null) { + val delimiter = Config.getString("itemTextFormatTextDelimiter") ?: "\"" + val escape = Config.getString("itemTextFormatTextEscape") ?: "\\" + val expression = Config.getString("itemTextFormatText") ?: "T\"%s\"" + + if (delimiter.isNotEmpty() && escape.isNotEmpty()) { + val newName = name + .replace(escape, "$escape$escape") + .replace(delimiter, "$escape$delimiter") + + expression.format(newName) + } else { + expression.format(name) + } + } else { + null + } + } + + // Number + "num" -> { + val dataObject = compound.get("data") as? JsonObject + val name = (dataObject?.get("name") as? JsonPrimitive)?.asString // Yes, asString + + if (dataObject != null && name != null) { + val expression = if (NUMBER_LITERAL_REGEX.matches(name)) { + Config.getString("itemTextFormatNumberLiteral") ?: "%s" + } else { + Config.getString("itemTextFormatNumber") ?: "%s" + } + + expression.format(name) + } else { + null + } + } + + // General + null -> { + ChatUtil.sendMessage(INVALID_VALUE_ITEM_ID, ChatType.FAIL) + return@cmd -1 + } + + else -> { + ChatUtil.sendMessage(UNSUPPORTED_VALUE_ITEM, ChatType.FAIL) + return@cmd -1 + } + } + + if (result != null) { + mc.keyboardHandler.clipboard = result + ChatUtil.sendMessage(SUCCESSFULLY_COPIED, ChatType.SUCCESS) + return@cmd 1 + } + + ChatUtil.sendMessage(INVALID_VALUE_ITEM_DATA, ChatType.FAIL) + return@cmd -1 + }) + } + + override fun getDescription(): String { + return """ + [blue]/$COMMAND_NAME [text][reset] + + Copies the held DF value item to clipboard using the format defined in the mod config. + """.trimIndent() + } + + override fun getName() = "/$COMMAND_NAME" + +} \ No newline at end of file diff --git a/src/main/java/io/github/homchom/recode/mod/config/impl/CommandsGroup.java b/src/main/java/io/github/homchom/recode/mod/config/impl/CommandsGroup.java index abdb7d17..2a4835eb 100644 --- a/src/main/java/io/github/homchom/recode/mod/config/impl/CommandsGroup.java +++ b/src/main/java/io/github/homchom/recode/mod/config/impl/CommandsGroup.java @@ -5,6 +5,7 @@ import io.github.homchom.recode.mod.config.types.BooleanSetting; import io.github.homchom.recode.mod.config.types.IntegerSetting; import io.github.homchom.recode.mod.config.types.LongSetting; +import io.github.homchom.recode.mod.config.types.StringSetting; public class CommandsGroup extends ConfigGroup { public CommandsGroup(String name) { @@ -30,5 +31,22 @@ public void initialize() { heads.register(new BooleanSetting("headsEnabled", false)); heads.register(new IntegerSetting("headMenuMaxRender", 1000)); this.register(heads); + + // copyval + ConfigSubGroup copyVal = new ConfigSubGroup("copyval"); + copyVal.register(new StringSetting("itemTextFormatNumber", "%s")); + copyVal.register(new StringSetting("itemTextFormatNumberLiteral", "%s")); + + copyVal.register(new StringSetting("itemTextFormatLocation", "[%f, %f, %f, %f, %f]")); + copyVal.register(new StringSetting("itemTextFormatVector", "<%f, %f, %f>")); + + copyVal.register(new StringSetting("itemTextFormatString", "\"%s\"")); + copyVal.register(new StringSetting("itemTextFormatStringEscape", "\\")); + copyVal.register(new StringSetting("itemTextFormatStringDelimiter", "\"")); + + copyVal.register(new StringSetting("itemTextFormatText", "T\"%s\"")); + copyVal.register(new StringSetting("itemTextFormatTextEscape", "\\")); + copyVal.register(new StringSetting("itemTextFormatTextDelimiter", "\"")); + this.register(copyVal); } } diff --git a/src/main/java/io/github/homchom/recode/util/item/ValueItemUtils.kt b/src/main/java/io/github/homchom/recode/util/item/ValueItemUtils.kt new file mode 100644 index 00000000..e5d05f1a --- /dev/null +++ b/src/main/java/io/github/homchom/recode/util/item/ValueItemUtils.kt @@ -0,0 +1,33 @@ +package io.github.homchom.recode.util.item + +import com.google.gson.Gson +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import net.minecraft.nbt.CompoundTag +import net.minecraft.world.item.ItemStack + +/** + * If this ItemStack is a DF ValueItem, returns its json object, otherwise return null. + * ValueItem compounds are in the following format: + * + * ```json + * { + * "id": , + * "data": + * } + * ``` + * + * @return This item's DF ValueItem JsonObject, if one exists. + */ +fun ItemStack.getDFValueItemData() : JsonObject? { + // TODO May need to change once Item Components release? + val nbt = this.tag + ?.getCompound("PublicBukkitValues") + ?.getString("hypercube:varitem") + + if (nbt == null) { + return null + } + + return JsonParser.parseString(nbt) as? JsonObject +} \ No newline at end of file diff --git a/src/main/resources/assets/recode/lang/en_us.json b/src/main/resources/assets/recode/lang/en_us.json index 690556b0..e29e46d8 100644 --- a/src/main/resources/assets/recode/lang/en_us.json +++ b/src/main/resources/assets/recode/lang/en_us.json @@ -91,6 +91,35 @@ "config.recode.option.autotimeval": "Auto /time value", "config.recode.option.autotimeval.tooltip": "Value to use in auto /time", + + "config.recode.subcategory.commands_copyval": "Copied Value Text Form", + "config.recode.subcategory.commands_copyval.tooltip": "Changes how /copyval generates\ntext versions of values.\nUses Java's formatting rules.", + + "config.recode.option.itemTextFormatNumber": "Number", + "config.recode.option.itemTextFormatNumber.tooltip": "Args:\n1. Value (String)", + "config.recode.option.itemTextFormatNumberLiteral": "Number Literal", + "config.recode.option.itemTextFormatNumberLiteral.tooltip": "Args:\n1. Value (String)\n\nUsed when a number value is just a single\nnumber with no % expressions.", + + "config.recode.option.itemTextFormatLocation": "Location", + "config.recode.option.itemTextFormatLocation.tooltip": "Args:\n1. X (double)\n2. Y (double)\n3. Z (double)\n4. Pitch (double)\n5. Yaw (double)", + "config.recode.option.itemTextFormatVector": "Vector", + "config.recode.option.itemTextFormatVector.tooltip": "Args:\n1. X (double)\n2. Y (double)\n3. Z (double)", + + "config.recode.option.itemTextFormatString": "String", + "config.recode.option.itemTextFormatString.tooltip": "Args:\n1. Value (String)", + "config.recode.option.itemTextFormatStringDelimiter": "String - Delimiter", + "config.recode.option.itemTextFormatStringDelimiter.tooltip": "If both this and delimiter are set,\nevery instance of this delimiter in\nthe source text will be preceded\nby the escape code.", + "config.recode.option.itemTextFormatStringEscape": "String - Escape", + "config.recode.option.itemTextFormatStringEscape.tooltip": "If both this and delimiter are set,\nevery instance of this escape will\nbe preceded by another escape code.", + + "config.recode.option.itemTextFormatText": "Styled Text", + "config.recode.option.itemTextFormatText.tooltip": "Args:\n1. Value (String)", + "config.recode.option.itemTextFormatTextDelimiter": "Styled Text - Delimiter", + "config.recode.option.itemTextFormatTextDelimiter.tooltip": "If both this and delimiter are set,\nevery instance of this delimiter in\nthe source text will be preceded\nby the escape code.", + "config.recode.option.itemTextFormatTextEscape": "Styled Text - Escape", + "config.recode.option.itemTextFormatTextEscape.tooltip": "If both this and delimiter are set,\nevery instance of this escape will\nbe preceded by another escape code.", + + "config.recode.option.autowand": "Auto //wand", "config.recode.option.autowand.tooltip": "Automatically runs //wand upon\nentering build mode", "config.recode.option.autoRC": "Auto /rc",