diff --git a/build.gradle b/build.gradle index cf54af4..deb06da 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,10 @@ repositories { maven { url "https://maven.gegy.dev" } maven { url "https://maven.quiltmc.org/repository/release" } maven { url "https://repo.sleeping.town/" } + exclusiveContent { + forRepository { maven { url "https://api.modrinth.com/maven" } } + filter { includeGroup "maven.modrinth" } + } } dependencies { @@ -28,9 +32,26 @@ dependencies { modImplementation libs.spruceui modRuntimeOnly libs.modmenu + modRuntimeOnly libs.sresld include libs.spruceui } +loom { + mods { + register(modId) { + sourceSet(sourceSets["main"]) + sourceSet(sourceSets["test"]) + } + } +} + +sourceSets { + named("main") { + runtimeClasspath += sourceSets["test"].runtimeClasspath + runtimeClasspath += sourceSets["test"].output + } +} + processResources { final Map meta = [ version : version, diff --git a/gradle.properties b/gradle.properties index de7a6a9..7f464ab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,12 +14,12 @@ modId=ballotbox modName=Ballotbox modDescription=In-game category voting! authors=ModFest -contributors=Prospector, Sisby folk, acikek, afamiliarquiet +contributors=Prospector, Sisby folk, acikek, afamiliarquiet, MerchantCalico, Sylv license=MIT # Mod Version -baseVersion=0.6.4 +baseVersion=0.6.5 # Branch Metadata -branch=1.21 -tagBranch=1.21 -compatibleVersions=1.21, 1.21.1 -compatibleLoaders=fabric, quilt, neoforge +branch=1.21.5 +tagBranch=1.21.5 +compatibleVersions=1.21.5 +compatibleLoaders=fabric, quilt diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0d18421..d6e308a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/libs.versions.toml b/libs.versions.toml index 3b02062..5b91c6e 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -1,16 +1,18 @@ [versions] -loom = "1.7.+" +loom = "1.10.+" minotaur = "2.+" kaleidoConfig = "0.3.1+1.3.1" -mc = "1.21.1" -fl = "0.16.7" -yarn = "1.21.1+build.3" -fapi = "0.104.0+1.21.1" +mc = "1.21.5" +fl = "0.16.14" +yarn = "1.21.5+build.1" +fapi = "0.126.0+1.21.5" -spruceui = "5.1.0+1.21" -modmenu = "11.0.3" +spruceui = "7.0.2+1.21.5" +modmenu = "14.0.0-rc.2" + +sresld = "1.0.0+1.21.1" [plugins] loom = { id = "fabric-loom", version.ref = "loom" } @@ -26,3 +28,5 @@ fapi = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = " spruceui = { group = "dev.lambdaurora", name = "spruceui", version.ref = "spruceui" } modmenu = { group = "com.terraformersmc", name = "modmenu", version.ref = "modmenu" } + +sresld = { group = "maven.modrinth", name = "simple-resource-loader", version.ref = "sresld" } diff --git a/src/main/java/net/modfest/ballotbox/BallotBox.java b/src/main/java/net/modfest/ballotbox/BallotBox.java index 576001e..509a9aa 100644 --- a/src/main/java/net/modfest/ballotbox/BallotBox.java +++ b/src/main/java/net/modfest/ballotbox/BallotBox.java @@ -25,7 +25,6 @@ public class BallotBox implements ModInitializer { public static final String ID = "ballotbox"; public static final Logger LOGGER = LoggerFactory.getLogger(ID); public static final BallotBoxConfig CONFIG = BallotBoxConfig.createToml(FabricLoader.getInstance().getConfigDir(), "", ID, BallotBoxConfig.class); - public static final String STATE_KEY = "ballotbox_ballots"; public static BallotState STATE = null; public static Instant closingTime = null; @@ -66,7 +65,7 @@ public void onInitialize() { CommandRegistrationCallback.EVENT.register(BallotBoxCommands::register); ServerWorldEvents.LOAD.register(((server, world) -> { if (world.getRegistryKey() == World.OVERWORLD) { - STATE = world.getPersistentStateManager().getOrCreate(BallotState.getPersistentStateType(), STATE_KEY); + STATE = world.getPersistentStateManager().getOrCreate(BallotState.TYPE); } })); ServerLifecycleEvents.SERVER_STARTED.register((server -> { @@ -82,8 +81,8 @@ public void onInitialize() { VotingSelections selections = STATE.selections().get(handler.getPlayer().getUuid()); int totalVotes = BallotBoxPlatformClient.categories.values().stream().mapToInt(VotingCategory::limit).sum(); int remainingVotes = totalVotes - (selections == null ? 0 : selections.votes().size()); - sender.sendPacket(new S2CGameJoin(CONFIG.closingTime.value(), remainingVotes)); + sender.sendPacket(new S2CGameJoin(CONFIG.closingTime.value(), !BallotBoxPlatformClient.categories.isEmpty() && !BallotBoxPlatformClient.options.isEmpty(), remainingVotes)); })); LOGGER.info("[BallotBox] Initialized!"); } -} \ No newline at end of file +} diff --git a/src/main/java/net/modfest/ballotbox/BallotBoxCommands.java b/src/main/java/net/modfest/ballotbox/BallotBoxCommands.java index ad7c99c..4d85e49 100644 --- a/src/main/java/net/modfest/ballotbox/BallotBoxCommands.java +++ b/src/main/java/net/modfest/ballotbox/BallotBoxCommands.java @@ -87,6 +87,10 @@ private static int vote(ServerPlayerEntity player, Consumer feedback) { feedback.accept(Text.literal("[BallotBox] ").formatted(Formatting.GREEN).append(Text.literal("Voting is unavailable! Voting closed %s.".formatted(BallotBox.relativeTime(BallotBox.closingTime))).formatted(Formatting.RED))); return 0; } + if (BallotBoxPlatformClient.categories.isEmpty() || BallotBoxPlatformClient.options.isEmpty()) { + feedback.accept(Text.literal("[BallotBox] ").formatted(Formatting.GREEN).append(Text.literal("Voting is unavailable! Nothing to vote for.").formatted(Formatting.RED))); + return 0; + } ServerPlayNetworking.send(player, new OpenVoteScreen()); BallotBoxNetworking.sendVoteScreenData(player); return 1; diff --git a/src/main/java/net/modfest/ballotbox/BallotBoxConfig.java b/src/main/java/net/modfest/ballotbox/BallotBoxConfig.java index e0287e4..765e668 100644 --- a/src/main/java/net/modfest/ballotbox/BallotBoxConfig.java +++ b/src/main/java/net/modfest/ballotbox/BallotBoxConfig.java @@ -9,7 +9,7 @@ public class BallotBoxConfig extends ReflectiveConfig { @Comment("Whether to add a voting button") - public final ButtonSettings voting_button = new ButtonSettings(List.of("menu.feedback", "menu.sendFeedback"), false, true); + public final ButtonSettings voting_button = new ButtonSettings(List.of("menu.playerReporting"), false, true); @Comment("Whether to replace the bug report button with another link") public final ButtonSettings custom_link_button = new ButtonSettings(List.of("menu.reportBugs"), false, true); @Comment("The text to use to replace the bug report button") @@ -17,13 +17,13 @@ public class BallotBoxConfig extends ReflectiveConfig { @Comment("The link to use to replace the bug report button") public final TrackedValue custom_link_url = value("https://discord.gg/gn543Ee"); @Comment("Whether to add a credits button") - public final ButtonSettings credits_button = new ButtonSettings(List.of("menu.online", "menu.playerReporting"), true, true); + public final ButtonSettings credits_button = new ButtonSettings(List.of("menu.online"), true, false); @Comment("The text to use for replacement credits but tons button") public final TrackedValue credits_text = value("Modpack Credits"); @Comment("The number of top results to show when displaying voting results") public final TrackedValue awardLimit = value(8); @Comment("The closing date, as an ISO local date time - or an empty string for none") - public final TrackedValue closingTime = value("2024-12-16T12:00:00"); + public final TrackedValue closingTime = value(""); @Comment("Settings for the reminder on the pause screen") public final ReminderSettings reminder_settings = new ReminderSettings(); diff --git a/src/main/java/net/modfest/ballotbox/BallotBoxPlatformClient.java b/src/main/java/net/modfest/ballotbox/BallotBoxPlatformClient.java index 489f312..faeb18a 100644 --- a/src/main/java/net/modfest/ballotbox/BallotBoxPlatformClient.java +++ b/src/main/java/net/modfest/ballotbox/BallotBoxPlatformClient.java @@ -5,6 +5,7 @@ import com.google.gson.JsonArray; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.JsonOps; +import net.minecraft.resource.Resource; import net.minecraft.resource.ResourceManager; import net.minecraft.util.Identifier; import net.modfest.ballotbox.data.VotingCategory; @@ -14,6 +15,7 @@ import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Map; +import java.util.Optional; import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -29,9 +31,25 @@ public class BallotBoxPlatformClient { public static void init(ResourceManager resourceManager) { try { categories.clear(); + Optional categoriesData = resourceManager.getResource(CATEGORIES_DATA); + if (categoriesData.isPresent()) { + GSON.fromJson(new BufferedReader(new InputStreamReader(resourceManager.getResourceOrThrow(CATEGORIES_DATA).getInputStream())), JsonArray.class).asList().stream().map(e -> VotingCategory.CODEC.decode(JsonOps.INSTANCE, e).mapOrElse(Pair::getFirst, a -> null)).filter(Objects::nonNull).forEach(category -> categories.put(category.id(), category)); + } options.clear(); - GSON.fromJson(new BufferedReader(new InputStreamReader(resourceManager.getResourceOrThrow(CATEGORIES_DATA).getInputStream())), JsonArray.class).asList().stream().map(e -> VotingCategory.CODEC.decode(JsonOps.INSTANCE, e).mapOrElse(Pair::getFirst, a -> null)).filter(Objects::nonNull).forEach(category -> categories.put(category.id(), category)); - GSON.fromJson(new BufferedReader(new InputStreamReader(resourceManager.getResourceOrThrow(OPTIONS_DATA).getInputStream())), JsonArray.class).asList().stream().map(e -> VotingOption.CODEC.decode(JsonOps.INSTANCE, e).mapOrElse(Pair::getFirst, a -> null)).filter(Objects::nonNull).forEach(option -> options.put(option.id(), option)); + Optional optionsData = resourceManager.getResource(OPTIONS_DATA); + if (optionsData.isPresent()) { + GSON.fromJson( + new BufferedReader(new InputStreamReader(resourceManager.getResourceOrThrow( + OPTIONS_DATA).getInputStream())), + JsonArray.class + ).asList().stream().map(e -> VotingOption.CODEC.decode( + JsonOps.INSTANCE, + e + ).mapOrElse( + Pair::getFirst, + a -> null + )).filter(Objects::nonNull).forEach(option -> options.put(option.id(), option)); + } } catch (Exception e) { BallotBox.LOGGER.info("[BallotBox] Failed to load ballotbox data!", e); } diff --git a/src/main/java/net/modfest/ballotbox/BallotState.java b/src/main/java/net/modfest/ballotbox/BallotState.java index 5612e41..debe402 100644 --- a/src/main/java/net/modfest/ballotbox/BallotState.java +++ b/src/main/java/net/modfest/ballotbox/BallotState.java @@ -2,11 +2,9 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtOps; -import net.minecraft.registry.RegistryWrapper; import net.minecraft.util.Uuids; import net.minecraft.world.PersistentState; +import net.minecraft.world.PersistentStateType; import net.modfest.ballotbox.data.VotingSelections; import java.util.Map; @@ -17,10 +15,12 @@ public class BallotState extends PersistentState { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.unboundedMap(Uuids.CODEC, VotingSelections.CODEC).xmap(s -> (Map) new ConcurrentHashMap<>(s), ConcurrentHashMap::new).fieldOf("selections").forGetter(BallotState::selections) ).apply(instance, BallotState::new)); - - public static PersistentState.Type getPersistentStateType() { - return new PersistentState.Type<>(() -> new BallotState(new ConcurrentHashMap<>()), BallotState::fromNbt, null); - } + public static final PersistentStateType TYPE = new PersistentStateType<>( + "ballotbox_ballots", + () -> new BallotState(new ConcurrentHashMap<>()), + BallotState.CODEC, + null + ); private final Map selections; @@ -28,16 +28,6 @@ private BallotState(Map selections) { this.selections = selections; } - private static BallotState fromNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { - return BallotState.CODEC.decode(NbtOps.INSTANCE, nbt.getCompound("data")).getOrThrow().getFirst(); - } - - @Override - public NbtCompound writeNbt(NbtCompound nbt, RegistryWrapper.WrapperLookup registryLookup) { - nbt.put("data", BallotState.CODEC.encodeStart(NbtOps.INSTANCE, this).getOrThrow()); - return nbt; - } - public Map selections() { return selections; } diff --git a/src/main/java/net/modfest/ballotbox/client/BallotBoxButtons.java b/src/main/java/net/modfest/ballotbox/client/BallotBoxButtons.java index 67a20f1..def338e 100644 --- a/src/main/java/net/modfest/ballotbox/client/BallotBoxButtons.java +++ b/src/main/java/net/modfest/ballotbox/client/BallotBoxButtons.java @@ -4,12 +4,12 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ConfirmLinkScreen; import net.minecraft.client.gui.screen.CreditsScreen; -import net.minecraft.client.gui.screen.GameMenuScreen; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.tooltip.Tooltip; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.ClickableWidget; import net.minecraft.client.gui.widget.Widget; +import net.minecraft.client.sound.MusicInstance; import net.minecraft.sound.MusicType; import net.minecraft.text.Text; import net.minecraft.text.TranslatableTextContent; @@ -38,7 +38,7 @@ public static List(BallotBox.CONFIG.credits_button, (screen) -> ButtonWidget.builder(Text.of(BallotBox.CONFIG.credits_text.value()), b -> { MinecraftClient.getInstance().setScreen(new CreditsScreen(false, () -> MinecraftClient.getInstance().setScreen(screen))); MinecraftClient.getInstance().getMusicTracker().stop(); - MinecraftClient.getInstance().getMusicTracker().play(MusicType.CREDITS); + MinecraftClient.getInstance().getMusicTracker().play(new MusicInstance(MusicType.CREDITS)); }))); return list; diff --git a/src/main/java/net/modfest/ballotbox/client/BallotBoxClient.java b/src/main/java/net/modfest/ballotbox/client/BallotBoxClient.java index e1a6dcd..1e37429 100644 --- a/src/main/java/net/modfest/ballotbox/client/BallotBoxClient.java +++ b/src/main/java/net/modfest/ballotbox/client/BallotBoxClient.java @@ -17,6 +17,7 @@ public class BallotBoxClient implements ClientModInitializer { public static final Logger LOGGER = LoggerFactory.getLogger("%s-client".formatted(BallotBox.ID)); public static Instant closingTime = null; + public static boolean hasVotingOptions = true; public static int remainingVotes = 0; public static boolean isEnabled(MinecraftClient client) { @@ -33,6 +34,7 @@ public void onInitializeClient() { ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { remainingVotes = 0; closingTime = null; + hasVotingOptions = true; }); BallotBoxClientNetworking.init(); BallotBoxKeybinds.init(); diff --git a/src/main/java/net/modfest/ballotbox/client/BallotBoxClientNetworking.java b/src/main/java/net/modfest/ballotbox/client/BallotBoxClientNetworking.java index a3de5f4..dc115af 100644 --- a/src/main/java/net/modfest/ballotbox/client/BallotBoxClientNetworking.java +++ b/src/main/java/net/modfest/ballotbox/client/BallotBoxClientNetworking.java @@ -16,6 +16,7 @@ public static void init() { private static void handleGameJoin(S2CGameJoin packet, ClientPlayNetworking.Context context) { BallotBoxClient.closingTime = BallotBox.parseClosingTime(packet.closingTime()); + BallotBoxClient.hasVotingOptions = packet.hasVotingOptions(); BallotBoxClient.remainingVotes = packet.remainingVotes(); } diff --git a/src/main/java/net/modfest/ballotbox/client/BallotBoxKeybinds.java b/src/main/java/net/modfest/ballotbox/client/BallotBoxKeybinds.java index bbf9729..5aed9a7 100644 --- a/src/main/java/net/modfest/ballotbox/client/BallotBoxKeybinds.java +++ b/src/main/java/net/modfest/ballotbox/client/BallotBoxKeybinds.java @@ -23,7 +23,9 @@ private static void tick(MinecraftClient client) { while (OPEN_VOTING_SCREEN.wasPressed() && BallotBoxClient.isEnabled(client)) { if (!BallotBoxClient.isOpen()) { client.inGameHud.setOverlayMessage(Text.literal("[BallotBox] ").formatted(Formatting.GREEN).append(Text.literal("Voting is unavailable! Voting closed %s.".formatted(BallotBox.relativeTime(BallotBoxClient.closingTime))).formatted(Formatting.RED)), false); - } else if (client.currentScreen == null) { + } else if (!BallotBoxClient.hasVotingOptions) { + client.inGameHud.setOverlayMessage(Text.literal("[BallotBox] ").formatted(Formatting.GREEN).append(Text.literal("Voting is unavailable! Nothing to vote for.").formatted(Formatting.RED)), false); + } else if (client.currentScreen == null) { client.setScreen(new VotingScreen()); ClientPlayNetworking.send(new OpenVoteScreen()); } diff --git a/src/main/java/net/modfest/ballotbox/client/VotingScreen.java b/src/main/java/net/modfest/ballotbox/client/VotingScreen.java index 8ba81d9..ce36b62 100644 --- a/src/main/java/net/modfest/ballotbox/client/VotingScreen.java +++ b/src/main/java/net/modfest/ballotbox/client/VotingScreen.java @@ -3,7 +3,6 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; -import com.mojang.blaze3d.systems.RenderSystem; import dev.lambdaurora.spruceui.Position; import dev.lambdaurora.spruceui.background.EmptyBackground; import dev.lambdaurora.spruceui.screen.SpruceScreen; @@ -16,6 +15,7 @@ import net.fabricmc.loader.api.ModContainer; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.render.RenderLayer; import net.minecraft.client.texture.NativeImageBackedTexture; import net.minecraft.client.texture.Sprite; import net.minecraft.text.Text; @@ -50,6 +50,8 @@ public class VotingScreen extends SpruceScreen { ); public static final Identifier LOCKUP_TEXTURE = Identifier.of(BallotBox.ID, "emblem"); + public static final int LOCKUP_TEXTURE_WIDTH = 1101; + public static final int LOCKUP_TEXTURE_HEIGHT = 256; protected final Multimap previousSelections = HashMultimap.create(); protected final Multimap selections = HashMultimap.create(); @@ -112,12 +114,10 @@ public void renderBackground(DrawContext context, int mouseX, int mouseY, float public void renderLockup(DrawContext context) { if (lockupSprite == null) return; - RenderSystem.enableBlend(); int texHeight = lockupSprite.getContents().getHeight(); int texWidth = lockupSprite.getContents().getWidth(); int drawHeight = sidePanelWidth * texHeight / texWidth; - context.drawGuiTexture(LOCKUP_TEXTURE, 0, (sidePanelVerticalPadding - drawHeight) / 2, sidePanelWidth, drawHeight); - RenderSystem.disableBlend(); + context.drawTexture(RenderLayer::getGuiTextured, LOCKUP_TEXTURE, 0, (sidePanelVerticalPadding - drawHeight) / 2, sidePanelWidth, drawHeight, 0, 0, LOCKUP_TEXTURE_WIDTH, LOCKUP_TEXTURE_HEIGHT, LOCKUP_TEXTURE_WIDTH, LOCKUP_TEXTURE_HEIGHT); } @Override @@ -184,6 +184,7 @@ public VotingOptionButtonWidget(Position position, int width, int height, Voting this.parent = parent; selected = selections.containsEntry(category.id(), option.id()); this.prohibited = prohibited; + this.active = prohibited; if (!modIconCache.containsKey(option.id())) { modIconCache.put(option.id(), Identifier.of(BallotBox.ID, option.id() + "_icon")); Optional mod = FabricLoader.getInstance().getModContainer(option.mod_id().isPresent() ? option.mod_id().get() : option.id()) @@ -198,14 +199,9 @@ public VotingOptionButtonWidget(Position position, int width, int height, Voting setTooltip(url == null ? Text.literal(option.description()).formatted(Formatting.GRAY) : Text.literal(option.description()).formatted(Formatting.GRAY).append(Text.literal("\n")).append(Text.literal("Right-Click").formatted(Formatting.GOLD)).append(Text.literal(" to open the mod page.").formatted(Formatting.WHITE))); } - @Override - public boolean isActive() { - return !prohibited && super.isActive(); - } - @Override public Optional getTooltip() { - return isActive() ? super.getTooltip() : prohibited ? Optional.of(Text.literal("Prohibited by another category!").formatted(Formatting.GRAY)) : Optional.of(Text.literal("You've reached the category vote limit!").formatted(Formatting.GRAY)); + return active ? super.getTooltip() : prohibited ? Optional.of(Text.literal("Prohibited by another category!").formatted(Formatting.GRAY)) : Optional.of(Text.literal("You've reached the category vote limit!").formatted(Formatting.GRAY)); } @Override @@ -232,7 +228,7 @@ protected void renderButton(DrawContext context, int mouseX, int mouseY, float d int bottom = getY() + getHeight(); int textY = (getY() * 2 + getHeight() - 9) / 2 + 1; if (texture != null) { - context.drawTexture(texture, left, getY() + 2, 16, 16, 0, 0, 16, 16, 16, 16); + context.drawTexture(RenderLayer::getGuiTextured, texture, left, getY() + 2, 16, 16, 16, 16, 16, 16, 16, 16); } if (textWidth <= getWidth()) { context.drawCenteredTextWithShadow(client.textRenderer, getMessage(), left + getWidth() / 2, textY, 0xFFFFFFFF); @@ -252,7 +248,7 @@ protected void renderButton(DrawContext context, int mouseX, int mouseY, float d protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { super.renderWidget(context, mouseX, mouseY, delta); if (selected) { - context.drawTexture(CHECKMARK_TEXTURE, getX() + getWidth() - 11, getY() + getHeight() - 9, 0, 0, 7, 6, 7, 6); + context.drawTexture(RenderLayer::getGuiTextured, CHECKMARK_TEXTURE, getX() + getWidth() - 11, getY() + getHeight() - 9, 0, 0, 7, 6, 7, 6); } } diff --git a/src/main/java/net/modfest/ballotbox/mixin/client/ClickableWidgetAccessor.java b/src/main/java/net/modfest/ballotbox/mixin/client/ClickableWidgetAccessor.java new file mode 100644 index 0000000..4f4b99d --- /dev/null +++ b/src/main/java/net/modfest/ballotbox/mixin/client/ClickableWidgetAccessor.java @@ -0,0 +1,11 @@ +package net.modfest.ballotbox.mixin.client; + +import net.minecraft.client.gui.widget.ClickableWidget; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ClickableWidget.class) +public interface ClickableWidgetAccessor { + @Accessor + void setWidth(int width); +} diff --git a/src/main/java/net/modfest/ballotbox/mixin/client/GameMenuScreenMixin.java b/src/main/java/net/modfest/ballotbox/mixin/client/GameMenuScreenMixin.java index 812fe9d..b08ba0b 100644 --- a/src/main/java/net/modfest/ballotbox/mixin/client/GameMenuScreenMixin.java +++ b/src/main/java/net/modfest/ballotbox/mixin/client/GameMenuScreenMixin.java @@ -1,15 +1,11 @@ package net.modfest.ballotbox.mixin.client; -import com.llamalad7.mixinextras.injector.ModifyReceiver; import com.llamalad7.mixinextras.sugar.Local; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.GameMenuScreen; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.GridWidget; -import net.minecraft.client.gui.widget.SimplePositioningWidget; -import net.minecraft.client.gui.widget.TexturedButtonWidget; +import net.minecraft.client.gui.widget.*; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.modfest.ballotbox.BallotBox; @@ -21,12 +17,10 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.function.Consumer; - @Mixin(value = GameMenuScreen.class, priority = 1200) public abstract class GameMenuScreenMixin extends Screen { private static ButtonWidget ballotbox$voteButton = null; - + protected GameMenuScreenMixin(Text title) { super(title); } @@ -50,6 +44,16 @@ private void onInitWidgets(CallbackInfo ci, @Local GridWidget instance) { if (settings == BallotBox.CONFIG.voting_button) { ballotbox$voteButton = button; } + + // Workaround for Mod Menu's atrociously invasive Mods button + if (child instanceof ButtonWidget buttonWidget && buttonWidget.getMessage().getString().equals(Text.translatable("menu.reportBugs").getString())) { + for (var child1 : children) { + if (child1.getClass().getName().equals("com.terraformersmc.modmenu.gui.widget.ModMenuButtonWidget")) { + ((ClickableWidgetAccessor) child1).setWidth(button.getWidth()); + } + } + } + children.set(i, button); break; } diff --git a/src/main/java/net/modfest/ballotbox/mixin/client/TitleScreenMixin.java b/src/main/java/net/modfest/ballotbox/mixin/client/TitleScreenMixin.java index bcbeb84..46eb38b 100644 --- a/src/main/java/net/modfest/ballotbox/mixin/client/TitleScreenMixin.java +++ b/src/main/java/net/modfest/ballotbox/mixin/client/TitleScreenMixin.java @@ -13,9 +13,6 @@ import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.ArrayList; import java.util.Comparator; diff --git a/src/main/java/net/modfest/ballotbox/packet/S2CGameJoin.java b/src/main/java/net/modfest/ballotbox/packet/S2CGameJoin.java index c86be94..5241555 100644 --- a/src/main/java/net/modfest/ballotbox/packet/S2CGameJoin.java +++ b/src/main/java/net/modfest/ballotbox/packet/S2CGameJoin.java @@ -7,10 +7,11 @@ import net.minecraft.util.Identifier; import net.modfest.ballotbox.BallotBox; -public record S2CGameJoin(String closingTime, int remainingVotes) implements CustomPayload { +public record S2CGameJoin(String closingTime, boolean hasVotingOptions, int remainingVotes) implements CustomPayload { public static final Id ID = new Id<>(Identifier.of(BallotBox.ID, "game_join")); public static final PacketCodec CODEC = PacketCodec.tuple( PacketCodecs.STRING, S2CGameJoin::closingTime, + PacketCodecs.BOOLEAN, S2CGameJoin::hasVotingOptions, PacketCodecs.INTEGER, S2CGameJoin::remainingVotes, S2CGameJoin::new ); diff --git a/src/main/java/net/modfest/ballotbox/util/ModMetaUtil.java b/src/main/java/net/modfest/ballotbox/util/ModMetaUtil.java index 98c0fac..afa2677 100644 --- a/src/main/java/net/modfest/ballotbox/util/ModMetaUtil.java +++ b/src/main/java/net/modfest/ballotbox/util/ModMetaUtil.java @@ -21,7 +21,7 @@ public class ModMetaUtil { private static final Map modIconCache = new HashMap<>(); - public static NativeImageBackedTexture createIcon(ModContainer iconSource, String iconPath) { + public static NativeImageBackedTexture createIcon(String name, ModContainer iconSource, String iconPath) { try { Path path = iconSource.getPath(iconPath); NativeImageBackedTexture cachedIcon = modIconCache.get(path); @@ -35,7 +35,7 @@ public static NativeImageBackedTexture createIcon(ModContainer iconSource, Strin try (InputStream inputStream = Files.newInputStream(path)) { NativeImage image = NativeImage.read(Objects.requireNonNull(inputStream)); Validate.validState(image.getHeight() == image.getWidth(), "Must be square icon"); - NativeImageBackedTexture tex = new NativeImageBackedTexture(image); + NativeImageBackedTexture tex = new NativeImageBackedTexture(() -> name, image); modIconCache.put(path, tex); return tex; } @@ -60,6 +60,7 @@ public static NativeImageBackedTexture createIcon(ModContainer iconSource, Strin public static NativeImageBackedTexture getMissingIcon() { return createIcon( + "Missing Icon", FabricLoader.getInstance() .getModContainer(BallotBox.ID) .orElseThrow(() -> new RuntimeException("Cannot get ModContainer for Fabric mod with id " + BallotBox.ID)), @@ -75,7 +76,7 @@ public static NativeImageBackedTexture getIcon(ModContainer mod, int preferredSi ModContainer iconSource = FabricLoader.getInstance() .getModContainer(modId) .orElseThrow(() -> new RuntimeException("Cannot get ModContainer for Fabric mod with id " + finalIconSourceId)); - NativeImageBackedTexture icon = createIcon(iconSource, iconPath); + NativeImageBackedTexture icon = createIcon(mod.getMetadata().getName() + " (Mod Icon)", iconSource, iconPath); if (icon == null) return getMissingIcon(); return icon; } diff --git a/src/main/resources/ballotbox.mixins.json b/src/main/resources/ballotbox.mixins.json index 5f34bb7..7b68da9 100644 --- a/src/main/resources/ballotbox.mixins.json +++ b/src/main/resources/ballotbox.mixins.json @@ -1,19 +1,20 @@ { - "required": true, - "minVersion": "0.8", - "package": "net.modfest.ballotbox.mixin", - "compatibilityLevel": "JAVA_21", - "client": [ - "client.GameMenuScreenMixin", - "client.GridWidgetAccessor", - "client.OptionEntryAccessor", - "client.ScreenAccessor", - "client.TitleScreenMixin" - ], - "injectors": { - "defaultRequire": 1 - }, - "mixins": [ - "client.ElementAccessor" - ] + "required": true, + "minVersion": "0.8", + "package": "net.modfest.ballotbox.mixin", + "compatibilityLevel": "JAVA_21", + "client": [ + "client.ClickableWidgetAccessor", + "client.GameMenuScreenMixin", + "client.GridWidgetAccessor", + "client.OptionEntryAccessor", + "client.ScreenAccessor", + "client.TitleScreenMixin" + ], + "injectors": { + "defaultRequire": 1 + }, + "mixins": [ + "client.ElementAccessor" + ] } diff --git a/src/main/resources/data/ballotbox/ballot/categories.json b/src/main/resources/data/ballotbox/ballot/categories.json deleted file mode 100644 index 8d1fd6c..0000000 --- a/src/main/resources/data/ballotbox/ballot/categories.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "id": "best_time_themed", - "name": "Best Time-Themed", - "description": "The best of the best!", - "type": "theme", - "limit": 5 - }, - { - "id": "best_technology_themed", - "name": "Best Technology-Themed", - "description": "The best of the best!", - "type": "theme", - "limit": 5 - }, - { - "id": "best_throwbacks_themed", - "name": "Best Throwbacks-Themed", - "description": "The best of the best!", - "type": "theme", - "limit": 5 - }, - { - "id": "funniest", - "name": "Funniest", - "description": "Mods with a great sense of humour.", - "type": "community", - "limit": 3 - }, - { - "id": "most_likely_to_use", - "name": "Most Likely to Use", - "description": "Essential, approachable mods for future packs.", - "type": "community", - "limit": 3 - }, - { - "id": "most_innovative", - "name": "Most Innovative", - "description": "Mods pushing the medium with fresh, surprising ideas.", - "type": "community", - "limit": 3 - } -] diff --git a/src/main/resources/data/ballotbox/ballot/options.json b/src/main/resources/data/ballotbox/ballot/options.json deleted file mode 100644 index 2f6b827..0000000 --- a/src/main/resources/data/ballotbox/ballot/options.json +++ /dev/null @@ -1,1118 +0,0 @@ -[ - { - "id": "5_hour_energy", - "name": "5 Hour Energy", - "description": "It's not a drink, it's a drink.", - "authors": [ - "pug" - ], - "platform": { - "type": "modrinth", - "project_id": "UTw9dIz3", - "version_id": "mlE6Dv3d" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/UTw9dIz3/db47485b73f9028654fa13078ac83bb5b16d33ad.png" - }, - "download": "https://cdn.modrinth.com/data/UTw9dIz3/versions/mlE6Dv3d/five-hour-energy-1.0.2.jar", - "source": "https://github.com/MerchantPug/5-hour-energy" - }, - { - "id": "amys_industrious_mod", - "name": "Amy's Industrious Mod", - "description": "A mod all about adding friendly industrial resources like Rubber and Cotton, as well as fun blocks, tools and toys to add some flavor to your game!", - "authors": [ - "applebax", - "chai" - ], - "platform": { - "type": "modrinth", - "project_id": "qDPkkwh3", - "version_id": "eAUKHCke" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/qDPkkwh3/6f1f8cfadb3142d66d617f13a083d41fffa51f07.png", - "screenshot": "https://cdn.modrinth.com/data/qDPkkwh3/images/6f63d30d40131ae44681fcbd2f7a95ca631bb2e3_350.webp" - }, - "download": "https://cdn.modrinth.com/data/qDPkkwh3/versions/eAUKHCke/industrious-1.0.1%2B1.21.jar", - "source": "https://github.com/chailotl/industrious" - }, - { - "id": "armistice", - "name": "Armistice: the Peace Engines", - "description": "Machines from tomorrow, bringing peace to today", - "authors": [ - "divineinversion", - "lgmrszd", - "superkat32", - "callmeecho", - "trudle", - "hamaindustries", - "nulb", - "virtualilith", - "worldwidepixel" - ], - "platform": { - "type": "modrinth", - "project_id": "J85FGh0c", - "version_id": "UKaokbWc" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/J85FGh0c/2a4646d20d84534f36e32492bdaa918a90e7c8de_96.webp" - }, - "download": "https://cdn.modrinth.com/data/J85FGh0c/versions/UKaokbWc/armistice-1.0.0-SNAPSHOT19.jar", - "source": "https://github.com/SpiritGameStudios/Armistice" - }, - { - "id": "bubblelife", - "name": "Life in a Bubble", - "description": "A mod about bubbles that control time created for Modfest 1.21", - "authors": [], - "platform": { - "type": "modrinth", - "project_id": "yAZMEMrZ", - "version_id": "vRqrlkdj" - }, - "images": {}, - "download": "https://cdn.modrinth.com/data/yAZMEMrZ/versions/vRqrlkdj/bubblelife-0.2.1.jar", - "source": "https://github.com/ilcheese2/LifeInABubble" - }, - { - "id": "charta", - "name": "Charta", - "description": "An extensive card mod for Minecraft.\nMade for Modfest 1.21", - "authors": [ - "d4rkness_king" - ], - "platform": { - "type": "modrinth", - "project_id": "sFamPxlk", - "version_id": "FiVIMpQX" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/sFamPxlk/da7b407c91b2943230c579351a7d63e039e318df_96.webp", - "screenshot": "https://cdn.modrinth.com/data/sFamPxlk/images/0ac24e544b2afba4ba1b0c6a791f661bd380caa7_350.webp" - }, - "download": "https://cdn.modrinth.com/data/sFamPxlk/versions/FiVIMpQX/charta-0.3-ALPHA.jar", - "source": "https://github.com/lucaargolo/charta" - }, - { - "id": "chickensaurs", - "name": "Chickensaurs", - "description": "A mod that adds the predatory descendants of the chicken to the Nether.", - "authors": [ - "hyperpigeon", - "lumiscosity" - ], - "platform": { - "type": "modrinth", - "project_id": "I3lDQn3Y", - "version_id": "yXdZEtdy" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/I3lDQn3Y/9904ac42d4623457a07493bfd6e504c767988fb0_96.webp", - "screenshot": "https://cdn.modrinth.com/data/I3lDQn3Y/images/26bdefc2eb9e3bd5d2274cd9f2f171f5fa1cb02a.gif" - }, - "download": "https://cdn.modrinth.com/data/I3lDQn3Y/versions/yXdZEtdy/chickensaurs-neoforge-1.21.1-1.0.3.jar", - "source": "https://github.com/HyperPigeon/chickensaurs" - }, - { - "id": "chronoception", - "name": "Chronoception", - "description": "A mod about perception of time. (Adds the ability to desync a player's client's world time from the server's, with blocks and items that change behavior based on it)", - "authors": [ - "chromonym" - ], - "platform": { - "type": "modrinth", - "project_id": "xlEusAGq", - "version_id": "ZoFSm0aN" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/xlEusAGq/7f020aa38837891f689842be257cbebdec1fe889.png", - "screenshot": "https://cdn.modrinth.com/data/xlEusAGq/images/99493e03ec1c9915e22d0171ade8f07ea47a3daa_350.webp" - }, - "download": "https://cdn.modrinth.com/data/xlEusAGq/versions/ZoFSm0aN/chronoception-neoforge-1.2.2.jar", - "source": "https://github.com/chromonym/chronoception" - }, - { - "id": "colourful_clocks", - "name": "Colourful Clocks", - "description": "Tell time in style with Pocket Watches and Customizable Grandfather Clocks!", - "authors": [ - "chefmooon" - ], - "platform": { - "type": "modrinth", - "project_id": "h8cGboOg", - "version_id": "4Tn26yiB" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/h8cGboOg/5d10843b8f9e477236ba9773a41811c519390c61.gif", - "screenshot": "https://cdn.modrinth.com/data/h8cGboOg/images/a1d14245eac249dc5bc5aa58e8d25e1fce0d00bc_350.webp" - }, - "download": "https://cdn.modrinth.com/data/h8cGboOg/versions/4Tn26yiB/colourfulclocks-neoforge-1.21.1-0.1.3.jar", - "source": "https://github.com/ChefMooon/colourful-clocks" - }, - { - "id": "craftsense", - "name": "CraftSense", - "description": "A Minecraft mod that predicts crafting recipes in real-time, allowing you to instantly craft suggested recipes with one click, while adapting to your crafting habits over time.", - "authors": [ - "dooji" - ], - "platform": { - "type": "modrinth", - "project_id": "btPzz1Xc", - "version_id": "HgA9bXPU" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/btPzz1Xc/fabb9fe1cb843a7621ff57d9f5e98c4f72aed281_96.webp", - "screenshot": "https://cdn.modrinth.com/data/btPzz1Xc/images/0598c8519f0dcebd4e5bff95bc68e8b77c644bf6_350.webp" - }, - "download": "https://cdn.modrinth.com/data/btPzz1Xc/versions/HgA9bXPU/craftsense-1.21-v1.0.3.jar", - "source": "https://github.com/dooji2/craftsense/" - }, - { - "id": "crimes_against_the_jvm", - "name": "Crimes Against the JVM", - "description": "Execute arbitrary bytecode (client-only!) from books you wrote. Because funny.", - "authors": [ - "prismatic" - ], - "platform": { - "type": "modrinth", - "project_id": "qn08mIXQ", - "version_id": "ASfYBSF3" - }, - "images": {}, - "download": "https://cdn.modrinth.com/data/qn08mIXQ/versions/ASfYBSF3/catj-1.0.0.jar" - }, - { - "id": "cyberninja", - "name": "CyberNinja", - "description": "An action and build-crafting mod that allows you to become a cyber ninja who fights stylishly with gadgets and skills.", - "authors": [ - "ckenja", - "baguchi" - ], - "platform": { - "type": "modrinth", - "project_id": "IRjG0435", - "version_id": "qgr7P1RF" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/IRjG0435/319e9fdad37b1e7ffe89ea29bdddafe576473b4d_96.webp" - }, - "download": "https://cdn.modrinth.com/data/IRjG0435/versions/qgr7P1RF/cyninja-0.0.1.jar", - "source": "https://github.com/CKenJa/CyberNinja" - }, - { - "id": "dated_draughts", - "name": "Dated Draughts", - "description": "Get blasted on the past! Potion effects that restore old versions' mechanics.", - "authors": [ - "ssblur" - ], - "platform": { - "type": "modrinth", - "project_id": "VLrtW1GN", - "version_id": "KGZIorK0" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/VLrtW1GN/ba9da2bd4ed1d14549ef9546ad07e51eac4d2e6e.png", - "screenshot": "https://cdn.modrinth.com/data/VLrtW1GN/images/5176344e2f6bf5934afb919de69113ed31f15777_350.webp" - }, - "download": "https://cdn.modrinth.com/data/VLrtW1GN/versions/KGZIorK0/dated_draughts-neoforge-0.2.4-sgd.jar", - "source": "https://github.com/ssblur/dated-draughts/" - }, - { - "id": "daysgoneby", - "name": "Days Gone By", - "description": "Legacy blocks from long-forgotten versions~", - "authors": [ - "prismatic" - ], - "platform": { - "type": "modrinth", - "project_id": "A2S1kKiU", - "version_id": "tw6sCMnw" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/A2S1kKiU/1b7a592e621a60cb0a94469e3374cc654034846a_96.webp" - }, - "download": "https://cdn.modrinth.com/data/A2S1kKiU/versions/tw6sCMnw/days-gone-by-1.0.0.jar" - }, - { - "id": "dissolution", - "name": "Dissolution", - "description": "Dissolve blocks in cauldrons to infuse items", - "authors": [ - "leo60228" - ], - "platform": { - "type": "modrinth", - "project_id": "gVB8tSZY", - "version_id": "NBGZp7Ef" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/gVB8tSZY/b63ab647fe9b341c60cf5a61a008c9f4e690b779.png" - }, - "download": "https://cdn.modrinth.com/data/gVB8tSZY/versions/NBGZp7Ef/dissolution-1.0.0.jar", - "source": "https://gitlab.com/vriska/dissolution" - }, - { - "id": "everwinter_modfest", - "mod_id": "everwinter", - "name": "Everwinter", - "description": "Modfest 2024 Entry adding the Iceologer and Snow Machine", - "authors": [ - "thefluffycart55" - ], - "platform": { - "type": "modrinth", - "project_id": "tQdBh6NH", - "version_id": "wbjSZjiG" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/tQdBh6NH/355466c251613198f1585e3aa22ccdd1aa512d2f_96.webp", - "screenshot": "https://cdn.modrinth.com/data/tQdBh6NH/images/b30ef5eeb601b401f2a62010b2168082455dc9b6_350.webp" - }, - "download": "https://cdn.modrinth.com/data/tQdBh6NH/versions/wbjSZjiG/everwinter-1.1a.jar", - "source": "https://github.com/TheFluffycart55/Everwinter" - }, - { - "id": "familiar_magic", - "name": "familiar magic", - "description": "a mod for summoning! (much like /tpahere, but for any entities and way more inconvenient)", - "authors": [ - "afamiliarquiet" - ], - "platform": { - "type": "modrinth", - "project_id": "BF0Jps3E", - "version_id": "QbUo63gI" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/BF0Jps3E/8fc4bc384f1922630de49dd4c7ee9d409c18af2c.png", - "screenshot": "https://cdn.modrinth.com/data/BF0Jps3E/images/5253fdd7041b9335dc3df1dd2b115aacdc9c533d_350.webp" - }, - "download": "https://cdn.modrinth.com/data/BF0Jps3E/versions/QbUo63gI/familiar_magic-1.0.7.jar", - "source": "https://github.com/afamiliarquiet/familiar-magic" - }, - { - "id": "fbombs", - "name": "FBombs", - "description": "Inspired by old-school Forge nuke mods, this has everything that goes boom!", - "authors": [ - "chai", - "milkyfur", - "shirojr", - "lufurrius" - ], - "platform": { - "type": "modrinth", - "project_id": "LEghcpvj", - "version_id": "ZAhVBaVJ" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/LEghcpvj/b77d6aa187c3f46af7e71c4ed0f9ed05f77f1fa2.png", - "screenshot": "https://cdn.modrinth.com/data/LEghcpvj/images/68ca18fd4149d62d3eb555f2e6de07c32e0579cc_350.webp" - }, - "download": "https://cdn.modrinth.com/data/LEghcpvj/versions/ZAhVBaVJ/fbombs-1.2.1%2B1.21.jar", - "source": "https://github.com/Chailotl/fbombs" - }, - { - "id": "foup", - "name": "FOUP", - "description": "Overhead rail item transport", - "authors": [ - "xfacthd" - ], - "platform": { - "type": "modrinth", - "project_id": "qQdpIiMD", - "version_id": "4lCjlnIq" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/qQdpIiMD/d7e6f43f4c438ca2f70d58e8459e909c6107b539_96.webp" - }, - "download": "https://cdn.modrinth.com/data/qQdpIiMD/versions/4lCjlnIq/foup-0.2.5.jar", - "source": "https://github.com/XFactHD/FOUP" - }, - { - "id": "future_things", - "name": "Future Things", - "description": "A mod made for ModFest adding holograms and some space-age blocks.", - "authors": [ - "twins730" - ], - "platform": { - "type": "modrinth", - "project_id": "ZFNTYm4J", - "version_id": "nIl8gOkf" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/ZFNTYm4J/5dbd772f9d459117c540a127dda21c78f339d322_96.webp", - "screenshot": "https://cdn.modrinth.com/data/ZFNTYm4J/images/42975faae3007ee185eb70c19a77e674118870f5_350.webp" - }, - "download": "https://cdn.modrinth.com/data/ZFNTYm4J/versions/nIl8gOkf/future_things-1.0.1.jar", - "source": "https://github.com/Twins730/FutureThings" - }, - { - "id": "gastroarchaeology", - "name": "Gastroarchaeology", - "description": "A mod about finding more ancient edible crops and long-lost recipes!", - "authors": [ - "nateplays95", - "a11v1r15" - ], - "platform": { - "type": "modrinth", - "project_id": "spA4jQeG", - "version_id": "HQL23Ieh" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/spA4jQeG/9f3ea4c913efc928e611e79cf837c4e2106034ec.png" - }, - "download": "https://cdn.modrinth.com/data/spA4jQeG/versions/HQL23Ieh/gastroarchaeology-1.0.0.jar", - "source": "https://github.com/NatePlays95/ModFestProject121" - }, - { - "id": "glowbricks_printing_press_mod", - "name": "Glowbrick's Printing Press Mod", - "description": "This mod aims to allow enchanted book duplication. This is achieved utilizing the past technologies of a printing press. Glowbrick's Printing Press Mod was made for ModFest 1.21.", - "authors": [ - "krysawyr", - "bitflare", - "azinx" - ], - "platform": { - "type": "modrinth", - "project_id": "hZKwRXsF", - "version_id": "PfwNVd9A" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/hZKwRXsF/b298020c09b5eebf17cadec126c9cfd9a7d36eed_96.webp" - }, - "download": "https://cdn.modrinth.com/data/hZKwRXsF/versions/PfwNVd9A/printingpress-1.0.0.jar", - "source": "https://github.com/AzinxtheOnix/Printing_Press_Mod-Modfest_2024" - }, - { - "id": "glowsheep", - "name": "Glow Sheep", - "description": "Adds Glow Sheep.", - "authors": [ - "mclegoman" - ], - "platform": { - "type": "modrinth", - "project_id": "XYM4Qvhm", - "version_id": "HFNGORRN" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/XYM4Qvhm/146bddf214cb6b48094dd2e6f9632c629187af69_96.webp", - "screenshot": "https://cdn.modrinth.com/data/XYM4Qvhm/images/73297d467e017706eee25a59d9b107ef172c6f36_350.webp" - }, - "download": "https://cdn.modrinth.com/data/XYM4Qvhm/versions/HFNGORRN/glowsheep-1.0.1.jar", - "source": "https://github.com/mclegoman/glowsheep" - }, - { - "id": "hyphapiracea", - "name": "HyphaPiracea", - "description": "Channel the Garden's Rot; Steal the Works of the Worlds; Bridge the Gap", - "authors": [ - "phanastrae" - ], - "platform": { - "type": "modrinth", - "project_id": "AfIa2f3W", - "version_id": "EY9MaYNo" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/AfIa2f3W/9902e3dc9a1763ca0686cc78d2fc05db58d6b01a_96.webp", - "screenshot": "https://cdn.modrinth.com/data/AfIa2f3W/images/0ebf00c0b0a9bdf5cddfde2f0b8ad4a80cc0a33b_350.webp" - }, - "download": "https://cdn.modrinth.com/data/AfIa2f3W/versions/EY9MaYNo/hyphapiracea-neoforge-mc1.21.1-0.1.2.jar", - "source": "https://github.com/Phanastrae/hyphapiracea" - }, - { - "id": "interstellar", - "name": "Interstellar", - "description": "Explore the universe in style!", - "authors": [ - "redstonewizard08" - ], - "platform": { - "type": "modrinth", - "project_id": "5hSfW5Sc", - "version_id": "cDWzJQ5k" - }, - "images": {}, - "download": "https://cdn.modrinth.com/data/5hSfW5Sc/versions/cDWzJQ5k/interstellar-modfest-edition-neoforge-0.1.0%2Bmc1.21.1.jar", - "source": "https://github.com/StardustModding/Interstellar-ModFest-Edition" - }, - { - "id": "just_another_witchery_remake", - "name": "Just Another Witchery Remake", - "description": "An old mod in new light, everything remade and re-imagined. Hopefully it will re-spark the old cauldron", - "authors": [ - "anatevka", - "mrsterner", - "techtastic" - ], - "platform": { - "type": "modrinth", - "project_id": "LCO8KZvC", - "version_id": "SULG3Yck" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/LCO8KZvC/c9dd4918d92adaae7e2ce48556d65734f3d4438f.png", - "screenshot": "https://cdn.modrinth.com/data/LCO8KZvC/images/5113381cc9da2c2e27c0b22f065540a847daf955_350.webp" - }, - "download": "https://cdn.modrinth.com/data/LCO8KZvC/versions/SULG3Yck/witchery-neoforge-0.1.2e-modfest.jar", - "source": "https://github.com/mrsterner/witchery" - }, - { - "id": "kill_youkai_with_knives", - "name": "Kill Youkai With Knives", - "description": "Adds magic throwing knives with timestasis capabilities, inspired by The Touhou Project!", - "authors": [ - "pug" - ], - "platform": { - "type": "modrinth", - "project_id": "4OYPLELG", - "version_id": "BkLaZ1Q7" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/4OYPLELG/0cc8d949119f31b2d7c80ffd7090331abb303aaa.png", - "screenshot": "https://cdn.modrinth.com/data/4OYPLELG/images/247bef678f144b65f382f4b7c8d7f0234e6fbdab_350.webp" - }, - "download": "https://cdn.modrinth.com/data/4OYPLELG/versions/BkLaZ1Q7/kill-youkai-with-knives-neoforge-1.1.1%2B1.21.1.jar", - "source": "https://github.com/MerchantPug/kill-youkai-with-knives/" - }, - { - "id": "kinetic_weaponry", - "name": "Kinetic Weaponry", - "description": "Small equipment mod made for ModFest 1.21!", - "authors": [ - "myriantics" - ], - "platform": { - "type": "modrinth", - "project_id": "VlhWIxQe", - "version_id": "RlU9Jvbp" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/VlhWIxQe/bbc76afc91e1fc9272139bf6d7b057b6f5f13256_96.webp", - "screenshot": "https://cdn.modrinth.com/data/VlhWIxQe/images/ce02a112767d10d0091ba2fbed05cc4983b8e897_350.webp" - }, - "download": "https://cdn.modrinth.com/data/VlhWIxQe/versions/RlU9Jvbp/kinetic_weaponry-1.0.3-neoforge-1.21.1.jar", - "source": "https://github.com/myriantics/kinetic-weaponry" - }, - { - "id": "legacy_buildcraft", - "mod_id": "buildcraft", - "name": "Buildcraft-Legacy", - "description": "A port of buildcraft with new textures, new features and a feeling of nostalgia", - "authors": [ - "thepigcat76" - ], - "platform": { - "type": "modrinth", - "project_id": "LTaa2o7Y", - "version_id": "BPOlUFkd" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/LTaa2o7Y/2f622ecc73f0a1e5e3304e7f6f9b5bd5d3ab82a0.png", - "screenshot": "https://cdn.modrinth.com/data/LTaa2o7Y/images/7a6a0b74ecdbaa9d69de251fe08e179413377b01_350.webp" - }, - "download": "https://cdn.modrinth.com/data/LTaa2o7Y/versions/BPOlUFkd/buildcraft-1.0.6.jar", - "source": "https://github.com/Thepigcat76/Buildcraft-Legacy" - }, - { - "id": "legacy_landscape", - "name": "Legacy Landscape", - "description": "Legacy landscapes for your lugubrious level.", - "authors": [ - "sylv" - ], - "platform": { - "type": "modrinth", - "project_id": "EMniOm7T", - "version_id": "74cN8nGq" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/EMniOm7T/749fff8ae53d25fdfc0aa9aba26dbb15fb9dffa7_96.webp", - "screenshot": "https://cdn.modrinth.com/data/EMniOm7T/images/30bef39643d2fe570513010b7ff75e41258ea666_350.webp" - }, - "download": "https://cdn.modrinth.com/data/EMniOm7T/versions/74cN8nGq/legacy_landscape-0.8.4%2B1.21.1.jar", - "source": "https://github.com/VulpixelMC/legacy-landscape" - }, - { - "id": "life_size_bdubs", - "name": "Life-Size Bdubs", - "description": "Befriend a life-size replica of BDoubleO100(Bdubs)! Or add your own friends with data-driven variants! Made for ModFest 1.21!", - "authors": [ - "superkat32" - ], - "platform": { - "type": "modrinth", - "project_id": "3KnJVIlK", - "version_id": "bfpn0I0k" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/3KnJVIlK/846e17b6481177f69dad5dbbc615fb0325e0c1b3_96.webp", - "screenshot": "https://cdn.modrinth.com/data/3KnJVIlK/images/ef670121ecd2195017209f34664e52c00fb98bbe_350.webp" - }, - "download": "https://cdn.modrinth.com/data/3KnJVIlK/versions/bfpn0I0k/lifesizebdubs-1.0.3.jar", - "source": "https://github.com/Superkat32/LifesizeBdubs" - }, - { - "id": "magisterium", - "name": "Magisterium", - "description": "Curate your own spell book from the magical pages scattered around the world!", - "authors": [ - "reoseah" - ], - "platform": { - "type": "modrinth", - "project_id": "Gi97fZyd", - "version_id": "aRjUz6cQ" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/Gi97fZyd/a190b49064cc2662fa377514b5c112067221ef1d.png", - "screenshot": "https://cdn.modrinth.com/data/Gi97fZyd/images/a46771cfd9a290a44e0f00fe53e6599fe7930db4.png" - }, - "download": "https://cdn.modrinth.com/data/Gi97fZyd/versions/aRjUz6cQ/magisterium-0.0.5.jar", - "source": "https://github.com/reoseah/magisterium" - }, - { - "id": "minecon_ruins", - "name": "Minecon Ruins", - "description": "An archaeology mod about a relic from Minecraft's past. Explore the ruins of Minecon and find its sought-after capes", - "authors": [ - "chai" - ], - "platform": { - "type": "modrinth", - "project_id": "iaFRY0rC", - "version_id": "oqr9EHBh" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/iaFRY0rC/d4191b75c82942f1f6a19c277f910c94a2a00add_96.webp", - "screenshot": "https://cdn.modrinth.com/data/iaFRY0rC/images/b7197f39dda6b108129e00c2775a4f8898642d5a_350.webp" - }, - "download": "https://cdn.modrinth.com/data/iaFRY0rC/versions/oqr9EHBh/minecon_ruins-1.0.0%2B1.21.jar", - "source": "https://github.com/Chailotl/minecon-ruins" - }, - { - "id": "minefactorial", - "name": "MineFactorial", - "description": "MineFactory revived and reimagined for modern versions", - "authors": [ - "emmathemartian" - ], - "platform": { - "type": "modrinth", - "project_id": "4sjHMjq5", - "version_id": "yOb70ayL" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/4sjHMjq5/c149e1226898e4846900af255eb89c5bd10cb1ab_96.webp" - }, - "download": "https://cdn.modrinth.com/data/4sjHMjq5/versions/yOb70ayL/minefactorial-0.1.1-modfest.jar", - "source": "https://github.com/EmmaTheMartian/minefactorial/" - }, - { - "id": "minimal_exchange", - "name": "Minimal-Exchange", - "description": "A mod inspired by the equivalent exchange series with a focus on balance and minimalism", - "authors": [ - "thepigcat76" - ], - "platform": { - "type": "modrinth", - "project_id": "xYVtPMaB", - "version_id": "csTJzAtj" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/xYVtPMaB/533550437f589499a1ed9e8f166eefa35e984707.png", - "screenshot": "https://cdn.modrinth.com/data/xYVtPMaB/images/390a5e02b1e82a1a57a25ea127a1e0400573c3f5_350.webp" - }, - "download": "https://cdn.modrinth.com/data/xYVtPMaB/versions/csTJzAtj/minimal_exchange-1.0.4.jar", - "source": "https://github.com/Thepigcat76/minimal-exchange" - }, - { - "id": "modernutils", - "name": "ModernUtils", - "description": "Mod that adds useful utility blocks", - "authors": [ - "roboxgamer" - ], - "platform": { - "type": "modrinth", - "project_id": "6h1fK7Va", - "version_id": "aUoRQ7zd" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/6h1fK7Va/84cb83fc7ab10d879aa5349174950a950af4f767_96.webp" - }, - "download": "https://cdn.modrinth.com/data/6h1fK7Va/versions/aUoRQ7zd/modernutils-0.0.4.jar", - "source": "https://github.com/RoboXGamer/Modern-Utils-Mod" - }, - { - "id": "moretraps", - "name": "MoreTraps", - "description": "Adds skeleton-horse-type traps to other mobs.", - "authors": [ - "notryken" - ], - "platform": { - "type": "modrinth", - "project_id": "JaeCrIFa", - "version_id": "FdI3UYXw" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/JaeCrIFa/b52005a23560893a7045578d392cc32e8c04716e_96.webp", - "screenshot": "https://cdn.modrinth.com/data/JaeCrIFa/images/aa9e0ecb79dfb8dc65601f2ff6ecb2e4633ed5e1_350.webp" - }, - "download": "https://cdn.modrinth.com/data/JaeCrIFa/versions/FdI3UYXw/moretraps-neoforge-1.21-0.1.2.jar", - "source": "https://github.com/TerminalMC/MoreTraps" - }, - { - "id": "mystcraft_ageless", - "name": "Mystcraft: Ageless", - "description": "Mystcraft: Ageless is an attempt at reviving the original Mystcraft Minecraft mod by XCompWiz as it was before it became tedious and over-powered (pre 1.4.7) while expanding it to be a fully polished mod.", - "authors": [ - "mynamesraph" - ], - "platform": { - "type": "modrinth", - "project_id": "Olgeoshd", - "version_id": "E2YDUyWQ" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/Olgeoshd/65c3481e16933cb488e3cd4868d082ae1599aa54.png", - "screenshot": "https://cdn.modrinth.com/data/Olgeoshd/images/f2d333a4f420f1469c839d00f4c93db2777f3817_350.webp" - }, - "download": "https://cdn.modrinth.com/data/Olgeoshd/versions/E2YDUyWQ/mystcraft_ageless-0.2.4-alpha.jar", - "source": "https://github.com/MyNamesRaph/Mystcraft-Ageless" - }, - { - "id": "nyctography", - "name": "Nyctography", - "description": "Adds the Nyctography substitution cipher as a usable Minecraft font.", - "authors": [ - "darkhax" - ], - "platform": { - "type": "modrinth", - "project_id": "ViRnT7xw", - "version_id": "9yssMvwL" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/ViRnT7xw/08284a34258ad0c078151215457339ad51497687_96.webp" - }, - "download": "https://cdn.modrinth.com/data/ViRnT7xw/versions/9yssMvwL/nyctography-neoforge-1.21.1-21.1.1.jar", - "source": "https://github.com/Darkhax-Minecraft/Nyctography" - }, - { - "id": "paper_pyrotechnics", - "name": "Paper Pyrotechnics", - "description": "Adds a fire lance. It explodes once.", - "authors": [ - "sisby_folk" - ], - "platform": { - "type": "modrinth", - "project_id": "TOIhmYrN", - "version_id": "qfm8Y0Lp" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/TOIhmYrN/18826592cb685e54a989693bd33b81c85e506bfb.png", - "screenshot": "https://cdn.modrinth.com/data/TOIhmYrN/images/53f0afa7d7920c6e17897ac3966b85f35a08e8be.png" - }, - "download": "https://cdn.modrinth.com/data/TOIhmYrN/versions/qfm8Y0Lp/paper-pyrotechnics-0.1.0%2B1.21.jar", - "source": "https://github.com/sisby-folk/paper-pyrotechnics" - }, - { - "id": "parachymistry", - "name": "Parachymistry", - "description": "Consumable focused alchemy mod, made for Modfest 2024", - "authors": [ - "sneakedy", - "byantine08", - "tuffetspider", - "devmc", - "auzdin" - ], - "platform": { - "type": "modrinth", - "project_id": "HO4X0h3C", - "version_id": "TmCCr57x" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/HO4X0h3C/b1b5410e7231f42f5740cf8cd3a4537518de8213.png", - "screenshot": "https://cdn.modrinth.com/data/HO4X0h3C/images/5fec3f6952e2bb0d630c01388aebeade6f96b366_350.webp" - }, - "download": "https://cdn.modrinth.com/data/HO4X0h3C/versions/TmCCr57x/parachymistry-1.0.2.2.jar", - "source": "https://github.com/TuffetSpider/Parachymistry" - }, - { - "id": "past_el_palettes", - "name": "Past-el Palettes", - "description": "Bringing back classic colors as dyes", - "authors": [ - "mynamesraph" - ], - "platform": { - "type": "modrinth", - "project_id": "3AvyzCh4", - "version_id": "gsiYEI5b" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/3AvyzCh4/286d60e610b2364a2c54c5d96126b9f0852316ea.png", - "screenshot": "https://cdn.modrinth.com/data/3AvyzCh4/images/49ea65ae351e34fcc7ee1f47307fb9fc00f00ef0.png" - }, - "download": "https://cdn.modrinth.com/data/3AvyzCh4/versions/gsiYEI5b/past_el_palettes-1.0.2.jar", - "source": "https://github.com/MyNamesRaph/Past-el_Palettes" - }, - { - "id": "plastar", - "name": "Mecha Soldier PLASTAR", - "description": "Tiny Mecha Mod!", - "authors": [ - "pug", - "khazoda", - "lemmaeof", - "snakefangox", - "kneelawk", - "mattidragon" - ], - "platform": { - "type": "modrinth", - "project_id": "wyMBaCLa", - "version_id": "cY29BjfU" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/wyMBaCLa/f55d83ccf14dbd9611ee8b1405c9269c27e657d5_96.webp", - "screenshot": "https://cdn.modrinth.com/data/wyMBaCLa/images/fc37b3885b647e871fe93213442970de65e14d44_350.webp" - }, - "download": "https://cdn.modrinth.com/data/wyMBaCLa/versions/cY29BjfU/plastar-neoforge-0.2.2%2B1.21.1.jar", - "source": "https://github.com/Kneelawk/Mecha-Soldier-PLASTAR" - }, - { - "id": "pocketwatchery", - "name": "Pocketwatchery", - "description": "Adds some neat pocketwatches (one word don't @ me)", - "authors": [ - "afamiliarquiet", - "hexvolt" - ], - "platform": { - "type": "modrinth", - "project_id": "dx70wdmQ", - "version_id": "NoaSy0O0" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/dx70wdmQ/34c5934ea6318b9f17a215c894bd5396d29ca1a4.png" - }, - "download": "https://cdn.modrinth.com/data/dx70wdmQ/versions/NoaSy0O0/pocketwatchery-1.0.0.jar" - }, - { - "id": "rewindwatch", - "name": "Rewind Watch", - "description": "Use the Rewind Watch to save your position, and then use it again to rewind yourself to that position", - "authors": [ - "gaming32" - ], - "platform": { - "type": "modrinth", - "project_id": "9TIvoEkj", - "version_id": "krUZWn8Z" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/9TIvoEkj/3a2023b7738ab1fc07d46655af9958ffcc89240f.png" - }, - "download": "https://cdn.modrinth.com/data/9TIvoEkj/versions/krUZWn8Z/rewindwatch-1.0.3.jar", - "source": "https://github.com/Gaming32/rewind-watch" - }, - { - "id": "rods_from_god", - "name": "Rods from God", - "description": "Miscellaneous superweapons for Modfest 1.21", - "authors": [ - "skynotthelimit" - ], - "platform": { - "type": "modrinth", - "project_id": "lfJQNrlz", - "version_id": "NbJERsNj" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/lfJQNrlz/314a805dce71f4086cd9a13cc8044b64808223da_96.webp", - "screenshot": "https://cdn.modrinth.com/data/lfJQNrlz/images/bc5ff292a649c66ace668dc2f2e47439806a356e_350.webp" - }, - "download": "https://cdn.modrinth.com/data/lfJQNrlz/versions/NbJERsNj/rods_from_god-1.1.3%2B1.21.1.jar", - "source": "https://github.com/ekulxam/rods_from_god" - }, - { - "id": "roehrchen", - "name": "R\u00f6hrchen", - "description": "Automate the transfer of items in large quantities efficiently and beautifully.", - "authors": [ - "sammy" - ], - "platform": { - "type": "modrinth", - "project_id": "B1xmduSG", - "version_id": "MXrFcS0y" - }, - "images": {}, - "download": "https://cdn.modrinth.com/data/B1xmduSG/versions/MXrFcS0y/roehrchen-0.5.2.jar", - "source": "https://github.com/kleinbox/Roehrchen/" - }, - { - "id": "sakuracake", - "name": "SakuraCake", - "description": "A magic mod themed around nature and Thaumcraft", - "authors": [ - "awakened_redstone" - ], - "platform": { - "type": "modrinth", - "project_id": "6gkyPuKT", - "version_id": "UYYiH0JE" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/6gkyPuKT/b9cc8a7ab62ca9cc12bbadeb0b81e35ec15dc833_96.webp", - "screenshot": "https://cdn.modrinth.com/data/6gkyPuKT/images/dcfc09e6feb5372dd0f20055a20222b2b91bbc67.gif" - }, - "download": "https://cdn.modrinth.com/data/6gkyPuKT/versions/UYYiH0JE/sakuracake-1.0.0-alpha.1%2Bpatch.12-neoforge%2B1.21.1.jar", - "source": "https://github.com/Awakened-Redstone/SakuraCake" - }, - { - "id": "shattered_stopwatch", - "name": "Shattered Stopwatch", - "description": "A broken traversal mechanic styled after first-person puzzler games and SM64 ABC.", - "authors": [ - "sisby_folk" - ], - "platform": { - "type": "modrinth", - "project_id": "So5Pd4qV", - "version_id": "n7ywGC5O" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/So5Pd4qV/a2644b424fdc0ecec6f8d50e76ccf766cb8dcfc8.png", - "screenshot": "https://cdn.modrinth.com/data/So5Pd4qV/images/0f239b5698e2fa294aa132f4e3492003b49906fc_350.webp" - }, - "download": "https://cdn.modrinth.com/data/So5Pd4qV/versions/n7ywGC5O/shattered-stopwatch-0.1.2%2B1.21.jar", - "source": "https://github.com/sisby-folk/shattered-stopwatch" - }, - { - "id": "shrink_ray", - "name": "Shrink Ray", - "description": "Two new tools for resizing mobs.", - "authors": [ - "theblindbandit6" - ], - "platform": { - "type": "modrinth", - "project_id": "9Ppvgyp0", - "version_id": "AOgXtOXl" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/9Ppvgyp0/a9c6049ef9de9751ef572c5b92f829be22c963c4.png", - "screenshot": "https://cdn.modrinth.com/data/9Ppvgyp0/images/afddb2421d46208e0d92705b347fcea8b6a67427.png" - }, - "download": "https://cdn.modrinth.com/data/9Ppvgyp0/versions/AOgXtOXl/shrinkray-1.0.2.jar", - "source": "https://github.com/theblindband/Modfest-ShrinkRay" - }, - { - "id": "skycats_lucky_blocks", - "name": "Skycat's Lucky Blocks", - "description": "The result of thinking the original lucky blocks was dead (which is false) and ModFest 1.21", - "authors": [ - "skycatminepokie" - ], - "platform": { - "type": "modrinth", - "project_id": "hDMovHxj", - "version_id": "pxRY2QnF" - }, - "images": {}, - "download": "https://cdn.modrinth.com/data/hDMovHxj/versions/pxRY2QnF/skycats-lucky-blocks-0.0.2%2B1.21.1.jar" - }, - { - "id": "spleef_toys", - "name": "Spleef Toys", - "description": "Build spleef arenas in survival with the level of convenience offered by server plugins", - "authors": [ - "chai" - ], - "platform": { - "type": "modrinth", - "project_id": "GTNhQH0z", - "version_id": "8GQaEWmj" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/GTNhQH0z/7f5ff6ba977f49d819029ccb46f51f12311a8508.png", - "screenshot": "https://cdn.modrinth.com/data/GTNhQH0z/images/d641a0fae6c3865156928266490390bba5db9b6f_350.webp" - }, - "download": "https://cdn.modrinth.com/data/GTNhQH0z/versions/8GQaEWmj/spleef_toys-1.0.1%2B1.21.jar", - "source": "https://github.com/chailotl/spleef-toys" - }, - { - "id": "super_mod", - "name": "SuperMod", - "description": "Time only moves when you move (Superhot in mc)", - "authors": [ - "tomate0613" - ], - "platform": { - "type": "modrinth", - "project_id": "AwxkDzPI", - "version_id": "HqWnX72U" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/AwxkDzPI/55d2188683d3a6111b0a5341d70e9deb7c305e74_96.webp", - "screenshot": "https://cdn.modrinth.com/data/AwxkDzPI/images/3a2894586582b58e1828a1ed4cd2945dfe41ada0_350.webp" - }, - "download": "https://cdn.modrinth.com/data/AwxkDzPI/versions/HqWnX72U/super_mod-1.2.5.jar" - }, - { - "id": "tanglr", - "name": "Tanglr", - "description": "A time-tangling time-travel based redstone mod", - "authors": [ - "theepicblock" - ], - "platform": { - "type": "modrinth", - "project_id": "fylLfV6Z", - "version_id": "UUWljnQv" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/fylLfV6Z/7f9f3f7854d330e66671ebacc93af92ac549cc35.png" - }, - "download": "https://cdn.modrinth.com/data/fylLfV6Z/versions/UUWljnQv/tanglr-1.2.2.jar", - "source": "https://github.com/TheEpicBlock/tanglr" - }, - { - "id": "temporang", - "name": "Temporang", - "description": "Travel through the ages using a mysterious Obelisk and an unlikely tool...", - "authors": [ - "doublepi" - ], - "platform": { - "type": "modrinth", - "project_id": "5babTvQV", - "version_id": "VsTLyc1U" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/5babTvQV/cae989a9c31172f12f72dde321229befb83f40f3_96.webp", - "screenshot": "https://cdn.modrinth.com/data/5babTvQV/images/83fa1f385430c8811a1db99c51e746920e050330_350.webp" - }, - "download": "https://cdn.modrinth.com/data/5babTvQV/versions/VsTLyc1U/temporang-1.0.5.jar", - "source": "https://github.com/Hyarin215/modfest_1.21_submission" - }, - { - "id": "the_arcane_path_of_chemistry", - "name": "The Arcane path of Chemistry", - "description": "Unleash the power of alchemy and technology to break down items into atoms, recombine elements, and create magical artifacts with advanced machines and arcane devices.", - "authors": [ - "zuperz" - ], - "platform": { - "type": "modrinth", - "project_id": "d2pc786h", - "version_id": "TYiiVLZZ" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/d2pc786h/3ab76f0bff0d3d989d4bf1933e9fa6c63998b478.png" - }, - "download": "https://cdn.modrinth.com/data/d2pc786h/versions/TYiiVLZZ/The-Arcane-path-of-Chemistry-1.0.3-1.21.1-1.21.2.jar", - "source": "https://github.com/ZuperZV/The-Arcane-path-of-Chemistry" - }, - { - "id": "timewarp", - "name": "Timewarp", - "description": "A Minecraft mod that revives classic Minecraft mechanics, adding areas with customizable old features, random challenges, and old-school gameplay elements for a unique nostalgic experience.", - "authors": [ - "dooji" - ], - "platform": { - "type": "modrinth", - "project_id": "xZwmqKG0", - "version_id": "N4JxYtPO" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/xZwmqKG0/04ba5e4a35abd781e8ff8c8f1db1d6cbcda0228a_96.webp", - "screenshot": "https://cdn.modrinth.com/data/xZwmqKG0/images/3a511fd649429b54b0900345f2bcfb5073440de3_350.webp" - }, - "download": "https://cdn.modrinth.com/data/xZwmqKG0/versions/N4JxYtPO/timewarp-1.21-1.1.6.jar", - "source": "https://github.com/dooji2/timewarp" - }, - { - "id": "unearth", - "name": "Unearth", - "description": "Lightweight Archaeology expansion mod adding more uses for pottery sherds through stamps and dyeable pots!", - "authors": [ - "ira_the_56", - "squishy_com", - "itsmecryptic", - "artificial_fever" - ], - "platform": { - "type": "modrinth", - "project_id": "aK10HizP", - "version_id": "diunSaUI" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/aK10HizP/a6924d160656d16526b3de3c94557cd3c7b2ac86.png", - "screenshot": "https://cdn.modrinth.com/data/aK10HizP/images/719a589ee75487fdec22ac69a410e2b38567516e_350.webp" - }, - "download": "https://cdn.modrinth.com/data/aK10HizP/versions/diunSaUI/unearth-1.21.1-1.0.8.jar" - }, - { - "id": "unstable_timepiece", - "name": "Unstable Timepiece", - "description": "A strange artifact that lets you manipulate your own time field, slow down time to reduce damage taken or speed it up to go fast!", - "authors": [ - "chai" - ], - "platform": { - "type": "modrinth", - "project_id": "Xt9hp0ch", - "version_id": "lJ4dddL9" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/Xt9hp0ch/c152892f212ddee51589d0f950d7fdfdc436a3c6.png" - }, - "download": "https://cdn.modrinth.com/data/Xt9hp0ch/versions/lJ4dddL9/unstable_timepiece-1.0.1%2B1.21.jar", - "source": "https://github.com/Chailotl/unstable-timepiece" - }, - { - "id": "wandering_rana", - "name": "Wandering Rana", - "description": "Readds the Rana mob from classic minecraft, in form of a wandering trader that sells various frog related items, such as her boots and hat", - "authors": [ - "ultrusbot" - ], - "platform": { - "type": "modrinth", - "project_id": "5R8Qlz9L", - "version_id": "EL4qAdjS" - }, - "images": { - "icon": "https://cdn.modrinth.com/data/5R8Qlz9L/92e622b6c462753e143cdafef459bed148b86187_96.webp", - "screenshot": "https://cdn.modrinth.com/data/5R8Qlz9L/images/0c03dd3bd65afa2d4132ee3c9fd4cce2e83be83a_350.webp" - }, - "download": "https://cdn.modrinth.com/data/5R8Qlz9L/versions/EL4qAdjS/WanderingRana-neoforge-0.2.0%2B1.21.1.jar", - "source": "https://github.com/UltrusBot/Wandering-Rana" - } -] diff --git a/src/test/resources/data/ballotbox/ballot/categories.json b/src/test/resources/data/ballotbox/ballot/categories.json new file mode 100644 index 0000000..dcea806 --- /dev/null +++ b/src/test/resources/data/ballotbox/ballot/categories.json @@ -0,0 +1,16 @@ +[ + { + "id": "most_aura", + "name": "Most Aura", + "description": "Projects showcased with the most aura.", + "type": "community", + "limit": 10 + }, + { + "id": "best_rizz", + "name": "Best Rizz", + "description": "The project that has the best rizz.", + "type": "community", + "limit": 1 + } +] diff --git a/src/test/resources/data/ballotbox/ballot/options.json b/src/test/resources/data/ballotbox/ballot/options.json new file mode 100644 index 0000000..5350eda --- /dev/null +++ b/src/test/resources/data/ballotbox/ballot/options.json @@ -0,0 +1,32 @@ +[ + { + "id": "fabric-api", + "mod_id": "fabric-api", + "name": "Fabric API", + "description": "Lightweight and modular API providing common hooks and intercompatibility measures utilized by mods using the Fabric toolchain.", + "platform": { + "type": "modrinth", + "project_id": "P7dR8mSH" + } + }, + { + "id": "modmenu", + "mod_id": "modmenu", + "name": "Mod Menu", + "description": "Adds a mod menu to view the list of mods you have installed.", + "platform": { + "type": "modrinth", + "project_id": "mOgUt4GM" + } + }, + { + "id": "simple-resource-loader", + "mod_id": "simple-resource-loader", + "name": "Simple Resource Loader", + "description": "Simple global / modpack resource pack and data pack loader. No configuration, just few folders.", + "platform": { + "type": "modrinth", + "project_id": "5e65FGXQ" + } + } +]