Skip to content

Commit

Permalink
Move lore handler from intergalactic to gtnhlib
Browse files Browse the repository at this point in the history
  • Loading branch information
serenibyss committed Nov 19, 2024
1 parent e6e2f86 commit 0bbb6ee
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 0 deletions.
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();
}

0 comments on commit 0bbb6ee

Please sign in to comment.