Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move lore handler from intergalactic to gtnhlib #92

Merged
merged 1 commit into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/main/java/com/gtnewhorizon/gtnhlib/ClientProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import net.minecraftforge.client.ClientCommandHandler;

import com.gtnewhorizon.gtnhlib.client.model.ModelLoader;
import com.gtnewhorizon.gtnhlib.client.tooltip.LoreHandler;
import com.gtnewhorizon.gtnhlib.commands.ItemInHandCommand;
import com.gtnewhorizon.gtnhlib.compat.FalseTweaks;
import com.gtnewhorizon.gtnhlib.compat.Mods;
Expand Down Expand Up @@ -54,6 +55,8 @@ public void postInit(FMLPostInitializationEvent event) {
Minecraft.getMinecraft().refreshResources();
ModelLoader.loadModels();
}

LoreHandler.postInit();
}

@Override
Expand Down
189 changes: 189 additions & 0 deletions src/main/java/com/gtnewhorizon/gtnhlib/client/tooltip/LoreHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package com.gtnewhorizon.gtnhlib.client.tooltip;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.IResourceManagerReloadListener;
import net.minecraft.client.resources.SimpleReloadableResourceManager;
import net.minecraft.util.StatCollector;
import net.minecraft.util.WeightedRandom;

import org.apache.commons.lang3.StringUtils;

import com.gtnewhorizon.gtnhlib.GTNHLib;

/**
* Helper class for providing random, localized Strings to fields annotated with {@link LoreHolder}.
*
* @since 0.5.21
* @author glowredman
*/
public final class LoreHandler implements IResourceManagerReloadListener {

/**
* key: field to be updated; value: translation key to use
*/
private static final Map<Field, String> LORE_HOLDERS = new HashMap<>();
private static final Random RANDOM = new Random();

public static void postInit() {
((SimpleReloadableResourceManager) Minecraft.getMinecraft().getResourceManager())
.registerReloadListener(new LoreHandler());
}

private LoreHandler() {}

@Override
public void onResourceManagerReload(IResourceManager p_110549_1_) {
updateLoreHolders();
}

/**
* Register a class containing one or more static fields of type {@link String} annotated with {@link LoreHolder}.
* When the resources are reloaded, the field(s) are updated with a random translation. The possible lines are
* defined via lang files, using the translation key defined by {@link LoreHolder#value()}, appended by an index
* (starting with 0). Blank translations are ignored. The translations may be weighted by using {@code <weight>:} as
* prefix, {@code <weight>} being a non-negative integer. If no weight is specified, a default value of 1 is used.
* To prevent ':' being used as delimiter, escape it using '\'.
*
* @param clazz The class containing the field(s) to be updated when the resources are reloaded
* @since 0.5.21
*/
public static void registerLoreHolder(Class<?> clazz) {
try {
for (Field field : clazz.getDeclaredFields()) {
if (!field.getType().isAssignableFrom(String.class) || !Modifier.isStatic(field.getModifiers()))
continue;

LoreHolder loreHolder = field.getDeclaredAnnotation(LoreHolder.class);
if (loreHolder == null) continue;

field.setAccessible(true);
LORE_HOLDERS.put(field, loreHolder.value());
}
} catch (Exception e) {
GTNHLib.LOG
.error("An exception occured while looking for @LoreHolder annotations in " + clazz.toString(), e);
}
}

private static void updateLoreHolders() {
LORE_HOLDERS.forEach((field, keyPrefix) -> {
try {
field.set(null, getRandomLine(keyPrefix));
} catch (Exception e) {
GTNHLib.LOG.warn(
"Unable to update LoreHolder in " + field.getDeclaringClass()
+ " (Field: "
+ field.getName()
+ ")",
e);
}
});
}

private static String getRandomLine(String keyPrefix) {
List<WeightedRandom.Item> lines = getAllLines(keyPrefix);

if (lines.size() == 0) {
return null;
}

try {
return ((WeightedText) WeightedRandom.getRandomItem(RANDOM, lines)).text;
} catch (IllegalArgumentException e) {
GTNHLib.LOG.warn("The total weight of all lines for \"" + keyPrefix + "\" exceeds " + Integer.MAX_VALUE, e);
} catch (Exception e) {
GTNHLib.LOG
.error("An unexpected Exception occurred while choosing a random lore for \"" + keyPrefix + '"', e);
}

return null;
}

private static List<WeightedRandom.Item> getAllLines(String keyPrefix) {
List<WeightedRandom.Item> allLines = new ArrayList<>();

for (int i = 0; true; i++) {
String unlocalizedLine = keyPrefix + i;
String localizedLine = StatCollector.translateToLocal(unlocalizedLine);
if (unlocalizedLine.equals(localizedLine)) {
break;
} else {
if (!StringUtils.isBlank(localizedLine)) {
allLines.add(new WeightedText(localizedLine));
}
}
}

return allLines;
}

private static class WeightedText extends WeightedRandom.Item {

private String text;

private WeightedText(String weightedText) {
super(0);
this.extractWeightAndText(weightedText);
}

private void extractWeightAndText(String weightedText) {
int endOfWeight = weightedText.indexOf(':');

// no ':' was found or the ':' was escaped using '\'
// -> lore line has no weight specified
if (endOfWeight < 1) {
this.itemWeight = 1;
this.text = weightedText;
return;
}

if (weightedText.charAt(endOfWeight - 1) == '\\') {
this.itemWeight = 1;
this.text = weightedText.substring(0, endOfWeight - 1) + weightedText.substring(endOfWeight);
return;
}

// if a ':' was found, attempt to parse everything before it as int
String weightString = weightedText.substring(0, endOfWeight);
try {
int weight = Integer.parseInt(weightString);

if (weight < 0) {
GTNHLib.LOG.warn(
"\"{}\" has a negative weight ({}). This is not allowed, a weight of 1 will be used instead.",
weightedText,
weight);
this.itemWeight = 1;
} else {
this.itemWeight = weight;
}

this.text = weightedText.substring(endOfWeight + 1);
return;
} catch (NumberFormatException e) {
GTNHLib.LOG.warn(
"Could not parse \"" + weightString
+ "\" as Integer. If it is not supposed to be a weight, escape the ':' delimiter using '\\'.",
e);
} catch (Exception e) {
GTNHLib.LOG.error(
"An unexpected Exception occurred while extracting weight and text from lore \"" + weightedText
+ '"',
e);
}

// fallback
this.itemWeight = 1;
this.text = weightedText;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.gtnewhorizon.gtnhlib.client.tooltip;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation used to identify fields which should be updated on a resource refresh. Annotated fields must be static and
* of type {@link String}. To use this, register the declaring class via {@link LoreHandler#registerLoreHolder(Class)}.
*
* @since 0.5.21
* @author glowredman
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface LoreHolder {

/**
* The localization key
*/
String value();
}