Clean up .utils

This commit is contained in:
KingRainbow44
2023-05-20 02:25:49 -04:00
Unverified
parent 51c5cb5c62
commit 1a6fa43367
147 changed files with 623 additions and 291 deletions
@@ -1,124 +0,0 @@
package emu.grasscutter.utils;
import com.github.davidmoten.rtreemulti.geometry.Point;
import dev.morphia.annotations.Entity;
import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
@Entity
public final class GridPosition implements Serializable {
private static final long serialVersionUID = -2001232300615923575L;
@Getter @Setter private int x;
@Getter @Setter private int z;
@Getter @Setter private int width;
public GridPosition() {}
public GridPosition(int x, int y, int width) {
set(x, y, width);
}
public GridPosition(GridPosition pos) {
this.set(pos);
}
public GridPosition(Position pos, int width) {
this.set((int) (pos.getX() / width), (int) (pos.getZ() / width), width);
}
public GridPosition(List<Integer> xzwidth) {
this.width = xzwidth.get(2);
this.z = xzwidth.get(1);
this.x = xzwidth.get(0);
}
@SneakyThrows
public GridPosition(String str) {
var listOfParams = str.replace(" ", "").replace("(", "").replace(")", "").split(",");
if (listOfParams.length != 3)
throw new IOException("invalid size on GridPosition definition - ");
try {
this.x = Integer.parseInt(listOfParams[0]);
this.z = Integer.parseInt(listOfParams[1]);
this.width = Integer.parseInt(listOfParams[2]);
} catch (NumberFormatException ignored) {
throw new IOException("invalid number on GridPosition definition - ");
}
}
public GridPosition set(int x, int z) {
this.x = x;
this.z = z;
return this;
}
public GridPosition set(int x, int z, int width) {
this.x = x;
this.z = z;
this.width = width;
return this;
}
// Deep copy
public GridPosition set(GridPosition pos) {
return this.set(pos.getX(), pos.getZ(), pos.getWidth());
}
public GridPosition addClone(int x, int z) {
GridPosition pos = clone();
pos.x += x;
pos.z += z;
return pos;
}
@Override
public GridPosition clone() {
return new GridPosition(x, z, width);
}
@Override
public String toString() {
return "(" + this.getX() + ", " + this.getZ() + ", " + this.getWidth() + ")";
}
public int[] toIntArray() {
return new int[] {x, z, width};
}
public double[] toDoubleArray() {
return new double[] {x, z};
}
public int[] toXZIntArray() {
return new int[] {x, z};
}
public Point toPoint() {
return Point.create(x, z);
}
@Override
public int hashCode() {
int result = x ^ (x >>> 32);
result = 31 * result + (z ^ (z >>> 32));
result = 31 * result + (width ^ (width >>> 32));
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false;
GridPosition pos = (GridPosition) o;
// field comparison
return pos.x == x && pos.z == z && pos.width == width;
}
}
@@ -8,44 +8,50 @@ import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import emu.grasscutter.data.common.DynamicFloat;
import emu.grasscutter.game.world.GridPosition;
import emu.grasscutter.game.world.Position;
import it.unimi.dsi.fastutil.floats.FloatArrayList;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import lombok.val;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;
import lombok.val;
public class JsonAdapters {
static class DynamicFloatAdapter extends TypeAdapter<DynamicFloat> {
public interface JsonAdapters {
class DynamicFloatAdapter extends TypeAdapter<DynamicFloat> {
@Override
public DynamicFloat read(JsonReader reader) throws IOException {
switch (reader.peek()) {
case STRING:
case STRING -> {
return new DynamicFloat(reader.nextString());
case NUMBER:
}
case NUMBER -> {
return new DynamicFloat((float) reader.nextDouble());
case BOOLEAN:
}
case BOOLEAN -> {
return new DynamicFloat(reader.nextBoolean());
case BEGIN_ARRAY:
}
case BEGIN_ARRAY -> {
reader.beginArray();
val opStack = new ArrayList<DynamicFloat.StackOp>();
while (reader.hasNext()) {
opStack.add(
switch (reader.peek()) {
case STRING -> new DynamicFloat.StackOp(reader.nextString());
case NUMBER -> new DynamicFloat.StackOp((float) reader.nextDouble());
case BOOLEAN -> new DynamicFloat.StackOp(reader.nextBoolean());
default -> throw new IOException(
"Invalid DynamicFloat definition - " + reader.peek().name());
});
switch (reader.peek()) {
case STRING -> new DynamicFloat.StackOp(reader.nextString());
case NUMBER -> new DynamicFloat.StackOp((float) reader.nextDouble());
case BOOLEAN -> new DynamicFloat.StackOp(reader.nextBoolean());
default -> throw new IOException(
"Invalid DynamicFloat definition - " + reader.peek().name());
});
}
reader.endArray();
return new DynamicFloat(opStack);
default:
throw new IOException("Invalid DynamicFloat definition - " + reader.peek().name());
}
default -> throw new IOException("Invalid DynamicFloat definition - " + reader.peek().name());
}
}
@@ -53,7 +59,7 @@ public class JsonAdapters {
public void write(JsonWriter writer, DynamicFloat f) {}
}
static class IntListAdapter extends TypeAdapter<IntList> {
class IntListAdapter extends TypeAdapter<IntList> {
@Override
public IntList read(JsonReader reader) throws IOException {
if (Objects.requireNonNull(reader.peek()) == JsonToken.BEGIN_ARRAY) {
@@ -72,12 +78,12 @@ public class JsonAdapters {
public void write(JsonWriter writer, IntList l) throws IOException {
writer.beginArray();
for (val i : l) // .forEach() doesn't appreciate exceptions
writer.value(i);
writer.value(i);
writer.endArray();
}
}
public static class ByteArrayAdapter extends TypeAdapter<byte[]> {
public class ByteArrayAdapter extends TypeAdapter<byte[]> {
@Override
public void write(JsonWriter out, byte[] value) throws IOException {
out.value(Utils.base64Encode(value));
@@ -89,7 +95,7 @@ public class JsonAdapters {
}
}
static class GridPositionAdapter extends TypeAdapter<GridPosition> {
class GridPositionAdapter extends TypeAdapter<GridPosition> {
@Override
public void write(JsonWriter out, GridPosition value) throws IOException {
out.value("(" + value.getX() + ", " + value.getZ() + ", " + value.getWidth() + ")");
@@ -113,17 +119,18 @@ public class JsonAdapters {
}
}
static class PositionAdapter extends TypeAdapter<Position> {
class PositionAdapter extends TypeAdapter<Position> {
@Override
public Position read(JsonReader reader) throws IOException {
switch (reader.peek()) {
case BEGIN_ARRAY: // "pos": [x,y,z]
case BEGIN_ARRAY -> { // "pos": [x,y,z]
reader.beginArray();
val array = new FloatArrayList(3);
while (reader.hasNext()) array.add(reader.nextInt());
reader.endArray();
return new Position(array);
case BEGIN_OBJECT: // "pos": {"x": x, "y": y, "z": z}
}
case BEGIN_OBJECT -> { // "pos": {"x": x, "y": y, "z": z}
float x = 0f;
float y = 0f;
float z = 0f;
@@ -139,8 +146,8 @@ public class JsonAdapters {
}
reader.endObject();
return new Position(x, y, z);
default:
throw new IOException("Invalid Position definition - " + reader.peek().name());
}
default -> throw new IOException("Invalid Position definition - " + reader.peek().name());
}
}
@@ -154,7 +161,7 @@ public class JsonAdapters {
}
}
static class EnumTypeAdapterFactory implements TypeAdapterFactory {
class EnumTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<T> enumClass = (Class<T>) type.getRawType();
@@ -186,16 +193,13 @@ public class JsonAdapters {
}
}
return new TypeAdapter<T>() {
return new TypeAdapter<>() {
public T read(JsonReader reader) throws IOException {
switch (reader.peek()) {
case STRING:
return map.get(reader.nextString());
case NUMBER:
return map.get(String.valueOf(reader.nextInt()));
default:
throw new IOException("Invalid Enum definition - " + reader.peek().name());
}
return switch (reader.peek()) {
case STRING -> map.get(reader.nextString());
case NUMBER -> map.get(String.valueOf(reader.nextInt()));
default -> throw new IOException("Invalid Enum definition - " + reader.peek().name());
};
}
public void write(JsonWriter writer, T value) throws IOException {
@@ -3,6 +3,8 @@ package emu.grasscutter.utils;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import emu.grasscutter.data.common.DynamicFloat;
import emu.grasscutter.game.world.GridPosition;
import emu.grasscutter.game.world.Position;
import emu.grasscutter.utils.JsonAdapters.*;
import it.unimi.dsi.fastutil.ints.IntList;
import java.io.FileInputStream;
@@ -1,40 +0,0 @@
package emu.grasscutter.utils;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.game.world.Scene;
import lombok.Getter;
import lombok.Setter;
@Entity
public class Location extends Position {
@Transient @Getter @Setter private Scene scene;
public Location(Scene scene, Position position) {
this.set(position);
this.scene = scene;
}
public Location(Scene scene, float x, float y) {
this.set(x, y);
this.scene = scene;
}
public Location(Scene scene, float x, float y, float z) {
this.set(x, y, z);
this.scene = scene;
}
@Override
public Location clone() {
return new Location(this.scene, super.clone());
}
@Override
public String toString() {
return String.format("%s:%s,%s,%s", this.scene.getId(), this.getX(), this.getY(), this.getZ());
}
}
@@ -1,21 +0,0 @@
package emu.grasscutter.utils;
public class MessageHandler {
private String message;
public MessageHandler() {
this.message = "";
}
public void append(String message) {
this.message += message + "\r\n\r\n";
}
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
}
@@ -1,200 +0,0 @@
package emu.grasscutter.utils;
import com.github.davidmoten.rtreemulti.geometry.Point;
import com.google.gson.annotations.SerializedName;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
@Entity
public class Position implements Serializable {
private static final long serialVersionUID = -2001232313615923575L;
public static final Position ZERO = new Position(0, 0, 0);
public static final Position IDENTITY = new Position(0, 0);
@SerializedName(
value = "x",
alternate = {"_x", "X"})
@Getter
@Setter
private float x;
@SerializedName(
value = "y",
alternate = {"_y", "Y"})
@Getter
@Setter
private float y;
@SerializedName(
value = "z",
alternate = {"_z", "Z"})
@Getter
@Setter
private float z;
public Position() {}
public Position(float x, float y) {
set(x, y);
}
public Position(float x, float y, float z) {
set(x, y, z);
}
public Position(List<Float> xyz) {
switch (xyz.size()) {
default: // Might want to error on excess elements, but maybe we want to extend to 3+3
// representation later.
case 3:
this.z = xyz.get(2); // Fall-through
case 2:
this.y = xyz.get(1); // Fall-through
case 1:
this.y = xyz.get(0); // pointless fall-through
case 0:
break;
}
}
public Position(String p) {
String[] split = p.split(",");
if (split.length >= 2) {
this.x = Float.parseFloat(split[0]);
this.y = Float.parseFloat(split[1]);
}
if (split.length >= 3) {
this.z = Float.parseFloat(split[2]);
}
}
public Position(Vector vector) {
this.set(vector);
}
public Position(Position pos) {
this.set(pos);
}
public Position set(float x, float y) {
this.x = x;
this.y = y;
return this;
}
// Deep copy
public Position set(Position pos) {
return this.set(pos.getX(), pos.getY(), pos.getZ());
}
public Position set(Vector pos) {
return this.set(pos.getX(), pos.getY(), pos.getZ());
}
public Position set(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
return this;
}
public Position add(Position add) {
this.x += add.getX();
this.y += add.getY();
this.z += add.getZ();
return this;
}
public Position addX(float d) {
this.x += d;
return this;
}
public Position addY(float d) {
this.y += d;
return this;
}
public Position addZ(float d) {
this.z += d;
return this;
}
public Position subtract(Position sub) {
this.x -= sub.getX();
this.y -= sub.getY();
this.z -= sub.getZ();
return this;
}
/** In radians */
public Position translate(float dist, float angle) {
this.x += dist * Math.sin(angle);
this.y += dist * Math.cos(angle);
return this;
}
public boolean equal2d(Position other) {
// Y is height
return getX() == other.getX() && getZ() == other.getZ();
}
public boolean equal3d(Position other) {
return getX() == other.getX() && getY() == other.getY() && getZ() == other.getZ();
}
public double computeDistance(Position b) {
double detX = getX() - b.getX();
double detY = getY() - b.getY();
double detZ = getZ() - b.getZ();
return Math.sqrt(detX * detX + detY * detY + detZ * detZ);
}
public Position nearby2d(float range) {
Position position = clone();
position.z += Utils.randomFloatRange(-range, range);
position.x += Utils.randomFloatRange(-range, range);
return position;
}
public Position translateWithDegrees(float dist, float angle) {
angle = (float) Math.toRadians(angle);
this.x += dist * Math.sin(angle);
this.y += -dist * Math.cos(angle);
return this;
}
@Override
public Position clone() {
return new Position(x, y, z);
}
@Override
public String toString() {
return "(" + this.getX() + ", " + this.getY() + ", " + this.getZ() + ")";
}
public Vector toProto() {
return Vector.newBuilder().setX(this.getX()).setY(this.getY()).setZ(this.getZ()).build();
}
public Point toPoint() {
return Point.create(x, y, z);
}
/** To XYZ array for Spatial Index */
public double[] toDoubleArray() {
return new double[] {x, y, z};
}
/** To XZ array for Spatial Index (Blocks) */
public double[] toXZDoubleArray() {
return new double[] {x, z};
}
}
@@ -1,22 +1,23 @@
package emu.grasscutter.utils;
import static emu.grasscutter.config.Configuration.*;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import emu.grasscutter.BuildConfig;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.net.packet.PacketOpcodesUtils;
import emu.grasscutter.tools.Dumpers;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.slf4j.LoggerFactory;
import static emu.grasscutter.config.Configuration.*;
/** A parser for start-up arguments. */
public final class StartupArguments {
public interface StartupArguments {
/* A map of parameter -> argument handler. */
private static final Map<String, Function<String, Boolean>> argumentHandlers =
Map<String, Function<String, Boolean>> argumentHandlers =
new HashMap<>() {
{
putAll(
@@ -74,17 +75,13 @@ public final class StartupArguments {
}
};
private StartupArguments() {
// This class is not meant to be instantiated.
}
/**
* Parses the provided start-up arguments.
*
* @param args The application start-up arguments.
* @return If the application should exit.
*/
public static boolean parse(String[] args) {
static boolean parse(String[] args) {
boolean exitEarly = false;
// Parse the arguments.
@@ -1,7 +1,5 @@
package emu.grasscutter.utils;
import static emu.grasscutter.utils.Utils.nonRegexSplit;
import com.google.gson.*;
import com.google.gson.annotations.SerializedName;
import emu.grasscutter.Grasscutter;
@@ -9,6 +7,8 @@ import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import lombok.val;
import java.io.IOException;
import java.lang.reflect.*;
import java.nio.charset.StandardCharsets;
@@ -19,11 +19,12 @@ import java.util.*;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import lombok.val;
import static emu.grasscutter.utils.Utils.nonRegexSplit;
// Throughout this file, commented System.out.println debug log calls are left in.
// This is because the default logger will deadlock when operating on parallel streams.
public class TsvUtils {
public final class TsvUtils {
private static final Map<Type, Object> defaultValues =
Map.ofEntries(
// Map.entry(String.class, null), // builder hates null values
@@ -679,4 +680,8 @@ public class TsvUtils {
return map;
}
}
private TsvUtils() {
// No instantiation.
}
}
@@ -1,11 +1,12 @@
package emu.grasscutter.utils;
import static emu.grasscutter.utils.FileUtils.getResourcePath;
import static emu.grasscutter.utils.Language.translate;
import static emu.grasscutter.utils.lang.Language.translate;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.config.ConfigContainer;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.game.world.Position;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
@@ -1,16 +1,16 @@
package emu.grasscutter.utils;
package emu.grasscutter.utils.helpers;
public class ByteHelper {
public static byte[] changeBytes(byte[] a) {
byte[] b = new byte[a.length];
for (int i = 0; i < a.length; i++) {
public interface ByteHelper {
static byte[] changeBytes(byte[] a) {
var b = new byte[a.length];
for (var i = 0; i < a.length; i++) {
b[i] = a[a.length - i - 1];
}
return b;
}
public static byte[] longToBytes(long x) {
byte[] bytes = new byte[8];
static byte[] longToBytes(long x) {
var bytes = new byte[8];
bytes[0] = (byte) (x >> 56);
bytes[1] = (byte) (x >> 48);
bytes[2] = (byte) (x >> 40);
@@ -1,11 +1,11 @@
package emu.grasscutter.utils;
package emu.grasscutter.utils.helpers;
import java.util.Calendar;
import java.util.Date;
public final class DateHelper {
public static Date onlyYearMonthDay(Date now) {
Calendar calendar = Calendar.getInstance();
public interface DateHelper {
static Date onlyYearMonthDay(Date now) {
var calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
@@ -14,7 +14,7 @@ public final class DateHelper {
return calendar.getTime();
}
public static int getUnixTime(Date localDateTime) {
static int getUnixTime(Date localDateTime) {
return (int) (localDateTime.getTime() / 1000L);
}
}
@@ -1,10 +1,10 @@
package emu.grasscutter.utils;
package emu.grasscutter.utils.helpers;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.PropValueOuterClass.PropValue;
public final class ProtoHelper {
public static PropValue newPropValue(PlayerProperty key, int value) {
public interface ProtoHelper {
static PropValue newPropValue(PlayerProperty key, int value) {
return PropValue.newBuilder().setType(key.getId()).setIval(value).setVal(value).build();
}
}
@@ -1,8 +1,4 @@
package emu.grasscutter.utils;
import static emu.grasscutter.config.Configuration.FALLBACK_LANGUAGE;
import static emu.grasscutter.utils.FileUtils.getCachePath;
import static emu.grasscutter.utils.FileUtils.getResourcePath;
package emu.grasscutter.utils.lang;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -11,12 +7,16 @@ import emu.grasscutter.data.GameData;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.data.excels.achievement.AchievementData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.JsonUtils;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.EqualsAndHashCode;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -28,7 +28,10 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import lombok.EqualsAndHashCode;
import static emu.grasscutter.config.Configuration.FALLBACK_LANGUAGE;
import static emu.grasscutter.utils.FileUtils.getCachePath;
import static emu.grasscutter.utils.FileUtils.getResourcePath;
public final class Language {
private static final Map<String, Language> cachedLanguages = new ConcurrentHashMap<>();
@@ -1,4 +1,4 @@
package emu.grasscutter.utils;
package emu.grasscutter.utils.objects;
import emu.grasscutter.server.event.Event;
@@ -1,16 +1,15 @@
package emu.grasscutter.utils;
package emu.grasscutter.utils.objects;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import emu.grasscutter.Grasscutter;
import java.util.Arrays;
public class JlineLogbackAppender extends ConsoleAppender<ILoggingEvent> {
public final class JLineLogbackAppender extends ConsoleAppender<ILoggingEvent> {
@Override
protected void append(ILoggingEvent eventObject) {
if (!started) {
return;
}
if (!this.started) return;
Arrays.stream(new String(encoder.encode(eventObject)).split("\n\r"))
.forEach(Grasscutter.getConsole()::printAbove);
}
@@ -1,4 +1,4 @@
package emu.grasscutter.utils;
package emu.grasscutter.utils.objects;
import java.util.ArrayList;
import java.util.HashMap;
@@ -1,4 +1,4 @@
package emu.grasscutter.utils;
package emu.grasscutter.utils.objects;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
@@ -6,7 +6,7 @@ import ch.qos.logback.core.encoder.Encoder;
import emu.grasscutter.server.event.internal.ServerLogEvent;
import java.nio.charset.StandardCharsets;
public class ServerLogEventAppender<E> extends AppenderBase<E> {
public final class ServerLogEventAppender<E> extends AppenderBase<E> {
protected Encoder<E> encoder;
@Override
@@ -1,4 +1,4 @@
package emu.grasscutter.utils;
package emu.grasscutter.utils.objects;
import java.util.ArrayList;
import java.util.List;
@@ -1,4 +1,4 @@
package emu.grasscutter.utils;
package emu.grasscutter.utils.objects;
import java.util.NavigableMap;
import java.util.TreeMap;
@@ -0,0 +1,235 @@
package emu.grasscutter.utils.objects.text;
import lombok.Builder;
import lombok.Data;
import java.awt.*;
import java.util.HashMap;
import java.util.Map;
/* Text style container. */
@Builder
@Data
public final class Style {
private static final Map<Character, String> unity = new HashMap<>();
private static final Map<Character, String> ansi = new HashMap<>();
static {
// Add the Minecraft color codes to the color map.
unity.put('0', "#000000"); // Black
unity.put('1', "#0000AA"); // Dark Blue
unity.put('2', "#00AA00"); // Dark Green
unity.put('3', "#00AAAA"); // Dark Aqua
unity.put('4', "#AA0000"); // Dark Red
unity.put('5', "#AA00AA"); // Dark Purple
unity.put('6', "#FFAA00"); // Gold
unity.put('7', "#AAAAAA"); // Gray
unity.put('8', "#555555"); // Dark Gray
unity.put('9', "#5555FF"); // Blue
unity.put('a', "#55FF55"); // Green
unity.put('b', "#55FFFF"); // Aqua
unity.put('c', "#FF5555"); // Red
unity.put('d', "#FF55FF"); // Light Purple
unity.put('e', "#FFFF55"); // Yellow
unity.put('f', "#FFFFFF"); // White
ansi.put('0', "\u001B[30m"); // Black
ansi.put('1', "\u001B[34m"); // Dark Blue
ansi.put('2', "\u001B[32m"); // Dark Green
ansi.put('3', "\u001B[36m"); // Dark Aqua
ansi.put('4', "\u001B[31m"); // Dark Red
ansi.put('5', "\u001B[35m"); // Dark Purple
ansi.put('6', "\u001B[33m"); // Gold
ansi.put('7', "\u001B[37m"); // Gray
ansi.put('8', "\u001B[90m"); // Dark Gray
ansi.put('9', "\u001B[94m"); // Blue
ansi.put('a', "\u001B[92m"); // Green
ansi.put('b', "\u001B[96m"); // Aqua
ansi.put('c', "\u001B[91m"); // Red
ansi.put('d', "\u001B[95m"); // Light Purple
ansi.put('e', "\u001B[93m"); // Yellow
ansi.put('f', "\u001B[97m"); // White
}
@Builder.Default private int size = -1; // Unity only.
@Builder.Default private boolean bold = false; // Unity only.
@Builder.Default private boolean italic = false; // Unity only.
@Builder.Default private Color color = null;
/**
* Replaces detected sequences of &color with the specified text.
*
* @param input The input text.
* @return The replaced text.
*/
private String replaceUnity(String input) {
// Thanks ChatGPT! (from ChatGPT)
// Check if the input string is null or empty
if (input == null || input.isEmpty()) {
return "";
}
var output = new StringBuilder();
var i = 0;
while (i < input.length()) {
var c = input.charAt(i);
if (c == '&') {
// Check if the Minecraft color code is valid
if (i + 1 < input.length() && unity.containsKey(input.charAt(i + 1))) {
// Append the Unity color code
output.append("<color=").append(unity.get(input.charAt(i + 1))).append(">");
// Move the index past the Minecraft color code
i += 2;
// Find the end of the color code span
var end = input.indexOf('&', i);
if (end == -1) {
end = input.length();
}
// Append the text within the color code span
output.append(input, i, end);
// Append the closing tag for the Unity color code
output.append("</color>");
// Move the index to the end of the color code span
i = end;
} else {
// Invalid Minecraft color code, treat it as regular text
output.append(c);
i++;
}
} else {
// Append regular text
output.append(c);
i++;
}
}
return output.toString();
}
/**
* Replaces detected sequences of &color with the specified text.
*
* @param input The input text.
* @return The replaced text.
*/
private String replaceTerminal(String input) {
// Check if the input string is null or empty
if (input == null || input.isEmpty()) {
return "";
}
var output = new StringBuilder();
var i = 0;
while (i < input.length()) {
var c = input.charAt(i);
if (c == '&') {
// Check if the Minecraft color code is valid
if (i + 1 < input.length() && ansi.containsKey(input.charAt(i + 1))) {
// Append the ANSI escape code
output.append(ansi.get(input.charAt(i + 1)));
// Move the index past the Minecraft color code
i += 2;
// Find the end of the color code span
var end = input.indexOf('&', i);
if (end == -1) {
end = input.length();
}
// Append the text within the color code span
output.append(input, i, end);
// Reset the color back to default
output.append("\u001B[0m");
// Move the index to the end of the color code span
i = end;
} else {
// Invalid Minecraft color code, treat it as regular text
output.append(c);
i++;
}
} else {
// Append regular text
output.append(c);
i++;
}
}
return output.toString();
}
/**
* Wraps the text in the style. Formatted for Unity clients.
*
* @param text The text to wrap.
* @return The wrapped text.
*/
public String toUnity(String text) {
var builder = new StringBuilder();
// Set the size.
if (this.size != -1) {
builder.append("<size=").append(this.size).append(">");
}
// Set the color.
if (this.color != null) {
builder
.append("<color=")
.append(
String.format(
"#%02x%02x%02x",
this.color.getRed(), this.color.getGreen(), this.color.getBlue()))
.append(">");
}
// Set the boldness.
if (this.bold) builder.append("<b>");
// Set the italicness.
if (this.italic) builder.append("<i>");
// Append the text.
builder.append(this.replaceUnity(text));
// Close the tags.
if (this.italic) builder.append("</i>");
if (this.bold) builder.append("</b>");
if (this.color != null) builder.append("</color>");
if (this.size != -1) builder.append("</size>");
return builder.toString();
}
/**
* Wraps the text in the style. Formatted for terminal clients.
*
* @param text The text to wrap.
* @return The wrapped text.
*/
public String toTerminal(String text) {
// Check for color.
if (this.color == null) return this.replaceTerminal(text);
// Convert the color to an ANSI color.
var ansiColor =
this.color.getRed() > 127
? this.color.getGreen() > 127
? this.color.getBlue() > 127 ? 15 : 11
: this.color.getBlue() > 127 ? 13 : 9
: this.color.getGreen() > 127
? this.color.getBlue() > 127 ? 14 : 10
: this.color.getBlue() > 127 ? 12 : 8;
// Return the text with the ANSI color.
// Reset the color at the end.
return "\u001B[38;5;" + ansiColor + "m" + this.replaceTerminal(text) + "\u001B[0m";
}
}
@@ -0,0 +1,103 @@
package emu.grasscutter.utils.objects.text;
import lombok.Getter;
import lombok.Setter;
import java.awt.*;
/* An instance of text. */
public final class Text {
/**
* Creates a new rich instance of text.
*
* @param text The text to use.
* @return The new instance of text.
*/
public static Text of(String text) {
return new Text(text, false);
}
@Getter private final boolean raw;
private final Style.StyleBuilder style = Style.builder();
@Setter private String text;
/**
* Creates a new rich instance of text.
*
* @param text The text to use.
*/
public Text(String text) {
this.raw = false;
this.text = text;
}
/**
* Creates a new instance of text.
*
* @param text The text to use.
* @param raw Whether the text is raw.
*/
public Text(String text, boolean raw) {
this.raw = raw;
this.text = text;
}
/**
* Sets the size of the text.
*
* @param size The size of the text.
* @return This object.
*/
public Text size(int size) {
this.style.size(size);
return this;
}
/**
* Sets the color of the text.
*
* @param color The color of the text.
* @return This object.
*/
public Text color(Color color) {
this.style.color(color);
return this;
}
/**
* Sets the boldness of the text.
*
* @param bold Whether the text is bold.
* @return This object.
*/
public Text bold(boolean bold) {
this.style.bold(bold);
return this;
}
/**
* Sets the italicness of the text.
*
* @param italic Whether the text is italic.
* @return This object.
*/
public Text italic(boolean italic) {
this.style.italic(italic);
return this;
}
/**
* Converts this object to a string. Converts to a console or Unity format.
*
* @param console Whether to convert the text for the console.
* @return The converted string.
*/
public String toString(boolean console) {
// Pull instances of style and text.
var style = this.style.build();
var text = this.text;
return console ? style.toTerminal(text) : style.toUnity(text);
}
}