Compare commits
9 Commits
Author | SHA1 | Date |
---|---|---|
Johannes Frohnmeyer | 4e47338803 | |
Johannes Frohnmeyer | 7a1dec5e1a | |
Johannes Frohnmeyer | bfd812ddbc | |
Johannes Frohnmeyer | 0c2bffe0be | |
Johannes Frohnmeyer | 8c5c2e168e | |
Johannes Frohnmeyer | 857774fa2c | |
Johannes Frohnmeyer | 0b2b537996 | |
Johannes Frohnmeyer | d8cd4ccf08 | |
Johannes Frohnmeyer | d07a8dabd4 |
|
@ -1,15 +1,42 @@
|
|||
import io.gitlab.jfronny.scripts.*
|
||||
|
||||
plugins {
|
||||
id("jfmod") version "1.3-SNAPSHOT"
|
||||
id("io.github.juuxel.loom-quiltflower") version "1.8.0"
|
||||
id("jfmod") version "1.6-SNAPSHOT"
|
||||
}
|
||||
|
||||
allprojects { group = "io.gitlab.jfronny" }
|
||||
base.archivesName = "chat-transform"
|
||||
|
||||
jfMod {
|
||||
minecraftVersion = "1.20.5"
|
||||
yarn("build.1")
|
||||
loaderVersion = "0.15.10"
|
||||
libJfVersion = "3.15.6"
|
||||
fabricApiVersion = "0.97.6+1.20.5"
|
||||
|
||||
modrinth {
|
||||
projectId = "chat-transform"
|
||||
requiredDependencies.add("libjf")
|
||||
optionalDependencies.add("modmenu")
|
||||
}
|
||||
|
||||
curseforge {
|
||||
projectId = "839175"
|
||||
requiredDependencies.add("libjf")
|
||||
optionalDependencies.add("modmenu")
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
modImplementation("io.gitlab.jfronny.libjf:libjf-config-core-v1:${prop("libjf_version")}")
|
||||
modImplementation("io.gitlab.jfronny.libjf:libjf-config-core-v2")
|
||||
include(modImplementation("net.fabricmc.fabric-api:fabric-message-api-v1")!!)
|
||||
include(modImplementation("net.fabricmc.fabric-api:fabric-command-api-v2")!!)
|
||||
modImplementation("net.fabricmc.fabric-api:fabric-networking-api-v1")
|
||||
|
||||
// Dev env
|
||||
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-config-ui-tiny-v1:${prop("libjf_version")}")
|
||||
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-devutil:${prop("libjf_version")}")
|
||||
modLocalRuntime("com.terraformersmc:modmenu:6.1.0-rc.4")
|
||||
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-config-ui-tiny")
|
||||
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-devutil")
|
||||
modLocalRuntime("com.terraformersmc:modmenu:10.0.0-beta.1")
|
||||
// for modmenu
|
||||
modLocalRuntime("net.fabricmc.fabric-api:fabric-resource-loader-v0")
|
||||
modLocalRuntime("net.fabricmc.fabric-api:fabric-screen-api-v1")
|
||||
modLocalRuntime("net.fabricmc.fabric-api:fabric-key-binding-api-v1")
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
# https://fabricmc.net/develop/
|
||||
minecraft_version=1.19.4
|
||||
yarn_mappings=build.1
|
||||
loader_version=0.14.17
|
||||
|
||||
maven_group=io.gitlab.jfronny
|
||||
archives_base_name=chat-transform
|
||||
|
||||
modrinth_id=chat-transform
|
||||
modrinth_required_dependencies=libjf
|
||||
modrinth_optional_dependencies=modmenu
|
||||
curseforge_id=839175
|
||||
curseforge_required_dependencies=libjf
|
||||
curseforge_optional_dependencies=modmenu
|
||||
|
||||
libjf_version=3.7.0
|
||||
fabric_version=0.75.3+1.19.4
|
|
@ -1,13 +0,0 @@
|
|||
package io.gitlab.jfronny.chattransform;
|
||||
|
||||
import io.gitlab.jfronny.commons.log.Logger;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
|
||||
public class ChatTransform implements ClientModInitializer {
|
||||
public static final Logger LOG = Logger.forName("chat-transform");
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
LOG.info("Loaded chat-transform");
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.chattransform;
|
||||
package io.gitlab.jfronny.chattransform.client;
|
||||
|
||||
public interface ITextFieldWidget {
|
||||
void chattransform$activate();
|
|
@ -0,0 +1,11 @@
|
|||
package io.gitlab.jfronny.chattransform.client;
|
||||
|
||||
public record Substitution(int start, int end, long time) {
|
||||
public Substitution(int start, int end) {
|
||||
this(start, end, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public boolean shouldShow() {
|
||||
return time >= System.currentTimeMillis() - 500;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package io.gitlab.jfronny.chattransform.client;
|
||||
|
||||
public class TransformStart {
|
||||
private boolean available = false;
|
||||
private int value = -1;
|
||||
private boolean hasPrevious = false;
|
||||
private int previous = -1;
|
||||
private long previousTime;
|
||||
|
||||
public void clear() {
|
||||
this.available = false;
|
||||
this.hasPrevious = false;
|
||||
}
|
||||
|
||||
public void set(int value) {
|
||||
if (available) {
|
||||
previous = this.value;
|
||||
previousTime = System.currentTimeMillis();
|
||||
hasPrevious = true;
|
||||
} else available = true;
|
||||
if (value < 0) value = 0;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int get() {
|
||||
if (!available) throw new IllegalStateException("TransformStart is not set");
|
||||
return value;
|
||||
}
|
||||
|
||||
public void increment() {
|
||||
set(get() + 1);
|
||||
}
|
||||
|
||||
public boolean isAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
public boolean hasPrevious() {
|
||||
return hasPrevious;
|
||||
}
|
||||
|
||||
public int getPrevious() {
|
||||
if (!hasPrevious) throw new IllegalStateException("TransformStart has no previous");
|
||||
return previous;
|
||||
}
|
||||
|
||||
public boolean showPrevious() {
|
||||
return hasPrevious() && previousTime >= System.currentTimeMillis() - 500;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package io.gitlab.jfronny.chattransform.client.mixin;
|
||||
|
||||
import io.gitlab.jfronny.chattransform.Cfg;
|
||||
import io.gitlab.jfronny.chattransform.ChatTransform;
|
||||
import io.gitlab.jfronny.chattransform.client.ITextFieldWidget;
|
||||
import net.minecraft.client.gui.screen.ChatScreen;
|
||||
import net.minecraft.client.gui.widget.TextFieldWidget;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(ChatScreen.class)
|
||||
public abstract class ChatScreenMixin {
|
||||
@Shadow protected TextFieldWidget chatField;
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "init()V")
|
||||
void init(CallbackInfo ci) {
|
||||
if (Cfg.Client.mode == Cfg.Mode.Live) ((ITextFieldWidget) chatField).chattransform$activate();
|
||||
}
|
||||
|
||||
@Redirect(method = "keyPressed(III)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/TextFieldWidget;getText()Ljava/lang/String;"))
|
||||
String finalizeTransforms(TextFieldWidget instance) {
|
||||
String text = ((ITextFieldWidget) chatField).chattransform$finalize();
|
||||
if (Cfg.Client.mode == Cfg.Mode.OnSend) text = ChatTransform.transform(text);
|
||||
return text;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
package io.gitlab.jfronny.chattransform.client.mixin;
|
||||
|
||||
import io.gitlab.jfronny.chattransform.Cfg;
|
||||
import io.gitlab.jfronny.chattransform.ChatTransform;
|
||||
import io.gitlab.jfronny.chattransform.client.*;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ClickableWidget;
|
||||
import net.minecraft.client.gui.widget.TextFieldWidget;
|
||||
import net.minecraft.text.Text;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Mixin(TextFieldWidget.class)
|
||||
public abstract class TextFieldWidgetMixin extends ClickableWidget implements ITextFieldWidget {
|
||||
public TextFieldWidgetMixin(int x, int y, int width, int height, Text message) {
|
||||
super(x, y, width, height, message);
|
||||
}
|
||||
|
||||
@Shadow public abstract String getText();
|
||||
@Shadow private String text;
|
||||
@Shadow private int selectionStart;
|
||||
@Shadow private int selectionEnd;
|
||||
@Shadow private boolean drawsBackground;
|
||||
@Shadow public abstract int getCharacterX(int index);
|
||||
@Shadow public abstract boolean isVisible();
|
||||
@Shadow public abstract void setCursor(int cursor, boolean shiftKeyPressed);
|
||||
|
||||
@Shadow private Predicate<String> textPredicate;
|
||||
@Shadow @Final private TextRenderer textRenderer;
|
||||
@Unique private boolean chattransform$active = false;
|
||||
@Override
|
||||
public void chattransform$activate() {
|
||||
chattransform$active = true;
|
||||
ChatTransform.LOG.info("Activated widget " + this);
|
||||
}
|
||||
|
||||
@Unique private boolean chattransform$shouldTransform = false;
|
||||
@Redirect(method = "write(Ljava/lang/String;)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/widget/TextFieldWidget;text:Ljava/lang/String;", opcode = Opcodes.PUTFIELD))
|
||||
void transformBeforeWrite(TextFieldWidget instance, String value, String tx) {
|
||||
if (chattransform$active) {
|
||||
if (selectionStart == selectionEnd && tx.length() == 1 && !Screen.hasAltDown() && !getText().startsWith("/")) {
|
||||
chattransform$shouldTransform = true;
|
||||
} else {
|
||||
transformStart.clear();
|
||||
}
|
||||
}
|
||||
this.text = value;
|
||||
}
|
||||
|
||||
@Inject(method = "write(Ljava/lang/String;)V", at = @At("TAIL"))
|
||||
void transformAfterWrite(String text, CallbackInfo ci) {
|
||||
if (chattransform$shouldTransform) {
|
||||
chattransform$shouldTransform = false;
|
||||
transform();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setCursor(IZ)V", at = @At("TAIL"))
|
||||
void updateStartOnSetCursor(int cursor, boolean shiftKeyPressed, CallbackInfo ci) {
|
||||
transformStart.clear();
|
||||
lastSubstitution = null;
|
||||
}
|
||||
|
||||
@Unique private final TransformStart transformStart = new TransformStart();
|
||||
@Unique private Substitution lastSubstitution = null;
|
||||
void transform() {
|
||||
if (!transformStart.isAvailable()) transformStart.set(selectionStart - 1);
|
||||
if (transformStart.get() >= selectionStart) return;
|
||||
String currentString = getText().substring(transformStart.get(), selectionStart);
|
||||
Iterator<? extends Map.Entry<String, String>> complete = Cfg.substitutions.getKeyValuePairsForKeysStartingWith(currentString).iterator();
|
||||
if (complete.hasNext()) {
|
||||
Map.Entry<String, String> substitution = complete.next();
|
||||
if (substitution.getKey().length() == currentString.length() // Match without "overshoot"
|
||||
&& !complete.hasNext() // No other options
|
||||
&& substitute(transformStart.get(), selectionStart, substitution.getValue())) {
|
||||
transformStart.set(selectionStart);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// No matches
|
||||
if (transformStart.get() == selectionStart - 1) {
|
||||
// Nothing starts with this char
|
||||
transformStart.increment();
|
||||
} else {
|
||||
// Something previously started with this...
|
||||
String previousString = getText().substring(transformStart.get(), selectionStart - 1);
|
||||
String substitution = Cfg.substitutions.get(previousString);
|
||||
if (substitution != null
|
||||
// ...and matched -> replace
|
||||
&& substitute(transformStart.get(), selectionStart - 1, substitution)) {
|
||||
setCursor(selectionStart + 1, false);
|
||||
} else {
|
||||
// ...and didn't match -> move transform start and call transform again (substring might have matched)
|
||||
transformStart.increment();
|
||||
transform();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chattransform$finalize() {
|
||||
String str = getText();
|
||||
if (!transformStart.isAvailable() || transformStart.get() >= selectionStart) return str;
|
||||
String currentString = str.substring(transformStart.get(), selectionStart);
|
||||
String substitution = Cfg.substitutions.get(currentString);
|
||||
if (substitution == null || !substitute(transformStart.get(), selectionStart, substitution)) {
|
||||
transformStart.increment();
|
||||
return chattransform$finalize();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
boolean substitute(int start, int end, String substitution) {
|
||||
ChatTransform.LOG.info("Transforming " + getText().substring(start, end) + " to " + substitution);
|
||||
String sub = text.substring(0, start) + substitution + text.substring(end);
|
||||
if (textPredicate.test(sub)) {
|
||||
this.text = sub;
|
||||
int oldLen = end - start;
|
||||
int newLen = substitution.length();
|
||||
if (selectionStart > end) selectionStart += newLen - oldLen;
|
||||
else if (selectionStart > start) selectionStart = start + newLen;
|
||||
selectionEnd = selectionStart;
|
||||
lastSubstitution = new Substitution(start, start + newLen);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
@Inject(method = "renderWidget(Lnet/minecraft/client/gui/DrawContext;IIF)V", at = @At(value = "TAIL"))
|
||||
void renderTransformStart(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
if (isVisible() && Cfg.Client.visualize) {
|
||||
int y = this.drawsBackground ? this.getY() + (this.height - 8) / 2 : this.getY();
|
||||
if (transformStart.isAvailable()) {
|
||||
int x = getCharacterX(transformStart.get());
|
||||
chattransform$fill(context, x, y - 1, x + 1, y + 1 + 9, 0x7f0000ff);
|
||||
}
|
||||
if (transformStart.showPrevious()) {
|
||||
int x = getCharacterX(transformStart.getPrevious());
|
||||
chattransform$fill(context, x, y - 1, x + 1, y + 1 + 9, 0x7fff0000);
|
||||
}
|
||||
if (lastSubstitution != null && lastSubstitution.shouldShow()) {
|
||||
int start = getCharacterX(lastSubstitution.start());
|
||||
int end = getCharacterX(Math.min(lastSubstitution.end() + 1, text.length()));
|
||||
chattransform$fill(context, start, y - 1, end, y + 1 + 9, 0x7fffff00);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void chattransform$fill(DrawContext context, int x1, int y1, int x2, int y2, int color) {
|
||||
if (x1 < 0) x1 = 0;
|
||||
if (x2 < 0) x2 = 0;
|
||||
int maxX = getX() + getWidth();
|
||||
if (x1 > maxX) x1 = maxX;
|
||||
if (x2 > maxX) x2 = maxX;
|
||||
|
||||
if (x1 < x2) {
|
||||
int i = x1;
|
||||
x1 = x2;
|
||||
x2 = i;
|
||||
}
|
||||
|
||||
if (y1 < y2) {
|
||||
int i = y1;
|
||||
y1 = y2;
|
||||
y2 = i;
|
||||
}
|
||||
|
||||
context.drawText(textRenderer, "X={" + x1 + ";" + x2 + "} Y={" + y1 + ";" + y2 + "}", 0, 0, 0xFF000000, false);
|
||||
context.fill(x1, y1, x2, y2, color);
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package io.gitlab.jfronny.chattransform.mixin;
|
||||
|
||||
import io.gitlab.jfronny.chattransform.Cfg;
|
||||
import io.gitlab.jfronny.chattransform.ITextFieldWidget;
|
||||
import net.minecraft.client.gui.screen.ChatScreen;
|
||||
import net.minecraft.client.gui.widget.TextFieldWidget;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mixin(ChatScreen.class)
|
||||
public abstract class ChatScreenMixin {
|
||||
@Shadow protected TextFieldWidget chatField;
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "init()V")
|
||||
void init(CallbackInfo ci) {
|
||||
if (Cfg.mode == Cfg.Mode.Live) ((ITextFieldWidget) chatField).chattransform$activate();
|
||||
}
|
||||
|
||||
@Redirect(method = "keyPressed(III)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/TextFieldWidget;getText()Ljava/lang/String;"))
|
||||
String finalizeTransforms(TextFieldWidget instance) {
|
||||
String str = ((ITextFieldWidget) chatField).chattransform$finalize();
|
||||
if (Cfg.mode == Cfg.Mode.OnSend) {
|
||||
for (Map.Entry<String, String> e : Cfg.substitutions
|
||||
.entrySet()
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(s -> s.getKey().length()))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.sorted(Map.Entry.<Integer, List<Map.Entry<String, String>>>comparingByKey().reversed())
|
||||
.map(Map.Entry::getValue)
|
||||
.flatMap(Collection::stream)
|
||||
.toList()) {
|
||||
str = str.replaceAll(Pattern.quote(e.getKey()), e.getValue());
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
package io.gitlab.jfronny.chattransform.mixin;
|
||||
|
||||
import io.gitlab.jfronny.chattransform.*;
|
||||
import net.minecraft.client.gui.DrawableHelper;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.ClickableWidget;
|
||||
import net.minecraft.client.gui.widget.TextFieldWidget;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.text.Text;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mixin(TextFieldWidget.class)
|
||||
public abstract class TextFieldWidgetMixin extends ClickableWidget implements ITextFieldWidget {
|
||||
public TextFieldWidgetMixin(int x, int y, int width, int height, Text message) {
|
||||
super(x, y, width, height, message);
|
||||
}
|
||||
|
||||
@Shadow public abstract String getText();
|
||||
@Shadow private String text;
|
||||
@Shadow private int selectionStart;
|
||||
@Shadow private int selectionEnd;
|
||||
@Shadow private boolean drawsBackground;
|
||||
@Shadow public abstract int getCharacterX(int index);
|
||||
@Shadow public abstract boolean isVisible();
|
||||
@Shadow public abstract void setCursor(int cursor);
|
||||
|
||||
@Shadow private Predicate<String> textPredicate;
|
||||
@Unique private boolean chattransform$active = false;
|
||||
@Override
|
||||
public void chattransform$activate() {
|
||||
chattransform$active = true;
|
||||
ChatTransform.LOG.info("Activated widget " + this);
|
||||
}
|
||||
|
||||
@Unique private boolean chattransform$shouldTransform = false;
|
||||
@Redirect(method = "write(Ljava/lang/String;)V", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/widget/TextFieldWidget;text:Ljava/lang/String;", opcode = Opcodes.PUTFIELD))
|
||||
void transformBeforeWrite(TextFieldWidget instance, String value, String tx) {
|
||||
if (chattransform$active) {
|
||||
if (selectionStart == selectionEnd && tx.length() == 1 && !Screen.hasAltDown() && !getText().startsWith("/")) {
|
||||
chattransform$shouldTransform = true;
|
||||
} else {
|
||||
chattransform$start = null;
|
||||
}
|
||||
}
|
||||
this.text = value;
|
||||
}
|
||||
|
||||
@Inject(method = "write(Ljava/lang/String;)V", at = @At("TAIL"))
|
||||
void transformAfterWrite(String text, CallbackInfo ci) {
|
||||
if (chattransform$shouldTransform) {
|
||||
chattransform$shouldTransform = false;
|
||||
transform();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "setCursor(I)V", at = @At("TAIL"))
|
||||
void updateStartOnSetCursor(int cursor, CallbackInfo ci) {
|
||||
chattransform$start = null;
|
||||
}
|
||||
|
||||
@Unique private Integer chattransform$start = null;
|
||||
void transform() {
|
||||
if (chattransform$start == null) chattransform$start = selectionStart - 1;
|
||||
if (chattransform$start < 0) chattransform$start = 0;
|
||||
if (chattransform$start >= selectionStart) return;
|
||||
String currentString = getText().substring(chattransform$start, selectionStart);
|
||||
Set<Map.Entry<String, String>> complete = getStartingWith(currentString);
|
||||
// Exact match
|
||||
if (Cfg.substitutions.containsKey(currentString) && complete.size() == 1
|
||||
&& substitute(chattransform$start, selectionStart, Cfg.substitutions.get(currentString))) {
|
||||
chattransform$start = selectionStart;
|
||||
return;
|
||||
}
|
||||
if (complete.isEmpty()) {
|
||||
if (chattransform$start == selectionStart - 1) {
|
||||
// Nothing starts with this char
|
||||
chattransform$start++;
|
||||
} else {
|
||||
// Something previously started with this...
|
||||
String previousString = getText().substring(chattransform$start, selectionStart - 1);
|
||||
if (Cfg.substitutions.containsKey(previousString)
|
||||
// ...and matched -> replace
|
||||
&& substitute(chattransform$start, selectionStart - 1, Cfg.substitutions.get(previousString))) {
|
||||
setCursor(selectionStart + 1);
|
||||
} else {
|
||||
// ...and didn't match -> move transform start and call transform again (substring might have matched)
|
||||
chattransform$start++;
|
||||
transform();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chattransform$finalize() {
|
||||
String str = getText();
|
||||
if (chattransform$start == null || chattransform$start >= selectionStart) return str;
|
||||
String currentString = str.substring(chattransform$start, selectionStart);
|
||||
if (!Cfg.substitutions.containsKey(currentString)
|
||||
|| !substitute(chattransform$start, selectionStart, Cfg.substitutions.get(currentString))) {
|
||||
chattransform$start++;
|
||||
return chattransform$finalize();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
boolean substitute(int start, int end, String substitution) {
|
||||
ChatTransform.LOG.info("Transforming " + getText().substring(start, end) + " to " + substitution);
|
||||
String sub = text.substring(0, start) + substitution + text.substring(end);
|
||||
if (textPredicate.test(sub)) {
|
||||
this.text = sub;
|
||||
int oldLen = end - start;
|
||||
int newLen = substitution.length();
|
||||
if (selectionStart > end) selectionStart -= oldLen + newLen;
|
||||
else if (selectionStart > start) selectionStart = start + newLen;
|
||||
selectionEnd = selectionStart;
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
Set<Map.Entry<String, String>> getStartingWith(String start) {
|
||||
return Cfg.substitutions.entrySet().stream().filter(s -> s.getKey().startsWith(start)).collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
@Inject(method = "renderButton(Lnet/minecraft/client/util/math/MatrixStack;IIF)V", at = @At(value = "TAIL"))
|
||||
void renderTransformStart(MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) {
|
||||
if (isVisible() && chattransform$start != null) {
|
||||
int x = getCharacterX(chattransform$start);
|
||||
int y = this.drawsBackground ? this.getY() + (this.height - 8) / 2 : this.getY();
|
||||
DrawableHelper.fill(matrices, x, y - 1, x + 1, y + 1 + 9, 0x7f0000ff);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,23 @@
|
|||
"chat-transform.jfconfig.title": "Chat-Transform",
|
||||
"chat-transform.jfconfig.substitutions": "Substitutions",
|
||||
"chat-transform.jfconfig.substitutions.tooltip": "The substitutions to perform on text sent in the chat. See the presets for examples",
|
||||
"chat-transform.jfconfig.mode": "Mode",
|
||||
"chat-transform.jfconfig.mode.tooltip": "When to perform replacements",
|
||||
"chat-transform.jfconfig.enum.Mode.Live": "Live",
|
||||
"chat-transform.jfconfig.enum.Mode.OnSend": "On Send"
|
||||
"chat-transform.jfconfig.client.title": "Client Settings",
|
||||
"chat-transform.jfconfig.client.mode": "Mode",
|
||||
"chat-transform.jfconfig.client.mode.tooltip": "When to perform replacements",
|
||||
"chat-transform.jfconfig.client.enum.Mode.Live": "Live",
|
||||
"chat-transform.jfconfig.client.enum.Mode.OnSend": "On Send",
|
||||
"chat-transform.jfconfig.client.visualize": "Visualize",
|
||||
"chat-transform.jfconfig.client.visualize.tooltip": "Visualize transformations as you type in live mode",
|
||||
"chat-transform.jfconfig.server.title": "Server Settings",
|
||||
"chat-transform.jfconfig.server.messageOnFirstConnect": "Message on first connect",
|
||||
"chat-transform.jfconfig.server.messageOnFirstConnect.tooltip": "Whether to send a chat message to players about chat-transform when they first connect",
|
||||
"chat-transform.jfconfig.server.playerStates": "Player States",
|
||||
"chat-transform.jfconfig.server.playerStates.tooltip": "For which players messages should be transformed",
|
||||
"chat-transform.jfconfig.server.playerConfigurable": "Player Configurable",
|
||||
"chat-transform.jfconfig.server.playerConfigurable.tooltip": "Whether players should be able to toggle chat transformation",
|
||||
"chat-transform.jfconfig.server.defaultEnable": "Default Enable",
|
||||
"chat-transform.jfconfig.server.defaultEnable.tooltip": "Whether players' chat should be transformed by default",
|
||||
"chat-transform.jfconfig.owo": "OwO",
|
||||
"chat-transform.jfconfig.katakana": "Katakana",
|
||||
"chat-transform.jfconfig.hiragana": "Hiragana"
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "io.gitlab.jfronny.chattransform.mixin",
|
||||
"package": "io.gitlab.jfronny.chattransform.client.mixin",
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"client": [
|
||||
"ChatScreenMixin",
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package io.gitlab.jfronny.chattransform;
|
||||
|
||||
import io.gitlab.jfronny.commons.data.String2ObjectMap;
|
||||
import io.gitlab.jfronny.commons.serialize.annotations.Ignore;
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@JfConfig
|
||||
public class Cfg {
|
||||
@Entry public static String2ObjectMap<String> substitutions = new String2ObjectMap<>();
|
||||
|
||||
@Category
|
||||
public static class Client {
|
||||
@Entry public static Mode mode = Mode.Live;
|
||||
@Entry public static boolean visualize = true;
|
||||
}
|
||||
|
||||
@Category
|
||||
public static class Server {
|
||||
@Entry public static boolean messageOnFirstConnect = true;
|
||||
@Entry public static Map<String, Boolean> playerStates = new HashMap<>();
|
||||
@Entry public static boolean playerConfigurable = true;
|
||||
@Entry public static boolean defaultEnable = true;
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
Live, OnSend
|
||||
}
|
||||
|
||||
@Preset
|
||||
public static void owo() {
|
||||
substitutions = new String2ObjectMap<>();
|
||||
substitutions.put("r", "w");
|
||||
substitutions.put("l", "w");
|
||||
substitutions.put("R", "W");
|
||||
substitutions.put("L", "W");
|
||||
substitutions.put("no", "nu");
|
||||
substitutions.put("has", "haz");
|
||||
substitutions.put("have", "haz");
|
||||
substitutions.put("you", "uu");
|
||||
substitutions.put("the ", "da ");
|
||||
substitutions.put("The ", "Da ");
|
||||
substitutions.put("This", "Dis");
|
||||
substitutions.put("this", "dis");
|
||||
}
|
||||
|
||||
@Ignore private static final char[] consonants = "bcdfghjklmprstwz".toCharArray();
|
||||
|
||||
@Preset
|
||||
public static void katakana() {
|
||||
substitutions = new String2ObjectMap<>();
|
||||
substitutions.put("-", "ー");
|
||||
for (char c : consonants) {
|
||||
substitutions.put("" + c + c, "ッ" + c);
|
||||
}
|
||||
substitutions.put("nn", "ン");
|
||||
JapaneseMap.fromTable(JapaneseMap.katakanaTable, substitutions::put);
|
||||
System.out.println("katakana " + substitutions);
|
||||
}
|
||||
|
||||
@Preset
|
||||
public static void hiragana() {
|
||||
substitutions = new String2ObjectMap<>();
|
||||
substitutions.put("-", "ー");
|
||||
for (char c : consonants) {
|
||||
substitutions.put("" + c + c, "っ" + c);
|
||||
}
|
||||
substitutions.put("nn", "ん");
|
||||
JapaneseMap.fromTable(JapaneseMap.hiraganaTable, substitutions::put);
|
||||
System.out.println("hiragana " + substitutions);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package io.gitlab.jfronny.chattransform;
|
||||
|
||||
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
|
||||
|
||||
public class ChatTransform {
|
||||
public static final String MOD_ID = "chat-transform";
|
||||
public static final SystemLoggerPlus LOG = SystemLoggerPlus.forName(MOD_ID);
|
||||
|
||||
public static String transform(String text) {
|
||||
return Cfg.substitutions.asSubstitution().apply(text);
|
||||
}
|
||||
}
|
|
@ -1,36 +1,9 @@
|
|||
package io.gitlab.jfronny.chattransform;
|
||||
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.v1.Ignore;
|
||||
import io.gitlab.jfronny.libjf.config.api.v1.*;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@JfConfig
|
||||
public class Cfg {
|
||||
@Entry public static Map<String, String> substitutions = new LinkedHashMap<>();
|
||||
@Entry public static Mode mode = Mode.Live;
|
||||
|
||||
public enum Mode {
|
||||
Live, OnSend
|
||||
}
|
||||
|
||||
@Preset
|
||||
public static void owo() {
|
||||
substitutions.clear();
|
||||
substitutions.put("r", "w");
|
||||
substitutions.put("l", "w");
|
||||
substitutions.put("R", "W");
|
||||
substitutions.put("L", "W");
|
||||
substitutions.put("no", "nu");
|
||||
substitutions.put("has", "haz");
|
||||
substitutions.put("have", "haz");
|
||||
substitutions.put("you", "uu");
|
||||
substitutions.put("the ", "da ");
|
||||
substitutions.put("The ", "Da ");
|
||||
}
|
||||
|
||||
@Ignore private static final String katakanaTable = """
|
||||
public class JapaneseMap {
|
||||
public static final String katakanaTable = """
|
||||
a i u e o n
|
||||
ア イ ウ エ オ ン
|
||||
x ァ ィ ゥ ェ ォ
|
||||
|
@ -63,7 +36,7 @@ public class Cfg {
|
|||
py ピャ ピュ ピョ
|
||||
v ゔ""";
|
||||
|
||||
@Ignore private static final String hiraganaTable = """
|
||||
public static final String hiraganaTable = """
|
||||
a i u e o n
|
||||
あ い う え お ん
|
||||
x ぁ ぃ ぅ ぇ ぉ
|
||||
|
@ -96,9 +69,7 @@ public class Cfg {
|
|||
py ぴゃ ぴゅ ぴょ
|
||||
v ゔ""";
|
||||
|
||||
@Ignore private static final char[] consonants = "bcdfghjklmprstwz".toCharArray();
|
||||
|
||||
private static void fromTable(String table) {
|
||||
public static void fromTable(String table, BiConsumer<String, String> target) {
|
||||
String[] rows = table.split("\n");
|
||||
String[] colNames = rows[0].split("\t");
|
||||
for (int i = 1; i < rows.length; i++) {
|
||||
|
@ -111,32 +82,10 @@ public class Cfg {
|
|||
String[] kanas = kana.split("/");
|
||||
for (String singleKana : kanas) {
|
||||
if (!singleKana.isEmpty()) {
|
||||
substitutions.put(value, singleKana);
|
||||
target.accept(value, singleKana);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preset
|
||||
public static void katakana() {
|
||||
substitutions.clear();
|
||||
substitutions.put("-", "ー");
|
||||
for (char c : consonants) {
|
||||
substitutions.put("" + c + c, "ッ" + c);
|
||||
}
|
||||
substitutions.put("nn", "ン");
|
||||
fromTable(katakanaTable);
|
||||
}
|
||||
|
||||
@Preset
|
||||
public static void hiragana() {
|
||||
substitutions.clear();
|
||||
substitutions.put("-", "ー");
|
||||
for (char c : consonants) {
|
||||
substitutions.put("" + c + c, "っ" + c);
|
||||
}
|
||||
substitutions.put("nn", "ん");
|
||||
fromTable(hiraganaTable);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
package io.gitlab.jfronny.chattransform.server;
|
||||
|
||||
import com.mojang.brigadier.Command;
|
||||
import io.gitlab.jfronny.chattransform.Cfg;
|
||||
import io.gitlab.jfronny.chattransform.ChatTransform;
|
||||
import io.gitlab.jfronny.libjf.config.api.v2.ConfigInstance;
|
||||
import net.fabricmc.api.DedicatedServerModInitializer;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent;
|
||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import static net.minecraft.server.command.CommandManager.literal;
|
||||
|
||||
public class ChatTransformServer implements DedicatedServerModInitializer {
|
||||
public static final ConfigInstance CONFIG_INSTANCE = Objects.requireNonNull(ConfigInstance.get(ChatTransform.MOD_ID));
|
||||
|
||||
@Override
|
||||
public void onInitializeServer() {
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> {
|
||||
var root = literal(ChatTransform.MOD_ID).executes(context -> {
|
||||
context.getSource().sendMessage(Text.literal(ChatTransform.MOD_ID + " is " + (get(context.getSource().getPlayer()) ? "enabled" : "disabled") + " for you."));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
});
|
||||
root.then(literal("opt-in").requires(src -> Cfg.Server.playerConfigurable).executes(context -> {
|
||||
if (context.getSource().getPlayer() == null) {
|
||||
context.getSource().sendError(Text.literal("You are not a player."));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
if (get(context.getSource().getPlayer())) {
|
||||
context.getSource().sendError(Text.literal("You are already opted in to using chat-transform."));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
set(context.getSource().getPlayer(), true);
|
||||
context.getSource().sendMessage(Text.literal("Toggled " + ChatTransform.MOD_ID + " on"));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}));
|
||||
root.then(literal("opt-out").requires(src -> Cfg.Server.playerConfigurable).executes(context -> {
|
||||
if (context.getSource().getPlayer() == null) {
|
||||
context.getSource().sendError(Text.literal("You are not a player."));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
if (!get(context.getSource().getPlayer())) {
|
||||
context.getSource().sendError(Text.literal("You are already opted out of using chat-transform."));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
set(context.getSource().getPlayer(), false);
|
||||
context.getSource().sendMessage(Text.literal("Toggled " + ChatTransform.MOD_ID + " off"));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}));
|
||||
root.then(literal("reset").requires(src -> Cfg.Server.playerConfigurable).executes(context -> {
|
||||
if (context.getSource().getPlayer() == null) {
|
||||
context.getSource().sendError(Text.literal("You are not a player."));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
reset(context.getSource().getPlayer());
|
||||
context.getSource().sendMessage(Text.literal("Reset " + ChatTransform.MOD_ID + " to " + (Cfg.Server.defaultEnable ? "on" : "off")));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}));
|
||||
root.then(literal("reset-all").requires(src -> src.hasPermissionLevel(4)).executes(context -> {
|
||||
Cfg.Server.playerStates.clear();
|
||||
CONFIG_INSTANCE.write();
|
||||
context.getSource().sendMessage(Text.literal("Reset all player states"));
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}));
|
||||
dispatcher.register(root);
|
||||
});
|
||||
|
||||
ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> {
|
||||
if (Cfg.Server.messageOnFirstConnect && !has(handler.getPlayer())) {
|
||||
set(handler.getPlayer(), Cfg.Server.defaultEnable);
|
||||
MutableText text = Text.literal("This server uses " + ChatTransform.MOD_ID + " to transform messages you send.");
|
||||
if (Cfg.Server.playerConfigurable) {
|
||||
text.append(" To turn it " + (Cfg.Server.defaultEnable ? "off" : "on") + ", use ");
|
||||
String command = "/" + ChatTransform.MOD_ID + " opt-" + (Cfg.Server.defaultEnable ? "out" : "in");
|
||||
text.append(Text.literal(command)
|
||||
.styled(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command)))
|
||||
);
|
||||
text.append(".");
|
||||
}
|
||||
handler.getPlayer().sendMessage(text);
|
||||
}
|
||||
});
|
||||
|
||||
ServerMessageDecoratorEvent.EVENT.register(ServerMessageDecoratorEvent.CONTENT_PHASE, (sender, message) -> {
|
||||
return sender != null && get(sender) ? transform(message) : message;
|
||||
});
|
||||
}
|
||||
|
||||
private boolean has(ServerPlayerEntity player) {
|
||||
return Cfg.Server.playerStates.containsKey(player.getUuidAsString());
|
||||
}
|
||||
|
||||
private boolean get(ServerPlayerEntity player) {
|
||||
return Cfg.Server.playerStates.computeIfAbsent(player.getUuidAsString(), k -> Cfg.Server.defaultEnable);
|
||||
}
|
||||
|
||||
private void set(ServerPlayerEntity player, boolean value) {
|
||||
Cfg.Server.playerStates.put(player.getUuidAsString(), value);
|
||||
CONFIG_INSTANCE.write();
|
||||
}
|
||||
|
||||
private void reset(ServerPlayerEntity player) {
|
||||
Cfg.Server.playerStates.remove(player.getUuidAsString());
|
||||
CONFIG_INSTANCE.write();
|
||||
}
|
||||
|
||||
private Text transform(Text source) {
|
||||
MutableText result = MutableText.of(transform(source.getContent()))
|
||||
.setStyle(source.getStyle());
|
||||
for (Text sibling : source.getSiblings()) {
|
||||
result.append(transform(sibling));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private TextContent transform(TextContent source) {
|
||||
if (source instanceof TranslatableTextContent tx) {
|
||||
Object[] args = tx.getArgs();
|
||||
args = Arrays.copyOf(args, args.length);
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i] instanceof Text tx1) args[i] = transform(tx1);
|
||||
else if (args[i] instanceof TextContent tx1) args[i] = transform(tx1);
|
||||
else if (args[i] instanceof String tx1) args[i] = ChatTransform.transform(tx1);
|
||||
else args[i] = args[i];
|
||||
}
|
||||
return new TranslatableTextContent(tx.getKey(), ChatTransform.transform(tx.getFallback()), args);
|
||||
} else if (source instanceof PlainTextContent.Literal tx) {
|
||||
return new PlainTextContent.Literal(ChatTransform.transform(tx.string()));
|
||||
} else {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,9 +13,9 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"icon": "assets/chat-transform/icon.png",
|
||||
"environment": "client",
|
||||
"environment": "*",
|
||||
"entrypoints": {
|
||||
"client": ["io.gitlab.jfronny.chattransform.ChatTransform"],
|
||||
"server": ["io.gitlab.jfronny.chattransform.server.ChatTransformServer"],
|
||||
"libjf:config": ["io.gitlab.jfronny.chattransform.JFC_Cfg"]
|
||||
},
|
||||
"mixins": [
|
||||
|
@ -26,6 +26,6 @@
|
|||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.14.17",
|
||||
"minecraft": "1.19.4"
|
||||
"minecraft": "*"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue