Skip to content

Commit

Permalink
Major Mod Menu Refactor. Closes #23, Closes #56, Closes #80, Closes #112
Browse files Browse the repository at this point in the history
, Closes #135, Closes #149, Closes #183

- Major internal refactors
- Added links, contributors, and license information
- Added an options screen for Mod Menu
- Added a compact list mode option
- Added an options to change Mods button style to: Below Realms (Default), Shrink Realms, Replace Realms, and Fabric Icon
- Added an option to hide mod badges
- Added an option to move where the mod count is displayed (on button, on title screen, both, neither)
- Added options to specify whether or not to count libraries, children, and hidden mods in the mod count
- Added options to hide mod links, credits, or license info
- Added options to disable the modification of the title screen and/or game menu screen
- Changed the hardcoded icon for "java"
- Probably more that got forgotten about in this massive update
  • Loading branch information
Prospector committed Feb 1, 2021
1 parent 253ec5b commit 73bd4ad
Show file tree
Hide file tree
Showing 83 changed files with 2,590 additions and 1,473 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies {

mod "fabric-loader", "net.fabricmc:fabric-loader:$project.loader_version"
includeMod "fabric-api", fabricApi.module("fabric-resource-loader-v0", project.fabric_version)
includeMod "fabric-api", fabricApi.module("fabric-screen-api-v1", project.fabric_version)
}

