diff --git a/src/main/java/carpet/commands/ScriptCommand.java b/src/main/java/carpet/commands/ScriptCommand.java
index 8941d296..7e6fb5f1 100644
--- a/src/main/java/carpet/commands/ScriptCommand.java
+++ b/src/main/java/carpet/commands/ScriptCommand.java
@@ -233,7 +233,7 @@ public class ScriptCommand
executes( (cc) -> listEvents(cc.getSource())).
then(literal("add_to").
then(argument("event", StringArgumentType.word()).
- suggests( (cc, bb) -> suggestMatching(CarpetServer.scriptServer.events.eventHandlers.keySet() ,bb)).
+ suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.byName.keySet() ,bb)).
then(argument("call", StringArgumentType.word()).
suggests( (cc, bb) -> suggestMatching(suggestFunctionCalls(cc), bb)).
executes( (cc) -> CarpetServer.scriptServer.events.addEvent(
@@ -253,9 +253,9 @@ public class ScriptCommand
)?1:0)))))).
then(literal("remove_from").
then(argument("event", StringArgumentType.word()).
- suggests( (cc, bb) -> suggestMatching(CarpetServer.scriptServer.events.eventHandlers.keySet() ,bb)).
+ suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.byName.keySet() ,bb)).
then(argument("call", StringArgumentType.greedyString()).
- suggests( (cc, bb) -> suggestMatching(CarpetServer.scriptServer.events.eventHandlers.get(StringArgumentType.getString(cc, "event")).callList.stream().map(CarpetEventServer.Callback::toString), bb)).
+ suggests( (cc, bb) -> suggestMatching(CarpetEventServer.Event.byName.get(StringArgumentType.getString(cc, "event")).handler.callList.stream().map(CarpetEventServer.Callback::toString), bb)).
executes( (cc) -> CarpetServer.scriptServer.events.removeEvent(
StringArgumentType.getString(cc, "event"),
StringArgumentType.getString(cc, "call")
@@ -293,14 +293,14 @@ public class ScriptCommand
private static int listEvents(ServerCommandSource source)
{
Messenger.m(source, "w Lists ALL event handlers:");
- for (String event: CarpetServer.scriptServer.events.eventHandlers.keySet())
+ for (CarpetEventServer.Event event: CarpetEventServer.Event.values())
{
boolean shownEvent = false;
- for (CarpetEventServer.Callback c: CarpetServer.scriptServer.events.eventHandlers.get(event).callList)
+ for (CarpetEventServer.Callback c: event.handler.callList)
{
if (!shownEvent)
{
- Messenger.m(source, "w Handlers for "+event+": ");
+ Messenger.m(source, "w Handlers for "+event.name+": ");
shownEvent = true;
}
Messenger.m(source, "w - "+c.udf+(c.host==null?"":" (from "+c.host+")"));
diff --git a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java
index 652b9ede..76751d20 100644
--- a/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java
+++ b/src/main/java/carpet/mixins/MinecraftServer_scarpetMixin.java
@@ -14,6 +14,10 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.function.BooleanSupplier;
+import static carpet.script.CarpetEventServer.Event.ENDER_TICK;
+import static carpet.script.CarpetEventServer.Event.NETHER_TICK;
+import static carpet.script.CarpetEventServer.Event.TICK;
+
@Mixin(MinecraftServer.class)
public abstract class MinecraftServer_scarpetMixin extends NonBlockingThreadExecutor implements MinecraftServerInterface
{
@@ -44,6 +48,8 @@ public abstract class MinecraftServer_scarpetMixin extends NonBlockingThreadExec
))
public void tickTasks(BooleanSupplier booleanSupplier_1, CallbackInfo ci)
{
- CarpetServer.scriptServer.events.onTick();
+ TICK.onTick();
+ NETHER_TICK.onTick();
+ ENDER_TICK.onTick();
}
}
diff --git a/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java b/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java
index bbe6f640..b62d34f7 100644
--- a/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java
+++ b/src/main/java/carpet/mixins/ServerPlayNetworkHandler_scarpetEventsMixin.java
@@ -1,6 +1,6 @@
package carpet.mixins;
-import carpet.CarpetServer;
+import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayNetworkHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.network.packet.ClientCommandC2SPacket;
@@ -16,8 +16,25 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import static carpet.script.CarpetEventServer.Event.PLAYER_ATTACKS_ENTITY;
+import static carpet.script.CarpetEventServer.Event.PLAYER_CLICKS_BLOCK;
+import static carpet.script.CarpetEventServer.Event.PLAYER_DEPLOYS_ELYTRA;
+import static carpet.script.CarpetEventServer.Event.PLAYER_INERACTSW_WITH_ENTITY;
+import static carpet.script.CarpetEventServer.Event.PLAYER_RELEASED_ITEM;
+import static carpet.script.CarpetEventServer.Event.PLAYER_RIDES;
+import static carpet.script.CarpetEventServer.Event.PLAYER_JUMPS;
+import static carpet.script.CarpetEventServer.Event.PLAYER_RIGHT_CLICKS_BLOCK;
+import static carpet.script.CarpetEventServer.Event.PLAYER_STARTS_SNEAKING;
+import static carpet.script.CarpetEventServer.Event.PLAYER_STARTS_SPRINTING;
+import static carpet.script.CarpetEventServer.Event.PLAYER_STOPS_SNEAKING;
+import static carpet.script.CarpetEventServer.Event.PLAYER_STOPS_SPRINTING;
+import static carpet.script.CarpetEventServer.Event.PLAYER_USES_ITEM;
+import static carpet.script.CarpetEventServer.Event.PLAYER_WAKES_UP;
+
+
@Mixin(ServerPlayNetworkHandler.class)
public class ServerPlayNetworkHandler_scarpetEventsMixin
{
@@ -26,9 +43,9 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
@Inject(method = "onPlayerInput", at = @At(value = "RETURN"))
private void checkMoves(PlayerInputC2SPacket p, CallbackInfo ci)
{
- if (p.getSideways() != 0.0F || p.getForward() != 0.0F || p.isJumping() || p.isSneaking())
+ if (PLAYER_RIDES.isNeeded() && (p.getSideways() != 0.0F || p.getForward() != 0.0F || p.isJumping() || p.isSneaking()))
{
- CarpetServer.scriptServer.events.onMountControls(player, p.getSideways(), p.getForward(), p.isJumping(), p.isSneaking());
+ PLAYER_RIDES.onMountControls(player, p.getSideways(), p.getForward(), p.isJumping(), p.isSneaking());
}
}
@@ -38,17 +55,37 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
))
private void onJump(PlayerMoveC2SPacket playerMoveC2SPacket_1, CallbackInfo ci)
{
- CarpetServer.scriptServer.events.onPlayerJumped(player);
+ PLAYER_JUMPS.onPlayerEvent(player);
}
@Inject(method = "onPlayerAction", at = @At(
value = "INVOKE",
- target = "Lnet/minecraft/server/network/ServerPlayerInteractionManager;processBlockBreakingAction(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/server/network/packet/PlayerActionC2SPacket$Action;Lnet/minecraft/util/math/Direction;I)V"
+ target = "Lnet/minecraft/server/network/ServerPlayerInteractionManager;processBlockBreakingAction(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/server/network/packet/PlayerActionC2SPacket$Action;Lnet/minecraft/util/math/Direction;I)V",
+ shift = At.Shift.AFTER
))
private void onClicked(PlayerActionC2SPacket packet, CallbackInfo ci)
{
if (packet.getAction() == PlayerActionC2SPacket.Action.START_DESTROY_BLOCK)
- CarpetServer.scriptServer.events.onBlockClicked(player, packet.getPos(), packet.getDirection());
+ PLAYER_CLICKS_BLOCK.onBlockAction(player, packet.getPos(), packet.getDirection());
+ }
+
+ @Redirect(method = "onPlayerAction", at = @At(
+ value = "INVOKE",
+ target = "Lnet/minecraft/server/network/ServerPlayerEntity;stopUsingItem()V"
+ ))
+ private void onStopUsing(ServerPlayerEntity serverPlayerEntity)
+ {
+ if (PLAYER_RELEASED_ITEM.isNeeded())
+ {
+ Hand hand = serverPlayerEntity.getActiveHand();
+ ItemStack stack = serverPlayerEntity.getActiveItem().copy();
+ serverPlayerEntity.stopUsingItem();
+ PLAYER_RELEASED_ITEM.onItemAction(player, hand, stack);
+ }
+ else
+ {
+ serverPlayerEntity.stopUsingItem();
+ }
}
@Inject(method = "onPlayerInteractBlock", at = @At(
@@ -57,9 +94,12 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
))
private void onBlockInteracted(PlayerInteractBlockC2SPacket playerInteractBlockC2SPacket_1, CallbackInfo ci)
{
- Hand hand = playerInteractBlockC2SPacket_1.getHand();
- BlockHitResult hitRes = playerInteractBlockC2SPacket_1.getHitY();
- CarpetServer.scriptServer.events.onRightClickBlock(player, hand, hitRes );
+ if (PLAYER_RIGHT_CLICKS_BLOCK.isNeeded())
+ {
+ Hand hand = playerInteractBlockC2SPacket_1.getHand();
+ BlockHitResult hitRes = playerInteractBlockC2SPacket_1.getHitY();
+ PLAYER_RIGHT_CLICKS_BLOCK.onBlockHit(player, hand, hitRes);
+ }
}
@Inject(method = "onPlayerInteractItem", at = @At(
@@ -68,7 +108,11 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
))
private void onItemClicked(PlayerInteractItemC2SPacket playerInteractItemC2SPacket_1, CallbackInfo ci)
{
- CarpetServer.scriptServer.events.onRightClick(player, playerInteractItemC2SPacket_1.getHand());
+ if (PLAYER_USES_ITEM.isNeeded())
+ {
+ Hand hand = playerInteractItemC2SPacket_1.getHand();
+ PLAYER_USES_ITEM.onItemAction(player, hand, player.getStackInHand(hand).copy());
+ }
}
@Inject(method = "onClientCommand", at = @At(
@@ -78,7 +122,7 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
))
private void onStartSneaking(ClientCommandC2SPacket clientCommandC2SPacket_1, CallbackInfo ci)
{
- CarpetServer.scriptServer.events.onStartSneaking(player);
+ PLAYER_STARTS_SNEAKING.onPlayerEvent(player);
}
@Inject(method = "onClientCommand", at = @At(
@@ -88,7 +132,7 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
))
private void onStopSneaking(ClientCommandC2SPacket clientCommandC2SPacket_1, CallbackInfo ci)
{
- CarpetServer.scriptServer.events.onStopSneaking(player);
+ PLAYER_STOPS_SNEAKING.onPlayerEvent(player);
}
@Inject(method = "onClientCommand", at = @At(
@@ -98,7 +142,7 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
))
private void onStartSprinting(ClientCommandC2SPacket clientCommandC2SPacket_1, CallbackInfo ci)
{
- CarpetServer.scriptServer.events.onStartSprinting(player);
+ PLAYER_STARTS_SPRINTING.onPlayerEvent(player);
}
@Inject(method = "onClientCommand", at = @At(
@@ -108,7 +152,7 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
))
private void onStopSprinting(ClientCommandC2SPacket clientCommandC2SPacket_1, CallbackInfo ci)
{
- CarpetServer.scriptServer.events.onStopSprinting(player);
+ PLAYER_STOPS_SPRINTING.onPlayerEvent(player);
}
@Inject(method = "onClientCommand", at = @At(
@@ -119,7 +163,7 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
private void onWakeUp(ClientCommandC2SPacket clientCommandC2SPacket_1, CallbackInfo ci)
{
//weird one - doesn't seem to work, maybe MP
- CarpetServer.scriptServer.events.onOutOfBed(player);
+ PLAYER_WAKES_UP.onPlayerEvent(player);
}
@Inject(method = "onClientCommand", at = @At(
@@ -129,7 +173,7 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
))
private void onElytraEngage(ClientCommandC2SPacket clientCommandC2SPacket_1, CallbackInfo ci)
{
- CarpetServer.scriptServer.events.onElytraDeploy(player);
+ PLAYER_DEPLOYS_ELYTRA.onPlayerEvent(player);
}
@Inject(method = "onPlayerInteractEntity", at = @At(
@@ -138,7 +182,7 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
))
private void onEntityInteract(PlayerInteractEntityC2SPacket playerInteractEntityC2SPacket_1, CallbackInfo ci)
{
- CarpetServer.scriptServer.events.onEntityInteracted(player, playerInteractEntityC2SPacket_1.getEntity(player.getServerWorld()), playerInteractEntityC2SPacket_1.getHand());
+ PLAYER_INERACTSW_WITH_ENTITY.onEntityAction(player, playerInteractEntityC2SPacket_1.getEntity(player.getServerWorld()), playerInteractEntityC2SPacket_1.getHand());
}
@Inject(method = "onPlayerInteractEntity", at = @At(
@@ -148,6 +192,6 @@ public class ServerPlayNetworkHandler_scarpetEventsMixin
private void onEntityAttack(PlayerInteractEntityC2SPacket playerInteractEntityC2SPacket_1, CallbackInfo ci)
{
//todo add hit and hand in the future
- CarpetServer.scriptServer.events.onEntityAttacked(player, playerInteractEntityC2SPacket_1.getEntity(player.getServerWorld()));
+ PLAYER_ATTACKS_ENTITY.onEntityAction(player, playerInteractEntityC2SPacket_1.getEntity(player.getServerWorld()), null);
}
}
diff --git a/src/main/java/carpet/mixins/ServerPlayerEntity_scarpetEventMixin.java b/src/main/java/carpet/mixins/ServerPlayerEntity_scarpetEventMixin.java
new file mode 100644
index 00000000..d291efb5
--- /dev/null
+++ b/src/main/java/carpet/mixins/ServerPlayerEntity_scarpetEventMixin.java
@@ -0,0 +1,46 @@
+package carpet.mixins;
+
+import com.mojang.authlib.GameProfile;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.util.Hand;
+import net.minecraft.world.World;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+import static carpet.script.CarpetEventServer.Event.PLAYER_FINISHED_USING_ITEM;
+
+@Mixin(ServerPlayerEntity.class)
+public abstract class ServerPlayerEntity_scarpetEventMixin extends PlayerEntity
+{
+ public ServerPlayerEntity_scarpetEventMixin(World world_1, GameProfile gameProfile_1)
+ {
+ super(world_1, gameProfile_1);
+ }
+
+ @Shadow protected abstract void method_6040();
+
+ @Redirect(method = "method_6040", at = @At(
+ value = "INVOKE",
+ target = "Lnet/minecraft/entity/player/PlayerEntity;method_6040()V"
+ ))
+ private void finishedUsingItem(PlayerEntity playerEntity)
+ {
+ if (PLAYER_FINISHED_USING_ITEM.isNeeded())
+ {
+ Hand hand = getActiveHand();
+ ItemStack stack = getActiveItem().copy();
+ // do vanilla
+ super.method_6040();
+ PLAYER_FINISHED_USING_ITEM.onItemAction((ServerPlayerEntity) (Object)this, hand, stack);
+ }
+ else
+ {
+ // do vanilla
+ super.method_6040();
+ }
+ }
+}
diff --git a/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java b/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java
index add9efd1..79e83cec 100644
--- a/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java
+++ b/src/main/java/carpet/mixins/ServerPlayerInteractionManager_scarpetEventsMixin.java
@@ -1,6 +1,5 @@
package carpet.mixins;
-import carpet.CarpetServer;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
@@ -14,6 +13,8 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+import static carpet.script.CarpetEventServer.Event.PLAYER_BREAK_BLOCK;
+
@Mixin(ServerPlayerInteractionManager.class)
public class ServerPlayerInteractionManager_scarpetEventsMixin
{
@@ -26,6 +27,6 @@ public class ServerPlayerInteractionManager_scarpetEventsMixin
))
private void onBlockBroken(BlockPos blockPos_1, CallbackInfoReturnable cir, BlockState blockState_1, BlockEntity be, Block b, boolean boolean_1)
{
- CarpetServer.scriptServer.events.onBlockBroken(player, blockPos_1, blockState_1);
+ PLAYER_BREAK_BLOCK.onBlockBroken(player, blockPos_1, blockState_1);
}
}
diff --git a/src/main/java/carpet/script/CarpetEventServer.java b/src/main/java/carpet/script/CarpetEventServer.java
index fb352c64..b1f0016c 100644
--- a/src/main/java/carpet/script/CarpetEventServer.java
+++ b/src/main/java/carpet/script/CarpetEventServer.java
@@ -7,13 +7,10 @@ import carpet.script.value.ListValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
-import carpet.settings.CarpetSettings;
import net.minecraft.block.BlockState;
import net.minecraft.entity.damage.DamageSource;
-import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.entity.Entity;
-import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Hand;
@@ -21,9 +18,6 @@ import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
-import net.minecraft.util.registry.Registry;
-import net.minecraft.world.World;
-import net.minecraft.world.dimension.DimensionType;
import java.util.ArrayList;
import java.util.Arrays;
@@ -36,10 +30,11 @@ import java.util.Map;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.stream.Collectors;
public class CarpetEventServer
{
+ public List scheduledCalls = new LinkedList<>();
+
public static class Callback
{
public String host;
@@ -146,9 +141,262 @@ public class CarpetEventServer
}
}
- public Map eventHandlers = new HashMap<>();
+ public enum Event
+ {
+ TICK("tick", new CallbackList(0))
+ {
+ @Override
+ public void onTick()
+ {
+ handler.call(Collections::emptyList, CarpetServer.minecraft_server::getCommandSource);
+ }
+ },
+ NETHER_TICK("tick_nether",new CallbackList(0))
+ {
+ @Override
+ public void onTick()
+ {
+ handler.call(Collections::emptyList, CarpetServer.minecraft_server::getCommandSource);
+ }
+ },
+ ENDER_TICK("tick_ender",new CallbackList(0))
+ {
+ @Override
+ public void onTick()
+ {
+ handler.call(Collections::emptyList, CarpetServer.minecraft_server::getCommandSource);
+ }
+ },
+ PLAYER_JUMPS("player_jumps", new CallbackList(1))
+ {
+ @Override
+ public void onPlayerEvent(ServerPlayerEntity player)
+ {
+ handler.call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
+ }
+ },
+ PLAYER_DEPLOYS_ELYTRA("player_deploys_elytra",new CallbackList(1))
+ {
+ @Override
+ public void onPlayerEvent(ServerPlayerEntity player)
+ {
+ handler.call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
+ }
+ },
+ PLAYER_WAKES_UP("player_wakes_up",new CallbackList(1))
+ {
+ @Override
+ public void onPlayerEvent(ServerPlayerEntity player)
+ {
+ handler.call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
+ }
+ },
+ PLAYER_RIDES("player_rides",new CallbackList(5))
+ {
+ @Override
+ public void onMountControls(ServerPlayerEntity player, float strafeSpeed, float forwardSpeed, boolean jumping, boolean sneaking)
+ {
+ handler.call( () -> Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> new NumericValue(forwardSpeed)),
+ ((c, t) -> new NumericValue(strafeSpeed)),
+ ((c, t) -> new NumericValue(jumping)),
+ ((c, t) -> new NumericValue(sneaking))
+ ), player::getCommandSource);
+ }
+ },
+ PLAYER_USES_ITEM("player_uses_item",new CallbackList(3))
+ {
+ public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack)
+ {
+ handler.call( () ->
+ {
+ //ItemStack itemstack = player.getStackInHand(enumhand);
+ return Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> ListValue.fromItemStack(itemstack)),
+ ((c, t) -> new StringValue(enumhand == Hand.MAIN_HAND ? "mainhand" : "offhand"))
+ );
+ }, player::getCommandSource);
+ }
+ },
+ PLAYER_CLICKS_BLOCK("player_clicks_block",new CallbackList(3))
+ {
+ public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack)
+ {
+ handler.call( () ->
+ {
+ return Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> ListValue.fromItemStack(itemstack)),
+ ((c, t) -> new StringValue(enumhand == Hand.MAIN_HAND ? "mainhand" : "offhand"))
+ );
+ }, player::getCommandSource);
+ }
+ },
+ PLAYER_RIGHT_CLICKS_BLOCK("player_right_clicks_block",new CallbackList(6))
+ {
+ @Override
+ public void onBlockHit(ServerPlayerEntity player, Hand enumhand, BlockHitResult hitRes)//ItemStack itemstack, Hand enumhand, BlockPos blockpos, Direction enumfacing, Vec3d vec3d)
+ {
+ handler.call( () ->
+ {
+ ItemStack itemstack = player.getStackInHand(enumhand);
+ BlockPos blockpos = hitRes.getBlockPos();
+ Direction enumfacing = hitRes.getSide();
+ Vec3d vec3d = hitRes.getPos().subtract(blockpos.getX(), blockpos.getY(), blockpos.getZ());
+ return Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> ListValue.fromItemStack(itemstack)),
+ ((c, t) -> new StringValue(enumhand == Hand.MAIN_HAND ? "mainhand" : "offhand")),
+ ((c, t) -> new BlockValue(null, player.getServerWorld(), blockpos)),
+ ((c, t) -> new StringValue(enumfacing.getName())),
+ ((c, t) -> ListValue.of(
+ new NumericValue(vec3d.x),
+ new NumericValue(vec3d.y),
+ new NumericValue(vec3d.z)
+ ))
+ );
+ }, player::getCommandSource);
+ }
+ },
+ PLAYER_BREAK_BLOCK("player_breaks_block",new CallbackList(2))
+ {
+ @Override
+ public void onBlockBroken(ServerPlayerEntity player, BlockPos pos, BlockState previousBS)
+ {
+ handler.call( () -> Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> new BlockValue(previousBS, player.getServerWorld(), pos))
+ ), player::getCommandSource);
+ }
+ },
+ PLAYER_INERACTSW_WITH_ENTITY("player_interacts_with_entity",new CallbackList(3))
+ {
+ @Override
+ public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand)
+ {
+ handler.call( () -> Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> new EntityValue(entity)),
+ ((c, t) -> new StringValue(enumhand==Hand.MAIN_HAND?"mainhand":"offhand"))
+ ), player::getCommandSource);
+ }
+ },
+ PLAYER_ATTACKS_ENTITY("player_attacks_entity",new CallbackList(2))
+ {
+ @Override
+ public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand)
+ {
+ handler.call( () -> Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> new EntityValue(entity))
+ ), player::getCommandSource);
+ }
+ },
+ PLAYER_STARTS_SNEAKING("player_starts_sneaking",new CallbackList(1))
+ {
+ @Override
+ public void onPlayerEvent(ServerPlayerEntity player)
+ {
+ handler.call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
+ }
+ },
+ PLAYER_STOPS_SNEAKING("player_stops_sneaking",new CallbackList(1))
+ {
+ @Override
+ public void onPlayerEvent(ServerPlayerEntity player)
+ {
+ handler.call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
+ }
+ },
+ PLAYER_STARTS_SPRINTING("player_starts_sprinting",new CallbackList(1))
+ {
+ @Override
+ public void onPlayerEvent(ServerPlayerEntity player)
+ {
+ handler.call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
+ }
+ },
+ PLAYER_STOPS_SPRINTING("player_stops_sprinting",new CallbackList(1))
+ {
+ @Override
+ public void onPlayerEvent(ServerPlayerEntity player)
+ {
+ handler.call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
+ }
+ },
- public List scheduledCalls = new LinkedList<>();
+ PLAYER_RELEASED_ITEM("player_releases_item",new CallbackList(3))
+ {
+ @Override
+ public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack)
+ {
+ // this.getStackInHand(this.getActiveHand()), this.activeItemStack)
+ handler.call( () ->
+ {
+ return Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> ListValue.fromItemStack(itemstack)),
+ ((c, t) -> new StringValue(enumhand == Hand.MAIN_HAND ? "mainhand" : "offhand"))
+ );
+ }, player::getCommandSource);
+ }
+ },
+ PLAYER_FINISHED_USING_ITEM("player_finishes_using_item",new CallbackList(3))
+ {
+ public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack)
+ {
+ // this.getStackInHand(this.getActiveHand()), this.activeItemStack)
+ handler.call( () ->
+ {
+ return Arrays.asList(
+ ((c, t) -> new EntityValue(player)),
+ ((c, t) -> ListValue.fromItemStack(itemstack)),
+ ((c, t) -> new StringValue(enumhand == Hand.MAIN_HAND ? "mainhand" : "offhand"))
+ );
+ }, player::getCommandSource);
+ }
+ };
+ // on projectile thrown (arrow from bows, crossbows, tridents, snoballs, e-pearls
+
+
+
+
+ public String name;
+ public static Map byName = new HashMap(){{
+ for (Event e: Event.values())
+ {
+ put(e.name, e);
+ }
+ }};
+ public CallbackList handler;
+ Event(String name, CallbackList eventHandler)
+ {
+ this.name = name;
+ this.handler = eventHandler;
+ }
+ public boolean isNeeded()
+ {
+ return handler.callList.size() > 0;
+ }
+ //stubs for calls just to ease calls in vanilla code so they don't need to deal with scarpet value types
+ public void onTick() { }
+ public void onPlayerEvent(ServerPlayerEntity player) { }
+ public void onMountControls(ServerPlayerEntity player, float strafeSpeed, float forwardSpeed, boolean jumping, boolean sneaking) { }
+ public void onItemAction(ServerPlayerEntity player, Hand enumhand, ItemStack itemstack) { }
+ public void onBlockAction(ServerPlayerEntity player, BlockPos blockpos, Direction facing) { }
+ public void onBlockHit(ServerPlayerEntity player, Hand enumhand, BlockHitResult hitRes) { }
+ public void onBlockBroken(ServerPlayerEntity player, BlockPos pos, BlockState previousBS) { }
+ public void onEntityAction(ServerPlayerEntity player, Entity entity, Hand enumhand) { }
+ }
+
+
+ public CarpetEventServer()
+ {
+ for (Event e: Event.values())
+ e.handler.callList.clear();
+ }
public void tick()
{
@@ -175,50 +423,28 @@ public class CarpetEventServer
scheduledCalls.add(new ScheduledCall(context, function, args, due));
}
-
- public CarpetEventServer()
- {
- eventHandlers.put("tick",new CallbackList(0));
- eventHandlers.put("tick_nether",new CallbackList(0));
- eventHandlers.put("tick_ender",new CallbackList(0));
- eventHandlers.put("player_jumps",new CallbackList(1));
- eventHandlers.put("player_deploys_elytra",new CallbackList(1));
- eventHandlers.put("player_wakes_up",new CallbackList(1));
- eventHandlers.put("player_rides",new CallbackList(5));
- eventHandlers.put("player_uses_item",new CallbackList(3));
- eventHandlers.put("player_clicks_block",new CallbackList(3));
- eventHandlers.put("player_right_clicks_block",new CallbackList(6));
- eventHandlers.put("player_breaks_block",new CallbackList(2));
- eventHandlers.put("player_interacts_with_entity",new CallbackList(3));
- eventHandlers.put("player_attacks_entity",new CallbackList(2));
- eventHandlers.put("player_starts_sneaking",new CallbackList(1));
- eventHandlers.put("player_stops_sneaking",new CallbackList(1));
- eventHandlers.put("player_starts_sprinting",new CallbackList(1));
- eventHandlers.put("player_stops_sprinting",new CallbackList(1));
- }
-
public boolean addEvent(String event, String host, String funName)
{
- if (!eventHandlers.containsKey(event))
+ if (!Event.byName.containsKey(event))
{
return false;
}
- return eventHandlers.get(event).addEventCall(host, funName);
+ return Event.byName.get(event).handler.addEventCall(host, funName);
}
public boolean removeEvent(String event, String funName)
{
- if (!eventHandlers.containsKey(event))
+ if (!Event.byName.containsKey(event))
return false;
Callback callback= decodeCallback(funName);
- eventHandlers.get(event).removeEventCall(callback.host, callback.udf);
+ Event.byName.get(event).handler.removeEventCall(callback.host, callback.udf);
return true;
}
public void removeAllHostEvents(String hostName)
{
- eventHandlers.forEach((e, a) -> a.removeAllCalls(hostName));
+ Event.byName.values().forEach((e) -> e.handler.removeAllCalls(hostName));
}
private Callback decodeCallback(String funName)
@@ -235,7 +461,7 @@ public class CarpetEventServer
private ScheduledCall makeEventCall(CarpetContext cc, String function, List extraArgs, int argCount)
{
UserDefinedFunction udf = cc.host.globalFunctions.get(function);
- if (udf == null || (udf.getArguments().size()-extraArgs.size()) != argCount)
+ if (udf == null || (udf.getArguments().size()-(extraArgs == null ? 0 : extraArgs.size())) != argCount)
{
// call won't match arguments
return null;
@@ -250,131 +476,6 @@ public class CarpetEventServer
return new ScheduledCall(cc, function, lazyArgs, 0);
}
- public void onTick()
- {
- eventHandlers.get("tick").call(Collections::emptyList, CarpetServer.minecraft_server::getCommandSource);
- eventHandlers.get("tick_nether").call(Collections::emptyList, () ->
- CarpetServer.minecraft_server.getCommandSource().withWorld(CarpetServer.minecraft_server.getWorld(DimensionType.THE_NETHER)));
- eventHandlers.get("tick_ender").call(Collections::emptyList, () ->
- CarpetServer.minecraft_server.getCommandSource().withWorld(CarpetServer.minecraft_server.getWorld(DimensionType.THE_END)));
- }
-
- public void onPlayerJumped(PlayerEntity player)
- {
- eventHandlers.get("player_jumps").call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
- }
-
- public void onElytraDeploy(ServerPlayerEntity player)
- {
- eventHandlers.get("player_deploys_elytra").call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
- }
-
- public void onOutOfBed(ServerPlayerEntity player)
- {
- eventHandlers.get("player_wakes_up").call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
- }
-
- public void onMountControls(PlayerEntity player, float strafeSpeed, float forwardSpeed, boolean jumping, boolean sneaking)
- {
- eventHandlers.get("player_rides").call( () -> Arrays.asList(
- ((c, t) -> new EntityValue(player)),
- ((c, t) -> new NumericValue(forwardSpeed)),
- ((c, t) -> new NumericValue(strafeSpeed)),
- ((c, t) -> new NumericValue(jumping)),
- ((c, t) -> new NumericValue(sneaking))
- ), player::getCommandSource);
- }
-
- public void onRightClick(ServerPlayerEntity player, Hand enumhand)
- {
- eventHandlers.get("player_uses_item").call( () ->
- {
- ItemStack itemstack = player.getStackInHand(enumhand);
- return Arrays.asList(
- ((c, t) -> new EntityValue(player)),
- ((c, t) -> ListValue.fromItemStack(itemstack)),
- ((c, t) -> new StringValue(enumhand == Hand.MAIN_HAND ? "mainhand" : "offhand"))
- );
- }, player::getCommandSource);
- }
-
- public void onBlockClicked(ServerPlayerEntity player, BlockPos blockpos, Direction facing)
- {
- eventHandlers.get("player_clicks_block").call( () -> Arrays.asList(
- ((c, t) -> new EntityValue(player)),
- ((c, t) -> new BlockValue(null, player.getServerWorld(), blockpos)),
- ((c, t) -> new StringValue(facing.getName()))
- ), player::getCommandSource);
- }
-
- public void onRightClickBlock(ServerPlayerEntity player, Hand enumhand, BlockHitResult hitRes)//ItemStack itemstack, Hand enumhand, BlockPos blockpos, Direction enumfacing, Vec3d vec3d)
- {
- eventHandlers.get("player_right_clicks_block").call( () ->
- {
- ItemStack itemstack = player.getStackInHand(enumhand);
- BlockPos blockpos = hitRes.getBlockPos();
- Direction enumfacing = hitRes.getSide();
- Vec3d vec3d = hitRes.getPos().subtract(blockpos.getX(), blockpos.getY(), blockpos.getZ());
- return Arrays.asList(
- ((c, t) -> new EntityValue(player)),
- ((c, t) -> ListValue.fromItemStack(itemstack)),
- ((c, t) -> new StringValue(enumhand == Hand.MAIN_HAND ? "mainhand" : "offhand")),
- ((c, t) -> new BlockValue(null, player.getServerWorld(), blockpos)),
- ((c, t) -> new StringValue(enumfacing.getName())),
- ((c, t) -> ListValue.of(
- new NumericValue(vec3d.x),
- new NumericValue(vec3d.y),
- new NumericValue(vec3d.z)
- ))
- );
- }, player::getCommandSource);
- }
-
- public void onBlockBroken(ServerPlayerEntity player, BlockPos pos, BlockState previousBS)
- {
- eventHandlers.get("player_breaks_block").call( () -> Arrays.asList(
- ((c, t) -> new EntityValue(player)),
- ((c, t) -> new BlockValue(previousBS, player.getServerWorld(), pos))
- ), player::getCommandSource);
- }
-
- public void onEntityInteracted(ServerPlayerEntity player, Entity entity, Hand enumhand)
- {
- eventHandlers.get("player_interacts_with_entity").call( () -> Arrays.asList(
- ((c, t) -> new EntityValue(player)),
- ((c, t) -> new EntityValue(entity)),
- ((c, t) -> new StringValue(enumhand==Hand.MAIN_HAND?"mainhand":"offhand"))
- ), player::getCommandSource);
- }
-
- public void onEntityAttacked(ServerPlayerEntity player, Entity entity)
- {
- eventHandlers.get("player_attacks_entity").call( () -> Arrays.asList(
- ((c, t) -> new EntityValue(player)),
- ((c, t) -> new EntityValue(entity))
- ), player::getCommandSource);
- }
-
- public void onStartSneaking(ServerPlayerEntity player)
- {
- eventHandlers.get("player_starts_sneaking").call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
- }
-
- public void onStopSneaking(ServerPlayerEntity player)
- {
- eventHandlers.get("player_stops_sneaking").call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
- }
-
- public void onStartSprinting(ServerPlayerEntity player)
- {
- eventHandlers.get("player_starts_sprinting").call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
- }
-
- public void onStopSprinting(ServerPlayerEntity player)
- {
- eventHandlers.get("player_stops_sprinting").call( () -> Arrays.asList(((c, t) -> new EntityValue(player))), player::getCommandSource);
- }
-
public ScheduledCall makeDeathCall(CarpetContext cc, String function, List extraArgs)
{
return makeEventCall(cc, function, extraArgs, 2);
@@ -389,11 +490,9 @@ public class CarpetEventServer
}
public ScheduledCall makeDamageCall(CarpetContext cc, String function, List extraArgs)
{
- return makeEventCall(cc, function, extraArgs, 3);
+ return makeEventCall(cc, function, extraArgs, 4);
}
-
-
public void onEntityDeath(ScheduledCall call, Entity e, String reason)
{
call.execute( Arrays.asList( (c, t)-> new EntityValue(e), (c, t)-> new StringValue(reason)));
@@ -415,7 +514,4 @@ public class CarpetEventServer
(c, t) -> source.getAttacker()==null?Value.NULL:new EntityValue(source.getAttacker())
));
}
-
-
-
}
diff --git a/src/main/java/carpet/script/CarpetExpression.java b/src/main/java/carpet/script/CarpetExpression.java
index 5fa422e5..42eb003c 100644
--- a/src/main/java/carpet/script/CarpetExpression.java
+++ b/src/main/java/carpet/script/CarpetExpression.java
@@ -100,7 +100,7 @@ import static java.lang.Math.sqrt;
/**
* Minecraft specific API and scarpet language add-ons and commands
* Here is the gist of the Minecraft related functions. Otherwise the CarpetScript could live without Minecraft.
- * Dimension issues
+ * Dimension warning
* One note, which is important is that most of the calls for entities and blocks
* would refer to the current dimension of the caller, meaning, that if we for example
* list all the players using player('all') function, if a player is in the
@@ -109,7 +109,8 @@ import static java.lang.Math.sqrt;
* refer to the overworld blocks and entities.
* In case you would want to run commands across all dimensions, just run three of them, using
* /execute in overworld/the_nether/the_end run script run ... and query
- * players using player('*'), which only returns players in current dimension.
+ * players using player('*'), which only returns players in current dimension, or use
+ * in_dimension(expr) function.
*/
public class CarpetExpression
{
@@ -348,7 +349,7 @@ public class CarpetExpression
* Blocks / World API
*
*
Specifying blocks
- *
block(x, y, z), block(state)
+ *
block(x, y, z), block(l(x,y,z)), block(state)
*
Returns either a block from specified location, or block with a specific state
* (as used by /setblock command), so allowing for block properties, block entity data etc.
* Blocks otherwise can be referenced everywhere by its simple string name, but its only used in its default state
@@ -401,6 +402,8 @@ public class CarpetExpression
* place_item('piston,x,y,z,'down') // places a piston facing down
* place_item('carrot',x,y,z) // attempts to plant a carrot plant. Returns true if could place carrots at that position.
*
+ *
biome(pos)
+ *
returns biome at that block position.
*
set_biome(pos, biome_name)
*
changes biome at that block position.
*
update(pos)
@@ -998,6 +1001,16 @@ public class CarpetExpression
return (_c, _t) -> res;
});
+ this.expr.addLazyFunction("biome", -1, (c, t, lv) -> {
+ CarpetContext cc = (CarpetContext)c;
+ BlockValue.LocatorResult locator = BlockValue.fromParams(cc, lv, 0);
+ ServerWorld world = cc.s.getWorld();
+ BlockPos pos = locator.block.getPos();
+ Biome biome = world.getBiome(pos);
+ Value res = new StringValue(biome.getTranslationKey().replaceFirst("^biome\\.minecraft\\.", ""));
+ return (_c, _t) -> res;
+ });
+
this.expr.addLazyFunction("set_biome", -1, (c, t, lv) ->
{
CarpetContext cc = (CarpetContext)c;
@@ -1179,6 +1192,8 @@ public class CarpetExpression
Value nbtValue = lv.get(inventoryLocator.offset+3).evalValue(c);
if (nbtValue instanceof NBTSerializableValue)
nbt = ((NBTSerializableValue)nbtValue).getCompoundTag();
+ else if (nbtValue instanceof NullValue)
+ nbt = null;
else
nbt = new NBTSerializableValue(nbtValue.getString()).getCompoundTag();
}
@@ -1241,7 +1256,7 @@ public class CarpetExpression
if (inventoryLocator == null)
return (_c, _t) -> Value.NULL;
if (lv.size() <= inventoryLocator.offset)
- throw new InternalExpressionException("inventory_set requires at least an item to be removed");
+ throw new InternalExpressionException("inventory_remove requires at least an item to be removed");
ItemStackArgument searchItem = NBTSerializableValue.parseItem(lv.get(inventoryLocator.offset).evalValue(c).getString());
int amount = 1;
if (lv.size() > inventoryLocator.offset+1)
@@ -1464,9 +1479,11 @@ public class CarpetExpression
*
query(e,'age')
*
Age, in ticks, of the entity, i.e. how long it existed.
*
query(e,'item')
- *
Name of the item if its an item entity, null otherwise
+ *
The item triple (name, count, nbt) if its an item entity, null otherwise
*
query(e,'count')
*
Number of items in a stack from item entity, null otherwise
+ *
query(e,'pickup_delay')
+ *
Retrieves pickup delay timeout for an item entity, null otherwise
*
query(e,'is_baby')
*
Boolean, true if its a baby.
*
query(e,'target')
@@ -1528,10 +1545,9 @@ public class CarpetExpression
* that corresponds to the path. For specification of
path attribute, consult
* vanilla
/data get entity command.
*
Note that calls to nbt are considerably more expensive comparing to other
- * calls in Minecraft API, and should only be used when there is no other option. Also returned
- * NBT is just a string to any retrieval of information post can currently only be done with matching
- * operator ~. With time we are hoping not to support the 'nbt' call better,
- * but rather to fill the API, so that 'nbt' calls are not needed
+ * calls in Minecraft API, and should only be used when there is no other option. Returned value is of type
nbt,
+ * which can be further manipulated with nbt type objects via
get, put, has, delete, so try to use API calls
+ * first for that.
*
Entity Modification
*
Like with entity querying, entity modifications happen through one function. Most position and movements
* modifications don't work currently on players as their position is controlled by clients.
@@ -1557,7 +1573,8 @@ public class CarpetExpression
*
modify(e, 'accelerate', x, y, z), modify(e, 'accelerate', l(x, y, z) )
*
Adds a vector to the motion vector. Most realistic way to apply a force to an entity.
*
modify(e, 'custom_name'), modify(e, 'custom_name', name )
- *
Sets a custom name for an entity. Removes a custom name if the argument is null
+ *
modify(e,'pickup_delay')
+ *
Sets a custom pickup delay if the entity argument is an item entity
*
modify(e, 'dismount')
*
Dismounts riding entity.
*
modify(e, 'mount', other)
@@ -1576,6 +1593,44 @@ public class CarpetExpression
*
Sets AI to stay around the home position, within distance blocks from it. distance
* defaults to 16 blocks. null removes it. May not work fully with mobs that have this AI built in, like
* Villagers.
+ *
+ *
Entity Events
+ *
There is a number of events that happen to entities that you can attach your own code to in the form of event
+ * handlers. The event handler is any function that runs in your package that accepts certain expected parameters, which
+ * you can expand with your own arguments. When it comes to the moment when the given command needs to be executed, it
+ * does so providing that number of arguments it accepts is equal number of event arguments, and extra arguments passed when
+ * defining the callback with entity_event
+ *
The following events can be handled by entities.
+ *
+ * 'on_tick': executes every tick right before entity is ticked in the game. Required arguments: entity
+ * 'on_death': executes once when a living entity dies. Required arguments: entity, reason
+ * 'on_removed': execute once when an entity is removed. Required arguments: entity
+ * 'on_damaged': executed every time a living entity is about to receive damage. Required arguments: entity, amount, source, attacking_entity
+ *
+ *
It doesn't mean that all entity types will have a chance to
+ * execute a given event, but entities will not error when you will attach inapplicable event to it.
+ *
entity_event(e, event, call_name, args...)
+ *
Attaches specific function from the current package to be called upon the event, with extra args
+ * curried to the original required arguments for the event handler
+ *
+ * protect_villager(entity, amount, source, source_entity, healing_player) ->
+ * (
+ * if(source_entity && source_entity~'type' != 'player',
+ * modify(entity, 'health', amount + entity~'health' );
+ * particle('end_rod', pos(entity)+l(0,3,0));
+ * print(str('%s healed thanks to %s', entity, healing_player))
+ * )
+ * );
+ *
+ * __on_player_interacts_with_entity(player, entity, hand) ->
+ * (
+ * if (entity~'type' == 'villager',
+ * entity_event(entity, 'on_damage', 'protect_villager', player~'name')
+ * )
+ * )
+ *
+ *
In this case this will protect a villager from entity damage (zombies, etc.) except players by granting all the health
+ * back to the villager after being harmed.
*
*/
@@ -1673,8 +1728,12 @@ public class CarpetExpression
boolean hasTag = false;
if (lv.size() > position.offset)
{
- hasTag = true;
- tag = new NBTSerializableValue(lv.get(position.offset).evalValue(c).getString()).getCompoundTag();
+ NBTSerializableValue v = NBTSerializableValue.parseString(lv.get(position.offset).evalValue(c).getString());
+ if (v != null)
+ {
+ hasTag = true;
+ tag = v.getCompoundTag();
+ }
}
tag.putString("id", entityId.toString());
Vec3d vec3d = position.vec;
@@ -2104,6 +2163,12 @@ public class CarpetExpression
y = miny;
z = minz;
}
+
+ @Override
+ public String getString()
+ {
+ return String.format("rect[(%d,%d,%d),..,(%d,%d,%d)]",minx, miny, minz, maxx, maxy, maxz);
+ }
};
});
@@ -2195,6 +2260,12 @@ public class CarpetExpression
curradius = 0;
curpos = 0;
}
+
+ @Override
+ public String getString()
+ {
+ return String.format("diamond[(%d,%d,%d),%d,0]",cx, cy, cz, width);
+ }
};
}
else
@@ -2251,6 +2322,12 @@ public class CarpetExpression
curpos = 0;
curheight = -height;
}
+
+ @Override
+ public String getString()
+ {
+ return String.format("diamond[(%d,%d,%d),%d,%d]",cx, cy, cz, width, height);
+ }
};
}
});
@@ -2286,8 +2363,8 @@ public class CarpetExpression
* System function
* nbt(expr)
* Treats the argument as a nbt serializable string and returns its nbt value.
- * In case nbt is not in a correct nbt compound tag format, the execution of a script will stop with an error,
- * whenever that tag is used.
+ * In case nbt is not in a correct nbt compound tag format, it will return null value.
+ * Consult section about container operations in Expression to learn about possible operations on nbt values.
* print(expr)
* Displays the result of the expression to the chat. Overrides default scarpet behaviour of
* sending everyting to stderr.
@@ -2307,11 +2384,11 @@ public class CarpetExpression
* loop(1000,tick(100)) // runs the game twice as slow for 1000 ticks
*
* current_dimension()
- * Returns current dimension that scripts run in.
+ * Returns current dimension that the script runs in.
* in_dimension(smth, expr)
* Evaluates the expression expr with different dimension execution context. smth can
* be an entity, world-localized block, so not block('stone'), or a string representing a dimension like:
- * 'nether', 'end' or 'overworld'.
+ * 'nether', 'the_nether', 'end' or 'overworld', etc.
* schedule(delay, function, args...)
* Schedules a user defined function to run with a specified delay ticks of delay.
* Scheduled functions run at the end of the tick, and they will run in order they were scheduled.
@@ -2608,7 +2685,9 @@ public class CarpetExpression
Value v = lv.get(0).evalValue(c);
if (v instanceof NBTSerializableValue)
return (cc, tt) -> v;
- Value ret = new NBTSerializableValue(v.getString());
+ Value ret = NBTSerializableValue.parseString(v.getString());
+ if (ret == null)
+ return LazyValue.NULL;
return (cc, tt) -> ret;
});
@@ -3005,22 +3084,25 @@ public class CarpetExpression
*
* Event list
* Here is a list of events that can be handled by scarpet. This list includes prefixes required by modules to
- * autoload them, but you can add any function to any event if it had required parameters:
+ * autoload them, but you can add any function to any event if it accepts required number of parameters:
*
* __on_tick() // can access blocks and entities in the overworld
* __on_tick_nether() // can access blocks and entities in the nether
* __on_tick_ender() // can access blocks and entities in the end
+ *
* // player specific callbacks
+ * __on_player_uses_item(player, item_tuple, hand) // right click action
+ * __on_player_releases_item(player, item_tuple, hand) // client action (e.g. bow)
+ * __on_player_finishes_using_item(player, item_tuple, hand)) // server action (e.g. food), called item is from before it is used.
+ * __on_player_clicks_block(player, block, face) // left click attack on a block
+ * __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec)
+ * __on_player_breaks_block(player, block) // called after block is broken (the caller receives previous blockstate)
+ * __on_player_interacts_with_entity(player, entity, hand)
+ * __on_player_attacks_entity(player, entity)
+ * __on_player_rides(player, forward, strafe, jumping, sneaking)
* __on_player_jumps(player)
* __on_player_deploys_elytra(player)
* __on_player_wakes_up(player)
- * __on_player_rides(player, forward, strafe, jumping, sneaking)
- * __on_player_uses_item(player, item_tuple, hand)
- * __on_player_clicks_block(player, block, face)
- * __on_player_right_clicks_block(player, item_tuple, hand, block, face, hitvec)
- * __on_player_breaks_block(player, block)
- * __on_player_interacts_with_entity(player, entity, hand)
- * __on_player_attacks_entity(player, entity)
* __on_player_starts_sneaking(player)
* __on_player_stops_sneaking(player)
* __on_player_starts_sprinting(player)
@@ -3028,7 +3110,10 @@ public class CarpetExpression
*
* /script event command
* used to display current events and bounded functions. use add_to ro register new event, or remove_from
- * to unbind a specific function from an event.
+ * to unbind a specific function from an event. Function to be bounded to an event needs to have the same
+ * number of parameters as the action is attempting to bind to (see list above). All calls in modules
+ * loaded via /script load that have functions listed above will be automatically bounded and unbounded when
+ * script is unloaded.
*
*/
public void gameEventsSystem()
diff --git a/src/main/java/carpet/script/CarpetScriptServer.java b/src/main/java/carpet/script/CarpetScriptServer.java
index f9e5c602..d4a29872 100644
--- a/src/main/java/carpet/script/CarpetScriptServer.java
+++ b/src/main/java/carpet/script/CarpetScriptServer.java
@@ -200,7 +200,7 @@ public class CarpetScriptServer
if (!fun.startsWith("__on_"))
continue;
String event = fun.replaceFirst("__on_","");
- if (!events.eventHandlers.containsKey(event))
+ if (!CarpetEventServer.Event.byName.containsKey(event))
continue;
events.addEvent(event, hostName, fun);
}
diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java
index f3256963..7ffaabe8 100644
--- a/src/main/java/carpet/script/Expression.java
+++ b/src/main/java/carpet/script/Expression.java
@@ -13,13 +13,16 @@ import carpet.script.Fluff.TriFunction;
import carpet.script.exception.ExpressionException;
import carpet.script.exception.InternalExpressionException;
import carpet.script.value.AbstractListValue;
+import carpet.script.value.ContainerValueInterface;
import carpet.script.value.FunctionSignatureValue;
import carpet.script.value.GlobalValue;
import carpet.script.value.LazyListValue;
import carpet.script.value.ListValue;
+import carpet.script.value.MapValue;
import carpet.script.value.NumericValue;
import carpet.script.value.StringValue;
import carpet.script.value.Value;
+import org.apache.commons.lang3.text.WordUtils;
import java.math.BigInteger;
import java.util.ArrayList;
@@ -47,7 +50,7 @@ import static java.lang.Math.min;
/**
- * Fundamental components of scarpet programming language.
+ * Fundamental components of scarpet programming language (towards upcoming version 1.5).
*
* Scarpet (a.k.a. Carpet Script, or Script for Carpet) is a programming language designed to provide
* the ability to write custom programs to run within Minecraft and
@@ -71,7 +74,7 @@ import static java.lang.Math.min;
*
* script run print('Hello World!')
*
- * or an overly complex example:
+ * or an OVERLY complex example:
*
* /script run
* block_check(x1, y1, z1, x2, y2, z2, block_to_check) ->
@@ -80,7 +83,7 @@ import static java.lang.Math.min;
* l(miny, maxy) = sort(l(y1, y2));
* l(minz, maxz) = sort(l(z1, z2));
* 'Need to compute the size of the area of course';
- * 'Cause this language doesn\'t support comments';
+ * 'Cause this language doesn\'t support comments in the command mode';
* xsize = maxx - minx + 1;
* ysize = maxy - miny + 1;
* zsize = maxz - minz + 1;
@@ -199,11 +202,36 @@ import static java.lang.Math.min;
* Variables from outer scopes have a similar behaviour to, for example, nonlocal variables from python.
*
*
- * Line indicators
+ * Code delivery, line indicators
* Note that this should only apply to pasting your code to execute with commandblock. Scarpet recommends placing your
* code in packages (files with .sc extension that can be placed inside "/scripts" folder in the world files
* and loaded as a module with command /script load [package_name]. Scarpet modules loaded from disk should only
* contain code, no need to start with "/script run" prefix
+ * The following is the code that could be provided in a foo.sc file located in world /scripts folder
+ *
+ *
+ * run_program() -> (
+ * loop( 10,
+ * // looping 10 times
+ * // comments are allowed in scripts located in world files
+ * // since we can tell where that line ends
+ * foo = floor(rand(10));
+ * check_not_zero(foo);
+ * print(_+' - foo: '+foo);
+ * print(' reciprocal: '+ _/foo )
+ * )
+ * );
+ * check_not_zero(foo) -> (
+ * if (foo==0, foo = 1)
+ * )
+ *
+ * Which we then call in-game with:
+ *
+ * /script load foo
+ * /script in foo invoke run_program
+ *
+ * However the following code can also be input as a command, or in a command block.
+ *
* Since the maximum command that can be input to the chat is limited in length, you will be probably inserting your
* programs by pasting them to command blocks or reading from world files, however pasting to command blocks will remove some whitespaces and squish
* your newlines making the code not readable. If you are pasting a program that is perfect and will never cause an error,
@@ -308,6 +336,7 @@ import static java.lang.Math.min;
public class Expression implements Cloneable
{
private static final Map precedence = new HashMap() {{
+ put("attribute~.", 80);
put("unary+-!", 60);
put("exponent^", 40);
put("multiplication*/%", 30);
@@ -362,29 +391,6 @@ public class Expression implements Cloneable
private Map functions = new HashMap<>();
Set getFunctionNames() {return functions.keySet();}
- //static final Map globalFunctions = new HashMap<>();
-
- //static final Map globalVariables = new HashMap<>();
-
- /*static
- {
- setGlobals();
- }
- static void setGlobals()
- {
- globalVariables.put("euler", (c, t) -> euler);
- globalVariables.put("pi", (c, t) -> PI);
- globalVariables.put("null", (c, t) -> Value.NULL);
- globalVariables.put("true", (c, t) -> Value.TRUE);
- globalVariables.put("false", (c, t) -> Value.FALSE);
-
- //special variables for second order functions so we don't need to check them all the time
- globalVariables.put("_", (c, t) -> Value.ZERO);
- globalVariables.put("_i", (c, t) -> Value.ZERO);
- globalVariables.put("_a", (c, t) -> Value.ZERO);
- }
- */
-
@Override
protected Expression clone() throws CloneNotSupportedException
{
@@ -559,7 +565,9 @@ public class Expression implements Cloneable
return new ExpressionException(e, token, "Your math is wrong, "+exc.getMessage());
if (exc instanceof ExpressionException)
return exc;
- return new ExpressionException(e, token, "Error while evaluating expression: "+exc.getMessage());
+ // unexpected really - should be caught earlier and converted to InternalExpressionException
+ exc.printStackTrace();
+ return new ExpressionException(e, token, "Error while evaluating expression: "+exc);
}
private void addUnaryOperator(String surface, boolean leftAssoc, Function fun)
@@ -850,7 +858,8 @@ public class Expression implements Cloneable
* Names in the signature don't need to be used anywhere else, other occurrences of these names
* will be masked in this function scope.
* Function call creates new scope for variables inside expr, so all non-global variables are not
- * visible from the caller scope. All parameters are passed by value to the new scope, including lists
+ * visible from the caller scope. All parameters are passed by value to the new scope, including lists and other
+ * containers, however their copy will be shallow.
*
* a(lst) -> lst+=1; list = l(1,2,3); a(list); a(list); list // => [1,2,3]
*
@@ -911,7 +920,7 @@ public class Expression implements Cloneable
public void UserDefinedFunctionsAndControlFlow() // public just to get the javadoc right
{
// artificial construct to handle user defined functions and function definitions
- addLazyFunction(".",-1, (c, t, lv) -> { // adjust based on c
+ addLazyFunction(" ",-1, (c, t, lv) -> { // adjust based on c
String name = lv.get(lv.size()-1).evalValue(c).getString();
//lv.remove(lv.size()-1); // aint gonna cut it // maybe it will because of the eager eval changes
if (t != Context.SIGNATURE) // just call the function
@@ -1057,23 +1066,78 @@ public class Expression implements Cloneable
* has to deal with
* Operator Precedence
*
- * Here is the complete list of operators in scarpet including control flow operators.
- * note, that commas and brackets are not operators, but behave like them:
- *
- *
- * - Unary
+ - !
- * - Exponent
^
- * - Multiplication
* / %
- * - Addition
+ -
- * - Comparison
== != > >= <= < ~
- * - Logical And
&&
- * - Logical Or
||
- * - Assignment
= += <>
- * - Definition
->
- * - Next statement
;
- * - Comma
,
- * - Bracket
( )
- *
+ * Here is the complete list of operators in scarpet including control flow operators.
+ * Note, that commas and brackets are not technically operators, but part of the language,
+ * even if they look like them:
+ *
+ *
+ * - Match, Get
~ .
+ * - Unary
+ - !
+ * - Exponent
^
+ * - Multiplication
* / %
+ * - Addition
+ -
+ * - Comparison
== != > >= <= <
+ * - Logical And
&&
+ * - Logical Or
||
+ * - Assignment
= += <>
+ * - Definition
->
+ * - Next statement
;
+ * - Comma
,
+ * - Bracket
( )
+ *
+ *
+ * Get, Accessor Operator .
+ * Operator version of the get(...) function to access elements of lists, maps, and potentially other
+ * containers (i.e. NBTs). It is important to distinguish from ~ operator, which is a matching operator,
+ * which is expected to perform some extra computations to retrieve the result, while . should be
+ * straightforward and immediate, and the source object should behave like a container and support full container
+ * API, meaning get(...), put(...), delete(...),
+ * and has(...) functions
+ * WARNING: Unlike for some languages, where item access operator can be an L-value, to assign new values to
+ * container elements, use put function instead, i.e. a.1='foo' won't work.
+ *
+ * Matching Operator ~
+ * This operator should be understood as 'matches', 'contains', 'is_in',
+ * or 'find me some stuff about something else. For strings it matches the right operand as a regular
+ * expression to the left, returning the first match. This can be used to extract information from unparsed nbt's
+ * in a more convoluted way (use get(...) for more appropriate way of doing it).
+ * For lists it checks if an element is in the list, and returns the index of that element,
+ * or null if no such element was found, especially that the use of first(...) function will not
+ * return the index. Currently it doesn't have any special behaviour for numbers - it checks for existence of characters
+ * in string representation of the left operand with respect of the regular expression on the right hand side.
+ * string
+ * in Minecraft API portion entity ~ feature is a shortcode for query(entity,feature)
+ * for queries that do not take any extra arguments.
+ *
+ * l(1,2,3) ~ 2 => 1
+ * l(1,2,3) ~ 4 => null
+ * 'foobar' ~ '.b' => 'ob'
+ * player('*') ~ 'gnembon' // null unless player gnembon is logged in (better to use player('gnembon') instead
+ * p ~ 'sneaking' // if p is an entity returns whether p is sneaking
+ *
+ * Or a longer example of an ineffective way to searching for a squid
+ *
+ * entities = entities_area('all',x,y,z,100,10,100);
+ * sid = entities ~ 'Squid';
+ * if(sid != null, run('execute as '+query(get(entities,sid),'id')+' run say I am here '+query(get(entities,sid),'pos') ) )
+ *
+ * Or an example to find if a player has specific enchantment on a held axe (either hand) and get its level
+ * (not using proper NBTs query support via get(...)):
+ *
+ * global_get_enchantment(p, ench) -> (
+ * $ for(l('main','offhand'),
+ * $ holds = query(p, 'holds', _);
+ * $ if( holds,
+ * $ l(what, count, nbt) = holds;
+ * $ if( what ~ '_axe' && nbt ~ ench,
+ * $ lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') )
+ * $ )
+ * $ )
+ * $ );
+ * $ lvl
+ * $);
+ * /script run global_get_enchantment(players(), 'sharpness')
+ *
*
* Basic Arithmetic Operators + - * /
* Allows to add the results of two expressions. If the operands resolve to numbers, the result is
@@ -1139,46 +1203,7 @@ public class Expression implements Cloneable
* null != false && run('kill gnembon') => 1 // gnembon dies, cheats allowed
*
*
- *
Matching Operator ~
- * This operator should be understood as 'matches', or 'in'. For strings it matches the right operand as a regular
- * expression to the left one, returning the first match. This can be used to extract information from unparsed nbt's
- * in a more efficient way. For lists it checks if an element is in the list, and returns the index of that element,
- * or null if no such element was found, especially that the use of first function will not
- * return the index. Currently it doesn't have any special behaviour for numbers - it checks for existence of characters
- * in string representation of the left operand with respect of the regular expression on the right hand side.
- * string
- * in Minecraft API portion entity ~ feature is a shortcode for query(entity,feature)
- * for queries that do not take any extra arguments.
- *
- * l(1,2,3) ~ 2 => 1
- * l(1,2,3) ~ 4 => null
- * 'foobar' ~ '.b' => 'ob'
- * player('*') ~ 'gnembon' // null unless player gnembon is logged in (better to use player('gnembon') instead
- * p ~ 'sneaking' // if p is an entity returns whether p is sneaking
- *
- * Or a longer example of an ineffective way to searching for a squid
- *
- * entities = entities_area('all',x,y,z,100,10,100);
- * sid = entities ~ 'Squid';
- * if(sid != null, run('execute as '+query(element(entities,sid),'id')+' run say I am here '+query(element(entities,sid),'pos') ) )
- *
- * Or an example to find if a player has specific enchantment on a held axe (either hand) and get its level
- * (despite obvious lack of support for json NBT's):
- *
- * global_get_enchantment(p, ench) -> (
- * $ for(l('main','offhand'),
- * $ holds = query(p, 'holds', _);
- * $ if( holds,
- * $ l(what, count, nbt) = holds;
- * $ if( what ~ '_axe' && nbt ~ ench,
- * $ lvl = max(lvl, number(nbt ~ '(?<=lvl:)\\d') )
- * $ )
- * $ )
- * $ );
- * $ lvl
- * $);
- * /script run global_get_enchantment(players(), 'sharpness')
- *
+
* Assignment Operators = <> +=
* A set of assignment operators. All require bounded variable on the LHS, <> requires
* bounded arguments on the right hand side as well (bounded, meaning being variables). Additionally they can also
@@ -1217,6 +1242,12 @@ public class Expression implements Cloneable
*/
public void Operators()
{
+ addBinaryOperator(".", precedence.get("attribute~."),true, (container, key) ->
+ {
+ if (container instanceof ContainerValueInterface)
+ return ((ContainerValueInterface) container).get(key);
+ throw new InternalExpressionException("Cannot access elements of a non-container");
+ });
addBinaryOperator("+", precedence.get("addition+-"), true, Value::add);
addBinaryOperator("-", precedence.get("addition+-"), true, Value::subtract);
addBinaryOperator("*", precedence.get("multiplication*/%"), true, Value::multiply);
@@ -1242,7 +1273,7 @@ public class Expression implements Cloneable
return v2.getBoolean() ? ((cc, tt) -> v2) : LazyValue.FALSE;
});
- addBinaryOperator("~", precedence.get("compare>=><=<"), true, Value::in);
+ addBinaryOperator("~", precedence.get("attribute~."), true, Value::in);
addBinaryOperator(">", precedence.get("compare>=><=<"), false, (v1, v2) ->
v1.compareTo(v2) > 0 ? Value.TRUE : Value.FALSE);
@@ -1311,7 +1342,7 @@ public class Expression implements Cloneable
v1.assertAssignable();
String varname = v1.getVariable();
LazyValue boundedLHS;
- if (v1 instanceof ListValue)
+ if (v1 instanceof ListValue || v1 instanceof MapValue)
{
((ListValue) v1).append(v2);
boundedLHS = (cc, tt)-> v1;
@@ -1397,14 +1428,14 @@ public class Expression implements Cloneable
*
log10(n)
* Decimal logarithm of n. Its ceiling is the length of its floor.
* log(n)
- * Binary logarithm of n. Finally, a proper one, not like the other 100.
+ * Binary logarithm of n. Finally, a proper one, not like the previous 11.
* log1p(n)
* Binary logarithm of n+1. Also always positive.
* mandelbrot(a, b, limit)
* Computes the value of the mandelbrot set, for set a and b spot.
* Spot the beetle. Why not.
* min(arg, ...), min(list), max(arg, ...), max(list)
- * Compute minimum or maximum of supplied arguments assuming default sortraphical order. In case you are
+ *
Compute minimum or maximum of supplied arguments assuming default sorthoraphical order. In case you are
* missing argmax, just use a ~ max(a), little less efficient, but still fun.
*
*
@@ -1551,118 +1582,24 @@ public class Expression implements Cloneable
}
/**
- *
Lists, loops, and higher order functions
+ * Loops, and higher order functions
*
- *
Efficient use of these functions cangreatly simplify your programs and speed them up, as these functions
- * will internalize most of the operations that need to be applied on multiple values at the same time
- *
Basic list operations
- *
l(values ...), l(iterator)
- *
Creates a list of values of the expressions passed as parameters. It can be used as an L-value and if all
- * elements are variables, you coujld use it to return multiple results from one function call, if that function returns
- * a list of results with the same size as the l call uses. In case there is only one argument and it is
- * an iterator (vanilla expression specification has range, but Minecraft API implements
- * a bunch of them, like diamond), it will convert it to a proper list. Iterators can only be used in
- * high order functions, and are treated as empty lists, unless unrolled with l
+ *
Efficient use of these functions can greatly simplify your programs and speed them up, as these functions
+ * will internalize most of the operations that need to be applied on multiple values at the same time. Most
+ * of them take a list argument which can be any iterable structure in scarpet, including
+ * generators, like rect, or range, and maps, where the iterator returns all the map keys
+ *
Loops
+ *
+ *
for(list,expr(_,_i),exit(_,_i)?)
+ *
Evaluates expression over list of items from the list, and can optionally stop early if
+ * exit expression is specified and evaluates true for a given iteration. Supplies
+ * _(value) and _i(iteration number) to both expr and exit.
+ * Returns the number of times expr was successful
*
- * l(1,2,'foo') => [1, 2, foo]
- * l() => [] (empty list)
- * l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- * l(1, 2) = l(3, 4) => Error: l is not a variable
- * l(foo, bar) = l(3,4); foo==3 && bar==4 => 1
- * l(foo, bar, baz) = l(2, 4, 6); l(min(foo, bar), baz) = l(3, 5); l(foo, bar, baz) => [3, 4, 5]
- *
- *
In the last example l(min(foo, bar), baz) creates a valid L-value, as min(foo, bar) finds the
- * lower of the variables (in this case foo) creating a valid assignable L-list of [foo, baz], and these
- * values will be assigned new values
- *
- *
join(delim, list), join(delim, values ...)
- *
Returns a string that contains joined elements of the list, iterator, or all values, concatenated with delim delimiter
- *
- * join('-',range(10)) => 0-1-2-3-4-5-6-7-8-9
- * join('-','foo') => foo
- * join('-', 'foo', 'bar') => foo-bar
- *
- *
- *
split(delim, expr)
- *
Splits a string undr expr by delim which can be a regular expression
- *
- * split('',foo) => [f, o, o]
- * split('.','foo.bar') => []
- * split('\\.','foo.bar') => [foo, bar]
- *
- *
- *
slice(expr, from, to?)
- *
extracts a substring, or sublist (based on the type of the result of the expression under expr with starting index
- * of from, and ending at to if provided, or the end, if omitted
- *
- * slice(l(0,1,2,3,4,5), 1, 3) => [1, 2, 3]
- * slice('foobar', 0, 1) => 'f'
- * slice('foobar', 3) => 'bar'
- * slice(range(10), 3, 5) => [3, 4, 5]
- * slice(range(10), 5) => [5, 6, 7, 8, 9]
- *
- *
- *
- *
- *
sort(list), sort(values ...)
- *
Sorts in the default sortographical order either all arguments, or a list if its the only argument. It returns a new
- * sorted list, not affecting the list passed to the argument
- *
- * sort(3,2,1) => [1, 2, 3]
- * sort('a',3,11,1) => [1, 3, 11, 'a']
- * list = l(4,3,2,1); sort(list) => [1, 2, 3, 4]
- *
- *
- *
sort_key(list, key_expr)
- *
Sorts a copy of the list in the order or keys as defined by the key_expr for each element
- *
- * sort_key([1,3,2],_) => [1, 2, 3]
- * sort_key([1,3,2],-_) => [3, 2, 1]
- * sort_key(l(range(10)),rand(1)) => [1, 0, 9, 6, 8, 2, 4, 5, 7, 3]
- * sort_key(l(range(20)),str(_)) => [0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9]
- *
- *
- *
range(to), range(from, to), range(from, to, step)
- *
Creates a range of numbers from from, no greater/larger than to.
- * The step parameter dictates not only the increment size, but also direction (can be negative).
- * The returned value is not a proper list, just the iterator
- * but if for whatever reason you need a proper list with all items evaluated, use l(range(to)).
- * Primarily to be used in higher order functions
- *
- * range(10) => [...]
- * l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- * map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
- * reduce(range(10),_a+_, 0) => 45
- * range(5,10) => [5, 6, 7, 8, 9]
- * range(20, 10, -2) => [20, 18, 16, 14, 12]
- *
- *
- *
get(value, address), element(value, address)(deprecated)
- *
Returns the value at address element from the value.
- * For lists it indicates an index, use negative numbers to reach elements from the end of the list. get
- * call will always be able to find the index. In case there is few items, it will loop over
- *
[Minecraft specific usecase]: In case value is of nbt type, uses addess as the nbt path to query,
- * returning null, if path is not found, one value if there was one match, or list of values if result is a list.
- * Returned elements can be of numerical type, string texts, or another compound nbt tags
- *
- * get(l(range(10)), 5) => 5
- * get(l(range(10)), -1) => 9
- * get(l(range(10)), 10) => 0
- * get(l(range(10)), 93) => 3
- * get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health
- *
- *
- *
put(list, index, values ...), put(list, null, values ...)
- *
Modifies the list by replacing values starting from index with values.
- * use negative numbers to reach elements from the end of the list. put
- * call will always be able to find the index. In case there is few items, it will loop over. In case end
- * of the list is reached before values run out, list is extended to accomodate for more values. in case you
- * want to append at the end of the list, use null as index. returns number of elements inserted.
- *
- * a = l(1, 2, 3); put(a, 1, 4); a => [1, 4, 3]
- * a = l(1, 2, 3); put(a, null, 4, 5, 6); a => [1, 2, 3, 4, 5, 6]
- * a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(element(a, 1), 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
+ * check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) );
+ * for(range(1000000,1100000),check_prime(_)) => 7216
*
+ *
From which we can learn that there is 7216 primes between 1M and 1.1M
*
while(cond, limit, expr)
*
Evaluates expression expr repeatedly until condition cond becomes false,
* but not more than limit times. Returns the result of the last expr evaluation,
@@ -1692,6 +1629,7 @@ public class Expression implements Cloneable
*
* // outputs: [0, 1, 2, 3, 5, 7, 11, 13, 17, 19]
*
+ *
Higher Order Functions
*
map(list,expr(_,_i), exit(_,_i)?)
*
Converts a list of values, to another list where each value is result of an expression
* v = expr(_, _i) where _ is passed as each element of the list, and _i is
@@ -1736,17 +1674,6 @@ public class Expression implements Cloneable
* // ditto, but requiring at most 7 bedrock block in the 18 blocks below them
*
*
- *
- *
for(list,expr(_,_i),exit(_,_i)?)
- *
Evaluates expression over list of items from the list, and can optionally stop early if
- * exit expression is specified and evaluates true for a given iteration. Supplies
- * _(value) and _i(iteration number) to both expr and exit.
- * Returns the number of times expr was successful
- *
- * check_prime(n) -> !first( range(2, sqrt(n)+1), !(n % _) );
- * for(range(1000000,1100000),check_prime(_)) => 7216
- *
- *
From which we can learn that there is 7216 primes between 1M and 1.1M
*
reduce(list,expr(_a,_,_i), initial)
*
Applies expr for each element of the list and saves the result in _a accumulator.
* Consecutive calls to expr can access that value to apply more values. You also need to specify
@@ -1757,154 +1684,11 @@ public class Expression implements Cloneable
*
*
*/
- public void ListsLoopsAndHigherOrderFunctions()
+ public void LoopsAndHigherOrderFunctions()
{
- addFunction("l", lv ->
- {
- if (lv.size() == 1 && lv.get(0) instanceof LazyListValue)
- return ListValue.wrap(((LazyListValue) lv.get(0)).unroll());
- return new ListValue.ListConstructorValue(lv);
- });
-
- addFunction("join", (lv) ->
- {
- if (lv.size() < 2)
- throw new InternalExpressionException("join takes at least 2 arguments");
- String delimiter = lv.get(0).getString();
- List toJoin;
- if (lv.size()==2 && lv.get(1) instanceof LazyListValue)
- {
- toJoin = ((LazyListValue) lv.get(1)).unroll();
-
- }
- else if (lv.size() == 2 && lv.get(1) instanceof ListValue)
- {
- toJoin = new ArrayList<>(((ListValue)lv.get(1)).getItems());
- }
- else
- {
- toJoin = lv.subList(1,lv.size());
- }
- return new StringValue(toJoin.stream().map(Value::getString).collect(Collectors.joining(delimiter)));
- });
-
- addBinaryFunction("split", (d, v) -> {
- String delimiter = d.getString();
- String hwat = v.getString();
- return ListValue.wrap(Arrays.stream(hwat.split(delimiter)).map(StringValue::new).collect(Collectors.toList()));
- });
-
- addFunction("slice", (lv) -> {
-
- if (lv.size() != 2 && lv.size() != 3)
- throw new InternalExpressionException("slice takes 2 or 3 arguments");
- Value hwat = lv.get(0);
- long from = NumericValue.asNumber(lv.get(1)).getLong();
- long to = -1;
- if (lv.size()== 3)
- to = NumericValue.asNumber(lv.get(2)).getLong();
- return hwat.slice(from, to);
- });
-
- addFunction("sort", (lv) ->
- {
- List toSort = lv;
- if (lv.size()==1 && lv.get(0) instanceof ListValue)
- {
- toSort = new ArrayList<>(((ListValue)lv.get(0)).getItems());
- }
- Collections.sort(toSort);
- return ListValue.wrap(toSort);
- });
-
- addLazyFunction("sort_key", 2, (c, t, lv) -> //get working with iterators
- {
- Value v = lv.get(0).evalValue(c);
- if (!(v instanceof ListValue))
- throw new InternalExpressionException("First argument for sort_key should be a List");
- LazyValue sortKey = lv.get(1);
- //scoping
- LazyValue __ = c.getVariable("_");
-
- List toSort = new ArrayList<>(((ListValue) v).getItems());
-
- Collections.sort(toSort,(v1, v2) -> {
- c.setVariable("_",(cc, tt) -> v1);
- Value ev1 = sortKey.evalValue(c);
- c.setVariable("_",(cc, tt) -> v2);
- Value ev2 = sortKey.evalValue(c);
- return ev1.compareTo(ev2);
- });
- //revering scope
- c.setVariable("_", __);
- return (cc, tt) -> ListValue.wrap(toSort);
- });
-
- addFunction("range", (lv) ->
- {
- long from = 0;
- long to = 0;
- long step = 1;
- int argsize = lv.size();
- if (argsize == 0 || argsize > 3)
- throw new InternalExpressionException("range accepts from 1 to 3 arguments, not "+argsize);
- to = NumericValue.asNumber(lv.get(0)).getLong();
- if (lv.size() > 1)
- {
- from = to;
- to = NumericValue.asNumber(lv.get(1)).getLong();
- if (lv.size() > 2)
- {
- step = NumericValue.asNumber(lv.get(2)).getLong();
- }
- }
- return LazyListValue.range(from, to, step);
- });
-
- addBinaryFunction("get", Value::getElementAt);
-
- //Deprecated, use "get" instead
- addBinaryFunction("element", (v1, v2) ->
- {
- if (!(v1 instanceof ListValue))
- throw new InternalExpressionException("First argument of get should be a list");
- List items = ((ListValue)v1).getItems();
- long index = NumericValue.asNumber(v2).getLong();
- int numitems = items.size();
- long range = abs(index)/numitems;
- index += (range+2)*numitems;
- index = index % numitems;
- return items.get((int)index);
- });
-
- addFunction("put", (lv) ->
- {
- if(lv.size()<3)
- {
- throw new InternalExpressionException("put takes at least three arguments, a list, index, and values to insert at that index");
- }
- Value list = lv.get(0);
- if (!(list instanceof ListValue))
- {
- throw new InternalExpressionException("First argument of element should be a list");
- }
- int size = lv.size();
- //List items = ((ListValue)lv.get(0)).getItems();
- Value index = lv.get(1);
- if (index == Value.NULL)
- {
- ((ListValue) list).extend(lv.subList(2,size));
- }
- else
- {
- ((ListValue) list).addAtIndex((int) NumericValue.asNumber(index).getLong(), lv.subList(2,size));
- }
- return new NumericValue(size-2);
- });
-
- //condition and expression will get a bound 'i'
- //returns last successful expression or false
- // while(cond, limit, expr) => ??
+ // condition and expression will get a bound '_i'
+ // returns last successful expression or false
+ // while(cond, limit, expr) => ??
addLazyFunction("while", 3, (c, t, lv) ->
{
long limit = NumericValue.asNumber(lv.get(1).evalValue(c)).getLong();
@@ -2217,6 +2001,401 @@ public class Expression implements Cloneable
});
}
+ /**
+ * Lists, Maps and API support for Containers
+ *
+ *
Scarpet supports basic container types: lists and maps (aka hashmaps, dicts etc..)
+ *
Container manipulation
+ *
Here is a list of operations that work on all types of containers: lists, maps, as well as other Minecraft
+ * specific modifyable containers, like NBTs
+ *
+ *
get(container, address), '.' operator
+ *
Returns the value at address element from the value.
+ * For lists it indicates an index, use negative numbers to reach elements from the end of the list. get
+ * call will always be able to find the index. In case there is few items, it will loop over
+ *
for maps, retrieves the value under the key specified in the address or null otherwise
+ *
[Minecraft specific usecase]: In case value is of nbt type, uses address as the
+ * nbt path to query,
+ * returning null, if path is not found, one value if there was one match, or list of values if result is a list.
+ * Returned elements can be of numerical type, string texts, or another compound nbt tags
+ *
+ * get(l(range(10)), 5) => 5
+ * get(l(range(10)), -1) => 9
+ * get(l(range(10)), 10) => 0
+ * l(range(10)).93 => 3
+ *
+ * get(player() ~ 'nbt', 'Health') => 20 // inefficient way to get player health, use player() ~ 'health' instead
+ *
+ * get(m( l('foo',2), l('bar',3), l('baz',4) ), 'bar') => 3
+ *
+ *
+ *
has(container, address)
+ *
Similar to get, but returns boolean value indicating if the given index / key / path is in the
+ * container. Can be used to determine if get(...)==null means the element doesn't exist, or the stored value
+ * for this address is null, and is cheaper to run than get
+ *
+ *
delete(container, address)
+ *
Removes specific entry from the container. For the lists - removes the element and shrinks it. For maps, it
+ * removes the key from the map, and for nbt - removes content from a given path. For lists and maps returns previous
+ * entry at the address, for nbt's - number of removed objects, with 0 indicating that the original value was unaffected.
+ *
+ *
put(container, address, value), put(container, address, value, mode)
+ *
Lists
+ *
Modifies the container by replacing the value under the address with the supplied value.
+ * For lists, a valid index is required, but can be negative as well to indicate positions from the end of the list.
+ * If null is supplied as the address, it always means - add to the end of the list.
+ *
There are three modes that lists can have items added to them:
+ *
+ * replace(default): Replaces item under given index(address). Doesn't change the size of the array
+ * unless null address is used, which is an exception and then it appends to the end
+ * insert: Inserts given element at a specified index, shifting the rest of the array to make space
+ * for the item. Note that index of -1 points to the last element of the list, thus inserting at that position and
+ * moving the previous last element to the new last element position. To insert at the end, use +=
+ * operator, or null address in put
+ * extend: treats the supplied value as an iterable set of values to insert at a given index,
+ * extending the list by this amount of items. Again use null address/index to point to the end of
+ * the list
+ *
+ *
Maps
+ *
For maps there are no modes available (yet, seems there is no reason to). It replaces the value under the supplied
+ * key (address), or sets it if not currently present.
+ *
NBT Tags
+ *
+ * The address for nbt values is a valid nbt path that you would use with /data command, and tag is any
+ * tag that would be applicable for a given insert operation. Note that to distinguish between proper types (like integer
+ * types, you need to use command notation, i.e. regular ints is 123, while byte size int would be 123b
+ * and an explicit string would be "5", so it helps that scarpet uses single quotes in his strings. Unlike
+ * for lists and maps, it returns the number of affected nodes, or 0 if none were affected.
+ *
+ *
There are three modes that NBT tags can have items added to them:
+ *
+ * replace(default): Replaces item under given path(address). Removes them first if possible, and then
+ * adds given element to the supplied position. The target path can indicate compound tag keys, lists, or individual
+ * elements of the lists.
+ * <N>: Index for list insertions. Inserts given element at a specified index, inside a list
+ * specified with the path address. Fails if list is not specified. It behaves like insert mode for lists,
+ * i.e. it is not removing any of the existing elements. Use replace to remove and replace existing element.
+ *
+ * merge: assumes that both path and replacement target are of compound type (dictionaries, maps,
+ * {} types), and merges keys from value with the compound tag under the path
+ *
+ *
+ *
+ * a = l(1, 2, 3); put(a, 1, 4); a => [1, 4, 3]
+ * a = l(1, 2, 3); put(a, null, 4); a => [1, 2, 3, 4]
+ * a = l(1, 2, 3); put(a, 1, 4, 'insert'); a => [1, 4, 2, 3]
+ * a = l(1, 2, 3); put(a, null, l(4, 5, 6), 'extend'); a => [1, 2, 3, 4, 5, 6]
+ * a = l(1, 2, 3); put(a, 1, l(4, 5, 6), 'extend'); a => [1, 4, 5, 6, 2, 3]
+ * a = l(l(0,0,0),l(0,0,0),l(0,0,0)); put(a.1, 1, 1); a => [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
+ * a = m(1,2,3,4); put(a, 5, null); a => {1: null, 2: null, 3: null, 4: null, 5: null}
+ * tag = nbt('{}'); put(tag, 'BlockData.Properties', '[1,2,3,4]'); tag => {BlockData:{Properties:[1,2,3,4]}}
+ * tag = nbt('{a:[{lvl:3},{lvl:5},{lvl:2}]}'); put(tag, 'a[].lvl', 1); tag => {a:[{lvl:1},{lvl:1},{lvl:1}]}
+ * tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl', 1, 2); tag
+ * => {a:[{lvl:[1,2,1,3]},{lvl:[3,2,1,1]},{lvl:[4,5,1,6]}]}
+ * tag = nbt('{a:[{lvl:[1,2,3]},{lvl:[3,2,1]},{lvl:[4,5,6]}]}'); put(tag, 'a[].lvl[1]', 1); tag
+ * => {a:[{lvl:[1,1,3]},{lvl:[3,1,1]},{lvl:[4,1,6]}]}
+ *
+ *
+ *
List operations
+ *
l(values ...), l(iterator)
+ *
Creates a list of values of the expressions passed as parameters. It can be used as an L-value and if all
+ * elements are variables, you coujld use it to return multiple results from one function call, if that function returns
+ * a list of results with the same size as the l call uses. In case there is only one argument and it is
+ * an iterator (vanilla expression specification has range, but Minecraft API implements
+ * a bunch of them, like diamond), it will convert it to a proper list. Iterators can only be used in
+ * high order functions, and are treated as empty lists, unless unrolled with l
+ *
+ * l(1,2,'foo') => [1, 2, foo]
+ * l() => [] (empty list)
+ * l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ * l(1, 2) = l(3, 4) => Error: l is not a variable
+ * l(foo, bar) = l(3,4); foo==3 && bar==4 => 1
+ * l(foo, bar, baz) = l(2, 4, 6); l(min(foo, bar), baz) = l(3, 5); l(foo, bar, baz) => [3, 4, 5]
+ *
+ *
In the last example l(min(foo, bar), baz) creates a valid L-value, as min(foo, bar) finds the
+ * lower of the variables (in this case foo) creating a valid assignable L-list of [foo, baz], and these
+ * values will be assigned new values
+ *
+ *
join(delim, list), join(delim, values ...)
+ *
Returns a string that contains joined elements of the list, iterator, or all values, concatenated with delim delimiter
+ *
+ * join('-',range(10)) => 0-1-2-3-4-5-6-7-8-9
+ * join('-','foo') => foo
+ * join('-', 'foo', 'bar') => foo-bar
+ *
+ *
+ *
split(delim, expr)
+ *
Splits a string undr expr by delim which can be a regular expression
+ *
+ * split('',foo) => [f, o, o]
+ * split('.','foo.bar') => []
+ * split('\\.','foo.bar') => [foo, bar]
+ *
+ *
+ *
slice(expr, from, to?)
+ *
extracts a substring, or sublist (based on the type of the result of the expression under expr with starting index
+ * of from, and ending at to if provided, or the end, if omitted
+ *
+ * slice(l(0,1,2,3,4,5), 1, 3) => [1, 2, 3]
+ * slice('foobar', 0, 1) => 'f'
+ * slice('foobar', 3) => 'bar'
+ * slice(range(10), 3, 5) => [3, 4, 5]
+ * slice(range(10), 5) => [5, 6, 7, 8, 9]
+ *
+ *
+ *
sort(list), sort(values ...)
+ *
Sorts in the default sortographical order either all arguments, or a list if its the only argument. It returns a new
+ * sorted list, not affecting the list passed to the argument
+ *
+ * sort(3,2,1) => [1, 2, 3]
+ * sort('a',3,11,1) => [1, 3, 11, 'a']
+ * list = l(4,3,2,1); sort(list) => [1, 2, 3, 4]
+ *
+ *
+ *
sort_key(list, key_expr)
+ *
Sorts a copy of the list in the order or keys as defined by the key_expr for each element
+ *
+ * sort_key([1,3,2],_) => [1, 2, 3]
+ * sort_key([1,3,2],-_) => [3, 2, 1]
+ * sort_key(l(range(10)),rand(1)) => [1, 0, 9, 6, 8, 2, 4, 5, 7, 3]
+ * sort_key(l(range(20)),str(_)) => [0, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 3, 4, 5, 6, 7, 8, 9]
+ *
+ *
+ *
range(to), range(from, to), range(from, to, step)
+ *
Creates a range of numbers from from, no greater/larger than to.
+ * The step parameter dictates not only the increment size, but also direction (can be negative).
+ * The returned value is not a proper list, just the iterator
+ * but if for whatever reason you need a proper list with all items evaluated, use l(range(to)).
+ * Primarily to be used in higher order functions
+ *
+ * range(10) => [...]
+ * l(range(10)) => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ * map(range(10),_*_) => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
+ * reduce(range(10),_a+_, 0) => 45
+ * range(5,10) => [5, 6, 7, 8, 9]
+ * range(20, 10, -2) => [20, 18, 16, 14, 12]
+ *
+ *
+ *
element(list, index)(deprecated)
+ *
Legacy support for older method that worked only on lists. Please use get(...) for equivalent
+ * support, or . operator. Also previous unique behaviours with put on lists has been
+ * removed to support all type of container types.
+ *
+ *
Map operations
+ *
Scarpet supports map structures, aka hashmaps, dicts etc. Map structure can also be used, with null
+ * values as sets. Apart from container access functions, (. , get, put, has, delete),
+ * the following functions:
+ *
m(values ...), m(iterator), m(key_value_pairs)
+ *
creates and initializes a map with supplied keys, and values. If the arguments contains a flat list,
+ * these are all treated as keys with no value, same goes with the iterator - creates a map that behaves like a set.
+ * If the arguments is a list of lists, they have to have two elements each, and then first is a key, and second,
+ * a value
+ *
+ * m(1,2,'foo') => {1: null, 2: null, foo: null}
+ * m() => {} (empty map)
+ * m(range(10)) => {0: null, 1: null, 2: null, 3: null, 4: null, 5: null, 6: null, 7: null, 8: null, 9: null}
+ * m(l(1, 2), l(3, 4)) => {1: 2, 3: 4}
+ * reduce(range(10), put(_a, _, _*_); _a, m())
+ * => {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
+ *
+ *
keys(map), values(map), pairs(map)
+ *
Returns full lists of keys, values and key-value pairs (2-element lists) for all the entries in the map
+ *
+ *
+ */
+
+ public void BasicDataStructures()
+ {
+ addFunction("l", lv ->
+ {
+ if (lv.size() == 1 && lv.get(0) instanceof LazyListValue)
+ return ListValue.wrap(((LazyListValue) lv.get(0)).unroll());
+ return new ListValue.ListConstructorValue(lv);
+ });
+
+ addFunction("join", (lv) ->
+ {
+ if (lv.size() < 2)
+ throw new InternalExpressionException("join takes at least 2 arguments");
+ String delimiter = lv.get(0).getString();
+ List toJoin;
+ if (lv.size()==2 && lv.get(1) instanceof LazyListValue)
+ {
+ toJoin = ((LazyListValue) lv.get(1)).unroll();
+
+ }
+ else if (lv.size() == 2 && lv.get(1) instanceof ListValue)
+ {
+ toJoin = new ArrayList<>(((ListValue)lv.get(1)).getItems());
+ }
+ else
+ {
+ toJoin = lv.subList(1,lv.size());
+ }
+ return new StringValue(toJoin.stream().map(Value::getString).collect(Collectors.joining(delimiter)));
+ });
+
+ addBinaryFunction("split", (d, v) -> {
+ String delimiter = d.getString();
+ String hwat = v.getString();
+ return ListValue.wrap(Arrays.stream(hwat.split(delimiter)).map(StringValue::new).collect(Collectors.toList()));
+ });
+
+ addFunction("slice", (lv) -> {
+
+ if (lv.size() != 2 && lv.size() != 3)
+ throw new InternalExpressionException("slice takes 2 or 3 arguments");
+ Value hwat = lv.get(0);
+ long from = NumericValue.asNumber(lv.get(1)).getLong();
+ long to = -1;
+ if (lv.size()== 3)
+ to = NumericValue.asNumber(lv.get(2)).getLong();
+ return hwat.slice(from, to);
+ });
+
+ addFunction("sort", (lv) ->
+ {
+ List toSort = lv;
+ if (lv.size()==1 && lv.get(0) instanceof ListValue)
+ {
+ toSort = new ArrayList<>(((ListValue)lv.get(0)).getItems());
+ }
+ Collections.sort(toSort);
+ return ListValue.wrap(toSort);
+ });
+
+ addLazyFunction("sort_key", 2, (c, t, lv) -> //get working with iterators
+ {
+ Value v = lv.get(0).evalValue(c);
+ if (!(v instanceof ListValue))
+ throw new InternalExpressionException("First argument for sort_key should be a List");
+ LazyValue sortKey = lv.get(1);
+ //scoping
+ LazyValue __ = c.getVariable("_");
+
+ List toSort = new ArrayList<>(((ListValue) v).getItems());
+
+ Collections.sort(toSort,(v1, v2) -> {
+ c.setVariable("_",(cc, tt) -> v1);
+ Value ev1 = sortKey.evalValue(c);
+ c.setVariable("_",(cc, tt) -> v2);
+ Value ev2 = sortKey.evalValue(c);
+ return ev1.compareTo(ev2);
+ });
+ //revering scope
+ c.setVariable("_", __);
+ return (cc, tt) -> ListValue.wrap(toSort);
+ });
+
+ addFunction("range", (lv) ->
+ {
+ long from = 0;
+ long to = 0;
+ long step = 1;
+ int argsize = lv.size();
+ if (argsize == 0 || argsize > 3)
+ throw new InternalExpressionException("range accepts from 1 to 3 arguments, not "+argsize);
+ to = NumericValue.asNumber(lv.get(0)).getLong();
+ if (lv.size() > 1)
+ {
+ from = to;
+ to = NumericValue.asNumber(lv.get(1)).getLong();
+ if (lv.size() > 2)
+ {
+ step = NumericValue.asNumber(lv.get(2)).getLong();
+ }
+ }
+ return LazyListValue.range(from, to, step);
+ });
+
+ addFunction("m", lv ->
+ {
+ if (lv.size() == 1 && lv.get(0) instanceof LazyListValue)
+ return new MapValue(((LazyListValue) lv.get(0)).unroll());
+ return new MapValue(lv);
+ });
+
+ addUnaryFunction("keys", v -> {
+ if (v instanceof MapValue)
+ return new ListValue(((MapValue) v).getMap().keySet());
+ throw new InternalExpressionException("keys applies only to maps");
+ });
+
+ addUnaryFunction("values", v -> {
+ if (v instanceof MapValue)
+ return new ListValue(((MapValue) v).getMap().values());
+ throw new InternalExpressionException("keys applies only to maps");
+ });
+
+ addUnaryFunction("pairs", v -> {
+ if (v instanceof MapValue)
+ return ListValue.wrap(((MapValue) v).getMap().entrySet().stream().map(
+ (p) -> ListValue.of(p.getKey(), p.getValue())
+ ).collect(Collectors.toList()));
+ throw new InternalExpressionException("pairs applies only to maps");
+ });
+
+ addBinaryFunction("get", (container, key) ->
+ {
+ if (container instanceof ContainerValueInterface)
+ return ((ContainerValueInterface) container).get(key);
+ throw new InternalExpressionException("Cannot access elements of a non-container");
+ });
+
+ addBinaryFunction("has", (container, key) ->
+ {
+ if (container instanceof ContainerValueInterface)
+ return new NumericValue(((ContainerValueInterface) container).has(key));
+ throw new InternalExpressionException("Cannot access elements of a non-container");
+ });
+
+ //Deprecated, use "get" instead, or . operator
+ addBinaryFunction("element", (v1, v2) ->
+ {
+ if (!(v1 instanceof ListValue))
+ throw new InternalExpressionException("First argument of get should be a list");
+ List items = ((ListValue)v1).getItems();
+ long index = NumericValue.asNumber(v2).getLong();
+ int numitems = items.size();
+ long range = abs(index)/numitems;
+ index += (range+2)*numitems;
+ index = index % numitems;
+ return items.get((int)index);
+ });
+
+ addFunction("put", (lv) ->
+ {
+ if(lv.size()<3)
+ {
+ throw new InternalExpressionException("put takes at least three arguments, a container, address, and values to insert at that index");
+ }
+ Value container = lv.get(0);
+ if (!(container instanceof ContainerValueInterface))
+ {
+ throw new InternalExpressionException("First argument of put should be a container: list, map or nbt");
+ }
+ Value where = lv.get(1);
+ Value what = lv.get(2);
+ if (lv.size()>3)
+ {
+ return ((ContainerValueInterface) container).put(where, what, lv.get(3));
+ }
+ else
+ {
+ return ((ContainerValueInterface) container).put(where, what);
+ }
+ });
+
+ addBinaryFunction("delete", (container, address) ->
+ {
+ if (container instanceof ContainerValueInterface)
+ {
+ return ((ContainerValueInterface) container).delete(address);
+ }
+ throw new InternalExpressionException("First argument of delete should be a container: list, map or nbt");
+ });
+ }
+
/**
*
*
@@ -2288,6 +2467,14 @@ public class Expression implements Cloneable
*
* Auxiliary functions
*
+ * lower(expr), upper(expr), title(expr)
+ * Returns lowercase, uppercase or titlecase representation of a string representation of the passed expression
+ *
+ * lower('aBc') => 'abc'
+ * upper('aBc') => 'ABC'
+ * title('aBc') => 'Abc'
+ *
+ *
* length(expr)
* Returns length of the expression, the length of the string,
* the length of the integer part of the number, or length of the list
@@ -2339,8 +2526,13 @@ public class Expression implements Cloneable
* print(str('this took %d milliseconds',time()-start_time))
*
*
+ * profile_expr(expression)
+ * Returns number of times given expression can be run in 50ms time. Useful to profile and optimize your
+ * code. Note that, even if its only a number, it WILL run these commands, so if they are destructive,
+ * you need to be careful.
+ *
*
- * Access to variables and stored functions
+ * Access to variables and stored functions (use with caution)
*
* var(expr)
* Returns the variable under the name of the string value of the expression. Allows to
@@ -2383,6 +2575,8 @@ public class Expression implements Cloneable
*/
public void SystemFunctions()
{
+ addUnaryFunction("hash_code", v -> new NumericValue(v.hashCode()));
+
addLazyFunction("bool", 1, (c, t, lv) -> {
Value v = lv.get(0).evalValue(c, Context.BOOLEAN);
if (v instanceof StringValue)
@@ -2483,6 +2677,13 @@ public class Expression implements Cloneable
}
});
+ addUnaryFunction("lower", v -> new StringValue(v.getString().toLowerCase(Locale.ROOT)));
+
+ addUnaryFunction("upper", v -> new StringValue(v.getString().toUpperCase(Locale.ROOT)));
+
+ addUnaryFunction("title", v -> new StringValue(WordUtils.capitalizeFully(v.getString())));
+
+
addUnaryFunction("type", v -> new StringValue(v.getTypeString()));
addUnaryFunction("length", v -> new NumericValue(v.length()));
@@ -2613,7 +2814,8 @@ public class Expression implements Cloneable
Operators();
ArithmeticOperations();
SystemFunctions();
- ListsLoopsAndHigherOrderFunctions();
+ LoopsAndHigherOrderFunctions();
+ BasicDataStructures();
}
private List shuntingYard()
@@ -2874,7 +3076,7 @@ public class Expression implements Cloneable
}
else // potentially unknown function or just unknown function
{
- f = functions.get(".");
+ f = functions.get(" ");
p = new ArrayList<>();
}
// pop parameters off the stack until we hit the start of
diff --git a/src/main/java/carpet/script/Tokenizer.java b/src/main/java/carpet/script/Tokenizer.java
index b84be668..0a1f7524 100644
--- a/src/main/java/carpet/script/Tokenizer.java
+++ b/src/main/java/carpet/script/Tokenizer.java
@@ -94,7 +94,8 @@ public class Tokenizer implements Iterator
boolean isHex = false;
- if (Character.isDigit(ch) || (ch == decimalSeparator && Character.isDigit(peekNextChar())))
+ if (Character.isDigit(ch)) // || (ch == decimalSeparator && Character.isDigit(peekNextChar())))
+ // decided to no support this notation to favour element access via . operator
{
if (ch == '0' && (peekNextChar() == 'x' || peekNextChar() == 'X'))
isHex = true;
diff --git a/src/main/java/carpet/script/value/AbstractListValue.java b/src/main/java/carpet/script/value/AbstractListValue.java
index f7e33a02..f72ffd59 100644
--- a/src/main/java/carpet/script/value/AbstractListValue.java
+++ b/src/main/java/carpet/script/value/AbstractListValue.java
@@ -1,9 +1,15 @@
package carpet.script.value;
+import carpet.script.exception.InternalExpressionException;
+
import java.util.Iterator;
public abstract class AbstractListValue extends Value
{
public abstract Iterator iterator();
public void fatality() { }
+ public void append(Value v)
+ {
+ throw new InternalExpressionException("Cannot append a value to abstract list");
+ }
}
diff --git a/src/main/java/carpet/script/value/BlockValue.java b/src/main/java/carpet/script/value/BlockValue.java
index 49f21c73..ad5a4a91 100644
--- a/src/main/java/carpet/script/value/BlockValue.java
+++ b/src/main/java/carpet/script/value/BlockValue.java
@@ -13,6 +13,7 @@ import net.minecraft.item.ItemPlacementContext;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.GlobalPos;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
@@ -254,6 +255,14 @@ public class BlockValue extends Value
return new BlockValue(blockState, world, pos);
}
+ @Override
+ public int hashCode()
+ {
+ if (world != null && pos != null )
+ return GlobalPos.create(world.getDimension().getType(), pos).hashCode();
+ return ("b"+getString()).hashCode();
+ }
+
public BlockPos getPos()
{
return pos;
diff --git a/src/main/java/carpet/script/value/ContainerValueInterface.java b/src/main/java/carpet/script/value/ContainerValueInterface.java
new file mode 100644
index 00000000..3b62d327
--- /dev/null
+++ b/src/main/java/carpet/script/value/ContainerValueInterface.java
@@ -0,0 +1,13 @@
+package carpet.script.value;
+
+public interface ContainerValueInterface
+{
+ public Value put(Value where, Value value);
+ default Value put(Value where, Value value, Value conditions)
+ {
+ return put(where, value);
+ }
+ public Value get(Value where);
+ public boolean has(Value where);
+ public Value delete(Value where);
+}
diff --git a/src/main/java/carpet/script/value/EntityValue.java b/src/main/java/carpet/script/value/EntityValue.java
index 52961658..5338278b 100644
--- a/src/main/java/carpet/script/value/EntityValue.java
+++ b/src/main/java/carpet/script/value/EntityValue.java
@@ -101,13 +101,13 @@ public class EntityValue extends Value
}
@Override
- public boolean equals(Value v)
+ public boolean equals(Object v)
{
if (v instanceof EntityValue)
{
return entity.getEntityId()==((EntityValue) v).entity.getEntityId();
}
- return super.equals(v);
+ return super.equals((Value)v);
}
@Override
@@ -138,6 +138,13 @@ public class EntityValue extends Value
return "entity";
}
+
+ @Override
+ public int hashCode()
+ {
+ return entity.hashCode();
+ }
+
public static Pair, Predicate super Entity>> getPredicate(String who)
{
Pair, Predicate super Entity>> res = entityPredicates.get(who);
@@ -386,21 +393,11 @@ public class EntityValue extends Value
put("nbt",(e, a) -> {
CompoundTag nbttagcompound = e.toTag((new CompoundTag()));
if (a==null)
- return new NBTSerializableValue(nbttagcompound);//StringValue(nbttagcompound.toString());
- NbtPathArgumentType.NbtPath path = NBTSerializableValue.cachePath(a.getString());
- try
- {
- List tags = path.get(nbttagcompound);
- if (tags.size()==0)
- return Value.NULL;
- if (tags.size()==1)
- return NBTSerializableValue.decodeTag(tags.get(0));
- return ListValue.wrap(tags.stream().map(NBTSerializableValue::decodeTag).collect(Collectors.toList()));
- }
- catch (CommandSyntaxException ignored) { }
- return Value.NULL;
+ return new NBTSerializableValue(nbttagcompound);
+ return new NBTSerializableValue(nbttagcompound).get(a);
});
}};
+
private static Req assertEntityArgType(Class klass, Value arg)
{
if (!(arg instanceof EntityValue))
@@ -728,6 +725,6 @@ public class EntityValue extends Value
put("on_death", (c, e, f, l) -> ((EntityInterface)e).setDeathCallback(c, f, l));
put("on_removed", (c, e, f, l) -> ((EntityInterface)e).setRemovedCallback(c, f, l));
put("on_tick", (c, e, f, l) -> ((EntityInterface)e).setTickCallback(c, f, l));
- put("on_damage", (c, e, f, l) -> ((EntityInterface)e).setDamageCallback(c, f, l));
+ put("on_damaged", (c, e, f, l) -> ((EntityInterface)e).setDamageCallback(c, f, l));
}};
}
diff --git a/src/main/java/carpet/script/value/FunctionSignatureValue.java b/src/main/java/carpet/script/value/FunctionSignatureValue.java
index 73976f6b..48596a49 100644
--- a/src/main/java/carpet/script/value/FunctionSignatureValue.java
+++ b/src/main/java/carpet/script/value/FunctionSignatureValue.java
@@ -43,5 +43,11 @@ public class FunctionSignatureValue extends Value
}
public List getGlobals() {return globals;}
+ @Override
+ public int hashCode()
+ {
+ throw new UnsupportedOperationException("function signature value cannot be used as map key");
+ }
+
}
diff --git a/src/main/java/carpet/script/value/GlobalValue.java b/src/main/java/carpet/script/value/GlobalValue.java
index 47dcf994..451582d9 100644
--- a/src/main/java/carpet/script/value/GlobalValue.java
+++ b/src/main/java/carpet/script/value/GlobalValue.java
@@ -19,4 +19,10 @@ public class GlobalValue extends Value
{
return false;
}
+
+ @Override
+ public int hashCode()
+ {
+ throw new UnsupportedOperationException("global value cannot be used as map key");
+ }
}
diff --git a/src/main/java/carpet/script/value/LazyListValue.java b/src/main/java/carpet/script/value/LazyListValue.java
index 016d6556..4a22d0de 100644
--- a/src/main/java/carpet/script/value/LazyListValue.java
+++ b/src/main/java/carpet/script/value/LazyListValue.java
@@ -44,6 +44,12 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato
{
return stepp > 0?(current < limit):(current > limit);
}
+
+ @Override
+ public String getString()
+ {
+ return String.format("[%d, %d, ..., %d]",start, start+stepp, limit);
+ }
};
}
@@ -115,16 +121,11 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato
}
@Override
- public boolean equals(final Value o)
+ public boolean equals(final Object o)
{
return false;
}
- @Override
- public Value getElementAt(Value v2)
- {
- throw new InternalExpressionException("get element can only be obtained for regular lists");
- }
@Override
public String getTypeString()
{
@@ -145,4 +146,10 @@ public abstract class LazyListValue extends AbstractListValue implements Iterato
((LazyListValue)copy).reset();
return copy;
}
+
+ @Override
+ public int hashCode()
+ {
+ return ("i"+getString()).hashCode();
+ }
}
diff --git a/src/main/java/carpet/script/value/ListValue.java b/src/main/java/carpet/script/value/ListValue.java
index 6f0b656c..3e1b43fd 100644
--- a/src/main/java/carpet/script/value/ListValue.java
+++ b/src/main/java/carpet/script/value/ListValue.java
@@ -4,16 +4,13 @@ import carpet.script.exception.InternalExpressionException;
import net.minecraft.item.ItemStack;
import net.minecraft.util.registry.Registry;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
+import java.util.*;
import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
import static java.lang.Math.abs;
-public class ListValue extends AbstractListValue
+public class ListValue extends AbstractListValue implements ContainerValueInterface
{
protected List items;
@Override
@@ -102,6 +99,7 @@ public class ListValue extends AbstractListValue
}
return output;
}
+ @Override
public void append(Value v)
{
items.add(v);
@@ -215,18 +213,11 @@ public class ListValue extends AbstractListValue
}
@Override
- public boolean equals(final Value o)
+ public boolean equals(final Object o)
{
if (o instanceof ListValue)
{
- ListValue ol = (ListValue)o;
- int this_size = this.getItems().size();
- int o_size = ol.getItems().size();
- if (this_size != o_size) return false;
- if (this_size == 0) return true;
- for (int i = 0; i < this_size; i++)
- if (!this.items.get(i).equals(ol.items.get(i))) return false;
- return true;
+ return getItems().equals(((ListValue) o).getItems());
}
return false;
}
@@ -275,6 +266,8 @@ public class ListValue extends AbstractListValue
super(list);
}
}
+
+ @Override
public int length()
{
return items.size();
@@ -313,7 +306,69 @@ public class ListValue extends AbstractListValue
}
@Override
- public Value getElementAt(Value value)
+ public Value put(Value where, Value value, Value conditionValue)
+ {
+ String condition = conditionValue.getString();
+ if (condition.equalsIgnoreCase("insert"))
+ return put(where, value, false, false);
+ if (condition.equalsIgnoreCase("extend"))
+ return put(where, value, false, true);
+ if (condition.equalsIgnoreCase("replace"))
+ return put(where, value, true, false);
+ throw new InternalExpressionException("list put modifier could be either insert, or replace");
+ }
+
+ @Override
+ public Value put(Value ind, Value value)
+ {
+ return put(ind, value, true, false);
+ }
+ private Value put(Value ind, Value value, boolean replace, boolean extend)
+ {
+ if (ind == Value.NULL)
+ {
+ if (extend && value instanceof AbstractListValue)
+ {
+ ((AbstractListValue) value).iterator().forEachRemaining((v)-> items.add(v));
+ }
+ else
+ {
+ items.add(value);
+ }
+ return items.get(items.size()-1);
+ }
+ else
+ {
+ int numitems = items.size();
+ int index = (int) NumericValue.asNumber(ind).getLong();
+ if (index < 0)
+ {
+ long range = abs(index) / numitems;
+ index += (range + 2) * numitems;
+ index = index % numitems;
+ }
+ if (replace)
+ {
+ while (index >= items.size()) items.add(Value.NULL);
+ return items.set(index, value);
+ }
+ while (index > items.size()) items.add(Value.NULL);
+
+ if (extend && value instanceof AbstractListValue)
+ {
+ Iterable iterable = ((AbstractListValue) value)::iterator;
+ List appendix = StreamSupport.stream( iterable.spliterator(), false).collect(Collectors.toList());
+ items.addAll(index, appendix );
+ return items.get(index+appendix.size()-1);
+ }
+ items.add(index, value);
+ return value;
+
+ }
+ }
+
+ @Override
+ public Value get(Value value)
{
long index = NumericValue.asNumber(value).getLong();
int numitems = items.size();
@@ -322,9 +377,34 @@ public class ListValue extends AbstractListValue
index = index % numitems;
return items.get((int)index);
}
+
+ @Override
+ public boolean has(Value where)
+ {
+ long index = NumericValue.asNumber(where).getLong();
+ return index >= 0 && index < items.size();
+ }
+
+ @Override
+ public Value delete(Value where)
+ {
+ long index = NumericValue.asNumber(where).getLong();
+ int numitems = items.size();
+ long range = abs(index)/numitems;
+ index += (range+2)*numitems;
+ index = index % numitems;
+ return items.remove((int)index);
+ }
+
@Override
public String getTypeString()
{
return "list";
}
+
+ @Override
+ public int hashCode()
+ {
+ return items.hashCode();
+ }
}
diff --git a/src/main/java/carpet/script/value/MapValue.java b/src/main/java/carpet/script/value/MapValue.java
new file mode 100644
index 00000000..bf217311
--- /dev/null
+++ b/src/main/java/carpet/script/value/MapValue.java
@@ -0,0 +1,208 @@
+package carpet.script.value;
+
+import carpet.script.exception.InternalExpressionException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class MapValue extends AbstractListValue implements ContainerValueInterface
+{
+ private Map map;
+
+ private MapValue()
+ {
+ map = new HashMap<>();
+ }
+
+ public MapValue(List kvPairs)
+ {
+ this();
+ for (Value v : kvPairs)
+ {
+ put(v);
+ }
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return map.keySet().iterator();
+ }
+
+ @Override
+ public String getString()
+ {
+ return "{"+map.entrySet().stream().map((p) -> p.getKey().getString()+": "+p.getValue().getString()).collect(Collectors.joining(", "))+"}";
+ }
+
+ @Override
+ public String getPrettyString()
+ {
+ if (map.size()<6)
+ return "{"+map.entrySet().stream().map((p) -> p.getKey().getPrettyString()+": "+p.getValue().getPrettyString()).collect(Collectors.joining(", "))+"}";
+ List keys = new ArrayList<>(map.keySet());
+ int max = keys.size();
+ return "{"+keys.get(0).getPrettyString()+": "+map.get(keys.get(0)).getPrettyString()+", "+
+ keys.get(1).getPrettyString()+": "+map.get(keys.get(1)).getPrettyString()+", ..., "+
+ keys.get(max-2).getPrettyString()+": "+map.get(keys.get(max-2)).getPrettyString()+", "+
+ keys.get(max-1).getPrettyString()+": "+map.get(keys.get(max-1)).getPrettyString()+"}";
+ }
+
+ @Override
+ public boolean getBoolean()
+ {
+ return !map.isEmpty();
+ }
+
+ @Override
+ public Value clone()
+ {
+ return new MapValue(map);
+ }
+ public MapValue(Map other)
+ {
+ map = new HashMap<>(other);
+ }
+
+ @Override
+ public Value add(Value o)
+ {
+ throw new InternalExpressionException("cannot add to a map value");
+ }
+
+ @Override
+ public Value subtract(Value v)
+ {
+ throw new InternalExpressionException("cannot subtract to a map value");
+ }
+
+ @Override
+ public Value multiply(Value v)
+ {
+ throw new InternalExpressionException("cannot multiply to a map value");
+ }
+
+ @Override
+ public Value divide(Value v)
+ {
+ throw new InternalExpressionException("cannot divide to a map value");
+ }
+
+ public void put(Value v)
+ {
+ if (!(v instanceof ListValue))
+ {
+ map.put(v, Value.NULL);
+ return;
+ }
+ ListValue pair = (ListValue)v;
+ if (pair.getItems().size() != 2)
+ {
+ throw new InternalExpressionException("map constructor requires elements that have two elements");
+ }
+ map.put(pair.getItems().get(0), pair.getItems().get(1));
+ }
+
+ @Override
+ public void append(Value v)
+ {
+ put(v);
+ }
+
+ @Override
+ public int compareTo(Value o)
+ {
+ throw new InternalExpressionException("cannot compare to a map value");
+ }
+
+ @Override
+ public boolean equals(final Object o)
+ {
+ if (o instanceof MapValue)
+ {
+ return map.equals(((MapValue) o).map);
+ }
+ return false;
+ }
+
+ public Map getMap()
+ {
+ return map;
+ }
+
+ public void extend(List subList)
+ {
+ for (Value v: subList) put(v);
+ }
+
+ @Override
+ public int length()
+ {
+ return map.size();
+ }
+
+ @Override
+ public Value in(Value value1)
+ {
+ if (map.containsKey(value1)) return value1;
+ return Value.NULL;
+ }
+
+ @Override
+ public Value slice(long from, long to)
+ {
+ throw new InternalExpressionException("cannot slice a map value");
+ }
+
+ @Override
+ public double readNumber()
+ {
+ return (double)map.size();
+ }
+
+ @Override
+ public Value get(Value v2)
+ {
+ return map.getOrDefault(v2, Value.NULL);
+ }
+
+ @Override
+ public boolean has(Value where)
+ {
+ return map.containsKey(where);
+ }
+
+ @Override
+ public Value delete(Value where)
+ {
+ Value ret = map.remove(where);
+ if (ret == null)
+ return Value.NULL;
+ return ret;
+ }
+
+ @Override
+ public Value put(Value key, Value value)
+ {
+ Value ret = map.put(key, value);
+ if (ret == null)
+ return Value.NULL;
+ return ret;
+ }
+
+ @Override
+ public String getTypeString()
+ {
+ return "map";
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return map.hashCode();
+ }
+}
diff --git a/src/main/java/carpet/script/value/NBTSerializableValue.java b/src/main/java/carpet/script/value/NBTSerializableValue.java
index efb616ee..ebfee982 100644
--- a/src/main/java/carpet/script/value/NBTSerializableValue.java
+++ b/src/main/java/carpet/script/value/NBTSerializableValue.java
@@ -14,23 +14,28 @@ import net.minecraft.entity.passive.VillagerEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
+import net.minecraft.nbt.AbstractListTag;
import net.minecraft.nbt.AbstractNumberTag;
import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.StringNbtReader;
import net.minecraft.nbt.Tag;
import net.minecraft.util.math.BlockPos;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
-public class NBTSerializableValue extends Value
+public class NBTSerializableValue extends Value implements ContainerValueInterface
{
private String nbtString = null;
private Tag nbtTag = null;
- private Supplier nbtSupplier = null;
+ private Supplier nbtSupplier = null;
public NBTSerializableValue(ItemStack stack)
{
@@ -43,7 +48,7 @@ public class NBTSerializableValue extends Value
{
try
{
- return (new StringNbtReader(new StringReader(nbtString))).parseCompoundTag();
+ return (new StringNbtReader(new StringReader(nbtString))).parseTag();
}
catch (CommandSyntaxException e)
{
@@ -52,6 +57,26 @@ public class NBTSerializableValue extends Value
};
}
+ public static NBTSerializableValue parseString(String nbtString)
+ {
+ Tag tag;
+ try
+ {
+ tag = (new StringNbtReader(new StringReader(nbtString))).parseTag();
+ }
+ catch (CommandSyntaxException e)
+ {
+ return null;
+ }
+ NBTSerializableValue value = new NBTSerializableValue(tag);
+ value.nbtString = nbtString;
+ return value;
+ }
+
+ public Value clone()
+ {
+ return new NBTSerializableValue(getTag().copy());
+ }
public NBTSerializableValue(Tag tag)
{
nbtTag = tag;
@@ -156,7 +181,7 @@ public class NBTSerializableValue extends Value
return slot;
}
- public static Value decodeTag(Tag t)
+ private static Value decodeTag(Tag t)
{
if (t instanceof CompoundTag)
return new NBTSerializableValue(t);
@@ -166,19 +191,26 @@ public class NBTSerializableValue extends Value
return new StringValue(t.asString());
}
- public Tag getTag()
+ private Tag getTag()
{
if (nbtTag == null)
nbtTag = nbtSupplier.get();
return nbtTag;
}
+ private void replaceTag(Tag newTag)
+ {
+ nbtTag = newTag;
+ nbtString = null;
+ nbtSupplier = null; // just to be sure
+ }
+
@Override
- public boolean equals(final Value o)
+ public boolean equals(final Object o)
{
if (o instanceof NBTSerializableValue)
return getTag().equals(((NBTSerializableValue) o).getTag());
- return super.equals(o);
+ return super.equals((Value)o);
}
@Override
@@ -207,6 +239,188 @@ public class NBTSerializableValue extends Value
}
}
+ @Override
+ public Value put(Value where, Value value)
+ {
+ return put(where, value, new StringValue("replace"));
+ }
+
+ @Override
+ public Value put(Value where, Value value, Value conditions)
+ {
+ /// WIP
+
+ NbtPathArgumentType.NbtPath path = cachePath(where.getString());
+ Tag tagToInsert = value instanceof NBTSerializableValue ?
+ ((NBTSerializableValue) value).getTag() :
+ new NBTSerializableValue(value.getString()).getTag();
+ Tag modifiedTag;
+ if (conditions instanceof NumericValue)
+ {
+ modifiedTag = modify_insert((int)((NumericValue) conditions).getLong(), path, tagToInsert);
+ }
+ else
+ {
+ String ops = conditions.getString();
+ if (ops.equalsIgnoreCase("merge"))
+ {
+ modifiedTag = modify_merge(path, tagToInsert);
+ }
+ else if (ops.equalsIgnoreCase("replace"))
+ {
+ modifiedTag = modify_replace(path, tagToInsert);
+ }
+ else
+ {
+ return this;
+ }
+ }
+ if (modifiedTag != null)
+ {
+ replaceTag(modifiedTag);
+ }
+ return this;
+ }
+
+
+
+ private Tag modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement)
+ {
+ return modify_insert(index, nbtPath, newElement, this.getTag().copy());
+ }
+
+ private Tag modify_insert(int index, NbtPathArgumentType.NbtPath nbtPath, Tag newElement, Tag currentTag)
+ {
+ Collection targets;
+ try
+ {
+ targets = nbtPath.putIfAbsent(currentTag, ListTag::new);
+ }
+ catch (CommandSyntaxException e)
+ {
+ return null;
+ }
+
+ boolean modified = false;
+ for (Tag target : targets)
+ {
+ if (!(target instanceof AbstractListTag))
+ {
+ continue;
+ }
+ try
+ {
+ AbstractListTag> targetList = (AbstractListTag) target;
+ if (!targetList.addTag(index < 0 ? targetList.size() + index + 1 : index, newElement.copy()))
+ return null;
+ modified = true;
+ }
+ catch (IndexOutOfBoundsException ignored)
+ {
+ }
+ }
+ return modified?currentTag:null;
+ }
+
+
+ private Tag modify_merge(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1)
+ {
+ if (!(replacement instanceof CompoundTag))
+ {
+ return getTag();
+ }
+ Tag originalTag = getTag().copy();
+ try
+ {
+ for (Tag target : nbtPath.putIfAbsent(originalTag, CompoundTag::new))
+ {
+ if (!(target instanceof CompoundTag))
+ {
+ continue;
+ }
+ ((CompoundTag) target).copyFrom((CompoundTag) replacement);
+ }
+ }
+ catch (CommandSyntaxException ignored) { }
+ return originalTag;
+ }
+
+ private Tag modify_replace(NbtPathArgumentType.NbtPath nbtPath, Tag replacement) //nbtPathArgumentType$NbtPath_1, list_1)
+ {
+ Tag originalTag = getTag().copy();
+ String pathText = nbtPath.toString();
+ if (pathText.endsWith("]")) // workaround for array replacement or item in the array replacement
+ {
+ if (nbtPath.remove(originalTag)==0)
+ return null;
+ Pattern pattern = Pattern.compile("\\[[^\\[]*]$");
+ Matcher matcher = pattern.matcher(pathText);
+ if (!matcher.find()) // malformed path
+ {
+ return null;
+ }
+ String arrAccess = matcher.group();
+ int pos;
+ if (arrAccess.length()==2) // we just removed entire array
+ pos = 0;
+ else
+ {
+ try
+ {
+ pos = Integer.parseInt(arrAccess.substring(1, arrAccess.length() - 1));
+ }
+ catch (NumberFormatException e)
+ {
+ return null;
+ }
+ }
+ NbtPathArgumentType.NbtPath newPath = cachePath(pathText.substring(0, pathText.length()-arrAccess.length()));
+ return modify_insert(pos,newPath,replacement, originalTag);
+ }
+ try
+ {
+ nbtPath.put(originalTag, () -> replacement);
+ }
+ catch (CommandSyntaxException e)
+ {
+ return null;
+ }
+ return originalTag;
+ }
+
+ @Override
+ public Value get(Value value)
+ {
+ NbtPathArgumentType.NbtPath path = cachePath(value.getString());
+ try
+ {
+ List tags = path.get(getTag());
+ if (tags.size()==0)
+ return Value.NULL;
+ if (tags.size()==1)
+ return NBTSerializableValue.decodeTag(tags.get(0));
+ return ListValue.wrap(tags.stream().map(NBTSerializableValue::decodeTag).collect(Collectors.toList()));
+ }
+ catch (CommandSyntaxException ignored) { }
+ return Value.NULL;
+ }
+
+ @Override
+ public boolean has(Value where)
+ {
+ return cachePath(where.getString()).count(getTag()) > 0;
+ }
+
+ @Override
+ public Value delete(Value where)
+ {
+ NbtPathArgumentType.NbtPath path = cachePath(where.getString());
+ Tag tag = getTag().copy();
+ int removed = path.remove(tag);
+ if (removed > 0) replaceTag(tag);
+ return new NumericValue(removed);
+ }
+
public static class InventoryLocator
{
public Object owner;
@@ -223,7 +437,7 @@ public class NBTSerializableValue extends Value
}
private static Map pathCache = new HashMap<>();
- public static NbtPathArgumentType.NbtPath cachePath(String arg)
+ private static NbtPathArgumentType.NbtPath cachePath(String arg)
{
NbtPathArgumentType.NbtPath res = pathCache.get(arg);
if (res != null)
@@ -242,23 +456,6 @@ public class NBTSerializableValue extends Value
return res;
}
- @Override
- public Value getElementAt(Value value)
- {
- NbtPathArgumentType.NbtPath path = cachePath(value.getString());
- try
- {
- List tags = path.get(getTag());
- if (tags.size()==0)
- return Value.NULL;
- if (tags.size()==1)
- return NBTSerializableValue.decodeTag(tags.get(0));
- return ListValue.wrap(tags.stream().map(NBTSerializableValue::decodeTag).collect(Collectors.toList()));
- }
- catch (CommandSyntaxException ignored) { }
- return Value.NULL;
- }
-
@Override
public String getTypeString()
{
diff --git a/src/main/java/carpet/script/value/NullValue.java b/src/main/java/carpet/script/value/NullValue.java
index a749a40b..eeef2f0d 100644
--- a/src/main/java/carpet/script/value/NullValue.java
+++ b/src/main/java/carpet/script/value/NullValue.java
@@ -28,7 +28,7 @@ public class NullValue extends NumericValue // TODO check nonsingleton code
public NullValue() {super(0.0D);}
@Override
- public boolean equals(final Value o)
+ public boolean equals(final Object o)
{
return o instanceof NullValue;
}
@@ -44,4 +44,10 @@ public class NullValue extends NumericValue // TODO check nonsingleton code
{
return "null";
}
+
+ @Override
+ public int hashCode()
+ {
+ return 0;
+ }
}
diff --git a/src/main/java/carpet/script/value/NumericValue.java b/src/main/java/carpet/script/value/NumericValue.java
index c47c6ae0..77ca3ea7 100644
--- a/src/main/java/carpet/script/value/NumericValue.java
+++ b/src/main/java/carpet/script/value/NumericValue.java
@@ -125,7 +125,7 @@ public class NumericValue extends Value
return getString().compareTo(o.getString());
}
@Override
- public boolean equals(Value o)
+ public boolean equals(Object o)
{
if (o instanceof NullValue)
{
@@ -133,7 +133,7 @@ public class NumericValue extends Value
}
if (o instanceof NumericValue)
{
- return !this.subtract(o).getBoolean();
+ return !this.subtract((Value) o).getBoolean();
}
return super.equals(o);
}
@@ -179,4 +179,13 @@ public class NumericValue extends Value
return "number";
}
+ @Override
+ public int hashCode()
+ {
+ if (Math.abs(Math.floor(value + 0.5D)-value) < epsilon) // is sufficiently close to the integer value
+ return Long.hashCode(getLong());
+ return Double.hashCode(value);
+ }
+
+
}
diff --git a/src/main/java/carpet/script/value/Value.java b/src/main/java/carpet/script/value/Value.java
index ce061449..c585748d 100644
--- a/src/main/java/carpet/script/value/Value.java
+++ b/src/main/java/carpet/script/value/Value.java
@@ -115,6 +115,16 @@ public abstract class Value implements Comparable, Cloneable
}
return getString().compareTo(o.getString());
}
+
+ @Override // for hashmap key access
+ public boolean equals(final Object o)
+ {
+ if (o instanceof Value)
+ return this.compareTo((Value) o)==0;
+ return false;
+ }
+
+ // for general use
public boolean equals(final Value o)
{
return this.compareTo(o)==0;
@@ -173,13 +183,16 @@ public abstract class Value implements Comparable, Cloneable
return (long)readNumber();
}
- public Value getElementAt(Value v2)
- {
- throw new InternalExpressionException("get element can only be obtained for lists and nbt values");
- }
-
public String getTypeString()
{
throw new InternalExpressionException("How did you get here? Cannot get type of an intenal type.");
}
+
+ @Override
+ public int hashCode()
+ {
+ String stringVal = getString();
+ if (stringVal.isEmpty()) return 0;
+ return ("s"+stringVal).hashCode();
+ }
}
diff --git a/src/main/resources/carpet.mixins.json b/src/main/resources/carpet.mixins.json
index 4581b9aa..fc54826d 100644
--- a/src/main/resources/carpet.mixins.json
+++ b/src/main/resources/carpet.mixins.json
@@ -1,5 +1,6 @@
{
"required": true,
+ "refmap": "carpet-refmap.json",
"package": "carpet.mixins",
"compatibilityLevel": "JAVA_8",
"mixins": [
@@ -125,6 +126,7 @@
"Entity_scarpetEventsMixin",
"LivingEntity_scarpetEventsMixin",
"DamageTracker_scarpetEventMixin",
+ "ServerPlayerEntity_scarpetEventMixin",
"PistonBlock_movableTEMixin",