Skip to content

Commit cd9818c

Browse files
committed
feat: lambdamoji
1 parent bdd7453 commit cd9818c

File tree

12 files changed

+473
-222
lines changed

12 files changed

+473
-222
lines changed

common/src/main/java/com/lambda/mixin/render/ChatInputSuggestorMixin.java

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,46 @@
1717

1818
package com.lambda.mixin.render;
1919

20+
import com.google.common.base.Strings;
2021
import com.lambda.command.CommandManager;
22+
import com.lambda.module.modules.client.LambdaMoji;
23+
import com.lambda.module.modules.client.RenderSettings;
2124
import com.mojang.brigadier.CommandDispatcher;
25+
import com.mojang.brigadier.suggestion.Suggestions;
26+
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
2227
import net.minecraft.client.gui.screen.ChatInputSuggestor;
2328
import net.minecraft.client.gui.widget.TextFieldWidget;
2429
import net.minecraft.client.network.ClientPlayNetworkHandler;
2530
import net.minecraft.command.CommandSource;
31+
import org.jetbrains.annotations.Nullable;
2632
import org.spongepowered.asm.mixin.Final;
2733
import org.spongepowered.asm.mixin.Mixin;
2834
import org.spongepowered.asm.mixin.Shadow;
35+
import org.spongepowered.asm.mixin.Unique;
2936
import org.spongepowered.asm.mixin.injection.At;
37+
import org.spongepowered.asm.mixin.injection.Inject;
3038
import org.spongepowered.asm.mixin.injection.ModifyVariable;
3139
import org.spongepowered.asm.mixin.injection.Redirect;
40+
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
41+
42+
import java.util.concurrent.CompletableFuture;
43+
import java.util.regex.Matcher;
44+
import java.util.regex.Pattern;
45+
import java.util.stream.Stream;
3246