repositories {
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ archive_name=modmenu
minecraft_version=1.16.5
yarn_mappings=1.16.5+build.3
loader_version=0.11.1
fabric_version=0.29.4+1.16
fabric_version=0.30.0+1.16

# Project Metadata
project_name=Mod Menu
Expand Down
111 changes: 111 additions & 0 deletions src/main/java/com/terraformersmc/modmenu/ModMenu.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.terraformersmc.modmenu;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.LinkedListMultimap;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import com.terraformersmc.modmenu.config.ModMenuConfig;
import com.terraformersmc.modmenu.config.ModMenuConfigManager;
import com.terraformersmc.modmenu.event.ModMenuEventHandler;
import com.terraformersmc.modmenu.util.ModMenuApiMarker;
import com.terraformersmc.modmenu.util.mod.Mod;
import com.terraformersmc.modmenu.util.mod.fabric.FabricDummyParentMod;
import com.terraformersmc.modmenu.util.mod.fabric.FabricMod;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.minecraft.client.gui.screen.Screen;

import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Map;

public class ModMenu implements ClientModInitializer {
public static final String MOD_ID = "modmenu";
public static final Gson GSON = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).setPrettyPrinting().create();

public static final Map<String, Mod> MODS = new HashMap<>();
public static final Map<String, Mod> ROOT_MODS = new HashMap<>();
public static final LinkedListMultimap<Mod, Mod> PARENT_MAP = LinkedListMultimap.create();

private static ImmutableMap<String, ConfigScreenFactory<?>> configScreenFactories = ImmutableMap.of();

private static int cachedDisplayedModCount = -1;

public static Screen getConfigScreen(String modid, Screen menuScreen) {
ConfigScreenFactory<?> factory = configScreenFactories.get(modid);
return factory != null ? factory.create(menuScreen) : null;
}

@Override
public void onInitializeClient() {
ModMenuConfigManager.initializeConfig();
Map<String, ConfigScreenFactory<?>> factories = new HashMap<>();
FabricLoader.getInstance().getEntrypointContainers("modmenu", ModMenuApiMarker.class).forEach(entrypoint -> {
ModMenuApiMarker marker = entrypoint.getEntrypoint();
if (marker instanceof ModMenuApi) {
/* Current API */
ModMenuApi api = (ModMenuApi) marker;
factories.put(entrypoint.getProvider().getMetadata().getId(), api.getModConfigScreenFactory());
api.getProvidedConfigScreenFactories().forEach(factories::putIfAbsent);
} else if (marker instanceof io.github.prospector.modmenu.api.ModMenuApi) {
/* Legacy API */
io.github.prospector.modmenu.api.ModMenuApi api = (io.github.prospector.modmenu.api.ModMenuApi) entrypoint.getEntrypoint();
factories.put(entrypoint.getProvider().getMetadata().getId(), screen -> api.getModConfigScreenFactory().create(screen));
api.getProvidedConfigScreenFactories().forEach((id, legacyFactory) -> factories.put(id, legacyFactory::create));
} else {
throw new RuntimeException(entrypoint.getProvider().getMetadata().getId() + " is providing an invalid ModMenuApi implementation");
}
});
configScreenFactories = new ImmutableMap.Builder<String, ConfigScreenFactory<?>>().putAll(factories).build();


// Fill mods map
for (ModContainer modContainer : FabricLoader.getInstance().getAllMods()) {
if (!ModMenuConfig.HIDDEN_MODS.getValue().contains(modContainer.getMetadata().getId())) {
FabricMod mod = new FabricMod(modContainer);
MODS.put(mod.getId(), mod);
}
}

Map<String, Mod> dummyParents = new HashMap<>();

// Initialize parent map
for (Mod mod : MODS.values()) {
String parentId = mod.getParent();
if (parentId != null) {
Mod parent = MODS.getOrDefault(parentId, dummyParents.get(parentId));
if (parent == null) {
if (mod instanceof FabricMod) {
parent = new FabricDummyParentMod((FabricMod) mod, parentId);
dummyParents.put(parentId, parent);
}
}
PARENT_MAP.put(parent, mod);
} else {
ROOT_MODS.put(mod.getId(), mod);
}
}
MODS.putAll(dummyParents);
ModMenuEventHandler.register();
}

public static void clearModCountCache() {
cachedDisplayedModCount = -1;
}

public static String getDisplayedModCount() {
if (cachedDisplayedModCount == -1) {
// listen, if you have >= 2^32 mods then that's on you
cachedDisplayedModCount = Math.toIntExact(MODS.values().stream().filter(mod ->
(ModMenuConfig.COUNT_CHILDREN.getValue() || mod.getParent() == null) &&
(ModMenuConfig.COUNT_LIBRARIES.getValue() || !mod.getBadges().contains(Mod.Badge.LIBRARY)) &&
(ModMenuConfig.COUNT_HIDDEN_MODS.getValue() || !ModMenuConfig.HIDDEN_MODS.getValue().contains(mod.getId()))
).count());
}
return NumberFormat.getInstance().format(cachedDisplayedModCount);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package io.github.prospector.modmenu;
package com.terraformersmc.modmenu;

import com.google.common.collect.ImmutableMap;
import io.github.prospector.modmenu.api.ConfigScreenFactory;
import io.github.prospector.modmenu.api.ModMenuApi;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import com.terraformersmc.modmenu.gui.ModMenuOptionsScreen;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.options.OptionsScreen;

import java.util.Map;

public class VanillaModMenuCompat implements ModMenuApi {
public class ModMenuModMenuCompat implements ModMenuApi {

@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return ModMenuOptionsScreen::new;
}

@Override
public Map<String, ConfigScreenFactory<?>> getProvidedConfigScreenFactories() {
return ImmutableMap.of("minecraft", parent -> new OptionsScreen(parent, MinecraftClient.getInstance().options));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.terraformersmc.modmenu.api;

import net.minecraft.client.gui.screen.Screen;

@FunctionalInterface
public interface ConfigScreenFactory<S extends Screen> extends io.github.prospector.modmenu.api.ConfigScreenFactory<S> {
S create(Screen parent);
}
48 changes: 48 additions & 0 deletions src/main/java/com/terraformersmc/modmenu/api/ModMenuApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.terraformersmc.modmenu.api;

import com.google.common.collect.ImmutableMap;
import com.terraformersmc.modmenu.gui.ModsScreen;
import com.terraformersmc.modmenu.util.ModMenuApiMarker;
import net.minecraft.client.gui.screen.Screen;

import java.util.Map;

public interface ModMenuApi extends ModMenuApiMarker {
/**
* Used for creating a {@link Screen} instance of the Mod Menu
* "Mods" screen
*
* @param previous The screen before opening
* @return A "Mods" Screen
*/
static Screen createModsScreen(Screen previous) {
return new ModsScreen(previous);
}

/**
* Used to construct a new config screen instance when your mod's
* configuration button is selected on the mod menu screen. The
* screen instance parameter is the active mod menu screen.
*
* @return A factory for constructing config screen instances.
*/
default ConfigScreenFactory<?> getModConfigScreenFactory() {
return screen -> null;
}

/**
* Used to provide config screen factories for other mods. This takes second
* priority to a mod's own config screen factory provider. For example, if
* mod `xyz` supplies a config screen factory, mod `abc` providing a config
* screen to `xyz` will be pointless, as the one provided by `xyz` will be
* used.
* <p>
* This method is NOT meant to be used to add a config screen factory to
* your own mod.
*
* @return a map of mod ids to screen factories.
*/
default Map<String, ConfigScreenFactory<?>> getProvidedConfigScreenFactories() {
return ImmutableMap.of();
}
}
116 changes: 116 additions & 0 deletions src/main/java/com/terraformersmc/modmenu/config/ModMenuConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.terraformersmc.modmenu.config;

import com.google.gson.annotations.SerializedName;
import com.terraformersmc.modmenu.config.option.BooleanConfigOption;
import com.terraformersmc.modmenu.config.option.EnumConfigOption;
import com.terraformersmc.modmenu.config.option.StringSetConfigOption;
import com.terraformersmc.modmenu.util.mod.Mod;
import net.minecraft.client.options.Option;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Locale;

public class ModMenuConfig {
public static final EnumConfigOption<Sorting> SORTING = new EnumConfigOption<>("sorting", Sorting.ASCENDING);
public static final BooleanConfigOption SHOW_LIBRARIES = new BooleanConfigOption("show_libraries", false);
public static final BooleanConfigOption COMPACT_LIST = new BooleanConfigOption("compact_list", false);
public static final BooleanConfigOption HIDE_BADGES = new BooleanConfigOption("hide_badges", false);
public static final EnumConfigOption<ModsButtonStyle> MODS_BUTTON_STYLE = new EnumConfigOption<>("mods_button_style", ModsButtonStyle.CLASSIC);
public static final EnumConfigOption<ModCountLocation> MOD_COUNT_LOCATION = new EnumConfigOption<>("mod_count_location", ModCountLocation.TITLE_SCREEN);
public static final BooleanConfigOption COUNT_LIBRARIES = new BooleanConfigOption("count_libraries", true);
public static final BooleanConfigOption COUNT_CHILDREN = new BooleanConfigOption("count_children", true);
public static final BooleanConfigOption COUNT_HIDDEN_MODS = new BooleanConfigOption("count_hidden_mods", true);
public static final BooleanConfigOption HIDE_CONFIG_BUTTONS = new BooleanConfigOption("hide_config_buttons", false);
public static final BooleanConfigOption HIDE_MOD_LINKS = new BooleanConfigOption("hide_mod_links", false);
public static final BooleanConfigOption HIDE_MOD_CREDITS = new BooleanConfigOption("hide_mod_credits", false);
public static final BooleanConfigOption HIDE_MOD_LICENSE = new BooleanConfigOption("hide_mod_license", false);
public static final BooleanConfigOption EASTER_EGGS = new BooleanConfigOption("easter_eggs", true);
public static final BooleanConfigOption MODIFY_TITLE_SCREEN = new BooleanConfigOption("modify_title_screen", true);
public static final BooleanConfigOption MODIFY_GAME_MENU = new BooleanConfigOption("modify_game_menu", true);
public static final StringSetConfigOption HIDDEN_MODS = new StringSetConfigOption("hidden_mods", new HashSet<>());

public static Option[] asOptions() {
ArrayList<Option> options = new ArrayList<>();
for (Field field : ModMenuConfig.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && Option.class.isAssignableFrom(field.getType()) && !field.getName().equals("HIDE_CONFIG_BUTTONS") && !field.getName().equals("MODIFY_TITLE_SCREEN") && !field.getName().equals("MODIFY_GAME_MENU")) {
try {
options.add((Option) field.get(null));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return options.stream().toArray(Option[]::new);
}

public enum Sorting {
@SerializedName("ascending")
ASCENDING(Comparator.comparing(mod -> mod.getName().toLowerCase(Locale.ROOT))),
@SerializedName("descending")
DESCENDING(ASCENDING.getComparator().reversed());

Comparator<Mod> comparator;

Sorting(Comparator<Mod> comparator) {
this.comparator = comparator;
}

public Comparator<Mod> getComparator() {
return comparator;
}
}

public enum ModCountLocation {
@SerializedName("title_screen")
TITLE_SCREEN(true, false),
@SerializedName("mods_button")
MODS_BUTTON(false, true),
@SerializedName("title_screen_and_mods_button")
TITLE_SCREEN_AND_MODS_BUTTON(true, true),
@SerializedName("none")
NONE(false, false);

private final boolean titleScreen, modsButton;

ModCountLocation(boolean titleScreen, boolean modsButton) {
this.titleScreen = titleScreen;
this.modsButton = modsButton;
}

public boolean isOnTitleScreen() {
return titleScreen;
}

public boolean isOnModsButton() {
return modsButton;
}
}

public enum ModsButtonStyle {
@SerializedName("classic")
CLASSIC(false),
@SerializedName("replace_realms")
REPLACE_REALMS(true),
@SerializedName("shrink")
SHRINK(false),
@SerializedName("fabric_icon")
FABRIC_ICON(false);

private final boolean titleScreenOnly;

ModsButtonStyle(boolean titleScreenOnly) {
this.titleScreenOnly = titleScreenOnly;
}

public ModsButtonStyle forGameMenu() {
if (titleScreenOnly) {
return CLASSIC;
}
return this;
}
}
}
Loading

0 comments on commit 73bd4ad

Please sign in to comment.