Compare commits

..

No commits in common. "hotfix-1.21-fabric" and "1.21.9" have entirely different histories.

56 changed files with 1151 additions and 587 deletions

11
.gitattributes vendored
View file

@ -1,9 +1,2 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf
# Auto detect text files and perform LF normalization
* text=auto

View file

@ -1,37 +0,0 @@
# Automatically build the project and run any configured tests for every push
# and submitted pull request. This can help catch issues that only occur on
# certain platforms or Java versions, and provides a first line of defence
# against bad commits.
name: build
on: [pull_request, push]
jobs:
build:
strategy:
matrix:
# Use these Java versions
java: [
21, # Current Java LTS
]
runs-on: ubuntu-22.04
steps:
- name: checkout repository
uses: actions/checkout@v4
- name: validate gradle wrapper
uses: gradle/wrapper-validation-action@v2
- name: setup jdk ${{ matrix.java }}
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: 'microsoft'
- name: make gradle wrapper executable
run: chmod +x ./gradlew
- name: build
run: ./gradlew build
- name: capture build artifacts
if: ${{ matrix.java == '21' }} # Only upload artifacts built from latest java
uses: actions/upload-artifact@v4
with:
name: Artifacts
path: build/libs/

48
.gitignore vendored
View file

@ -1,40 +1,20 @@
# gradle
.gradle/
.architectury-transformer/
build/
out/
classes/
# eclipse
*.launch
# idea
.idea/
*.iml
*.ipr
run/
*.iws
# vscode
.settings/
.vscode/
out/
*.iml
.gradle/
output/
bin/
libs/
.classpath
.project
# macos
*.DS_Store
# fabric
run/
# java
hs_err_*.log
replay_*.log
*.hprof
*.jfr
.idea/
classes/
.metadata
.vscode
.settings
*.launch

View file

@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
<https://www.gnu.org/licenses/why-not-lgpl.html>.

53
README.md Normal file
View file

