diff --git a/.gitignore b/.gitignore index f69c48932..55922505c 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ ignore/ remappedSrc remappedSrc/ /libs +.antlr diff --git a/Library-of-Exile-Rework b/Library-of-Exile-Rework index 48853f017..440c4f2d3 160000 --- a/Library-of-Exile-Rework +++ b/Library-of-Exile-Rework @@ -1 +1 @@ -Subproject commit 48853f017d362ed3d3d4ca2e0cc335d975e1b8b9 +Subproject commit 440c4f2d39eb8d6a8882c3f4fcbd8e9b08d5ae79 diff --git a/build.gradle b/build.gradle index b17488b61..ee8b38607 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java-library' id 'net.neoforged.moddev.legacyforge' version "2.0.107" id "me.modmuss50.mod-publish-plugin" version "0.8.4" + id 'antlr' } //archivesBaseName = "${mod_archive_name}-${minecraft_version}" @@ -20,11 +21,19 @@ base { archivesName = "${mod_archive_name}-${minecraft_version}" } +sourceSets.main.java { + srcDir 'build/generated-src/antlr' +} + sourceSets.main.resources{ srcDir 'src/generated/resources' exclude '.cache/**' } +generateGrammarSource { + arguments += ["-visitor", "-package", "com.robertx22.mine_and_slash.antlr"] +} + legacyForge { version = "${minecraft_version}-${forge_version}" @@ -159,6 +168,8 @@ dependencies { modImplementation("com.robertx22:the_harvest:${project.the_harvest_version}") modImplementation("com.robertx22:dungeon_realm:${project.dungeon_realm_version}") //modImplementation("maven.modrinth:library-of-exile:${project.exile_library_version}") + + antlr "org.antlr:antlr4:4.5" // use ANTLR version 4 } diff --git a/src/main/antlr/com/robertx22/mine_and_slash/antlr/Spell.g4 b/src/main/antlr/com/robertx22/mine_and_slash/antlr/Spell.g4 new file mode 100644 index 000000000..6b637ba43 --- /dev/null +++ b/src/main/antlr/com/robertx22/mine_and_slash/antlr/Spell.g4 @@ -0,0 +1,88 @@ +grammar Spell; + +/* + * Parser + */ + +spell: statement+; + +statement: propertyStatement | attachedStatement; + +// json property values +propertyBlock: '{' propertyStatement* '}' | propertyStatement; +propertyStatement: assignment | subobject; + +assignment: propertyName '=' propertyValue ';'; +propertyName: Identifier; +propertyValue: literal | literalArrayValue | objectArrayValue; +literalArrayValue: '[' (literal (',' literal)* ','?)? ']'; +objectArrayValue: '[' (arrayElement (',' arrayElement)* ','?)? ']'; +arrayElement: '{' propertyStatement* '}'; + +subobject: subobjectName propertyBlock; +subobjectName: Identifier; + +// spell attached action blocks +attachedStatement: eventHandler | entity; +eventHandler: eventName scriptBlock; +eventName: OnCast | OnTick | OnCastEnd; +entity: 'entity' entityName scriptBlock; +entityName: Identifier; + +// spell action syntax +scriptBlock: '{' scriptStatement* '}' | scriptStatement; +scriptStatement: ifBlock | selectBlock | actions; +ifBlock: 'if' '(' conditionExpr ')' scriptBlock elseBlock?; +elseBlock: 'else' scriptBlock; +selectBlock: 'select' '(' selectorList ')' scriptBlock; +actions: mapHolder+ (';' | perEntityHit); // spell action list +perEntityHit: 'per_entity_hit' scriptBlock; + +selectorList: selector ('||' selector)*; +selector: mapHolder; // spell targets entry + +enPredPrefix: 'en' '.'; +condition: enPredPrefix? mapHolder; // single condition +conditionParen: '(' conditionExpr ')' | condition; +conditionNot: Not conditionParen | conditionParen; +conditionAnd: conditionNot ('&&' conditionNot)*; +conditionExpr: conditionAnd; + +// corresponds to MapHolder with type and map +mapHolder: mapHolderType '(' mapHolderArguments? ')'; +mapHolderType: Identifier; +mapHolderArguments: argumentPositional (',' argumentPositional)* (',' argumentKeyValue)* + | argumentKeyValue (',' argumentKeyValue)*; +argumentPositional: literal; +argumentKeyValue: argumentKey '=' argumentValue; +argumentKey: Identifier; +argumentValue: literal; + +literal: String | Double | Int | bool; +bool: True | False; + +/* + * Lexer + */ + +Not: '!'; + +OnCast: 'on_cast'; +OnTick: 'on_tick'; +OnCastEnd: 'on_cast_end'; + +True: 'true'; +False: 'false'; + +Double: Number? '.' Number; +Int: Number; +fragment Number: '0'..'9'+; + +String: '"' ~[\r\n"]* '"'; + +Identifier: ('a'..'z' | 'A'..'Z' | '0'..'9' | [_$])+; + +Comment: '//' ~[\r\n]* [\r]? [\n] -> skip; +BlockComment: '/*' .*? '*/' -> skip; + +WS: [ \t\r\n]+ -> skip; \ No newline at end of file diff --git a/src/main/java/com/robertx22/mine_and_slash/database/data/StatMod.java b/src/main/java/com/robertx22/mine_and_slash/database/data/StatMod.java index c10a66cd2..6fca86076 100644 --- a/src/main/java/com/robertx22/mine_and_slash/database/data/StatMod.java +++ b/src/main/java/com/robertx22/mine_and_slash/database/data/StatMod.java @@ -33,7 +33,7 @@ public class StatMod implements ISerializable { public static StatMod EMPTY = new StatMod(); - private StatMod() { + public StatMod() { } diff --git a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/SpellCompiler.java b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/SpellCompiler.java new file mode 100644 index 000000000..a59ea6fd0 --- /dev/null +++ b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/SpellCompiler.java @@ -0,0 +1,645 @@ +package com.robertx22.mine_and_slash.database.data.spells; + +import java.io.BufferedReader; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CodePointCharStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.tree.ParseTree; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.robertx22.library_of_exile.main.ExileLog; +import com.robertx22.library_of_exile.registry.ExileRegistryType; +import com.robertx22.library_of_exile.registry.JsonExileRegistry; +import com.robertx22.mine_and_slash.antlr.SpellLexer; +import com.robertx22.mine_and_slash.antlr.SpellParser; +import com.robertx22.mine_and_slash.antlr.SpellParser.*; +import com.robertx22.mine_and_slash.database.data.spells.components.BaseFieldNeeder; +import com.robertx22.mine_and_slash.database.data.spells.components.ComponentPart; +import com.robertx22.mine_and_slash.database.data.spells.components.MapHolder; +import com.robertx22.mine_and_slash.database.data.spells.components.Spell; +import com.robertx22.mine_and_slash.database.data.spells.components.actions.SpellAction; +import com.robertx22.mine_and_slash.database.data.spells.components.conditions.EffectCondition; +import com.robertx22.mine_and_slash.database.data.spells.components.selectors.BaseTargetSelector; +import com.robertx22.mine_and_slash.database.data.spells.map_fields.MapField; +import com.robertx22.mine_and_slash.database.registry.ExileDB; + +import net.minecraft.resources.FileToIdConverter; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; + +public class SpellCompiler { + private static enum MapHolderType { + ACTION(SpellAction.MAP), + CONDITION(EffectCondition.MAP), + SELECTOR(BaseTargetSelector.MAP); + + private final HashMap map; + + private MapHolderType(HashMap map) { + this.map = map; + } + } + + private static abstract class CompilerError extends Exception { + public int startLine; + public int startChar; + public int endLine; + public int endChar; + + public CompilerError(ParserRuleContext context, String details) { + super(details); + startLine = context.start.getLine(); + startChar = context.start.getCharPositionInLine(); + endLine = context.stop.getLine(); + endChar = context.stop.getCharPositionInLine(); + } + @Override + public String toString() { + return String.format("%d,%d: %s: %s", startLine, startChar, errorType(), getMessage()); + } + public abstract String errorType(); + } + + private static class PropertyNameError extends CompilerError { + Object accessObject; + public PropertyNameError(ParserRuleContext context, Object accessObject, String details) { + super(context, details); + this.accessObject = accessObject; + } + @Override public String errorType() { return String.format("No such property in %s", accessObject.getClass().getSimpleName()); } + } + + private static class PropertyTypeError extends CompilerError { + Object accessObject; + public PropertyTypeError(ParserRuleContext context, Object accessObject, String details) { + super(context, details); + this.accessObject = accessObject; + } + @Override public String errorType() { return String.format("Accessed property as wrong type in %s", accessObject.getClass().getSimpleName()); } + } + + private static class PropertyEnumError extends CompilerError { + Object accessObject; + public PropertyEnumError(ParserRuleContext context, Object accessObject, String details) { + super(context, details); + this.accessObject = accessObject; + } + @Override public String errorType() { return String.format("Passed invalid value for enum in %s", accessObject.getClass().getSimpleName()); } + } + + private static class MapHolderNameError extends CompilerError { + String type; + public MapHolderNameError(ParserRuleContext context, String name, MapHolderType type) { + super(context, name); + this.type = type.toString().toLowerCase(); + } + @Override public String errorType() { return String.format("No such %s", type); } + } + + private static class SyntaxError extends CompilerError { + public SyntaxError(ParserRuleContext context, String details) { super(context, details); } + @Override public String errorType() { return "Syntax error"; } + } + + private static class ValueWrapper { + Object value; + }; + + private static class ConditionHolder { + private static enum Type { + IF, + EN_PRED + }; + MapHolder map; + Type type; + }; + + public static void compileSpells(ExileRegistryType type, ResourceManager manager, String name, Gson gson, Map output) { + var converter = new FileToIdConverter(name, ".spell"); + + ExileLog.get().log("Parsing spell sources"); + + converter.listMatchingResources(manager).forEach((location, resource) -> { + var fileId = converter.fileToId(location); + + ExileLog.get().log("Parsing spell source {}", location.getPath()); + + BufferedReader reader; + CodePointCharStream charStream; + + try { + reader = resource.openAsReader(); + charStream = CharStreams.fromReader(reader); + } catch (IOException exception) { + ExileLog.get().warn("IOException while reading " + location.toString() + "\n" + exception.toString()); + JsonExileRegistry.addToErroredJsons(type, fileId); + return; + } + + var compiler = new SpellCompiler(charStream, fileId); + JsonElement spellJson; + + try { + spellJson = compiler.compile(); + } catch (CompilerError error) { + ExileLog.get().warn("{}:{}", location.toString(), error.toString()); + JsonExileRegistry.addToErroredJsons(type, fileId); + return; + } finally { + try { + reader.close(); + } catch (IOException exception) { + ExileLog.get().warn("{}: IOException while closing\n{}", location.toString(), exception.toString()); + JsonExileRegistry.addToErroredJsons(type, fileId); + return; + } + } + + output.put(fileId, spellJson); + }); + } + + private interface IfNotNullFunction { + void accept(T node) throws CompilerError; + } + + private interface ElseFunction { + void run() throws CompilerError; + } + + private static abstract class ElseIfNotNull { + public abstract ElseIfNotNull elseIfNotNull(T node, IfNotNullFunction consumer) throws CompilerError; + public abstract void elseDo(ElseFunction runnable) throws CompilerError; + } + + private static class TrueElseIfNotNull extends ElseIfNotNull { + @Override + public ElseIfNotNull elseIfNotNull(T node, IfNotNullFunction consumer) throws CompilerError { + return new TrueElseIfNotNull(); + } + @Override + public void elseDo(ElseFunction runnable) throws CompilerError { + } + } + + private static class FalseElseIfNotNull extends ElseIfNotNull { + @Override + public ElseIfNotNull elseIfNotNull(T node, IfNotNullFunction consumer) throws CompilerError { + return ifNotNull(node, consumer); + } + @Override + public void elseDo(ElseFunction runnable) throws CompilerError { + runnable.run(); + } + } + + private static ElseIfNotNull ifNotNull(T node, IfNotNullFunction consumer) throws CompilerError { + if (node != null) { + consumer.accept(node); + return new TrueElseIfNotNull(); + } else { + return new FalseElseIfNotNull(); + } + } + + private SpellLexer lexer; + private TokenStream tokenStream; + private SpellParser parser; + private ResourceLocation fileId; + private Spell spell; + + // each nested json subobject we're accessing + private Deque objectAccessStack = new ArrayDeque<>(); + + // nested conditional blocks + private Deque> conditionStack = new ArrayDeque<>(); + + // nested select blocks + private Deque> selectorStack = new ArrayDeque<>(); + + private SpellCompiler(CodePointCharStream charStream, ResourceLocation fileId) { + this.lexer = new SpellLexer(charStream); + this.tokenStream = new CommonTokenStream(lexer); + this.parser = new SpellParser(tokenStream); + this.fileId = fileId; + } + + private JsonElement compile() throws CompilerError { + spell = new Spell(); + spell.identifier = fileId.getPath(); + objectAccessStack.push(spell); + + var root = parser.spell(); + + for (var statement : root.statement()) { + ifNotNull(statement.propertyStatement(), propertyStatement -> { + handlePropertyStatement(propertyStatement); + }).elseIfNotNull(statement.attachedStatement(), attachedStatement -> { + handleAttachedStatement(attachedStatement); + }); + } + + return spell.toJson(); + } + + private void handlePropertyBlock(PropertyBlockContext node) throws CompilerError { + for (var statement : node.propertyStatement()) { + handlePropertyStatement(statement); + } + } + + private void handlePropertyStatement(PropertyStatementContext node) throws CompilerError { + ifNotNull(node.subobject(), subobject -> { + handleSubobject(subobject); + }).elseIfNotNull(node.assignment(), assignment -> { + handleAssignment(assignment); + }); + } + + private void handleAssignment(AssignmentContext node) throws CompilerError { + var propertyName = node.propertyName().getText(); + + ifNotNull(node.propertyValue().literalArrayValue(), literalArrayValue -> { + // handle arrays of primitives + var elements = new ArrayList(); + for (var literal : literalArrayValue.literal()) { + elements.add(handleLiteral(literal)); + } + writeTopObjectFieldCollection(node, propertyName, elements); + }).elseIfNotNull(node.propertyValue().objectArrayValue(), objectArrayValue -> { + // handle arrays of objects + writeTopObjectFieldObjectCollection(node, propertyName, elementConstructor -> { + var elements = new ArrayList(); + + for (var elementBlock : objectArrayValue.arrayElement()) { + // descend into each array element + var element = elementConstructor.newInstance(); + objectAccessStack.push(element); + for (var statement : elementBlock.propertyStatement()) { + handlePropertyStatement(statement); + } + objectAccessStack.pop(); + elements.add(element); + } + + return elements; + }); + }).elseDo(() -> { + // handle primitive values + var propertyValue = handleLiteral(node.propertyValue().literal()); + writeTopObjectField(node, propertyName, propertyValue); + }); + } + + private Object handleLiteral(LiteralContext node) throws CompilerError { + var value = new ValueWrapper(); + + ifNotNull(node.String(), asString -> { + var text = asString.getText(); // includes quotes + value.value = text.substring(1, text.length() - 1); + }).elseIfNotNull(node.Double(), asDouble -> { + value.value = Double.parseDouble(asDouble.getText()); + }).elseIfNotNull(node.Int(), asInt -> { + value.value = Integer.parseInt(asInt.getText()); + }).elseIfNotNull(node.bool(), asBool -> { + value.value = asBool.True() != null; + }); + + return value.value; + } + + private void handleSubobject(SubobjectContext node) throws CompilerError { + var name = node.subobjectName().getText(); + objectAccessStack.push(readTopObjectField(node, name)); + handlePropertyBlock(node.propertyBlock()); + objectAccessStack.pop(); + } + + private void handleAttachedStatement(AttachedStatementContext node) throws CompilerError { + ifNotNull(node.eventHandler(), (eventHandler) -> { + handleEventHandler(eventHandler); + }).elseDo(() -> { + handleEntity(node.entity()); + }); + } + + private void handleEventHandler(EventHandlerContext node) throws CompilerError { + var parts = handleScriptBlock(node.scriptBlock()); + + if (node.eventName().OnCast() != null) { + spell.attached.on_cast.addAll(parts); + } else if (node.eventName().OnTick() != null) { + //spell.attached.on_tick.addAll(parts); + } else if (node.eventName().OnCastEnd() != null) { + //spell.attached.on_cast_end.addAll(parts); + } + } + + private void handleEntity(EntityContext node) throws CompilerError { + var parts = handleScriptBlock(node.scriptBlock()); + var name = node.entityName().toString(); + spell.attached.entity_components.put(name, parts); + } + + // returns list of spell parts + private List handleScriptBlock(ScriptBlockContext node) throws CompilerError { + var parts = new ArrayList(); + + for (var statement : node.scriptStatement()) { + ifNotNull(statement.ifBlock(), ifBlock -> { + parts.addAll(handleIfBlock(ifBlock)); + }).elseIfNotNull(statement.selectBlock(), selectBlock -> { + parts.addAll(handleSelectBlock(selectBlock)); + }).elseIfNotNull(statement.actions(), actions -> { + parts.add(handleActions(actions)); + }); + } + + return parts; + } + + // action list / spell part + private ComponentPart handleActions(ActionsContext node) throws CompilerError { + var part = new ComponentPart(); + + for (var action : node.mapHolder()) { + part.acts.add(handleAction(action)); + } + + for (var conditions : conditionStack) { + for (var condition : conditions) { + switch (condition.type) { + case IF: + part.ifs.add(condition.map); + break; + case EN_PRED: + part.en_preds.add(condition.map); + break; + } + } + } + + selectorStack.forEach(conditions -> part.targets.addAll(conditions)); + + ifNotNull(node.perEntityHit(), perEntityHit -> { + // don't propagate conditions and selectors to per_entity_hit block + var oldConditionStack = conditionStack; + var oldSelectorStack = selectorStack; + conditionStack = new ArrayDeque(); + selectorStack = new ArrayDeque(); + part.per_entity_hit = handleScriptBlock(perEntityHit.scriptBlock()); + conditionStack = oldConditionStack; + selectorStack = oldSelectorStack; + }); + + return part; + } + + // returns list of spell parts + private List handleIfBlock(IfBlockContext node) throws CompilerError { + var conditions = handleConditionExpr(node.conditionExpr()); + conditionStack.push(conditions); + + var parts = handleScriptBlock(node.scriptBlock()); + + ifNotNull(node.elseBlock(), elseBlock -> { + invertConditions(conditions); + parts.addAll(handleScriptBlock(elseBlock.scriptBlock())); + }); + + conditionStack.pop(); + return parts; + } + + // returns list of conditions + private List handleConditionExpr(ConditionExprContext node) throws CompilerError { + return handleConditionAnd(node.conditionAnd()); + } + + // returns list of conditions + private List handleConditionAnd(ConditionAndContext node) throws CompilerError { + var conditions = new ArrayList(); + for (var child : node.conditionNot()) { + conditions.addAll(handleConditionNot(child)); + } + return conditions; + } + + // returns list of conditions + private List handleConditionNot(ConditionNotContext node) throws CompilerError { + var conditions = handleConditionParen(node.conditionParen()); + if (node.Not() != null) { + invertConditions(conditions); + } + return conditions; + } + + private void invertConditions(List conditions) { + for (var condition : conditions) { + if (condition.map.has(MapField.IS_FALSE) && condition.map.get(MapField.IS_FALSE)) { + condition.map.remove(MapField.IS_FALSE); + } else { + condition.map.put(MapField.IS_FALSE, true); + } + } + } + + // returns list of conditions + private List handleConditionParen(ConditionParenContext node) throws CompilerError { + if (node.condition() != null) { + return List.of(handleCondition(node.condition())); + } else { + return handleConditionExpr(node.conditionExpr()); + } + } + + // returns list of spell parts + private List handleSelectBlock(SelectBlockContext node) throws CompilerError { + var selectors = new ArrayList(); + + for (var selector : node.selectorList().selector()) { + selectors.add(handleSelector(selector)); + } + + selectorStack.push(selectors); + var parts = handleScriptBlock(node.scriptBlock()); + selectorStack.pop(); + + return parts; + } + + // returns single action + private MapHolder handleAction(MapHolderContext node) throws CompilerError { + return handleMapHolder(node, MapHolderType.ACTION); + } + + // returns single condition + private ConditionHolder handleCondition(ConditionContext node) throws CompilerError { + var condition = new ConditionHolder(); + condition.map = handleMapHolder(node.mapHolder(), MapHolderType.CONDITION); + condition.type = node.enPredPrefix() != null ? ConditionHolder.Type.EN_PRED : ConditionHolder.Type.IF; + return condition; + } + + // returns single selector + private MapHolder handleSelector(SelectorContext node) throws CompilerError { + return handleMapHolder(node.mapHolder(), MapHolderType.SELECTOR); + } + + private MapHolder handleMapHolder(MapHolderContext node, MapHolderType type) throws CompilerError { + var map = new MapHolder(); + map.type = node.mapHolderType().getText(); + + if (!type.map.containsKey(map.type)) { + throw new MapHolderNameError(node, map.type, type); + } + + var args = node.mapHolderArguments(); + var typeObject = (BaseFieldNeeder) type.map.get(map.type); + var requiredArgCount = typeObject.requiredPieces.size(); + var positionalArgCount = args != null ? args.argumentPositional().size() : 0; + + if (positionalArgCount > requiredArgCount) { + throw new SyntaxError( + node, String.format("Expected at most %d positional arguments to '%s', got %d", + requiredArgCount, map.type, positionalArgCount)); + } + + if (args != null) { + for (var i = 0; i < positionalArgCount; i++) { + var positionalArg = args.argumentPositional(i); + var field = typeObject.requiredPieces.get(i); + var value = handleLiteral(positionalArg.literal()); + addToMapHolder(map, field, value); + } + + for (var keywordArg : args.argumentKeyValue()) { + var fieldName = keywordArg.argumentKey().getText(); + + if (!MapField.MAP.containsKey(fieldName)) { + throw new SyntaxError(node, String.format("Unknown argument name '%s'", fieldName)); + } + + var field = MapField.MAP.get(fieldName); + var value = handleLiteral(keywordArg.argumentValue().literal()); + addToMapHolder(map, field, value); + } + } + + for (var requiredField : typeObject.requiredPieces) { + if (!map.has(requiredField)) { + throw new SyntaxError(node, String.format("Missing required field '%s'", requiredField.GUID())); + } + } + + return map; + } + + private void addToMapHolder(MapHolder map, MapField field, Object value) { + if (field == MapField.VALUE_CALCULATION && value instanceof String valueCalcName) { + map.put(field, ExileDB.ValueCalculations().get(valueCalcName)); + } else { + map.put(field, value); + } + } + + private Object getTopObject() { + return objectAccessStack.getFirst(); + } + + private Field getObjectField(ParserRuleContext node, Object object, String name) throws CompilerError { + Field field; + try { + field = object.getClass().getDeclaredField(name); + } catch (NoSuchFieldException e) { + throw new PropertyNameError(node, object, e.getMessage()); + } + + field.setAccessible(true); + return field; + } + + private Object readTopObjectField(ParserRuleContext context, String name) throws CompilerError { + var topObject = getTopObject(); + var field = getObjectField(context, topObject, name); + try { + return field.get(topObject); + } catch (IllegalAccessException e) { + throw new PropertyTypeError(context, topObject, e.getMessage()); + } + } + + private void writeTopObjectField(ParserRuleContext context, String name, Object value) throws CompilerError { + var topObject = getTopObject(); + var field = getObjectField(context, topObject, name); + var fieldType = field.getType(); + + if (fieldType.equals(float.class) && value instanceof Double doubleValue) { + // allow assigning double literals to float fields + value = (float) (double) doubleValue; + } else if (fieldType.isEnum() && value instanceof String enumValue) { + // allow assigning string literals to enum fields + try { + value = Enum.valueOf((Class) fieldType, enumValue); + } catch (Exception e) { + throw new PropertyEnumError(context, topObject, e.getMessage()); + } + } + + try { + field.set(topObject, value); + } catch (Exception e) { + throw new PropertyTypeError(context, topObject, e.getMessage()); + } + } + + private void writeTopObjectFieldCollection(ParserRuleContext context, String name, Collection value) throws CompilerError { + var topObject = getTopObject(); + var field = getObjectField(context, topObject, name); + try { + var collection = (Collection) field.get(topObject); + collection.clear(); + collection.addAll(value); + } catch (Exception e) { + throw new PropertyTypeError(context, topObject, e.getMessage()); + } + } + + private interface CollectionProducerFunction { + Collection produce(Constructor elementConstructor) throws Exception; + } + + // for writing to lists of objects, allows default constructing elements + private void writeTopObjectFieldObjectCollection(ParserRuleContext context, String name, CollectionProducerFunction producer) throws CompilerError { + var topObject = getTopObject(); + var field = getObjectField(context, topObject, name); + try { + var fieldGenericType = (ParameterizedType) field.getGenericType(); + var elementType = (Class) fieldGenericType.getActualTypeArguments()[0]; + var constructor = elementType.getDeclaredConstructor(); + constructor.setAccessible(true); + var collection = (Collection) field.get(topObject); + collection.clear(); + collection.addAll(producer.produce(elementType.getConstructor())); + } catch (CompilerError e) { + throw e; + } catch (Exception e) { + throw new PropertyTypeError(context, topObject, e.getMessage()); + } + } +} diff --git a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/BaseFieldNeeder.java b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/BaseFieldNeeder.java index 6e46f0c69..569bc9b3a 100644 --- a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/BaseFieldNeeder.java +++ b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/BaseFieldNeeder.java @@ -8,7 +8,7 @@ public abstract class BaseFieldNeeder implements IGUID { - List requiredPieces; + public List requiredPieces; public BaseFieldNeeder(List requiredPieces) { this.requiredPieces = requiredPieces; diff --git a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/ComponentPart.java b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/ComponentPart.java index a882c79be..c9fd99629 100644 --- a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/ComponentPart.java +++ b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/ComponentPart.java @@ -18,7 +18,7 @@ public class ComponentPart { public List ifs = new ArrayList<>(); public List en_preds = new ArrayList<>(); - List per_entity_hit = null; + public List per_entity_hit = null; public ComponentPart addPerEntityHit(ComponentPart add) { @@ -72,7 +72,7 @@ public ComponentPart addActivationRequirement(EntityActivation act) { } else { throw new NullPointerException("Activation not found"); // ... why did i do it like this } - + return this; } diff --git a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/MapHolder.java b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/MapHolder.java index 76f3bcf3c..c40ccb6ad 100644 --- a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/MapHolder.java +++ b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/MapHolder.java @@ -62,6 +62,10 @@ public T get(MapField field) { return (T) map.get(field.GUID()); } + public void remove(MapField field) { + this.map.remove(field); + } + public ExileEffect getExileEffect() { return ExileDB.ExileEffects().get(get(EXILE_POTION_ID)); } diff --git a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/actions/vanity/ParticleInRadiusAction.java b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/actions/vanity/ParticleInRadiusAction.java index 5cb00e0cc..6851c2518 100644 --- a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/actions/vanity/ParticleInRadiusAction.java +++ b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/actions/vanity/ParticleInRadiusAction.java @@ -41,14 +41,16 @@ public void tryActivate(Collection targets, SpellCtx ctx, MapHolde int amount = data.get(PARTICLE_COUNT).intValue(); amount *= ctx.calculatedSpellData.data.getNumber(EventData.AREA_MULTI, 1F).number; - - ParticleMotion motion = null; - - try { - motion = ParticleMotion.valueOf(data.get(MOTION)); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - motion = ParticleMotion.None; + + ParticleMotion motion = ParticleMotion.None; + + if (data.has(MOTION)) { + try { + motion = ParticleMotion.valueOf(data.get(MOTION)); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + motion = ParticleMotion.None; + } } float yrand = data.getOrDefault(Y_RANDOM, 0D).floatValue(); diff --git a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/actions/vanity/SoundAction.java b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/actions/vanity/SoundAction.java index 8bdc3fe00..8ba41d32a 100644 --- a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/actions/vanity/SoundAction.java +++ b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/actions/vanity/SoundAction.java @@ -16,14 +16,14 @@ public class SoundAction extends SpellAction { public SoundAction() { - super(Arrays.asList(SOUND, PITCH, VOLUME)); + super(Arrays.asList(SOUND, VOLUME)); } @Override public void tryActivate(Collection targets, SpellCtx ctx, MapHolder data) { if (!ctx.world.isClientSide) { try { - float pitch = data.get(PITCH).floatValue(); + float pitch = data.getOrDefault(PITCH, 1.0).floatValue(); float volume = data.get(VOLUME).floatValue(); SoundEvent sound = data.getSound(); diff --git a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/selectors/AoeSelector.java b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/selectors/AoeSelector.java index 84c7dc43e..167f6f77f 100644 --- a/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/selectors/AoeSelector.java +++ b/src/main/java/com/robertx22/mine_and_slash/database/data/spells/components/selectors/AoeSelector.java @@ -22,7 +22,7 @@ public class AoeSelector extends BaseTargetSelector { public AoeSelector() { - super(Arrays.asList(RADIUS, SELECTION_TYPE, ENTITY_PREDICATE)); + super(Arrays.asList(RADIUS, ENTITY_PREDICATE)); } @Override diff --git a/src/main/java/com/robertx22/mine_and_slash/database/registry/ExileRegistryTypes.java b/src/main/java/com/robertx22/mine_and_slash/database/registry/ExileRegistryTypes.java index a3604fe30..a89d0c36c 100644 --- a/src/main/java/com/robertx22/mine_and_slash/database/registry/ExileRegistryTypes.java +++ b/src/main/java/com/robertx22/mine_and_slash/database/registry/ExileRegistryTypes.java @@ -1,5 +1,9 @@ package com.robertx22.mine_and_slash.database.registry; +import java.util.Map; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.robertx22.addons.orbs_of_crafting.currency.reworked.addon.ExtendedOrb; import com.robertx22.library_of_exile.registry.ExileRegistryType; import com.robertx22.library_of_exile.registry.SyncTime; @@ -30,6 +34,7 @@ import com.robertx22.mine_and_slash.database.data.runes.Rune; import com.robertx22.mine_and_slash.database.data.runewords.RuneWord; import com.robertx22.mine_and_slash.database.data.spell_school.SpellSchool; +import com.robertx22.mine_and_slash.database.data.spells.SpellCompiler; import com.robertx22.mine_and_slash.database.data.spells.components.Spell; import com.robertx22.mine_and_slash.database.data.stat_compat.StatCompat; import com.robertx22.mine_and_slash.database.data.stats.datapacks.base.BaseDatapackStat; @@ -44,6 +49,8 @@ import com.robertx22.mine_and_slash.uncommon.effectdatas.rework.condition.StatCondition; import com.robertx22.mine_and_slash.uncommon.enumclasses.WeaponTypes; import net.minecraft.ChatFormatting; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; public class ExileRegistryTypes { @@ -70,7 +77,14 @@ public class ExileRegistryTypes { public static ExileRegistryType RUNEWORDS = ExileRegistryType.register(SlashRef.MODID, "runeword", 12, RuneWord.SERIALIZER, SyncTime.ON_LOGIN); public static ExileRegistryType DIMENSION_CONFIGS = ExileRegistryType.register(SlashRef.MODID, "dimension", 13, DimensionConfig.EMPTY, SyncTime.ON_LOGIN); public static ExileRegistryType ENTITY_CONFIGS = ExileRegistryType.register(SlashRef.MODID, "entity", 14, Serializers.ENTITY_CONFIG_SER, SyncTime.NEVER); - public static ExileRegistryType SPELL = ExileRegistryType.register(SlashRef.MODID, "spells", 17, Spell.SERIALIZER, SyncTime.ON_LOGIN); + + public static ExileRegistryType SPELL = ExileRegistryType.register(new ExileRegistryType(SlashRef.MODID, "spells", 17, Spell.SERIALIZER, SyncTime.ON_LOGIN) { + @Override + public void injectResources(ResourceManager manager, String name, Gson gson, Map output) { + SpellCompiler.compileSpells(this, manager, name, gson, output); + } + }); + public static ExileRegistryType PERK = ExileRegistryType.register(SlashRef.MODID, "perk", 18, Perk.SERIALIZER, SyncTime.ON_LOGIN); public static ExileRegistryType TALENT_TREE = ExileRegistryType.register(SlashRef.MODID, "talent_tree", 19, TalentTree.SERIALIZER, SyncTime.ON_LOGIN); public static ExileRegistryType BASE_STATS = ExileRegistryType.register(SlashRef.MODID, "base_stats", 22, BaseStatsConfig.SERIALIZER, SyncTime.ON_LOGIN);