diff --git a/common/src/main/java/org/figuramc/figura/lua/api/entity/EntityAPI.java b/common/src/main/java/org/figuramc/figura/lua/api/entity/EntityAPI.java index e221fc289..5dc8753ae 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/entity/EntityAPI.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/entity/EntityAPI.java @@ -4,11 +4,9 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityDimensions; -import net.minecraft.world.entity.HasCustomInventoryScreen; -import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.*; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.projectile.ProjectileUtil; import net.minecraft.world.entity.vehicle.ContainerEntity; @@ -35,10 +33,12 @@ import org.figuramc.figura.utils.EntityUtils; import org.figuramc.figura.utils.LuaUtils; import org.jetbrains.annotations.NotNull; +import org.luaj.vm2.LuaError; import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.UUID; @@ -459,6 +459,48 @@ public Object[] getTargetedEntity(Double distance) { return null; } + @LuaWhitelist + @LuaMethodDoc( + overloads = { + @LuaMethodOverload( + argumentTypes = { String.class, Double.class }, + argumentNames = { "type", "radius" } + ), + @LuaMethodOverload( + argumentTypes = { String.class }, + argumentNames = { "type" } + ), + @LuaMethodOverload() + }, + value = "entity.get_nearest_entity" + ) + public EntityAPI getNearestEntity(String type, Double radius) { + checkEntity(); + radius = radius != null ? radius : 20; + + EntityType entityType; + if (type != null) { + ResourceLocation id = ResourceLocation.tryParse(type); + if (id == null) { + throw new LuaError("Invalid entity type: " + type); + } + entityType = BuiltInRegistries.ENTITY_TYPE.get(id); + } else { + entityType = null; + } + + FiguraVec3 pos = getPos(1f); + + AABB aabb = new AABB(pos.offseted(-radius).asVec3(), pos.offseted(radius).asVec3()); + + return getLevel().getEntities(entity, aabb) + .stream() + .filter(e -> entityType == null || e.getType() == entityType) + .min(Comparator.comparingDouble(e -> e.distanceToSqr(pos.x(), pos.y(), pos.z()))) + .map(EntityAPI::wrap) + .orElse(null); + } + @LuaWhitelist @LuaMethodDoc( overloads = { diff --git a/common/src/main/java/org/figuramc/figura/lua/api/world/WorldAPI.java b/common/src/main/java/org/figuramc/figura/lua/api/world/WorldAPI.java index acc24005b..d8ff7da0f 100644 --- a/common/src/main/java/org/figuramc/figura/lua/api/world/WorldAPI.java +++ b/common/src/main/java/org/figuramc/figura/lua/api/world/WorldAPI.java @@ -7,6 +7,7 @@ import net.minecraft.commands.arguments.blocks.BlockStateArgument; import net.minecraft.commands.arguments.item.ItemArgument; import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; @@ -14,6 +15,10 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.saveddata.maps.MapDecoration; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import net.minecraft.world.phys.AABB; +import org.apache.commons.lang3.ArrayUtils; import org.figuramc.figura.avatar.Avatar; import org.figuramc.figura.avatar.AvatarManager; import org.figuramc.figura.lua.LuaNotNil; @@ -32,6 +37,7 @@ import org.luaj.vm2.LuaTable; import java.util.*; +import java.util.stream.Collectors; @LuaWhitelist @LuaTypeDoc( @@ -156,6 +162,45 @@ public static List getBlocks(Object x, Object y, Double z, Double return list; } + @LuaWhitelist + @LuaMethodDoc( + overloads = { + @LuaMethodOverload( + argumentTypes = String.class, + argumentNames = "id" + ), + }, + value = "world.get_map_data" + ) + public static HashMap getMapData(String id) { + MapItemSavedData data = getCurrentWorld().getMapData(id); + + if (data == null) + return null; + + HashMap map = new HashMap<>(); + + map.put("center_x", data.centerX); + map.put("center_z", data.centerZ); + map.put("locked", data.locked); + map.put("scale", data.scale); + + ArrayList> decorations = new ArrayList<>(); + for (MapDecoration decoration : data.getDecorations()) { + HashMap decorationMap = new HashMap<>(); + decorationMap.put("type", decoration.getType().toString()); + decorationMap.put("name", decoration.getName() == null ? "" : decoration.getName().getString()); + decorationMap.put("x", decoration.getX()); + decorationMap.put("y", decoration.getY()); + decorationMap.put("rot", decoration.getRot()); + decorationMap.put("image", decoration.getImage()); + decorations.add(decorationMap); + } + map.put("decorations", decorations); + + return map; + } + @LuaWhitelist @LuaMethodDoc( overloads = { @@ -432,6 +477,32 @@ public static Map> getPlayers() { return playerList; } + @LuaWhitelist + @LuaMethodDoc( + overloads = { + @LuaMethodOverload( + argumentTypes = {FiguraVec3.class, FiguraVec3.class}, + argumentNames = {"pos1", "pos2"} + ), + @LuaMethodOverload( + argumentTypes = {Double.class, Double.class, Double.class, Double.class, Double.class, Double.class}, + argumentNames = {"x1", "y1", "z1", "x2", "y2", "z2"} + ) + }, + value = "world.get_entities" + ) + public static List> getEntities(Object x1, Object y1, Double z1, Double x2, Double y2, Double z2) { + Pair pair = LuaUtils.parse2Vec3("getEntities", x1, y1, z1, x2, y2, z2, 1); + FiguraVec3 pos1 = pair.getFirst(); + FiguraVec3 pos2 = pair.getSecond(); + + AABB aabb = new AABB(pos1.asVec3(), pos2.asVec3()); + return getCurrentWorld().getEntitiesOfClass(Entity.class, aabb) + .stream() + .map(EntityAPI::wrap) + .collect(Collectors.toList()); + } + @LuaWhitelist @LuaMethodDoc( overloads = @LuaMethodOverload( diff --git a/common/src/main/resources/assets/figura/lang/en_us.json b/common/src/main/resources/assets/figura/lang/en_us.json index 8d4cf31d0..4256e9946 100644 --- a/common/src/main/resources/assets/figura/lang/en_us.json +++ b/common/src/main/resources/assets/figura/lang/en_us.json @@ -989,6 +989,7 @@ "figura.docs.entity.has_inventory": "Checks if the entity has an inventory (Horses, Camels, LLamas, …)", "figura.docs.entity.get_targeted_block": "Returns a proxy for your currently targeted BlockState\nThis BlockState appears on the F3 screen\nThe maximum (and default) distance is 20, minimum is -20\nReturns the block, the hit position, and the targeted block face as three separate values", "figura.docs.entity.get_targeted_entity": "Returns a proxy for your currently targeted Entity\nThis Entity appears on the F3 screen\nMaximum and Default distance is 20, Minimum is 0", + "figura.docs.entity.get_nearest_entity": "Returns the closest entity to this entity\nIf `type` is an entity id, (e.g. `minecraft:bee`), only entities of that type will be considered\nRadius defaults to 20, and controls the size of the area for checking entities as a box expanding in every direction from the player", "figura.docs.entity.get_variable": "Gets the value of a variable this entity stored in themselves using the Avatar API's store() function", "figura.docs.entity.is_living": "Gets if this entity is a Living Entity", "figura.docs.entity.is_player": "Gets if this entity is a Player Entity", @@ -1673,6 +1674,7 @@ "figura.docs.world.get_height": "Returns the highest point at the given position according to the provided heightmap\nDefaults to MOTION_BLOCKING if no heightmap is provided", "figura.docs.world.get_dimension": "Gets the dimension name of this world", "figura.docs.world.get_entity": "Returns an EntityAPI object from this UUID's entity, or nil if no entity was found", + "figura.docs.world.get_entities": "Returns a list of entities within the bounding box formed by the two given positions", "figura.docs.world.get_players": "Returns a table containing instances of Player for all players in the world\nThe players are indexed by their names", "figura.docs.world.avatar_vars": "Returns a table containing variables stored from all loaded Avatars \"avatar:store()\" function\nThe table will be indexed by the avatar's owner UUID", "figura.docs.world.new_block": "Parses and creates a new BlockState from the given string\nA world position can be optionally given for the blockstate functions that rely on its position", @@ -1682,6 +1684,7 @@ "figura.docs.world.get_spawn_point": "Returns a vector with the coordinates of the world spawn", "figura.docs.world.raycast_entity": "Raycasts an Entity in the world, returns a map containing the entity and it's position.", "figura.docs.world.raycast_block": "Raycasts a Block in the world, returns a map containing the block and it's position.", + "figura.docs.world.get_map_data": "Takes a string, e.g., `map_3`, and returns a table of data if the map exists.\nMap data may be unsynced, and will only update when holding the map", "figura.docs.data": "A global API that provides functions to work with data related features", "figura.docs.data.create_buffer": "Creates an empty buffer", "figura.docs.buffer": "A byte buffer object",