2267 lines
78 KiB
Java
2267 lines
78 KiB
Java
package carpet.script.utils;
|
|
|
|
import carpet.script.CarpetScriptServer;
|
|
import carpet.script.exception.InternalExpressionException;
|
|
import carpet.script.exception.ThrowStatement;
|
|
import carpet.script.exception.Throwables;
|
|
import carpet.script.external.Carpet;
|
|
import carpet.script.external.Vanilla;
|
|
import carpet.script.language.Sys;
|
|
import carpet.script.utils.shapes.ShapeDirection;
|
|
import carpet.script.value.AbstractListValue;
|
|
import carpet.script.value.BlockValue;
|
|
import carpet.script.value.BooleanValue;
|
|
import carpet.script.value.EntityValue;
|
|
import carpet.script.value.FormattedTextValue;
|
|
import carpet.script.value.ListValue;
|
|
import carpet.script.value.MapValue;
|
|
import carpet.script.value.NBTSerializableValue;
|
|
import carpet.script.value.NumericValue;
|
|
import carpet.script.value.StringValue;
|
|
import carpet.script.value.Value;
|
|
import carpet.script.value.ValueConversions;
|
|
|
|
import com.google.common.collect.Sets;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.Registry;
|
|
import net.minecraft.core.RegistryAccess;
|
|
import net.minecraft.core.particles.ParticleOptions;
|
|
import net.minecraft.core.registries.Registries;
|
|
import net.minecraft.nbt.ByteTag;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.nbt.DoubleTag;
|
|
import net.minecraft.nbt.FloatTag;
|
|
import net.minecraft.nbt.IntTag;
|
|
import net.minecraft.nbt.ListTag;
|
|
import net.minecraft.nbt.NbtOps;
|
|
import net.minecraft.nbt.NbtUtils;
|
|
import net.minecraft.nbt.StringTag;
|
|
import net.minecraft.nbt.Tag;
|
|
import net.minecraft.nbt.NumericTag;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.resources.RegistryOps;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.server.level.ServerPlayer;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.util.RandomSource;
|
|
import net.minecraft.world.entity.Entity;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.Block;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.phys.Vec3;
|
|
|
|
import javax.annotation.Nullable;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
import java.util.Random;
|
|
import java.util.Set;
|
|
import java.util.function.BiFunction;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Supplier;
|
|
|
|
import static java.util.Map.entry;
|
|
|
|
public class ShapeDispatcher
|
|
{
|
|
public record ShapeWithConfig(ExpiringShape shape, Map<String, Value> config)
|
|
{
|
|
}
|
|
|
|
public static ShapeWithConfig fromFunctionArgs(
|
|
MinecraftServer server, ServerLevel world,
|
|
List<Value> lv,
|
|
Set<ServerPlayer> playerSet
|
|
)
|
|
{
|
|
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<String, Value> params;
|
|
if (lv.size() == 3)
|
|
{
|
|
Value paramValue = lv.get(2);
|
|
if (paramValue instanceof final MapValue map)
|
|
{
|
|
params = new HashMap<>();
|
|
map.getMap().forEach((key, value) -> params.put(key.getString(), value));
|
|
}
|
|
else if (paramValue instanceof final ListValue list)
|
|
{
|
|
params = parseParams(list.getItems());
|
|
}
|
|
else
|
|
{
|
|
throw new InternalExpressionException("Parameters for 'draw_shape' need to be defined either in a list or a map");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
List<Value> 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.dimension().location().toString()));
|
|
params.putIfAbsent("duration", duration);
|
|
|
|
if (params.containsKey("player"))
|
|
{
|
|
Value players = params.get("player");
|
|
List<Value> playerVals;
|
|
if (players instanceof final ListValue list)
|
|
{
|
|
playerVals = list.getItems();
|
|
}
|
|
else
|
|
{
|
|
playerVals = Collections.singletonList(players);
|
|
}
|
|
for (Value pVal : playerVals)
|
|
{
|
|
ServerPlayer player = EntityValue.getPlayerByValue(server, pVal);
|
|
if (player == null)
|
|
{
|
|
throw new InternalExpressionException("'player' parameter needs to represent an existing player, not " + pVal.getString());
|
|
}
|
|
playerSet.add(player);
|
|
}
|
|
params.remove("player");
|
|
}
|
|
return new ShapeWithConfig(ShapeDispatcher.create(server, shapeType, params), params);
|
|
}
|
|
|
|
public static void sendShape(Collection<ServerPlayer> players, List<ShapeWithConfig> shapes, RegistryAccess regs)
|
|
{
|
|
List<ServerPlayer> clientPlayers = new ArrayList<>();
|
|
List<ServerPlayer> alternativePlayers = new ArrayList<>();
|
|
for (ServerPlayer player : players)
|
|
{
|
|
(Carpet.isValidCarpetPlayer(player) ? clientPlayers : alternativePlayers).add(player);
|
|
}
|
|
if (!clientPlayers.isEmpty())
|
|
{
|
|
ListTag tag = new ListTag();
|
|
int tagcount = 0;
|
|
for (ShapeWithConfig s : shapes)
|
|
{
|
|
tag.add(ExpiringShape.toTag(s.config(), regs)); // 4000 shapes limit boxes
|
|
if (tagcount++ > 1000)
|
|
{
|
|
tagcount = 0;
|
|
Tag finalTag = tag;
|
|
clientPlayers.forEach(p -> Vanilla.sendScarpetShapesDataToPlayer(p, finalTag));
|
|
tag = new ListTag();
|
|
}
|
|
}
|
|
Tag finalTag = tag;
|
|
if (!tag.isEmpty())
|
|
{
|
|
clientPlayers.forEach(p -> Vanilla.sendScarpetShapesDataToPlayer(p, finalTag));
|
|
}
|
|
}
|
|
if (!alternativePlayers.isEmpty())
|
|
{
|
|
List<Consumer<ServerPlayer>> alternatives = new ArrayList<>();
|
|
shapes.forEach(s -> alternatives.add(s.shape().alternative()));
|
|
alternativePlayers.forEach(p -> alternatives.forEach(a -> a.accept(p)));
|
|
}
|
|
}
|
|
|
|
public static ParticleOptions getParticleData(String name, RegistryAccess regs)
|
|
{
|
|
try
|
|
{
|
|
return ParticleParser.getEffect(name, regs);
|
|
}
|
|
catch (IllegalArgumentException e)
|
|
{
|
|
throw new ThrowStatement(name, Throwables.UNKNOWN_PARTICLE);
|
|
}
|
|
}
|
|
|
|
public static Map<String, Value> parseParams(List<Value> items)
|
|
{
|
|
// parses params from API function
|
|
if (items.size() % 2 == 1)
|
|
{
|
|
throw new InternalExpressionException("Shape parameters list needs to be of even size");
|
|
}
|
|
Map<String, Value> 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<String, Value> 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)));
|
|
});
|
|
BiFunction<Map<String, Value>, RegistryAccess, ExpiringShape> factory = ExpiringShape.shapeProviders.get(shapeType);
|
|
if (factory == null)
|
|
{
|
|
throw new InternalExpressionException("Unknown shape: " + shapeType);
|
|
}
|
|
return factory.apply(userParams, server.registryAccess());
|
|
}
|
|
|
|
// client
|
|
@Nullable
|
|
public static ExpiringShape fromTag(CompoundTag tag, Level level)
|
|
{
|
|
Map<String, Value> options = new HashMap<>();
|
|
for (String key : tag.getAllKeys())
|
|
{
|
|
Param decoder = Param.of.get(key);
|
|
if (decoder == null)
|
|
{
|
|
CarpetScriptServer.LOG.info("Unknown parameter for shape: " + key);
|
|
return null;
|
|
}
|
|
Value decodedValue = decoder.decode(tag.get(key), level);
|
|
options.put(key, decodedValue);
|
|
}
|
|
Value shapeValue = options.get("shape");
|
|
if (shapeValue == null)
|
|
{
|
|
CarpetScriptServer.LOG.info("Shape id missing in " + String.join(", ", tag.getAllKeys()));
|
|
return null;
|
|
}
|
|
BiFunction<Map<String, Value>, RegistryAccess, ExpiringShape> factory = ExpiringShape.shapeProviders.get(shapeValue.getString());
|
|
if (factory == null)
|
|
{
|
|
CarpetScriptServer.LOG.info("Unknown shape: " + shapeValue.getString());
|
|
return null;
|
|
}
|
|
try
|
|
{
|
|
return factory.apply(options, level.registryAccess());
|
|
}
|
|
catch (InternalExpressionException exc)
|
|
{
|
|
CarpetScriptServer.LOG.info(exc.getMessage());
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public abstract static class ExpiringShape
|
|
{
|
|
public static final Map<String, BiFunction<Map<String, Value>, RegistryAccess, ExpiringShape>> shapeProviders = new HashMap<>()
|
|
{{
|
|
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));
|
|
put("polygon", creator(Polyface::new));
|
|
put("block", creator(() -> new DisplayedSprite(false)));
|
|
put("item", creator(() -> new DisplayedSprite(true)));
|
|
}};
|
|
|
|
private static BiFunction<Map<String, Value>, RegistryAccess, ExpiringShape> creator(Supplier<ExpiringShape> shapeFactory)
|
|
{
|
|
return (o, regs) -> {
|
|
ExpiringShape shape = shapeFactory.get();
|
|
shape.fromOptions(o, regs);
|
|
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 ResourceKey<Level> shapeDimension;
|
|
protected boolean debug;
|
|
|
|
|
|
protected ExpiringShape()
|
|
{
|
|
}
|
|
|
|
public static CompoundTag toTag(Map<String, Value> params, RegistryAccess regs)
|
|
{
|
|
CompoundTag tag = new CompoundTag();
|
|
params.forEach((k, v) -> {
|
|
Tag valTag = Param.of.get(k).toTag(v, regs);
|
|
if (valTag != null)
|
|
{
|
|
tag.put(k, valTag);
|
|
}
|
|
});
|
|
return tag;
|
|
}
|
|
|
|
private void fromOptions(Map<String, Value> options, RegistryAccess regs)
|
|
{
|
|
Set<String> optionalParams = optionalParams();
|
|
Set<String> requiredParams = requiredParams();
|
|
Set<String> all = Sets.union(optionalParams, requiredParams);
|
|
if (!all.containsAll(options.keySet()))
|
|
{
|
|
throw new InternalExpressionException("Received unexpected parameters for shape: " + Sets.difference(options.keySet(), all));
|
|
}
|
|
if (!options.keySet().containsAll(requiredParams))
|
|
{
|
|
throw new InternalExpressionException("Missing required parameters for shape: " + Sets.difference(requiredParams, 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, regs);
|
|
}
|
|
|
|
|
|
protected void init(Map<String, Value> options, RegistryAccess regs)
|
|
{
|
|
|
|
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 = (fillColor >> 24 & 0xFF) / 255.0F;
|
|
this.fg = (fillColor >> 16 & 0xFF) / 255.0F;
|
|
this.fb = (fillColor >> 8 & 0xFF) / 255.0F;
|
|
this.fa = (fillColor & 0xFF) / 255.0F;
|
|
|
|
color = NumericValue.asNumber(options.getOrDefault("color", optional.get("color"))).getInt();
|
|
this.r = (color >> 24 & 0xFF) / 255.0F;
|
|
this.g = (color >> 16 & 0xFF) / 255.0F;
|
|
this.b = (color >> 8 & 0xFF) / 255.0F;
|
|
this.a = (color & 0xFF) / 255.0F;
|
|
|
|
debug = false;
|
|
if (options.containsKey("debug"))
|
|
{
|
|
debug = options.get("debug").getBoolean();
|
|
}
|
|
|
|
key = 0;
|
|
followEntity = -1;
|
|
shapeDimension = ResourceKey.create(Registries.DIMENSION, ResourceLocation.parse(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 Vec3 toAbsolute(Entity e, Vec3 vec, float partialTick)
|
|
{
|
|
return vec.add(
|
|
snapX ? (discreteX ? Mth.floor(e.getX()) : Mth.lerp(partialTick, e.xo, e.getX())) : 0.0,
|
|
snapY ? (discreteY ? Mth.floor(e.getY()) : Mth.lerp(partialTick, e.yo, e.getY())) : 0.0,
|
|
snapZ ? (discreteZ ? Mth.floor(e.getZ()) : Mth.lerp(partialTick, e.zo, e.getZ())) : 0.0
|
|
);
|
|
}
|
|
|
|
public Vec3 relativiseRender(Level world, Vec3 vec, float partialTick)
|
|
{
|
|
if (followEntity < 0)
|
|
{
|
|
return vec;
|
|
}
|
|
Entity e = world.getEntity(followEntity);
|
|
if (e == null)
|
|
{
|
|
return vec;
|
|
}
|
|
return toAbsolute(e, vec, partialTick);
|
|
}
|
|
|
|
public Vec3 vecFromValue(Value value)
|
|
{
|
|
if (!(value instanceof final ListValue list))
|
|
{
|
|
throw new InternalExpressionException("decoded value of " + value.getPrettyString() + " is not a triple");
|
|
}
|
|
List<Value> elements = list.getItems();
|
|
return new Vec3(
|
|
NumericValue.asNumber(elements.get(0)).getDouble(),
|
|
NumericValue.asNumber(elements.get(1)).getDouble(),
|
|
NumericValue.asNumber(elements.get(2)).getDouble()
|
|
);
|
|
}
|
|
|
|
protected ParticleOptions replacementParticle(RegistryAccess regs)
|
|
{
|
|
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, regs);
|
|
}
|
|
|
|
|
|
public abstract Consumer<ServerPlayer> alternative();
|
|
|
|
public long key(RegistryAccess regs)
|
|
{
|
|
if (key != 0)
|
|
{
|
|
return key;
|
|
}
|
|
key = calcKey(regs);
|
|
return key;
|
|
}
|
|
|
|
protected long calcKey(RegistryAccess regs)
|
|
{ // using FNV-1a algorithm
|
|
long hash = -3750763034362895579L;
|
|
hash ^= shapeDimension.hashCode();
|
|
hash *= 1099511628211L;
|
|
hash ^= color;
|
|
hash *= 1099511628211L;
|
|
hash ^= followEntity;
|
|
hash *= 1099511628211L;
|
|
hash ^= Boolean.hashCode(debug);
|
|
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('x').nextDouble();
|
|
private static final double ydif = new Random('y').nextDouble();
|
|
private static final double zdif = new Random('z').nextDouble();
|
|
|
|
int vec3dhash(Vec3 vec)
|
|
{
|
|
return vec.add(xdif, ydif, zdif).hashCode();
|
|
}
|
|
|
|
// list of params that need to be there
|
|
private final Set<String> required = Set.of("duration", "shape", "dim");
|
|
private final Map<String, Value> optional = Map.of(
|
|
"color", new NumericValue(-1),
|
|
"follow", new NumericValue(-1),
|
|
"line", new NumericValue(2.0),
|
|
"debug", Value.FALSE,
|
|
"fill", new NumericValue(0xffffff00),
|
|
"snap", new StringValue("xyz")
|
|
);
|
|
|
|
protected Set<String> requiredParams()
|
|
{
|
|
return required;
|
|
}
|
|
|
|
// list of params that can be there, with defaults
|
|
protected Set<String> 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<String> required = Set.of("pos", "text");
|
|
private final Map<String, Value> optional = Map.ofEntries(
|
|
entry("facing", new StringValue("player")),
|
|
entry("raise", new NumericValue(0)),
|
|
entry("tilt", new NumericValue(0)),
|
|
entry("lean", new NumericValue(0)),
|
|
entry("turn", new NumericValue(0)),
|
|
entry("indent", new NumericValue(0)),
|
|
entry("height", new NumericValue(0)),
|
|
entry("align", new StringValue("center")),
|
|
entry("size", new NumericValue(10)),
|
|
entry("value", Value.NULL),
|
|
entry("doublesided", new NumericValue(0)));
|
|
|
|
@Override
|
|
protected Set<String> requiredParams()
|
|
{
|
|
return Sets.union(super.requiredParams(), required);
|
|
}
|
|
|
|
@Override
|
|
protected Set<String> optionalParams()
|
|
{
|
|
return Sets.union(super.optionalParams(), optional.keySet());
|
|
}
|
|
|
|
public DisplayedText()
|
|
{
|
|
}
|
|
|
|
Vec3 pos;
|
|
String text;
|
|
int textcolor;
|
|
int textbck;
|
|
|
|
ShapeDirection facing;
|
|
float raise;
|
|
float tilt;
|
|
float lean;
|
|
float turn;
|
|
float size;
|
|
float indent;
|
|
int align;
|
|
float height;
|
|
Component value;
|
|
boolean doublesided;
|
|
|
|
@Override
|
|
protected void init(Map<String, Value> options, RegistryAccess regs)
|
|
{
|
|
super.init(options, regs);
|
|
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 = ShapeDirection.fromString(dir);
|
|
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<ServerPlayer> alternative()
|
|
{
|
|
return s -> {
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public long calcKey(RegistryAccess regs)
|
|
{
|
|
long hash = super.calcKey(regs);
|
|
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 DisplayedSprite extends ExpiringShape
|
|
{
|
|
private final Set<String> required = Set.of("pos");
|
|
private final Map<String, Value> optional = Map.ofEntries(
|
|
entry("facing", new StringValue("north")),
|
|
entry("tilt", new NumericValue(0)),
|
|
entry("lean", new NumericValue(0)),
|
|
entry("turn", new NumericValue(0)),
|
|
entry("scale", ListValue.fromTriple(1, 1, 1)),
|
|
entry("blocklight", new NumericValue(-1)),
|
|
entry("skylight", new NumericValue(-1)));
|
|
private final boolean isitem;
|
|
|
|
@Override
|
|
protected Set<String> requiredParams()
|
|
{
|
|
return Sets.union(Sets.union(super.requiredParams(), required), Set.of(isitem ? "item" : "block"));
|
|
}
|
|
|
|
@Override
|
|
protected Set<String> optionalParams()
|
|
{
|
|
return Sets.union(Sets.union(super.optionalParams(), optional.keySet()), isitem ? Set.of("variant") : Set.of());
|
|
}
|
|
|
|
public DisplayedSprite(boolean i)
|
|
{
|
|
isitem = i;
|
|
}
|
|
|
|
Vec3 pos;
|
|
|
|
ShapeDirection facing;
|
|
|
|
float tilt;
|
|
float lean;
|
|
float turn;
|
|
|
|
int blockLight;
|
|
int skyLight;
|
|
|
|
float scaleX = 1.0f;
|
|
float scaleY = 1.0f;
|
|
float scaleZ = 1.0f;
|
|
CompoundTag blockEntity;
|
|
BlockState blockState;
|
|
ItemStack item = null;
|
|
String itemTransformType;
|
|
|
|
@Override
|
|
protected void init(Map<String, Value> options, RegistryAccess regs)
|
|
{
|
|
super.init(options, regs);
|
|
pos = vecFromValue(options.get("pos"));
|
|
if (!this.isitem)
|
|
{
|
|
BlockValue block = (BlockValue) options.get("block");
|
|
blockState = block.getBlockState();
|
|
blockEntity = block.getData();
|
|
}
|
|
else
|
|
{
|
|
this.item = ItemStack.CODEC.parse(regs.createSerializationContext(NbtOps.INSTANCE), ((NBTSerializableValue) options.get("item")).getCompoundTag() ).result().orElse(null);
|
|
}
|
|
blockLight = NumericValue.asNumber(options.getOrDefault("blocklight", optional.get("blocklight"))).getInt();
|
|
if (blockLight > 15)
|
|
{
|
|
blockLight = 15;
|
|
}
|
|
skyLight = NumericValue.asNumber(options.getOrDefault("skylight", optional.get("skylight"))).getInt();
|
|
if (skyLight > 15)
|
|
{
|
|
skyLight = 15;
|
|
}
|
|
|
|
itemTransformType = "none";
|
|
if (options.containsKey("variant"))
|
|
{
|
|
itemTransformType = options.get("variant").getString().toLowerCase(Locale.ROOT);
|
|
}
|
|
|
|
String dir = options.getOrDefault("facing", optional.get("facing")).getString();
|
|
facing = ShapeDirection.fromString(dir);
|
|
|
|
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();
|
|
List<Value> scale = ((ListValue) options.getOrDefault("scale", optional.get("scale"))).unpack();
|
|
scaleY = NumericValue.asNumber(scale.get(1)).getFloat();
|
|
scaleX = NumericValue.asNumber(scale.get(0)).getFloat();
|
|
scaleZ = NumericValue.asNumber(scale.get(2)).getFloat();
|
|
}
|
|
|
|
@Override
|
|
public Consumer<ServerPlayer> alternative()
|
|
{
|
|
return p -> {
|
|
ParticleOptions particle;
|
|
Registry<Block> blocks = p.getServer().registryAccess().lookupOrThrow(Registries.BLOCK);
|
|
if (this.isitem)
|
|
{
|
|
if (Block.byItem(this.item.getItem()).defaultBlockState().isAir())
|
|
{
|
|
return;
|
|
}
|
|
particle = getParticleData("block_marker " + blocks.getKey(Block.byItem(this.item.getItem())), p.level().registryAccess());
|
|
}
|
|
else
|
|
{
|
|
particle = getParticleData("block_marker " + blocks.getKey(this.blockState.getBlock()), p.level().registryAccess());
|
|
}
|
|
|
|
Vec3 v = relativiseRender(p.level(), this.pos, 0);
|
|
p.serverLevel().sendParticles(p, particle, true, true, v.x, v.y, v.z, 1, 0.0, 0.0, 0.0, 0.0);
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public long calcKey(RegistryAccess regs)
|
|
{
|
|
long hash = super.calcKey(regs);
|
|
hash ^= 7;
|
|
hash *= 1099511628211L;
|
|
hash ^= Boolean.hashCode(isitem);
|
|
hash *= 1099511628211L;
|
|
hash ^= vec3dhash(pos);
|
|
hash *= 1099511628211L;
|
|
if (facing != null)
|
|
{
|
|
hash ^= facing.hashCode();
|
|
}
|
|
hash *= 1099511628211L;
|
|
hash ^= Float.hashCode(tilt);
|
|
hash *= 1099511628211L;
|
|
hash ^= Float.hashCode(lean);
|
|
hash *= 1099511628211L;
|
|
hash ^= Float.hashCode(turn);
|
|
hash *= 1099511628211L;
|
|
hash ^= Float.hashCode(scaleY);
|
|
hash *= 1099511628211L;
|
|
hash ^= Float.hashCode(scaleZ);
|
|
hash *= 1099511628211L;
|
|
hash ^= Float.hashCode(scaleX);
|
|
hash *= 1099511628211L;
|
|
hash ^= Float.hashCode(skyLight);
|
|
hash *= 1099511628211L;
|
|
hash ^= Float.hashCode(blockLight);
|
|
hash *= 1099511628211L;
|
|
if (blockEntity != null)
|
|
{
|
|
hash ^= blockEntity.toString().hashCode();
|
|
}
|
|
hash *= 1099511628211L;
|
|
if (blockState != null)
|
|
{
|
|
hash ^= blockState.hashCode();
|
|
}
|
|
hash *= 1099511628211L;
|
|
hash ^= ValueConversions.of(item, regs).getString().hashCode();
|
|
hash *= 1099511628211L;
|
|
hash ^= itemTransformType.hashCode();
|
|
hash *= 1099511628211L;
|
|
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
|
|
public static class Box extends ExpiringShape
|
|
{
|
|
private final Set<String> required = Set.of("from", "to");
|
|
private final Map<String, Value> optional = Map.of();
|
|
|
|
@Override
|
|
protected Set<String> requiredParams()
|
|
{
|
|
return Sets.union(super.requiredParams(), required);
|
|
}
|
|
|
|
@Override
|
|
protected Set<String> optionalParams()
|
|
{
|
|
return Sets.union(super.optionalParams(), optional.keySet());
|
|
}
|
|
|
|
public Box()
|
|
{
|
|
}
|
|
|
|
Vec3 from;
|
|
Vec3 to;
|
|
|
|
@Override
|
|
protected void init(Map<String, Value> options, RegistryAccess regs)
|
|
{
|
|
super.init(options, regs);
|
|
from = vecFromValue(options.get("from"));
|
|
to = vecFromValue(options.get("to"));
|
|
}
|
|
|
|
@Override
|
|
public Consumer<ServerPlayer> alternative()
|
|
{
|
|
double density = Math.max(2.0, from.distanceTo(to) / 50 / (a + 0.1));
|
|
return p ->
|
|
{
|
|
if (p.level().dimension() == shapeDimension)
|
|
{
|
|
particleMesh(
|
|
Collections.singletonList(p),
|
|
replacementParticle(p.level().registryAccess()),
|
|
density,
|
|
relativiseRender(p.level(), from, 0),
|
|
relativiseRender(p.level(), to, 0)
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public long calcKey(RegistryAccess regs)
|
|
{
|
|
long hash = super.calcKey(regs);
|
|
hash ^= 1;
|
|
hash *= 1099511628211L;
|
|
hash ^= vec3dhash(from);
|
|
hash *= 1099511628211L;
|
|
hash ^= vec3dhash(to);
|
|
hash *= 1099511628211L;
|
|
return hash;
|
|
}
|
|
|
|
public static int particleMesh(List<ServerPlayer> playerList, ParticleOptions particle, double density,
|
|
Vec3 from, Vec3 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 Vec3(x1, y1, z1), new Vec3(x1, y2, z1), density) +
|
|
drawParticleLine(playerList, particle, new Vec3(x1, y2, z1), new Vec3(x2, y2, z1), density) +
|
|
drawParticleLine(playerList, particle, new Vec3(x2, y2, z1), new Vec3(x2, y1, z1), density) +
|
|
drawParticleLine(playerList, particle, new Vec3(x2, y1, z1), new Vec3(x1, y1, z1), density) +
|
|
|
|
drawParticleLine(playerList, particle, new Vec3(x1, y1, z2), new Vec3(x1, y2, z2), density) +
|
|
drawParticleLine(playerList, particle, new Vec3(x1, y2, z2), new Vec3(x2, y2, z2), density) +
|
|
drawParticleLine(playerList, particle, new Vec3(x2, y2, z2), new Vec3(x2, y1, z2), density) +
|
|
drawParticleLine(playerList, particle, new Vec3(x2, y1, z2), new Vec3(x1, y1, z2), density) +
|
|
|
|
drawParticleLine(playerList, particle, new Vec3(x1, y1, z1), new Vec3(x1, y1, z2), density) +
|
|
drawParticleLine(playerList, particle, new Vec3(x1, y2, z1), new Vec3(x1, y2, z2), density) +
|
|
drawParticleLine(playerList, particle, new Vec3(x2, y2, z1), new Vec3(x2, y2, z2), density) +
|
|
drawParticleLine(playerList, particle, new Vec3(x2, y1, z1), new Vec3(x2, y1, z2), density);
|
|
}
|
|
}
|
|
|
|
public static class Polyface extends ExpiringShape
|
|
{
|
|
@Override
|
|
public long calcKey(RegistryAccess regs)
|
|
{
|
|
long hash = super.calcKey(regs);
|
|
hash ^= 6;
|
|
hash *= 1099511628211L;
|
|
hash ^= mode;
|
|
hash *= 1099511628211L;
|
|
hash ^= relative.hashCode();
|
|
hash *= 1099511628211L;
|
|
for (Vec3 i : vertexList)
|
|
{
|
|
hash ^= vec3dhash(i);
|
|
hash *= 1099511628211L;
|
|
}
|
|
hash ^= Boolean.hashCode(doublesided);
|
|
hash *= 1099511628211L;
|
|
hash ^= Integer.hashCode(vertexList.size());
|
|
hash *= 1099511628211L;
|
|
hash ^= Boolean.hashCode(inneredges);
|
|
hash *= 1099511628211L;
|
|
return hash;
|
|
}
|
|
|
|
ArrayList<Vec3> alterPoint = null;
|
|
final Random random = new Random();
|
|
boolean doublesided;
|
|
|
|
ArrayList<Vec3> getAlterPoint(ServerPlayer p)
|
|
{
|
|
if (alterPoint != null)
|
|
{
|
|
return alterPoint;
|
|
}
|
|
alterPoint = new ArrayList<>();
|
|
switch (mode)
|
|
{
|
|
case 4:
|
|
for (int i = 0; i < vertexList.size(); i++)
|
|
{
|
|
Vec3 vecA = vertexList.get(i);
|
|
if (relative.get(i))
|
|
{
|
|
vecA = relativiseRender(p.level(), vecA, 0);
|
|
}
|
|
i++;
|
|
Vec3 vecB = vertexList.get(i);
|
|
if (relative.get(i))
|
|
{
|
|
vecB = relativiseRender(p.level(), vecB, 0);
|
|
}
|
|
i++;
|
|
Vec3 vecC = vertexList.get(i);
|
|
if (relative.get(i))
|
|
{
|
|
vecC = relativiseRender(p.level(), vecC, 0);
|
|
}
|
|
alterDrawTriangles(vecA, vecB, vecC);
|
|
}
|
|
break;
|
|
case 6:
|
|
Vec3 vec0 = vertexList.get(0);
|
|
if (relative.get(0))
|
|
{
|
|
vec0 = relativiseRender(p.level(), vec0, 0);
|
|
}
|
|
Vec3 vec1 = vertexList.get(1);
|
|
if (relative.get(1))
|
|
{
|
|
vec1 = relativiseRender(p.level(), vec1, 0);
|
|
}
|
|
for (int i = 2; i < vertexList.size(); i++)
|
|
{
|
|
Vec3 vec = vertexList.get(i);
|
|
if (relative.get(i))
|
|
{
|
|
vec = relativiseRender(p.level(), vec, 0);
|
|
}
|
|
alterDrawTriangles(vec0, vec1, vec);
|
|
vec1 = vec;
|
|
}
|
|
break;
|
|
case 5:
|
|
Vec3 vecA = vertexList.get(0);
|
|
if (relative.get(0))
|
|
{
|
|
vecA = relativiseRender(p.level(), vecA, 0);
|
|
}
|
|
Vec3 vecB = vertexList.get(1);
|
|
if (relative.get(1))
|
|
{
|
|
vecB = relativiseRender(p.level(), vecB, 0);
|
|
}
|
|
for (int i = 2; i < vertexList.size(); i++)
|
|
{
|
|
Vec3 vec = vertexList.get(i);
|
|
if (relative.get(i))
|
|
{
|
|
vec = relativiseRender(p.level(), vec, 0);
|
|
}
|
|
alterDrawTriangles(vecA, vecB, vec);
|
|
vecA = vecB;
|
|
vecB = vec;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return alterPoint;
|
|
}
|
|
|
|
void alterDrawTriangles(Vec3 a, Vec3 b, Vec3 c)
|
|
{
|
|
Vec3 bb = b.subtract(a);
|
|
Vec3 cc = c.subtract(a);
|
|
for (int i = 0; i / 8 < bb.cross(cc).length(); i++)
|
|
{
|
|
double x = random.nextDouble();
|
|
double y = random.nextDouble();
|
|
alterPoint.add(a.add(bb.scale(x / 2)).add(cc.scale(y / 2)));
|
|
if (x + y < 1)
|
|
{
|
|
alterPoint.add(a.add(bb.scale((x + 1) / 2)).add(cc.scale(y / 2)));
|
|
}
|
|
else
|
|
{
|
|
x = 1 - x;
|
|
y = 1 - y;
|
|
alterPoint.add(a.add(bb.scale(x / 2)).add(cc.scale((y + 1) / 2)));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Consumer<ServerPlayer> alternative()
|
|
{
|
|
return p -> {
|
|
if (p.level().dimension() != this.shapeDimension)
|
|
{
|
|
return;
|
|
}
|
|
if (fa > 0.0f)
|
|
{
|
|
ParticleOptions locparticledata = getParticleData(String.format(Locale.ROOT, "dust %.1f %.1f %.1f %.1f", fr, fg, fb, fa), p.level().registryAccess());
|
|
for (Vec3 v : getAlterPoint(p))
|
|
{
|
|
p.serverLevel().sendParticles(p, locparticledata, true, true,
|
|
v.x, v.y, v.z, 1,
|
|
0.0, 0.0, 0.0, 0.0);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private final Set<String> required = Set.of("points");
|
|
private final Map<String, Value> optional = Map.ofEntries(
|
|
entry("relative", Value.NULL),
|
|
entry("mode", new StringValue("polygon")),
|
|
entry("inner", Value.TRUE),
|
|
entry("doublesided", Value.TRUE)
|
|
);
|
|
|
|
@Override
|
|
protected Set<String> requiredParams()
|
|
{
|
|
return Sets.union(super.requiredParams(), required);
|
|
}
|
|
|
|
@Override
|
|
protected Set<String> optionalParams()
|
|
{
|
|
return Sets.union(super.optionalParams(), optional.keySet());
|
|
}
|
|
|
|
ArrayList<Vec3> vertexList = new ArrayList<>();
|
|
int mode;
|
|
ArrayList<Boolean> relative = new ArrayList<>();
|
|
boolean inneredges;
|
|
|
|
@Override
|
|
protected void init(Map<String, Value> options, RegistryAccess regs)
|
|
{
|
|
super.init(options, regs);
|
|
|
|
doublesided = options.getOrDefault("doublesided", optional.get("doublesided")).getBoolean();
|
|
|
|
if (options.get("points") instanceof final AbstractListValue abl)
|
|
{
|
|
abl.forEach(x -> vertexList.add(vecFromValue(x)));
|
|
}
|
|
String modeOption = options.getOrDefault("mode", optional.get("mode")).getString();
|
|
inneredges = options.getOrDefault("inner", optional.get("inner")).getBoolean();
|
|
if (vertexList.size() < 3)
|
|
{
|
|
throw new IllegalArgumentException("Unexpected vertex list size: " + vertexList.size());
|
|
}
|
|
else if (vertexList.size() < 4)
|
|
{
|
|
inneredges = false;
|
|
}
|
|
if ("polygon".equals(modeOption))
|
|
{
|
|
this.mode = 6;
|
|
}
|
|
else if ("strip".equals(modeOption))
|
|
{
|
|
this.mode = 5;
|
|
}
|
|
else if ("triangles".equals(modeOption))
|
|
{
|
|
this.mode = 4;
|
|
if (vertexList.size() % 3 != 0)
|
|
{
|
|
throw new IllegalArgumentException("Unexpected vertex list size: " + vertexList.size());
|
|
}
|
|
}
|
|
if (options.getOrDefault("relative", optional.get("relative")) instanceof final AbstractListValue abl)
|
|
{
|
|
Iterator<Value> it = abl.iterator();
|
|
for (long i = 0L; i < vertexList.size(); i++)
|
|
{
|
|
relative.add(it.hasNext() && it.next().getBoolean());//if part of it got defined.
|
|
}
|
|
}
|
|
else if (options.getOrDefault("relative", optional.get("relative")) instanceof final BooleanValue boolv)
|
|
{
|
|
for (long i = 0L; i < vertexList.size(); i++)
|
|
{
|
|
relative.add(boolv.getBoolean());//if it is a boolean.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (long i = 0L; i < vertexList.size(); i++)
|
|
{
|
|
relative.add(true);//if there is nothing defined at all.
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
public static class Line extends ExpiringShape
|
|
{
|
|
private final Set<String> required = Set.of("from", "to");
|
|
private final Map<String, Value> optional = Map.of();
|
|
|
|
@Override
|
|
protected Set<String> requiredParams()
|
|
{
|
|
return Sets.union(super.requiredParams(), required);
|
|
}
|
|
|
|
@Override
|
|
protected Set<String> optionalParams()
|
|
{
|
|
return Sets.union(super.optionalParams(), optional.keySet());
|
|
}
|
|
|
|
private Line()
|
|
{
|
|
super();
|
|
}
|
|
|
|
Vec3 from;
|
|
Vec3 to;
|
|
|
|
@Override
|
|
protected void init(Map<String, Value> options, RegistryAccess regs)
|
|
{
|
|
super.init(options, regs);
|
|
from = vecFromValue(options.get("from"));
|
|
to = vecFromValue(options.get("to"));
|
|
}
|
|
|
|
@Override
|
|
public Consumer<ServerPlayer> alternative()
|
|
{
|
|
double density = Math.max(2.0, from.distanceTo(to) / 50) / (a + 0.1);
|
|
return p ->
|
|
{
|
|
if (p.level().dimension() == shapeDimension)
|
|
{
|
|
drawParticleLine(
|
|
Collections.singletonList(p),
|
|
replacementParticle(p.level().registryAccess()),
|
|
relativiseRender(p.level(), from, 0),
|
|
relativiseRender(p.level(), to, 0),
|
|
density
|
|
);
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public long calcKey(RegistryAccess regs)
|
|
{
|
|
long hash = super.calcKey(regs);
|
|
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<String> required = Set.of("center", "radius");
|
|
private final Map<String, Value> optional = Map.of("level", Value.ZERO);
|
|
|
|
@Override
|
|
protected Set<String> requiredParams()
|
|
{
|
|
return Sets.union(super.requiredParams(), required);
|
|
}
|
|
|
|
@Override
|
|
protected Set<String> optionalParams()
|
|
{
|
|
return Sets.union(super.optionalParams(), optional.keySet());
|
|
}
|
|
|
|
private Sphere()
|
|
{
|
|
super();
|
|
}
|
|
|
|
Vec3 center;
|
|
float radius;
|
|
int level;
|
|
int subdivisions;
|
|
|
|
@Override
|
|
protected void init(Map<String, Value> options, RegistryAccess regs)
|
|
{
|
|
super.init(options, regs);
|
|
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<ServerPlayer> alternative()
|
|
{
|
|
return p ->
|
|
{
|
|
int partno = Math.min(1000, 20 * subdivisions);
|
|
RandomSource rand = p.level().getRandom();
|
|
ServerLevel world = p.serverLevel();
|
|
ParticleOptions particle = replacementParticle(world.registryAccess());
|
|
|
|
Vec3 ccenter = relativiseRender(world, center, 0);
|
|
|
|
double ccx = ccenter.x;
|
|
double ccy = ccenter.y;
|
|
double ccz = ccenter.z;
|
|
|
|
for (int i = 0; i < partno; i++)
|
|
{
|
|
float theta = (float) Math.asin(rand.nextDouble() * 2.0 - 1.0);
|
|
float phi = (float) (2 * Math.PI * rand.nextDouble());
|
|
|
|
double x = radius * Mth.cos(theta) * Mth.cos(phi);
|
|
double y = radius * Mth.cos(theta) * Mth.sin(phi);
|
|
double z = radius * Mth.sin(theta);
|
|
world.sendParticles(p, particle, true, true,
|
|
x + ccx, y + ccy, z + ccz, 1,
|
|
0.0, 0.0, 0.0, 0.0);
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public long calcKey(RegistryAccess regs)
|
|
{
|
|
long hash = super.calcKey(regs);
|
|
hash ^= 3;
|
|
hash *= 1099511628211L;
|
|
hash ^= vec3dhash(center);
|
|
hash *= 1099511628211L;
|
|
hash ^= Double.hashCode(radius);
|
|
hash *= 1099511628211L;
|
|
hash ^= level;
|
|
hash *= 1099511628211L;
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
public static class Cylinder extends ExpiringShape
|
|
{
|
|
private final Set<String> required = Set.of("center", "radius");
|
|
private final Map<String, Value> optional = Map.of(
|
|
"level", Value.ZERO,
|
|
"height", Value.ZERO,
|
|
"axis", new StringValue("y")
|
|
);
|
|
|
|
@Override
|
|
protected Set<String> requiredParams()
|
|
{
|
|
return Sets.union(super.requiredParams(), required);
|
|
}
|
|
|
|
@Override
|
|
protected Set<String> optionalParams()
|
|
{
|
|
return Sets.union(super.optionalParams(), optional.keySet());
|
|
}
|
|
|
|
Vec3 center;
|
|
float height;
|
|
float radius;
|
|
int level;
|
|
int subdivisions;
|
|
Direction.Axis axis;
|
|
|
|
private Cylinder()
|
|
{
|
|
super();
|
|
}
|
|
|
|
@Override
|
|
protected void init(Map<String, Value> options, RegistryAccess regs)
|
|
{
|
|
super.init(options, regs);
|
|
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.byName(options.getOrDefault("axis", optional.get("axis")).getString());
|
|
}
|
|
|
|
|
|
@Override
|
|
public Consumer<ServerPlayer> alternative()
|
|
{
|
|
return p ->
|
|
{
|
|
int partno = (int) Math.min(1000, Math.sqrt(20 * subdivisions * (1 + height)));
|
|
RandomSource rand = p.level().getRandom();
|
|
ServerLevel world = p.serverLevel();
|
|
ParticleOptions particle = replacementParticle(world.registryAccess());
|
|
|
|
Vec3 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 < partno; i++)
|
|
{
|
|
float d = rand.nextFloat() * height;
|
|
float phi = (float) (2 * Math.PI * rand.nextDouble());
|
|
double x = radius * Mth.cos(phi);
|
|
double y = d;
|
|
double z = radius * Mth.sin(phi);
|
|
world.sendParticles(p, particle, true, true,x + ccx, y + ccy, z + ccz, 1, 0.0, 0.0, 0.0, 0.0);
|
|
}
|
|
}
|
|
else if (axis == Direction.Axis.X)
|
|
{
|
|
for (int i = 0; i < partno; i++)
|
|
{
|
|
float d = rand.nextFloat() * height;
|
|
float phi = (float) (2 * Math.PI * rand.nextDouble());
|
|
double x = d;
|
|
double y = radius * Mth.cos(phi);
|
|
double z = radius * Mth.sin(phi);
|
|
world.sendParticles(p, particle, true, true, x + ccx, y + ccy, z + ccz, 1, 0.0, 0.0, 0.0, 0.0);
|
|
}
|
|
}
|
|
else // Z
|
|
{
|
|
for (int i = 0; i < partno; i++)
|
|
{
|
|
float d = rand.nextFloat() * height;
|
|
float phi = (float) (2 * Math.PI * rand.nextDouble());
|
|
double x = radius * Mth.sin(phi);
|
|
double y = radius * Mth.cos(phi);
|
|
double z = d;
|
|
world.sendParticles(p, particle, true, true, x + ccx, y + ccy, z + ccz, 1, 0.0, 0.0, 0.0, 0.0);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public long calcKey(RegistryAccess regs)
|
|
{
|
|
long hash = super.calcKey(regs);
|
|
hash ^= 4;
|
|
hash *= 1099511628211L;
|
|
hash ^= vec3dhash(center);
|
|
hash *= 1099511628211L;
|
|
hash ^= Double.hashCode(radius);
|
|
hash *= 1099511628211L;
|
|
hash ^= Double.hashCode(height);
|
|
hash *= 1099511628211L;
|
|
hash ^= level;
|
|
hash *= 1099511628211L;
|
|
return hash;
|
|
}
|
|
}
|
|
|
|
|
|
public abstract static class Param
|
|
{
|
|
public static final Map<String, Param> of = new HashMap<>()
|
|
{{
|
|
put("mode", new StringChoiceParam("mode", "polygon", "strip", "triangles"));
|
|
put("relative", new OptionalBoolListParam("relative"));
|
|
put("inner", new BoolParam("inner"));
|
|
put("shape", new ShapeParam());
|
|
put("dim", new DimensionParam());
|
|
put("duration", new NonNegativeIntParam("duration"));
|
|
put("color", new ColorParam("color"));
|
|
put("follow", new EntityParam("follow"));
|
|
put("variant", new StringChoiceParam("variant",
|
|
"NONE",
|
|
"THIRD_PERSON_LEFT_HAND",
|
|
"THIRD_PERSON_RIGHT_HAND",
|
|
"FIRST_PERSON_LEFT_HAND",
|
|
"FIRST_PERSON_RIGHT_HAND",
|
|
"HEAD",
|
|
"GUI",
|
|
"GROUND",
|
|
"FIXED")
|
|
{
|
|
@Override
|
|
public Value validate(Map<String, Value> o, MinecraftServer s, Value v)
|
|
{
|
|
return super.validate(o, s, new StringValue(v.getString().toUpperCase(Locale.ROOT)));
|
|
}
|
|
});
|
|
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("width", new FloatParam("width"));
|
|
put("scale", new Vec3Param("scale", false)
|
|
{
|
|
@Override
|
|
public Value validate(java.util.Map<String, Value> options, MinecraftServer server, Value value)
|
|
{
|
|
if (value instanceof final NumericValue vn)
|
|
{
|
|
value = ListValue.of(vn, vn, vn);
|
|
}
|
|
return super.validate(options, server, value);
|
|
}
|
|
|
|
});
|
|
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("block", new BlockParam("block"));
|
|
put("item", new ItemParam("item"));
|
|
put("blocklight", new NonNegativeIntParam("blocklight"));
|
|
put("skylight", new NonNegativeIntParam("skylight"));
|
|
put("indent", new FloatParam("indent"));
|
|
put("raise", new FloatParam("raise"));
|
|
put("tilt", new FloatParam("tilt"));
|
|
put("lean", new FloatParam("lean"));
|
|
put("turn", new FloatParam("turn"));
|
|
put("facing", new StringChoiceParam("facing", "player", "camera", "north", "south", "east", "west", "up", "down"));
|
|
put("doublesided", new BoolParam("doublesided"));
|
|
put("debug", new BoolParam("debug"));
|
|
|
|
}};
|
|
protected String id;
|
|
|
|
protected Param(String id)
|
|
{
|
|
this.id = id;
|
|
}
|
|
|
|
@Nullable
|
|
public abstract Tag toTag(Value value, final RegistryAccess regs); //validates value, returning null if not necessary to keep it and serialize
|
|
|
|
public abstract Value validate(Map<String, Value> options, MinecraftServer server, Value value); // makes sure the value is proper
|
|
|
|
public abstract Value decode(Tag tag, Level level);
|
|
}
|
|
|
|
public static class OptionalBoolListParam extends Param
|
|
{
|
|
public OptionalBoolListParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
return value.toTag(true, regs);
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
if (tag instanceof final ListTag list)
|
|
{
|
|
return ListValue.wrap(list.stream().map(x -> BooleanValue.of(((NumericTag) x).getAsNumber().doubleValue() != 0)));
|
|
}
|
|
if (tag instanceof final ByteTag booltag)
|
|
{
|
|
return BooleanValue.of(booltag.getAsByte() != 0);
|
|
}
|
|
return Value.NULL;
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> options, MinecraftServer server, Value value)
|
|
{
|
|
if (value instanceof final AbstractListValue lv)
|
|
{
|
|
return ListValue.wrap(lv.unpack().stream().map(Value::getBoolean).map(BooleanValue::of));
|
|
}
|
|
if (value instanceof BooleanValue || value.isNull())
|
|
{
|
|
return value;
|
|
}
|
|
return BooleanValue.of(value.getBoolean());
|
|
}
|
|
}
|
|
|
|
public abstract static class StringParam extends Param
|
|
{
|
|
protected StringParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
return StringTag.valueOf(value.getString());
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
return new StringValue(tag.getAsString());
|
|
}
|
|
}
|
|
|
|
public static class BlockParam extends Param
|
|
{
|
|
|
|
protected BlockParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> options, MinecraftServer server, Value value)
|
|
{
|
|
if (value instanceof BlockValue)
|
|
{
|
|
return value;
|
|
}
|
|
return BlockValue.fromString(value.getString(), server.overworld());
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
if (value instanceof final BlockValue blv)
|
|
{
|
|
CompoundTag com = NbtUtils.writeBlockState(blv.getBlockState());
|
|
CompoundTag dataTag = blv.getData();
|
|
if (dataTag != null)
|
|
{
|
|
com.put("TileEntityData", dataTag);
|
|
}
|
|
return com;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
BlockState bs = NbtUtils.readBlockState(level.holderLookup(Registries.BLOCK), (CompoundTag) tag);
|
|
CompoundTag compoundTag2 = null;
|
|
if (((CompoundTag) tag).contains("TileEntityData", 10))
|
|
{
|
|
compoundTag2 = ((CompoundTag) tag).getCompound("TileEntityData");
|
|
}
|
|
return new BlockValue(bs, compoundTag2);
|
|
}
|
|
}
|
|
|
|
public static class ItemParam extends Param
|
|
{
|
|
|
|
protected ItemParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> options, MinecraftServer server, Value value)
|
|
{
|
|
ItemStack item = ValueConversions.getItemStackFromValue(value, true, server.registryAccess());
|
|
return new NBTSerializableValue(ItemStack.CODEC.encodeStart(server.registryAccess().createSerializationContext(NbtOps.INSTANCE), item).result().orElse(null));
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
return ((NBTSerializableValue) value).getTag();
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
return new NBTSerializableValue(tag);
|
|
}
|
|
}
|
|
|
|
public static class TextParam extends StringParam
|
|
{
|
|
protected TextParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> options, MinecraftServer server, Value value)
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
|
|
public static class FormattedTextParam extends StringParam
|
|
{
|
|
|
|
protected FormattedTextParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> options, MinecraftServer server, Value value)
|
|
{
|
|
if (!(value instanceof FormattedTextValue))
|
|
{
|
|
value = new FormattedTextValue(Component.literal(value.getString()));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
if (!(value instanceof FormattedTextValue))
|
|
{
|
|
value = new FormattedTextValue(Component.literal(value.getString()));
|
|
}
|
|
return StringTag.valueOf(((FormattedTextValue) value).serialize(regs));
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
return FormattedTextValue.deserialize(tag.getAsString(), level.registryAccess());
|
|
}
|
|
}
|
|
|
|
|
|
public static class StringChoiceParam extends StringParam
|
|
{
|
|
private final Set<String> options;
|
|
|
|
public StringChoiceParam(String id, String... options)
|
|
{
|
|
super(id);
|
|
this.options = Sets.newHashSet(options);
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public Value validate(Map<String, Value> 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<String, Value> options, MinecraftServer server, Value value)
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
|
|
public static class ShapeParam extends StringParam
|
|
{
|
|
protected ShapeParam()
|
|
{
|
|
super("shape");
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> options, MinecraftServer server, Value value)
|
|
{
|
|
String shape = value.getString();
|
|
if (!ExpiringShape.shapeProviders.containsKey(shape))
|
|
{
|
|
throw new InternalExpressionException("Unknown shape: " + shape);
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
|
|
public abstract static class NumericParam extends Param
|
|
{
|
|
protected NumericParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> 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 Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
return ByteTag.valueOf(value.getBoolean());
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
return BooleanValue.of(((ByteTag) tag).getAsByte() > 0);
|
|
}
|
|
}
|
|
|
|
public static class FloatParam extends NumericParam
|
|
{
|
|
protected FloatParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
return new NumericValue(((FloatTag) tag).getAsFloat());
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
return FloatTag.valueOf(NumericValue.asNumber(value, id).getFloat());
|
|
}
|
|
}
|
|
|
|
public abstract static class PositiveParam extends NumericParam
|
|
{
|
|
protected PositiveParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> 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(Tag tag, Level level)
|
|
{
|
|
return new NumericValue(((FloatTag) tag).getAsFloat());
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
return FloatTag.valueOf(NumericValue.asNumber(value, id).getFloat());
|
|
}
|
|
|
|
}
|
|
|
|
public static class PositiveIntParam extends PositiveParam
|
|
{
|
|
protected PositiveIntParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
return new NumericValue(((IntTag) tag).getAsInt());
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
return IntTag.valueOf(NumericValue.asNumber(value, id).getInt());
|
|
}
|
|
|
|
}
|
|
|
|
public static class NonNegativeIntParam extends NumericParam
|
|
{
|
|
protected NonNegativeIntParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
return new NumericValue(((IntTag) tag).getAsInt());
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
return IntTag.valueOf(NumericValue.asNumber(value, id).getInt());
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> 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(Tag tag, Level level)
|
|
{
|
|
return new NumericValue(((FloatTag) tag).getAsFloat());
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
return FloatTag.valueOf(NumericValue.asNumber(value, id).getFloat());
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> 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 final boolean roundsUpForBlocks;
|
|
|
|
protected Vec3Param(String id, boolean doesRoundUpForBlocks)
|
|
{
|
|
super(id);
|
|
roundsUpForBlocks = doesRoundUpForBlocks;
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> options, MinecraftServer server, Value value)
|
|
{
|
|
return validate(this, options, value, roundsUpForBlocks);
|
|
}
|
|
|
|
public static Value validate(Param p, Map<String, Value> options, Value value, boolean roundsUp)
|
|
{
|
|
if (value instanceof final BlockValue bv)
|
|
{
|
|
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 = bv.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 final ListValue list)
|
|
{
|
|
List<Value> values = list.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 final EntityValue ev)
|
|
{
|
|
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 = ev.getEntity();
|
|
return ListValue.of(
|
|
new NumericValue(e.getX()),
|
|
new NumericValue(e.getY()),
|
|
new NumericValue(e.getZ())
|
|
);
|
|
}
|
|
CarpetScriptServer.LOG.error("Value: " + value.getString());
|
|
throw new InternalExpressionException("'" + p.id + "' requires a triple, block or entity to indicate position");
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
ListTag ctag = (ListTag) tag;
|
|
return ListValue.of(
|
|
new NumericValue(ctag.getDouble(0)),
|
|
new NumericValue(ctag.getDouble(1)),
|
|
new NumericValue(ctag.getDouble(2))
|
|
);
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
List<Value> lv = ((ListValue) value).getItems();
|
|
ListTag tag = new ListTag();
|
|
tag.add(DoubleTag.valueOf(NumericValue.asNumber(lv.get(0), "x").getDouble()));
|
|
tag.add(DoubleTag.valueOf(NumericValue.asNumber(lv.get(1), "y").getDouble()));
|
|
tag.add(DoubleTag.valueOf(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<String, Value> options, MinecraftServer server, Value value)
|
|
{
|
|
if (!(value instanceof final ListValue list))
|
|
{
|
|
throw new InternalExpressionException(id + " parameter should be a list");
|
|
}
|
|
List<Value> points = new ArrayList<>();
|
|
for (Value point : list.getItems())
|
|
{
|
|
points.add(Vec3Param.validate(this, options, point, false));
|
|
}
|
|
return ListValue.wrap(points);
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
ListTag ltag = (ListTag) tag;
|
|
List<Value> points = new ArrayList<>();
|
|
for (int i = 0, ll = ltag.size(); i < ll; i++)
|
|
{
|
|
ListTag ptag = ltag.getList(i);
|
|
points.add(ListValue.of(
|
|
new NumericValue(ptag.getDouble(0)),
|
|
new NumericValue(ptag.getDouble(1)),
|
|
new NumericValue(ptag.getDouble(2))
|
|
));
|
|
}
|
|
return ListValue.wrap(points);
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value pointsValue, final RegistryAccess regs)
|
|
{
|
|
List<Value> lv = ((ListValue) pointsValue).getItems();
|
|
ListTag ltag = new ListTag();
|
|
for (Value value : lv)
|
|
{
|
|
List<Value> coords = ((ListValue) value).getItems();
|
|
ListTag tag = new ListTag();
|
|
tag.add(DoubleTag.valueOf(NumericValue.asNumber(coords.get(0), "x").getDouble()));
|
|
tag.add(DoubleTag.valueOf(NumericValue.asNumber(coords.get(1), "y").getDouble()));
|
|
tag.add(DoubleTag.valueOf(NumericValue.asNumber(coords.get(2), "z").getDouble()));
|
|
ltag.add(tag);
|
|
}
|
|
return ltag;
|
|
}
|
|
}
|
|
|
|
|
|
public static class ColorParam extends NumericParam
|
|
{
|
|
protected ColorParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
return new NumericValue(((IntTag) tag).getAsInt());
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
return IntTag.valueOf(NumericValue.asNumber(value, id).getInt());
|
|
}
|
|
}
|
|
|
|
public static class EntityParam extends Param
|
|
{
|
|
|
|
protected EntityParam(String id)
|
|
{
|
|
super(id);
|
|
}
|
|
|
|
@Override
|
|
public Tag toTag(Value value, final RegistryAccess regs)
|
|
{
|
|
return IntTag.valueOf(NumericValue.asNumber(value, id).getInt());
|
|
}
|
|
|
|
@Override
|
|
public Value validate(Map<String, Value> options, MinecraftServer server, Value value)
|
|
{
|
|
if (value instanceof final EntityValue ev)
|
|
{
|
|
return new NumericValue(ev.getEntity().getId());
|
|
}
|
|
ServerPlayer 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());
|
|
}
|
|
|
|
@Override
|
|
public Value decode(Tag tag, Level level)
|
|
{
|
|
return new NumericValue(((IntTag) tag).getAsInt());
|
|
}
|
|
}
|
|
|
|
private static boolean isStraight(Vec3 from, Vec3 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<ServerPlayer> playerList, ParticleOptions particle, Vec3 from, Vec3 to, double density)
|
|
{
|
|
double distance = from.distanceTo(to);
|
|
int particles = (int) (distance / density);
|
|
Vec3 towards = to.subtract(from);
|
|
int parts = 0;
|
|
for (ServerPlayer player : playerList)
|
|
{
|
|
ServerLevel world = player.serverLevel();
|
|
world.sendParticles(player, particle, true, 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.sendParticles(player, particle, true, true,
|
|
from.x, from.y, from.z, 1, 0.0, 0.0, 0.0, 0.0);
|
|
world.sendParticles(player, particle, true, 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 (ServerPlayer player : playerList)
|
|
{
|
|
ServerLevel world = player.serverLevel();
|
|
world.sendParticles(player, particle, true, 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.sendParticles(player, particle, true, 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<ServerPlayer> players, ParticleOptions particle, Vec3 from, Vec3 to, double density)
|
|
{
|
|
double distance = from.distanceToSqr(to);
|
|
if (distance == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
int pcount = 0;
|
|
if (distance < 100)
|
|
{
|
|
RandomSource rand = players.get(0).level().random;
|
|
int particles = (int) (distance / density) + 1;
|
|
Vec3 towards = to.subtract(from);
|
|
for (int i = 0; i < particles; i++)
|
|
{
|
|
Vec3 at = from.add(towards.scale(rand.nextDouble()));
|
|
for (ServerPlayer player : players)
|
|
{
|
|
player.serverLevel().sendParticles(player, particle, true, 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);
|
|
}
|
|
Vec3 incvec = to.subtract(from).scale(2 * density / Math.sqrt(distance));
|
|
|
|
for (Vec3 delta = new Vec3(0.0, 0.0, 0.0);
|
|
delta.lengthSqr() < distance;
|
|
delta = delta.add(incvec.scale(Sys.randomizer.nextFloat())))
|
|
{
|
|
for (ServerPlayer player : players)
|
|
{
|
|
player.serverLevel().sendParticles(player, particle, true, true,
|
|
delta.x + from.x, delta.y + from.y, delta.z + from.z, 1,
|
|
0.0, 0.0, 0.0, 0.0);
|
|
pcount++;
|
|
}
|
|
}
|
|
return pcount;
|
|
}
|
|
}
|