package carpet.script.utils; import carpet.CarpetSettings; import carpet.network.ServerNetworkHandler; import carpet.script.CarpetContext; import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ThrowStatement; import carpet.script.exception.Throwables; import carpet.script.language.Sys; import carpet.script.value.BlockValue; import carpet.script.value.EntityValue; import carpet.script.value.FormattedTextValue; import carpet.script.value.ListValue; import carpet.script.value.MapValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.Value; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.minecraft.command.argument.ParticleEffectArgumentType; import net.minecraft.entity.Entity; import net.minecraft.nbt.NbtByte; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtDouble; import net.minecraft.nbt.NbtFloat; import net.minecraft.nbt.NbtInt; import net.minecraft.nbt.NbtList; import net.minecraft.nbt.NbtString; import net.minecraft.nbt.NbtElement; import net.minecraft.particle.ParticleEffect; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; 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.world.World; import net.minecraft.util.registry.RegistryKey; import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; public class ShapeDispatcher { private static final Map particleCache = new HashMap<>(); public static Pair> fromFunctionArgs( MinecraftServer server, ServerWorld world, List lv, ServerPlayerEntity[] playerRef ) { if (lv.size() < 3) throw new InternalExpressionException("'draw_shape' takes at least three parameters, shape name, duration, and its params"); String shapeType = lv.get(0).getString(); Value duration = NumericValue.asNumber(lv.get(1), "duration"); Map params; if (lv.size() == 3) { Value paramValue = lv.get(2); if (paramValue instanceof MapValue) { params = new HashMap<>(); ((MapValue) paramValue).getMap().entrySet().forEach(e -> params.put(e.getKey().getString(),e.getValue())); } else if (paramValue instanceof ListValue) { params = parseParams(((ListValue) paramValue).getItems()); } else throw new InternalExpressionException("Parameters for 'draw_shape' need to be defined either in a list or a map"); } else { List paramList = new ArrayList<>(); for (int i=2; i < lv.size(); i++) paramList.add(lv.get(i)); params = ShapeDispatcher.parseParams(paramList); } params.putIfAbsent("dim", new StringValue(world.getRegistryKey().getValue().toString())); params.putIfAbsent("duration", duration); if (params.containsKey("player")) { ServerPlayerEntity player = EntityValue.getPlayerByValue(server, params.get("player")); if (player == null) throw new InternalExpressionException("'player' parameter needs to represent an existing player"); params.remove("player"); playerRef[0] = player; } return Pair.of(ShapeDispatcher.create(server, shapeType, params), params); } public static void sendShape(List players, List>> shapes) { List clientPlayers = new ArrayList<>(); List alternativePlayers = new ArrayList<>(); for (ServerPlayerEntity player : players) { if (ServerNetworkHandler.isValidCarpetPlayer(player)) { clientPlayers.add(player); } else { alternativePlayers.add(player); } } if (!clientPlayers.isEmpty()) { NbtList tag = new NbtList(); int tagcount = 0; for (Pair> s : shapes) { tag.add(ExpiringShape.toTag(s.getRight())); // 4000 shapes limit boxes if (tagcount++>1000) { tagcount = 0; NbtElement finalTag = tag; clientPlayers.forEach( p -> ServerNetworkHandler.sendCustomCommand(p, "scShapes", finalTag)); tag = new NbtList(); } } NbtElement finalTag = tag; if (tag.size() > 0) clientPlayers.forEach( p -> ServerNetworkHandler.sendCustomCommand(p, "scShapes", finalTag)); } if (!alternativePlayers.isEmpty()) { List> alternatives = new ArrayList<>(); shapes.forEach(s -> alternatives.add(s.getLeft().alternative())); alternativePlayers.forEach(p -> alternatives.forEach( a -> a.accept(p))); } } public static ParticleEffect getParticleData(String name) { ParticleEffect particle = particleCache.get(name); if (particle != null) return particle; try { particle = ParticleEffectArgumentType.readParameters(new StringReader(name)); } catch (CommandSyntaxException e) { throw new ThrowStatement(name, Throwables.UNKNOWN_PARTICLE); } particleCache.put(name, particle); return particle; } public static Map parseParams(List items) { // parses params from API function if (items.size()%2 == 1) throw new InternalExpressionException("Shape parameters list needs to be of even size"); Map param = new HashMap<>(); int i = 0; while(i < items.size()) { String name = items.get(i).getString(); Value val = items.get(i+1); param.put(name, val); i += 2; } return param; } public static ExpiringShape create(MinecraftServer server, String shapeType, Map userParams) { userParams.put("shape", new StringValue(shapeType)); userParams.keySet().forEach(key -> { Param param = Param.of.get(key); if (param==null) throw new InternalExpressionException("Unknown feature for shape: "+key); userParams.put(key, param.validate(userParams, server, userParams.get(key))); }); Function,ExpiringShape> factory = ExpiringShape.shapeProviders.get(shapeType); if (factory == null) throw new InternalExpressionException("Unknown shape: "+shapeType); return factory.apply(userParams); } // client public static ExpiringShape fromTag(NbtCompound tag) { Map options = new HashMap<>(); for (String key : tag.getKeys()) { Param decoder = Param.of.get(key); if (decoder==null) { CarpetSettings.LOG.info("Unknown parameter for shape: "+key); return null; } Value decodedValue = decoder.decode(tag.get(key)); options.put(key, decodedValue); } Value shapeValue = options.get("shape"); if (shapeValue == null) { CarpetSettings.LOG.info("Shape id missing in "+ String.join(", ", tag.getKeys())); return null; } Function,ExpiringShape> factory = ExpiringShape.shapeProviders.get(shapeValue.getString()); if (factory == null) { CarpetSettings.LOG.info("Unknown shape: "+shapeValue.getString()); return null; } try { return factory.apply(options); } catch (InternalExpressionException exc) { CarpetSettings.LOG.info(exc.getMessage()); } return null; } public abstract static class ExpiringShape { public static final Map,ExpiringShape>> shapeProviders = new HashMap, ExpiringShape>>(){{ put("line", creator(Line::new)); put("box", creator(Box::new)); put("sphere", creator(Sphere::new)); put("cylinder", creator(Cylinder::new)); put("label", creator(DisplayedText::new)); }}; private static Function,ExpiringShape> creator(Supplier shapeFactory) { return o -> { ExpiringShape shape = shapeFactory.get(); shape.fromOptions(o); return shape; }; } float lineWidth; protected float r, g, b, a; protected int color; protected float fr, fg, fb, fa; protected int fillColor; protected int duration = 0; private long key; protected int followEntity; protected String snapTo; protected boolean snapX, snapY, snapZ; protected boolean discreteX, discreteY, discreteZ; protected RegistryKey shapeDimension; protected ExpiringShape() { } public static NbtCompound toTag(Map params) { NbtCompound tag = new NbtCompound(); params.forEach((k, v) -> { NbtElement valTag = Param.of.get(k).toTag(v); if (valTag != null) tag.put(k, valTag); }); return tag; } private void fromOptions(Map options) { Set optional = optionalParams(); Set required = requiredParams(); Set all = Sets.union(optional, required); if (!all.containsAll(options.keySet())) throw new InternalExpressionException("Received unexpected parameters for shape: "+Sets.difference(options.keySet(), all)); if (!options.keySet().containsAll(required)) throw new InternalExpressionException("Missing required parameters for shape: "+Sets.difference(required, options.keySet())); options.keySet().forEach(k ->{ if (!this.canTake(k)) throw new InternalExpressionException("Parameter "+k+" doesn't apply for shape "+options.get("shape").getString()); }); init(options); } protected void init(Map options) { duration = NumericValue.asNumber(options.get("duration")).getInt(); lineWidth = NumericValue.asNumber(options.getOrDefault("line", optional.get("line"))).getFloat(); fillColor = NumericValue.asNumber(options.getOrDefault("fill", optional.get("fill"))).getInt(); this.fr = (float)(fillColor >> 24 & 0xFF) / 255.0F; this.fg = (float)(fillColor >> 16 & 0xFF) / 255.0F; this.fb = (float)(fillColor >> 8 & 0xFF) / 255.0F; this.fa = (float)(fillColor & 0xFF) / 255.0F; color = NumericValue.asNumber(options.getOrDefault("color", optional.get("color"))).getInt(); this.r = (float)(color >> 24 & 0xFF) / 255.0F; this.g = (float)(color >> 16 & 0xFF) / 255.0F; this.b = (float)(color >> 8 & 0xFF) / 255.0F; this.a = (float)(color & 0xFF) / 255.0F; key = 0; followEntity = -1; shapeDimension = RegistryKey.of(Registry.WORLD_KEY, new Identifier(options.get("dim").getString())); if (options.containsKey("follow")) { followEntity = NumericValue.asNumber(options.getOrDefault("follow", optional.get("follow"))).getInt(); snapTo = options.getOrDefault("snap", optional.get("snap")).getString().toLowerCase(Locale.ROOT); snapX = snapTo.contains("x"); snapY = snapTo.contains("y"); snapZ = snapTo.contains("z"); discreteX = snapTo.contains("dx"); discreteY = snapTo.contains("dy"); discreteZ = snapTo.contains("dz"); } } public int getExpiry() { return duration; } public Vec3d toAbsolute(Entity e, Vec3d vec, float partialTick) { return vec.add( snapX?(discreteX ?MathHelper.floor(e.getX()):MathHelper.lerp(partialTick, e.prevX, e.getX())):0.0, snapY?(discreteY ?MathHelper.floor(e.getY()):MathHelper.lerp(partialTick, e.prevY, e.getY())):0.0, snapZ?(discreteZ ?MathHelper.floor(e.getZ()):MathHelper.lerp(partialTick, e.prevZ, e.getZ())):0.0 ); } public Vec3d relativiseRender(World world, Vec3d vec, float partialTick) { if (followEntity < 0) return vec; Entity e = world.getEntityById(followEntity); if (e == null) return vec; return toAbsolute(e, vec, partialTick); } public Vec3d vecFromValue(Value value) { if (!(value instanceof ListValue)) throw new InternalExpressionException("decoded value of "+value.getPrettyString()+" is not a triple"); List elements = ((ListValue) value).getItems(); return new Vec3d( NumericValue.asNumber( elements.get(0)).getDouble(), NumericValue.asNumber( elements.get(1)).getDouble(), NumericValue.asNumber( elements.get(2)).getDouble() ); } protected ParticleEffect replacementParticle() { String particleName = fa ==0 ? String.format(Locale.ROOT , "dust %.1f %.1f %.1f 1.0", r, g, b): String.format(Locale.ROOT , "dust %.1f %.1f %.1f 1.0", fr, fg, fb); return getParticleData(particleName); } public abstract Consumer alternative(); public long key() { if (key!=0) return key; key = calcKey(); return key; } protected long calcKey() { // using FNV-1a algorithm long hash = -3750763034362895579L; hash ^= shapeDimension.hashCode(); hash *= 1099511628211L; hash ^= color; hash *= 1099511628211L; hash ^= followEntity; hash *= 1099511628211L; if (followEntity >= 0) { hash ^= snapTo.hashCode(); hash *= 1099511628211L; } hash ^= Float.hashCode(lineWidth); hash *= 1099511628211L; if (fa != 0.0) { hash = 31*hash + fillColor; hash *= 1099511628211L; } return hash; } private static final double xdif = new Random().nextDouble(); private static final double ydif = new Random().nextDouble(); private static final double zdif = new Random().nextDouble(); int vec3dhash(Vec3d vec) { return vec.add(xdif, ydif, zdif).hashCode(); } // list of params that need to be there private final Set required = ImmutableSet.of("duration", "shape", "dim"); private final Map optional = ImmutableMap.of( "color", new NumericValue(-1), "follow", new NumericValue(-1), "line", new NumericValue(2.0), "fill", new NumericValue(0xffffff00), "snap", new StringValue("xyz") ); protected Set requiredParams() {return required;} // list of params that can be there, with defaults protected Set optionalParams() {return optional.keySet();} private boolean canTake(String param) { return requiredParams().contains(param) || optionalParams().contains(param); } } public static class DisplayedText extends ExpiringShape { private final Set required = ImmutableSet.of("pos", "text"); private final Map optional = ImmutableMap.builder(). put("facing", new StringValue("player")). put("raise", new NumericValue(0)). put("tilt", new NumericValue(0)). put("lean", new NumericValue(0)). put("turn", new NumericValue(0)). put("indent", new NumericValue(0)). put("height", new NumericValue(0)). put("align", new StringValue("center")). put("size", new NumericValue(10)). put("value", Value.NULL). put("doublesided", new NumericValue(0)). build(); @Override protected Set requiredParams() { return Sets.union(super.requiredParams(), required); } @Override protected Set optionalParams() { return Sets.union(super.optionalParams(), optional.keySet()); } public DisplayedText() { } Vec3d pos; String text; int textcolor; int textbck; Direction facing; float raise; float tilt; float lean; float turn; float size; float indent; int align; float height; Text value; boolean doublesided; @Override protected void init(Map options) { super.init(options); pos = vecFromValue(options.get("pos")); value = ((FormattedTextValue)options.get("text")).getText(); text = value.getString(); if (options.containsKey("value")) { value = ((FormattedTextValue)options.get("value")).getText(); } textcolor = rgba2argb(color); textbck = rgba2argb(fillColor); String dir = options.getOrDefault("facing", optional.get("facing")).getString(); facing = null; switch (dir) { case "north": facing = Direction.NORTH; break; case "south": facing = Direction.SOUTH; break; case "east": facing = Direction.EAST; break; case "west": facing = Direction.WEST; break; case "up": facing = Direction.UP; break; case "down": facing = Direction.DOWN; break; } align = 0; if (options.containsKey("align")) { String alignStr = options.get("align").getString(); if ("right".equalsIgnoreCase(alignStr)) align = 1; else if ("left".equalsIgnoreCase(alignStr)) align = -1; } doublesided = false; if (options.containsKey("doublesided")) { doublesided = options.get("doublesided").getBoolean(); } raise = NumericValue.asNumber(options.getOrDefault("raise", optional.get("raise"))).getFloat(); tilt = NumericValue.asNumber(options.getOrDefault("tilt", optional.get("tilt"))).getFloat(); lean = NumericValue.asNumber(options.getOrDefault("lean", optional.get("lean"))).getFloat(); turn = NumericValue.asNumber(options.getOrDefault("turn", optional.get("turn"))).getFloat(); indent = NumericValue.asNumber(options.getOrDefault("indent", optional.get("indent"))).getFloat(); height = NumericValue.asNumber(options.getOrDefault("height", optional.get("height"))).getFloat(); size = NumericValue.asNumber(options.getOrDefault("size", optional.get("size"))).getFloat(); } private int rgba2argb(int color) { int r = Math.max(1, color >> 24 & 0xFF); int g = Math.max(1, color >> 16 & 0xFF); int b = Math.max(1, color >> 8 & 0xFF); int a = color & 0xFF; return (a << 24) + (r << 16) + (g << 8) + b; } @Override public Consumer alternative() { return s -> {}; } @Override public long calcKey() { long hash = super.calcKey(); hash ^= 5; hash *= 1099511628211L; hash ^= vec3dhash(pos); hash *= 1099511628211L; hash ^= text.hashCode(); hash *= 1099511628211L; if (facing!= null) hash ^= facing.hashCode(); hash *= 1099511628211L; hash ^= Float.hashCode(raise); hash *= 1099511628211L; hash ^= Float.hashCode(tilt); hash *= 1099511628211L; hash ^= Float.hashCode(lean); hash *= 1099511628211L; hash ^= Float.hashCode(turn); hash *= 1099511628211L; hash ^= Float.hashCode(indent); hash *= 1099511628211L; hash ^= Float.hashCode(height); hash *= 1099511628211L; hash ^= Float.hashCode(size); hash *= 1099511628211L; hash ^= Integer.hashCode(align); hash *= 1099511628211L; hash ^= Boolean.hashCode(doublesided); hash *= 1099511628211L; return hash; } } public static class Box extends ExpiringShape { private final Set required = ImmutableSet.of("from", "to"); private final Map optional = ImmutableMap.of(); @Override protected Set requiredParams() { return Sets.union(super.requiredParams(), required); } @Override protected Set optionalParams() { return Sets.union(super.optionalParams(), optional.keySet()); } public Box() { } Vec3d from; Vec3d to; @Override protected void init(Map options) { super.init(options); from = vecFromValue(options.get("from")); to = vecFromValue(options.get("to")); } @Override public Consumer alternative() { ParticleEffect particle = replacementParticle(); double density = Math.max(2.0, from.distanceTo(to) /50/ (a+0.1)) ; return p -> { if (p.world.getRegistryKey() == shapeDimension) { particleMesh( Collections.singletonList(p), particle, density, relativiseRender(p.getServerWorld(), from, 0), relativiseRender(p.getServerWorld(), to, 0) ); } }; } @Override public long calcKey() { long hash = super.calcKey(); hash ^= 1; hash *= 1099511628211L; hash ^= vec3dhash(from); hash *= 1099511628211L; hash ^= vec3dhash(to); hash *= 1099511628211L; return hash; } public static int particleMesh(List playerList, ParticleEffect particle, double density, Vec3d from, Vec3d to) { double x1 = from.x; double y1 = from.y; double z1 = from.z; double x2 = to.x; double y2 = to.y; double z2 = to.z; return drawParticleLine(playerList, particle, new Vec3d(x1, y1, z1), new Vec3d(x1, y2, z1), density)+ drawParticleLine(playerList, particle, new Vec3d(x1, y2, z1), new Vec3d(x2, y2, z1), density)+ drawParticleLine(playerList, particle, new Vec3d(x2, y2, z1), new Vec3d(x2, y1, z1), density)+ drawParticleLine(playerList, particle, new Vec3d(x2, y1, z1), new Vec3d(x1, y1, z1), density)+ drawParticleLine(playerList, particle, new Vec3d(x1, y1, z2), new Vec3d(x1, y2, z2), density)+ drawParticleLine(playerList, particle, new Vec3d(x1, y2, z2), new Vec3d(x2, y2, z2), density)+ drawParticleLine(playerList, particle, new Vec3d(x2, y2, z2), new Vec3d(x2, y1, z2), density)+ drawParticleLine(playerList, particle, new Vec3d(x2, y1, z2), new Vec3d(x1, y1, z2), density)+ drawParticleLine(playerList, particle, new Vec3d(x1, y1, z1), new Vec3d(x1, y1, z2), density)+ drawParticleLine(playerList, particle, new Vec3d(x1, y2, z1), new Vec3d(x1, y2, z2), density)+ drawParticleLine(playerList, particle, new Vec3d(x2, y2, z1), new Vec3d(x2, y2, z2), density)+ drawParticleLine(playerList, particle, new Vec3d(x2, y1, z1), new Vec3d(x2, y1, z2), density); } } public static class Line extends ExpiringShape { private final Set required = ImmutableSet.of("from", "to"); private final Map optional = ImmutableMap.of(); @Override protected Set requiredParams() { return Sets.union(super.requiredParams(), required); } @Override protected Set optionalParams() { return Sets.union(super.optionalParams(), optional.keySet()); } private Line() { super(); } Vec3d from; Vec3d to; @Override protected void init(Map options) { super.init(options); from = vecFromValue(options.get("from")); to = vecFromValue(options.get("to")); } @Override public Consumer alternative() { ParticleEffect particle = replacementParticle(); double density = Math.max(2.0, from.distanceTo(to) /50) / (a+0.1); return p -> { if (p.world.getRegistryKey() == shapeDimension) drawParticleLine( Collections.singletonList(p), particle, relativiseRender(p.getServerWorld(), from, 0), relativiseRender(p.getServerWorld(), to, 0), density ); }; } @Override public long calcKey() { long hash = super.calcKey(); hash ^= 2; hash *= 1099511628211L; hash ^= vec3dhash(from); hash *= 1099511628211L; hash ^= vec3dhash(to); hash *= 1099511628211L; return hash; } } public static class Sphere extends ExpiringShape { private final Set required = ImmutableSet.of("center", "radius"); private final Map optional = ImmutableMap.of("level", Value.ZERO); @Override protected Set requiredParams() { return Sets.union(super.requiredParams(), required); } @Override protected Set optionalParams() { return Sets.union(super.optionalParams(), optional.keySet()); } private Sphere() { super(); } Vec3d center; float radius; int level; int subdivisions; @Override protected void init(Map options) { super.init(options); center = vecFromValue(options.get("center")); radius = NumericValue.asNumber(options.get("radius")).getFloat(); level = NumericValue.asNumber(options.getOrDefault("level", optional.get("level"))).getInt(); subdivisions = level; if (subdivisions <= 0) { subdivisions = Math.max(10, (int)(10*Math.sqrt(radius))); } } @Override public Consumer alternative() { return p -> { ParticleEffect particle = replacementParticle(); int partno = Math.min(1000,20*subdivisions); Random rand = p.world.getRandom(); ServerWorld world = p.getServerWorld(); Vec3d ccenter = relativiseRender(world, center, 0 ); double ccx = ccenter.x; double ccy = ccenter.y; double ccz = ccenter.z; for (int i=0; i required = ImmutableSet.of("center", "radius"); private final Map optional = ImmutableMap.of( "level", Value.ZERO, "height", Value.ZERO, "axis", new StringValue("y") ); @Override protected Set requiredParams() { return Sets.union(super.requiredParams(), required); } @Override protected Set optionalParams() { return Sets.union(super.optionalParams(), optional.keySet()); } Vec3d center; float height; float radius; int level; int subdivisions; Direction.Axis axis; private Cylinder() { super(); } @Override protected void init(Map options) { super.init(options); center = vecFromValue(options.get("center")); radius = NumericValue.asNumber(options.get("radius")).getFloat(); level = NumericValue.asNumber(options.getOrDefault("level", optional.get("level"))).getInt(); subdivisions = level; if (subdivisions <= 0) { subdivisions = Math.max(10, (int)(10*Math.sqrt(radius))); } height = NumericValue.asNumber(options.getOrDefault("height", optional.get("height"))).getFloat(); axis = Direction.Axis.fromName(options.getOrDefault("axis", optional.get("axis")).getString()); } @Override public Consumer alternative() { return p -> { ParticleEffect particle = replacementParticle(); int partno = (int)Math.min(1000,Math.sqrt(20*subdivisions*(1+height))); Random rand = p.world.getRandom(); ServerWorld world = p.getServerWorld(); Vec3d ccenter = relativiseRender(world, center, 0 ); double ccx = ccenter.x; double ccy = ccenter.y; double ccz = ccenter.z; if (axis == Direction.Axis.Y) { for (int i=0; i of = new HashMap(){{ put("shape", new ShapeParam()); put("dim", new DimensionParam()); put("duration", new NonNegativeIntParam("duration")); put("color", new ColorParam("color")); put("follow", new EntityParam("follow")); put("snap", new StringChoiceParam("snap", "xyz", "xz", "yz", "xy", "x", "y", "z", "dxdydz", "dxdz", "dydz", "dxdy", "dx", "dy", "dz", "xdz", "dxz", "ydz", "dyz", "xdy", "dxy", "xydz", "xdyz", "xdydz", "dxyz", "dxydz", "dxdyz" )); put("line", new PositiveFloatParam("line")); put("fill", new ColorParam("fill")); put("from", new Vec3Param("from", false)); put("to", new Vec3Param("to", true)); put("center", new Vec3Param("center", false)); put("pos", new Vec3Param("pos", false)); put("radius", new PositiveFloatParam("radius")); put("level", new PositiveIntParam("level")); put("height", new FloatParam("height")); put("axis", new StringChoiceParam("axis", "x", "y", "z")); put("points", new PointsParam("points")); put("text", new FormattedTextParam("text")); put("value", new FormattedTextParam("value")); put("size", new PositiveIntParam("size")); put("align", new StringChoiceParam("align", "center", "left", "right")); put("indent", new FloatParam("indent")); put("raise", new FloatParam("raise")); put("tilt", new FloatParam("tilt")); put("facing", new StringChoiceParam("axis", "player", "north", "south", "east", "west", "up", "down")); put("doublesided", new BoolParam("doublesided")); }}; protected String id; protected Param(String id) { this.id = id; } public abstract NbtElement toTag(Value value); //validates value, returning null if not necessary to keep it and serialize public abstract Value validate(Map options, MinecraftServer server, Value value); // makes sure the value is proper public abstract Value decode(NbtElement tag); } public abstract static class StringParam extends Param { protected StringParam(String id) { super(id); } @Override public NbtElement toTag(Value value) { return NbtString.of(value.getString()); } public Value decode(NbtElement tag) { return new StringValue(tag.asString()); } } public static class TextParam extends StringParam { protected TextParam(String id) { super(id); } @Override public Value validate(Map options, MinecraftServer server, Value value) { return value; } } public static class FormattedTextParam extends StringParam { protected FormattedTextParam(String id) { super(id); } @Override public Value validate(Map options, MinecraftServer server, Value value) { if (!(value instanceof FormattedTextValue)) value = new FormattedTextValue(new LiteralText(value.getString())); return value; } @Override public NbtElement toTag(Value value) { if (!(value instanceof FormattedTextValue)) value = new FormattedTextValue(new LiteralText(value.getString())); return NbtString.of(((FormattedTextValue)value).serialize()); } @Override public Value decode(NbtElement tag) { return FormattedTextValue.deserialize(tag.asString()); } } public static class StringChoiceParam extends StringParam { private Set options; public StringChoiceParam(String id, String ... options) { super(id); this.options = Sets.newHashSet(options); } @Override public Value validate(Map options, MinecraftServer server, Value value) { if (this.options.contains(value.getString())) return value; return null; } } public static class DimensionParam extends StringParam { protected DimensionParam() { super("dim"); } @Override public Value validate(Map options, MinecraftServer server, Value value) { return value; } } public static class ShapeParam extends StringParam { protected ShapeParam() { super("shape"); } @Override public Value validate(Map options, MinecraftServer server, Value value) { String shape = value.getString(); if (!ExpiringShape.shapeProviders.containsKey(shape)) throw new InternalExpressionException("Unknown shape: "+shape); return value; } } public static abstract class NumericParam extends Param { protected NumericParam(String id) { super(id); } @Override public Value validate(Map options, MinecraftServer server, Value value) { if (!(value instanceof NumericValue)) throw new InternalExpressionException("'" + id + "' needs to be a number"); return value; } } public static class BoolParam extends NumericParam { protected BoolParam(String id) { super(id); } @Override public NbtElement toTag(Value value) { return NbtByte.of(value.getBoolean()); } @Override public Value decode(NbtElement tag) { return new NumericValue(((NbtByte) tag).byteValue() > 0); } } public static class FloatParam extends NumericParam { protected FloatParam(String id) { super(id); } @Override public Value decode(NbtElement tag) { return new NumericValue(((NbtFloat)tag).floatValue()); } @Override public NbtElement toTag(Value value) { return NbtFloat.of(NumericValue.asNumber(value, id).getFloat()); } } public static abstract class PositiveParam extends NumericParam { protected PositiveParam(String id) { super(id); } @Override public Value validate(Map options, MinecraftServer server, Value value) { Value ret = super.validate(options, server, value); if (((NumericValue)ret).getDouble()<=0) throw new InternalExpressionException("'"+id+"' should be positive"); return ret; } } public static class PositiveFloatParam extends PositiveParam { protected PositiveFloatParam(String id) { super(id); } @Override public Value decode(NbtElement tag) { return new NumericValue(((NbtFloat)tag).floatValue()); } @Override public NbtElement toTag(Value value) { return NbtFloat.of(NumericValue.asNumber(value, id).getFloat()); } } public static class PositiveIntParam extends PositiveParam { protected PositiveIntParam(String id) { super(id); } @Override public Value decode(NbtElement tag) { return new NumericValue(((NbtInt)tag).intValue()); } @Override public NbtElement toTag(Value value) { return NbtInt.of(NumericValue.asNumber(value, id).getInt()); } } public static class NonNegativeIntParam extends NumericParam { protected NonNegativeIntParam(String id) { super(id); } @Override public Value decode(NbtElement tag) { return new NumericValue(((NbtInt)tag).intValue()); } @Override public NbtElement toTag(Value value) { return NbtInt.of(NumericValue.asNumber(value, id).getInt()); } @Override public Value validate(Map options, MinecraftServer server, Value value) { Value ret = super.validate(options, server, value); if (((NumericValue)ret).getDouble()<0) throw new InternalExpressionException("'"+id+"' should be non-negative"); return ret; } } public static class NonNegativeFloatParam extends NumericParam { protected NonNegativeFloatParam(String id) { super(id); } @Override public Value decode(NbtElement tag) { return new NumericValue(((NbtFloat)tag).floatValue()); } @Override public NbtElement toTag(Value value) { return NbtFloat.of(NumericValue.asNumber(value, id).getFloat()); } @Override public Value validate(Map options, MinecraftServer server, Value value) { Value ret = super.validate(options, server, value); if (((NumericValue)ret).getDouble()<0) throw new InternalExpressionException("'"+id+"' should be non-negative"); return ret; } } public static class Vec3Param extends Param { private boolean roundsUpForBlocks; protected Vec3Param(String id, boolean doesRoundUpForBlocks) { super(id); roundsUpForBlocks = doesRoundUpForBlocks; } @Override public Value validate(Map options, MinecraftServer server, Value value) { return validate(this, options, value, roundsUpForBlocks); } public static Value validate(Param p, Map options, Value value, boolean roundsUp) { if (value instanceof BlockValue) { if (options.containsKey("follow")) throw new InternalExpressionException(p.id+" parameter cannot use blocks as positions for relative positioning due to 'follow' attribute being present"); BlockPos pos = ((BlockValue) value).getPos(); int offset = roundsUp?1:0; return ListValue.of( new NumericValue(pos.getX()+offset), new NumericValue(pos.getY()+offset), new NumericValue(pos.getZ()+offset) ); } if (value instanceof ListValue) { List values = ((ListValue) value).getItems(); if (values.size()!=3) throw new InternalExpressionException("'"+p.id+"' requires 3 numerical values"); for (Value component : values) { if (!(component instanceof NumericValue)) throw new InternalExpressionException("'"+p.id+"' requires 3 numerical values"); } return value; } if (value instanceof EntityValue) { if (options.containsKey("follow")) throw new InternalExpressionException(p.id+" parameter cannot use entity as positions for relative positioning due to 'follow' attribute being present"); Entity e = ((EntityValue) value).getEntity(); return ListValue.of( new NumericValue(e.getX()), new NumericValue(e.getY()), new NumericValue(e.getZ()) ); } CarpetSettings.LOG.error("Value: "+value.getString()); throw new InternalExpressionException("'"+p.id+"' requires a triple, block or entity to indicate position"); } public Value decode(NbtElement tag) { NbtList ctag = (NbtList)tag; return ListValue.of( new NumericValue(ctag.getDouble(0)), new NumericValue(ctag.getDouble(1)), new NumericValue(ctag.getDouble(2)) ); } @Override public NbtElement toTag(Value value) { List lv = ((ListValue)value).getItems(); NbtList tag = new NbtList(); tag.add(NbtDouble.of(NumericValue.asNumber(lv.get(0), "x").getDouble())); tag.add(NbtDouble.of(NumericValue.asNumber(lv.get(1), "y").getDouble())); tag.add(NbtDouble.of(NumericValue.asNumber(lv.get(2), "z").getDouble())); return tag; } } public static class PointsParam extends Param { public PointsParam(String id) { super(id); } @Override public Value validate(Map options, MinecraftServer server, Value value) { if (!(value instanceof ListValue)) throw new InternalExpressionException(id+ " parameter should be a list"); List points = new ArrayList<>(); for (Value point: ((ListValue) value).getItems()) points.add(Vec3Param.validate(this, options, point, false)); return ListValue.wrap(points); } public Value decode(NbtElement tag) { NbtList ltag = (NbtList)tag; List points = new ArrayList<>(); for (int i=0, ll = ltag.size(); i lv = ((ListValue)pointsValue).getItems(); NbtList ltag = new NbtList(); for (Value value : lv) { List coords = ((ListValue)value).getItems(); NbtList tag = new NbtList(); tag.add(NbtDouble.of(NumericValue.asNumber(lv.get(0), "x").getDouble())); tag.add(NbtDouble.of(NumericValue.asNumber(lv.get(1), "y").getDouble())); tag.add(NbtDouble.of(NumericValue.asNumber(lv.get(2), "z").getDouble())); ltag.add(tag); } return ltag; } } public static class ColorParam extends NumericParam { protected ColorParam(String id) { super(id); } public Value decode(NbtElement tag) { return new NumericValue(((NbtInt)tag).intValue()); } @Override public NbtElement toTag(Value value) { return NbtInt.of(NumericValue.asNumber(value, id).getInt()); } } public static class EntityParam extends Param { protected EntityParam(String id) { super(id); } @Override public NbtElement toTag(Value value) { return NbtInt.of(NumericValue.asNumber(value, id).getInt()); } @Override public Value validate(Map options, MinecraftServer server, Value value) { if (value instanceof EntityValue) return new NumericValue(((EntityValue) value).getEntity().getId()); ServerPlayerEntity player = EntityValue.getPlayerByValue(server, value); if (player == null) throw new InternalExpressionException(id+" parameter needs to represent an entity or player"); return new NumericValue(player.getId()); } public Value decode(NbtElement tag) { return new NumericValue(((NbtInt)tag).intValue()); } } private static boolean isStraight(Vec3d from, Vec3d to, double density) { if ( (from.x == to.x && from.y == to.y) || (from.x == to.x && from.z == to.z) || (from.y == to.y && from.z == to.z)) return from.distanceTo(to) / density > 20; return false; } private static int drawOptimizedParticleLine(List playerList, ParticleEffect particle, Vec3d from, Vec3d to, double density) { double distance = from.distanceTo(to); int particles = (int)(distance/density); Vec3d towards = to.subtract(from); int parts = 0; for (ServerPlayerEntity player : playerList) { ServerWorld world = player.getServerWorld(); world.spawnParticles(player, particle, true, (towards.x)/2+from.x, (towards.y)/2+from.y, (towards.z)/2+from.z, particles/3, towards.x/6, towards.y/6, towards.z/6, 0.0); world.spawnParticles(player, particle, true, from.x, from.y, from.z,1,0.0,0.0,0.0,0.0); world.spawnParticles(player, particle, true, to.x, to.y, to.z,1,0.0,0.0,0.0,0.0); parts += particles/3+2; } int divider = 6; while (particles/divider > 1) { int center = (divider*2)/3; int dev = 2*divider; for (ServerPlayerEntity player : playerList) { ServerWorld world = player.getServerWorld(); world.spawnParticles(player, particle, true, (towards.x)/center+from.x, (towards.y)/center+from.y, (towards.z)/center+from.z, particles/divider, towards.x/dev, towards.y/dev, towards.z/dev, 0.0); world.spawnParticles(player, particle, true, (towards.x)*(1.0-1.0/center)+from.x, (towards.y)*(1.0-1.0/center)+from.y, (towards.z)*(1.0-1.0/center)+from.z, particles/divider, towards.x/dev, towards.y/dev, towards.z/dev, 0.0); } parts += 2*particles/divider; divider = 2*divider; } return parts; } public static int drawParticleLine(List players, ParticleEffect particle, Vec3d from, Vec3d to, double density) { double distance = from.squaredDistanceTo(to); if (distance == 0) return 0; int pcount = 0; if (distance < 100) { Random rand = players.get(0).getServerWorld().random; int particles = (int)(distance/density)+1; Vec3d towards = to.subtract(from); for (int i = 0; i < particles; i++) { Vec3d at = from.add(towards.multiply( rand.nextDouble())); for (ServerPlayerEntity player : players) { player.getServerWorld().spawnParticles(player, particle, true, at.x, at.y, at.z, 1, 0.0, 0.0, 0.0, 0.0); pcount ++; } } return pcount; } if (isStraight(from, to, density)) return drawOptimizedParticleLine(players, particle, from, to, density); Vec3d incvec = to.subtract(from).multiply(2*density/MathHelper.sqrt(distance)); for (Vec3d delta = new Vec3d(0.0,0.0,0.0); delta.lengthSquared()