Skip to content

Commit 018e14f

Browse files
committed
Support YSM runtime extensions in Molang evaluation
Normalize YSM variable names and execute item, food, bone rotation, yaw speed, and second-order expressions against live ModelEngine entity state.
1 parent 8ade1af commit 018e14f

7 files changed

Lines changed: 474 additions & 40 deletions

File tree

build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ dependencies {
2424

2525
testImplementation("org.junit.jupiter:junit-jupiter:5.12.2")
2626
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
27+
testRuntimeOnly("io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT")
28+
testRuntimeOnly("com.ticxo.modelengine:ModelEngine:R4.1.0") {
29+
isTransitive = false
30+
}
2731
testCompileOnly("io.papermc.paper:paper-api:1.21.8-R0.1-SNAPSHOT")
2832
testCompileOnly("com.ticxo.modelengine:ModelEngine:R4.1.0") {
2933
isTransitive = false

src/main/java/com/ysm/modelengine/molang/MolangCompilerFacade.java

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,23 @@
55
import gg.moonflower.molangcompiler.api.MolangExpression;
66
import gg.moonflower.molangcompiler.api.exception.MolangException;
77

8+
import java.util.Locale;
89
import java.util.Map;
910
import java.util.concurrent.ConcurrentHashMap;
11+
import java.util.regex.Matcher;
12+
import java.util.regex.Pattern;
1013

1114
final class MolangCompilerFacade {
15+
private static final Pattern NAMESPACE_IDENTIFIER = Pattern.compile(
16+
"(?i)\\b(v|variable|ctrl|query|q|math|ysm)\\.([a-z_][a-z0-9_]*)");
17+
private static final Pattern SECOND_ORDER_CALL = Pattern.compile(
18+
"(?i)\\bysm\\.second_order\\(\\s*'([^']*)'\\s*,");
19+
private static final Pattern BONE_ROTATION_CALL = Pattern.compile(
20+
"(?i)\\bysm\\.bone_rot\\(\\s*'([^']*)'\\s*\\)\\s*\\.\\s*([xyz])\\b");
21+
1222
private final MolangCompiler compiler = GlobalMolangCompiler.get();
1323
private final Map<String, MolangExpression> cache = new ConcurrentHashMap<>();
24+
private final MolangSymbolTable symbols = new MolangSymbolTable();
1425
private final int maxEntries;
1526
private final RateLimitedDiagnostics diagnostics;
1627

@@ -20,7 +31,7 @@ final class MolangCompilerFacade {
2031
}
2132

2233
MolangExpression compile(String source) {
23-
String normalized = normalize(source);
34+
String normalized = rewriteDynamicFunctionArguments(normalize(source));
2435
if (normalized.isBlank()) {
2536
throw new IllegalArgumentException("Molang expression cannot be blank");
2637
}
@@ -40,6 +51,11 @@ MolangExpression compile(String source) {
4051

4152
void clear() {
4253
cache.clear();
54+
symbols.clear();
55+
}
56+
57+
MolangSymbolTable symbols() {
58+
return symbols;
4359
}
4460

4561
int size() {
@@ -59,12 +75,48 @@ private MolangExpression compileUncached(String source) {
5975
static String normalize(String source) {
6076
if (source == null) return "";
6177
String normalized = source.trim();
62-
if (normalized.startsWith("molang:")) normalized = normalized.substring("molang:".length()).trim();
63-
normalized = normalized.replace("ysm.head_yaw", "query.head_y_rotation");
64-
normalized = normalized.replace("ysm.head_pitch", "query.head_x_rotation");
65-
normalized = normalized.replace("ysm.body_yaw", "query.body_y_rotation");
66-
normalized = normalized.replace("variable.", "v.");
67-
normalized = normalized.replace("ctrl.", "v.ctrl_");
68-
return normalized;
78+
if (normalized.regionMatches(true, 0, "molang:", 0, "molang:".length())) {
79+
normalized = normalized.substring("molang:".length()).trim();
80+
}
81+
normalized = normalized.replaceAll("(?i)\\bysm\\.head_yaw\\b", "query.head_y_rotation");
82+
normalized = normalized.replaceAll("(?i)\\bysm\\.head_pitch\\b", "query.head_x_rotation");
83+
normalized = normalized.replaceAll("(?i)\\bysm\\.body_yaw\\b", "query.body_y_rotation");
84+
85+
Matcher matcher = NAMESPACE_IDENTIFIER.matcher(normalized);
86+
StringBuffer output = new StringBuffer();
87+
while (matcher.find()) {
88+
String namespace = matcher.group(1).toLowerCase(Locale.ROOT);
89+
String member = matcher.group(2).toLowerCase(Locale.ROOT);
90+
String replacement = switch (namespace) {
91+
case "variable" -> "v." + member;
92+
case "ctrl" -> "v.ctrl_" + member;
93+
default -> namespace + "." + member;
94+
};
95+
matcher.appendReplacement(output, Matcher.quoteReplacement(replacement));
96+
}
97+
matcher.appendTail(output);
98+
return output.toString();
99+
}
100+
101+
private String rewriteDynamicFunctionArguments(String source) {
102+
Matcher secondOrder = SECOND_ORDER_CALL.matcher(source);
103+
StringBuffer output = new StringBuffer();
104+
while (secondOrder.find()) {
105+
int symbolId = symbols.idFor(secondOrder.group(1));
106+
String replacement = "ysm.second_order(" + symbolId + ",";
107+
secondOrder.appendReplacement(output, Matcher.quoteReplacement(replacement));
108+
}
109+
secondOrder.appendTail(output);
110+
111+
Matcher boneRotation = BONE_ROTATION_CALL.matcher(output.toString());
112+
StringBuffer rewritten = new StringBuffer();
113+
while (boneRotation.find()) {
114+
int symbolId = symbols.idFor(boneRotation.group(1));
115+
String axis = boneRotation.group(2).toLowerCase(Locale.ROOT);
116+
String replacement = "ysm.bone_rot_" + axis + "(" + symbolId + ")";
117+
boneRotation.appendReplacement(rewritten, Matcher.quoteReplacement(replacement));
118+
}
119+
boneRotation.appendTail(rewritten);
120+
return rewritten.toString();
69121
}
70122
}

0 commit comments

Comments
 (0)