@ -0,0 +1,53 @@
# Options Profiles
[![Modrinth](https://github.com/intergrav/devins-badges/blob/v3/assets/cozy/available/modrinth_64h.png?raw=true)](https://modrinth.com/mod/options-profiles)
[![Curseforge](https://github.com/intergrav/devins-badges/blob/v3/assets/cozy/available/curseforge_64h.png?raw=true)](https://curseforge.com/minecraft/mc-mods/options-profiles)
[![GitHub](https://github.com/intergrav/devins-badges/blob/v3/assets/cozy/available/git_64h.png?raw=true)](https://github.com/trafficlunar/options-profiles)
Options Profiles is a Minecraft mod that lets you load and save your options as profiles from in-game.
## Features
- Save and load profiles in-game
- Load specific options (e.g. only load keybinds, resource packs, FOV, etc.)
- Load profiles when joining a certain server
- Edit profiles in-game (deleting, renaming, overwriting, pick options to only load)
- Fabric, NeoForge support
- Third party mod support (see below)
## Mod Support
Options Profiles supports these mods which means you can create and load profiles with them and the mod will load their configuration.
- Sodium
- Sodium Extra
- Iris
- Distant Horizons
If you would like support for another mod, open an issue.
## Frequently Asked Questions
- How to open profiles menu without going to options?
> You can open the profiles menu by using the command `/optionsprofiles` or by accessing it from Mod Menu
- Can you port [version]?
> Open an issue in the GitHub repository.
- Can I use this in my modpack?
> You may use this mod in modpacks with credit.
- Where are the profiles saved?
> Profiles are saved in a folder called "options-profiles" in the specified ".minecraft" directory.
- Where can I find the source code for older versions?
> You can find them in the branches.
- Dependencies?
> Architectury API and Fabric API (if on Fabric)
- For any other questions, create an issue or contact me at hello@trafficlunar.net
## Gallery
| Profiles Menu | Edit Profile Screen |
| :--------------------------------------------------------: | :--------------------------------------------------------------------: |
| <img src="gallery/profiles-menu.png" alt="profiles list"/> | <img src="gallery/edit-profile-screen.png" alt="edit profile screen"/> |
| Options Toggle Menu | Options Screen |
| :--------------------------------------------------------------------: | :----------------------------------------------------------: |
| <img src="gallery/options-toggle-menu.png" alt="options toggle menu"/> | <img src="gallery/options-screen.png" alt="options screen"/> |

View file

@ -1,86 +1,74 @@
plugins {
id 'fabric-loom' version '1.8-SNAPSHOT'
id 'maven-publish'
id 'dev.architectury.loom' version '1.11-SNAPSHOT' apply false
id 'architectury-plugin' version '3.4-SNAPSHOT'
id 'com.gradleup.shadow' version '8.3.6' apply false
}
version = project.mod_version
group = project.maven_group
base {
archivesName = project.archives_base_name
architectury {
minecraft = project.minecraft_version
}
repositories {
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
filter {
includeGroup "maven.modrinth"
}
}
allprojects {
group = rootProject.maven_group
version = rootProject.mod_version
}
dependencies {
// To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings loom.officialMojangMappings()
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
subprojects {
apply plugin: 'dev.architectury.loom'
apply plugin: 'architectury-plugin'
apply plugin: 'maven-publish'
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
base {
// Set up a suffixed format for the mod jar names, e.g. `example-fabric`.
archivesName = "$rootProject.archives_name-$project.name"
}
modImplementation "maven.modrinth:sodium:mc1.21.1-0.6.0-fabric" // Sodium
modImplementation "maven.modrinth:sodium-extra:mc1.21.1-0.6.0-beta.3+fabric" // Sodium Extra
modImplementation "maven.modrinth:embeddium:1.0.11+mc1.21.1" // Embeddium
modImplementation "maven.modrinth:distanthorizons:2.2.1-a-1.21.1" // Distant Horizons
}
repositories {
// Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
}
processResources {
inputs.property "version", project.version
loom {
silentMojangMappingsLicense()
}
filesMatching("fabric.mod.json") {
expand "version": project.version
}
}
dependencies {
minecraft "net.minecraft:minecraft:$rootProject.minecraft_version"
mappings loom.officialMojangMappings()
}
tasks.withType(JavaCompile).configureEach {
it.options.release = 21
}
java {
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
java {
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
tasks.withType(JavaCompile).configureEach {
it.options.release = 21
}
jar {
from("LICENSE") {
rename { "${it}_${project.base.archivesName.get()}"}
}
}
// Configure Maven publishing.
publishing {
publications {
mavenJava(MavenPublication) {
artifactId = base.archivesName.get()
from components.java
}
}
// configure the maven publication
publishing {
publications {
create("mavenJava", MavenPublication) {
artifactId = project.archives_base_name
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
// Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
// Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
}
}
}

34
common/build.gradle Normal file
View file

@ -0,0 +1,34 @@
architectury {
common rootProject.enabled_platforms.split(',')
}
repositories {
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
filter {
includeGroup "maven.modrinth"
}
}
}
dependencies {
// We depend on Fabric Loader here to use the Fabric @Environment annotations,
// which get remapped to the correct annotations on each platform.
// Do NOT use other classes from Fabric Loader.
modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}"
// Architectury API
modImplementation "dev.architectury:architectury:$rootProject.architectury_api_version"
// Mod implementations
modImplementation "maven.modrinth:sodium:mc1.21.10-0.7.2-fabric" // Sodium
modImplementation "maven.modrinth:sodium-extra:mc1.21.9-0.7.0+fabric" // Sodium Extra
modImplementation "maven.modrinth:iris:1.9.6+1.21.10-fabric" // Iris
modImplementation "maven.modrinth:distanthorizons:2.3.6-b-1.21.10" // Distant Horizons
modImplementation "maven.modrinth:controlify:2.4.2-fabric,1.21.9" // Controlify
}

View file

@ -0,0 +1,18 @@
package net.trafficlunar.optionsprofiles;
import dev.architectury.event.events.common.CommandRegistrationEvent;
import net.minecraft.client.Minecraft;
import net.trafficlunar.optionsprofiles.gui.ProfilesScreen;
public class Commands {
public static void init() {
CommandRegistrationEvent.EVENT.register(((dispatcher, buildContext, selection) -> dispatcher.register(
net.minecraft.commands.Commands
.literal("optionsprofiles")
.executes(context -> {
Minecraft.getInstance().execute(() -> Minecraft.getInstance().setScreen(new ProfilesScreen(null)));
return 1;
})
)));
}
}

View file

@ -0,0 +1,58 @@
package net.trafficlunar.optionsprofiles;
import com.mojang.blaze3d.platform.InputConstants;
import dev.architectury.event.events.client.ClientTickEvent;
import dev.architectury.registry.client.keymappings.KeyMappingRegistry;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.trafficlunar.optionsprofiles.profiles.ProfileConfiguration;
import net.trafficlunar.optionsprofiles.profiles.Profiles;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class Keybinds {
private static final KeyMapping[] PROFILE_KEYMAPPINGS = new KeyMapping[3];
public static void init() {
KeyMapping.Category category = KeyMapping.Category.register(ResourceLocation.fromNamespaceAndPath(OptionsProfilesMod.MOD_ID, "keys"));
for (int i = 0; i < PROFILE_KEYMAPPINGS.length; i++) {
PROFILE_KEYMAPPINGS[i] = new KeyMapping(
"key.optionsprofiles.profile_" + (i + 1),
InputConstants.Type.KEYSYM,
-1,
category
);
KeyMappingRegistry.register(PROFILE_KEYMAPPINGS[i]);
}
ClientTickEvent.CLIENT_POST.register(minecraft -> {
for (int i = 0; i < PROFILE_KEYMAPPINGS.length; i++) {
while (PROFILE_KEYMAPPINGS[i].consumeClick()) {
loadProfilesByKeybind(i + 1);
}
}
});
}
private static void loadProfilesByKeybind(int keybindIndex) {
try (Stream<Path> paths = Files.list(Profiles.PROFILES_DIRECTORY)) {
paths.filter(Files::isDirectory)
.forEach(path -> {
String profileName = path.getFileName().toString();
ProfileConfiguration profileConfiguration = ProfileConfiguration.get(profileName);
if (profileConfiguration.getKeybindIndex() == keybindIndex) {
Profiles.loadProfile(profileName);
OptionsProfilesMod.LOGGER.warn("[Profile '{}']: Loaded through keybind", profileName);
}
});
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("An error occurred when loading profiles through keybinds", e);
}
}
}

View file

@ -0,0 +1,115 @@
package net.trafficlunar.optionsprofiles;
import dev.architectury.event.events.client.ClientLifecycleEvent;
import dev.architectury.event.events.client.ClientPlayerEvent;
import net.minecraft.client.player.LocalPlayer;
import net.minecraft.network.Connection;
import net.trafficlunar.optionsprofiles.profiles.ProfileConfiguration;
import net.trafficlunar.optionsprofiles.profiles.Profiles;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.Stream;
public class OptionsProfilesMod {
public static final String MOD_ID = "optionsprofiles";
public static final Logger LOGGER = LogManager.getLogger("Options Profiles");
private static OptionsProfilesModConfiguration CONFIG;
public static void init() {
// Create options-profiles directory
Path profilesDirectory = Paths.get("options-profiles");
if (Files.notExists(profilesDirectory)) {
try {
Files.createDirectory(profilesDirectory);
} catch (IOException e) {
LOGGER.error("An error occurred when creating the 'options-profiles' directory.", e);
}
}
// Load mod config
CONFIG = OptionsProfilesModConfiguration.load();
// Load profiles marked to load on startup
ClientLifecycleEvent.CLIENT_STARTED.register(client -> {
try (Stream<Path> paths = Files.list(Profiles.PROFILES_DIRECTORY)) {
paths.filter(Files::isDirectory)
.forEach(path -> {
String profileName = path.getFileName().toString();
// This gets the configuration but also creates the configuration file if it is not there
ProfileConfiguration profileConfiguration = ProfileConfiguration.get(profileName);
if (profileConfiguration.shouldLoadOnStartup()) {
Profiles.loadProfile(profileName);
OptionsProfilesMod.LOGGER.info("[Profile '{}']: Loaded on startup", profileName);
}
});
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("An error occurred when initializing", e);
}
});
// Load profiles marked to load on server join
ClientPlayerEvent.CLIENT_PLAYER_JOIN.register((LocalPlayer player) -> {
handleClientPlayerEvent(player, false);
});
// Load profiles marked to load on server leave
ClientPlayerEvent.CLIENT_PLAYER_QUIT.register((LocalPlayer player) -> {
handleClientPlayerEvent(player, true);
});
Keybinds.init();
Commands.init();
}
public static OptionsProfilesModConfiguration config() {
if (CONFIG == null) {
throw new IllegalStateException("Config not yet available");
} else {
return CONFIG;
}
}
private static void handleClientPlayerEvent(LocalPlayer player, boolean isOnLeave) {
if (player == null) return;
Connection connection = player.connection.getConnection();
// Check if it's not an integrated server
if (!connection.isMemoryConnection()) {
// Get IP address
SocketAddress address = connection.getRemoteAddress();
if (address instanceof InetSocketAddress inetAddress) {
String ip = inetAddress.getHostString().trim();
// Go through all profiles and check what profiles to load
try (Stream<Path> paths = Files.list(Profiles.PROFILES_DIRECTORY)) {
paths.filter(Files::isDirectory)
.forEach(path -> {
String profileName = path.getFileName().toString();
ProfileConfiguration profileConfiguration = ProfileConfiguration.get(profileName);
String[] servers = profileConfiguration.getServers().split(",");
if (servers.length == 0) return;
// Check if "leave" for the leave event or IP for the join event is in profile's servers
if (Arrays.asList(servers).contains(isOnLeave ? "leave" : ip)) {
Profiles.loadProfile(profileName);
OptionsProfilesMod.LOGGER.info("[Profile '{}']: Loaded on server ({})" + (isOnLeave ? "leave" : ""), profileName, ip);
}
});
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("An error occurred when initializing", e);
}
}
}
}
}

View file

@ -0,0 +1,59 @@
package net.trafficlunar.optionsprofiles;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.trafficlunar.optionsprofiles.profiles.Profiles;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class OptionsProfilesModConfiguration {
private static Path configurationFile;
private boolean showProfilesButton = true;
public OptionsProfilesModConfiguration save() {
OptionsProfilesModConfiguration configuration = new OptionsProfilesModConfiguration();
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();
try (BufferedWriter writer = Files.newBufferedWriter(configurationFile)) {
gson.toJson(this, writer);
OptionsProfilesMod.LOGGER.info("Main configuration saved");
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("Unable to write main configuration.json!", e);
}
return configuration;
}
public static OptionsProfilesModConfiguration load() {
OptionsProfilesModConfiguration configuration = new OptionsProfilesModConfiguration();
configurationFile = Profiles.PROFILES_DIRECTORY.resolve("configuration.json");
if (Files.notExists(configurationFile))
configuration.save();
try (BufferedReader reader = Files.newBufferedReader(configurationFile)) {
Gson gson = new Gson();
configuration = gson.fromJson(reader, OptionsProfilesModConfiguration.class);
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("An error occurred when reading the main configuration.json", e);
}
return configuration;
}
public boolean shouldShowProfilesButton() {
return showProfilesButton;
}
public void setShowProfilesButton(boolean showProfilesButton) {
this.showProfilesButton = showProfilesButton;
}
}

View file

@ -1,29 +1,30 @@
package com.trafficlunar.optionsprofiles.gui;
package net.trafficlunar.optionsprofiles.gui;
import com.trafficlunar.optionsprofiles.profiles.Profiles;
import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.components.StringWidget;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.components.*;
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout;
import net.minecraft.client.gui.layouts.LayoutSettings;
import net.minecraft.client.gui.layouts.LinearLayout;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.trafficlunar.optionsprofiles.profiles.ProfileConfiguration;
import net.trafficlunar.optionsprofiles.profiles.Profiles;
public class EditProfileScreen extends Screen {
private final ProfilesScreen profilesScreen;
private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this, 24, 33);
private final Component profileName;
private final ProfileConfiguration profileConfiguration;
private EditBox profileNameEdit;
private EditBox serversEdit;
public EditProfileScreen(ProfilesScreen profilesScreen, Component profileName) {
super(Component.literal(Component.translatable("gui.optionsprofiles.editing-profile-title").getString() + profileName.getString()));
this.profilesScreen = profilesScreen;
this.profileName = profileName;
this.profileConfiguration = ProfileConfiguration.get(profileName.getString());
}
protected void init() {
@ -33,14 +34,22 @@ public class EditProfileScreen extends Screen {
this.profileNameEdit = new EditBox(this.font, this.width / 2 - 102, 116, 204, 20, Component.empty());
this.profileNameEdit.setValue(profileName.getString());
this.serversEdit = new EditBox(this.font, this.width / 2 - 102, 137, 204, 20, Component.empty());
this.serversEdit.setMaxLength(128);
this.serversEdit.setValue(this.profileConfiguration.getServers());
this.serversEdit.setHint(Component.translatable("gui.optionsprofiles.servers-hint").withStyle(ChatFormatting.GRAY));
this.serversEdit.setTooltip(Tooltip.create(Component.translatable("gui.optionsprofiles.servers.tooltip")));
LinearLayout linearLayoutContent = this.layout.addToContents(LinearLayout.vertical().spacing(12), LayoutSettings::alignHorizontallyCenter);
LinearLayout linearLayoutEditBox = linearLayoutContent.addChild(LinearLayout.vertical().spacing(6), LayoutSettings::alignHorizontallyCenter);
LinearLayout linearLayoutEditBox = linearLayoutContent.addChild(LinearLayout.vertical().spacing(3), LayoutSettings::alignHorizontallyCenter);
linearLayoutEditBox.addChild(new StringWidget(Component.translatable("gui.optionsprofiles.profile-name-text"), this.font), LayoutSettings::alignHorizontallyCenter);
linearLayoutEditBox.addChild(this.profileNameEdit);
linearLayoutEditBox.addChild(LinearLayout.vertical()); // add an extra spacing above edit box
linearLayoutEditBox.addChild(new StringWidget(Component.translatable("gui.optionsprofiles.servers-text"), this.font), LayoutSettings::alignHorizontallyCenter);
linearLayoutEditBox.addChild(this.serversEdit);
LinearLayout linearLayoutButtons = linearLayoutContent.addChild(LinearLayout.vertical().spacing(1), LayoutSettings::alignHorizontallyCenter);
linearLayoutButtons.addChild(
Button.builder(
Component.translatable("gui.optionsprofiles.overwrite-options"),
@ -54,22 +63,10 @@ public class EditProfileScreen extends Screen {
.build(),
LayoutSettings::alignHorizontallyCenter
);
linearLayoutButtons.addChild(
Button.builder(
Component.translatable("gui.optionsprofiles.rename-profile"),
(button) -> {
Profiles.renameProfile(profileName.getString(), this.profileNameEdit.getValue());
this.minecraft.setScreen(new EditProfileScreen(profilesScreen, Component.literal(this.profileNameEdit.getValue())));
})
.size(150, 20)
.pos(this.width / 2 - 75, 166)
.build(),
LayoutSettings::alignHorizontallyCenter
);
linearLayoutButtons.addChild(
Button.builder(
Component.translatable("gui.optionsprofiles.options-toggle").append("..."),
(button) -> this.minecraft.setScreen(new OptionsToggleScreen(this, profileName)))
(button) -> this.minecraft.setScreen(new OptionsToggleScreen(this, profileName, profileConfiguration)))
.size(150, 20)
.pos(this.width / 2 - 75, 187)
.tooltip(Tooltip.create(Component.translatable("gui.optionsprofiles.options-toggle.tooltip")))
@ -77,6 +74,24 @@ public class EditProfileScreen extends Screen {
LayoutSettings::alignHorizontallyCenter
);
LinearLayout linearLayoutSettings = linearLayoutContent.addChild(LinearLayout.vertical().spacing(1), LayoutSettings::alignHorizontallyCenter);
linearLayoutSettings.addChild(
CycleButton.<Integer>builder(value -> Component.literal(value.toString()))
.withValues(0, 1, 2, 3)
.withInitialValue(this.profileConfiguration.getKeybindIndex())
.create(0, 0, 150, 20, Component.translatable("gui.optionsprofiles.keybind-index"), (button, keybindIndex) -> {
this.profileConfiguration.setKeybindIndex(keybindIndex);
}),
LayoutSettings::alignHorizontallyCenter
);
linearLayoutSettings.addChild(
CycleButton.onOffBuilder(this.profileConfiguration.shouldLoadOnStartup())
.create(0, 0, 150, 20, Component.translatable("gui.optionsprofiles.load-on-startup"), (button, boolean_) -> {
this.profileConfiguration.setLoadOnStartup(boolean_);
}),
LayoutSettings::alignHorizontallyCenter
);
this.layout.addToFooter(
Button.builder(
CommonComponents.GUI_DONE,
@ -90,7 +105,7 @@ public class EditProfileScreen extends Screen {
.withStyle(ChatFormatting.RED),
(button) -> {
Profiles.deleteProfile(profileName.getString());
this.onClose();
this.onClose(true);
})
.width(50)
.build(),
@ -105,7 +120,18 @@ public class EditProfileScreen extends Screen {
this.layout.arrangeElements();
}
@Override
public void onClose() {
this.onClose(false);
}
public void onClose(boolean deleted) {
if (!deleted) {
this.profileConfiguration.setServers(this.serversEdit.getValue());
this.profileConfiguration.save();
Profiles.renameProfile(profileName.getString(), this.profileNameEdit.getValue());
}
this.minecraft.setScreen(this.profilesScreen);
this.profilesScreen.profilesList.refreshEntries();
}

View file

@ -1,15 +1,19 @@
package com.trafficlunar.optionsprofiles.gui;
package net.trafficlunar.optionsprofiles.gui;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.gui.screens.options.controls.KeyBindsList;
import net.trafficlunar.optionsprofiles.OptionsProfilesMod;
import net.trafficlunar.optionsprofiles.profiles.ProfileConfiguration;
import net.trafficlunar.optionsprofiles.profiles.Profiles;
import com.google.common.collect.ImmutableList;
import com.trafficlunar.optionsprofiles.OptionsProfilesMod;
import com.trafficlunar.optionsprofiles.profiles.ProfileConfiguration;
import com.trafficlunar.optionsprofiles.profiles.Profiles;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.ContainerObjectSelectionList;
import net.minecraft.client.gui.components.CycleButton;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.narration.NarratableEntry;
@ -22,21 +26,40 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class OptionsToggleList extends ContainerObjectSelectionList<OptionsToggleList.Entry> {
public class OptionsToggleList extends ContainerObjectSelectionList<OptionsToggleList.OptionEntry> {
private final String profileName;
private final ProfileConfiguration profileConfiguration;
private final EditBox searchBox;
private final List<OptionEntry> allEntries = new ArrayList<>();
public OptionsToggleList(OptionsToggleScreen optionsToggleScreen, Minecraft minecraft, String profileName) {
public OptionsToggleList(OptionsToggleScreen optionsToggleScreen, Minecraft minecraft, String profileName, ProfileConfiguration profileConfiguration, EditBox searchBox) {
super(minecraft, optionsToggleScreen.width, optionsToggleScreen.layout.getContentHeight(), optionsToggleScreen.layout.getHeaderHeight(), 20);
this.profileConfiguration = optionsToggleScreen.profileConfiguration;
this.profileConfiguration = profileConfiguration;
this.profileName = profileName;
this.searchBox = searchBox;
this.searchBox.setResponder(this::filterEntries);
refreshEntries(false, false);
}
private void filterEntries(String searchText) {
this.clearEntries();
if (searchText.isEmpty()) {
allEntries.forEach(this::addEntry);
return;
}
String filter = searchText.toLowerCase();
allEntries.stream()
.filter(entry -> entry.key.toLowerCase().contains(filter))
.forEach(this::addEntry);
}
// If overriding boolean is set to true then this function will set every option in the list to overrideToggle (false or true)
public void refreshEntries(boolean overriding, boolean overrideToggle) {
this.clearEntries();
allEntries.clear();
Path profile = Profiles.PROFILES_DIRECTORY.resolve(profileName);
Path optionsFile = profile.resolve("options.txt");
@ -60,29 +83,33 @@ public class OptionsToggleList extends ContainerObjectSelectionList<OptionsToggl
}
// Add entry with option key and value and if the key is in the profile configuration
this.addEntry(new OptionEntry(option[0], option[1], profileConfiguration.getOptionsToLoad().contains(option[0])));
allEntries.add(new OptionEntry(option[0], option[1], profileConfiguration.getOptionsToLoad().contains(option[0])));
} else {
this.addEntry(new OptionEntry(option[0], "", profileConfiguration.getOptionsToLoad().contains(option[0])));
allEntries.add(new OptionEntry(option[0], "", profileConfiguration.getOptionsToLoad().contains(option[0])));
}
});
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("An error occurred when listing options", e);
}
filterEntries(searchBox.getValue());
}
protected int getScrollbarPosition() {
return super.getScrollbarPosition() + 15;
protected int scrollBarX() {
return super.scrollBarX() + 15;
}
public int getRowWidth() {
return 340;
}
public class OptionEntry extends Entry {
public class OptionEntry extends ContainerObjectSelectionList.Entry<OptionEntry> {
private final String key;
private final Component optionKey;
private final CycleButton<Boolean> toggleButton;
OptionEntry(String optionKey, String optionValue, boolean toggled) {
this.key = optionKey;
this.optionKey = Component.literal(optionKey);
this.toggleButton = CycleButton.onOffBuilder(toggled).displayOnlyValue().create(0, 0, 44, 20, Component.empty(), (button, boolean_) -> {
@ -110,17 +137,14 @@ public class OptionsToggleList extends ContainerObjectSelectionList<OptionsToggl
}
}
public void render(GuiGraphics guiGraphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
Font fontRenderer = OptionsToggleList.this.minecraft.font;
int posX = OptionsToggleList.this.getScrollbarPosition() - this.toggleButton.getWidth() - 10;
int posY = y - 2;
int textY = y + entryHeight / 2;
guiGraphics.drawString(fontRenderer, this.optionKey, x, textY - 9 / 2, 16777215, false);
public void renderContent(GuiGraphics guiGraphics, int mouseX, int mouseY, boolean hovered, float tickDelta) {
int posX = OptionsToggleList.this.scrollBarX() - this.toggleButton.getWidth() - 10;
int posY = this.getContentY() - 2;
this.toggleButton.setPosition(posX, posY);
this.toggleButton.render(guiGraphics, mouseX, mouseY, tickDelta);
guiGraphics.drawString(OptionsToggleList.this.minecraft.font, this.optionKey, this.getContentX(), this.getContentYMiddle() - 4, -1);
}
public List<? extends GuiEventListener> children() {
@ -131,9 +155,4 @@ public class OptionsToggleList extends ContainerObjectSelectionList<OptionsToggl
return ImmutableList.of(this.toggleButton);
}
}
public abstract static class Entry extends ContainerObjectSelectionList.Entry<OptionsToggleList.Entry> {
public Entry() {
}
}
}

View file

@ -1,30 +1,45 @@
package com.trafficlunar.optionsprofiles.gui;
package net.trafficlunar.optionsprofiles.gui;
import com.trafficlunar.optionsprofiles.profiles.ProfileConfiguration;
import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.components.StringWidget;
import net.minecraft.client.gui.layouts.LinearLayout;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.options.OptionsSubScreen;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.trafficlunar.optionsprofiles.profiles.ProfileConfiguration;
public class OptionsToggleScreen extends OptionsSubScreen {
private final Component profileName;
private final ProfileConfiguration profileConfiguration;
private OptionsToggleList optionsToggleList;
public ProfileConfiguration profileConfiguration;
private EditBox searchBox;
protected OptionsToggleScreen(Screen lastScreen, Component profileName) {
protected OptionsToggleScreen(Screen lastScreen, Component profileName, ProfileConfiguration profileConfiguration) {
super(lastScreen, null, Component.literal(Component.translatable("gui.optionsprofiles.options-toggle").append(": ").getString() + profileName.getString()));
this.profileName = profileName;
this.profileConfiguration = ProfileConfiguration.get(profileName.getString());
this.profileConfiguration = profileConfiguration;
}
protected void init() {
this.searchBox = new EditBox(this.minecraft.font, 0, 0, 200, 20, Component.empty());
this.searchBox.setHint(Component.translatable("gui.optionsprofiles.options-search-hint"));
super.init();
}
protected void addTitle() {
this.layout.addToHeader(new StringWidget(this.title, this.font), (settings) -> settings.alignVerticallyTop().paddingTop(6));
this.layout.addToHeader(this.searchBox, (settings) -> settings.alignVerticallyBottom().padding(4));
}
protected void addOptions() {}
protected void addContents() {
this.layout.setHeaderHeight(24);
this.optionsToggleList = this.layout.addToContents(new OptionsToggleList(this, this.minecraft, profileName.getString()));
this.layout.setHeaderHeight(42);
this.optionsToggleList = this.layout.addToContents(new OptionsToggleList(this, this.minecraft, profileName.getString(), this.profileConfiguration, this.searchBox));
}
protected void addFooter() {
@ -55,7 +70,7 @@ public class OptionsToggleScreen extends OptionsSubScreen {
}
public void removed() {
profileConfiguration.save();
this.profileConfiguration.save();
}
protected void repositionElements() {

View file

@ -1,9 +1,9 @@
package com.trafficlunar.optionsprofiles.gui;
package net.trafficlunar.optionsprofiles.gui;
import net.trafficlunar.optionsprofiles.OptionsProfilesMod;
import net.trafficlunar.optionsprofiles.profiles.ProfileConfiguration;
import net.trafficlunar.optionsprofiles.profiles.Profiles;
import com.google.common.collect.ImmutableList;
import com.trafficlunar.optionsprofiles.OptionsProfilesMod;
import com.trafficlunar.optionsprofiles.profiles.ProfileConfiguration;
import com.trafficlunar.optionsprofiles.profiles.Profiles;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
@ -36,7 +36,9 @@ public class ProfilesList extends ContainerObjectSelectionList<ProfilesList.Prof
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Profiles.PROFILES_DIRECTORY)) {
List<Path> profileList = new ArrayList<>();
for (Path profile : directoryStream) {
profileList.add(profile);
if (Files.isDirectory(profile)) {
profileList.add(profile);
}
}
// Sort the list alphabetically based on the profile names
@ -49,15 +51,15 @@ public class ProfilesList extends ContainerObjectSelectionList<ProfilesList.Prof
OptionsProfilesMod.LOGGER.error("An error occurred when listing profiles", e);
}
checkEntriesLoaded();
// checkEntriesLoaded();
}
public void checkEntriesLoaded() {
this.children().forEach(ProfileEntry::checkLoaded);
}
// public void checkEntriesLoaded() {
// this.children().forEach(ProfileEntry::checkLoaded);
// }
protected int getScrollbarPosition() {
return super.getScrollbarPosition() + 15;
protected int scrollBarX() {
return super.scrollBarX() + 15;
}
public int getRowWidth() {
@ -82,38 +84,26 @@ public class ProfilesList extends ContainerObjectSelectionList<ProfilesList.Prof
Component.translatable("gui.optionsprofiles.load-profile"),
(button) -> {
Profiles.loadProfile(profileName.getString());
OptionsProfilesMod.LOGGER.warn("[Profile '{}']: Loaded through button", profileName);
minecraft.options.load();
if (ProfileConfiguration.get(profileName.getString()).getOptionsToLoad().contains("resourcePacks")) {
minecraft.options.loadSelectedResourcePacks(minecraft.getResourcePackRepository());
minecraft.reloadResourcePacks();
}
minecraft.options.save();
minecraft.levelRenderer.allChanged();
ProfilesList.this.checkEntriesLoaded();
button.active = false;
// ProfilesList.this.checkEntriesLoaded();
// button.active = false;
})
.size(75, 20)
.build();
}
public void render(GuiGraphics guiGraphics, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
Font fontRenderer = ProfilesList.this.minecraft.font;
int posX = ProfilesList.this.getScrollbarPosition() - this.loadButton.getWidth() - 10;
int posY = y - 2;
int textY = y + entryHeight / 2;
guiGraphics.drawString(fontRenderer, this.profileName, x, textY - 9 / 2, 16777215, false);
public void renderContent(GuiGraphics guiGraphics, int mouseX, int mouseY, boolean hovered, float tickDelta) {
int posX = ProfilesList.this.scrollBarX() - this.loadButton.getWidth() - 10;
int posY = this.getContentY() - 2;
this.editButton.setPosition(posX - this.editButton.getWidth(), posY);
this.editButton.render(guiGraphics, mouseX, mouseY, tickDelta);
this.loadButton.setPosition(posX, posY);
this.loadButton.render(guiGraphics, mouseX, mouseY, tickDelta);
guiGraphics.drawString(ProfilesList.this.minecraft.font, this.profileName, this.getContentX(), this.getContentYMiddle() - 4, -1);
}
public List<? extends GuiEventListener> children() {
@ -124,8 +114,8 @@ public class ProfilesList extends ContainerObjectSelectionList<ProfilesList.Prof
return ImmutableList.of(this.editButton, this.loadButton);
}
protected void checkLoaded() {
this.loadButton.active = !Profiles.isProfileLoaded(profileName.getString());
}
// protected void checkLoaded() {
// this.loadButton.active = !Profiles.isProfileLoaded(profileName.getString());
// }
}
}

View file

@ -1,21 +1,20 @@
package com.trafficlunar.optionsprofiles.gui;
package net.trafficlunar.optionsprofiles.gui;
import com.trafficlunar.optionsprofiles.profiles.Profiles;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.layouts.LinearLayout;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.options.OptionsScreen;
import net.minecraft.client.gui.screens.options.OptionsSubScreen;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.trafficlunar.optionsprofiles.profiles.Profiles;
public class ProfilesScreen extends OptionsSubScreen {
private Screen optionsLastScreen;
private final Screen lastScreen;
public ProfilesList profilesList;
public ProfilesScreen(Screen lastScreen, Screen optionsLastScreen) {
public ProfilesScreen(Screen lastScreen) {
super(lastScreen, null, Component.translatable("gui.optionsprofiles.profiles-menu"));
this.optionsLastScreen = optionsLastScreen;
this.lastScreen = lastScreen;
}
protected void addOptions() {}
@ -23,6 +22,13 @@ public class ProfilesScreen extends OptionsSubScreen {
protected void addContents() {
this.layout.setHeaderHeight(24);
this.profilesList = this.layout.addToContents(new ProfilesList(this, this.minecraft));
this.addRenderableWidget(Button.builder(
Component.translatable("gui.optionsprofiles.settings-button"),
(button) -> this.minecraft.setScreen(new SettingsScreen(this)))
.width(75)
.pos(1, 1)
.build());
}
protected void addFooter() {
@ -45,7 +51,7 @@ public class ProfilesScreen extends OptionsSubScreen {
}
public void onClose() {
this.minecraft.setScreen(new OptionsScreen(optionsLastScreen, this.minecraft.options));
this.minecraft.setScreen(lastScreen);
}
protected void repositionElements() {

View file

@ -0,0 +1,57 @@
package net.trafficlunar.optionsprofiles.gui;
import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.CycleButton;
import net.minecraft.client.gui.components.StringWidget;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout;
import net.minecraft.client.gui.layouts.LayoutSettings;
import net.minecraft.client.gui.layouts.LinearLayout;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.trafficlunar.optionsprofiles.OptionsProfilesMod;
public class SettingsScreen extends Screen {
private final Screen lastScreen;
private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this, 24, 33);
public SettingsScreen(Screen lastScreen) {
super(Component.translatable("gui.optionsprofiles.settings-menu"));
this.lastScreen = lastScreen;
}
protected void init() {
LinearLayout linearLayoutHeader = this.layout.addToHeader(LinearLayout.vertical());
linearLayoutHeader.addChild(new StringWidget(this.title, this.font), LayoutSettings::alignHorizontallyCenter);
CycleButton<Boolean> showProfilesButtonButton = CycleButton.onOffBuilder(OptionsProfilesMod.config().shouldShowProfilesButton())
.create(0, 0, 150, 20, Component.translatable("gui.optionsprofiles.show-profiles-button"), (button, boolean_) -> {
OptionsProfilesMod.config().setShowProfilesButton(boolean_);
});
showProfilesButtonButton.setTooltip(Tooltip.create(Component.translatable("gui.optionsprofiles.show-profiles-button.tooltip")));
this.layout.addToContents(showProfilesButtonButton);
this.layout.addToFooter(
Button.builder(
CommonComponents.GUI_DONE,
(button) -> this.onClose())
.width(200)
.build()
);
this.layout.visitWidgets(this::addRenderableWidget);
this.repositionElements();
}
protected void repositionElements() {
this.layout.arrangeElements();
}
public void onClose() {
this.minecraft.setScreen(this.lastScreen);
OptionsProfilesMod.config().save();
}
}

View file

@ -0,0 +1,37 @@
package net.trafficlunar.optionsprofiles.mixin;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.options.OptionsScreen;
import net.minecraft.network.chat.Component;
import net.trafficlunar.optionsprofiles.OptionsProfilesMod;
import net.trafficlunar.optionsprofiles.gui.ProfilesScreen;
import org.spongepowered.asm.mixin.Final;
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;
@Mixin(OptionsScreen.class)
public class MixinOptionsScreen extends Screen {
@Shadow @Final private Screen lastScreen;
protected MixinOptionsScreen(Component component) {
super(component);
}
@Inject(at = @At("HEAD"), method = "init")
private void init(CallbackInfo info) {
if (OptionsProfilesMod.config().shouldShowProfilesButton()) {
this.addRenderableWidget(
Button.builder(
Component.translatable("gui.optionsprofiles.profiles-menu"),
(button) -> this.minecraft.setScreen(new ProfilesScreen(this)))
.width(75)
.pos(5, 5)
.build()
);
}
}
}

View file

@ -1,8 +1,8 @@
package com.trafficlunar.optionsprofiles.profiles;
package net.trafficlunar.optionsprofiles.profiles;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.trafficlunar.optionsprofiles.OptionsProfilesMod;
import net.trafficlunar.optionsprofiles.OptionsProfilesMod;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@ -13,12 +13,12 @@ import java.util.ArrayList;
import java.util.List;
public class ProfileConfiguration {
private static final Path profilesDirectory = Profiles.PROFILES_DIRECTORY;
private static Path configurationFile;
private static String profileName;
public static int configurationVersion = 1; // Used to update configuration in later revisions
private int version = configurationVersion; // ^ same here - this variable is used to show it in the configuration.json file
private boolean loadOnStartup = false;
private String servers = "";
private int keybindIndex = 0;
private List<String> optionsToLoad = new ArrayList<>();
public ProfileConfiguration save() {
@ -41,13 +41,11 @@ public class ProfileConfiguration {
public static ProfileConfiguration get(String profile_name) {
ProfileConfiguration configuration = new ProfileConfiguration();
Path profile = profilesDirectory.resolve(profile_name);
configurationFile = profile.resolve("configuration.json");
configurationFile = Profiles.PROFILES_DIRECTORY.resolve(profile_name).resolve("configuration.json");
profileName = profile_name;
if (Files.notExists(configurationFile)) {
if (Files.notExists(configurationFile))
configuration.save();
}
try (BufferedReader reader = Files.newBufferedReader(configurationFile)) {
Gson gson = new Gson();
@ -59,12 +57,20 @@ public class ProfileConfiguration {
return configuration;
}
public int getVersion() {
return version;
public boolean shouldLoadOnStartup() {
return loadOnStartup;
}
public void setVersion(int version) {
this.version = version;
public void setLoadOnStartup(boolean loadOnStartup) {
this.loadOnStartup = loadOnStartup;
}
public int getKeybindIndex() {
return keybindIndex;
}
public void setKeybindIndex(int keybindIndex) {
this.keybindIndex = keybindIndex;
}
public List<String> getOptionsToLoad() {
@ -74,4 +80,12 @@ public class ProfileConfiguration {
public void setOptionsToLoad(List<String> optionsToLoad) {
this.optionsToLoad = optionsToLoad;
}
public String getServers() {
return servers;
}
public void setServers(String servers) {
this.servers = servers;
}
}

View file

@ -1,10 +1,12 @@
package com.trafficlunar.optionsprofiles.profiles;
package net.trafficlunar.optionsprofiles.profiles;
import com.trafficlunar.optionsprofiles.OptionsProfilesMod;
import com.trafficlunar.optionsprofiles.profiles.loaders.DistantHorizonsLoader;
import com.trafficlunar.optionsprofiles.profiles.loaders.EmbeddiumLoader;
import com.trafficlunar.optionsprofiles.profiles.loaders.SodiumExtraLoader;
import com.trafficlunar.optionsprofiles.profiles.loaders.SodiumLoader;
import dev.architectury.platform.Platform;
import net.minecraft.client.Minecraft;
import net.trafficlunar.optionsprofiles.OptionsProfilesMod;
import net.trafficlunar.optionsprofiles.profiles.loaders.DistantHorizonsLoader;
import net.trafficlunar.optionsprofiles.profiles.loaders.IrisLoader;
import net.trafficlunar.optionsprofiles.profiles.loaders.SodiumExtraLoader;
import net.trafficlunar.optionsprofiles.profiles.loaders.SodiumLoader;
import org.apache.commons.io.FileUtils;
import java.io.IOException;
@ -12,7 +14,9 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;
@ -22,47 +26,9 @@ public class Profiles {
public static final Path OPTIFINE_OPTIONS_FILE = Paths.get("optionsof.txt");
public static final Path SODIUM_OPTIONS_FILE = Paths.get("config/sodium-options.json");
public static final Path SODIUM_EXTRA_OPTIONS_FILE = Paths.get("config/sodium-extra-options.json");
public static final Path EMBEDDIUM_OPTIONS_FILE = Paths.get("config/embeddium-options.json");
public static final Path IRIS_OPTIONS_FILE = Paths.get("config/iris.properties");
public static final Path DISTANT_HORIZONS_OPTIONS_FILE = Paths.get("config/DistantHorizons.toml");
// This function goes through every profile and updates / adds the configuration file if it doesn't exist
public static void updateProfiles() {
try (Stream<Path> paths = Files.list(PROFILES_DIRECTORY)) {
paths.filter(Files::isDirectory)
.forEach(path -> {
String profileName = path.getFileName().toString();
// This gets the configuration but also creates the configuration file if it is not there
ProfileConfiguration profileConfiguration = ProfileConfiguration.get(profileName);
List<String> optionsToLoad = profileConfiguration.getOptionsToLoad();
// Checks for updates to the configuration
if (profileConfiguration.getVersion() != ProfileConfiguration.configurationVersion) {
Path configurationFile = path.resolve("configuration.json");
try {
Files.delete(configurationFile);
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("[Profile '{}']: Error deleting configuration file", profileName, e);
}
// Create the configuration.json again thus updating it
profileConfiguration = ProfileConfiguration.get(profileName);
// Add player's old configuration
profileConfiguration.setOptionsToLoad(optionsToLoad);
// Save configuration
profileConfiguration.save();
}
OptionsProfilesMod.LOGGER.warn("[Profile '{}']: Profile configuration updated / added", profileName);
});
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("An error occurred when updating profiles", e);
}
}
public static void createProfile() {
String profileName = "Profile 1";
Path profile = PROFILES_DIRECTORY.resolve(profileName);
@ -122,10 +88,14 @@ public class Profiles {
copyOptionFile(profile, OPTIONS_FILE);
copyOptionFile(profile, OPTIFINE_OPTIONS_FILE);
copyOptionFile(profile, SODIUM_OPTIONS_FILE);
copyOptionFile(profile, SODIUM_EXTRA_OPTIONS_FILE);
copyOptionFile(profile, EMBEDDIUM_OPTIONS_FILE);
copyOptionFile(profile, DISTANT_HORIZONS_OPTIONS_FILE);
if (Platform.isModLoaded("sodium"))
copyOptionFile(profile, SODIUM_OPTIONS_FILE);
if (Platform.isModLoaded("sodium-extra"))
copyOptionFile(profile, SODIUM_EXTRA_OPTIONS_FILE);
if (Platform.isModLoaded("iris"))
copyOptionFile(profile, IRIS_OPTIONS_FILE);
if (Platform.isModLoaded("distanthorizons"))
copyOptionFile(profile, DISTANT_HORIZONS_OPTIONS_FILE);
if (!overwriting) {
ProfileConfiguration profileConfiguration = ProfileConfiguration.get(profileName);
@ -146,34 +116,61 @@ public class Profiles {
}
}
public static boolean isProfileLoaded(String profileName) {
Path profile = PROFILES_DIRECTORY.resolve(profileName);
// public static boolean isProfileLoaded(String profileName) {
// TODO: rewrite/fix; returns incorrect results
List<Path> optionFiles = new ArrayList<>();
optionFiles.add(OPTIONS_FILE);
// Path profile = PROFILES_DIRECTORY.resolve(profileName);
// ProfileConfiguration profileConfiguration = ProfileConfiguration.get(profileName);
//
// List<Path> optionFiles = new ArrayList<>();
// optionFiles.add(OPTIONS_FILE);
//
// // The next few lines check if the specified file exists. If so, it adds it to the optionFiles ArrayList.
// Optional.of(OPTIFINE_OPTIONS_FILE).filter(Files::exists).ifPresent(optionFiles::add);
// Optional.of(SODIUM_OPTIONS_FILE).filter(file -> Platform.isModLoaded("sodium")).ifPresent(optionFiles::add);
// Optional.of(SODIUM_EXTRA_OPTIONS_FILE).filter(file -> Platform.isModLoaded("sodium-extra")).ifPresent(optionFiles::add);
// Optional.of(IRIS_OPTIONS_FILE).filter(file -> Platform.isModLoaded("iris")).ifPresent(optionFiles::add);
// Optional.of(DISTANT_HORIZONS_OPTIONS_FILE).filter(file -> Platform.isModLoaded("distanthorizons")).ifPresent(optionFiles::add);
//
// // Check if the original option file and the profile option file have the same content
// try {
// for (Path optionFile : optionFiles) {
// Path profileOptions = profile.resolve(optionFile.getFileName());
//
// if (optionFile.getFileName().equals(OPTIONS_FILE)) {
// try (Stream<String> lines = Files.lines(optionFile)) {
// List<String> optionsToLoad = profileConfiguration.getOptionsToLoad();
// AtomicBoolean loaded = new AtomicBoolean(false);
//
// lines.forEach((line) -> {
// String[] option = line.split(":");
//
// if (optionsToLoad.contains(option[0])) {
// try (Stream<String> profileLines = Files.lines(profileOptions)) {
// loaded.set(profileLines.anyMatch(profileLine -> profileLine.equals(line)));
// } catch (IOException e) {
// OptionsProfilesMod.LOGGER.error("[Profile '{}']: An error occurred when checking each line in options.txt if the profiles is loaded", profileName, e);
// }
// }
// });
//
// return loaded.get();
// } catch (IOException e) {
// OptionsProfilesMod.LOGGER.error("[Profile '{}']: An error occurred when opening options.txt to check if the profile is loaded", profileName, e);
// }
// } else {
// if (!FileUtils.contentEquals(optionFile.toFile(), profileOptions.toFile())) {
// return false;
// }
// }
// }
// } catch (IOException e) {
// OptionsProfilesMod.LOGGER.error("[Profile '{}']: An error occurred when checking if the profile is loaded", profileName, e);
// return false;
// }
// The next few lines check if the specified file exists. If so, it adds it to the optionFiles ArrayList.
Optional.of(OPTIFINE_OPTIONS_FILE).filter(Files::exists).ifPresent(optionFiles::add);
Optional.of(SODIUM_OPTIONS_FILE).filter(Files::exists).ifPresent(optionFiles::add);
Optional.of(SODIUM_EXTRA_OPTIONS_FILE).filter(Files::exists).ifPresent(optionFiles::add);
Optional.of(EMBEDDIUM_OPTIONS_FILE).filter(Files::exists).ifPresent(optionFiles::add);
Optional.of(DISTANT_HORIZONS_OPTIONS_FILE).filter(Files::exists).ifPresent(optionFiles::add);
// Check if the original option file and the profile option file have the same content
try {
for (Path optionFile : optionFiles) {
Path profileOptions = profile.resolve(optionFile.getFileName());
if (!FileUtils.contentEquals(optionFile.toFile(), profileOptions.toFile())) {
return false;
}
}
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("[Profile '{}']: An error occurred when checking if the profile is loaded", profileName, e);
return false;
}
return true;
}
// return false;
// }
private static void loadOptionFile(String profileName, Path options) {
ProfileConfiguration profileConfiguration = ProfileConfiguration.get(profileName);
@ -257,12 +254,29 @@ public class Profiles {
public static void loadProfile(String profileName) {
loadOptionFile(profileName, OPTIONS_FILE);
loadOptionFile(profileName, OPTIFINE_OPTIONS_FILE);
loadOptionFile(profileName, SODIUM_OPTIONS_FILE, SodiumLoader::load);
loadOptionFile(profileName, SODIUM_EXTRA_OPTIONS_FILE, SodiumExtraLoader::load);
loadOptionFile(profileName, EMBEDDIUM_OPTIONS_FILE, EmbeddiumLoader::load);
loadOptionFile(profileName, DISTANT_HORIZONS_OPTIONS_FILE); // Overwrite / load original Disant Horizons option file
loadOptionFile(profileName, DISTANT_HORIZONS_OPTIONS_FILE, DistantHorizonsLoader::load); // Tell Distant Horizons mod to reload configuration
if (Platform.isModLoaded("sodium"))
loadOptionFile(profileName, SODIUM_OPTIONS_FILE, SodiumLoader::load);
if (Platform.isModLoaded("sodium-extra"))
loadOptionFile(profileName, SODIUM_EXTRA_OPTIONS_FILE, SodiumExtraLoader::load);
if (Platform.isModLoaded("iris"))
loadOptionFile(profileName, IRIS_OPTIONS_FILE, IrisLoader::load);
if (Platform.isModLoaded("distanthorizons")) {
loadOptionFile(profileName, DISTANT_HORIZONS_OPTIONS_FILE); // Overwrite / load original Disant Horizons option file
loadOptionFile(profileName, DISTANT_HORIZONS_OPTIONS_FILE, DistantHorizonsLoader::load); // Tell Distant Horizons mod to reload configuration
}
// Reload Minecraft options
Minecraft minecraft = Minecraft.getInstance();
minecraft.options.load();
if (ProfileConfiguration.get(profileName).getOptionsToLoad().contains("resourcePacks")) {
minecraft.options.loadSelectedResourcePacks(minecraft.getResourcePackRepository());
minecraft.reloadResourcePacks();
}
minecraft.options.save();
minecraft.levelRenderer.allChanged();
}
public static void renameProfile(String profileName, String newProfileName) {
@ -292,4 +306,4 @@ public class Profiles {
OptionsProfilesMod.LOGGER.error("[Profile '{}']: Profile was not deleted", profileName, e);
}
}
}
}

View file

@ -0,0 +1,11 @@
package net.trafficlunar.optionsprofiles.profiles.loaders;
import com.seibel.distanthorizons.core.config.ConfigHandler;
import java.nio.file.Path;
public class DistantHorizonsLoader {
public static void load(Path file) {
ConfigHandler.INSTANCE.configFileHandler.loadFromFile();
}
}

View file

@ -0,0 +1,35 @@
package net.trafficlunar.optionsprofiles.profiles.loaders;
import net.trafficlunar.optionsprofiles.OptionsProfilesMod;
import net.irisshaders.iris.Iris;
import net.irisshaders.iris.api.v0.IrisApi;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Properties;
public class IrisLoader {
public static void load(Path file) {
try (FileReader reader = new FileReader(file.toFile())) {
Properties properties = new Properties();
properties.load(reader);
apply(properties);
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("An error occurred when reading and loading Iris's configuration", e);
}
}
private static void apply(Properties properties) {
Iris.getIrisConfig().setShaderPackName(properties.getProperty("shaderPack"));
Iris.getIrisConfig().setDebugEnabled("true".equals(properties.getProperty("enableDebugOptions")));
IrisApi.getInstance().getConfig().setShadersEnabledAndApply("true".equals(properties.getProperty("enableShaders")));
try {
Iris.getIrisConfig().save();
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("An error occurred when loading Sodium's configuration", e);
}
}
}

View file

@ -1,21 +1,25 @@
package com.trafficlunar.optionsprofiles.profiles.loaders;
package net.trafficlunar.optionsprofiles.profiles.loaders;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.trafficlunar.optionsprofiles.OptionsProfilesMod;
import me.flashyreese.mods.sodiumextra.client.SodiumExtraClientMod;
import me.flashyreese.mods.sodiumextra.client.gui.FogTypeConfig;
import me.flashyreese.mods.sodiumextra.client.gui.SodiumExtraGameOptions;
import me.flashyreese.mods.sodiumextra.common.util.ResourceLocationSerializer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.material.FogType;
import net.trafficlunar.optionsprofiles.OptionsProfilesMod;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Path;
import java.util.EnumMap;
import java.util.Map;
public class SodiumExtraLoader {
public static void load(Path file) {
try (FileReader reader = new FileReader(file.toFile())) {
Gson gson = new GsonBuilder().create();
Gson gson = new GsonBuilder().registerTypeAdapter(ResourceLocation.class, new ResourceLocationSerializer()).create();
Configuration configuration = gson.fromJson(reader, Configuration.class);
apply(configuration);
@ -40,16 +44,15 @@ public class SodiumExtraLoader {
SodiumExtraClientMod.options().particleSettings.otherMap = configuration.particle_settings.other;
SodiumExtraClientMod.options().detailSettings.sky = configuration.detail_settings.sky;
SodiumExtraClientMod.options().detailSettings.sunMoon = configuration.detail_settings.sun_moon;
SodiumExtraClientMod.options().detailSettings.sun = configuration.detail_settings.sun;
SodiumExtraClientMod.options().detailSettings.moon = configuration.detail_settings.moon;
SodiumExtraClientMod.options().detailSettings.stars = configuration.detail_settings.stars;
SodiumExtraClientMod.options().detailSettings.rainSnow = configuration.detail_settings.rain_snow;
SodiumExtraClientMod.options().detailSettings.biomeColors = configuration.detail_settings.biome_colors;
SodiumExtraClientMod.options().detailSettings.skyColors = configuration.detail_settings.sky_colors;
SodiumExtraClientMod.options().renderSettings.fogDistance = configuration.render_settings.fog_distance;
SodiumExtraClientMod.options().renderSettings.fogStart = configuration.render_settings.fog_start;
SodiumExtraClientMod.options().renderSettings.multiDimensionFogControl = configuration.render_settings.multi_dimension_fog_control;
SodiumExtraClientMod.options().renderSettings.dimensionFogDistanceMap = configuration.render_settings.dimensionFogDistance;
SodiumExtraClientMod.options().renderSettings.globalFog = configuration.render_settings.global_fog;
SodiumExtraClientMod.options().renderSettings.fogTypeConfig = configuration.render_settings.fog_type_config;
SodiumExtraClientMod.options().renderSettings.lightUpdates = configuration.render_settings.light_updates;
SodiumExtraClientMod.options().renderSettings.itemFrame = configuration.render_settings.item_frame;
SodiumExtraClientMod.options().renderSettings.armorStand = configuration.render_settings.armor_stand;
@ -69,7 +72,6 @@ public class SodiumExtraLoader {
SodiumExtraClientMod.options().extraSettings.reduceResolutionOnMac = configuration.extra_settings.reduce_resolution_on_mac;
SodiumExtraClientMod.options().extraSettings.useAdaptiveSync = configuration.extra_settings.use_adaptive_sync;
SodiumExtraClientMod.options().extraSettings.cloudHeight = configuration.extra_settings.cloud_height;
SodiumExtraClientMod.options().extraSettings.cloudDistance = configuration.extra_settings.cloud_distance;
SodiumExtraClientMod.options().extraSettings.toasts = configuration.extra_settings.toasts;
SodiumExtraClientMod.options().extraSettings.advancementToast = configuration.extra_settings.advancement_toast;
SodiumExtraClientMod.options().extraSettings.recipeToast = configuration.extra_settings.recipe_toast;
@ -110,7 +112,8 @@ public class SodiumExtraLoader {
public static class DetailSettings {
public boolean sky;
public boolean sun_moon;
public boolean sun;
public boolean moon;
public boolean stars;
public boolean rain_snow;
public boolean biome_colors;
@ -118,10 +121,8 @@ public class SodiumExtraLoader {
}
public static class RenderSettings {
public int fog_distance;
public int fog_start;
public boolean multi_dimension_fog_control;
public Map<ResourceLocation, Integer> dimensionFogDistance;
public boolean global_fog;
public EnumMap<FogType, FogTypeConfig> fog_type_config;
public boolean light_updates;
public boolean item_frame;
public boolean armor_stand;
@ -143,7 +144,6 @@ public class SodiumExtraLoader {
public boolean reduce_resolution_on_mac;
public boolean use_adaptive_sync;
public int cloud_height;
public int cloud_distance;
public boolean toasts;
public boolean advancement_toast;
public boolean recipe_toast;

View file

@ -1,8 +1,10 @@
package com.trafficlunar.optionsprofiles.profiles.loaders;
package net.trafficlunar.optionsprofiles.profiles.loaders;
import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode;
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode;
import net.trafficlunar.optionsprofiles.OptionsProfilesMod;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.trafficlunar.optionsprofiles.OptionsProfilesMod;
import net.caffeinemc.mods.sodium.client.SodiumClientMod;
import net.caffeinemc.mods.sodium.client.gui.SodiumGameOptions;
@ -23,8 +25,8 @@ public class SodiumLoader {
}
private static void apply(Configuration configuration) {
SodiumClientMod.options().quality.weatherQuality = SodiumGameOptions.GraphicsQuality.valueOf(configuration.quality.weather_quality);
SodiumClientMod.options().quality.leavesQuality = SodiumGameOptions.GraphicsQuality.valueOf(configuration.quality.leaves_quality);
SodiumClientMod.options().quality.weatherQuality = SodiumGameOptions.WeatherQuality.valueOf(configuration.quality.weather_quality);
SodiumClientMod.options().quality.leavesQuality = SodiumGameOptions.LeavesQuality.valueOf(configuration.quality.leaves_quality);
SodiumClientMod.options().quality.enableVignette = configuration.quality.enable_vignette;
SodiumClientMod.options().advanced.enableMemoryTracing = configuration.advanced.enable_memory_tracing;
@ -32,17 +34,19 @@ public class SodiumLoader {
SodiumClientMod.options().advanced.cpuRenderAheadLimit = configuration.advanced.cpu_render_ahead_limit;
SodiumClientMod.options().performance.chunkBuilderThreads = configuration.performance.chunk_builder_threads;
SodiumClientMod.options().performance.alwaysDeferChunkUpdates = configuration.performance.always_defer_chunk_updates_v2;
SodiumClientMod.options().performance.chunkBuildDeferMode = configuration.performance.chunk_build_defer_mode;
SodiumClientMod.options().performance.animateOnlyVisibleTextures = configuration.performance.animate_only_visible_textures;
SodiumClientMod.options().performance.useEntityCulling = configuration.performance.use_entity_culling;
SodiumClientMod.options().performance.useFogOcclusion = configuration.performance.use_fog_occlusion;
SodiumClientMod.options().performance.useBlockFaceCulling = configuration.performance.use_block_face_culling;
SodiumClientMod.options().performance.useNoErrorGLContext = configuration.performance.use_no_error_g_l_context;
SodiumClientMod.options().performance.sortingEnabled = configuration.performance.sorting_enabled_v2;
SodiumClientMod.options().performance.quadSplittingMode = configuration.performance.quad_splitting_mode;
SodiumClientMod.options().notifications.hasClearedDonationButton = configuration.notifications.has_cleared_donation_button;
SodiumClientMod.options().notifications.hasSeenDonationPrompt = configuration.notifications.has_seen_donation_prompt;
SodiumClientMod.options().debug.terrainSortingEnabled = configuration.debug.terrain_sorting_enabled;
try {
SodiumGameOptions.writeToDisk(SodiumClientMod.options());
} catch (IOException e) {
@ -55,6 +59,7 @@ public class SodiumLoader {
public Advanced advanced;
public Performance performance;
public Notifications notifications;
public Debug debug;
public static class Quality {
public String weather_quality;
@ -70,18 +75,22 @@ public class SodiumLoader {
public static class Performance {
public int chunk_builder_threads;
public boolean always_defer_chunk_updates_v2;
public DeferMode chunk_build_defer_mode;
public boolean animate_only_visible_textures;
public boolean use_entity_culling;
public boolean use_fog_occlusion;
public boolean use_block_face_culling;
public boolean use_no_error_g_l_context;
public boolean sorting_enabled_v2;
public QuadSplittingMode quad_splitting_mode;
}
public static class Notifications {
public boolean has_cleared_donation_button;
public boolean has_seen_donation_prompt;
}
public static class Debug {
public boolean terrain_sorting_enabled;
}
}
}

View file

@ -0,0 +1,36 @@
{
"gui.optionsprofiles.profiles-menu": "Profiles",
"gui.optionsprofiles.save-current-options": "Save Current Options",
"gui.optionsprofiles.load-profile": "✔ (Load)",
"gui.optionsprofiles.edit-profile": "✎ (Edit)",
"gui.optionsprofiles.editing-profile-title": "Editing Profile: ",
"gui.optionsprofiles.profile-name-text": "Profile Name",
"gui.optionsprofiles.servers-text": "Server List",
"gui.optionsprofiles.servers-hint": "mc.hypixel.net, play.mccis...",
"gui.optionsprofiles.servers.tooltip": "List servers (comma-separated) to load this profile on join. Add 'leave' to load on server leave.",
"gui.optionsprofiles.overwrite-options": "Overwrite",
"gui.optionsprofiles.overwrite-options.tooltip": "Replaces the profile's options with your current options",
"gui.optionsprofiles.rename-profile": "Rename",
"gui.optionsprofiles.delete-profile": "Delete",
"gui.optionsprofiles.keybind-index": "Keybind index",
"gui.optionsprofiles.load-on-startup": "Load on startup",
"gui.optionsprofiles.options-toggle": "Select options to toggle",
"gui.optionsprofiles.options-search-hint": "Search options...",
"gui.optionsprofiles.options-toggle.tooltip": "Select the options you want to load in this profile",
"gui.optionsprofiles.all-on": "All ON",
"gui.optionsprofiles.all-off": "All OFF",
"gui.optionsprofiles.settings-button": "Settings",
"gui.optionsprofiles.settings-menu": "Options Profiles Settings",
"gui.optionsprofiles.show-profiles-button": "Show Profiles Button",
"gui.optionsprofiles.show-profiles-button.tooltip": "Toggle to remove the profiles button in Options. Access profiles through /optionsprofiles.",
"key.category.optionsprofiles.keys": "Options Profiles",
"key.optionsprofiles.profile_1": "Profile 1",
"key.optionsprofiles.profile_2": "Profile 2",
"key.optionsprofiles.profile_3": "Profile 3"
}

View file

@ -0,0 +1,14 @@
{
"required": true,
"package": "net.trafficlunar.optionsprofiles.mixin",
"compatibilityLevel": "JAVA_17",
"minVersion": "0.8",
"client": [
"MixinOptionsScreen"
],
"mixins": [
],
"injectors": {
"defaultRequire": 1
}
}

65
fabric/build.gradle Normal file
View file

@ -0,0 +1,65 @@
plugins {
id 'com.gradleup.shadow'
}
architectury {
platformSetupLoomIde()
fabric()
}
configurations {
common {
canBeResolved = true
canBeConsumed = false
}
compileClasspath.extendsFrom common
runtimeClasspath.extendsFrom common
developmentFabric.extendsFrom common
// Files in this configuration will be bundled into your mod using the Shadow plugin.
// Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files.
shadowBundle {
canBeResolved = true
canBeConsumed = false
}
}
repositories {
maven {
name = "Terraformers"
url = "https://maven.terraformersmc.com/"
}
}
dependencies {
modImplementation "net.fabricmc:fabric-loader:$rootProject.fabric_loader_version"
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:$rootProject.fabric_api_version"
// Architectury API
modImplementation "dev.architectury:architectury-fabric:$rootProject.architectury_api_version"
// Mod Menu API
modImplementation("com.terraformersmc:modmenu:16.0.0-rc.1")
common(project(path: ':common', configuration: 'namedElements')) { transitive false }
shadowBundle project(path: ':common', configuration: 'transformProductionFabric')
}
processResources {
inputs.property 'version', project.version
filesMatching('fabric.mod.json') {
expand version: project.version
}
}
shadowJar {
configurations = [project.configurations.shadowBundle]
archiveClassifier = 'dev-shadow'
}
remapJar {
input.set shadowJar.archiveFile
}

View file

@ -0,0 +1,12 @@
package net.trafficlunar.optionsprofiles.fabric;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import net.trafficlunar.optionsprofiles.gui.ProfilesScreen;
public class ModMenuApiImpl implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return ProfilesScreen::new;
}
}

View file

@ -0,0 +1,11 @@
package net.trafficlunar.optionsprofiles.fabric;
import net.trafficlunar.optionsprofiles.OptionsProfilesMod;
import net.fabricmc.api.ModInitializer;
public class OptionsProfilesModFabric implements ModInitializer {
@Override
public void onInitialize() {
OptionsProfilesMod.init();
}
}

View file

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -2,11 +2,9 @@
"schemaVersion": 1,
"id": "optionsprofiles",
"version": "${version}",
"name": "optionsprofiles",
"name": "Options Profiles",
"description": "Load and save your options from in-game.",
"authors": [
"trafficlunar"
],
"authors": ["trafficlunar"],
"contact": {
"homepage": "https://github.com/trafficlunar/options-profiles",
"sources": "https://github.com/trafficlunar/options-profiles",
@ -16,17 +14,15 @@
"icon": "assets/optionsprofiles/icon.png",
"environment": "*",
"entrypoints": {
"main": [
"com.trafficlunar.optionsprofiles.OptionsProfilesMod"
]
"main": ["net.trafficlunar.optionsprofiles.fabric.OptionsProfilesModFabric"],
"modmenu": ["net.trafficlunar.optionsprofiles.fabric.ModMenuApiImpl"]
},
"mixins": [
"optionsprofiles.mixins.json"
],
"mixins": ["optionsprofiles.mixins.json"],
"depends": {
"fabricloader": ">=0.16.9",
"fabricloader": ">=0.17.2",
"minecraft": "~1.21",
"java": ">=21",
"architectury": ">=18.0.3",
"fabric-api": "*"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 KiB

BIN
gallery/options-screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 KiB

BIN
gallery/profiles-menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 KiB

View file

@ -1,16 +1,20 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx6G
org.gradle.parallel=true
# Fabric Properties
# check these on https://fabricmc.net/develop
minecraft_version=1.21
loader_version=0.16.9
# Fixes errors caused by Loom version differences
loom.ignoreDependencyLoomVersionValidation=true
# Mod Properties
mod_version=1.3.2
maven_group=com.trafficlunar.optionsprofiles
archives_base_name=optionsprofiles
# Mod properties
mod_version=1.4.4
maven_group=net.trafficlunar.optionsprofiles
archives_name=optionsprofiles
enabled_platforms=fabric,neoforge
# Minecraft properties
minecraft_version=1.21.9
# Dependencies
fabric_version=0.102.0+1.21
architectury_api_version = 18.0.3
fabric_loader_version=0.17.2
fabric_api_version=0.134.0+1.21.9
neoforge_version=21.9.0-beta

Binary file not shown.

View file

@ -1,7 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

13
gradlew vendored Normal file → Executable file
View file

@ -15,8 +15,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -57,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -86,8 +84,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
APP_HOME=$( cd "${APP_HOME:-./}.." > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -115,7 +112,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH=$APP_HOME/lib/gradle-launcher-8.7.jar
# Determine the Java command to use to start the JVM.
@ -203,7 +200,7 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'" \"-javaagent:$APP_HOME/lib/agents/gradle-instrumentation-agent-8.7.jar\""
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
@ -214,7 +211,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
org.gradle.launcher.GradleMain \
"$@"
# Stop when "xargs" is not available.

10
gradlew.bat vendored
View file

@ -13,8 +13,6 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@ -30,13 +28,13 @@ set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
set APP_HOME=%DIRNAME%..
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" "-javaagent:%APP_HOME%/lib/agents/gradle-instrumentation-agent-8.7.jar"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@ -70,11 +68,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=%APP_HOME%\lib\gradle-launcher-8.7.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.launcher.GradleMain %*
:end
@rem End local scope for the variables with windows NT shell

59
neoforge/build.gradle Normal file
View file

@ -0,0 +1,59 @@
plugins {
id 'com.gradleup.shadow'
}
architectury {
platformSetupLoomIde()
neoForge()
}
configurations {
common {
canBeResolved = true
canBeConsumed = false
}
compileClasspath.extendsFrom common
runtimeClasspath.extendsFrom common
developmentNeoForge.extendsFrom common
// Files in this configuration will be bundled into your mod using the Shadow plugin.
// Don't use the `shadow` configuration from the plugin itself as it's meant for excluding files.
shadowBundle {
canBeResolved = true
canBeConsumed = false
}
}
repositories {
maven {
name = 'NeoForged'
url = 'https://maven.neoforged.net/releases'
}
}
dependencies {
neoForge "net.neoforged:neoforge:$rootProject.neoforge_version"
// Architectury API
modImplementation "dev.architectury:architectury-neoforge:$rootProject.architectury_api_version"
common(project(path: ':common', configuration: 'namedElements')) { transitive false }
shadowBundle project(path: ':common', configuration: 'transformProductionNeoForge')
}
processResources {
inputs.property 'version', project.version
filesMatching('META-INF/neoforge.mods.toml') {
expand version: project.version
}
}
shadowJar {
configurations = [project.configurations.shadowBundle]
archiveClassifier = 'dev-shadow'
}
remapJar {
input.set shadowJar.archiveFile
}

View file

@ -0,0 +1 @@
loom.platform = neoforge

View file

@ -0,0 +1,11 @@
package net.trafficlunar.optionsprofiles.neoforge;
import net.trafficlunar.optionsprofiles.OptionsProfilesMod;
import net.neoforged.fml.common.Mod;
@Mod(OptionsProfilesMod.MOD_ID)
public class OptionsProfilesModNeoForge {
public OptionsProfilesModNeoForge() {
OptionsProfilesMod.init();
}
}

View file

@ -0,0 +1,38 @@
modLoader = "javafml"
loaderVersion = "[10,)"
issueTrackerURL = "https://github.com/trafficlunar/options-profiles/issues"
license = "GNU GPL 3.0"
[[mods]]
modId = "optionsprofiles"
version = "${version}"
displayName = "Options Profiles"
authors = "trafficlunar"
description = '''
Load and save your options from in-game.
'''
logoFile = "icon.png"
[[dependencies.optionsprofiles]]
modId = "neoforge"
type = "required"
versionRange = "[21.9.0-beta,)"
ordering = "NONE"
side = "BOTH"
[[dependencies.optionsprofiles]]
modId = "minecraft"
type = "required"
versionRange = "[1.21,)"
ordering = "NONE"
side = "BOTH"
[[dependencies.optionsprofiles]]
modId = "architectury"
type = "required"
versionRange = "[18.0.3,)"
ordering = "AFTER"
side = "BOTH"
[[mixins]]
config = "optionsprofiles.mixins.json"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -1,10 +1,14 @@
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenCentral()
gradlePluginPortal()
}
}
repositories {
maven { url "https://maven.fabricmc.net/" }
maven { url "https://maven.architectury.dev/" }
maven { url "https://maven.minecraftforge.net/" }
gradlePluginPortal()
}
}
include("common")
include("fabric")
include("neoforge")
rootProject.name = "optionsprofiles-v1.4.4-1.21.9"

View file

@ -1,31 +0,0 @@
package com.trafficlunar.optionsprofiles;
import net.fabricmc.api.ModInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class OptionsProfilesMod implements ModInitializer {
public static final String MOD_ID = "optionsprofiles";
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
@Override
public void onInitialize() {
Path profilesDirectory = Paths.get("options-profiles");
if (Files.notExists(profilesDirectory)) {
try {
Files.createDirectory(profilesDirectory);
} catch (IOException e) {
LOGGER.error("An error occurred when creating the 'options-profiles' directory.", e);
}
}
}
}

View file

@ -1,34 +0,0 @@
package com.trafficlunar.optionsprofiles.mixin;
import com.trafficlunar.optionsprofiles.gui.ProfilesScreen;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.options.OptionsScreen;
import net.minecraft.network.chat.Component;
import org.spongepowered.asm.mixin.Final;
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;
@Mixin(OptionsScreen.class)
public class MixinOptionsScreen extends Screen {
@Shadow @Final private Screen lastScreen;
protected MixinOptionsScreen(Component component) {
super(component);
}
@Inject(at = @At("HEAD"), method = "init")
private void init(CallbackInfo info) {
this.addRenderableWidget(
Button.builder(
Component.translatable("gui.optionsprofiles.profiles-menu"),
(button) -> this.minecraft.setScreen(new ProfilesScreen(this, lastScreen)))
.width(75)
.pos(5, 5)
.build()
);
}
}

View file

@ -1,11 +0,0 @@
package com.trafficlunar.optionsprofiles.profiles.loaders;
import com.seibel.distanthorizons.core.config.ConfigBase;
import java.nio.file.Path;
public class DistantHorizonsLoader {
public static void load(Path file) {
ConfigBase.INSTANCE.configFileINSTANCE.loadFromFile();
}
}

View file

@ -1,70 +0,0 @@
package com.trafficlunar.optionsprofiles.profiles.loaders;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.trafficlunar.optionsprofiles.OptionsProfilesMod;
import org.embeddedt.embeddium.impl.Embeddium;
import org.embeddedt.embeddium.impl.gui.EmbeddiumOptions;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Path;
public class EmbeddiumLoader {
public static void load(Path file) {
try (FileReader reader = new FileReader(file.toFile())) {
Gson gson = new GsonBuilder().create();
Configuration configData = gson.fromJson(reader, Configuration.class);
apply(configData);
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("An error occurred when loading Sodium's configuration", e);
}
}
private static void apply(Configuration configuration) {
Embeddium.options().quality.weatherQuality = EmbeddiumOptions.GraphicsQuality.valueOf(configuration.quality.weather_quality);
Embeddium.options().quality.leavesQuality = EmbeddiumOptions.GraphicsQuality.valueOf(configuration.quality.leaves_quality);
Embeddium.options().quality.enableVignette = configuration.quality.enable_vignette;
Embeddium.options().advanced.enableMemoryTracing = configuration.advanced.enable_memory_tracing;
Embeddium.options().advanced.useAdvancedStagingBuffers = configuration.advanced.use_advanced_staging_buffers;
Embeddium.options().advanced.disableIncompatibleModWarnings = configuration.advanced.disable_incompatible_mod_warnings;
Embeddium.options().advanced.cpuRenderAheadLimit = configuration.advanced.cpu_render_ahead_limit;
Embeddium.options().performance.chunkBuilderThreads = configuration.performance.chunk_builder_threads;
Embeddium.options().performance.alwaysDeferChunkUpdates = configuration.performance.always_defer_chunk_updates_v2;
Embeddium.options().performance.animateOnlyVisibleTextures = configuration.performance.animate_only_visible_textures;
Embeddium.options().performance.useEntityCulling = configuration.performance.use_entity_culling;
Embeddium.options().performance.useFogOcclusion = configuration.performance.use_fog_occlusion;
Embeddium.options().performance.useBlockFaceCulling = configuration.performance.use_block_face_culling;
Embeddium.options().performance.useCompactVertexFormat = configuration.performance.use_compact_vertex_format;
Embeddium.options().performance.useTranslucentFaceSorting = configuration.performance.use_translucent_face_sorting_v2;
Embeddium.options().performance.useNoErrorGLContext = configuration.performance.use_no_error_g_l_context;
Embeddium.options().notifications.hasClearedDonationButton = configuration.notifications.has_cleared_donation_button;
Embeddium.options().notifications.hasSeenDonationPrompt = configuration.notifications.has_seen_donation_prompt;
try {
EmbeddiumOptions.writeToDisk(Embeddium.options());
} catch (IOException e) {
OptionsProfilesMod.LOGGER.error("An error occurred when loading Embeddium's configuration", e);
}
}
public static class Configuration {
public SodiumLoader.Configuration.Quality quality;
public Advanced advanced;
public Performance performance;
public SodiumLoader.Configuration.Notifications notifications;
public static class Advanced extends SodiumLoader.Configuration.Advanced {
public boolean disable_incompatible_mod_warnings;
}
public static class Performance extends SodiumLoader.Configuration.Performance {
public boolean use_compact_vertex_format;
public boolean use_translucent_face_sorting_v2;
}
}
}

View file

@ -1,18 +0,0 @@
{
"gui.optionsprofiles.profiles-menu": "Profiles",
"gui.optionsprofiles.save-current-options": "Save Current Options",
"gui.optionsprofiles.load-profile": "✔ (Load)",
"gui.optionsprofiles.edit-profile": "✎ (Edit)",
"gui.optionsprofiles.editing-profile-title": "Editing Profile: ",
"gui.optionsprofiles.profile-name-text": "Profile Name",
"gui.optionsprofiles.overwrite-options": "Overwrite",
"gui.optionsprofiles.overwrite-options.tooltip": "Replaces the profile's options with your current options",
"gui.optionsprofiles.rename-profile": "Rename",
"gui.optionsprofiles.delete-profile": "Delete",
"gui.optionsprofiles.options-toggle": "Select options to toggle",
"gui.optionsprofiles.options-toggle.tooltip": "Select the options you want to load in this profile",
"gui.optionsprofiles.all-on": "All ON",
"gui.optionsprofiles.all-off": "All OFF"
}

View file

@ -1,11 +0,0 @@
{
"required": true,
"package": "com.trafficlunar.optionsprofiles.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"MixinOptionsScreen"
],
"injectors": {
"defaultRequire": 1
}
}