refactor breaking up API and language defs

This commit is contained in:
gnembon committed 2020-09-09 15:37:03 -03:00
1 parent 34d548523d
commit 59575ec607
22 files changed
+5596 -5282

No files matched your search

@@ -0,0 +1,936 @@
package carpet.script.api;
import carpet.CarpetServer;
import carpet.CarpetSettings;
import carpet.fakes.MinecraftServerInterface;
import carpet.fakes.StatTypeInterface;
import carpet.fakes.ThreadedAnvilChunkStorageInterface;
import carpet.helpers.FeatureGenerator;
import carpet.script.CarpetContext;
import carpet.script.CarpetScriptHost;
import carpet.script.Context;
import carpet.script.Expression;
import carpet.script.LazyValue;
import carpet.script.argument.BlockArgument;
import carpet.script.argument.FunctionArgument;
import carpet.script.argument.Vector3Argument;
import carpet.script.exception.ExitStatement;
import carpet.script.exception.ExpressionException;
import carpet.script.exception.InternalExpressionException;
import carpet.script.utils.ShapeDispatcher;
import carpet.script.value.BlockValue;
import carpet.script.value.EntityValue;
import carpet.script.value.FormattedTextValue;
import carpet.script.value.ListValue;
import carpet.script.value.NBTSerializableValue;
import carpet.script.value.NullValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.ThreadValue;
import carpet.script.value.Value;
import carpet.script.value.ValueConversions;
import carpet.utils.Messenger;
import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.decoration.ArmorStandEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.stat.Stat;
import net.minecraft.stat.StatType;
import net.minecraft.text.LiteralText;
import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.EulerAngle;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.World;
import org.apache.commons.lang3.tuple.Pair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletionException;
import java.util.stream.Collectors;
import static java.lang.Math.max;
import static java.lang.Math.min;
public class Auxiliary {
public static final String MARKER_STRING = "__scarpet_marker";
private static final Map<String, SoundCategory> mixerMap = Arrays.stream(SoundCategory.values()).collect(Collectors.toMap(SoundCategory::getName, k -> k));
public static String recognizeResource(Value value)
{
String origfile = value.getString();
String file = origfile.toLowerCase(Locale.ROOT).replaceAll("[^A-Za-z0-9\\-+_/]", "");
file = Arrays.stream(file.split("/+")).filter(s -> !s.isEmpty()).collect(Collectors.joining("/"));
if (file.isEmpty())
{
throw new InternalExpressionException("Cannot use "+origfile+" as resource name - must have some letters and numbers");
}
return file;
}
public static void apply(Expression expression)
{
expression.addLazyFunction("sound", -1, (c, t, lv) -> {
CarpetContext cc = (CarpetContext)c;
Identifier soundName = new Identifier(lv.get(0).evalValue(c).getString());
Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1);
if (Registry.SOUND_EVENT.get(soundName) == null)
throw new InternalExpressionException("No such sound: "+soundName.getPath());
float volume = 1.0F;
float pitch = 1.0F;
SoundCategory mixer = SoundCategory.MASTER;
if (lv.size() > 0+locator.offset)
{
volume = (float) NumericValue.asNumber(lv.get(0+locator.offset).evalValue(c)).getDouble();
if (lv.size() > 1+locator.offset)
{
pitch = (float) NumericValue.asNumber(lv.get(1+locator.offset).evalValue(c)).getDouble();
if (lv.size() > 2+locator.offset)
{
String mixerName = lv.get(2+locator.offset).evalValue(c).getString();
mixer = mixerMap.get(mixerName.toLowerCase(Locale.ROOT));
if (mixer == null) throw new InternalExpressionException(mixerName +" is not a valid mixer name");
}
}
}
Vec3d vec = locator.vec;
double d0 = Math.pow(volume > 1.0F ? (double)(volume * 16.0F) : 16.0D, 2.0D);
int count = 0;
for (ServerPlayerEntity player : cc.s.getWorld().getPlayers( (p) -> p.squaredDistanceTo(vec) < d0))
{
count++;
player.networkHandler.sendPacket(new PlaySoundIdS2CPacket(soundName, mixer, vec, volume, pitch));
}
int totalPlayed = count;
return (_c, _t) -> new NumericValue(totalPlayed);
});
expression.addLazyFunction("particle", -1, (c, t, lv) ->
{
CarpetContext cc = (CarpetContext)c;
MinecraftServer ms = cc.s.getMinecraftServer();
ServerWorld world = cc.s.getWorld();
Vector3Argument locator = Vector3Argument.findIn(cc, lv, 1);
String particleName = lv.get(0).evalValue(c).getString();
int count = 10;
double speed = 0;
float spread = 0.5f;
ServerPlayerEntity player = null;
if (lv.size() > locator.offset)
{
count = (int) NumericValue.asNumber(lv.get(locator.offset).evalValue(c)).getLong();
if (lv.size() > 1+locator.offset)
{
spread = (float) NumericValue.asNumber(lv.get(1+locator.offset).evalValue(c)).getDouble();
if (lv.size() > 2+locator.offset)
{
speed = NumericValue.asNumber(lv.get(2 + locator.offset).evalValue(c)).getDouble();
if (lv.size() > 3 + locator.offset) // should accept entity as well as long as it is player
{
player = ms.getPlayerManager().getPlayer(lv.get(3 + locator.offset).evalValue(c).getString());
}
}
}
}
ParticleEffect particle = ShapeDispatcher.getParticleData(particleName);
Vec3d vec = locator.vec;
if (player == null)
{
for (PlayerEntity p : (world.getPlayers()))
{
world.spawnParticles((ServerPlayerEntity)p, particle, true, vec.x, vec.y, vec.z, count,
spread, spread, spread, speed);
}
}
else
{
world.spawnParticles(player,
particle, true, vec.x, vec.y, vec.z, count,
spread, spread, spread, speed);
}
return (c_, t_) -> Value.TRUE;
});
expression.addLazyFunction("particle_line", -1, (c, t, lv) ->
{
CarpetContext cc = (CarpetContext)c;
ServerWorld world = cc.s.getWorld();
String particleName = lv.get(0).evalValue(c).getString();
ParticleEffect particle = ShapeDispatcher.getParticleData(particleName);
Vector3Argument pos1 = Vector3Argument.findIn(cc, lv, 1);
Vector3Argument pos2 = Vector3Argument.findIn(cc, lv, pos1.offset);
double density = 1.0;
ServerPlayerEntity player = null;
if (lv.size() > pos2.offset+0 )
{
density = NumericValue.asNumber(lv.get(pos2.offset+0).evalValue(c)).getDouble();
if (density <= 0)
{
throw new InternalExpressionException("Particle density should be positive");
}
if (lv.size() > pos2.offset+1)
{
Value playerValue = lv.get(pos2.offset+1).evalValue(c);
if (playerValue instanceof EntityValue)
{
Entity e = ((EntityValue) playerValue).getEntity();
if (!(e instanceof ServerPlayerEntity)) throw new InternalExpressionException("'particle_line' player argument has to be a player");
player = (ServerPlayerEntity) e;
}
else
{
player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString());
}
}
}
Value retval = new NumericValue(ShapeDispatcher.drawParticleLine(
(player == null)?world.getPlayers(): Collections.singletonList(player),
particle, pos1.vec, pos2.vec, density));
return (c_, t_) -> retval;
});
expression.addLazyFunction("particle_box", -1, (c, t, lv) ->
{
CarpetContext cc = (CarpetContext)c;
ServerWorld world = cc.s.getWorld();
String particleName = lv.get(0).evalValue(c).getString();
ParticleEffect particle = ShapeDispatcher.getParticleData(particleName);
Vector3Argument pos1 = Vector3Argument.findIn(cc, lv, 1);
Vector3Argument pos2 = Vector3Argument.findIn(cc, lv, pos1.offset);
double density = 1.0;
ServerPlayerEntity player = null;
if (lv.size() > pos2.offset+0 )
{
density = NumericValue.asNumber(lv.get(pos2.offset+0).evalValue(c)).getDouble();
if (density <= 0)
{
throw new InternalExpressionException("Particle density should be positive");
}
if (lv.size() > pos2.offset+1)
{
Value playerValue = lv.get(pos2.offset+1).evalValue(c);
if (playerValue instanceof EntityValue)
{
Entity e = ((EntityValue) playerValue).getEntity();
if (!(e instanceof ServerPlayerEntity)) throw new InternalExpressionException("'particle_box' player argument has to be a player");
player = (ServerPlayerEntity) e;
}
else
{
player = cc.s.getMinecraftServer().getPlayerManager().getPlayer(playerValue.getString());
}
}
}
Vec3d a = pos1.vec;
Vec3d b = pos2.vec;
Vec3d from = new Vec3d(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z));
Vec3d to = new Vec3d(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z));
int particleCount = ShapeDispatcher.Box.particleMesh(
player==null?world.getPlayers():Collections.singletonList(player),
particle, density, from, to
);
return (c_, t_) -> new NumericValue(particleCount);
});
// deprecated
expression.alias("particle_rect", "particle_box");
expression.addLazyFunction("draw_shape", -1, (c, t, lv) ->
{
CarpetContext cc = (CarpetContext)c;
ServerPlayerEntity player[] = {null};
List<Pair<ShapeDispatcher.ExpiringShape, Map<String,Value>>> shapes = new ArrayList<>();
if (lv.size() == 1) // bulk
{
Value specLoad = lv.get(0).evalValue(c);
if (!(specLoad instanceof ListValue)) throw new InternalExpressionException("In bulk mode - shapes need to be provided as a list of shape specs");
for (Value list : ((ListValue) specLoad).getItems())
{
if (!(list instanceof ListValue)) throw new InternalExpressionException("In bulk mode - shapes need to be provided as a list of shape specs");
shapes.add( ShapeDispatcher.fromFunctionArgs(cc, ((ListValue) list).getItems(), player));
}
}
else
{
List<Value> params = new ArrayList<>();
for (LazyValue v : lv) params.add(v.evalValue(c));
shapes.add(ShapeDispatcher.fromFunctionArgs(cc, params, player));
}
ShapeDispatcher.sendShape(
(player[0]==null)?cc.s.getWorld().getPlayers():Collections.singletonList(player[0]),
shapes
);
return LazyValue.TRUE;
});
expression.addLazyFunction("create_marker", -1, (c, t, lv) ->{
CarpetContext cc = (CarpetContext)c;
BlockState targetBlock = null;
Vector3Argument pointLocator;
boolean interactable = true;
String name;
try
{
Value nameValue = lv.get(0).evalValue(c);
name = nameValue instanceof NullValue ? "" : nameValue.getString();
pointLocator = Vector3Argument.findIn(cc, lv, 1, true);
if (lv.size()>pointLocator.offset)
{
BlockArgument blockLocator = BlockArgument.findIn(cc, lv, pointLocator.offset, true, true);
if (blockLocator.block != null) targetBlock = blockLocator.block.getBlockState();
if (lv.size() > blockLocator.offset)
{
interactable = lv.get(blockLocator.offset).evalValue(c, Context.BOOLEAN).getBoolean();
}
}
}
catch (IndexOutOfBoundsException e)
{
throw new InternalExpressionException("'create_marker' requires a name and three coordinates, with optional direction, and optional block on its head");
}
ArmorStandEntity armorstand = new ArmorStandEntity(EntityType.ARMOR_STAND, cc.s.getWorld());
double yoffset;
if (targetBlock == null && name.isEmpty())
{
yoffset = 0.0;
}
else if (!interactable && targetBlock == null)
{
yoffset = -0.41;
}
else
{
if (targetBlock==null)
{
yoffset = -armorstand.getHeight()-0.41;
}
else
{
yoffset = -armorstand.getHeight()+0.3;
}
}
armorstand.refreshPositionAndAngles(
pointLocator.vec.x,
//pointLocator.vec.y - ((!interactable && targetBlock == null)?0.41f:((targetBlock==null)?(armorstand.getHeight()+0.41):(armorstand.getHeight()-0.3))),
pointLocator.vec.y + yoffset,
pointLocator.vec.z,
(float)pointLocator.yaw,
(float) pointLocator.pitch
);
armorstand.addScoreboardTag(MARKER_STRING+"_"+((cc.host.getName()==null)?"":cc.host.getName()));
armorstand.addScoreboardTag(MARKER_STRING);
if (targetBlock != null)
armorstand.equipStack(EquipmentSlot.HEAD, new ItemStack(targetBlock.getBlock().asItem()));
if (!name.isEmpty())
{
armorstand.setCustomName(new LiteralText(name));
armorstand.setCustomNameVisible(true);
}
armorstand.setHeadRotation(new EulerAngle((int)pointLocator.pitch,0,0));
armorstand.setNoGravity(true);
armorstand.setInvisible(true);
armorstand.setInvulnerable(true);
armorstand.getDataTracker().set(ArmorStandEntity.ARMOR_STAND_FLAGS, (byte)(interactable?8 : 16|8));
cc.s.getWorld().spawnEntity(armorstand);
EntityValue result = new EntityValue(armorstand);
return (_c, _t) -> result;
});
expression.addLazyFunction("remove_all_markers", 0, (c, t, lv) -> {
CarpetContext cc = (CarpetContext)c;
int total = 0;
String markerName = MARKER_STRING+"_"+((cc.host.getName()==null)?"":cc.host.getName());
for (Entity e : cc.s.getWorld().getEntitiesByType(EntityType.ARMOR_STAND, (as) -> as.getScoreboardTags().contains(markerName)))
{
total ++;
e.remove();
}
int finalTotal = total;
return (_cc, _tt) -> new NumericValue(finalTotal);
});
expression.addLazyFunction("nbt", 1, (c, t, lv) -> {
Value ret = NBTSerializableValue.fromValue(lv.get(0).evalValue(c));
return (cc, tt) -> ret;
});
expression.addLazyFunction("escape_nbt", 1, (c, t, lv) -> {
Value v = lv.get(0).evalValue(c);
String string = v.getString();
Value ret = new StringValue(StringTag.escape(string));
return (cc, tt) -> ret;
});
expression.addLazyFunction("parse_nbt", 1, (c, t, lv) -> {
Value v = lv.get(0).evalValue(c);
if (v instanceof NBTSerializableValue)
{
Value parsed = ((NBTSerializableValue) v).toValue();
return (cc, tt) -> parsed;
}
NBTSerializableValue ret = NBTSerializableValue.parseString(v.getString());
if (ret == null)
return LazyValue.NULL;
Value parsed = ret.toValue();
return (cc, tt) -> parsed;
});
expression.addLazyFunction("encode_nbt", -1, (c, t, lv) -> {
int argSize = lv.size();
if (argSize==0 || argSize > 2) throw new InternalExpressionException("'encode_nbt' requires 1 or 2 parameters");
Value v = lv.get(0).evalValue(c);
boolean force = (argSize > 1) && lv.get(1).evalValue(c).getBoolean();
Tag tag;
try
{
tag = v.toTag(force);
}
catch (NBTSerializableValue.IncompatibleTypeException ignored)
{
throw new InternalExpressionException("cannot reliably encode to a tag the value of '"+ignored.val.getPrettyString()+"'");
}
Value tagValue = new NBTSerializableValue(tag);
return (cc, tt) -> tagValue;
});
//"overridden" native call that prints to stderr
expression.addLazyFunction("print", -1, (c, t, lv) ->
{
if (lv.size() == 0 || lv.size() > 2) throw new InternalExpressionException("'print' takes one or two arguments");
ServerCommandSource s = ((CarpetContext)c).s;
Value res = lv.get(0).evalValue(c);
if (lv.size() == 2)
{
ServerPlayerEntity player = EntityValue.getPlayerByValue(s.getMinecraftServer(), res);
s = player.getCommandSource();
res = lv.get(1).evalValue(c);
}
if (res instanceof FormattedTextValue)
{
s.sendFeedback(((FormattedTextValue) res).getText(), false);
}
else
{
if (s.getEntity() instanceof PlayerEntity)
{
Messenger.m((PlayerEntity) s.getEntity(), "w " + res.getString());
}
else
{
Messenger.m(s, "w " + res.getString());
}
}
Value finalRes = res;
return (_c, _t) -> finalRes; // pass through for variables
});
expression.addLazyFunction("format", -1, (c, t, lv) -> {
if (lv.size() == 0 ) throw new InternalExpressionException("'format' requires at least one component");
List<Value> values = lv.stream().map(lazy -> lazy.evalValue(c)).collect(Collectors.toList());
if (values.get(0) instanceof ListValue && values.size()==1)
values = ((ListValue) values.get(0)).getItems();
Value ret = new FormattedTextValue(Messenger.c(values.stream().map(Value::getString).toArray()));
return (cc, tt) -> ret;
});
//"overidden" native call to cancel if on main thread
expression.addLazyFunction("task_join", 1, (c, t, lv) -> {
if (((CarpetContext)c).s.getMinecraftServer().isOnThread())
throw new InternalExpressionException("'task_join' cannot be called from main thread to avoid deadlocks");
Value v = lv.get(0).evalValue(c);
if (!(v instanceof ThreadValue))
throw new InternalExpressionException("'task_join' could only be used with a task value");
Value ret = ((ThreadValue) v).join();
return (_c, _t) -> ret;
});
expression.addLazyFunctionWithDelegation("task_dock", 1, (c, t, expr, tok, lv) -> {
CarpetContext cc = (CarpetContext)c;
MinecraftServer server = cc.s.getMinecraftServer();
if (server.isOnThread()) return lv.get(0); // pass through for on thread tasks
Value[] result = new Value[]{Value.NULL};
RuntimeException[] internal = new RuntimeException[]{null};
try
{
((CarpetContext) c).s.getMinecraftServer().submitAndJoin(() ->
{
try
{
result[0] = lv.get(0).evalValue(c, t);
}
catch (ExpressionException exc)
{
internal[0] = exc;
}
catch (InternalExpressionException exc)
{
internal[0] = new ExpressionException(c, expr, tok, exc.getMessage(), exc.stack);
}
catch (ArithmeticException exc)
{
internal[0] = new ExpressionException(c, expr, tok, "Your math is wrong, "+exc.getMessage());
}
});
}
catch (CompletionException exc)
{
throw new InternalExpressionException("Error while executing docked task section, internal stack trace is gone");
}
if (internal[0] != null)
{
throw internal[0];
}
Value ret = result[0]; // preventing from lazy evaluating of the result in case a future completes later
return (_c, _t) -> ret;
// pass through placeholder
// implmenetation should dock the task on the main thread.
});
expression.addLazyFunction("run", 1, (c, t, lv) -> {
BlockPos target = ((CarpetContext)c).origin;
Vec3d posf = new Vec3d((double)target.getX()+0.5D,(double)target.getY(),(double)target.getZ()+0.5D);
ServerCommandSource s = ((CarpetContext)c).s;
try
{
Value retval = new NumericValue(s.getMinecraftServer().getCommandManager().execute(
s.withPosition(posf).withSilent().withLevel(CarpetSettings.runPermissionLevel), lv.get(0).evalValue(c).getString()));
return (c_, t_) -> retval;
}
catch (Exception ignored) {}
return LazyValue.NULL;
});
expression.addLazyFunction("save", 0, (c, t, lv) -> {
ServerCommandSource s = ((CarpetContext)c).s;
s.getMinecraftServer().getPlayerManager().saveAllPlayerData();
s.getMinecraftServer().save(true,true,true);
s.getWorld().getChunkManager().tick(() -> true);
CarpetSettings.LOG.warn("Saved chunks");
return (cc, tt) -> Value.TRUE;
});
expression.addLazyFunction("tick_time", 0, (c, t, lv) ->
{
Value time = new NumericValue(((CarpetContext) c).s.getMinecraftServer().getTicks());
return (cc, tt) -> time;
});
expression.addLazyFunction("world_time", 0, (c, t, lv) ->
{
Value time = new NumericValue(((CarpetContext) c).s.getWorld().getTime());
return (cc, tt) -> time;
});
expression.addLazyFunction("day_time", -1, (c, t, lv) ->
{
Value time = new NumericValue(((CarpetContext) c).s.getWorld().getTimeOfDay());
if (lv.size() > 0)
{
long newTime = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong();
if (newTime < 0) newTime = 0;
((CarpetContext) c).s.getWorld().setTimeOfDay(newTime);// setTimeOfDay(newTime);
}
return (cc, tt) -> time;
});
expression.addLazyFunction("last_tick_times", -1, (c, t, lv) ->
{
//assuming we are in the tick world section
// might be off one tick when run in the off tasks or asynchronously.
int currentReportedTick = ((CarpetContext) c).s.getMinecraftServer().getTicks()-1;
List<Value> ticks = new ArrayList<>(100);
final long[] tickArray = ((CarpetContext) c).s.getMinecraftServer().lastTickLengths;
for (int i=currentReportedTick+100; i > currentReportedTick; i--)
{
ticks.add(new NumericValue(((double)tickArray[i % 100])/1000000.0));
}
Value ret = ListValue.wrap(ticks);
return (cc, tt) -> ret;
});
expression.addLazyFunction("game_tick", -1, (c, t, lv) -> {
ServerCommandSource s = ((CarpetContext)c).s;
if (!s.getMinecraftServer().isOnThread()) throw new InternalExpressionException("Unable to run ticks from threads");
((MinecraftServerInterface)s.getMinecraftServer()).forceTick( () -> System.nanoTime()- CarpetServer.scriptServer.tickStart<50000000L);
if (lv.size()>0)
{
long ms_total = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong();
long end_expected = CarpetServer.scriptServer.tickStart+ms_total*1000000L;
long wait = end_expected-System.nanoTime();
if (wait > 0L)
{
try
{
Thread.sleep(wait/1000000L);
}
catch (InterruptedException ignored)
{
}
}
}
CarpetServer.scriptServer.tickStart = System.nanoTime(); // for the next tick
Thread.yield();
if(CarpetServer.scriptServer.stopAll)
throw new ExitStatement(Value.NULL);
return (cc, tt) -> Value.TRUE;
});
expression.addLazyFunction("seed", -1, (c, t, lv) -> {
ServerCommandSource s = ((CarpetContext)c).s;
Value ret = new NumericValue(s.getWorld().getSeed());
return (cc, tt) -> ret;
});
expression.addLazyFunction("relight", -1, (c, t, lv) ->
{
CarpetContext cc = (CarpetContext) c;
BlockArgument locator = BlockArgument.findIn(cc, lv, 0);
BlockPos pos = locator.block.getPos();
ServerWorld world = cc.s.getWorld();
((ThreadedAnvilChunkStorageInterface) world.getChunkManager().threadedAnvilChunkStorage).relightChunk(new ChunkPos(pos));
WorldAccess.forceChunkUpdate(pos, world);
return LazyValue.TRUE;
});
expression.addLazyFunction("current_dimension", 0, (c, t, lv) -> {
ServerCommandSource s = ((CarpetContext)c).s;
Value retval = ValueConversions.dimName(s.getWorld());
return (cc, tt) -> retval;
});
expression.addLazyFunction("view_distance", 0, (c, t, lv) -> {
ServerCommandSource s = ((CarpetContext)c).s;
Value retval = new NumericValue(s.getMinecraftServer().getPlayerManager().getViewDistance());
return (cc, tt) -> retval;
});
expression.addLazyFunction("in_dimension", 2, (c, t, lv) -> {
ServerCommandSource outerSource = ((CarpetContext)c).s;
Value dimensionValue = lv.get(0).evalValue(c);
ServerCommandSource innerSource = outerSource;
if (dimensionValue instanceof EntityValue)
{
innerSource = outerSource.withWorld((ServerWorld) ((EntityValue)dimensionValue).getEntity().getEntityWorld());
}
else if (dimensionValue instanceof BlockValue)
{
BlockValue bv = (BlockValue)dimensionValue;
if (bv.getWorld() != null)
{
innerSource = outerSource.withWorld(bv.getWorld());
}
else
{
throw new InternalExpressionException("'in_dimension' accepts only world-localized block arguments");
}
}
else
{
String dimString = dimensionValue.getString().toLowerCase(Locale.ROOT);
switch (dimString)
{
case "nether":
case "the_nether":
innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(World.NETHER)); //nether
break;
case "end":
case "the_end":
innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(World.END)); //end
break;
case "overworld":
case "over_world":
innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(World.OVERWORLD)); //ow
break;
default:
RegistryKey<World> dim = null;
Identifier id = new Identifier(dimString);
// not using RegistryKey.of since that one creates on check
for (RegistryKey<World> world : (outerSource.getMinecraftServer().getWorldRegistryKeys()))
{
if (id.equals(world.getValue()))
{
dim = world;
break;
}
}
if (dim == null)
throw new InternalExpressionException("Incorrect dimension string: "+dimString);
innerSource = outerSource.withWorld(outerSource.getMinecraftServer().getWorld(dim));
}
};
if (innerSource.getWorld() == outerSource.getWorld()) return lv.get(1);
Context newCtx = c.recreate();
((CarpetContext) newCtx).s = innerSource;
newCtx.variables = c.variables;
Value retval = lv.get(1).evalValue(newCtx);
return (cc, tt) -> retval;
});
expression.addLazyFunction("plop", 4, (c, t, lv) ->{
BlockArgument locator = BlockArgument.findIn((CarpetContext)c, lv, 0);
if (lv.size() <= locator.offset)
throw new InternalExpressionException("'plop' needs extra argument indicating what to plop");
String what = lv.get(locator.offset).evalValue(c).getString();
Value [] result = new Value[]{Value.NULL};
((CarpetContext)c).s.getMinecraftServer().submitAndJoin( () ->
{
Boolean res = FeatureGenerator.plop(what, ((CarpetContext) c).s.getWorld(), locator.block.getPos());
//Boolean res = FeatureGenerator.spawn(what, ((CarpetContext) c).s.getWorld(), locator.block.getPos());
if (res == null)
return;
if (what.equalsIgnoreCase("boulder")) // there might be more of those
WorldAccess.forceChunkUpdate(locator.block.getPos(), ((CarpetContext) c).s.getWorld());
result[0] = new NumericValue(res);
});
Value ret = result[0]; // preventing from lazy evaluating of the result in case a future completes later
return (_c, _t) -> ret;
});
expression.addLazyFunction("schedule", -1, (c, t, lv) -> {
if (lv.size()<2)
throw new InternalExpressionException("'schedule' should have at least 2 arguments, delay and call name");
long delay = NumericValue.asNumber(lv.get(0).evalValue(c)).getLong();
FunctionArgument functionArgument = FunctionArgument.findIn(c, expression.module, lv, 1, true, true);
CarpetServer.scriptServer.events.scheduleCall(
(CarpetContext) c,
functionArgument.function,
functionArgument.resolveArgs(c, Context.NONE),
delay
);
return (c_, t_) -> Value.TRUE;
});
expression.addLazyFunction("logger", -1, (c, t, lv) ->
{
//CarpetSettings.LOG.error("Standard Structures:");
//CarpetSettings.LOG.error(Registry.STRUCTURE_FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted(). collect(Collectors.joining(", ")));
//CarpetSettings.LOG.error("");
//CarpetSettings.LOG.error("Configured Structures:");
//CarpetSettings.LOG.error(BuiltinRegistries.CONFIGURED_STRUCTURE_FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted().collect(Collectors.joining(", ")));
//CarpetSettings.LOG.error("");
//CarpetSettings.LOG.error("Features:");
//CarpetSettings.LOG.error(Registry.FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted().collect(Collectors.joining(", ")));
//CarpetSettings.LOG.error("");
//CarpetSettings.LOG.error("Configured Features:");
//CarpetSettings.LOG.error(BuiltinRegistries.CONFIGURED_FEATURE.getIds().stream().map(i -> "`'"+NBTSerializableValue.nameFromRegistryId(i)+"'`").sorted().collect(Collectors.joining(", ")));
//CarpetSettings.LOG.error("");
//CarpetSettings.LOG.error("Custom:");
//CarpetSettings.LOG.error(FeatureGenerator.featureMap.keySet().stream().map(i -> "`'"+i+"'`").sorted().collect(Collectors.joining(", ")));
//CarpetSettings.LOG.error(Registry.ENTITY_TYPE.getIds().stream().sorted().map(ValueConversions::simplify).collect(Collectors.joining("`, `")));
Value res;
if(lv.size()==1)
{
res = lv.get(0).evalValue(c);
CarpetSettings.LOG.info(res.getString());
}
else if(lv.size()==2)
{
String level = lv.get(0).evalValue(c).getString().toLowerCase(Locale.ROOT);
res = lv.get(1).evalValue(c);
switch(level){
case "error": CarpetSettings.LOG.error(res.getString()); break;
case "warn": CarpetSettings.LOG.warn(res.getString()); break;
case "debug": CarpetSettings.LOG.debug(res.getString()); break;
case "fatal": CarpetSettings.LOG.fatal(res.getString()); break;
case "info": CarpetSettings.LOG.info(res.getString()); break;
default: throw new InternalExpressionException("Unknown log level for 'logger': "+level);
}
}
else throw new InternalExpressionException("logger takes 1 or 2 arguments");
return (_c, _t) -> res; // pass through for variables
});
expression.addLazyFunction("read_file", 2, (c, t, lv) -> {
String resource = recognizeResource(lv.get(0).evalValue(c));
String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
boolean shared = origtype.startsWith("shared_");
String type = shared ? origtype.substring(7) : origtype; //len(shared_)
if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt"))
throw new InternalExpressionException("Unsupported file type: "+origtype);
Value retVal;
if (type.equals("nbt"))
{
Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(resource, shared);
if (state == null) return LazyValue.NULL;
retVal = new NBTSerializableValue(state);
}
else
{
List<String> content = ((CarpetScriptHost) ((CarpetContext) c).host).readTextResource(resource, shared);
if (content == null) return LazyValue.NULL;
retVal = ListValue.wrap(content.stream().map(StringValue::new).collect(Collectors.toList()));
}
return (cc, tt) -> retVal;
});
expression.addLazyFunction("delete_file", 2, (c, t, lv) -> {
String resource = recognizeResource(lv.get(0).evalValue(c));
String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
boolean shared = origtype.startsWith("shared_");
String type = shared ? origtype.substring(7) : origtype; //len(shared_)
if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt"))
throw new InternalExpressionException("Unsupported file type: "+origtype);
boolean success = ((CarpetScriptHost)((CarpetContext)c).host).removeResourceFile(resource, shared, type);
return success?LazyValue.TRUE:LazyValue.FALSE;
});
expression.addLazyFunction("write_file", -1, (c, t, lv) -> {
if (lv.size() < 3) throw new InternalExpressionException("'write_file' requires three or more arguments");
String resource = recognizeResource(lv.get(0).evalValue(c));
String origtype = lv.get(1).evalValue(c).getString().toLowerCase(Locale.ROOT);
boolean shared = origtype.startsWith("shared_");
String type = shared ? origtype.substring(7) : origtype; //len(shared_)
if (!type.equals("raw") && !type.equals("text") && !type.equals("nbt"))
throw new InternalExpressionException("Unsupported file type: "+origtype);
boolean success;
if (type.equals("nbt"))
{
Value val = lv.get(2).evalValue(c);
NBTSerializableValue tagValue = (val instanceof NBTSerializableValue)
? (NBTSerializableValue) val
: new NBTSerializableValue(val.getString());
Tag tag = tagValue.getTag();
success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, resource, shared);
}
else
{
List<String> data = new ArrayList<>();
if (lv.size()==3)
{
Value val = lv.get(2).evalValue(c);
if (val instanceof ListValue)
{
List<Value> lval = ((ListValue) val).getItems();
lval.forEach(v -> data.add(v.getString()));
}
else
{
data.add(val.getString());
}
}
else
{
for(int i = 2; i < lv.size(); i++)
{
data.add(lv.get(i).evalValue(c).getString());
}
}
success = ((CarpetScriptHost) ((CarpetContext) c).host).appendLogFile(resource, shared, type, data);
}
return success?LazyValue.TRUE:LazyValue.FALSE;
});
//write_file
expression.addLazyFunction("load_app_data", -1, (c, t, lv) ->
{
String file = null;
boolean shared = false;
if (lv.size()>0)
{
file = recognizeResource(lv.get(0).evalValue(c));
if (lv.size() > 1)
{
shared = lv.get(1).evalValue(c).getBoolean();
}
}
Tag state = ((CarpetScriptHost)((CarpetContext)c).host).readFileTag(file, shared);
if (state == null)
return (cc, tt) -> Value.NULL;
Value retVal = new NBTSerializableValue(state);
return (cc, tt) -> retVal;
});
expression.addLazyFunction("store_app_data", -1, (c, t, lv) ->
{
if (lv.size() == 0)
throw new InternalExpressionException("'store_app_data' needs NBT tag and an optional file");
Value val = lv.get(0).evalValue(c);
String file = null;
boolean shared = false;
if (lv.size()>1)
{
file = recognizeResource(lv.get(1).evalValue(c));
if (lv.size() > 2)
{
shared = lv.get(2).evalValue(c).getBoolean();
}
}
NBTSerializableValue tagValue = (val instanceof NBTSerializableValue)
? (NBTSerializableValue) val
: new NBTSerializableValue(val.getString());
Tag tag = tagValue.getTag();
boolean success = ((CarpetScriptHost)((CarpetContext)c).host).writeTagFile(tag, file, shared);
return success?LazyValue.TRUE:LazyValue.FALSE;
});
expression.addLazyFunction("statistic", 3, (c, t, lv) ->
{
Value playerValue = lv.get(0).evalValue(c);
CarpetContext cc = (CarpetContext)c;
ServerPlayerEntity player = EntityValue.getPlayerByValue(cc.s.getMinecraftServer(), playerValue);
if (player == null) return LazyValue.NULL;
Identifier category;
Identifier statName;
try
{
category = new Identifier(lv.get(1).evalValue(c).getString());
statName = new Identifier(lv.get(2).evalValue(c).getString());
}
catch (InvalidIdentifierException e)
{
return LazyValue.NULL;
}
StatType<?> type = Registry.STAT_TYPE.get(category);
if (type == null)
return LazyValue.NULL;
Stat<?> stat = getStat(type, statName);
if (stat == null)
return LazyValue.NULL;
return (_c, _t) -> new NumericValue(player.getStatHandler().getStat(stat));
});
}
private static <T> Stat<T> getStat(StatType<T> type, Identifier id)
{
T key = type.getRegistry().get(id);
if (key == null || !((StatTypeInterface)type).hasStatCreated(key))
return null;
return type.getOrCreateStat(key);
}
}