From f45a1c28b3039ee5399141ea56f9397db8523b2b Mon Sep 17 00:00:00 2001 From: TheRealWormbo Date: Sun, 31 Dec 2023 21:26:14 +0100 Subject: [PATCH] Fire an advancement trigger when reading a book (implements #714) --- .../fabric/common/FabricModInitializer.java | 2 + .../forge/common/ForgeModInitializer.java | 2 + .../advancement/PatchouliBookOpenTrigger.java | 136 ++++++++++++++++++ .../PatchouliCriteriaTriggers.java | 9 ++ .../common/base/PatchouliAPIImpl.java | 3 + .../mixin/AccessorCriteriaTriggers.java | 15 ++ .../resources/patchouli_xplat.mixins.json | 1 + 7 files changed, 168 insertions(+) create mode 100644 Xplat/src/main/java/vazkii/patchouli/common/advancement/PatchouliBookOpenTrigger.java create mode 100644 Xplat/src/main/java/vazkii/patchouli/common/advancement/PatchouliCriteriaTriggers.java create mode 100644 Xplat/src/main/java/vazkii/patchouli/mixin/AccessorCriteriaTriggers.java diff --git a/Fabric/src/main/java/vazkii/patchouli/fabric/common/FabricModInitializer.java b/Fabric/src/main/java/vazkii/patchouli/fabric/common/FabricModInitializer.java index 7a830a8d..599e4b8f 100644 --- a/Fabric/src/main/java/vazkii/patchouli/fabric/common/FabricModInitializer.java +++ b/Fabric/src/main/java/vazkii/patchouli/fabric/common/FabricModInitializer.java @@ -12,6 +12,7 @@ import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.CreativeModeTabs; +import vazkii.patchouli.common.advancement.PatchouliCriteriaTriggers; import vazkii.patchouli.common.base.PatchouliSounds; import vazkii.patchouli.common.book.BookRegistry; import vazkii.patchouli.common.command.OpenBookCommand; @@ -30,6 +31,7 @@ public void onInitialize() { CommandRegistrationCallback.EVENT.register((disp, buildCtx, selection) -> OpenBookCommand.register(disp)); UseBlockCallback.EVENT.register(LecternEventHandler::rightClick); + PatchouliCriteriaTriggers.init(); BookRegistry.INSTANCE.init(); ServerLifecycleEvents.END_DATA_PACK_RELOAD.register((server, _r, success) -> { diff --git a/Forge/src/main/java/vazkii/patchouli/forge/common/ForgeModInitializer.java b/Forge/src/main/java/vazkii/patchouli/forge/common/ForgeModInitializer.java index 8963c5ce..98130ff5 100644 --- a/Forge/src/main/java/vazkii/patchouli/forge/common/ForgeModInitializer.java +++ b/Forge/src/main/java/vazkii/patchouli/forge/common/ForgeModInitializer.java @@ -15,6 +15,7 @@ import net.minecraftforge.registries.RegisterEvent; import vazkii.patchouli.api.PatchouliAPI; +import vazkii.patchouli.common.advancement.PatchouliCriteriaTriggers; import vazkii.patchouli.common.base.PatchouliSounds; import vazkii.patchouli.common.book.BookRegistry; import vazkii.patchouli.common.command.OpenBookCommand; @@ -73,6 +74,7 @@ public static void onInitialize(FMLCommonSetupEvent evt) { ForgeNetworkHandler.registerMessages(); + PatchouliCriteriaTriggers.init(); BookRegistry.INSTANCE.init(); MinecraftForge.EVENT_BUS.addListener((ServerStartedEvent e) -> ReloadContentsHandler.dataReloaded(e.getServer())); diff --git a/Xplat/src/main/java/vazkii/patchouli/common/advancement/PatchouliBookOpenTrigger.java b/Xplat/src/main/java/vazkii/patchouli/common/advancement/PatchouliBookOpenTrigger.java new file mode 100644 index 00000000..7f82cacf --- /dev/null +++ b/Xplat/src/main/java/vazkii/patchouli/common/advancement/PatchouliBookOpenTrigger.java @@ -0,0 +1,136 @@ +package vazkii.patchouli.common.advancement; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import net.minecraft.advancements.critereon.*; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; + +import vazkii.patchouli.api.PatchouliAPI; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An advancement trigger for opening Patchouli books. + */ +public class PatchouliBookOpenTrigger extends SimpleCriterionTrigger { + public static final ResourceLocation ID = new ResourceLocation(PatchouliAPI.MOD_ID, "open_book"); + public static final PatchouliBookOpenTrigger INSTANCE = new PatchouliBookOpenTrigger(); + private static final String ELEMENT_BOOK = "book"; + private static final String ELEMENT_ENTRY = "entry"; + private static final String ELEMENT_PAGE = "page"; + + private PatchouliBookOpenTrigger() {} + + @NotNull + @Override + public ResourceLocation getId() { + return ID; + } + + @NotNull + @Override + protected PatchouliBookOpenTrigger.Instance createInstance(@NotNull JsonObject json, + @NotNull ContextAwarePredicate playerPred, @NotNull DeserializationContext conditions) { + JsonElement bookElement = json.get(ELEMENT_BOOK); + ResourceLocation book = bookElement instanceof JsonPrimitive bookPrimitive && bookPrimitive.isString() + ? new ResourceLocation(bookElement.getAsString()) : null; + JsonElement entryElement = json.get(ELEMENT_ENTRY); + ResourceLocation entry = entryElement instanceof JsonPrimitive entryPrimitive && entryPrimitive.isString() + ? new ResourceLocation(entryElement.getAsString()) : null; + JsonElement pageElement = json.get(ELEMENT_PAGE); + int page = pageElement instanceof JsonPrimitive pagePrimitive && pagePrimitive.isNumber() ? pageElement.getAsInt() : 0; + return new PatchouliBookOpenTrigger.Instance(playerPred, book, entry, page); + } + + public void trigger(@NotNull ServerPlayer player, @NotNull ResourceLocation book) { + trigger(player, instance -> instance.test(book, null, 0)); + } + + public void trigger(@NotNull ServerPlayer player, @NotNull ResourceLocation book, @Nullable ResourceLocation entry, int page) { + trigger(player, instance -> instance.test(book, entry, page)); + } + + public static class Instance extends AbstractCriterionTriggerInstance { + private final ResourceLocation book; + private final ResourceLocation entry; + private final int page; + + /** + * Creates a trigger instance that tests against a book ID, and optional entry ID and an optional page number. + * Note that the entry and page are only tested when the server opens the book for a player, not when the player + * navigates to that entry and page while the book is open. + * + * @param playerPred The player predicate. + * @param book The book's {@link ResourceLocation}. + * @param entry The entry's {@link ResourceLocation}. May be {@code null} to skip the check. + * @param page The page number. Specify zero to skip the check. + */ + public Instance(@NotNull ContextAwarePredicate playerPred, @Nullable ResourceLocation book, @Nullable ResourceLocation entry, int page) { + super(ID, playerPred); + if (book == null) { + PatchouliAPI.LOGGER.error("Missing book ID in advancement criterion {} - condition can never be satisfied!", getCriterion()); + } + this.book = book; + this.entry = entry; + this.page = page; + } + + /** + * Creates a trigger instance that tests only against a book ID. + * This type of instance will match regardless whether the book is opened on a particular entry or page. + * + * @param playerPred The player predicate. + * @param book The book's {@link ResourceLocation}. + */ + @SuppressWarnings("unused") + public Instance(@NotNull ContextAwarePredicate playerPred, @NotNull ResourceLocation book) { + this(playerPred, book, null, 0); + } + + @NotNull + @Override + public ResourceLocation getCriterion() { + return ID; + } + + @Nullable + public ResourceLocation getBook() { + return this.book; + } + + @Nullable + public ResourceLocation getEntry() { + return this.entry; + } + + public int getPage() { + return this.page; + } + + boolean test(ResourceLocation book, ResourceLocation entry, int page) { + return this.book != null && this.book.equals(book) + && (this.entry == null || this.entry.equals(entry)) + && (this.page <= 0 || this.page == page); + } + + @NotNull + @Override + public JsonObject serializeToJson(@NotNull SerializationContext serializationContext) { + JsonObject json = super.serializeToJson(serializationContext); + if (book != null) { + json.addProperty(ELEMENT_BOOK, book.toString()); + } + if (entry != null) { + json.addProperty(ELEMENT_ENTRY, entry.toString()); + } + if (page > 0) { + json.addProperty(ELEMENT_PAGE, page); + } + return json; + } + } +} diff --git a/Xplat/src/main/java/vazkii/patchouli/common/advancement/PatchouliCriteriaTriggers.java b/Xplat/src/main/java/vazkii/patchouli/common/advancement/PatchouliCriteriaTriggers.java new file mode 100644 index 00000000..9f781c79 --- /dev/null +++ b/Xplat/src/main/java/vazkii/patchouli/common/advancement/PatchouliCriteriaTriggers.java @@ -0,0 +1,9 @@ +package vazkii.patchouli.common.advancement; + +import vazkii.patchouli.mixin.AccessorCriteriaTriggers; + +public class PatchouliCriteriaTriggers { + public static void init() { + AccessorCriteriaTriggers.patchouli$register(PatchouliBookOpenTrigger.INSTANCE); + } +} diff --git a/Xplat/src/main/java/vazkii/patchouli/common/base/PatchouliAPIImpl.java b/Xplat/src/main/java/vazkii/patchouli/common/base/PatchouliAPIImpl.java index e6b41701..49b745ae 100644 --- a/Xplat/src/main/java/vazkii/patchouli/common/base/PatchouliAPIImpl.java +++ b/Xplat/src/main/java/vazkii/patchouli/common/base/PatchouliAPIImpl.java @@ -28,6 +28,7 @@ import vazkii.patchouli.client.book.template.BookTemplate; import vazkii.patchouli.client.book.text.BookTextParser; import vazkii.patchouli.client.handler.MultiblockVisualizationHandler; +import vazkii.patchouli.common.advancement.PatchouliBookOpenTrigger; import vazkii.patchouli.common.book.Book; import vazkii.patchouli.common.book.BookRegistry; import vazkii.patchouli.common.item.ItemModBook; @@ -80,11 +81,13 @@ public boolean getConfigFlag(String flag) { @Override public void openBookGUI(ServerPlayer player, ResourceLocation book) { + PatchouliBookOpenTrigger.INSTANCE.trigger(player, book); IXplatAbstractions.INSTANCE.sendOpenBookGui(player, book, null, 0); } @Override public void openBookEntry(ServerPlayer player, ResourceLocation book, ResourceLocation entry, int page) { + PatchouliBookOpenTrigger.INSTANCE.trigger(player, book, entry, page); IXplatAbstractions.INSTANCE.sendOpenBookGui(player, book, entry, page); } diff --git a/Xplat/src/main/java/vazkii/patchouli/mixin/AccessorCriteriaTriggers.java b/Xplat/src/main/java/vazkii/patchouli/mixin/AccessorCriteriaTriggers.java new file mode 100644 index 00000000..e23e60f1 --- /dev/null +++ b/Xplat/src/main/java/vazkii/patchouli/mixin/AccessorCriteriaTriggers.java @@ -0,0 +1,15 @@ +package vazkii.patchouli.mixin; + +import net.minecraft.advancements.CriteriaTriggers; +import net.minecraft.advancements.CriterionTrigger; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(CriteriaTriggers.class) +public interface AccessorCriteriaTriggers { + @Invoker("register") + static > T patchouli$register(T thing) { + throw new IllegalStateException(); + } +} diff --git a/Xplat/src/main/resources/patchouli_xplat.mixins.json b/Xplat/src/main/resources/patchouli_xplat.mixins.json index 4c8b5544..2c06c86b 100644 --- a/Xplat/src/main/resources/patchouli_xplat.mixins.json +++ b/Xplat/src/main/resources/patchouli_xplat.mixins.json @@ -4,6 +4,7 @@ "package": "vazkii.patchouli.mixin", "compatibilityLevel": "JAVA_17", "mixins": [ + "AccessorCriteriaTriggers", "AccessorSmithingTransformRecipe", "AccessorSmithingTrimRecipe" ],