Merge branch 'janino-scripts' into syntax-highlighter-unified

This commit is contained in:
bigguy345 committed 2026-01-10 00:27:53 +02:00
commit 0f27ef2628
8 files changed
+729 -5

No files matched your search

+2 -1
View File
@@ -168,7 +168,8 @@ public class CustomNpcs {
AdvancedClassFilter filter = new AdvancedClassFilter()
.addRegexes("noppes\\.npcs\\.api\\..*")
.banRegexes(".*ClassLoader.*",
".*File*.");
".*File*.",
"java\\.lang\\.reflect\\..*");
for (Consumer<AdvancedClassFilter> consumer : clientClassFilterConsumer) {
consumer.accept(filter);
@@ -0,0 +1,406 @@
package noppes.npcs.client.gui.script;
import kamkeel.npcdbc.client.gui.component.SubGuiSelectList;
import noppes.npcs.janino.JaninoScript;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.*;
import net.minecraft.util.StatCollector;
import noppes.npcs.NoppesStringUtils;
import noppes.npcs.client.gui.util.*;
import noppes.npcs.controllers.ScriptController;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;
public class GuiJaninoScript extends GuiNPCInterface implements GuiYesNoCallback, ITextChangeListener, ICustomScrollListener, ISubGuiListener {
private GuiScriptTextArea textArea;
private int activeTab = 0;
public List<String> allExternalScripts = new ArrayList<>();
private final int scriptLimit = 1;
public String previousHookClicked = "";
// GUI display list
List<String> hooklist = new ArrayList<>();
// Map from display name (with parameters if overloaded) to Method
Map<String, Method> hookMap = new HashMap<>();
public final GuiScreen parent;
private final JaninoScript container;
public Runnable saveFunction;
public GuiJaninoScript(GuiScreen parent, JaninoScript handler) {
this.drawDefaultBackground = true;
this.closeOnEsc = true;
this.xSize = 420;
this.setBackground("menubg.png");
this.parent = parent;
this.container = handler;
createHookList();
String lang = handler.getLanguage();
allExternalScripts.addAll(ScriptController.Instance.getScripts(lang));
}
public GuiJaninoScript setSave(Runnable run) {
saveFunction = run;
return this;
}
public void createHookList() {
// Group all declared methods of the handler's type by their method name
Map<String, List<Method>> grouped = Arrays.stream(container.type.getDeclaredMethods())
.collect(Collectors.groupingBy(Method::getName));
for (Map.Entry<String, List<Method>> entry : grouped.entrySet()) {
List<Method> methods = entry.getValue();
// Sort overloads by number of parameters (ascending)
// This ensures the simplest overload comes first
methods.sort(Comparator.comparingInt(Method::getParameterCount));
boolean first = true;
for (Method m : methods) {
if (Modifier.isFinal(m.getModifiers()))
continue; //skip finals
String displayName = first ? m.getName() :
m.getName() + "(" + Arrays.stream(m.getParameterTypes())
.map(Class::getSimpleName)
.collect(Collectors.joining(", ")) + ")";
hooklist.add(displayName);
hookMap.put(displayName, m);
first = false;
}
}
}
public void initGui() {
this.ySize = (int) ((double) this.xSize * 0.56);
if ((double) this.ySize > (double) this.height * 0.95) {
this.ySize = (int) ((double) this.height * 0.95);
this.xSize = (int) ((double) this.ySize / 0.56);
}
this.bgScale = (float) this.xSize / 400.0F;
super.initGui();
this.guiTop += 10;
int yoffset = (int) ((double) this.ySize * 0.02);
GuiMenuTopButton top;
addTopButton(new GuiMenuTopButton(-1, guiLeft + xSize-2 , guiTop - 17, "X"));
this.addTopButton(top = new GuiMenuTopButton(0, this.guiLeft + 4, this.guiTop - 17, "gui.settings"));
int topXoffset = 0;
int topYoffset = 0;
if (this.container != null) {
this.addTopButton(top = new GuiMenuTopButton(1, top.xPosition + top.width + topXoffset, top.yPosition + topYoffset, "Script"));
} else {
this.addTopButton(new GuiMenuTopButton(this.scriptLimit, top.xPosition + top.width, top.yPosition, "+"));
}
top = this.getTopButton(this.activeTab);
if (top == null) {
this.activeTab = 0;
top = this.getTopButton(0);
}
top.active = true;
if (this.activeTab == 1) {
GuiCustomScroll hooks = new GuiCustomScroll(this, 1);
hooks.allowUserInput = false;
hooks.setSize(108, 198);
hooks.guiLeft = this.guiLeft - 110;
hooks.guiTop = this.guiTop + 14;
hooks.setUnsortedList(this.hooklist);
this.addScroll(hooks);
GuiNpcLabel hookLabel = new GuiNpcLabel(0, "script.hooks", hooks.guiLeft, this.guiTop + 5);
hookLabel.color = 11184810;
this.addLabel(hookLabel);
JaninoScript container = this.container;
if (textArea == null)
textArea = new GuiScriptTextArea(this, 2, this.guiLeft + 1 + yoffset, this.guiTop + yoffset, this.xSize - 108 - yoffset, (int) ((double) this.ySize * 0.96) - yoffset * 2, container == null ? "" : container.script);
else
textArea.init(this.guiLeft + 1 + yoffset, this.guiTop + yoffset, this.xSize - 108 - yoffset, (int) ((double) this.ySize * 0.96) - yoffset * 2, container == null ? "" : container.script);
textArea.enableCodeHighlighting();
textArea.setListener(this);
this.closeOnEsc(textArea::closeOnEsc);
this.addTextField(textArea);
int left1 = this.guiLeft + this.xSize - 104;
this.addButton(new GuiNpcButton(102, left1, this.guiTop + yoffset, 60, 20, "gui.clear"));
this.addButton(new GuiNpcButton(101, left1 + 61, this.guiTop + yoffset, 60, 20, "gui.paste"));
this.addButton(new GuiNpcButton(100, left1, this.guiTop + 21 + yoffset, 60, 20, "gui.copy"));
this.addButton(new GuiNpcButton(105, left1 + 61, this.guiTop + 21 + yoffset, 60, 20, "gui.remove"));
this.addButton(new GuiNpcButton(107, left1, this.guiTop + 66 + yoffset, 80, 20, "script.loadscript"));
// this.getButton(107).enabled = false;
GuiCustomScroll scroll = (new GuiCustomScroll(this, 0)).setUnselectable();
scroll.setSize(100, (int) ((double) this.ySize * 0.54) - yoffset * 2);
scroll.guiLeft = left1;
scroll.guiTop = this.guiTop + 88 + yoffset;
if (container != null)
scroll.setList(container.externalScripts);
this.addScroll(scroll);
} else {
GuiNpcTextArea var8 = new GuiNpcTextArea(2, this, this.guiLeft + 4 + yoffset, this.guiTop + 6 + yoffset, this.xSize - 160 - yoffset, (int) ((float) this.ySize * 0.92F) - yoffset * 2, this.getConsoleText());
var8.enabled = false;
this.addTextField(var8);
int var9 = this.guiLeft + this.xSize - 150;
this.addButton(new GuiNpcButton(100, var9, this.guiTop + 125, 60, 20, "gui.copy"));
this.addButton(new GuiNpcButton(102, var9, this.guiTop + 146, 60, 20, "gui.clear"));
this.addLabel(new GuiNpcLabel(1, "script.language", var9, this.guiTop + 15));
String containerLang = container.getLanguage();
this.addButton(new GuiNpcButton(103, var9 + 60, this.guiTop + 10, 80, 20, new String[]{containerLang.equals("Java") ? "Janino (JVM 8)" : containerLang},
this.getScriptIndex()));
// this.getButton(103).enabled = !this.languages.isEmpty();
this.addLabel(new GuiNpcLabel(2, "gui.enabled", var9, this.guiTop + 36));
this.addButton(new GuiNpcButton(104, var9 + 60, this.guiTop + 31, 50, 20, new String[]{"gui.no", "gui.yes"}, this.container.getEnabled() ? 1 : 0));
this.addButton(new GuiNpcButton(109, var9, this.guiTop + 78, 80, 20, "gui.website"));
this.addButton(new GuiNpcButton(112, var9 + 81, this.guiTop + 78, 80, 20, "gui.forum"));
this.addButton(new GuiNpcButton(110, var9, this.guiTop + 99, 80, 20, "script.apidoc"));
this.addButton(new GuiNpcButton(111, var9 + 81, this.guiTop + 99, 80, 20, "script.apisrc"));
}
this.xSize = 420;
this.ySize = 256;
}
public void customScrollClicked(int i, int j, int k, GuiCustomScroll scroll) {
String hook = scroll.getSelected();
if (this.previousHookClicked.equals(hook)) {
String addString = "";
if (!this.getTextField(2)
.getText()
.isEmpty()) {
addString = addString + "\n";
}
addString = addString + generateMethodStub(hookMap.get(hook));
this.getTextField(2)
.setText(this.getTextField(2)
.getText() + addString);
this.previousHookClicked = "";
} else {
this.previousHookClicked = hook;
}
}
public void confirmClicked(boolean flag, int i) {
if (flag) {
if (i == 0)
this.openLink("https://www.curseforge.com/minecraft/mc-mods/customnpc-plus");
else if (i == 1)
this.openLink("https://kamkeel.github.io/CustomNPC-Plus/");
else if (i == 2)
this.openLink("https://kamkeel.github.io/CustomNPC-Plus/");
else if (i == 3)
this.openLink("http://www.minecraftforge.net/forum/index.php/board,122.0.html");
if (i == 10) { //Remove
this.container.setScript("");
this.activeTab = 0;
} else if (i == 101) { //Paste
this.getTextField(2)
.setText(NoppesStringUtils.getClipboardContents());
this.setScript();
} else if (i == 102) { //Clear
if (this.activeTab == 1)
container.script = "";
else
this.container.clearConsole();
this.initGui();
}
}
this.displayGuiScreen(this);
}
protected void actionPerformed(GuiButton guibutton) {
if (guibutton.id == -1) {
close();
return;
}
if (guibutton.id == 0) {
this.setScript();
this.activeTab = 0;
this.initGui();
container.ensureCompiled();
}
if (guibutton.id == this.scriptLimit) {
// if (container.container == null)
//this.container.container = new ScriptContainer(this.container);
// else
// this.setScript();
this.activeTab = 1;
this.initGui();
}
if (guibutton.id == 109) {
this.displayGuiScreen(new GuiConfirmOpenLink(this, "https://kamkeel.github.io/CustomNPC-Plus/", 0, true));
} else if (guibutton.id == 110) {
this.displayGuiScreen(new GuiConfirmOpenLink(this, "https://github.com/KAMKEEL/CustomNPC-Plus-API", 1, true));
} else if (guibutton.id == 111) {
this.displayGuiScreen(new GuiConfirmOpenLink(this, "https://github.com/Noppes/CustomNPCsAPI", 2, true));
} else if (guibutton.id == 112) {
this.displayGuiScreen(new GuiConfirmOpenLink(this, "http://www.minecraftforge.net/forum/index.php/board,122.0.html", 3, true));
}
GuiYesNo confirmScreen;
//Copy
if (guibutton.id == 100) {
NoppesStringUtils.setClipboardContents(this.getTextField(2)
.getText());
}
//Paste
else if (guibutton.id == 101) {
GuiYesNo guiyesno = new GuiYesNo(this, StatCollector.translateToLocal("gui.paste"), StatCollector.translateToLocal("gui.sure"), 101);
this.displayGuiScreen(guiyesno);
}
//Clear
else if (guibutton.id == 102) {
confirmScreen = new GuiYesNo(this, StatCollector.translateToLocal("gui.clear"), StatCollector.translateToLocal("gui.sure"), 102);
this.displayGuiScreen(confirmScreen);
}
//Enable
else if (guibutton.id == 104) {
this.container.setEnabled(((GuiNpcButton) guibutton).getValue() == 1);
}
//Remove
else if (guibutton.id == 105) {
confirmScreen = new GuiYesNo(this, "", guibutton.displayString, 10);
this.displayGuiScreen(confirmScreen);
}
//Load Script
else if (guibutton.id == 107) {
setSubGui(new SubGuiSelectList(allExternalScripts, container.externalScripts, "All Scripts", "Selected Scripts"));
}
}
public void subGuiClosed(SubGuiInterface sub) {
if (sub instanceof SubGuiSelectList)
container.setExternalScripts(((SubGuiSelectList) sub).selected);
}
/**
* Generates a stub string for a given Method.
*/
public static String generateMethodStub(Method method) {
String mods = java.lang.reflect.Modifier.toString(method.getModifiers());
if (!mods.isEmpty())
mods += " ";
String returnTypeStr = method.getReturnType()
.getSimpleName();
String name = method.getName();
Map<String, Integer> typeCount = new HashMap<>();
String params = Arrays.stream(method.getParameters())
.map(p -> {
String typeName = p.getType()
.getSimpleName();
String baseName = Character.toLowerCase(typeName.charAt(0)) + typeName.substring(1);
int count = typeCount.getOrDefault(baseName, 0) + 1;
typeCount.put(baseName, count);
// append number if same type of arg exists
String paramName = count == 1 ? baseName : baseName + (count - 1);
return typeName + " " + paramName;
})
.collect(Collectors.joining(", "));
String body;
Class<?> returnType = method.getReturnType();
if (returnType == void.class)
body = "";
else if (returnType.isPrimitive()) {
if (returnType == boolean.class)
body = " return false;";
else if (returnType == char.class)
body = " return '\\0';";
else
body = " return 0;";
} else {
body = " return null;";
}
return String.format("%s%s %s(%s) {\n%s\n}\n", mods, returnTypeStr, name, params, body);
}
private String getConsoleText() {
Map<Long, String> map = this.container.getConsoleText();
StringBuilder builder = new StringBuilder();
Iterator var3 = map.entrySet()
.iterator();
while (var3.hasNext()) {
Map.Entry<Long, String> entry = (Map.Entry) var3.next();
builder.insert(0, new Date(entry.getKey()) + entry.getValue() + "\n");
}
return builder.toString();
}
private int getScriptIndex() {
return 0;
}
private void setScript() {
if (this.activeTab == 1) {
String text = this.getTextField(2)
.getText();
text = text.replace("\r\n", "\n");
text = text.replace("\r", "\n");
container.setScript(text);
}
}
public void save() {
if (saveFunction != null)
saveFunction.run();
else if (parent instanceof GuiNPCInterface)
((GuiNPCInterface) parent).save();
}
public void textUpdate(String text) {
// container.setScript(text);
}
public static GuiJaninoScript create(GuiScreen parent, JaninoScript script, int width, int height) {
GuiJaninoScript gui = new GuiJaninoScript(parent, script);
gui.setWorldAndResolution(Minecraft.getMinecraft(), width, height);
Minecraft.getMinecraft().currentScreen = gui;
gui.initGui();
return gui;
}
@Override
public void close() {
this.save();
this.setScript();
container.ensureCompiled();
parent.setWorldAndResolution(mc, width, height);
parent.initGui();
mc.currentScreen = parent;
}
}
@@ -360,7 +360,7 @@ public class Animation implements IAnimation {
* to the new local instance on entity's AnimationData,
* and clear them from the global instance.
*/
public void takeTasksFrom(Animation animation) {
public void moveFromGlobalToLocal(Animation animation) {
this.onAnimationStart = animation.onAnimationStart;
this.onAnimationFrame = animation.onAnimationFrame;
this.onAnimationEnd = animation.onAnimationEnd;
@@ -368,6 +368,9 @@ public class Animation implements IAnimation {
animation.onAnimationEnd = null;
animation.onAnimationFrame = null;
animation.onAnimationStart = null;
this.dataStore.putAll(animation.dataStore);
animation.dataStore.clear();
}
@@ -272,7 +272,7 @@ public class AnimationData implements IAnimationData {
newAnim.currentFrame = 0;
newAnim.currentFrameTime = 0;
newAnim.parent = this;
newAnim.takeTasksFrom((Animation) animation);
newAnim.moveFromGlobalToLocal((Animation) animation);
}
@@ -0,0 +1,276 @@
package noppes.npcs.janino;
import cpw.mods.fml.common.FMLCommonHandler;
import io.github.somehussar.janinoloader.api.script.IScriptBodyBuilder;
import io.github.somehussar.janinoloader.api.script.IScriptClassBody;
import net.minecraft.nbt.NBTTagCompound;
import noppes.npcs.CustomNpcs;
import noppes.npcs.NBTTags;
import noppes.npcs.config.ConfigScript;
import noppes.npcs.controllers.ScriptController;
import org.codehaus.commons.compiler.InternalCompilerException;
import org.codehaus.commons.compiler.Sandbox;
import java.security.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
public abstract class JaninoScript<T> {
public boolean enabled;
private String language = "Java";
public TreeMap<Long, String> console = new TreeMap();
/**
* Text written in the script GUI
*/
public String script = "";
/**
* This script's selected external files
* stored in "world/customnpcs/scripts/java"
*/
public List<String> externalScripts = new ArrayList<>();
/**
* To evaluate if script/externalScripts were changed or not,
* and compile the fullScript freshly if so.
*/
public boolean evaluated;
public final Class<T> type;
protected final Sandbox sandbox;
public final IScriptBodyBuilder<T> builder;
protected IScriptClassBody<T> scriptBody;
protected JaninoScript(Class<T> type, Consumer<IScriptBodyBuilder<T>> buildSettings) {
this.type = type;
this.builder = IScriptBodyBuilder
.getBuilder(type, CustomNpcs.getClientCompiler());
buildSettings.accept(builder);
Permissions permissions = new Permissions();
permissions.setReadOnly();
sandbox = new Sandbox(permissions);
this.scriptBody = builder.build();
}
protected T getUnsafe() {
return scriptBody.get();
}
public <R> R call(Function<T, R> fn) {
ensureCompiled();
T t = getUnsafe();
if (t == null)
return null;
// CodeSource cs = t.getClass().getProtectionDomain().getCodeSource();
// ProtectionDomain pd = new ProtectionDomain(cs, new Permissions());
try {
return sandbox.confine((PrivilegedAction<R>) () -> fn.apply(t));
// return AccessController.doPrivileged((PrivilegedAction<? extends R>) () -> fn.apply(t), new AccessControlContext(
// new ProtectionDomain[] {
// pd
// }
// ));
} catch (Exception e) {
appendConsole("Runtime Error: " + e.getMessage());
return null;
}
}
public void run(Consumer<T> fn) {
ensureCompiled();
T t = getUnsafe();
if (t == null)
return;
try {
sandbox.confine((PrivilegedAction<Void>) () -> {
fn.accept(t);
return null;
});
} catch (Exception e) {
appendConsole("Runtime Error: " + e.getMessage());
}
}
public void unload() {
}
/**
* Feed the code into the engine and compile it
*/
public void compileScript(String code) {
try {
scriptBody.setScript(code);
} catch (InternalCompilerException e ) {
Throwable parentCause = null;
Throwable cause = e;
appendConsole("Compilation error: " + e.getMessage());
while (cause.getCause() != null) { parentCause = cause; cause = cause.getCause(); }
Throwable rootCause = cause;
if (e != cause) {
appendConsole("\n");
if (parentCause != null)
appendConsole(parentCause.getMessage());
appendConsole(rootCause.getMessage());
}
} catch (Exception e) {
appendConsole("Unknown error: " + e.getMessage());
}
}
public void compileScript() {
compileScript(getFullCode());
}
public void ensureCompiled() {
if (!isEnabled()) {
if (!evaluated) {
compileScript("");
evaluated = true;
}
return;
}
int global = ScriptController.Instance.globalRevision;
if (!evaluated || global != lastSeenGlobalRevision) {
compileScript();
lastSeenGlobalRevision = global;
evaluated = true;
}
}
/**
* globalRevision is incremented on server
* each time externalScripts are updated
* then sent to client.
*/
private int lastSeenGlobalRevision;
private String getFullCode() {
if (!isEnabled())
return "";
StringBuilder sb = new StringBuilder();
if (ConfigScript.RunLoadedScriptsFirst)
this.appendExternalScripts(sb);
// Main script
if (this.script != null && !this.script.isEmpty())
sb.append(this.script).append("\n");
if (!ConfigScript.RunLoadedScriptsFirst)
this.appendExternalScripts(sb);
return sb.toString();
}
private void appendExternalScripts(StringBuilder sb) {
for (String scriptName : externalScripts) {
String code = ScriptController.Instance.scripts.get(scriptName);
if (code != null && !code.isEmpty())
sb.append(code).append("\n");
}
}
public void setScript(String script) {
this.script = script;
this.evaluated = false;
}
public void setExternalScripts(List<String> externalScripts) {
this.externalScripts = externalScripts;
this.evaluated = false;
}
public NBTTagCompound writeToNBT(NBTTagCompound compound) {
compound.setBoolean("enabled", enabled);
compound.setTag("console", NBTTags.NBTLongStringMap(this.console));
compound.setTag("externalScripts", NBTTags.nbtStringList(this.externalScripts));
compound.setString("script", script);
return compound;
}
public JaninoScript readFromNBT(NBTTagCompound compound) {
this.enabled = compound.getBoolean("enabled");
this.console = NBTTags.GetLongStringMap(compound.getTagList("console", 10));
setExternalScripts(NBTTags.getStringList(compound.getTagList("externalScripts", 10)));
setScript(compound.getString("script")); //after everything else is read
return this;
}
public boolean isEnabled() {
return this.enabled && ScriptController.HasStart && ConfigScript.ScriptingEnabled;
}
public boolean isClient() {
return FMLCommonHandler.instance()
.getEffectiveSide()
.isClient();
}
public boolean getEnabled() {
return this.enabled;
}
public void setEnabled(boolean b) {
if (this.enabled != b) {
this.enabled = b;
this.evaluated = false;
}
}
public String getLanguage() {
return this.language;
}
public void appendConsole(String message) {
if (message != null && !message.isEmpty()) {
long time = System.currentTimeMillis();
if (this.console.containsKey(time)) {
message = (String) this.console.get(time) + "\n" + message;
}
this.console.put(time, message);
while (this.console.size() > 40) {
this.console.remove(this.console.firstKey());
}
}
}
public Map<Long, String> getConsoleText() {
TreeMap<Long, String> map = new TreeMap();
int tab = 0;
Iterator var5 = console.entrySet()
.iterator();
while (var5.hasNext()) {
Map.Entry<Long, String> longStringEntry = (Map.Entry) var5.next();
map.put(longStringEntry.getKey(), " tab " + tab + ":\n" + (String) longStringEntry.getValue());
}
return map;
}
public void clearConsole() {
console.clear();
}
}
@@ -0,0 +1,38 @@
package noppes.npcs.janino;
import net.minecraft.nbt.NBTTagCompound;
import java.util.function.Supplier;
/**
*/
public interface JaninoScriptable<T extends JaninoScript<T>> {
boolean hasScript();
JaninoScript<T> getScript();
JaninoScript<T> createScript();
void deleteScript();
void unloadScript();
static <T, S extends JaninoScript<T>> S readFromNBT(NBTTagCompound compound, S script, Supplier<S> factory) {
if (compound.hasKey("Script")) {
if (script == null)
script = factory.get();
script.readFromNBT(compound.getCompoundTag("Script"));
}
return script;
}
static <T> NBTTagCompound writeToNBT(NBTTagCompound compound, JaninoScript<T> script) {
if (script != null)
compound.setTag("Script", script.writeToNBT(new NBTTagCompound()));
return compound;
}
}
@@ -17,7 +17,7 @@ public class AdvancedClassFilter implements LoadClassCondition {
public AdvancedClassFilter() {
addClasses("java.io.Serializable", "java.util.Iterator");
addRegexes("java\\.lang\\..");
addRegexes("java\\.lang\\..*");
}
public AdvancedClassFilter addRegexes(String... classRegexes) {