diff --git a/common/build.gradle b/common/build.gradle index 453bdb7c..8eff8593 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -18,6 +18,7 @@ neoForge { } dependencies { + compileOnly "maven.modrinth:no-mans-land:${no_mans_land_version}" compileOnly "me.shedaniel.cloth:cloth-config-neoforge:${cloth_config_version}" compileOnly "dev.emi:emi-xplat-mojmap:${emi_version}" compileOnly "maven.modrinth:entitytexturefeatures:${etf_version}-neoforge-1.21" diff --git a/common/src/main/java/com/evandev/fieldguide/client/gui/screens/BookScreen.java b/common/src/main/java/com/evandev/fieldguide/client/gui/screens/BookScreen.java index e3fdc431..b993dc16 100644 --- a/common/src/main/java/com/evandev/fieldguide/client/gui/screens/BookScreen.java +++ b/common/src/main/java/com/evandev/fieldguide/client/gui/screens/BookScreen.java @@ -5,6 +5,7 @@ import com.evandev.fieldguide.client.FieldGuideClient; import com.evandev.fieldguide.client.gui.util.Bounds; import com.evandev.fieldguide.client.gui.widget.TabButton; +import com.evandev.fieldguide.compat.nomansland.NoMansLandCompat; import com.evandev.fieldguide.config.ServerConfig; import com.evandev.fieldguide.api.Category; import net.minecraft.client.Minecraft; @@ -57,6 +58,24 @@ protected void init() { initCategories(); } + @Override + public void removed() { + super.removed(); + if (this.minecraft != null) { + Minecraft minecraft = this.minecraft; + minecraft.tell(() -> { + Screen current = minecraft.screen; + if (current == null || !current.getClass().getName().startsWith("com.evandev.fieldguide.")) + NoMansLandCompat.stopReplay(); + }); + } + } + + @Override + public boolean isPauseScreen() { + return !NoMansLandCompat.isAvailable(); + } + private void initCategories() { // Sort categories this.sortedCategories.clear(); diff --git a/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideEntryScreen.java b/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideEntryScreen.java index f9d520b0..f57d32c2 100644 --- a/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideEntryScreen.java +++ b/common/src/main/java/com/evandev/fieldguide/client/gui/screens/FieldGuideEntryScreen.java @@ -19,6 +19,7 @@ import com.evandev.fieldguide.client.gui.util.Bounds; import com.evandev.fieldguide.client.gui.util.EntryRenderHelper; import com.evandev.fieldguide.client.gui.widget.FieldGuideSearchBox; +import com.evandev.fieldguide.client.gui.widget.FriendMoonWidget; import com.evandev.fieldguide.client.gui.widget.PageTurnButton; import com.evandev.fieldguide.client.gui.widget.PaginatedGridWidget; import com.evandev.fieldguide.client.gui.widget.VariantOverviewWidget; @@ -26,6 +27,7 @@ import com.evandev.fieldguide.client.progress.ProgressManager; import com.evandev.fieldguide.compat.cobblemon.ClientFieldGuideCobblemonCompat; import com.evandev.fieldguide.compat.exposure.ClientExposureCompat; +import com.evandev.fieldguide.compat.nomansland.NoMansLandCompat; import com.evandev.fieldguide.compat.scholar.ScholarCompat; import com.evandev.fieldguide.config.ClientConfig; import com.evandev.fieldguide.config.ServerConfig; @@ -82,6 +84,9 @@ public class FieldGuideEntryScreen extends BookScreen { private PageTurnButton prevVariantButton; private PageTurnButton nextVariantButton; private VariantOverviewWidget variantOverviewWidget; + private FriendMoonWidget friendMoonWidget; + private int descX, descY, descW; + private boolean replayResetForScreen = false; private float hoverScale = 1.0f; private long lastRenderTime = 0; @@ -172,6 +177,10 @@ private void updateWidgetVisibility() { widget.visible = !overviewVisible; } + if (this.friendMoonWidget != null) { + this.friendMoonWidget.visible = !overviewVisible; + } + if (this.prevVariantButton != null) { this.prevVariantButton.visible = !overviewVisible; this.prevVariantButton.active = currentVariantIndex > 0; @@ -227,6 +236,7 @@ protected void init() { setupNavigationButtons(); refreshExposureWidgets(); + setupFriendMoonWidget(unlocked); if (unlocked && ServerConfig.get().enableCopyingPages) { boolean hasPaper = this.minecraft != null && this.minecraft.player != null && (this.minecraft.player.isCreative() || this.minecraft.player.getInventory().contains(Items.PAPER.getDefaultInstance())); @@ -262,6 +272,34 @@ public void playDownSound(SoundManager handler) { } } + private void setupFriendMoonWidget(boolean unlocked) { + this.friendMoonWidget = null; + if (!replayResetForScreen) { + NoMansLandCompat.stopReplay(); + replayResetForScreen = true; + } + if (!unlocked || !NoMansLandCompat.isAvailable() || !NoMansLandCompat.isIntegrationUnlocked()) return; + + List dialogues = NoMansLandCompat.getDialoguesForEntry(EntryResolver.resolveCoreEntry(entry)); + if (dialogues.isEmpty()) return; + + int iconSize = 16; + int iconX = this.leftPageBounds.left() + this.leftPageBounds.width() - iconSize - 12; + int iconY = this.leftPageBounds.top() + 12; + + if (Services.PLATFORM.isModLoaded("exposure")) { + String variantId = (!entityVariants.isEmpty() && currentVariantIndex < entityVariants.size()) + ? entityVariants.get(currentVariantIndex).id() : null; + if (!ClientExposureCompat.willRenderAddPhotoButton(entry, variantId)) return; + iconY += iconSize + 2; + } + + this.friendMoonWidget = new FriendMoonWidget(iconX, iconY, iconSize, dialogues, + () -> ClientFieldGuideManager.getEntryDescription(entry, this.initialVariant)); + this.addRenderableWidget(this.friendMoonWidget); + updateWidgetVisibility(); + } + private void setupTextWidgets(boolean unlocked) { int textX = this.rightPageBounds.left() + 6; int titleY = this.leftPageBounds.top() + 8; @@ -308,6 +346,10 @@ private void setupTextWidgets(boolean unlocked) { int textAreaHeight = this.rightPageBounds.bottom() - 29 - textY; int maxLines = textAreaHeight / LINE_HEIGHT; + this.descX = textX; + this.descY = textY; + this.descW = textAreaWidth; + String initialDesc = ClientFieldGuideManager.getEntryDescription(entry, this.initialVariant); if (!ServerConfig.get().disableEditingDescriptions) { this.descriptionWidget = ScholarCompat.createTextArea(this.font, textX, textY, textAreaWidth, textAreaHeight, maxLines, LINE_HEIGHT, ClientConfig.get().getTextColorInt(), true, FieldGuideLimits.MAX_ENTRY_DESCRIPTION_LENGTH, initialDesc, @@ -414,6 +456,9 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { if (this.variantOverviewWidget.mouseClicked(mouseX, mouseY, button)) return true; } + if (this.friendMoonWidget != null && this.friendMoonWidget.visible + && this.friendMoonWidget.mouseClicked(mouseX, mouseY, button)) return true; + if (super.mouseClicked(mouseX, mouseY, button)) return true; List seasons = SeasonsAPI.getGrowingSeasons(entry); @@ -593,7 +638,12 @@ public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, flo } else { textY += LINE_HEIGHT; // Buffer } - guiGraphics.drawWordWrap(font, Component.literal(ClientFieldGuideManager.getEntryDescription(entry)), titleX, textY, textAreaWidth, ClientConfig.get().getTextColorInt()); + this.descX = titleX; + this.descY = textY; + this.descW = textAreaWidth; + if (!NoMansLandCompat.isReplayActive()) { + guiGraphics.drawWordWrap(font, Component.literal(ClientFieldGuideManager.getEntryDescription(entry)), titleX, textY, textAreaWidth, ClientConfig.get().getTextColorInt()); + } } } @@ -691,7 +741,13 @@ public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, flo } guiGraphics.pose().popPose(); + + boolean replaying = ClientFieldGuideManager.isUnlocked(entry) && NoMansLandCompat.isReplayActive(); + if (this.descriptionWidget != null) this.descriptionWidget.visible = !replaying; super.render(guiGraphics, mouseX, mouseY, partialTick); + if (replaying) { + NoMansLandCompat.renderDescriptionRewrite(guiGraphics, this.font, descX, descY, descW, ClientConfig.get().getTextColorInt()); + } } private void renderSeasons(GuiGraphics guiGraphics, int x, int y, int mouseX, int mouseY) { diff --git a/common/src/main/java/com/evandev/fieldguide/client/gui/widget/FriendMoonWidget.java b/common/src/main/java/com/evandev/fieldguide/client/gui/widget/FriendMoonWidget.java new file mode 100644 index 00000000..29cd494c --- /dev/null +++ b/common/src/main/java/com/evandev/fieldguide/client/gui/widget/FriendMoonWidget.java @@ -0,0 +1,89 @@ +package com.evandev.fieldguide.client.gui.widget; + +import com.evandev.fieldguide.Constants; +import com.evandev.fieldguide.compat.nomansland.NoMansLandCompat; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.client.sounds.SoundManager; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundEvents; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.function.Supplier; + +public class FriendMoonWidget extends AbstractWidget { + public static final ResourceLocation IDLE = texture("idle"); + public static final ResourceLocation HOVER = texture("hover"); + public static final ResourceLocation TALKING = texture("talking"); + public static final ResourceLocation UNHEARD = texture("unheard"); + private static final float TALK_FRAME_SPEED = 1f / 3f; + + private static ResourceLocation texture(String name) { + return ResourceLocation.fromNamespaceAndPath(Constants.MOD_ID, "textures/gui/friend_moon/" + name + ".png"); + } + + private final List dialogues; + private final Supplier descriptionSupplier; + private float talkProgress = 0f; + + public FriendMoonWidget(int x, int y, int size, List dialogues, Supplier descriptionSupplier) { + super(x, y, size, size, Component.translatable("gui.fieldguide.friend_moon")); + this.dialogues = dialogues; + this.descriptionSupplier = descriptionSupplier; + } + + @Override + protected void renderWidget(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + boolean heard = !NoMansLandCompat.getHeardDialogues(dialogues).isEmpty(); + boolean replayActive = NoMansLandCompat.isReplayActive(); + this.active = heard && !replayActive; + + ResourceLocation texture; + if (replayActive) { + if (NoMansLandCompat.isReplayTyping()) { + float delta = Minecraft.getInstance().getTimer().getGameTimeDeltaTicks(); + talkProgress = (talkProgress + (delta * TALK_FRAME_SPEED)) % 2f; + texture = (((int) talkProgress) == 1) ? TALKING : HOVER; + } else { + talkProgress = 0f; + texture = HOVER; + } + } else if (!heard) { + talkProgress = 0f; + texture = UNHEARD; + } else { + talkProgress = 0f; + texture = this.isHovered() ? HOVER : IDLE; + } + + RenderSystem.enableBlend(); + guiGraphics.blit(texture, getX(), getY(), 0, 0, this.width, this.height, this.width, this.height); + RenderSystem.disableBlend(); + } + + @Override + public void onClick(double mouseX, double mouseY) { + if (NoMansLandCompat.isReplayActive()) return; + List heard = NoMansLandCompat.getHeardDialogues(dialogues); + if (heard.isEmpty() || Minecraft.getInstance().player == null) return; + ResourceLocation pick = heard.get(Minecraft.getInstance().player.getRandom().nextInt(heard.size())); + NoMansLandCompat.startReplay(pick, descriptionSupplier.get()); + } + + @Override + public void playDownSound(@NotNull SoundManager handler) { + if (NoMansLandCompat.getHeardDialogues(dialogues).isEmpty()) return; + handler.play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK.value(), 0.9F, 0.25F)); + } + + @Override + protected void updateWidgetNarration(@NotNull NarrationElementOutput narrationElementOutput) { + this.defaultButtonNarrationText(narrationElementOutput); + } +} diff --git a/common/src/main/java/com/evandev/fieldguide/compat/exposure/ClientExposureCompat.java b/common/src/main/java/com/evandev/fieldguide/compat/exposure/ClientExposureCompat.java index 93c21ff8..a480068e 100644 --- a/common/src/main/java/com/evandev/fieldguide/compat/exposure/ClientExposureCompat.java +++ b/common/src/main/java/com/evandev/fieldguide/compat/exposure/ClientExposureCompat.java @@ -38,6 +38,14 @@ public class ClientExposureCompat { private static final WidgetSprites ADD_PHOTO_SPRITES = new WidgetSprites(ResourceLocation.fromNamespaceAndPath(Constants.MOD_ID, "widget/exposure/add_photo"), ResourceLocation.fromNamespaceAndPath(Constants.MOD_ID, "widget/exposure/add_photo_highlighted")); private static final ResourceLocation MISSING_PHOTOGRAPH_BACKGROUND = ResourceLocation.fromNamespaceAndPath(Constants.MOD_ID, "textures/gui/exposure/missing_photograph.png"); + public static boolean willRenderAddPhotoButton(Object entry, String variantId) { + if (!ClientFieldGuideManager.isUnlocked(entry)) return false; + if (variantId != null && !variantId.isEmpty() && !ServerConfig.get().unlockAllVariants + && !ClientFieldGuideManager.isVariantUnlocked(entry, variantId)) return false; + if (!ClientConfig.get().exposureAddPhotographButton) return false; + return ProgressManager.getInstance().getPhotograph(entry, variantId).isEmpty(); + } + public static void setupExposureWidgets(FieldGuideEntryScreen screen, Object entry, String variantId) { if (!ClientFieldGuideManager.isUnlocked(entry)) return; diff --git a/common/src/main/java/com/evandev/fieldguide/compat/nomansland/FriendMoonReplay.java b/common/src/main/java/com/evandev/fieldguide/compat/nomansland/FriendMoonReplay.java new file mode 100644 index 00000000..e980b3b4 --- /dev/null +++ b/common/src/main/java/com/evandev/fieldguide/compat/nomansland/FriendMoonReplay.java @@ -0,0 +1,199 @@ +package com.evandev.fieldguide.compat.nomansland; + +import com.farcr.nomansland.common.friend.dialogue.DialogueState; +import com.farcr.nomansland.common.friend.dialogue.DialogueUtil; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.client.resources.sounds.SoundInstance; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.FastColor; +import net.minecraft.util.Mth; + +import java.util.List; + +public final class FriendMoonReplay { + private FriendMoonReplay() {} + + public static final int FRIEND_MOON_GREEN = 0xFF000000 | DialogueUtil.FRIEND_MOON_TEXT_COLOR; + private static final ResourceLocation SPEAK_SOUND = ResourceLocation.fromNamespaceAndPath("nomansland", "entity.friend_moon.speak_bgr"); + public static final float INTRO_TICKS = 16f; + public static final float PLACED_FADE_TICKS = 20f; + public static final float HOLD_TICKS = 200f; + public static final float FADE_BACK_TICKS = 20f; + public static final float PULSE_AMPLITUDE = 0.5f; + public static final float PULSE_SPEED = 0.12f; + public static final int SPEAK_INTERVAL_TICKS = 2; + private static final int LINE_HEIGHT = 9; + + private static boolean active = false; + private static DialogueState state; + private static String oldText = ""; + private static String newFull = ""; + private static int newVisibleTotal = 1; + private static float[] revealTick = new float[0]; + private static int revealedCount = 0; + private static float elapsedTicks = 0f; + private static float lastSpeakTick = -1000f; + private static float doneTick = -1f; + + public static void start(DialogueState dialogueState, String currentDescription) { + state = dialogueState; + oldText = currentDescription == null ? "" : currentDescription; + + List fullLines = state.translateDialogue.constructText(Integer.MAX_VALUE); + state.translateDialogue.flagDoneConstructed = false; + newFull = String.join("\n", fullLines); + int visible = 0; + for (String line : fullLines) visible += line.length(); + newVisibleTotal = Math.max(visible, 1); + + revealTick = new float[newVisibleTotal + 1]; + revealedCount = 0; + elapsedTicks = 0f; + lastSpeakTick = -1000f; + doneTick = -1f; + active = true; + } + + public static void reset() { + active = false; + state = null; + oldText = ""; + newFull = ""; + revealTick = new float[0]; + revealedCount = 0; + doneTick = -1f; + } + + public static boolean isActive() { + return active && state != null; + } + + public static boolean isTyping() { + return isActive() && elapsedTicks >= INTRO_TICKS && !state.doneTalking; + } + + public static void render(GuiGraphics guiGraphics, Font font, int x, int y, int width, int normalColor) { + if (!isActive()) return; + Minecraft minecraft = Minecraft.getInstance(); + float delta = minecraft.isPaused() ? 0f : minecraft.getTimer().getGameTimeDeltaTicks(); + elapsedTicks += delta; + int opaqueNormal = normalColor | 0xFF000000; + + RenderSystem.enableBlend(); + if (elapsedTicks < INTRO_TICKS) { + float alpha = Mth.clamp(1f - (elapsedTicks / INTRO_TICKS), 0f, 1f); + drawOld(guiGraphics, font, x, y, width, FRIEND_MOON_GREEN, alpha); + RenderSystem.disableBlend(); + return; + } + + List revealedLines = state.progressText(delta); + int visible = 0; + for (String line : revealedLines) visible += line.length(); + for (int k = revealedCount; k < visible && k < revealTick.length; k++) revealTick[k] = elapsedTicks; + revealedCount = visible; + + if (!state.doneTalking) { + drawText(guiGraphics, font, x, y, width, newFull, 0, visible, opaqueNormal, 1f, 1f); + RenderSystem.disableBlend(); + tickSound(); + return; + } + + if (doneTick < 0f) doneTick = elapsedTicks; + float sinceDone = elapsedTicks - doneTick; + if (sinceDone < HOLD_TICKS) { + drawText(guiGraphics, font, x, y, width, newFull, 0, Integer.MAX_VALUE, opaqueNormal, 1f, 1f); + RenderSystem.disableBlend(); + return; + } + float back = Mth.clamp((sinceDone - HOLD_TICKS) / FADE_BACK_TICKS, 0f, 1f); + drawText(guiGraphics, font, x, y, width, newFull, 0, Integer.MAX_VALUE, opaqueNormal, 1f - back, 1f - back); + drawOld(guiGraphics, font, x, y, width, opaqueNormal, back); + RenderSystem.disableBlend(); + if (back >= 1f) reset(); + } + + private static boolean tooFaint(float alpha) { + return (int) (255f * Mth.clamp(alpha, 0f, 1f)) < 4; + } + + private static void drawOld(GuiGraphics guiGraphics, Font font, int x, int y, int width, int baseColor, float alpha) { + if (tooFaint(alpha)) return; + guiGraphics.drawWordWrap(font, Component.literal(oldText), x, y, width, scaleAlpha(baseColor, alpha)); + } + + private static void drawText(GuiGraphics guiGraphics, Font font, int x, int y, int width, + String full, int drawFrom, int drawTo, int baseColor, float alpha, float greenScale) { + if (tooFaint(alpha)) return; + int idx = 0, cx = x, cy = y, i = 0, n = full.length(); + while (i < n) { + if (full.charAt(i) == '\n') { + cx = x; + cy += LINE_HEIGHT; + i++; + continue; + } + int wordEnd = i; + while (wordEnd < n && full.charAt(wordEnd) != ' ' && full.charAt(wordEnd) != '\n') wordEnd++; + if (cx > x && cx + font.width(full.substring(i, wordEnd)) > x + width) { + cx = x; + cy += LINE_HEIGHT; + } + for (; i < wordEnd; i++) { + if (idx >= drawFrom && idx < drawTo) { + int color = scaleAlpha(newColor(idx, baseColor, greenScale), alpha); + guiGraphics.drawString(font, String.valueOf(full.charAt(i)), cx, cy, color, false); + } + cx += font.width(String.valueOf(full.charAt(i))); + idx++; + } + if (i < n && full.charAt(i) == ' ') { + cx += font.width(" "); + idx++; + i++; + } + } + } + + private static int newColor(int idx, int normalColor, float greenScale) { + float placement = revealTick.length == 0 ? 0f + : 1f - Mth.clamp((elapsedTicks - revealTick[Math.min(idx, revealTick.length - 1)]) / PLACED_FADE_TICKS, 0f, 1f); + float pulse = PULSE_AMPLITUDE * (0.5f + 0.5f * (float) Math.sin(elapsedTicks * PULSE_SPEED)); + float green = Math.max(placement, pulse) * Mth.clamp(greenScale, 0f, 1f); + return lerpColor(normalColor, FRIEND_MOON_GREEN, green); + } + + private static int scaleAlpha(int color, float alpha) { + int a = (int) (((color >>> 24) & 0xFF) * Mth.clamp(alpha, 0f, 1f)); + return (color & 0x00FFFFFF) | (a << 24); + } + + private static int lerpColor(int from, int to, float t) { + return FastColor.ARGB32.color( + (int) Mth.lerp(t, FastColor.ARGB32.alpha(from), FastColor.ARGB32.alpha(to)), + (int) Mth.lerp(t, FastColor.ARGB32.red(from), FastColor.ARGB32.red(to)), + (int) Mth.lerp(t, FastColor.ARGB32.green(from), FastColor.ARGB32.green(to)), + (int) Mth.lerp(t, FastColor.ARGB32.blue(from), FastColor.ARGB32.blue(to)) + ); + } + + private static void tickSound() { + if (state.doneTalking) return; + if (state.translateDialogue.isInPause((int) state.progress)) return; + if (!state.canSpeakCurrently()) return; + if (elapsedTicks - lastSpeakTick < SPEAK_INTERVAL_TICKS) return; + lastSpeakTick = elapsedTicks; + Minecraft.getInstance().getSoundManager().play(new SimpleSoundInstance( + SPEAK_SOUND, SoundSource.VOICE, 1.0f, 1.0f, + SoundInstance.createUnseededRandom(), false, 0, + SoundInstance.Attenuation.NONE, 0.0, 0.0, 0.0, true + )); + } +} diff --git a/common/src/main/java/com/evandev/fieldguide/compat/nomansland/NoMansLandCompat.java b/common/src/main/java/com/evandev/fieldguide/compat/nomansland/NoMansLandCompat.java new file mode 100644 index 00000000..eaac15b3 --- /dev/null +++ b/common/src/main/java/com/evandev/fieldguide/compat/nomansland/NoMansLandCompat.java @@ -0,0 +1,46 @@ +package com.evandev.fieldguide.compat.nomansland; + +import com.evandev.fieldguide.platform.Services; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.resources.ResourceLocation; + +import java.util.List; + +public class NoMansLandCompat { + public static boolean isAvailable() { + return Services.PLATFORM.isModLoaded("nomansland"); + } + + public static boolean isIntegrationUnlocked() { + return isAvailable() && NoMansLandCompatImpl.isIntegrationUnlocked(); + } + + public static List getDialoguesForEntry(Object coreEntry) { + return isAvailable() ? NoMansLandCompatImpl.getDialoguesForEntry(coreEntry) : List.of(); + } + + public static List getHeardDialogues(List dialogues) { + return isAvailable() ? NoMansLandCompatImpl.getHeardDialogues(dialogues) : List.of(); + } + + public static void startReplay(ResourceLocation dialogueLocation, String currentDescription) { + if (isAvailable()) NoMansLandCompatImpl.startReplay(dialogueLocation, currentDescription); + } + + public static void stopReplay() { + if (isAvailable()) NoMansLandCompatImpl.stopReplay(); + } + + public static boolean isReplayActive() { + return isAvailable() && NoMansLandCompatImpl.isReplayActive(); + } + + public static boolean isReplayTyping() { + return isAvailable() && NoMansLandCompatImpl.isReplayTyping(); + } + + public static void renderDescriptionRewrite(GuiGraphics guiGraphics, Font font, int x, int y, int width, int normalColor) { + if (isAvailable()) NoMansLandCompatImpl.renderDescriptionRewrite(guiGraphics, font, x, y, width, normalColor); + } +} diff --git a/common/src/main/java/com/evandev/fieldguide/compat/nomansland/NoMansLandCompatImpl.java b/common/src/main/java/com/evandev/fieldguide/compat/nomansland/NoMansLandCompatImpl.java new file mode 100644 index 00000000..9639cb35 --- /dev/null +++ b/common/src/main/java/com/evandev/fieldguide/compat/nomansland/NoMansLandCompatImpl.java @@ -0,0 +1,88 @@ +package com.evandev.fieldguide.compat.nomansland; + +import com.evandev.fieldguide.Constants; +import com.evandev.fieldguide.config.ServerConfig; +import com.farcr.nomansland.client.ClientDialogueTracker; +import com.farcr.nomansland.common.friend.dialogue.DialoguePool; +import com.farcr.nomansland.common.friend.dialogue.DialogueState; +import com.farcr.nomansland.common.registry.NMLRegistries; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class NoMansLandCompatImpl { + public static final ResourceLocation FIELD_GUIDE_OFFERING_DIALOGUE = ResourceLocation.fromNamespaceAndPath(Constants.MOD_ID, "field_guide"); + + public static boolean isIntegrationUnlocked() { + if (ServerConfig.get().enableFieldGuideItem) { + return ClientDialogueTracker.hasHeardDialogue(NMLRegistries.OFFERING_DIALOGUE_KEY.location(), FIELD_GUIDE_OFFERING_DIALOGUE); + } + return ClientDialogueTracker.hasHeardAnyDialogue(); + } + + public static boolean hasHeardAnyDialogue() { + return ClientDialogueTracker.hasHeardAnyDialogue(); + } + + public static List getDialoguesForEntry(Object coreEntry) { + if (coreEntry instanceof EntityType entityType) { + return ClientDialogueTracker.getOfferingDialoguesForEntity(entityType); + } + Item item = null; + if (coreEntry instanceof Item directItem) { + item = directItem; + } else if (coreEntry instanceof Block block && block.asItem() != Items.AIR) { + item = block.asItem(); + } + if (item == null) return List.of(); + return ClientDialogueTracker.getOfferingDialoguesForItem(item); + } + + public static List getHeardDialogues(List dialogues) { + List heard = new ArrayList<>(); + ResourceLocation offering = NMLRegistries.OFFERING_DIALOGUE_KEY.location(); + for (ResourceLocation dialogue : dialogues) { + if (ClientDialogueTracker.hasHeardDialogue(offering, dialogue)) { + heard.add(dialogue); + } + } + return heard; + } + + public static void startReplay(ResourceLocation dialogueLocation, String currentDescription) { + Minecraft minecraft = Minecraft.getInstance(); + if (minecraft.level == null) return; + Optional> registry = minecraft.level.registryAccess().registry(NMLRegistries.OFFERING_DIALOGUE_KEY); + if (registry.isEmpty()) return; + DialoguePool pool = registry.get().get(dialogueLocation); + if (pool == null) return; + String registryName = NMLRegistries.OFFERING_DIALOGUE_KEY.location().getPath().replace("/", "."); + FriendMoonReplay.start(new DialogueState(dialogueLocation, registryName, pool), currentDescription); + } + + public static void stopReplay() { + FriendMoonReplay.reset(); + } + + public static boolean isReplayActive() { + return FriendMoonReplay.isActive(); + } + + public static boolean isReplayTyping() { + return FriendMoonReplay.isTyping(); + } + + public static void renderDescriptionRewrite(GuiGraphics guiGraphics, Font font, int x, int y, int width, int normalColor) { + FriendMoonReplay.render(guiGraphics, font, x, y, width, normalColor); + } +} diff --git a/common/src/main/resources/assets/fieldguide/lang/en_us.json b/common/src/main/resources/assets/fieldguide/lang/en_us.json index df3abb26..1eb7e9d0 100644 --- a/common/src/main/resources/assets/fieldguide/lang/en_us.json +++ b/common/src/main/resources/assets/fieldguide/lang/en_us.json @@ -264,6 +264,7 @@ "gui.fieldguide.copy.requires_paper": "Requires Paper", "gui.fieldguide.back": "Back", "gui.fieldguide.loading": "Loading...", + "gui.fieldguide.friend_moon": "Friend Moon", "key.fieldguide.open": "Open Field Guide", "key.fieldguide.scan": "Start Scanning", diff --git a/common/src/main/resources/assets/fieldguide/lang/nomansland/dialogue_pools/offering/en_us.json b/common/src/main/resources/assets/fieldguide/lang/nomansland/dialogue_pools/offering/en_us.json new file mode 100644 index 00000000..cd8c5802 --- /dev/null +++ b/common/src/main/resources/assets/fieldguide/lang/nomansland/dialogue_pools/offering/en_us.json @@ -0,0 +1,3 @@ +{ + "field_guide": "Ooh!// A little book of discoveries./\nEvery leaf and creature,/ lovingly documented./\nAn honor it would be/ to lend my voice to its pages, wisdom printed for inquisitive eyes..." +} diff --git a/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/hover.png b/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/hover.png new file mode 100644 index 00000000..a58bc31a Binary files /dev/null and b/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/hover.png differ diff --git a/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/idle.png b/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/idle.png new file mode 100644 index 00000000..b9ba6e48 Binary files /dev/null and b/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/idle.png differ diff --git a/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/talking.png b/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/talking.png new file mode 100644 index 00000000..8d23e8d7 Binary files /dev/null and b/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/talking.png differ diff --git a/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/unheard.png b/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/unheard.png new file mode 100644 index 00000000..a194d8dd Binary files /dev/null and b/common/src/main/resources/assets/fieldguide/textures/gui/friend_moon/unheard.png differ diff --git a/common/src/main/resources/data/fieldguide/nomansland/dialogue_pools/offering/field_guide.json b/common/src/main/resources/data/fieldguide/nomansland/dialogue_pools/offering/field_guide.json new file mode 100644 index 00000000..b6821874 --- /dev/null +++ b/common/src/main/resources/data/fieldguide/nomansland/dialogue_pools/offering/field_guide.json @@ -0,0 +1,8 @@ +{ + "condition": { + "type": "nomansland:item_conditional", + "items": "fieldguide:field_guide" + }, + "weight": 2, + "text": "Ooh!// A little book of discoveries./\nEvery leaf and creature,/ lovingly documented./\nAn honor it would be/ to lend my voice to its pages, wisdom printed for inquisitive eyes..." +} diff --git a/fabric/build.gradle b/fabric/build.gradle index 05edc2a0..f22fa8d3 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -39,6 +39,9 @@ dependencies { modImplementation "maven.modrinth:exposure:${exposure_fabric_version}" modImplementation "maven.modrinth:forge-config-api-port:${fcapi_version}-Fabric" + // No Man's Land + compileOnly "maven.modrinth:no-mans-land:${no_mans_land_version}" + // Cobblemon modCompileOnly "com.cobblemon:fabric:${cobblemon_version}" diff --git a/gradle.properties b/gradle.properties index 14cd27a5..873d7e5f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,6 +35,7 @@ fcapi_version=v21.1.6-1.21.1 mixed_litter_version=1.2.2 primal_version=bjAno6mP spawn_version=4.0.4 +no_mans_land_version=1.5.11 cobblemon_version=1.7.3+1.21.1 kubejs_version=8nuqyxbw serene_seasons_neoforge_version=SPj5bJoM diff --git a/neoforge/build.gradle b/neoforge/build.gradle index cf40d25f..0c0cfd10 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -68,7 +68,7 @@ dependencies { compileOnly "maven.modrinth:mixed-litter:${mixed_litter_version}" // No Man's Land - implementation "maven.modrinth:no-mans-land:1.5.8" + compileOnly "maven.modrinth:no-mans-land:${no_mans_land_version}" // Spawn compileOnly "maven.modrinth:spawn-mod:${spawn_version}"