diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index 3a31838d..ed44b98e 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -240,6 +240,11 @@ Returns mob's attack target or null if none or not applicable. Returns creature's home position or null if none or not applicable. +### `query(e, 'path')` + +Returns path of the entity if present, `null` otherwise. The path comprises of list of nodes, each is a list +of block value, node type, penalty, and a boolean indicated if the node has been visited. + ### `query(e, 'pose')` Returns a pose of an entity, one of the following options @@ -359,6 +364,18 @@ entities and blocks, blocks will take over the priority even if transparent or n Regardless of the options selected, the result could be `null` if nothing is in reach, entity, if look targets an entity, and block value if block is in reach. +### `query(e, 'brain', memory)` + +Retrieves brain memory for entity. Possible memory units highly depend on the game version. Brain is availalble +for villagers (1.15+) and Piglins (1.16+). If memory is not present or not available for the entity, `null` is returned. + +Available retrievable memories for 1.15: +* `home`, `job_site`, `meeting_point`, `secondary_job_site`, `mobs`, `visible_mobs`, `visible_villager_babies`, +`nearest_players`, `nearest_visible_player`, `walk_target`, `look_target`, `interaction_target`, +`breed_target`, `path`, `interactable_doors`, `opened_doors`, `nearest_bed`, `hurt_by`, `hurt_by_entity`, +`nearest_hostile`, `hiding_place`, `heard_bell_time`, `cant_reach_walk_target_since`, +`golem_last_seen_time`, `last_slept`, `last_woken`, `last_worked_at_poi` + ### `query(e, 'nbt', path?)` Returns full NBT of the entity. If path is specified, it fetches only the portion of the NBT that corresponds to the diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 5dbdfc4a..18b363ca 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -13,6 +13,8 @@ import carpet.script.argument.Vector3Argument; import carpet.script.exception.InternalExpressionException; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.entity.ai.brain.MemoryModuleType; +import net.minecraft.entity.ai.pathing.Path; import net.minecraft.nbt.StringTag; import net.minecraft.nbt.Tag; import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket; @@ -60,6 +62,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -200,7 +203,14 @@ public class EntityValue extends Value { if (!(featureAccessors.containsKey(what))) throw new InternalExpressionException("Unknown entity feature: "+what); - return featureAccessors.get(what).apply(entity, arg); + try + { + return featureAccessors.get(what).apply(entity, arg); + } + catch (NullPointerException npe) + { + throw new InternalExpressionException("Cannot fetch '"+what+"' with these arguments"); + } } private static final Map inventorySlots = new HashMap(){{ put("mainhand", EquipmentSlot.MAINHAND); @@ -210,6 +220,7 @@ public class EntityValue extends Value put("legs", EquipmentSlot.LEGS); put("feet", EquipmentSlot.FEET); }}; + private static final Map> featureAccessors = new HashMap>() {{ //put("test", (e, a) -> a == null ? Value.NULL : new StringValue(a.getString())); put("removed", (entity, arg) -> new NumericValue(entity.removed)); @@ -321,6 +332,30 @@ public class EntityValue extends Value } return Value.NULL; }); + + put("path", (e, a) -> { + if (e instanceof MobEntity) + { + Path path = ((MobEntity)e).getNavigation().getCurrentPath(); + if (path == null) return Value.NULL; + return ValueConversions.fromPath((ServerWorld)e.getEntityWorld(), path); + } + return Value.NULL; + }); + + put("brain", (e, a) -> { + String module = a.getString(); + MemoryModuleType moduleType = Registry.MEMORY_MODULE_TYPE.get(new Identifier(module)); + if (moduleType == MemoryModuleType.DUMMY) return Value.NULL; + if (e instanceof LivingEntity) + { + LivingEntity livingEntity = (LivingEntity)e; + Optional memory = livingEntity.getBrain().getOptionalMemory(moduleType); + if (memory==null || !memory.isPresent()) return Value.NULL; + return ValueConversions.fromEntityMemory(e, memory.get()); + } + return Value.NULL; + }); put("gamemode_id", (e, a) -> { if (e instanceof ServerPlayerEntity) { diff --git a/src/main/java/carpet/script/value/ValueConversions.java b/src/main/java/carpet/script/value/ValueConversions.java new file mode 100644 index 00000000..eaa16379 --- /dev/null +++ b/src/main/java/carpet/script/value/ValueConversions.java @@ -0,0 +1,138 @@ +package carpet.script.value; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.ai.brain.LookTarget; +import net.minecraft.entity.ai.brain.WalkTarget; +import net.minecraft.entity.ai.pathing.Path; +import net.minecraft.entity.ai.pathing.PathNode; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.GlobalPos; +import net.minecraft.util.Identifier; +import net.minecraft.util.Timestamp; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.dimension.DimensionType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + +public class ValueConversions +{ + public static Value fromPos(BlockPos pos) + { + return ListValue.of(new NumericValue(pos.getX()), new NumericValue(pos.getY()), new NumericValue(pos.getZ())); + } + + public static Value dimName(ServerWorld world) + { + return ofId(Registry.DIMENSION_TYPE.getId(world.getDimension().getType())); + } + + public static Value dimName(DimensionType dim) + { + return ofId(Registry.DIMENSION_TYPE.getId(dim)); + } + + public static Value ofId(Identifier id) + { + if (id == null) // should be Value.NULL + return Value.NULL; + if (id.getNamespace().equals("minecraft")) + return new StringValue(id.getPath()); + return new StringValue(id.toString()); + } + public static Value ofGlobalPos(GlobalPos pos) + { + return ListValue.of( + ValueConversions.dimName(pos.getDimension()), + ValueConversions.fromPos(pos.getPos()) + ); + } + + public static Value fromPath(ServerWorld world, Path path) + { + List nodes = new ArrayList<>(); + for (PathNode node: path.getNodes()) + { + nodes.add( ListValue.of( + new BlockValue(null, world, node.getPos()), + new StringValue(node.type.name().toLowerCase(Locale.ROOT)), + new NumericValue(node.penalty), + new NumericValue(node.visited) + )); + } + return ListValue.wrap(nodes); + } + + public static Value fromEntityMemory(Entity e, Object v) + { + if (v instanceof GlobalPos) + { + GlobalPos pos = (GlobalPos)v; + return ofGlobalPos(pos); + }; + if (v instanceof Entity) + { + return new EntityValue((Entity)v); + }; + if (v instanceof BlockPos) + { + return new BlockValue(null, (ServerWorld) e.getEntityWorld(), (BlockPos) v); + }; + if (v instanceof Timestamp) + { + return new NumericValue(((Timestamp) v).getTime()); + } + if (v instanceof Long) + { + return new NumericValue((Long)v); + } + if (v instanceof DamageSource) + { + DamageSource source = (DamageSource) v; + return ListValue.of( + new StringValue(source.getName()), + source.getAttacker()==null?Value.NULL:new EntityValue(source.getAttacker()) + ); + } + if (v instanceof Path) + { + return fromPath((ServerWorld)e.getEntityWorld(), (Path)v); + } + if (v instanceof LookTarget) + { + return new BlockValue(null, (ServerWorld) e.getEntityWorld(), ((LookTarget)v).getBlockPos()); + } + if (v instanceof WalkTarget) + { + return ListValue.of( + new BlockValue(null, (ServerWorld) e.getEntityWorld(), ((LookTarget)v).getBlockPos()), + new NumericValue(((WalkTarget) v).getSpeed()), + new NumericValue(((WalkTarget) v).getCompletionRange()) + ); + } + if (v instanceof Set) + { + v = new ArrayList(((Set) v)); + } + if (v instanceof List) + { + List l = (List)v; + if (l.isEmpty()) return ListValue.of(); + Object el = l.get(0); + if (el instanceof Entity) + { + return ListValue.wrap((List) l.stream().map(o -> new EntityValue((Entity)o)).collect(Collectors.toList())); + } + if (el instanceof GlobalPos) + { + return ListValue.wrap((List) l.stream().map(o -> ofGlobalPos((GlobalPos) o)).collect(Collectors.toList())); + } + } + return Value.NULL; + } +}