mirror of
https://github.com/chylex/Minecraft-Window-Title.git
synced 2025-05-05 04:34:06 +02:00
Implement custom window title w/ {mcversion} and {modversion} tokens
This commit is contained in:
parent
abaa23c5c2
commit
c6102427aa
Fabric/src/main
Forge/src/main
java/chylex/customwindowtitle
resources
@ -0,0 +1,46 @@
|
||||
package chylex.customwindowtitle;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class TitleParser{
|
||||
private static final Pattern tokenRegex = Pattern.compile("\\{([a-z]+)(?::([^}]+))?}");
|
||||
private static final Logger logger = LogManager.getLogger("CustomWindowTitle");
|
||||
|
||||
public static String parse(String input){
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
Matcher matcher = tokenRegex.matcher(input);
|
||||
|
||||
while(matcher.find()){
|
||||
String token = matcher.group(1);
|
||||
String[] args = StringUtils.split(matcher.group(2), ',');
|
||||
|
||||
String result = null;
|
||||
|
||||
try{
|
||||
result = TitleTokens.getTokenFunction(token).apply(args == null ? ArrayUtils.EMPTY_STRING_ARRAY : args);
|
||||
}catch(TokenException e){
|
||||
logger.warn("Error processing token '" + token + "': " + e.getMessage());
|
||||
}catch(Throwable t){
|
||||
logger.warn("Error processing token '" + token + "': " + t.getMessage(), t);
|
||||
}
|
||||
|
||||
if (result == null){
|
||||
matcher.appendReplacement(buffer, input.substring(matcher.start(), matcher.end()));
|
||||
}
|
||||
else{
|
||||
matcher.appendReplacement(buffer, result);
|
||||
}
|
||||
}
|
||||
|
||||
matcher.appendTail(buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
// Static class
|
||||
|
||||
private TitleParser(){}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package chylex.customwindowtitle;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
public final class TitleTokens{
|
||||
|
||||
// Registry
|
||||
|
||||
private static final Map<String, Function<String[], String>> tokenMap = new HashMap<>();
|
||||
|
||||
public static void registerToken(String token, Function<String[], String> processor){
|
||||
tokenMap.putIfAbsent(token, processor);
|
||||
}
|
||||
|
||||
public static Function<String[], String> getTokenFunction(String token){
|
||||
return tokenMap.getOrDefault(token, args -> null);
|
||||
}
|
||||
|
||||
// Arguments
|
||||
|
||||
public static Function<String[], String> noArgs(Supplier<String> func){
|
||||
return args -> args.length > 0 ? fail("expected no arguments, got " + args.length) : func.get();
|
||||
}
|
||||
|
||||
public static Function<String[], String> oneArg(UnaryOperator<String> func){
|
||||
return args -> args.length != 1 ? fail("expected 1 argument, got " + args.length) : func.apply(args[0]);
|
||||
}
|
||||
|
||||
public static Function<String[], String> rangeArgs(int min, int max, Function<String[], String> func){
|
||||
return args -> args.length < min || args.length > max ? fail("expected between " + min + " and " + max + " arguments, got " + args.length) : func.apply(args);
|
||||
}
|
||||
|
||||
private static String fail(String message){
|
||||
throw new TokenException(message);
|
||||
}
|
||||
|
||||
// Static class
|
||||
|
||||
private TitleTokens(){}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package chylex.customwindowtitle;
|
||||
|
||||
public class TokenException extends RuntimeException{
|
||||
public TokenException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package chylex.customwindowtitle.fabric;
|
||||
import chylex.customwindowtitle.TitleParser;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
|
||||
public class CustomWindowTitle implements ClientModInitializer{
|
||||
private static final String defaultTitle = "Minecraft {mcversion}";
|
||||
private String configTitle;
|
||||
|
||||
@Override
|
||||
public void onInitializeClient(){
|
||||
Path configFile = Paths.get(FabricLoader.getInstance().getConfigDirectory().getAbsolutePath(), "customwindowtitle-client.toml");
|
||||
|
||||
try{
|
||||
String prefix = "title = ";
|
||||
|
||||
if (!Files.exists(configFile)){
|
||||
Files.write(configFile, Collections.singletonList(prefix + '"' + defaultTitle + '"'), StandardCharsets.UTF_8);
|
||||
configTitle = defaultTitle;
|
||||
}
|
||||
else{
|
||||
configTitle = Files
|
||||
.readAllLines(configFile, StandardCharsets.UTF_8)
|
||||
.stream()
|
||||
.filter(line -> line.startsWith(prefix))
|
||||
.map(line -> StringUtils.strip(StringUtils.removeStart(line, prefix).trim(), "\""))
|
||||
.findFirst()
|
||||
.orElse(defaultTitle);
|
||||
}
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException("CustomWindowTitle configuration error", e);
|
||||
}
|
||||
|
||||
TokenData.register();
|
||||
MinecraftClient.getInstance().execute(this::updateTitle);
|
||||
}
|
||||
|
||||
private void updateTitle(){
|
||||
MinecraftClient.getInstance().getWindow().setTitle(TitleParser.parse(configTitle));
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package chylex.customwindowtitle.fabric;
|
||||
import chylex.customwindowtitle.TokenException;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.SharedConstants;
|
||||
import static chylex.customwindowtitle.TitleTokens.noArgs;
|
||||
import static chylex.customwindowtitle.TitleTokens.oneArg;
|
||||
import static chylex.customwindowtitle.TitleTokens.registerToken;
|
||||
|
||||
final class TokenData{
|
||||
static void register(){
|
||||
registerToken("mcversion", noArgs(TokenData::getMinecraftVersion));
|
||||
registerToken("modversion", oneArg(TokenData::getModVersion));
|
||||
}
|
||||
|
||||
static String getMinecraftVersion(){
|
||||
return SharedConstants.getGameVersion().getName();
|
||||
}
|
||||
|
||||
static String getModVersion(String modId){
|
||||
return FabricLoader.getInstance().getModContainer(modId).orElseThrow(() -> new TokenException("mod info for '" + modId + "' not found")).getMetadata().getVersion().getFriendlyString();
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package chylex.customwindowtitle.fabric.mixin;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(MinecraftClient.class)
|
||||
public final class DisableVanillaTitle{
|
||||
@Inject(method = "updateWindowTitle()V", at = @At("HEAD"), cancellable = true)
|
||||
private void updateTitle(CallbackInfo info){
|
||||
info.cancel();
|
||||
}
|
||||
}
|
13
Fabric/src/main/resources/mixins.json
Normal file
13
Fabric/src/main/resources/mixins.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"required": true,
|
||||
"package": "chylex.customwindowtitle.fabric.mixin",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
],
|
||||
"client": [
|
||||
"DisableVanillaTitle"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package chylex.customwindowtitle;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class TitleParser{
|
||||
private static final Pattern tokenRegex = Pattern.compile("\\{([a-z]+)(?::([^}]+))?}");
|
||||
private static final Logger logger = LogManager.getLogger("CustomWindowTitle");
|
||||
|
||||
public static String parse(String input){
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
Matcher matcher = tokenRegex.matcher(input);
|
||||
|
||||
while(matcher.find()){
|
||||
String token = matcher.group(1);
|
||||
String[] args = StringUtils.split(matcher.group(2), ',');
|
||||
|
||||
String result = null;
|
||||
|
||||
try{
|
||||
result = TitleTokens.getTokenFunction(token).apply(args == null ? ArrayUtils.EMPTY_STRING_ARRAY : args);
|
||||
}catch(TokenException e){
|
||||
logger.warn("Error processing token '" + token + "': " + e.getMessage());
|
||||
}catch(Throwable t){
|
||||
logger.warn("Error processing token '" + token + "': " + t.getMessage(), t);
|
||||
}
|
||||
|
||||
if (result == null){
|
||||
matcher.appendReplacement(buffer, input.substring(matcher.start(), matcher.end()));
|
||||
}
|
||||
else{
|
||||
matcher.appendReplacement(buffer, result);
|
||||
}
|
||||
}
|
||||
|
||||
matcher.appendTail(buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
// Static class
|
||||
|
||||
private TitleParser(){}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package chylex.customwindowtitle;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
public final class TitleTokens{
|
||||
|
||||
// Registry
|
||||
|
||||
private static final Map<String, Function<String[], String>> tokenMap = new HashMap<>();
|
||||
|
||||
public static void registerToken(String token, Function<String[], String> processor){
|
||||
tokenMap.putIfAbsent(token, processor);
|
||||
}
|
||||
|
||||
public static Function<String[], String> getTokenFunction(String token){
|
||||
return tokenMap.getOrDefault(token, args -> null);
|
||||
}
|
||||
|
||||
// Arguments
|
||||
|
||||
public static Function<String[], String> noArgs(Supplier<String> func){
|
||||
return args -> args.length > 0 ? fail("expected no arguments, got " + args.length) : func.get();
|
||||
}
|
||||
|
||||
public static Function<String[], String> oneArg(UnaryOperator<String> func){
|
||||
return args -> args.length != 1 ? fail("expected 1 argument, got " + args.length) : func.apply(args[0]);
|
||||
}
|
||||
|
||||
public static Function<String[], String> rangeArgs(int min, int max, Function<String[], String> func){
|
||||
return args -> args.length < min || args.length > max ? fail("expected between " + min + " and " + max + " arguments, got " + args.length) : func.apply(args);
|
||||
}
|
||||
|
||||
private static String fail(String message){
|
||||
throw new TokenException(message);
|
||||
}
|
||||
|
||||
// Static class
|
||||
|
||||
private TitleTokens(){}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package chylex.customwindowtitle;
|
||||
|
||||
public class TokenException extends RuntimeException{
|
||||
public TokenException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package chylex.customwindowtitle.forge;
|
||||
import chylex.customwindowtitle.TitleParser;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
import net.minecraftforge.common.ForgeConfigSpec.ConfigValue;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.ModLoadingContext;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig.Type;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
|
||||
@Mod("customwindowtitle")
|
||||
public class CustomWindowTitle{
|
||||
private final ConfigValue<String> configTitle;
|
||||
|
||||
public CustomWindowTitle(){
|
||||
ForgeConfigSpec.Builder configBuilder = new ForgeConfigSpec.Builder();
|
||||
|
||||
configTitle = configBuilder.define("title", "Minecraft {mcversion}");
|
||||
|
||||
ModLoadingContext.get().registerConfig(Type.CLIENT, configBuilder.build());
|
||||
FMLJavaModLoadingContext.get().getModEventBus().register(this);
|
||||
|
||||
TokenData.register();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onClientSetup(FMLClientSetupEvent e){
|
||||
e.getMinecraftSupplier().get().execute(this::updateTitle);
|
||||
}
|
||||
|
||||
private void updateTitle(){
|
||||
Minecraft.getInstance().getMainWindow().func_230148_b_(TitleParser.parse(configTitle.get()));
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package chylex.customwindowtitle.forge;
|
||||
import chylex.customwindowtitle.TokenException;
|
||||
import net.minecraft.util.SharedConstants;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import static chylex.customwindowtitle.TitleTokens.noArgs;
|
||||
import static chylex.customwindowtitle.TitleTokens.oneArg;
|
||||
import static chylex.customwindowtitle.TitleTokens.registerToken;
|
||||
|
||||
final class TokenData{
|
||||
static void register(){
|
||||
registerToken("mcversion", noArgs(TokenData::getMinecraftVersion));
|
||||
registerToken("modversion", oneArg(TokenData::getModVersion));
|
||||
}
|
||||
|
||||
static String getMinecraftVersion(){
|
||||
return SharedConstants.getVersion().getName();
|
||||
}
|
||||
|
||||
static String getModVersion(String modId){
|
||||
ModFileInfo file = ModList.get().getModFileById(modId);
|
||||
|
||||
if (file == null){
|
||||
throw new TokenException("mod file for '" + modId + "' not found");
|
||||
}
|
||||
|
||||
for(IModInfo info : file.getMods()){
|
||||
if (info.getModId().equals(modId)){
|
||||
return info.getVersion().toString();
|
||||
}
|
||||
}
|
||||
|
||||
throw new TokenException("mod info for '" + modId + "' not found");
|
||||
}
|
||||
}
|
3
Forge/src/main/resources/META-INF/coremods.json
Normal file
3
Forge/src/main/resources/META-INF/coremods.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"CustomWindowTitle": "coremods/main.js"
|
||||
}
|
19
Forge/src/main/resources/coremods/main.js
Normal file
19
Forge/src/main/resources/coremods/main.js
Normal file
@ -0,0 +1,19 @@
|
||||
function initializeCoreMod(){
|
||||
var opcodes = Java.type("org.objectweb.asm.Opcodes");
|
||||
var InsnNode = Java.type("org.objectweb.asm.tree.InsnNode");
|
||||
|
||||
return {
|
||||
"CustomWindowTitle": {
|
||||
"target": {
|
||||
"type": "METHOD",
|
||||
"class": "net.minecraft.client.Minecraft",
|
||||
"methodName": "func_230150_b_",
|
||||
"methodDesc": "()V"
|
||||
},
|
||||
"transformer": function(methodNode){
|
||||
methodNode.instructions.insert(new InsnNode(opcodes.RETURN));
|
||||
return methodNode;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user