diff --git a/.gitignore b/.gitignore index 3408caf..30ff733 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ libraries/ logs/ generated/ output/ +states.txt +states_output.txt ### Java files ### *.class diff --git a/src/main/java/com/viaversion/mappingsgenerator/helper/AnnoyingBlockStateMapper.java b/src/main/java/com/viaversion/mappingsgenerator/helper/AnnoyingBlockStateMapper.java new file mode 100644 index 0000000..10f8898 --- /dev/null +++ b/src/main/java/com/viaversion/mappingsgenerator/helper/AnnoyingBlockStateMapper.java @@ -0,0 +1,88 @@ +package com.viaversion.mappingsgenerator.helper; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.viaversion.mappingsgenerator.util.GsonUtil; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import static com.viaversion.mappingsgenerator.MappingsOptimizer.MAPPINGS_DIR; + +/** + * Similar to {@link BlockStateMapper} in use, except for the array of wood/stone-based building blocks. + *

+ * Directly edits the diff file. + */ +final class AnnoyingBlockStateMapper { + + private static final List> MAPPERS = new ArrayList<>(); + private static final List WONDERFUL_STATES = List.of( + "_wood", "_log", "_sapling", "_wall", "_slab", "_stairs", + "_trapdoor", "_door", "_button", "_hanging_sign", "_wall_sign", "_sign", + "_leaves", "_fence_gate", "_fence", "_pressure_plate" + ); + + public static void main(final String[] args) throws IOException { + // Input examples + final String from = "1.21.4"; + final String to = "1.21.2"; + replace("resin_brick", "brick"); + replace("tuff", "andesite"); + contains("copper", "brick"); + + final Path path = MAPPINGS_DIR.resolve("diff").resolve(String.format("mapping-%sto%s.json", from, to)); + final JsonObject object = GsonUtil.GSON.fromJson(Files.readString(path), JsonObject.class); + final JsonObject blockStates = object.getAsJsonObject("blockstates"); + final JsonObject outputStates = new JsonObject(); + final Set handled = new HashSet<>(); + for (final Map.Entry entry : blockStates.entrySet()) { + final String value = entry.getValue().getAsString(); + final String key = entry.getKey(); + if (!value.isEmpty()) { + outputStates.add(key, entry.getValue()); + continue; + } + + final String keyPart = key.split("\\[")[0]; + if (handled.contains(keyPart)) { + outputStates.add(key, entry.getValue()); + continue; + } + + final String wonderfulState = WONDERFUL_STATES.stream().filter(keyPart::endsWith).findAny().orElse(null); + if (wonderfulState == null) { + outputStates.add(key, entry.getValue()); + continue; + } + + handled.add(key); + String outputKey = keyPart.replace(wonderfulState, ""); + for (final Function mapper : MAPPERS) { + outputKey = mapper.apply(outputKey); + } + outputStates.addProperty(keyPart, outputKey + wonderfulState + "["); + } + + object.add("blockstates", outputStates); + Files.writeString(path, GsonUtil.GSON.toJson(object)); + } + + private static void equals(final String from, final String to) { + MAPPERS.add(s -> s.equals(from) ? to : s); + } + + private static void contains(final String from, final String to) { + MAPPERS.add(s -> s.contains(from) ? to : s); + } + + private static void replace(final String from, final String to) { + MAPPERS.add(s -> s.replace(from, to)); + } +} diff --git a/src/main/java/com/viaversion/mappingsgenerator/helper/BlockStateMapper.java b/src/main/java/com/viaversion/mappingsgenerator/helper/BlockStateMapper.java new file mode 100644 index 0000000..7d1198a --- /dev/null +++ b/src/main/java/com/viaversion/mappingsgenerator/helper/BlockStateMapper.java @@ -0,0 +1,227 @@ +package com.viaversion.mappingsgenerator.helper; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import org.jetbrains.annotations.Nullable; + +/** + * Utility to more easily map blockstates. + *

+ * Copy the stubbed lines from diff mappings into a file called states.txt and update the CONSUMER contents. + * Different methods will do different things, the result will be printed to the console. + */ +final class BlockStateMapper { + + private static final Function CONSUMER = state -> { + state.addProperty(5, "waterlogged", "false"); + state.setState("glow_lichen"); + return state; + }; + + public static void main(final String[] args) throws IOException { + applyFunction(); + } + + public static void applyFunction() throws IOException { + final String content = Files.readString(Path.of("states.txt")); + for (final String line : content.split("\n")) { + String trimmedLine = line.replace("\"", "").trim(); + if (trimmedLine.endsWith(": ,")) { + trimmedLine = trimmedLine.substring(0, trimmedLine.length() - 3); + } + + final BlockState state = new BlockState(trimmedLine); + System.out.println("\"" + trimmedLine + "\": \"" + CONSUMER.apply(state) + "\","); + } + } + + public static void replace(final String... replacements) throws IOException { + final String content = Files.readString(Path.of("states.txt")); + for (final String line : content.split("\n")) { + boolean found = false; + for (int i = 0; i < replacements.length; i += 2) { + final String from = replacements[i]; + if (!line.contains(from)) { + continue; + } + + final String to = replacements[i + 1]; + final String[] split = line.split("\": \"", 2); + System.out.println(split[0] + "\": \"" + split[1].replace(from, to)); + found = true; + break; + } + + if (!found) { + System.out.println(line); + } + } + } + + public static void editKey() throws IOException { + final String newName = "dark_oak_slab"; + final String content = Files.readString(Path.of("states.txt")); + for (String line : content.split("\"\",")) { + if (line.trim().equals("}")) { + continue; + } + + final String firstPart = line; + line = line.replace("\"", "").replace(": ", "").trim(); + final String[] split = line.split("\\[", 2); + System.out.println(firstPart + "\"minecraft:" + newName + "[" + split[1] + "\","); + } + } + + public static final class BlockState { + + private final List properties = new ArrayList<>(); + private String state; + + public BlockState(final String state) { + final int start = state.indexOf('['); + if (start == -1) { + this.state = state; + return; + } + + if (!state.endsWith("]")) { + throw new IllegalArgumentException("Invalid block state: " + state); + } + + this.state = state.substring(0, start); + + int lastCommaIndex = start; + int commaIndex; + while ((commaIndex = state.indexOf(',', lastCommaIndex + 1)) != -1) { + final String part = state.substring(lastCommaIndex + 1, commaIndex); + final String[] split = part.split("=", 2); + properties.add(new Property(split[0], split[1])); + + lastCommaIndex = commaIndex; + } + + final String part = state.substring(lastCommaIndex + 1, state.length() - 1); + final String[] split = part.split("=", 2); + properties.add(new Property(split[0], split[1])); + } + + public @Nullable Property getProperty(final String key) { + for (final Property property : properties) { + if (property.key.equals(key)) { + return property; + } + } + return null; + } + + public void addProperty(final String key, final String value) { + properties.add(new Property(key, value)); + } + + public void addProperty(final int index, final String key, final String value) { + properties.add(index, new Property(key, value)); + } + + public @Nullable Property removeProperty(final String key) { + for (int i = 0; i < properties.size(); i++) { + final Property property = properties.get(i); + if (property.key.equals(key)) { + properties.remove(i); + return property; + } + } + return null; + } + + public List getProperties() { + return properties; + } + + public String getState() { + return state; + } + + public void setState(final String state) { + this.state = state; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(state).append('['); + for (final Property property : properties) { + builder.append(property.getKey()).append('=').append(property.getValue()).append(','); + } + return builder.substring(0, builder.length() - 1) + "]"; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final BlockState that = (BlockState) o; + if (!properties.equals(that.properties)) return false; + return state.equals(that.state); + } + + @Override + public int hashCode() { + int result = properties.hashCode(); + result = 31 * result + state.hashCode(); + return result; + } + + public BlockState copy() { + return new BlockState(toString()); + } + } + + public static final class Property { + + private String key; + private String value; + + public Property(final String key, final String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(final String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(final String value) { + this.value = value; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final Property property = (Property) o; + if (!key.equals(property.key)) return false; + return value.equals(property.value); + } + + @Override + public int hashCode() { + int result = key.hashCode(); + result = 31 * result + value.hashCode(); + return result; + } + } +} diff --git a/src/main/java/com/viaversion/mappingsgenerator/helper/TranslationMapper.java b/src/main/java/com/viaversion/mappingsgenerator/helper/TranslationMapper.java new file mode 100644 index 0000000..90d00ba --- /dev/null +++ b/src/main/java/com/viaversion/mappingsgenerator/helper/TranslationMapper.java @@ -0,0 +1,107 @@ +package com.viaversion.mappingsgenerator.helper; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.viaversion.mappingsgenerator.util.PathUtil; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility to compare generate a diff of translations between two Minecraft versions. + * Copy the output into VB's translations file. + */ +final class TranslationMapper { + + private static final Logger LOGGER = LoggerFactory.getLogger(TranslationMapper.class.getSimpleName()); + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); + + public static void main(final String[] args) throws IOException { + final String oldVer = "1.21.2"; + final String newVer = "1.21.4-rc3"; + + final Map oldTranslations = load(oldVer); + final Set oldValues = new HashSet<>(oldTranslations.values()); + final Map newTranslations = load(newVer); + + final JsonObject diff = new JsonObject(); + for (final Map.Entry entry : newTranslations.entrySet()) { + if (oldTranslations.containsKey(entry.getKey())) { + continue; + } + + if (oldValues.contains(entry.getValue())) { + LOGGER.warn("Changed value: {}", entry.getValue()); + continue; + } + + diff.addProperty(entry.getKey(), entry.getValue()); + } + + // Check for removed translations + for (final Map.Entry entry : oldTranslations.entrySet()) { + if (!newTranslations.containsKey(entry.getKey())) { + //LOGGER.warn("mappings.put(\"" + entry.getKey() + "\", \"" + entry.getValue() + "\");"); + } + } + + LOGGER.info(diff.toString()); + LOGGER.info("Mappings size: {}", diff.size()); + } + + private static Map load(final String version) throws IOException { + final File jarFile = PathUtil.minecraftDir().resolve("versions").resolve(version).resolve(version + ".jar").toFile(); + if (!jarFile.exists()) { + throw new IllegalArgumentException("File " + jarFile + " does not exist"); + } + + final String contents; + try (final ZipFile file = new ZipFile(jarFile)) { + ZipEntry langEntry = file.getEntry("assets/minecraft/lang/en_us.json"); + if (langEntry == null) { + // Pre 1.13 translations + langEntry = file.getEntry("assets/minecraft/lang/en_us.lang"); + if (langEntry != null) { + try (final InputStream inputStream = file.getInputStream(langEntry)) { + contents = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } + return loadLegacyTranslations(contents); + } + throw new IllegalArgumentException("File " + jarFile + " does not contain en_us.json"); + } + + try (final InputStream inputStream = file.getInputStream(langEntry)) { + contents = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } + } + + final JsonObject object = GSON.fromJson(contents, JsonObject.class); + final Map translations = new LinkedHashMap<>(); + for (final Map.Entry entry : object.entrySet()) { + translations.put(entry.getKey(), entry.getValue().getAsString()); + } + return translations; + } + + private static Map loadLegacyTranslations(final String contents) { + final Map translations = new LinkedHashMap<>(); + contents.lines().forEach(line -> { + final int index = line.indexOf('='); + if (index != -1) { + translations.put(line.substring(0, index), line.substring(index + 1)); + } + }); + return translations; + } +} diff --git a/src/main/java/com/viaversion/mappingsgenerator/util/GsonUtil.java b/src/main/java/com/viaversion/mappingsgenerator/util/GsonUtil.java new file mode 100644 index 0000000..4e3d45a --- /dev/null +++ b/src/main/java/com/viaversion/mappingsgenerator/util/GsonUtil.java @@ -0,0 +1,9 @@ +package com.viaversion.mappingsgenerator.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public final class GsonUtil { + + public static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); +} diff --git a/src/main/java/com/viaversion/mappingsgenerator/util/PathUtil.java b/src/main/java/com/viaversion/mappingsgenerator/util/PathUtil.java new file mode 100644 index 0000000..eba75a0 --- /dev/null +++ b/src/main/java/com/viaversion/mappingsgenerator/util/PathUtil.java @@ -0,0 +1,22 @@ +package com.viaversion.mappingsgenerator.util; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public final class PathUtil { + + public static Path minecraftDir() { + // Windows path + Path minecraftDir = Paths.get(home(), "AppData", "Roaming", ".minecraft"); + if (!Files.isDirectory(minecraftDir)) { + // MacOS path + minecraftDir = Paths.get(home(), "Library", "Application Support", "minecraft"); + } + return minecraftDir; + } + + private static String home() { + return System.getProperty("user.home"); + } +}