package me.km.snuviscript; import me.km.KajetansMod; import me.km.api.GlobalText; import me.km.api.Module; import me.km.exception.CodeTooLongException; import me.km.exception.GoHigherAtRootTreeException; import me.km.exception.GotoLabelNotFoundException; import me.km.exception.HoldCodeException; import me.km.exception.IllegalStringException; import me.km.exception.NoChildTreeException; import me.km.exception.PrescriptException; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.EmptyStackException; import java.util.List; import java.util.HashMap; import java.util.HashSet; import java.util.Stack; import java.util.stream.Collectors; import me.km.api.Location; import net.minecraft.command.ICommandSender; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.util.text.TextComponentString; public class QuestData { private final QuestParser parser; private final BenchmarkClock treeClock; public final BenchmarkClock exeClock; private boolean benchmark; private final Tree code; private CodeFunction currentCode; private final HashMap variables; private final HashMap gotos; private final Stack stack; private final HashSet events; private final HashSet loadedLocations; private boolean isWaiting; private boolean isTrying; private boolean tryFailed; private boolean shouldDoElse; private boolean isValid; private final Stack whileStack; private int overflowProtection; private String info; private String name; private final int id; private final boolean script; private int idCounter; private final ArrayList quester; public QuestData(int id, EntityPlayer p, String filename, ICommandSender cs) { parser = KajetansMod.quest.getQuestParser(); treeClock = new BenchmarkClock(); exeClock = new BenchmarkClock(); benchmark = false; this.id = id; code = new Tree<>(); idCounter = 0; variables = new HashMap<>(); gotos = new HashMap<>(); stack = new Stack<>(); events = new HashSet<>(); loadedLocations = new HashSet<>(); whileStack = new Stack<>(); name = filename; isWaiting = false; isValid = true; overflowProtection = 0; isTrying = false; quester = new ArrayList<>(); info = "Keine Info wurde gesetzt."; if(p != null) { quester.add(p); script = false; } else { script = true; } readCode(filename, cs); setVar("quote", "\""); } // ------------------------------------------------------------------------- // Überladen // ------------------------------------------------------------------------- public void loadNewCode(String filename, ICommandSender cs) { code.clear(); gotos.clear(); stack.clear(); whileStack.clear(); name = filename; readCode(filename, cs); isWaiting = false; isValid = true; } // ------------------------------------------------------------------------- // Flow-Control // ------------------------------------------------------------------------- public void goDeeper(boolean b) throws NoChildTreeException { code.goDeeper(); addWhileMode(b); } public void goHigher() { try { code.goHigher(); } catch(GoHigherAtRootTreeException ex) { } } public String getTreePath() { return Arrays.asList(code.getCurrentPosition()).stream().map(i -> i.toString()).collect(Collectors.joining(", ")); } private void readCode(String filename, ICommandSender cs) { int line = 0; StringBuilder codeLine = new StringBuilder(); try { File file = new File("./quests/" + filename + ".txt"); List lines; if(file.exists()) { try { lines = Files.readAllLines(file.toPath()); } catch (IOException ex) { throw new PrescriptException("Die Datei '" + filename + "' kann nicht gelesen werden."); } } else { //System.out.println("Die Datei '" + filename + "' wurde nicht gefunden."); throw new PrescriptException("Die Datei '" + filename + "' wurde nicht gefunden."); } int i = 0; int old = 0; int bracket = 0; boolean text = false; boolean bracketEnd = false; int bracketEndPos = 0; boolean oneLineChild = false; boolean comment = false; int commentPos = 0; while(line < lines.size()) { if(old >= codeLine.length() && !comment) { codeLine = new StringBuilder(lines.get(line).trim()); i = 0; old = 0; //System.out.println("NEU"); } else { codeLine.append(lines.get(line).trim()); //System.out.println("APPEND"); } //System.out.println(codeLine); while(i < codeLine.length()) { switch(codeLine.charAt(i)) { case '"': if(!comment) { text = !text; } break; case '@': if(comment || text) { break; } old = i; while(i < codeLine.length() && codeLine.charAt(i) != ' ') { i++; } try { code.selectLastChild(); // Nur für richtige Position } catch(NoChildTreeException ex) { } String s = codeLine.substring(old + 1, i); if(gotos.put(s, code.getCurrentPosition()) != null) { throw new PrescriptException("Doppeltes Goto: " + s + " - " + (line + 1)); } codeLine.delete(old, i + 1); i = old - 1; break; case '/': if(text) { break; } if(!comment && i + 1 < codeLine.length() && codeLine.charAt(i + 1) == '*') { comment = true; commentPos = i; } if(!comment && i + 1 < codeLine.length() && codeLine.charAt(i + 1) == '/') { codeLine.delete(i, codeLine.length()); old = codeLine.length() + 1; } break; case '*': if(text) { break; } if(comment && i + 1 < codeLine.length() && codeLine.charAt(i + 1) == '/') { comment = false; codeLine.delete(commentPos, i + 2); i = commentPos; } break; case ';': if(comment || text) { break; } if(bracket != 0) { //System.out.println(bracket); throw new PrescriptException("; aber Klammern ungültig - " + (line + 1)); } bracketEnd = false; code.addChild(new CodeFunction(codeLine.substring(old, i)), line); old = i + 1; if(oneLineChild) { oneLineChild = false; try { code.goHigher(); } catch(GoHigherAtRootTreeException ex) { throw new PrescriptException("Das sollte nicht passieren - " + (line + 1)); } } bracket = 0; break; case '(': if(!comment && !text) { bracket++; if(bracketEnd && !oneLineChild) { bracketEnd = false; oneLineChild = true; code.addChild(new CodeFunction(codeLine.substring(old, bracketEndPos)), line); old = bracketEndPos; try { code.selectLastChild(); code.goDeeper(); } catch(NoChildTreeException ex) { throw new PrescriptException("Das sollte nicht passieren - " + (line + 1)); } } } break; case ')': if(comment || text) { break; } bracket--; if(oneLineChild) { break; } if(bracket < 0) { throw new PrescriptException(") ohne ( - " + (line + 1)); } if(bracket == 0) { bracketEnd = true; bracketEndPos = i + 1; } break; case '{': if(comment || text) { break; } bracketEnd = false; code.addChild(new CodeFunction(codeLine.substring(old, i)), line); old = i + 1; try { code.selectLastChild(); code.goDeeper(); } catch(NoChildTreeException ex) { throw new PrescriptException("Das sollte nicht passieren - " + (line + 1)); } break; case '}': if(comment || text) { break; } old = i + 1; try { code.goHigher(); } catch(GoHigherAtRootTreeException ex) { throw new PrescriptException("} ohne { - " + (line + 1)); } break; } i++; } line++; } code.goToRoot(); } catch(Exception ex) { if(parser.printStack) { ex.printStackTrace(); } resetOverflowProtection(); Module m = KajetansMod.quest; int id = script ? 1 : 0; m.send(cs, "§cError in", id); m.sendHelpListElement(cs, "§cScript", getName(), id); m.sendHelpListElement(cs, "§cZeile", codeLine.toString(), id); m.sendHelpListElement(cs, "§cZeilennummer", String.valueOf(line + 1), id); if(ex.getLocalizedMessage() == null) { m.sendHelpListElement(cs, "§cException", ex.getClass().getSimpleName(), id); } else { m.sendHelpListElement(cs, "§cException", ex.getClass().getSimpleName() + " - " + ex.getLocalizedMessage(), id); } if(ex instanceof IllegalStringException) { m.sendHelpListElement(cs, "§cUngültiger Wert", ((IllegalStringException) ex).getBadString(), id); } KajetansMod.quest.term(this); } } public void runCode() { try { while(!isWaiting) { treeClock.pushTime(benchmark); try { //System.out.println("NEXT"); code.selectNextChild(); } catch(NoChildTreeException ex) { //System.out.println("NACH OBEN!"); code.goHigher(); setTryMode(false); setElseMode(false); if(removeWhileMode()) { increaseOverflowProtection(); code.selectPreviousChild(); } treeClock.pushTime(benchmark); continue; } currentCode = code.getCurrentChildData(); treeClock.pushTime(benchmark); //System.out.println(currentCode.getFunction()); //System.out.println(currentCode); //System.out.println("AUFRUF"); //findFunction(currentCode, 0, currentCode.length() - 1); doFunction(currentCode); } } catch(NoChildTreeException | GoHigherAtRootTreeException ex) { KajetansMod.quest.term(this); } catch(HoldCodeException ex2) { } catch(CodeTooLongException | PrescriptException ex3) { parser.printQuestException(this, ex3, currentCode.toString()); } } public void runCodeLine(String c, ICommandSender cs) { try { doFunction(new CodeFunction(c)); } catch(HoldCodeException ex) { } catch(Exception ex) { if(ex instanceof IllegalStringException) { KajetansMod.quest.send(cs, ((IllegalStringException) ex).getBadString(), script ? 1 : 0); } else { KajetansMod.quest.send(cs, ex.getClass().getSimpleName(), script ? 1 : 0); } } } private Object doFunction(CodeFunction cf) { Object[] old = cf.getParameters(); Object[] newPars = new Object[old.length]; for(int i = 0; i < old.length; i++) { if(old[i] != null && old[i].getClass() == CodeFunction.class) { newPars[i] = readParameter(doFunction((CodeFunction) old[i])); continue; } newPars[i] = readParameter(old[i]); } return parser.parseFunction(this, cf.getFunction(), newPars); } private Object readParameter(Object o) { if(o != null && o.getClass() == Variable.class) { return getVar(o.toString()); } return o; } public int getActualCodeLine() { return code.getActualCodeLine(); } // ------------------------------------------------------------------------- // Quest / Scriptdaten // ------------------------------------------------------------------------- public boolean isScript() { return script; } public int getId() { return id; } public String getInfo() { return info; } public void setInfo(String t) { info = t; } public String getName() { return name; } // ------------------------------------------------------------------------- // Wait für Zeitutils // ------------------------------------------------------------------------- public boolean isWaiting() { return isWaiting; } public void setIsWaiting(boolean b) { isWaiting = b; } // ------------------------------------------------------------------------- // Valid // ------------------------------------------------------------------------- public boolean isValid() { return isValid; } public void setIsValid(Boolean bool) { isValid = bool; } // ------------------------------------------------------------------------- // Variablen // ------------------------------------------------------------------------- public void setVar(String var, Object value) { variables.put(var, value); } public void setVar(Object var, Object value) { setVar(var.toString(), value); } public HashMap getVars() { return variables; } public Object getVar(String var) { return variables.get(var); } public Object getVar(Object var) { return getVar(var.toString()); } public boolean getBoolean(String var) { try { return (boolean) getVar(var); } catch(ClassCastException | NullPointerException ex) { return false; } } public void removeVar(String var) { variables.remove(var); } // ------------------------------------------------------------------------- // Goto // ------------------------------------------------------------------------- public void gotoLabel(String label) throws NoChildTreeException, CodeTooLongException { Integer[] i = gotos.get(label); if(i == null) { throw new GotoLabelNotFoundException(label); } increaseOverflowProtection(); resetWhileMode(); setTryMode(false); code.goToPosition(i); } public void gotoLabelWithReturn(String label) throws NoChildTreeException, CodeTooLongException { stack.push(code.getCurrentPosition()); gotoLabel(label); } public void doReturn() throws NoChildTreeException { code.goToPosition(stack.pop()); /*try { code.selectNextChild(); } catch(NoChildTreeException ex) { code.goHigher(); }*/ } public void resetOverflowProtection() { overflowProtection = 0; } public void increaseOverflowProtection() throws CodeTooLongException { overflowProtection++; if(overflowProtection > 1000) { overflowProtection = 0; throw new CodeTooLongException(); } } // ------------------------------------------------------------------------- // Event Handling // ------------------------------------------------------------------------- public void loadEvent(String event) { events.add(event); } public void unloadEvent(String event) { events.remove(event); } public boolean isEventLoaded(String event) { return events.contains(event); } // ------------------------------------------------------------------------- // Player Handling // ------------------------------------------------------------------------- public void addPlayer(EntityPlayer p) { quester.add(p); } public boolean removePlayer(EntityPlayer p) { quester.remove(p); return quester.isEmpty(); } public List getPlayers() { return quester; } public List getPlayerNames() { return getPlayers().stream().map(p -> p.getName()).collect(Collectors.toList()); } // ------------------------------------------------------------------------- // Location Handling für Wait-For-Location // ------------------------------------------------------------------------- public void addLocation(Location l) { loadedLocations.add(l); } public boolean removeLocation(Location l) { return loadedLocations.remove(l); } public void clearLocations() { loadedLocations.clear(); } // ------------------------------------------------------------------------- // Try-Catch Handler // ------------------------------------------------------------------------- public void setTryMode(boolean b) { isTrying = b; } public boolean getTryMode() { return isTrying; } public void setTryFail(boolean b) { tryFailed = b; } public boolean getTryFail() { return tryFailed; } // ------------------------------------------------------------------------- // While Handler // ------------------------------------------------------------------------- private void addWhileMode(boolean b) { whileStack.push(b); } public boolean removeWhileMode() { try { return whileStack.pop(); } catch(EmptyStackException ex) { return false; } } public void resetWhileMode() { whileStack.clear(); } // ------------------------------------------------------------------------- // Else Handler // ------------------------------------------------------------------------- public void setElseMode(boolean b) { shouldDoElse = b; } public boolean getElseMode() { return shouldDoElse; } // ------------------------------------------------------------------------- // Für Inventar-IDs // ------------------------------------------------------------------------- public int getNewId() { idCounter++; return idCounter; } // ------------------------------------------------------------------------- // Für Benchmarks // ------------------------------------------------------------------------- public void clearBenchmark() { exeClock.clear(); treeClock.clear(); } public boolean isBenchmarking() { return benchmark; } public void toggleBenchmark() { benchmark = !benchmark; } public void printBenchmarks(ICommandSender cs) { cs.sendMessage(new TextComponentString(GlobalText.Spacer())); cs.sendMessage(new TextComponentString("Ausführung: " + exeClock.getSummedTime() + " ms")); cs.sendMessage(new TextComponentString("Baum: " + treeClock.getSummedTime() + " ms")); } }