diff --git a/src/main/java/carpet/commands/ScriptCommand.java b/src/main/java/carpet/commands/ScriptCommand.java index 347876f2..fb4b690f 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 6e008402..a656b640 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 { @@ -42,6 +46,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 aacf95ac..82420afe 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;method_14263(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;method_14263(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 0ab51a92..84e27f33 100644 --- a/src/main/java/carpet/script/CarpetExpression.java +++ b/src/main/java/carpet/script/CarpetExpression.java @@ -99,7 +99,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 @@ -108,7 +108,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 { @@ -347,7 +348,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

@@ -400,6 +401,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)

@@ -997,6 +1000,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; BlockValue.LocatorResult locator = BlockValue.fromParams(cc, lv, 0); @@ -1177,6 +1190,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(); } @@ -1239,7 +1254,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) @@ -1462,9 +1477,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')

@@ -1526,10 +1543,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.

@@ -1555,7 +1571,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)

@@ -1574,6 +1591,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.

*
*/ @@ -1671,8 +1726,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; @@ -2102,6 +2161,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); + } }; }); @@ -2193,6 +2258,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 @@ -2249,6 +2320,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); + } }; } }); @@ -2284,8 +2361,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.

@@ -2305,11 +2382,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.

@@ -2606,7 +2683,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; }); @@ -3003,22 +3082,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)
@@ -3026,7 +3108,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 9818e8ee..c84071bc 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 e357fa10..32ad09b3 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> getPredicate(String who) { Pair, Predicate> 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 bbda0dea..afc47410 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": [ @@ -123,6 +124,7 @@ "Entity_scarpetEventsMixin", "LivingEntity_scarpetEventsMixin", "DamageTracker_scarpetEventMixin", + "ServerPlayerEntity_scarpetEventMixin", "PistonBlock_movableTEMixin",