diff --git a/CHANGELOG.md b/CHANGELOG.md index ef498cb..53113e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,26 +2,6 @@ User Changes: -This update extends the expression syntax to support nbt and block properties. - -* Returned to downloading mappings at startup. -* Compound and List NBT tags are now properly supported. -* Added `nbt` field to item stacks, entities and block entities. - -This Allows you to access NBT data like so: `this_entity.nbt.Air`. You should avoid NBT access, as it can get extremely slow. - -* Added `properties` field to *states. - -This can be used to access block state properties like so: `block_state.properties.candles`. - -* Added lots of new functions. Consult the wiki for more info! -* Added `short_circuit` to `defaulted`, `all_of`, `any_of`. If true, commands will terminate immediately upon the condition failing. -* `hasContext` and `structContainsKey` now accept VarArgs. -* Removed arbitrary map support, as it was pretty poorly implemented. -* Constants are now case-sensitive. - -Dev Changes: - -* Moved Command codecs to MapCodec. -* Added BooleanExpression, similar to Arithmtica. -* Added `runTriState` to EventExecutors. \ No newline at end of file +* Fixed `arrayAllMatch` acting like `arrayAnyMatch`. +* Added `attributes` field to living entities. +* Loot Context access in expressions is now cached. \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index b769ada..ba3339f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.gradle.caching=true minecraft_version=1.20.4 yarn_mappings=1.20.4+build.3 # Mod Properties -mod_version=0.4.0 +mod_version=0.4.1 maven_group=me.melontini archives_base_name=commander diff --git a/src/main/java/me/melontini/commander/impl/expression/EvalUtils.java b/src/main/java/me/melontini/commander/impl/expression/EvalUtils.java index 6758b38..231ff50 100644 --- a/src/main/java/me/melontini/commander/impl/expression/EvalUtils.java +++ b/src/main/java/me/melontini/commander/impl/expression/EvalUtils.java @@ -12,9 +12,10 @@ import com.google.common.base.CaseFormat; import com.google.common.collect.ImmutableMap; import com.mojang.serialization.DataResult; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import lombok.SneakyThrows; import me.melontini.commander.impl.event.data.types.ExtractionTypes; +import me.melontini.commander.impl.expression.extensions.ProxyMap; import me.melontini.commander.impl.expression.extensions.ReflectiveValueConverter; import me.melontini.commander.impl.expression.functions.*; import me.melontini.commander.impl.expression.functions.arrays.*; @@ -30,10 +31,10 @@ import org.jetbrains.annotations.Nullable; import java.math.BigDecimal; -import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; public class EvalUtils { @@ -51,7 +52,7 @@ public class EvalUtils { .singleQuoteStringLiteralsAllowed(true); var fd = ExpressionConfiguration.defaultConfiguration().getFunctionDictionary(); - Map functions = new HashMap<>(((MapBasedFunctionDictionaryAccessor) fd) + Map functions = new Object2ReferenceOpenHashMap<>(((MapBasedFunctionDictionaryAccessor) fd) .commander$getFunctions().entrySet().stream() .collect(Collectors.toMap(e -> CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, e.getKey()), Map.Entry::getValue))); functions.put("random", new RangedRandomFunction()); @@ -109,7 +110,7 @@ public static EvaluationValue evaluate(LootContext context, Expression exp) { public static DataResult parseExpression(String expression) { try { Expression exp = new Expression(expression, CONFIGURATION); - ((ExpressionAccessor) exp).commander$constants(new HashMap<>(CONFIGURATION.getDefaultConstants())); + ((ExpressionAccessor) exp).commander$constants(new Object2ReferenceOpenHashMap<>(CONFIGURATION.getDefaultConstants())); exp.validate(); return DataResult.success(exp); } catch (Throwable throwable) { @@ -119,44 +120,60 @@ public static DataResult parseExpression(String expression) { public static class LootContextDataAccessor implements DataAccessorIfc { - private static final Map> overrides = ImmutableMap.of( + private static final Map> overrides = new Object2ReferenceOpenHashMap<>(Map.of( new Identifier("level"), LootContext::getWorld, new Identifier("luck"), LootContext::getLuck - ); + )); public static final ThreadLocal LOCAL = new ThreadLocal<>(); - private final Map parameters = new HashMap<>(); + private final Map parameters = new Object2ReferenceOpenHashMap<>(); + //In most cases the expression is reused, so caching this helps us avoid some big overhead. + private final Map> varCache = new Object2ReferenceOpenHashMap<>(); @Override public @Nullable EvaluationValue getData(String variable) { - var localParam = parameters.get(variable); - if (localParam != null) return localParam; + var supplier = varCache.get(variable); + if (supplier != null) return supplier.get(); //Parameters are cached by setData, so this is fine. var r = Identifier.validate(variable); if (r.error().isPresent()) { - throw new CmdEvalException("%s - %s".formatted(variable, r.error().orElseThrow().message())); + throw new CmdEvalException("%s - no such variable or %s".formatted(variable, r.error().orElseThrow().message())); } var id = r.result().orElseThrow(); var func = overrides.get(id); - if (func != null) return CONFIGURATION.getEvaluationValueConverter().convertObject(func.apply(LOCAL.get()), CONFIGURATION); + if (func != null) { + supplier = () -> ProxyMap.convert(func.apply(LOCAL.get())); + varCache.put(variable, supplier); + return supplier.get(); + } var param = ExtractionTypes.getParameter(id); - if (param == null) throw new CmdEvalException("%s is not a registered loot context parameter!".formatted(id)); - - var object = LOCAL.get().get(param); - if (object == null) return null; - return CONFIGURATION.getEvaluationValueConverter().convertObject(object, CONFIGURATION); + if (param == null) + throw new CmdEvalException("%s is not a registered loot context parameter, variable or override!".formatted(id)); + supplier = () -> { + var object = LOCAL.get().get(param); + if (object == null) return null; + return ProxyMap.convert(object); + }; + varCache.put(variable, supplier); + return supplier.get(); } @Override public void setData(String variable, EvaluationValue value) { parameters.put(variable, value); + + if (value == null) { + varCache.remove(variable); + } else { + varCache.put(variable, () -> parameters.get(variable)); //We're already here, so might as well cache. + } } } public static class SimpleFunctionDictionary implements FunctionDictionaryIfc { - private final Map functions = new Object2ObjectOpenHashMap<>(); + private final Map functions = new Object2ReferenceOpenHashMap<>(); public static FunctionDictionaryIfc ofFunctions(Map functions) { FunctionDictionaryIfc dictionary = new SimpleFunctionDictionary(); diff --git a/src/main/java/me/melontini/commander/impl/expression/extensions/CustomFields.java b/src/main/java/me/melontini/commander/impl/expression/extensions/CustomFields.java index 690b8e8..3e098c6 100644 --- a/src/main/java/me/melontini/commander/impl/expression/extensions/CustomFields.java +++ b/src/main/java/me/melontini/commander/impl/expression/extensions/CustomFields.java @@ -1,8 +1,10 @@ package me.melontini.commander.impl.expression.extensions; +import me.melontini.commander.impl.expression.extensions.convert.attributes.EntityAttributesStruct; import me.melontini.commander.impl.expression.extensions.convert.states.StateStruct; import net.minecraft.block.entity.BlockEntity; import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.state.State; @@ -22,5 +24,6 @@ static void init() { ReflectiveMapStructure.addField(BlockEntity.class, "nbt", BlockEntity::createNbtWithIdentifyingData); ReflectiveMapStructure.addField(State.class, "properties", StateStruct::new); + ReflectiveMapStructure.addField(LivingEntity.class, "attributes", e -> new EntityAttributesStruct(e.getAttributes())); } } diff --git a/src/main/java/me/melontini/commander/impl/expression/extensions/ReflectiveMapStructure.java b/src/main/java/me/melontini/commander/impl/expression/extensions/ReflectiveMapStructure.java index 6a90465..3edacfd 100644 --- a/src/main/java/me/melontini/commander/impl/expression/extensions/ReflectiveMapStructure.java +++ b/src/main/java/me/melontini/commander/impl/expression/extensions/ReflectiveMapStructure.java @@ -105,10 +105,11 @@ public boolean containsKey(Object obj) { } private static @Nullable Tuple, Function> findFieldOrMethod(Class cls, String name) { + var keeper = Commander.get().mappingKeeper(); String mapped; Class target = cls; do { - if ((mapped = Commander.get().mappingKeeper().getFieldOrMethod(target, name)) != null) return findAccessor(target, mapped); + if ((mapped = keeper.getFieldOrMethod(target, name)) != null) return findAccessor(target, mapped); var targetItfs = target.getInterfaces(); if (targetItfs.length == 0) continue; @@ -116,7 +117,7 @@ public boolean containsKey(Object obj) { while (!interfaces.isEmpty()) { var itf = interfaces.poll(); - if ((mapped = Commander.get().mappingKeeper().getFieldOrMethod(itf, name)) != null) return findAccessor(itf, mapped); + if ((mapped = keeper.getFieldOrMethod(itf, name)) != null) return findAccessor(itf, mapped); if ((targetItfs = itf.getInterfaces()).length > 0) interfaces.addAll(List.of(targetItfs)); } } while ((target = target.getSuperclass()) != null); diff --git a/src/main/java/me/melontini/commander/impl/expression/extensions/ReflectiveValueConverter.java b/src/main/java/me/melontini/commander/impl/expression/extensions/ReflectiveValueConverter.java index 7fbea18..9d39d4d 100644 --- a/src/main/java/me/melontini/commander/impl/expression/extensions/ReflectiveValueConverter.java +++ b/src/main/java/me/melontini/commander/impl/expression/extensions/ReflectiveValueConverter.java @@ -11,7 +11,7 @@ public class ReflectiveValueConverter implements EvaluationValueConverterIfc { - static List converters = Arrays.asList( + private static final List converters = Arrays.asList( new NumberConverter(), new StringConverter(), new BooleanConverter(), diff --git a/src/main/java/me/melontini/commander/impl/expression/extensions/convert/attributes/EntityAttributesStruct.java b/src/main/java/me/melontini/commander/impl/expression/extensions/convert/attributes/EntityAttributesStruct.java new file mode 100644 index 0000000..03a4814 --- /dev/null +++ b/src/main/java/me/melontini/commander/impl/expression/extensions/convert/attributes/EntityAttributesStruct.java @@ -0,0 +1,37 @@ +package me.melontini.commander.impl.expression.extensions.convert.attributes; + +import com.ezylang.evalex.data.EvaluationValue; +import me.melontini.commander.impl.expression.extensions.ProxyMap; +import net.minecraft.entity.attribute.AttributeContainer; +import net.minecraft.registry.Registries; +import net.minecraft.util.Identifier; + +import java.math.BigDecimal; + +public class EntityAttributesStruct extends ProxyMap { + + private final AttributeContainer container; + + public EntityAttributesStruct(AttributeContainer container) { + this.container = container; + } + + @Override + public boolean containsKey(Object key) { + if (!(key instanceof String s)) return false; + var attr = Registries.ATTRIBUTE.get(new Identifier(s)); + if (attr == null) return false; + return container.hasAttribute(attr); + } + + @Override + public EvaluationValue get(Object key) { + if (!(key instanceof String s)) return EvaluationValue.nullValue(); + return EvaluationValue.numberValue(BigDecimal.valueOf(container.getValue(Registries.ATTRIBUTE.get(new Identifier(s))))); + } + + @Override + public String toString() { + return String.valueOf(container.toNbt()); + } +} diff --git a/src/main/java/me/melontini/commander/impl/expression/functions/arrays/ArrayAllMatch.java b/src/main/java/me/melontini/commander/impl/expression/functions/arrays/ArrayAllMatch.java index 94da381..63b5de0 100644 --- a/src/main/java/me/melontini/commander/impl/expression/functions/arrays/ArrayAllMatch.java +++ b/src/main/java/me/melontini/commander/impl/expression/functions/arrays/ArrayAllMatch.java @@ -22,6 +22,6 @@ public EvaluationValue evaluate(Expression expression, Token functionToken, Eval List array = par[0].getArrayValue(); ASTNode predicate = par[1].getExpressionNode(); - return array.stream().anyMatch(value -> runLambda(expression, value, predicate).getBooleanValue()) ? EvalUtils.TRUE : EvalUtils.FALSE; + return array.stream().allMatch(value -> runLambda(expression, value, predicate).getBooleanValue()) ? EvalUtils.TRUE : EvalUtils.FALSE; } }