diff --git a/src/main/java/com/mraof/minestuck/alchemy/CardCaptchas.java b/src/main/java/com/mraof/minestuck/alchemy/CardCaptchas.java index 07eb2976b2..43b112f526 100644 --- a/src/main/java/com/mraof/minestuck/alchemy/CardCaptchas.java +++ b/src/main/java/com/mraof/minestuck/alchemy/CardCaptchas.java @@ -3,7 +3,6 @@ import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.mraof.minestuck.Minestuck; -import com.mraof.minestuck.item.MSItems; import com.mraof.minestuck.world.storage.MSExtraData; import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; @@ -12,6 +11,8 @@ import net.minecraft.world.item.Item; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.registries.ForgeRegistries; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -32,12 +33,12 @@ public final class CardCaptchas public static final String AVAILABLE_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?"; public static final String EMPTY_CARD_CAPTCHA = "00000000"; - private final BiMap captchasMap = HashBiMap.create(); + private static final Logger LOGGER = LogManager.getLogger(); public CardCaptchas() { - this.setupPredeterminedCaptchas(); + } /** @@ -49,8 +50,20 @@ public static String getCaptcha(Item item, MinecraftServer server) MSExtraData data = MSExtraData.get(server); CardCaptchas captchas = data.getCardCaptchas(); - if(captchas.captchasMap.containsKey(item)) + if(PredeterminedCardCaptchas.getData().containsKey(item)) + { + if(captchas.captchasMap.containsKey(item)) + { + LOGGER.warn("Conflict: Item {} already has Code '{}', removing generated Code '{}' from data.", item, PredeterminedCardCaptchas.getData().get(item), captchas.captchasMap.get(item)); + captchas.captchasMap.remove(item); + data.setDirty(); + } + return PredeterminedCardCaptchas.getData().get(item); + } + else if(captchas.captchasMap.containsKey(item)) + { return captchas.captchasMap.get(item); + } else { String captcha = captchas.createCaptchaForItem(item, server.overworld().getSeed()); @@ -63,7 +76,23 @@ public static String getCaptcha(Item item, MinecraftServer server) @Nullable public static Item getItemFromCaptcha(String captcha, MinecraftServer server) { - return MSExtraData.get(server).getCardCaptchas().captchasMap.inverse().get(captcha); + MSExtraData data = MSExtraData.get(server); + CardCaptchas captchas = data.getCardCaptchas(); + + if(PredeterminedCardCaptchas.getData().inverse().containsKey(captcha)) + { + if(captchas.captchasMap.inverse().containsKey(captcha)) + { + LOGGER.warn("Conflict: Code '{}' already has assigned to Item {}, removing code '{}' reference to Item {}.", captcha, PredeterminedCardCaptchas.getData().inverse().get(captcha), captcha, captchas.captchasMap.inverse().get(captcha)); + captchas.captchasMap.inverse().remove(captcha); + data.setDirty(); + } + return PredeterminedCardCaptchas.getData().inverse().get(captcha); + } + else + { + return captchas.captchasMap.inverse().get(captcha); + } } public CompoundTag serialize() @@ -92,20 +121,13 @@ public void deserialize(CompoundTag tag) } } - - - private void setupPredeterminedCaptchas() - { - predetermineCaptcha(MSItems.GENERIC_OBJECT.get(), EMPTY_CARD_CAPTCHA); - predetermineCaptcha(MSItems.SORD.get(), "SUPRePIC"); - } - /** * Creates a captcha from the registry name of the item and then adds it to a BiMap. * There is some simple collision detection and backup captchas for redundancy */ private String createCaptchaForItem(Item item, long seed) { + ResourceLocation itemId = Objects.requireNonNull(ForgeRegistries.ITEMS.getKey(item)); RandomSource itemRandom = RandomSource.create(seed) @@ -117,17 +139,12 @@ private String createCaptchaForItem(Item item, long seed) String cutHash = shuffledHash.substring(shuffledHash.length() - 16); //last 16 characters of hash String captcha = captchaFromHash(cutHash); - if(captchasMap.containsValue(captcha)) + if(captchasMap.containsValue(captcha) || PredeterminedCardCaptchas.getData().containsValue(captcha)) return generateBackupCaptcha(itemRandom); return captcha; } - private void predetermineCaptcha(Item item, String captcha) - { - captchasMap.put(item, captcha); - } - private static String createHash(String registryName) { StringBuilder hexString = new StringBuilder(); @@ -216,7 +233,7 @@ private String generateBackupCaptcha(RandomSource random) String captchaString = captcha.toString(); //checks to make sure the captcha has not been created before - if(!captchasMap.containsValue(captchaString)) + if(!captchasMap.containsValue(captchaString) && !PredeterminedCardCaptchas.getData().containsValue(captchaString)) { return captchaString; } diff --git a/src/main/java/com/mraof/minestuck/alchemy/PredeterminedCardCaptchas.java b/src/main/java/com/mraof/minestuck/alchemy/PredeterminedCardCaptchas.java new file mode 100644 index 0000000000..571127924a --- /dev/null +++ b/src/main/java/com/mraof/minestuck/alchemy/PredeterminedCardCaptchas.java @@ -0,0 +1,134 @@ +package com.mraof.minestuck.alchemy; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.mojang.serialization.Codec; +import com.mojang.serialization.JsonOps; +import com.mraof.minestuck.Minestuck; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimplePreparableReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.item.Item; +import net.minecraftforge.event.AddReloadListenerEvent; +import net.minecraftforge.event.server.ServerStoppedEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.registries.ForgeRegistries; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import java.io.IOException; +import java.io.Reader; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +@Mod.EventBusSubscriber(modid = Minestuck.MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class PredeterminedCardCaptchas +{ + @Nullable + private static BiMap predefinedCardMap; + + private static void setData(BiMap predefinedCards) + { + PredeterminedCardCaptchas.predefinedCardMap = predefinedCards; + } + + public static BiMap getData() + { + return Objects.requireNonNull(PredeterminedCardCaptchas.predefinedCardMap, "Tried to get an instance of Predetermined Captchas too early."); + } + + @SubscribeEvent + public static void onServerStopped(ServerStoppedEvent event) + { + predefinedCardMap = null; + } + + @SubscribeEvent + public static void onResourceReload(AddReloadListenerEvent event) + { + event.addListener(new Loader()); + } + + private final static class Loader extends SimplePreparableReloadListener> + { + private static final Logger LOGGER = LogManager.getLogger(); + public static final String PATH = "minestuck/captcha_codes.json"; + @Override + protected Map prepare(ResourceManager resourceManager, ProfilerFiller profiler) + { + BiMap captchaData = HashBiMap.create(); + + for(String namespace : resourceManager.getNamespaces()) + { + parseCaptchaCodesFromNamespace(resourceManager, namespace).ifPresent(parsedData -> processCaptchaData(captchaData, parsedData)); + } + return captchaData; + } + + @Override + protected void apply(Map data, ResourceManager resourceManager, ProfilerFiller profiler) + { + ImmutableBiMap.Builder predefinedCards = ImmutableBiMap.builder(); + + for(Map.Entry entrySet : data.entrySet()) + { + predefinedCards.put(entrySet.getValue(), entrySet.getKey()); + } + + PredeterminedCardCaptchas.setData(predefinedCards.build()); + } + + private static final Codec> PREDEFINED_CAPTCHAS_CODEC = Codec.unboundedMap(Codec.STRING, ForgeRegistries.ITEMS.getCodec()); + + private static Optional> parseCaptchaCodesFromNamespace(ResourceManager resourceManager, String namespace) + { + ResourceLocation rs = new ResourceLocation(namespace, PATH); + + return resourceManager.getResource(rs).flatMap(resource -> { + try(Reader reader = resource.openAsReader()) + { + JsonElement json = JsonParser.parseReader(reader); + return PREDEFINED_CAPTCHAS_CODEC.parse(JsonOps.INSTANCE, json) + .resultOrPartial(message -> LOGGER.error("Problem parsing json: {}, reason: {}", rs, message)); + + } catch(IOException ignored) + { + return Optional.empty(); + } + }); + } + + private static void processCaptchaData(BiMap incompleteMap, Map parsedMap) + { + for(Map.Entry entry : parsedMap.entrySet()) + { + String captcha = entry.getKey(); + Item item = entry.getValue(); + + if(incompleteMap.containsValue(item)) + { + LOGGER.error("Item {} already has an existing Code of '{}'!", item , incompleteMap.inverse().get(item)); + } + else if(incompleteMap.containsKey(captcha)) + { + LOGGER.error("Code '{}' is already assigned to Item: {}!", captcha, incompleteMap.get(captcha)); + } + else + { + incompleteMap.put(entry.getKey(), entry.getValue()); + } + } + } + } +} diff --git a/src/main/resources/data/minestuck/minestuck/captcha_codes.json b/src/main/resources/data/minestuck/minestuck/captcha_codes.json new file mode 100644 index 0000000000..0ee9b3ce32 --- /dev/null +++ b/src/main/resources/data/minestuck/minestuck/captcha_codes.json @@ -0,0 +1,4 @@ +{ + "00000000": "minestuck:generic_object", + "SUPRePIC": "minestuck:sord" +} \ No newline at end of file