diff --git a/docs/scarpet/api/Entities.md b/docs/scarpet/api/Entities.md index db662ce2..4919ba47 100644 --- a/docs/scarpet/api/Entities.md +++ b/docs/scarpet/api/Entities.md @@ -278,7 +278,12 @@ Returns mob's attack target or null if none or not applicable. ### `query(e, 'home')` -Returns creature's home position or null if none or not applicable. +Returns creature's home position (as per mob's AI, leash etc) or null if none or not applicable. + +### `query(e, 'spawn_point')` + +Returns position tuple, dimension, spawn angle, and whether spawn is forced, assuming the player has a spawn position. +Returns `false` if spawn position is not set, and `null` if `e` is not a player. ### `query(e, 'path')` @@ -633,6 +638,13 @@ it clears all effects. Sets AI to stay around the home position, within `distance` blocks from it. `distance` defaults to 16 blocks. `null` removes it. _May_ not work fully with mobs that have this AI built in, like Villagers. + +### `modify(e, 'spawn_point')`, `modify(e, 'spawn_point', null)`, `modify(e, 'spawn_point', pos, dimension?, angle?, forced?)` + +Changes player respawn position to given position, optional dimension (defaults to current player dimension), angle (defaults to +current player facing) and spawn forced/fixed (defaults to `false`). If `none` or nothing is passed, the respawn point +will be reset (as removed) instead. + ### `modify(e, 'gamemode', gamemode?), modify(e, 'gamemode', gamemode_id?)` Modifies gamemode of player to whatever string (case-insensitive) or number you put in. diff --git a/src/main/java/carpet/script/api/Auxiliary.java b/src/main/java/carpet/script/api/Auxiliary.java index c9611d1a..e0b67829 100644 --- a/src/main/java/carpet/script/api/Auxiliary.java +++ b/src/main/java/carpet/script/api/Auxiliary.java @@ -18,7 +18,6 @@ import carpet.script.exception.ExitStatement; import carpet.script.exception.InternalExpressionException; import carpet.script.utils.ShapeDispatcher; import carpet.script.utils.WorldTools; -import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; import carpet.script.value.FormattedTextValue; import carpet.script.value.ListValue; @@ -59,7 +58,6 @@ import net.minecraft.util.math.EulerAngle; import net.minecraft.util.math.Vec2f; import net.minecraft.util.math.Vec3d; import net.minecraft.util.registry.Registry; -import net.minecraft.util.registry.RegistryKey; import net.minecraft.world.World; import org.apache.commons.lang3.tuple.Pair; @@ -606,58 +604,9 @@ public class Auxiliary { expression.addLazyFunction("in_dimension", 2, (c, t, lv) -> { ServerCommandSource outerSource = ((CarpetContext)c).s; Value dimensionValue = lv.get(0).evalValue(c); - ServerCommandSource innerSource = outerSource; - if (dimensionValue instanceof EntityValue) - { - innerSource = outerSource.withWorld((ServerWorld) ((EntityValue)dimensionValue).getEntity().getEntityWorld()); - } - else if (dimensionValue instanceof BlockValue) - { - BlockValue bv = (BlockValue)dimensionValue; - if (bv.getWorld() != null) - { - innerSource = outerSource.withWorld(bv.getWorld()); - } - else - { - throw new InternalExpressionException("'in_dimension' accepts only world-localized block arguments"); - } - } - else - { - String dimString = dimensionValue.getString().toLowerCase(Locale.ROOT); - switch (dimString) - { - case "nether": - case "the_nether": - innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(World.NETHER)); //nether - break; - case "end": - case "the_end": - innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(World.END)); //end - break; - case "overworld": - case "over_world": - innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(World.OVERWORLD)); //ow - break; - default: - RegistryKey dim = null; - Identifier id = new Identifier(dimString); - // not using RegistryKey.of since that one creates on check - for (RegistryKey world : (outerSource.getMinecraftServer().getWorldRegistryKeys())) - { - if (id.equals(world.getValue())) - { - dim = world; - break; - } - } - if (dim == null) - throw new InternalExpressionException("Incorrect dimension string: "+dimString); - innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(dim)); - } - }; - if (innerSource.getWorld() == outerSource.getWorld()) return lv.get(1); + World world = ValueConversions.dimFromValue(dimensionValue, outerSource.getMinecraftServer()); + if (world == outerSource.getWorld()) return lv.get(1); + ServerCommandSource innerSource = outerSource.withWorld((ServerWorld)world); Context newCtx = c.recreate(); ((CarpetContext) newCtx).s = innerSource; newCtx.variables = c.variables; diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java index 6a83c83d..334ab2f4 100644 --- a/src/main/java/carpet/script/value/EntityValue.java +++ b/src/main/java/carpet/script/value/EntityValue.java @@ -61,7 +61,9 @@ import net.minecraft.util.math.Direction; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryKey; import net.minecraft.world.GameMode; +import net.minecraft.world.World; import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; @@ -354,6 +356,20 @@ public class EntityValue extends Value } return Value.NULL; }); + put("spawn_point", (e, a) -> { + if (e instanceof ServerPlayerEntity) + { + ServerPlayerEntity spe = (ServerPlayerEntity)e; + if (spe.getSpawnPointPosition() == null) return Value.FALSE; + return ListValue.of( + ValueConversions.fromPos(spe.getSpawnPointPosition()), + ValueConversions.dimName(spe.getSpawnPointDimension()), + new NumericValue(spe.getSpawnAngle()), + new NumericValue(spe.isSpawnPointSet()) // true if forced spawn point + ); + } + return Value.NULL; + }); put("pose", (e, a) -> new StringValue(e.getPose().name().toLowerCase(Locale.ROOT))); put("sneaking", (e, a) -> e.isSneaking()?Value.TRUE:Value.FALSE); put("sprinting", (e, a) -> e.isSprinting()?Value.TRUE:Value.FALSE); @@ -1004,6 +1020,54 @@ public class EntityValue extends Value } }); //requires mixing + put("spawn_point", (e, a) -> { + if (!(e instanceof ServerPlayerEntity)) return; + ServerPlayerEntity spe = (ServerPlayerEntity)e; + if (a == null) + { + spe.setSpawnPoint(null, null, 0, false, false); + } + else if (a instanceof ListValue) + { + List params= ((ListValue) a).getItems(); + Vector3Argument blockLocator = Vector3Argument.findIn(params, 0, false); + BlockPos pos = new BlockPos(blockLocator.vec); + RegistryKey world = spe.getEntityWorld().getRegistryKey(); + float angle = spe.getHeadYaw(); + boolean forced = false; + if (params.size() > blockLocator.offset) + { + Value worldValue = params.get(blockLocator.offset+0); + world = ValueConversions.dimFromValue(worldValue, spe.getServer()).getRegistryKey(); + if (params.size() > blockLocator.offset+1) + { + angle = NumericValue.asNumber(params.get(blockLocator.offset+1), "angle").getFloat(); + if (params.size() > blockLocator.offset+2) + { + forced = params.get(blockLocator.offset+2).getBoolean(); + } + } + } + spe.setSpawnPoint(world, pos, angle, forced, false); + } + else if (a instanceof BlockValue) + { + BlockValue bv= (BlockValue)a; + if (bv.getPos()==null || bv.getWorld() == null) + throw new InternalExpressionException("block for spawn modification should be localised in the world"); + spe.setSpawnPoint(bv.getWorld().getRegistryKey(), bv.getPos(), e.yaw, true, false); + } + else if (a.isNull()) + { + spe.setSpawnPoint(null, null, 0, false, false); + } + else + { + throw new InternalExpressionException("modifying player respawn point requires a block position, optional world, optional angle, and optional force"); + + } + }); + put("pickup_delay", (e, v) -> { if (e instanceof ItemEntity) diff --git a/src/main/java/carpet/script/value/ValueConversions.java b/src/main/java/carpet/script/value/ValueConversions.java index 6e8432f3..8cfbe3e1 100644 --- a/src/main/java/carpet/script/value/ValueConversions.java +++ b/src/main/java/carpet/script/value/ValueConversions.java @@ -1,11 +1,13 @@ package carpet.script.value; +import carpet.script.exception.InternalExpressionException; 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.MinecraftServer; import net.minecraft.server.world.ServerWorld; import net.minecraft.structure.StructurePiece; @@ -36,6 +38,12 @@ public class ValueConversions return ListValue.of(new NumericValue(pos.getX()), new NumericValue(pos.getY()), new NumericValue(pos.getZ())); } + public static Value fromPosOptional(BlockPos pos) + { + if (pos == null) return Value.NULL; + return ListValue.of(new NumericValue(pos.getX()), new NumericValue(pos.getY()), new NumericValue(pos.getZ())); + } + public static Value fromVec(Vec3d vec) { return ListValue.of(new NumericValue(vec.x), new NumericValue(vec.y), new NumericValue(vec.z)); @@ -46,6 +54,57 @@ public class ValueConversions return ofId(world.getRegistryKey().getValue()); } + public static World dimFromValue(Value dimensionValue, MinecraftServer server) + { + if (dimensionValue instanceof EntityValue) + { + return ((EntityValue)dimensionValue).getEntity().getEntityWorld(); + } + else if (dimensionValue instanceof BlockValue) + { + BlockValue bv = (BlockValue)dimensionValue; + if (bv.getWorld() != null) + { + return bv.getWorld(); + } + else + { + throw new InternalExpressionException("dimension argument accepts only world-localized block arguments"); + } + } + else + { + String dimString = dimensionValue.getString().toLowerCase(Locale.ROOT); + switch (dimString) + { + case "nether": + case "the_nether": + return server.getWorld(World.NETHER); + case "end": + case "the_end": + return server.getWorld(World.END); + case "overworld": + case "over_world": + return server.getWorld(World.OVERWORLD); + default: + RegistryKey dim = null; + Identifier id = new Identifier(dimString); + // not using RegistryKey.of since that one creates on check + for (RegistryKey world : (server.getWorldRegistryKeys())) + { + if (id.equals(world.getValue())) + { + dim = world; + break; + } + } + if (dim == null) + throw new InternalExpressionException("Incorrect dimension string: "+dimString); + return server.getWorld(dim); + } + } + } + public static Value dimName(RegistryKey dim) { return ofId(dim.getValue());