3347
@Mixin(ChatInputSuggestor.class)
34-
public class ChatInputSuggestorMixin {
48+
public abstract class ChatInputSuggestorMixin {
3549

3650
@Shadow
3751
@Final
3852
TextFieldWidget textField;
3953

54+
@Shadow
55+
private @Nullable CompletableFuture<Suggestions> pendingSuggestions;
56+
57+
@Shadow
58+
public abstract void show(boolean narrateFirstSuggestion);
59+
4060
@ModifyVariable(method = "refresh", at = @At(value = "STORE"), index = 3)
4161
private boolean refreshModify(boolean showCompletions) {
4262
return CommandManager.INSTANCE.isCommand(textField.getText());
@@ -46,4 +66,55 @@ private boolean refreshModify(boolean showCompletions) {
4666
private CommandDispatcher<CommandSource> refreshRedirect(ClientPlayNetworkHandler instance) {
4767
return CommandManager.INSTANCE.currentDispatcher(textField.getText());
4868
}
69+
70+
@Inject(method = "refresh", at = @At("TAIL"))
71+
private void refreshEmojiSuggestion(CallbackInfo ci) {
72+
if (!LambdaMoji.INSTANCE.isEnabled() ||
73+
!LambdaMoji.INSTANCE.getSuggestions()) return;
74+
75+
String typing = textField.getText();
76+
77+
// Don't suggest emojis in commands
78+
if (CommandManager.INSTANCE.isCommand(typing) ||
79+
CommandManager.INSTANCE.isLambdaCommand(typing)) return;
80+
81+
int cursor = textField.getCursor();
82+
String textToCursor = typing.substring(0, cursor);
83+
if (textToCursor.isEmpty()) return;
84+
85+
// Most right index at the left of the regex expression
86+
int start = neoLambda$getLastColon(textToCursor);
87+
if (start == -1) return;
88+
89+
String emojiString = typing.substring(start + 1);
90+
91+
Stream<String> results = RenderSettings.INSTANCE.getEmojiFont().glyphs.getKeys()
92+
.stream()
93+
.filter(s -> s.startsWith(emojiString))
94+
.map(s -> s + ":");
95+
96+
pendingSuggestions = CommandSource.suggestMatching(results, new SuggestionsBuilder(textToCursor, start + 1));
97+
pendingSuggestions.thenRun(() -> {
98+
if (!pendingSuggestions.isDone()) return;
99+
100+
show(false);
101+
});
102+
}
103+
104+
@Unique
105+
private static final Pattern COLON_PATTERN = Pattern.compile("(:[a-zA-Z0-9_]+)");
106+
107+
@Unique
108+
private int neoLambda$getLastColon(String input) {
109+
if (Strings.isNullOrEmpty(input)) return -1;
110+
111+
int i = -1;
112+
Matcher matcher = COLON_PATTERN.matcher(input);
113+
114+
while (matcher.find()) {
115+
i = matcher.start();
116+
}
117+
118+
return i;
119+
}
49120
}

common/src/main/java/com/lambda/mixin/render/ChatScreenMixin.java

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,65 +17,15 @@
1717

1818
package com.lambda.mixin.render;
1919

20-
import com.lambda.Lambda;
2120
import com.lambda.command.CommandManager;
22-
import com.lambda.graphics.renderer.gui.font.FontRenderer;
23-
import com.lambda.graphics.renderer.gui.font.LambdaEmoji;
24-
import com.lambda.graphics.renderer.gui.font.glyph.GlyphInfo;
25-
import com.lambda.module.modules.client.LambdaMoji;
26-
import com.lambda.util.math.Vec2d;
27-
import kotlin.Pair;
28-
import kotlin.ranges.IntRange;
2921
import net.minecraft.client.gui.screen.ChatScreen;
3022
import org.spongepowered.asm.mixin.Mixin;
3123
import org.spongepowered.asm.mixin.injection.At;
3224
import org.spongepowered.asm.mixin.injection.Inject;
33-
import org.spongepowered.asm.mixin.injection.ModifyArg;
3425
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
3526

36-
import java.util.ArrayList;
37-
import java.util.Collections;
38-
import java.util.List;
39-
4027
@Mixin(ChatScreen.class)
4128
public abstract class ChatScreenMixin {
42-
@ModifyArg(method = "sendMessage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendChatMessage(Ljava/lang/String;)V"), index = 0)
43-
private String modifyChatText(String chatText) {
44-
if (LambdaMoji.INSTANCE.isDisabled()) return chatText;
45-
46-
List<Pair<GlyphInfo, IntRange>> emojis = FontRenderer.Companion.parseEmojis(chatText, LambdaEmoji.Twemoji);
47-
Collections.reverse(emojis);
48-
49-
List<String> pushEmojis = new ArrayList<>();
50-
List<Vec2d> pushPositions = new ArrayList<>();
51-
52-
for (Pair<GlyphInfo, IntRange> emoji : emojis) {
53-
String emojiString = chatText.substring(emoji.getSecond().getStart() + 1, emoji.getSecond().getEndInclusive());
54-
if (LambdaEmoji.Twemoji.get(emojiString) == null)
55-
continue;
56-
57-
// Because the width of a char is bigger than an emoji
58-
// we can simply replace the matches string by a space
59-
// and render it after the text
60-
chatText = chatText.substring(0, emoji.getSecond().getStart()) + " " + chatText.substring(emoji.getSecond().getEndInclusive() + 1);
61-
62-
// We cannot retain the position in the future, but we can
63-
// assume that every time you send a message the height of
64-
// the position will change by the height of the glyph
65-
// The positions are from the top left corner of the screen
66-
int x = Lambda.getMc().textRenderer.getWidth(chatText.substring(0, emoji.getSecond().getStart()));
67-
int y = Lambda.getMc().textRenderer.fontHeight;
68-
69-
pushEmojis.add(String.format(":%s:", emojiString));
70-
pushPositions.add(new Vec2d(x, y));
71-
}
72-
73-
// Not optimal because it has to parse the emoji again but who cares
74-
LambdaMoji.INSTANCE.add(pushEmojis, pushPositions);
75-
76-
return chatText;
77-
}
78-
7929
@Inject(method = "sendMessage", at = @At("HEAD"), cancellable = true)
8030
void sendMessageInject(String chatText, boolean addToHistory, CallbackInfoReturnable<Boolean> cir) {
8131
if (!CommandManager.INSTANCE.isLambdaCommand(chatText)) return;
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2024 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.mixin.render;
19+
20+
import com.lambda.Lambda;
21+
import com.lambda.graphics.renderer.gui.font.FontRenderer;
22+
import com.lambda.graphics.renderer.gui.font.LambdaEmoji;
23+
import com.lambda.module.modules.client.LambdaMoji;
24+
import com.lambda.util.math.Vec2d;
25+
import net.minecraft.client.font.TextRenderer;
26+
import net.minecraft.client.render.VertexConsumerProvider;
27+
import net.minecraft.text.OrderedText;
28+
import net.minecraft.text.Text;
29+
import org.joml.Matrix4f;
30+
import org.spongepowered.asm.mixin.Mixin;
31+
import org.spongepowered.asm.mixin.Overwrite;
32+
import org.spongepowered.asm.mixin.Shadow;
33+
import org.spongepowered.asm.mixin.Unique;
34+
35+
import java.util.List;
36+
37+
@Mixin(TextRenderer.class)
38+
public abstract class TextRendererMixin {
39+
@Shadow
40+
protected abstract int drawInternal(String text, float x, float y, int color, boolean shadow, Matrix4f matrix, VertexConsumerProvider vertexConsumers, TextRenderer.TextLayerType layerType, int backgroundColor, int light, boolean mirror);
41+
42+
@Shadow
43+
protected abstract int drawInternal(OrderedText text, float x, float y, int color, boolean shadow, Matrix4f matrix, VertexConsumerProvider vertexConsumerProvider, TextRenderer.TextLayerType layerType, int backgroundColor, int light);
44+
45+
/**
46+
* @author Edouard127
47+
* @reason xx
48+
*/
49+
@Overwrite
50+
public int draw(
51+
String text,
52+
float x,
53+
float y,
54+
int color,
55+
boolean shadow,
56+
Matrix4f matrix,
57+
VertexConsumerProvider vertexConsumers,
58+
TextRenderer.TextLayerType layerType,
59+
int backgroundColor,
60+
int light,
61+
boolean rightToLeft
62+
) {
63+
if (LambdaMoji.INSTANCE.isDisabled()) return this.drawInternal(text, x, y, color, shadow, matrix, vertexConsumers, layerType, backgroundColor, light, rightToLeft);
64+
65+
return this.drawInternal(neoLambda$parseEmojisAndRender(text, x, y), x, y, color, shadow, matrix, vertexConsumers, layerType, backgroundColor, light, rightToLeft);
66+
}
67+
68+
/**
69+
* @author Edouard127
70+
* @reason xx
71+
*/
72+
@Overwrite
73+
public int draw(
74+
OrderedText text,
75+
float x,
76+
float y,
77+
int color,
78+
boolean shadow,
79+
Matrix4f matrix,
80+
VertexConsumerProvider vertexConsumers,
81+
TextRenderer.TextLayerType layerType,
82+
int backgroundColor,
83+
int light
84+
) {
85+
if (LambdaMoji.INSTANCE.isDisabled()) return this.drawInternal(text, x, y, color, shadow, matrix, vertexConsumers, layerType, backgroundColor, light);
86+
87+
StringBuilder builder = new StringBuilder();
88+
text.accept((index, style, c) -> {
89+
builder.appendCodePoint(c);
90+
return true;
91+
});
92+
93+
return this.drawInternal(
94+
Text.literal(neoLambda$parseEmojisAndRender(builder.toString(), x, y)).asOrderedText(),
95+
x, y, color, shadow, matrix, vertexConsumers, layerType, backgroundColor, light
96+
);
97+
}
98+
99+
@Unique
100+
private String neoLambda$parseEmojisAndRender(String raw, float x, float y) {
101+
List<String> emojis = LambdaEmoji.Twemoji.parse(raw);
102+
103+
for (String emoji : emojis) {
104+
String constructed = ":" + emoji + ":";
105+
int index = raw.indexOf(constructed);
106+
107+
if (LambdaEmoji.Twemoji.get(emoji) == null ||
108+
index == -1) continue;
109+
110+
int height = Lambda.getMc().textRenderer.fontHeight;
111+
int width = Lambda.getMc().textRenderer.getWidth(raw.substring(0, index));
112+
113+
LambdaMoji.INSTANCE.push(constructed, new Vec2d(x + width, y + (float) height / 2));
114+
115+
// Replace the emoji with whitespaces depending on the player's settings
116+
raw = raw.replaceFirst(constructed, neoLambda$getReplacement());
117+
}
118+
119+
return raw;
120+
}
121+
122+
@Unique
123+
private String neoLambda$getReplacement() {
124+
int emojiWidth = (int) (((double) Lambda.getMc().textRenderer.fontHeight / 2 / Lambda.getMc().textRenderer.getWidth(" ")) * LambdaMoji.INSTANCE.getScale());
125+
return " ".repeat(emojiWidth);
126+
}
127+
}

0 commit comments

Comments
 (0)