mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-17 15:07:38 +00:00
Compare commits
16 Commits
v1.19.2-1.
...
mc-1.19.2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f4de575d35 | ||
![]() |
c1954b4071 | ||
![]() |
f9bb1b4979 | ||
![]() |
edf372a695 | ||
![]() |
aa89e51639 | ||
![]() |
7436447a6e | ||
![]() |
f629831b12 | ||
![]() |
f7fdb6e729 | ||
![]() |
db2616d1c0 | ||
![]() |
c0f982dc97 | ||
![]() |
2a9f35de5e | ||
![]() |
0fce3212a3 | ||
![]() |
652f954886 | ||
![]() |
6f65bad9af | ||
![]() |
e4dd4dbef0 | ||
![]() |
e1dffaa334 |
@@ -5,7 +5,7 @@ kotlin.stdlib.default.dependency=false
|
|||||||
kotlin.jvm.target.validation.mode=error
|
kotlin.jvm.target.validation.mode=error
|
||||||
|
|
||||||
# Mod properties
|
# Mod properties
|
||||||
modVersion=1.101.2
|
modVersion=1.101.4
|
||||||
|
|
||||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||||
mcVersion=1.19.2
|
mcVersion=1.19.2
|
||||||
|
1
src/generated/resources/data/computercraft/tags/blocks/turtle_can_use.json
generated
Normal file
1
src/generated/resources/data/computercraft/tags/blocks/turtle_can_use.json
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"values": ["#minecraft:cauldrons", "#minecraft:beehives", "minecraft:composter"]}
|
@@ -12,6 +12,8 @@ import net.minecraft.tags.ItemTags;
|
|||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tags provided by ComputerCraft.
|
* Tags provided by ComputerCraft.
|
||||||
@@ -58,6 +60,12 @@ public class ComputerCraftTags
|
|||||||
*/
|
*/
|
||||||
public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make( "turtle_hoe_harvestable" );
|
public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make( "turtle_hoe_harvestable" );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block which can be {@linkplain BlockState#use(Level, Player, InteractionHand, BlockHitResult) used} when
|
||||||
|
* calling {@code turtle.place()}.
|
||||||
|
*/
|
||||||
|
public static final TagKey<Block> TURTLE_CAN_USE = make( "turtle_can_use" );
|
||||||
|
|
||||||
private static TagKey<Block> make( String name )
|
private static TagKey<Block> make( String name )
|
||||||
{
|
{
|
||||||
return BlockTags.create( new ResourceLocation( ComputerCraft.MOD_ID, name ) );
|
return BlockTags.create( new ResourceLocation( ComputerCraft.MOD_ID, name ) );
|
||||||
|
@@ -8,9 +8,13 @@ package dan200.computercraft.core.apis.http.options;
|
|||||||
import com.google.common.net.InetAddresses;
|
import com.google.common.net.InetAddresses;
|
||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
|
||||||
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A predicate on an address. Matches against a domain and an ip address.
|
* A predicate on an address. Matches against a domain and an ip address.
|
||||||
@@ -135,13 +139,36 @@ interface AddressPredicate
|
|||||||
{
|
{
|
||||||
static final PrivatePattern INSTANCE = new PrivatePattern();
|
static final PrivatePattern INSTANCE = new PrivatePattern();
|
||||||
|
|
||||||
|
private static final Set<InetAddress> additionalAddresses = Arrays.stream( new String[] {
|
||||||
|
// Block various cloud providers internal IPs.
|
||||||
|
"100.100.100.200", // Alibaba
|
||||||
|
"192.0.0.192", // Oracle
|
||||||
|
} ).map( InetAddresses::forString ).collect( Collectors.toSet() );
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean matches( InetAddress socketAddress )
|
public boolean matches( InetAddress socketAddress )
|
||||||
{
|
{
|
||||||
return socketAddress.isAnyLocalAddress()
|
return socketAddress.isAnyLocalAddress() // 0.0.0.0, ::0
|
||||||
|| socketAddress.isLoopbackAddress()
|
|| socketAddress.isLoopbackAddress() // 127.0.0.0/8, ::1
|
||||||
|| socketAddress.isLinkLocalAddress()
|
|| socketAddress.isLinkLocalAddress() // 169.254.0.0/16, fe80::/10
|
||||||
|| socketAddress.isSiteLocalAddress();
|
|| socketAddress.isSiteLocalAddress() // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fec0::/10
|
||||||
|
|| socketAddress.isMulticastAddress() // 224.0.0.0/4, ff00::/8
|
||||||
|
|| isUniqueLocalAddress( socketAddress ) // fd00::/8
|
||||||
|
|| additionalAddresses.contains( socketAddress );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if an IP address lives inside the ULA address range.
|
||||||
|
*
|
||||||
|
* @param address The IP address to test.
|
||||||
|
* @return Whether this address sits in the ULA address range.
|
||||||
|
* @see <a href="https://en.wikipedia.org/wiki/Unique_local_address">Unique local address on Wikipedia</a>
|
||||||
|
*/
|
||||||
|
private boolean isUniqueLocalAddress( InetAddress address )
|
||||||
|
{
|
||||||
|
// ULA is actually defined as fc00::/7 (so both fc00::/8 and fd00::/8). However, only the latter is actually
|
||||||
|
// defined right now, so let's be conservative.
|
||||||
|
return address instanceof Inet6Address && (address.getAddress()[0] & 0xff) == 0xfd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -60,6 +60,11 @@ class BlockTagsGenerator extends BlockTagsProvider
|
|||||||
|
|
||||||
tag( TURTLE_SWORD_BREAKABLE ).addTags( BlockTags.WOOL ).add( Blocks.COBWEB );
|
tag( TURTLE_SWORD_BREAKABLE ).addTags( BlockTags.WOOL ).add( Blocks.COBWEB );
|
||||||
|
|
||||||
|
tag( TURTLE_CAN_USE )
|
||||||
|
.addTag( BlockTags.CAULDRONS )
|
||||||
|
.addTag( BlockTags.BEEHIVES )
|
||||||
|
.add( Blocks.COMPOSTER );
|
||||||
|
|
||||||
// Make all blocks aside from command computer mineable.
|
// Make all blocks aside from command computer mineable.
|
||||||
tag( BlockTags.MINEABLE_WITH_PICKAXE ).add(
|
tag( BlockTags.MINEABLE_WITH_PICKAXE ).add(
|
||||||
Registry.ModBlocks.COMPUTER_NORMAL.get(),
|
Registry.ModBlocks.COMPUTER_NORMAL.get(),
|
||||||
|
@@ -7,10 +7,13 @@ package dan200.computercraft.shared.command;
|
|||||||
|
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||||
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import com.mojang.brigadier.suggestion.Suggestions;
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
import dan200.computercraft.core.computer.ComputerSide;
|
import dan200.computercraft.core.computer.ComputerSide;
|
||||||
import dan200.computercraft.core.metrics.Metrics;
|
import dan200.computercraft.core.metrics.Metrics;
|
||||||
|
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
|
||||||
import dan200.computercraft.shared.command.text.TableBuilder;
|
import dan200.computercraft.shared.command.text.TableBuilder;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
@@ -194,7 +197,10 @@ public final class CommandComputerCraft
|
|||||||
|
|
||||||
.then( command( "queue" )
|
.then( command( "queue" )
|
||||||
.requires( UserLevel.ANYONE )
|
.requires( UserLevel.ANYONE )
|
||||||
.arg( "computer", manyComputers() )
|
.arg(
|
||||||
|
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument( "computer", manyComputers() )
|
||||||
|
.suggests( ( context, builder ) -> Suggestions.empty() )
|
||||||
|
)
|
||||||
.argManyValue( "args", StringArgumentType.string(), Collections.emptyList() )
|
.argManyValue( "args", StringArgumentType.string(), Collections.emptyList() )
|
||||||
.executes( ( ctx, args ) -> {
|
.executes( ( ctx, args ) -> {
|
||||||
Collection<ServerComputer> computers = getComputersArgument( ctx, "computer" );
|
Collection<ServerComputer> computers = getComputersArgument( ctx, "computer" );
|
||||||
|
@@ -61,12 +61,36 @@ public enum UserLevel implements Predicate<CommandSourceStack>
|
|||||||
return source.hasPermission( toLevel() );
|
return source.hasPermission( toLevel() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the union of two {@link UserLevel}s.
|
||||||
|
* <p>
|
||||||
|
* This satisfies the property that for all sources {@code s}, {@code a.test(s) || b.test(s) == (a ∪ b).test(s)}.
|
||||||
|
*
|
||||||
|
* @param left The first user level to take the union of.
|
||||||
|
* @param right The second user level to take the union of.
|
||||||
|
* @return The union of two levels.
|
||||||
|
*/
|
||||||
|
public static UserLevel union( UserLevel left, UserLevel right )
|
||||||
|
{
|
||||||
|
if( left == right ) return left;
|
||||||
|
|
||||||
|
// x ∪ ANYONE = ANYONE
|
||||||
|
if( left == ANYONE || right == ANYONE ) return ANYONE;
|
||||||
|
|
||||||
|
// x ∪ OWNER = OWNER
|
||||||
|
if( left == OWNER ) return right;
|
||||||
|
if( right == OWNER ) return left;
|
||||||
|
|
||||||
|
// At this point, we have x != y and x, y ∈ { OP, OWNER_OP }.
|
||||||
|
return OWNER_OP;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isOwner( CommandSourceStack source )
|
private static boolean isOwner( CommandSourceStack source )
|
||||||
{
|
{
|
||||||
MinecraftServer server = source.getServer();
|
MinecraftServer server = source.getServer();
|
||||||
Entity sender = source.getEntity();
|
Entity sender = source.getEntity();
|
||||||
return server.isDedicatedServer()
|
return server.isDedicatedServer()
|
||||||
? source.getEntity() == null && source.hasPermission( 4 ) && source.getTextName().equals( "Server" )
|
? source.getEntity() == null && source.hasPermission( 4 ) && source.getTextName().equals( "Server" )
|
||||||
: sender instanceof Player player && player.getGameProfile().getName().equalsIgnoreCase( server.getServerModName() );
|
: sender instanceof Player player && server.isSingleplayerOwner( player.getGameProfile() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -52,10 +52,15 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CommandBuilder<S> arg( ArgumentBuilder<S, ?> arg )
|
||||||
|
{
|
||||||
|
args.add( arg );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandBuilder<S> arg( String name, ArgumentType<?> type )
|
public CommandBuilder<S> arg( String name, ArgumentType<?> type )
|
||||||
{
|
{
|
||||||
args.add( RequiredArgumentBuilder.argument( name, type ) );
|
return arg( RequiredArgumentBuilder.argument( name, type ) );
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue( String name, ArgumentType<T> type, List<T> empty )
|
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue( String name, ArgumentType<T> type, List<T> empty )
|
||||||
@@ -84,7 +89,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
|
|||||||
|
|
||||||
return command -> {
|
return command -> {
|
||||||
// The node for no arguments
|
// The node for no arguments
|
||||||
ArgumentBuilder<S, ?> tail = tail( ctx -> command.run( ctx, empty.get() ) );
|
ArgumentBuilder<S, ?> tail = setupTail( ctx -> command.run( ctx, empty.get() ) );
|
||||||
|
|
||||||
// The node for one or more arguments
|
// The node for one or more arguments
|
||||||
ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder
|
ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder
|
||||||
@@ -93,7 +98,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
|
|||||||
|
|
||||||
// Chain all of them together!
|
// Chain all of them together!
|
||||||
tail.then( moreArg );
|
tail.then( moreArg );
|
||||||
return link( tail );
|
return buildTail( tail );
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,22 +111,18 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
|
|||||||
@Override
|
@Override
|
||||||
public CommandNode<S> executes( Command<S> command )
|
public CommandNode<S> executes( Command<S> command )
|
||||||
{
|
{
|
||||||
if( args.isEmpty() ) throw new IllegalStateException( "Cannot have empty arg chain builder" );
|
return buildTail( setupTail( command ) );
|
||||||
|
|
||||||
return link( tail( command ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArgumentBuilder<S, ?> tail( Command<S> command )
|
private ArgumentBuilder<S, ?> setupTail( Command<S> command )
|
||||||
{
|
{
|
||||||
ArgumentBuilder<S, ?> defaultTail = args.get( args.size() - 1 );
|
return args.get( args.size() - 1 ).executes( command );
|
||||||
defaultTail.executes( command );
|
|
||||||
if( requires != null ) defaultTail.requires( requires );
|
|
||||||
return defaultTail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommandNode<S> link( ArgumentBuilder<S, ?> tail )
|
private CommandNode<S> buildTail( ArgumentBuilder<S, ?> tail )
|
||||||
{
|
{
|
||||||
for( int i = args.size() - 2; i >= 0; i-- ) tail = args.get( i ).then( tail );
|
for( int i = args.size() - 2; i >= 0; i-- ) tail = args.get( i ).then( tail );
|
||||||
|
if( requires != null ) tail.requires( requires );
|
||||||
return tail.build();
|
return tail.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
|||||||
import com.mojang.brigadier.context.CommandContext;
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
|
import dan200.computercraft.shared.command.UserLevel;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.commands.CommandSourceStack;
|
import net.minecraft.commands.CommandSourceStack;
|
||||||
import net.minecraft.network.chat.ClickEvent;
|
import net.minecraft.network.chat.ClickEvent;
|
||||||
@@ -21,6 +22,10 @@ import net.minecraft.network.chat.MutableComponent;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
|
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
|
||||||
import static dan200.computercraft.shared.command.text.ChatHelpers.translate;
|
import static dan200.computercraft.shared.command.text.ChatHelpers.translate;
|
||||||
@@ -43,6 +48,33 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
|
|||||||
return new HelpingArgumentBuilder( literal );
|
return new HelpingArgumentBuilder( literal );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LiteralArgumentBuilder<CommandSourceStack> requires( Predicate<CommandSourceStack> requirement )
|
||||||
|
{
|
||||||
|
throw new IllegalStateException( "Cannot use requires on a HelpingArgumentBuilder" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Predicate<CommandSourceStack> getRequirement()
|
||||||
|
{
|
||||||
|
// The requirement of this node is the union of all child's requirements.
|
||||||
|
List<Predicate<CommandSourceStack>> requirements = Stream.concat(
|
||||||
|
children.stream().map( ArgumentBuilder::getRequirement ),
|
||||||
|
getArguments().stream().map( CommandNode::getRequirement )
|
||||||
|
).collect( Collectors.toList() );
|
||||||
|
|
||||||
|
// If all requirements are a UserLevel, take the union of those instead.
|
||||||
|
UserLevel userLevel = UserLevel.OWNER;
|
||||||
|
for( Predicate<CommandSourceStack> requirement : requirements )
|
||||||
|
{
|
||||||
|
if( !(requirement instanceof UserLevel level) ) return x -> requirements.stream().anyMatch( y -> y.test( x ) );
|
||||||
|
userLevel = UserLevel.union( userLevel, level );
|
||||||
|
}
|
||||||
|
|
||||||
|
return userLevel;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LiteralArgumentBuilder<CommandSourceStack> executes( final Command<CommandSourceStack> command )
|
public LiteralArgumentBuilder<CommandSourceStack> executes( final Command<CommandSourceStack> command )
|
||||||
{
|
{
|
||||||
@@ -98,9 +130,7 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
|
|||||||
helpCommand.node = node;
|
helpCommand.node = node;
|
||||||
|
|
||||||
// Set up a /... help command
|
// Set up a /... help command
|
||||||
LiteralArgumentBuilder<CommandSourceStack> helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal( "help" )
|
LiteralArgumentBuilder<CommandSourceStack> helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal( "help" ).executes( helpCommand );
|
||||||
.requires( x -> getArguments().stream().anyMatch( y -> y.getRequirement().test( x ) ) )
|
|
||||||
.executes( helpCommand );
|
|
||||||
|
|
||||||
// Add all normal command children to this and the help node
|
// Add all normal command children to this and the help node
|
||||||
for( CommandNode<CommandSourceStack> child : getArguments() )
|
for( CommandNode<CommandSourceStack> child : getArguments() )
|
||||||
|
@@ -167,7 +167,7 @@ public class BlockTurtle extends BlockComputerBase<TileTurtle> implements Simple
|
|||||||
@Override
|
@Override
|
||||||
public float getExplosionResistance( BlockState state, BlockGetter world, BlockPos pos, Explosion explosion )
|
public float getExplosionResistance( BlockState state, BlockGetter world, BlockPos pos, Explosion explosion )
|
||||||
{
|
{
|
||||||
Entity exploder = explosion.getExploder();
|
Entity exploder = explosion == null ? null : explosion.getExploder();
|
||||||
if( getFamily() == ComputerFamily.ADVANCED || exploder instanceof LivingEntity || exploder instanceof AbstractHurtingProjectile )
|
if( getFamily() == ComputerFamily.ADVANCED || exploder instanceof LivingEntity || exploder instanceof AbstractHurtingProjectile )
|
||||||
{
|
{
|
||||||
return 2000;
|
return 2000;
|
||||||
|
@@ -43,6 +43,8 @@ import org.apache.commons.lang3.tuple.Pair;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import static dan200.computercraft.api.ComputerCraftTags.Blocks.TURTLE_CAN_USE;
|
||||||
|
|
||||||
public class TurtlePlaceCommand implements ITurtleCommand
|
public class TurtlePlaceCommand implements ITurtleCommand
|
||||||
{
|
{
|
||||||
private final InteractDirection direction;
|
private final InteractDirection direction;
|
||||||
@@ -210,7 +212,7 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
|||||||
|
|
||||||
private static boolean deployOnBlock(
|
private static boolean deployOnBlock(
|
||||||
@Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side,
|
@Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side,
|
||||||
Object[] extraArguments, boolean allowReplace, ErrorMessage outErrorMessage
|
Object[] extraArguments, boolean adjacent, ErrorMessage outErrorMessage
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// Re-orient the fake player
|
// Re-orient the fake player
|
||||||
@@ -227,7 +229,7 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
|||||||
// Check if there's something suitable to place onto
|
// Check if there's something suitable to place onto
|
||||||
BlockHitResult hit = new BlockHitResult( new Vec3( hitX, hitY, hitZ ), side, position, false );
|
BlockHitResult hit = new BlockHitResult( new Vec3( hitX, hitY, hitZ ), side, position, false );
|
||||||
UseOnContext context = new UseOnContext( turtlePlayer, InteractionHand.MAIN_HAND, hit );
|
UseOnContext context = new UseOnContext( turtlePlayer, InteractionHand.MAIN_HAND, hit );
|
||||||
if( !canDeployOnBlock( new BlockPlaceContext( context ), turtle, turtlePlayer, position, side, allowReplace, outErrorMessage ) )
|
if( !canDeployOnBlock( new BlockPlaceContext( context ), turtle, turtlePlayer, position, side, adjacent, outErrorMessage ) )
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -235,7 +237,7 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
|||||||
Item item = stack.getItem();
|
Item item = stack.getItem();
|
||||||
BlockEntity existingTile = turtle.getLevel().getBlockEntity( position );
|
BlockEntity existingTile = turtle.getLevel().getBlockEntity( position );
|
||||||
|
|
||||||
boolean placed = doDeployOnBlock( stack, turtlePlayer, position, context, hit ).consumesAction();
|
boolean placed = doDeployOnBlock( stack, turtlePlayer, position, context, hit, adjacent ).consumesAction();
|
||||||
|
|
||||||
// Set text on signs
|
// Set text on signs
|
||||||
if( placed && item instanceof SignItem && extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String message )
|
if( placed && item instanceof SignItem && extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String message )
|
||||||
@@ -261,11 +263,13 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
|||||||
* @param position The block we're deploying against's position.
|
* @param position The block we're deploying against's position.
|
||||||
* @param context The context of this place action.
|
* @param context The context of this place action.
|
||||||
* @param hit Where the block we're placing against was clicked.
|
* @param hit Where the block we're placing against was clicked.
|
||||||
|
* @param adjacent If the block is directly adjacent to the turtle, and so can be interacted with via
|
||||||
|
* {@link BlockState#use(Level, Player, InteractionHand, BlockHitResult)}.
|
||||||
* @return If this item was deployed.
|
* @return If this item was deployed.
|
||||||
* @see net.minecraft.server.level.ServerPlayerGameMode#useItemOn For the original implementation.
|
* @see net.minecraft.server.level.ServerPlayerGameMode#useItemOn For the original implementation.
|
||||||
*/
|
*/
|
||||||
private static InteractionResult doDeployOnBlock(
|
private static InteractionResult doDeployOnBlock(
|
||||||
@Nonnull ItemStack stack, TurtlePlayer turtlePlayer, BlockPos position, UseOnContext context, BlockHitResult hit
|
@Nonnull ItemStack stack, TurtlePlayer turtlePlayer, BlockPos position, UseOnContext context, BlockHitResult hit, boolean adjacent
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
PlayerInteractEvent.RightClickBlock event = ForgeHooks.onRightClickBlock( turtlePlayer, InteractionHand.MAIN_HAND, position, hit );
|
PlayerInteractEvent.RightClickBlock event = ForgeHooks.onRightClickBlock( turtlePlayer, InteractionHand.MAIN_HAND, position, hit );
|
||||||
@@ -273,14 +277,18 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
|||||||
|
|
||||||
if( event.getUseItem() != Result.DENY )
|
if( event.getUseItem() != Result.DENY )
|
||||||
{
|
{
|
||||||
InteractionResult result = stack.onItemUseFirst( context );
|
InteractionResult resultUseFirst = stack.onItemUseFirst( context );
|
||||||
if( result != InteractionResult.PASS ) return result;
|
if( resultUseFirst != InteractionResult.PASS ) return resultUseFirst;
|
||||||
}
|
|
||||||
|
|
||||||
if( event.getUseItem() != Result.DENY )
|
var block = turtlePlayer.level.getBlockState( hit.getBlockPos() );
|
||||||
{
|
if ( event.getUseBlock() != Result.DENY && !block.isAir() && adjacent && block.is( TURTLE_CAN_USE ) )
|
||||||
InteractionResult result = stack.useOn( context );
|
{
|
||||||
if( result != InteractionResult.PASS ) return result;
|
var useResult = block.use( turtlePlayer.level, turtlePlayer, InteractionHand.MAIN_HAND, hit );
|
||||||
|
if ( useResult.consumesAction() ) return useResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
InteractionResult resultUseOn = stack.useOn( context );
|
||||||
|
if( resultUseOn != InteractionResult.PASS ) return resultUseOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
Item item = stack.getItem();
|
Item item = stack.getItem();
|
||||||
|
@@ -153,12 +153,22 @@ function locate(_nTimeout, _bDebug)
|
|||||||
if tFix.nDistance == 0 then
|
if tFix.nDistance == 0 then
|
||||||
pos1, pos2 = tFix.vPosition, nil
|
pos1, pos2 = tFix.vPosition, nil
|
||||||
else
|
else
|
||||||
table.insert(tFixes, tFix)
|
-- Insert our new position in our table, with a maximum of three items. If this is close to a
|
||||||
|
-- previous position, replace that instead of inserting.
|
||||||
|
local insIndex = math.min(3, #tFixes + 1)
|
||||||
|
for i, older in pairs(tFixes) do
|
||||||
|
if (older.vPosition - tFix.vPosition):length() < 1 then
|
||||||
|
insIndex = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
tFixes[insIndex] = tFix
|
||||||
|
|
||||||
if #tFixes >= 3 then
|
if #tFixes >= 3 then
|
||||||
if not pos1 then
|
if not pos1 then
|
||||||
pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[#tFixes])
|
pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[3])
|
||||||
else
|
else
|
||||||
pos1, pos2 = narrow(pos1, pos2, tFixes[#tFixes])
|
pos1, pos2 = narrow(pos1, pos2, tFixes[3])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -74,8 +74,7 @@ function undefine(name)
|
|||||||
details[name] = nil
|
details[name] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function set_value(name, value)
|
local function set_value(name, new)
|
||||||
local new = reserialize(value)
|
|
||||||
local old = values[name]
|
local old = values[name]
|
||||||
if old == nil then
|
if old == nil then
|
||||||
local opt = details[name]
|
local opt = details[name]
|
||||||
@@ -103,7 +102,7 @@ function set(name, value)
|
|||||||
local opt = details[name]
|
local opt = details[name]
|
||||||
if opt and opt.type then expect(2, value, opt.type) end
|
if opt and opt.type then expect(2, value, opt.type) end
|
||||||
|
|
||||||
set_value(name, value)
|
set_value(name, reserialize(value))
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Get the value of a setting.
|
--- Get the value of a setting.
|
||||||
@@ -214,7 +213,9 @@ function load(sPath)
|
|||||||
if type(k) == "string" and (ty_v == "string" or ty_v == "number" or ty_v == "boolean" or ty_v == "table") then
|
if type(k) == "string" and (ty_v == "string" or ty_v == "number" or ty_v == "boolean" or ty_v == "table") then
|
||||||
local opt = details[k]
|
local opt = details[k]
|
||||||
if not opt or not opt.type or ty_v == opt.type then
|
if not opt or not opt.type or ty_v == opt.type then
|
||||||
set_value(k, v)
|
-- This may fail if the table is recursive (or otherwise cannot be serialized).
|
||||||
|
local ok, v = pcall(reserialize, v)
|
||||||
|
if ok then set_value(k, v) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -292,6 +292,13 @@ local g_tLuaKeywords = {
|
|||||||
["while"] = true,
|
["while"] = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--- A version of the ipairs iterator which ignores metamethods
|
||||||
|
local function inext(tbl, i)
|
||||||
|
i = (i or 0) + 1
|
||||||
|
local v = rawget(tbl, i)
|
||||||
|
if v == nil then return nil else return i, v end
|
||||||
|
end
|
||||||
|
|
||||||
local serialize_infinity = math.huge
|
local serialize_infinity = math.huge
|
||||||
local function serialize_impl(t, tracking, indent, opts)
|
local function serialize_impl(t, tracking, indent, opts)
|
||||||
local sType = type(t)
|
local sType = type(t)
|
||||||
@@ -318,11 +325,11 @@ local function serialize_impl(t, tracking, indent, opts)
|
|||||||
|
|
||||||
result = open
|
result = open
|
||||||
local seen_keys = {}
|
local seen_keys = {}
|
||||||
for k, v in ipairs(t) do
|
for k, v in inext, t do
|
||||||
seen_keys[k] = true
|
seen_keys[k] = true
|
||||||
result = result .. sub_indent .. serialize_impl(v, tracking, sub_indent, opts) .. comma
|
result = result .. sub_indent .. serialize_impl(v, tracking, sub_indent, opts) .. comma
|
||||||
end
|
end
|
||||||
for k, v in pairs(t) do
|
for k, v in next, t do
|
||||||
if not seen_keys[k] then
|
if not seen_keys[k] then
|
||||||
local sEntry
|
local sEntry
|
||||||
if type(k) == "string" and not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then
|
if type(k) == "string" and not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then
|
||||||
|
@@ -127,11 +127,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
|||||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||||
for y = 1, nHeight do
|
for y = 1, nHeight do
|
||||||
tLines[y] = {
|
tLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
|
||||||
text = sEmptyText,
|
|
||||||
textColor = sEmptyTextColor,
|
|
||||||
backgroundColor = sEmptyBackgroundColor,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for i = 0, 15 do
|
for i = 0, 15 do
|
||||||
@@ -161,7 +157,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
|||||||
local function redrawLine(n)
|
local function redrawLine(n)
|
||||||
local tLine = tLines[n]
|
local tLine = tLines[n]
|
||||||
parent.setCursorPos(nX, nY + n - 1)
|
parent.setCursorPos(nX, nY + n - 1)
|
||||||
parent.blit(tLine.text, tLine.textColor, tLine.backgroundColor)
|
parent.blit(tLine[1], tLine[2], tLine[3])
|
||||||
end
|
end
|
||||||
|
|
||||||
local function redraw()
|
local function redraw()
|
||||||
@@ -184,9 +180,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
|||||||
-- Modify line
|
-- Modify line
|
||||||
local tLine = tLines[nCursorY]
|
local tLine = tLines[nCursorY]
|
||||||
if nStart == 1 and nEnd == nWidth then
|
if nStart == 1 and nEnd == nWidth then
|
||||||
tLine.text = sText
|
tLine[1] = sText
|
||||||
tLine.textColor = sTextColor
|
tLine[2] = sTextColor
|
||||||
tLine.backgroundColor = sBackgroundColor
|
tLine[3] = sBackgroundColor
|
||||||
else
|
else
|
||||||
local sClippedText, sClippedTextColor, sClippedBackgroundColor
|
local sClippedText, sClippedTextColor, sClippedBackgroundColor
|
||||||
if nStart < 1 then
|
if nStart < 1 then
|
||||||
@@ -206,9 +202,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
|||||||
sClippedBackgroundColor = sBackgroundColor
|
sClippedBackgroundColor = sBackgroundColor
|
||||||
end
|
end
|
||||||
|
|
||||||
local sOldText = tLine.text
|
local sOldText = tLine[1]
|
||||||
local sOldTextColor = tLine.textColor
|
local sOldTextColor = tLine[2]
|
||||||
local sOldBackgroundColor = tLine.backgroundColor
|
local sOldBackgroundColor = tLine[3]
|
||||||
local sNewText, sNewTextColor, sNewBackgroundColor
|
local sNewText, sNewTextColor, sNewBackgroundColor
|
||||||
if nStart > 1 then
|
if nStart > 1 then
|
||||||
local nOldEnd = nStart - 1
|
local nOldEnd = nStart - 1
|
||||||
@@ -227,9 +223,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
|||||||
sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth)
|
sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth)
|
||||||
end
|
end
|
||||||
|
|
||||||
tLine.text = sNewText
|
tLine[1] = sNewText
|
||||||
tLine.textColor = sNewTextColor
|
tLine[2] = sNewTextColor
|
||||||
tLine.backgroundColor = sNewBackgroundColor
|
tLine[3] = sNewBackgroundColor
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Redraw line
|
-- Redraw line
|
||||||
@@ -276,11 +272,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
|||||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
||||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||||
for y = 1, nHeight do
|
for y = 1, nHeight do
|
||||||
tLines[y] = {
|
local line = tLines[y]
|
||||||
text = sEmptyText,
|
line[1] = sEmptyText
|
||||||
textColor = sEmptyTextColor,
|
line[2] = sEmptyTextColor
|
||||||
backgroundColor = sEmptyBackgroundColor,
|
line[3] = sEmptyBackgroundColor
|
||||||
}
|
|
||||||
end
|
end
|
||||||
if bVisible then
|
if bVisible then
|
||||||
redraw()
|
redraw()
|
||||||
@@ -291,14 +286,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
|||||||
|
|
||||||
function window.clearLine()
|
function window.clearLine()
|
||||||
if nCursorY >= 1 and nCursorY <= nHeight then
|
if nCursorY >= 1 and nCursorY <= nHeight then
|
||||||
local sEmptyText = sEmptySpaceLine
|
local line = tLines[nCursorY]
|
||||||
local sEmptyTextColor = tEmptyColorLines[nTextColor]
|
line[1] = sEmptySpaceLine
|
||||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
line[2] = tEmptyColorLines[nTextColor]
|
||||||
tLines[nCursorY] = {
|
line[3] = tEmptyColorLines[nBackgroundColor]
|
||||||
text = sEmptyText,
|
|
||||||
textColor = sEmptyTextColor,
|
|
||||||
backgroundColor = sEmptyBackgroundColor,
|
|
||||||
}
|
|
||||||
if bVisible then
|
if bVisible then
|
||||||
redrawLine(nCursorY)
|
redrawLine(nCursorY)
|
||||||
updateCursorColor()
|
updateCursorColor()
|
||||||
@@ -427,11 +418,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
|||||||
if y >= 1 and y <= nHeight then
|
if y >= 1 and y <= nHeight then
|
||||||
tNewLines[newY] = tLines[y]
|
tNewLines[newY] = tLines[y]
|
||||||
else
|
else
|
||||||
tNewLines[newY] = {
|
tNewLines[newY] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
|
||||||
text = sEmptyText,
|
|
||||||
textColor = sEmptyTextColor,
|
|
||||||
backgroundColor = sEmptyBackgroundColor,
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
tLines = tNewLines
|
tLines = tNewLines
|
||||||
@@ -474,7 +461,8 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
|||||||
error("Line is out of range.", 2)
|
error("Line is out of range.", 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
return tLines[y].text, tLines[y].textColor, tLines[y].backgroundColor
|
local line = tLines[y]
|
||||||
|
return line[1], line[2], line[3]
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Other functions
|
-- Other functions
|
||||||
@@ -570,26 +558,22 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
|||||||
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
|
||||||
for y = 1, new_height do
|
for y = 1, new_height do
|
||||||
if y > nHeight then
|
if y > nHeight then
|
||||||
tNewLines[y] = {
|
tNewLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
|
||||||
text = sEmptyText,
|
|
||||||
textColor = sEmptyTextColor,
|
|
||||||
backgroundColor = sEmptyBackgroundColor,
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
local tOldLine = tLines[y]
|
local tOldLine = tLines[y]
|
||||||
if new_width == nWidth then
|
if new_width == nWidth then
|
||||||
tNewLines[y] = tOldLine
|
tNewLines[y] = tOldLine
|
||||||
elseif new_width < nWidth then
|
elseif new_width < nWidth then
|
||||||
tNewLines[y] = {
|
tNewLines[y] = {
|
||||||
text = string_sub(tOldLine.text, 1, new_width),
|
string_sub(tOldLine[1], 1, new_width),
|
||||||
textColor = string_sub(tOldLine.textColor, 1, new_width),
|
string_sub(tOldLine[2], 1, new_width),
|
||||||
backgroundColor = string_sub(tOldLine.backgroundColor, 1, new_width),
|
string_sub(tOldLine[3], 1, new_width),
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
tNewLines[y] = {
|
tNewLines[y] = {
|
||||||
text = tOldLine.text .. string_sub(sEmptyText, nWidth + 1, new_width),
|
tOldLine[1] .. string_sub(sEmptyText, nWidth + 1, new_width),
|
||||||
textColor = tOldLine.textColor .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
|
tOldLine[2] .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
|
||||||
backgroundColor = tOldLine.backgroundColor .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
|
tOldLine[3] .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -1,3 +1,24 @@
|
|||||||
|
# New features in CC: Tweaked 1.101.4
|
||||||
|
|
||||||
|
* Turtles can now right click items "into" certain blocks (cauldrons and hives by default, configurable with the `computercraft:turtle_can_use` block tag). (samuelWilliams99)
|
||||||
|
|
||||||
|
# New features in CC: Tweaked 1.101.3
|
||||||
|
|
||||||
|
* Improve syntax errors when missing commas in tables, and on trailing commas in parameter lists.
|
||||||
|
* `speaker` program now reports an error on common unsupported audio formats.
|
||||||
|
* Small optimisations to the `window` API.
|
||||||
|
|
||||||
|
Several bug fixes:
|
||||||
|
* Fix the REPL syntax reporting crashing on valid parses.
|
||||||
|
* Ignore metatables in `textutils.serialize`.
|
||||||
|
* Fix `gps.locate` returning `nan` when receiving a duplicate location (Wojbie).
|
||||||
|
* Ignore metatables in `textutils.serialize`.
|
||||||
|
* Fix crash when turtles are exploded by a null explosion.
|
||||||
|
* Lua REPL no longer accepts `)(` as a valid expression.
|
||||||
|
* Fix several inconsistencies with `require`/`package.path` in the Lua REPL (Wojbie).
|
||||||
|
* Fix private several IP address ranges not being blocked by the `$private` rule.
|
||||||
|
* Improve permission checks in the `/computercraft` command.
|
||||||
|
|
||||||
# New features in CC: Tweaked 1.101.2
|
# New features in CC: Tweaked 1.101.2
|
||||||
|
|
||||||
* Error messages in `edit` are now displayed in red on advanced computers.
|
* Error messages in `edit` are now displayed in red on advanced computers.
|
||||||
|
@@ -1,16 +1,5 @@
|
|||||||
New features in CC: Tweaked 1.101.2
|
New features in CC: Tweaked 1.101.4
|
||||||
|
|
||||||
* Error messages in `edit` are now displayed in red on advanced computers.
|
* Turtles can now right click items "into" certain blocks (cauldrons and hives by default, configurable with the `computercraft:turtle_can_use` block tag). (samuelWilliams99)
|
||||||
* Improvements to the display of errors in the shell and REPL.
|
|
||||||
|
|
||||||
Several bug fixes:
|
|
||||||
* Fix `import.lua` failing to upload a file.
|
|
||||||
* Fix several issues with sparse Lua tables (Shiranuit).
|
|
||||||
* Computer upgrades now accept normal computers, rather than uselessly allowing you to upgrade an advanced computer to an advanced computer!
|
|
||||||
* Correctly clamp speaker volume.
|
|
||||||
* Fix rednet queueing the wrong message when sending a message to the current computer.
|
|
||||||
* Fix the Lua VM crashing when a `__len` metamethod yields.
|
|
||||||
* Trim spaces from filesystem paths.
|
|
||||||
* Correctly format 12AM/PM with `%I`.
|
|
||||||
|
|
||||||
Type "help changelog" to see the full version history.
|
Type "help changelog" to see the full version history.
|
||||||
|
@@ -364,6 +364,48 @@ function errors.table_key_equals(start_pos, end_pos)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[- There is a trailing comma in this list of function arguments.
|
||||||
|
|
||||||
|
@tparam number token The token id.
|
||||||
|
@tparam number token_start The start position of the token.
|
||||||
|
@tparam number token_end The end position of the token.
|
||||||
|
@tparam number prev The start position of the previous entry.
|
||||||
|
@treturn table The resulting parse error.
|
||||||
|
]]
|
||||||
|
function errors.missing_table_comma(token, token_start, token_end, prev)
|
||||||
|
expect(1, token, "number")
|
||||||
|
expect(2, token_start, "number")
|
||||||
|
expect(3, token_end, "number")
|
||||||
|
expect(4, prev, "number")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"Unexpected " .. token_names[token] .. " in table.",
|
||||||
|
annotate(token_start, token_end),
|
||||||
|
annotate(prev + 1, prev + 1, "Are you missing a comma here?"),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[- There is a trailing comma in this list of function arguments.
|
||||||
|
|
||||||
|
@tparam number comma_start The start position of the `,` token.
|
||||||
|
@tparam number comma_end The end position of the `,` token.
|
||||||
|
@tparam number paren_start The start position of the `)` token.
|
||||||
|
@tparam number paren_end The end position of the `)` token.
|
||||||
|
@treturn table The resulting parse error.
|
||||||
|
]]
|
||||||
|
function errors.trailing_call_comma(comma_start, comma_end, paren_start, paren_end)
|
||||||
|
expect(1, comma_start, "number")
|
||||||
|
expect(2, comma_end, "number")
|
||||||
|
expect(3, paren_start, "number")
|
||||||
|
expect(4, paren_end, "number")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"Unexpected " .. code(")") .. " in function call.",
|
||||||
|
annotate(paren_start, paren_end),
|
||||||
|
annotate(comma_start, comma_end, "Tip: Try removing this " .. code(",") .. "."),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Statement parsing errors
|
-- Statement parsing errors
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
@@ -17,6 +17,8 @@ local error_printer = require "cc.internal.error_printer"
|
|||||||
local error_sentinel = {}
|
local error_sentinel = {}
|
||||||
|
|
||||||
local function make_context(input)
|
local function make_context(input)
|
||||||
|
expect(1, input, "string")
|
||||||
|
|
||||||
local context = {}
|
local context = {}
|
||||||
|
|
||||||
local lines = { 1 }
|
local lines = { 1 }
|
||||||
@@ -69,8 +71,9 @@ local function parse(input, start_symbol)
|
|||||||
expect(2, start_symbol, "number")
|
expect(2, start_symbol, "number")
|
||||||
|
|
||||||
local context = make_context(input)
|
local context = make_context(input)
|
||||||
function context.report(msg)
|
function context.report(msg, ...)
|
||||||
expect(1, msg, "table")
|
expect(1, msg, "table", "function")
|
||||||
|
if type(msg) == "function" then msg = msg(...) end
|
||||||
error_printer(context, msg)
|
error_printer(context, msg)
|
||||||
error(error_sentinel)
|
error(error_sentinel)
|
||||||
end
|
end
|
||||||
@@ -106,8 +109,9 @@ local function parse_repl(input)
|
|||||||
local context = make_context(input)
|
local context = make_context(input)
|
||||||
|
|
||||||
local last_error = nil
|
local last_error = nil
|
||||||
function context.report(msg)
|
function context.report(msg, ...)
|
||||||
expect(1, msg, "table")
|
expect(1, msg, "table", "function")
|
||||||
|
if type(msg) == "function" then msg = msg(...) end
|
||||||
last_error = msg
|
last_error = msg
|
||||||
error(error_sentinel)
|
error(error_sentinel)
|
||||||
end
|
end
|
||||||
@@ -120,22 +124,35 @@ local function parse_repl(input)
|
|||||||
assert(coroutine.resume(parsers[i], context, coroutine.yield, start_code))
|
assert(coroutine.resume(parsers[i], context, coroutine.yield, start_code))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Run all parsers together in parallel, feeding them one token at a time.
|
||||||
|
-- Once all parsers have failed, report the last failure (corresponding to
|
||||||
|
-- the longest parse).
|
||||||
local ok, err = pcall(function()
|
local ok, err = pcall(function()
|
||||||
local parsers_n = #parsers
|
local parsers_n = #parsers
|
||||||
while true do
|
while true do
|
||||||
local token, start, finish = lexer()
|
local token, start, finish = lexer()
|
||||||
|
|
||||||
local stop = true
|
local all_failed = true
|
||||||
for i = 1, parsers_n do
|
for i = 1, parsers_n do
|
||||||
local parser = parsers[i]
|
local parser = parsers[i]
|
||||||
if coroutine.status(parser) ~= "dead" then
|
if parser then
|
||||||
stop = false
|
|
||||||
local ok, err = coroutine.resume(parser, token, start, finish)
|
local ok, err = coroutine.resume(parser, token, start, finish)
|
||||||
if not ok and err ~= error_sentinel then error(err, 0) end
|
if ok then
|
||||||
|
-- This parser accepted our input, succeed immediately.
|
||||||
|
if coroutine.status(parser) == "dead" then return end
|
||||||
|
|
||||||
|
all_failed = false -- Otherwise continue parsing.
|
||||||
|
elseif err ~= error_sentinel then
|
||||||
|
-- An internal error occurred: propagate it.
|
||||||
|
error(err, 0)
|
||||||
|
else
|
||||||
|
-- The parser failed, stub it out so we don't try to continue using it.
|
||||||
|
parsers[i] = false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if stop then error(error_sentinel) end
|
if all_failed then error(error_sentinel) end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@@ -92,7 +92,7 @@ local function lex_number(context, str, start)
|
|||||||
local contents = sub(str, start, pos - 1)
|
local contents = sub(str, start, pos - 1)
|
||||||
if not tonumber(contents) then
|
if not tonumber(contents) then
|
||||||
-- TODO: Separate error for "2..3"?
|
-- TODO: Separate error for "2..3"?
|
||||||
context.report(errors.malformed_number(start, pos - 1))
|
context.report(errors.malformed_number, start, pos - 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
return tokens.NUMBER, pos - 1
|
return tokens.NUMBER, pos - 1
|
||||||
@@ -114,14 +114,14 @@ local function lex_string(context, str, start_pos, quote)
|
|||||||
return tokens.STRING, pos
|
return tokens.STRING, pos
|
||||||
elseif c == "\n" or c == "\r" or c == "" then
|
elseif c == "\n" or c == "\r" or c == "" then
|
||||||
-- We don't call newline here, as that's done for the next token.
|
-- We don't call newline here, as that's done for the next token.
|
||||||
context.report(errors.unfinished_string(start_pos, pos, quote))
|
context.report(errors.unfinished_string, start_pos, pos, quote)
|
||||||
return tokens.STRING, pos - 1
|
return tokens.STRING, pos - 1
|
||||||
elseif c == "\\" then
|
elseif c == "\\" then
|
||||||
c = sub(str, pos + 1, pos + 1)
|
c = sub(str, pos + 1, pos + 1)
|
||||||
if c == "\n" or c == "\r" then
|
if c == "\n" or c == "\r" then
|
||||||
pos = newline(context, str, pos + 1, c)
|
pos = newline(context, str, pos + 1, c)
|
||||||
elseif c == "" then
|
elseif c == "" then
|
||||||
context.report(errors.unfinished_string_escape(start_pos, pos, quote))
|
context.report(errors.unfinished_string_escape, start_pos, pos, quote)
|
||||||
return tokens.STRING, pos
|
return tokens.STRING, pos
|
||||||
elseif c == "z" then
|
elseif c == "z" then
|
||||||
pos = pos + 2
|
pos = pos + 2
|
||||||
@@ -129,7 +129,7 @@ local function lex_string(context, str, start_pos, quote)
|
|||||||
local next_pos, _, c = find(str, "([%S\r\n])", pos)
|
local next_pos, _, c = find(str, "([%S\r\n])", pos)
|
||||||
|
|
||||||
if not next_pos then
|
if not next_pos then
|
||||||
context.report(errors.unfinished_string(start_pos, #str, quote))
|
context.report(errors.unfinished_string, start_pos, #str, quote)
|
||||||
return tokens.STRING, #str
|
return tokens.STRING, #str
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ local function lex_long_str(context, str, start, len)
|
|||||||
elseif c == "[" then
|
elseif c == "[" then
|
||||||
local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "[")
|
local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "[")
|
||||||
if ok and boundary_pos - pos == len and len == 1 then
|
if ok and boundary_pos - pos == len and len == 1 then
|
||||||
context.report(errors.nested_long_str(pos, boundary_pos))
|
context.report(errors.nested_long_str, pos, boundary_pos)
|
||||||
end
|
end
|
||||||
|
|
||||||
pos = boundary_pos
|
pos = boundary_pos
|
||||||
@@ -234,12 +234,12 @@ local function lex_token(context, str, pos)
|
|||||||
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - pos)
|
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - pos)
|
||||||
if end_pos then return tokens.STRING, end_pos end
|
if end_pos then return tokens.STRING, end_pos end
|
||||||
|
|
||||||
context.report(errors.unfinished_long_string(pos, boundary_pos, boundary_pos - pos))
|
context.report(errors.unfinished_long_string, pos, boundary_pos, boundary_pos - pos)
|
||||||
return tokens.ERROR, #str
|
return tokens.ERROR, #str
|
||||||
elseif pos + 1 == boundary_pos then -- Just a "["
|
elseif pos + 1 == boundary_pos then -- Just a "["
|
||||||
return tokens.OSQUARE, pos
|
return tokens.OSQUARE, pos
|
||||||
else -- Malformed long string, for instance "[="
|
else -- Malformed long string, for instance "[="
|
||||||
context.report(errors.malformed_long_string(pos, boundary_pos, boundary_pos - pos))
|
context.report(errors.malformed_long_string, pos, boundary_pos, boundary_pos - pos)
|
||||||
return tokens.ERROR, boundary_pos
|
return tokens.ERROR, boundary_pos
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -256,7 +256,7 @@ local function lex_token(context, str, pos)
|
|||||||
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - comment_pos)
|
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - comment_pos)
|
||||||
if end_pos then return tokens.COMMENT, end_pos end
|
if end_pos then return tokens.COMMENT, end_pos end
|
||||||
|
|
||||||
context.report(errors.unfinished_long_comment(pos, boundary_pos, boundary_pos - comment_pos))
|
context.report(errors.unfinished_long_comment, pos, boundary_pos, boundary_pos - comment_pos)
|
||||||
return tokens.ERROR, #str
|
return tokens.ERROR, #str
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -313,18 +313,18 @@ local function lex_token(context, str, pos)
|
|||||||
if end_pos - pos <= 3 then
|
if end_pos - pos <= 3 then
|
||||||
local contents = sub(str, pos, end_pos)
|
local contents = sub(str, pos, end_pos)
|
||||||
if contents == "&&" then
|
if contents == "&&" then
|
||||||
context.report(errors.wrong_and(pos, end_pos))
|
context.report(errors.wrong_and, pos, end_pos)
|
||||||
return tokens.AND, end_pos
|
return tokens.AND, end_pos
|
||||||
elseif contents == "||" then
|
elseif contents == "||" then
|
||||||
context.report(errors.wrong_or(pos, end_pos))
|
context.report(errors.wrong_or, pos, end_pos)
|
||||||
return tokens.OR, end_pos
|
return tokens.OR, end_pos
|
||||||
elseif contents == "!=" or contents == "<>" then
|
elseif contents == "!=" or contents == "<>" then
|
||||||
context.report(errors.wrong_ne(pos, end_pos))
|
context.report(errors.wrong_ne, pos, end_pos)
|
||||||
return tokens.NE, end_pos
|
return tokens.NE, end_pos
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context.report(errors.unexpected_character(pos))
|
context.report(errors.unexpected_character, pos)
|
||||||
return tokens.ERROR, end_pos
|
return tokens.ERROR, end_pos
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -107,7 +107,9 @@ local function set_status(text, ok)
|
|||||||
status_text = text
|
status_text = text
|
||||||
end
|
end
|
||||||
|
|
||||||
if not bReadOnly and fs.getFreeSpace(sPath) < 1024 then
|
if bReadOnly then
|
||||||
|
set_status("File is read only", false)
|
||||||
|
elseif fs.getFreeSpace(sPath) < 1024 then
|
||||||
set_status("Disk is low on space", false)
|
set_status("Disk is low on space", false)
|
||||||
else
|
else
|
||||||
local message
|
local message
|
||||||
|
@@ -26,6 +26,12 @@ local function pcm_decoder(chunk)
|
|||||||
return buffer
|
return buffer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function report_invalid_format(format)
|
||||||
|
printError(("speaker cannot play %s files."):format(format))
|
||||||
|
local pp = require "cc.pretty"
|
||||||
|
pp.print("Run '" .. pp.text("help speaker", colours.lightGrey) .. "' for information on supported formats.")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
local cmd = ...
|
local cmd = ...
|
||||||
if cmd == "stop" then
|
if cmd == "stop" then
|
||||||
@@ -93,6 +99,11 @@ elseif cmd == "play" then
|
|||||||
|
|
||||||
handle.read(4)
|
handle.read(4)
|
||||||
start = nil
|
start = nil
|
||||||
|
-- Detect several other common audio files.
|
||||||
|
elseif start == "OggS" then return report_invalid_format("Ogg")
|
||||||
|
elseif start == "fLaC" then return report_invalid_format("FLAC")
|
||||||
|
elseif start:sub(1, 3) == "ID3" then return report_invalid_format("MP3")
|
||||||
|
elseif start == "<!DO" --[[<!DOCTYPE]] then return report_invalid_format("HTML")
|
||||||
end
|
end
|
||||||
|
|
||||||
print("Playing " .. file)
|
print("Playing " .. file)
|
||||||
|
@@ -21,21 +21,13 @@ local tEnv = {
|
|||||||
}
|
}
|
||||||
setmetatable(tEnv, { __index = _ENV })
|
setmetatable(tEnv, { __index = _ENV })
|
||||||
|
|
||||||
-- Replace our package.path, so that it loads from the current directory, rather
|
-- Replace our require with new instance that loads from the current directory
|
||||||
-- than from /rom/programs. This makes it a little more friendly to use and
|
-- rather than from /rom/programs. This makes it more friendly to use and closer
|
||||||
-- closer to what you'd expect.
|
-- to what you'd expect.
|
||||||
do
|
do
|
||||||
|
local make_package = require "cc.require".make
|
||||||
local dir = shell.dir()
|
local dir = shell.dir()
|
||||||
if dir:sub(1, 1) ~= "/" then dir = "/" .. dir end
|
_ENV.require, _ENV.package = make_package(_ENV, dir)
|
||||||
if dir:sub(-1) ~= "/" then dir = dir .. "/" end
|
|
||||||
|
|
||||||
local strip_path = "?;?.lua;?/init.lua;"
|
|
||||||
local path = package.path
|
|
||||||
if path:sub(1, #strip_path) == strip_path then
|
|
||||||
path = path:sub(#strip_path + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
package.path = dir .. "?;" .. dir .. "?.lua;" .. dir .. "?/init.lua;" .. path
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if term.isColour() then
|
if term.isColour() then
|
||||||
@@ -78,18 +70,13 @@ while running do
|
|||||||
|
|
||||||
local name, offset = "=lua[" .. chunk_idx .. "]", 0
|
local name, offset = "=lua[" .. chunk_idx .. "]", 0
|
||||||
|
|
||||||
local force_print = 0
|
|
||||||
local func, err = load(input, name, "t", tEnv)
|
local func, err = load(input, name, "t", tEnv)
|
||||||
|
if load("return " .. input) then
|
||||||
local expr_func = load("return _echo(" .. input .. ");", name, "t", tEnv)
|
-- We wrap the expression with a call to _echo(...), which prevents tail
|
||||||
if not func then
|
-- calls (and thus confusing errors). Note we check this is a valid
|
||||||
if expr_func then
|
-- expression separately, to avoid accepting inputs like `)--` (which are
|
||||||
func = expr_func
|
-- parsed as `_echo()--)`.
|
||||||
offset = 13
|
func = load("return _echo(" .. input .. "\n)", name, "t", tEnv)
|
||||||
force_print = 1
|
|
||||||
end
|
|
||||||
elseif expr_func then
|
|
||||||
func = expr_func
|
|
||||||
offset = 13
|
offset = 13
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -99,9 +86,8 @@ while running do
|
|||||||
|
|
||||||
local results = table.pack(exception.try(func))
|
local results = table.pack(exception.try(func))
|
||||||
if results[1] then
|
if results[1] then
|
||||||
local n = 1
|
for i = 2, results.n do
|
||||||
while n < results.n or n <= force_print do
|
local value = results[i]
|
||||||
local value = results[n + 1]
|
|
||||||
local ok, serialised = pcall(pretty.pretty, value, {
|
local ok, serialised = pcall(pretty.pretty, value, {
|
||||||
function_args = settings.get("lua.function_args"),
|
function_args = settings.get("lua.function_args"),
|
||||||
function_source = settings.get("lua.function_source"),
|
function_source = settings.get("lua.function_source"),
|
||||||
@@ -111,7 +97,6 @@ while running do
|
|||||||
else
|
else
|
||||||
print(tostring(value))
|
print(tostring(value))
|
||||||
end
|
end
|
||||||
n = n + 1
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
printError(results[2])
|
printError(results[2])
|
||||||
|
@@ -34,7 +34,14 @@ public class AddressRuleTest
|
|||||||
@ValueSource( strings = {
|
@ValueSource( strings = {
|
||||||
"0.0.0.0", "[::]",
|
"0.0.0.0", "[::]",
|
||||||
"localhost", "127.0.0.1.nip.io", "127.0.0.1", "[::1]",
|
"localhost", "127.0.0.1.nip.io", "127.0.0.1", "[::1]",
|
||||||
"172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1"
|
"172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1",
|
||||||
|
// Multicast
|
||||||
|
"224.0.0.1", "ff02::1",
|
||||||
|
// Cloud metadata providers
|
||||||
|
"100.100.100.200", // Alibaba
|
||||||
|
"192.0.0.192", // Oracle
|
||||||
|
"fd00:ec2::254", // AWS
|
||||||
|
"169.254.169.254" // AWS, Digital Ocean, GCP, etc..
|
||||||
} )
|
} )
|
||||||
public void blocksLocalDomains( String domain )
|
public void blocksLocalDomains( String domain )
|
||||||
{
|
{
|
||||||
|
@@ -49,4 +49,18 @@ describe("cc.internal.syntax", function()
|
|||||||
describe_golden("the lexer", "lexer_spec.md", true)
|
describe_golden("the lexer", "lexer_spec.md", true)
|
||||||
describe_golden("the parser", "parser_spec.md", false)
|
describe_golden("the parser", "parser_spec.md", false)
|
||||||
describe_golden("the parser (all states)", "parser_exhaustive_spec.md", false)
|
describe_golden("the parser (all states)", "parser_exhaustive_spec.md", false)
|
||||||
|
|
||||||
|
describe("the REPL input parser", function()
|
||||||
|
it("returns true when accepted by both parsers", function()
|
||||||
|
helpers.with_window(50, 10, function()
|
||||||
|
expect(syntax.parse_repl("print(x)")):eq(true)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("returns true when accepted by one parser", function()
|
||||||
|
helpers.with_window(50, 10, function()
|
||||||
|
expect(syntax.parse_repl("x")):eq(true)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
@@ -45,8 +45,9 @@ local function capture_parser(input, print_tokens, start)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local context = make_context(input)
|
local context = make_context(input)
|
||||||
function context.report(message)
|
function context.report(message, ...)
|
||||||
expect(3, message, "table")
|
expect(3, message, "table", "function")
|
||||||
|
if type(message) == "function" then message = message(...) end
|
||||||
|
|
||||||
for _, msg in ipairs(message) do
|
for _, msg in ipairs(message) do
|
||||||
if type(msg) == "table" and msg.tag == "annotate" then
|
if type(msg) == "table" and msg.tag == "annotate" then
|
||||||
|
@@ -151,10 +151,8 @@ class Turtle_Test {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks turtles can be cleaned in cauldrons.
|
* Checks turtles can be cleaned in cauldrons.
|
||||||
*
|
|
||||||
* Currently not required as turtles can no longer right-click cauldrons.
|
|
||||||
*/
|
*/
|
||||||
@GameTest(required = false)
|
@GameTest
|
||||||
fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence {
|
fun Cleaned_with_cauldrons(helper: GameTestHelper) = helper.sequence {
|
||||||
thenOnComputer {
|
thenOnComputer {
|
||||||
val details = getTurtleItemDetail(1, true)
|
val details = getTurtleItemDetail(1, true)
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
DataVersion: 2730,
|
DataVersion: 3218,
|
||||||
size: [3, 3, 3],
|
size: [3, 3, 3],
|
||||||
data: [
|
data: [
|
||||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||||
{pos: [1, 1, 0], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:turtle_normal", tag: {Color: 13388876, ComputerId: 0, display: {Name: '{"text":"Clean turtle"}'}}}], Label: "turtle_test.cleaned_with_cauldrons", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
|
{pos: [1, 1, 0], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [{Count: 1b, Slot: 0b, id: "computercraft:turtle_normal", tag: {Color: 13388876, ComputerId: 0, display: {Name: '{"text":"Clean turtle"}'}}}], Label: "turtle_test.cleaned_with_cauldrons", On: 1b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_normal"}},
|
||||||
{pos: [1, 1, 1], state: "minecraft:cauldron{level:3}"},
|
{pos: [1, 1, 1], state: "minecraft:water_cauldron{level:3}"},
|
||||||
{pos: [1, 1, 2], state: "minecraft:air"},
|
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||||
@@ -33,8 +33,8 @@
|
|||||||
entities: [],
|
entities: [],
|
||||||
palette: [
|
palette: [
|
||||||
"minecraft:polished_andesite",
|
"minecraft:polished_andesite",
|
||||||
"computercraft:turtle_normal{facing:south,waterlogged:false}",
|
|
||||||
"minecraft:air",
|
"minecraft:air",
|
||||||
"minecraft:cauldron{level:3}"
|
"minecraft:water_cauldron{level:3}",
|
||||||
|
"computercraft:turtle_normal{facing:south,waterlogged:false}"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user