1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-17 15:07:38 +00:00

Compare commits

...

16 Commits

Author SHA1 Message Date
Jonathan Coates
f4de575d35 Bump CC:T to 1.101.4 2024-01-03 18:39:32 +00:00
Samuel Williams
c1954b4071 Backport of TURTLE_CAN_USE (#1656) 2023-12-12 14:38:35 +00:00
Jonathan Coates
f9bb1b4979 Merge branch 'mc-1.18.x' into mc-1.19.2 2023-07-07 00:06:10 +01:00
Jonathan Coates
edf372a695 Merge branch 'mc-1.16.x' into mc-1.18.x 2023-07-07 00:02:42 +01:00
Jonathan Coates
aa89e51639 Bump CC:T to 1.101.3
0/10, would not recommend.
2023-07-06 23:54:44 +01:00
Jonathan Coates
7436447a6e Several command permission fixes
- Attach permission checks to the first argument (so the literal
   command name) rather than the last argument. This fixes commands
   showing up when they shouldn't.

 - HelpingArgumentBuilder now inherits permissions of its leaf nodes.
   This only really impacts the "track" subcommand.

 - Don't autocomplete the computer selector for the "queue" subcommand.
   As everyone has permission for this command, it's possible to find
   all computer ids and labels in the world.

   I'm in mixed minds about this, but don't think this is an exploit -
   computer ids/labels are sent to in-range players so shouldn't be
   considered secret - but worth patching none-the-less.
2023-07-06 23:41:23 +01:00
Jonathan Coates
f629831b12 Tighten up the $private HTTP rule
- Block multicast and the fd00::/8 address ranges.
 - Block several cloud metadata providers which sit outside the
   standard address ranges.
2023-07-06 23:27:17 +01:00
Jonathan Coates
f7fdb6e729 Backport a couple of ROM commits
- Improve REPL's handling of expressions
   (655d5aeca8)

 - Some tiny optimisations to the window API
   (4accda6b8e)

 - Be lazy in reporting errors in the lexer
   (54ab98473f)

 - Update lua.lua require logic.
   (88f0c44152)
2023-07-06 23:15:57 +01:00
Jonathan Coates
db2616d1c0 Don't (metaphorically) explode on null explosions
Closes #1423.
2023-05-03 23:38:12 +01:00
Jonathan Coates
c0f982dc97 Use correct model for the turtle modem
Introduced in 0c3de1087e, so should only
affect 1.16.5 and 1.18.2.

Fixes #1426
2023-05-03 23:34:22 +01:00
Jonathan Coates
2a9f35de5e Backport several ROM/Lua commits
- Fix GPS returning nan on duplicate positions.
 - Distinguish between all parsers passing and failing.
 - Improve several comma related parse errors.
 - Ignore metatables in textutils.serialize.
 - Detect common audio containers in "speaker".

Co-authored-by: Wojbie <Wojbie@gmail.com>
2023-05-03 23:26:44 +01:00
Jonathan Coates
0fce3212a3 Distinguish between all parsers passing and failing
Given an input like f(x), which is both a valid statement and
expression, both parsers would accept the whole input. However, this was
treated the same as both parsers rejecting the input, resulting in a
crash when trying to print the error.

We now return immediately when any parser accepts the input.

Fixes #1354
2023-03-11 22:45:50 +00:00
Jonathan Coates
652f954886 Merge pull request #1352 from MCJack123/lets-encrypt
Add certificate workaround for Let's Encrypt on 1.16
2023-03-04 11:11:33 +00:00
JackMacWindows
6f65bad9af Fixed CI 2023-03-04 02:02:05 -05:00
JackMacWindows
e4dd4dbef0 Added version check to certificate addition 2023-03-02 18:30:54 -05:00
MCJack123
e1dffaa334 Add certificate workaround for Let's Encrypt 2023-02-28 20:52:14 -05:00
29 changed files with 396 additions and 179 deletions

View File

@@ -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

View File

@@ -0,0 +1 @@
{"values": ["#minecraft:cauldrons", "#minecraft:beehives", "minecraft:composter"]}

View File

@@ -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 ) );

View File

@@ -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;
} }
} }

View File

@@ -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(),

View File

@@ -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" );

View File

@@ -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() );
} }
} }

View File

@@ -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();
} }
} }

View File

@@ -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() )

View File

@@ -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;

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View 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])

View File

@@ -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 )
{ {

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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}"
] ]
} }