mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-17 15:07:38 +00:00
Compare commits
37 Commits
v1.15.2-1.
...
v1.15.2-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
334ca65482 | ||
![]() |
8472112fc1 | ||
![]() |
84036d97d9 | ||
![]() |
0832974725 | ||
![]() |
6cee4efcd3 | ||
![]() |
6f868849ab | ||
![]() |
275ca58a82 | ||
![]() |
87393e8aef | ||
![]() |
86bf57e3cd | ||
![]() |
748ebbe66b | ||
![]() |
59de21eae2 | ||
![]() |
50473afea8 | ||
![]() |
37f925de0a | ||
![]() |
cefde3f003 | ||
![]() |
ae6124d1f4 | ||
![]() |
7e121ff72f | ||
![]() |
5155e18de2 | ||
![]() |
7365741088 | ||
![]() |
d5368d0719 | ||
![]() |
04509cefec | ||
![]() |
74b9f5dcb0 | ||
![]() |
183b342071 | ||
![]() |
0bb5515055 | ||
![]() |
9acfc0316f | ||
![]() |
29fb0baa09 | ||
![]() |
d5de39ebd4 | ||
![]() |
0faf76e4bd | ||
![]() |
e8e2ed9fe5 | ||
![]() |
9f72448ecd | ||
![]() |
3da3f16deb | ||
![]() |
0e2ce3c634 | ||
![]() |
fe00e00537 | ||
![]() |
cd879b067f | ||
![]() |
053cb1b53c | ||
![]() |
ac7979fb46 | ||
![]() |
d51851e763 | ||
![]() |
fb70a1a998 |
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -12,4 +12,5 @@ labels: bug
|
||||
## Useful information to include:
|
||||
- Minecraft version
|
||||
- CC: Tweaked version
|
||||
- Logs: These will be located in the `logs/` directory of your Minecraft instance. Please upload them as a gist or directly into this editor.
|
||||
- Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed.
|
||||
|
@@ -10,7 +10,7 @@ do use the issue templates - they provide a useful hint on what information to p
|
||||
|
||||
## Developing
|
||||
In order to develop CC: Tweaked, you'll need to download the source code and then run it. This is a pretty simple
|
||||
process.
|
||||
process. When building on Windows, Use `gradlew.bat` instead of `./gradlew`.
|
||||
|
||||
- **Clone the repository:** `git clone https://github.com/SquidDev-CC/CC-Tweaked.git && cd CC-Tweaked`
|
||||
- **Setup Forge:** `./gradlew build`
|
||||
|
10
README.md
10
README.md
@@ -1,4 +1,4 @@
|
||||
# 
|
||||
# 
|
||||
[](https://github.com/SquidDev-CC/CC-Tweaked/actions "Current build status") [](https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked on CurseForge")
|
||||
|
||||
CC: Tweaked is a fork of [ComputerCraft](https://github.com/dan200/ComputerCraft), adding programmable computers,
|
||||
@@ -50,12 +50,12 @@ I'd generally recommend you don't contact me directly (email, DM, etc...) unless
|
||||
report exploits). You'll get a far quicker response if you ask the whole community!
|
||||
|
||||
## Using
|
||||
If you want to depend on CC: Tweaked, we have a maven repo. However, you should be wary that some functionality is only
|
||||
exposed by CC:T's API and not vanilla ComputerCraft. If you wish to support all variations of ComputerCraft, I recommend
|
||||
using [cc.crzd.me's maven](https://cc.crzd.me/maven/) instead.
|
||||
CC: Tweaked is hosted on my maven repo, and so is relatively simple to depend on. You may wish to add a soft (or hard)
|
||||
dependency in your `mods.toml` file, with the appropriate version bounds, to ensure that API functionality you depend
|
||||
on is present.
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
repositories {
|
||||
maven { url 'https://squiddev.cc/maven/' }
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# Mod properties
|
||||
mod_version=1.90.0
|
||||
mod_version=1.93.0
|
||||
|
||||
# Minecraft properties (update mods.toml when changing)
|
||||
mc_version=1.15.2
|
||||
|
@@ -101,6 +101,9 @@
|
||||
(linters -doc:unresolved-reference))
|
||||
|
||||
(at /src/test/resources/test-rom
|
||||
; We should still be able to test deprecated members.
|
||||
(linters -var:deprecated)
|
||||
|
||||
(lint
|
||||
(globals
|
||||
:max sleep write
|
||||
|
@@ -8,6 +8,7 @@ package dan200.computercraft;
|
||||
import dan200.computercraft.api.turtle.event.TurtleAction;
|
||||
import dan200.computercraft.core.apis.http.options.Action;
|
||||
import dan200.computercraft.core.apis.http.options.AddressRule;
|
||||
import dan200.computercraft.core.asm.GenericSource;
|
||||
import dan200.computercraft.shared.Config;
|
||||
import dan200.computercraft.shared.Registry;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
|
||||
@@ -16,6 +17,7 @@ import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
|
||||
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
|
||||
import dan200.computercraft.shared.turtle.upgrades.*;
|
||||
import dan200.computercraft.shared.util.ServiceUtil;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -37,6 +39,7 @@ public final class ComputerCraft
|
||||
public static final String[] DEFAULT_HTTP_ALLOW = new String[] { "*" };
|
||||
public static final String[] DEFAULT_HTTP_DENY = new String[] {
|
||||
"127.0.0.0/8",
|
||||
"0.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
@@ -60,10 +63,10 @@ public final class ComputerCraft
|
||||
public static boolean httpWebsocketEnabled = true;
|
||||
public static List<AddressRule> httpRules = Collections.unmodifiableList( Stream.concat(
|
||||
Stream.of( DEFAULT_HTTP_DENY )
|
||||
.map( x -> AddressRule.parse( x, Action.DENY.toPartial() ) )
|
||||
.map( x -> AddressRule.parse( x, null, Action.DENY.toPartial() ) )
|
||||
.filter( Objects::nonNull ),
|
||||
Stream.of( DEFAULT_HTTP_ALLOW )
|
||||
.map( x -> AddressRule.parse( x, Action.ALLOW.toPartial() ) )
|
||||
.map( x -> AddressRule.parse( x, null, Action.ALLOW.toPartial() ) )
|
||||
.filter( Objects::nonNull )
|
||||
).collect( Collectors.toList() ) );
|
||||
|
||||
@@ -133,6 +136,6 @@ public final class ComputerCraft
|
||||
{
|
||||
Config.setup();
|
||||
Registry.setup();
|
||||
GenericSource.setup( () -> ServiceUtil.loadServicesForge( GenericSource.class ) );
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -479,6 +479,7 @@ public class FSAPI implements ILuaAPI
|
||||
BasicFileAttributes attributes = fileSystem.getAttributes( path );
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
|
||||
result.put( "modified", getFileTime( attributes.lastModifiedTime() ) );
|
||||
result.put( "created", getFileTime( attributes.creationTime() ) );
|
||||
result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
|
||||
result.put( "isDir", attributes.isDirectory() );
|
||||
|
@@ -24,14 +24,14 @@ public class CheckUrl extends Resource<CheckUrl>
|
||||
|
||||
private final IAPIEnvironment environment;
|
||||
private final String address;
|
||||
private final String host;
|
||||
private final URI uri;
|
||||
|
||||
public CheckUrl( ResourceGroup<CheckUrl> limiter, IAPIEnvironment environment, String address, URI uri )
|
||||
{
|
||||
super( limiter );
|
||||
this.environment = environment;
|
||||
this.address = address;
|
||||
host = uri.getHost();
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public void run()
|
||||
@@ -47,8 +47,9 @@ public class CheckUrl extends Resource<CheckUrl>
|
||||
|
||||
try
|
||||
{
|
||||
InetSocketAddress netAddress = NetworkUtils.getAddress( host, 80, false );
|
||||
NetworkUtils.getOptions( host, netAddress );
|
||||
boolean ssl = uri.getScheme().equalsIgnoreCase( "https" );
|
||||
InetSocketAddress netAddress = NetworkUtils.getAddress( uri, ssl );
|
||||
NetworkUtils.getOptions( uri.getHost(), netAddress );
|
||||
|
||||
if( tryClose() ) environment.queueEvent( EVENT, address, true );
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import io.netty.handler.ssl.SslContextBuilder;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.security.KeyStore;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
@@ -99,6 +100,21 @@ public final class NetworkUtils
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link InetSocketAddress} from a {@link java.net.URI}.
|
||||
*
|
||||
* Note, this may require a DNS lookup, and so should not be executed on the main CC thread.
|
||||
*
|
||||
* @param uri The URI to fetch.
|
||||
* @param ssl Whether to connect with SSL. This is used to find the default port if not otherwise specified.
|
||||
* @return The resolved address.
|
||||
* @throws HTTPRequestException If the host is not malformed.
|
||||
*/
|
||||
public static InetSocketAddress getAddress( URI uri, boolean ssl ) throws HTTPRequestException
|
||||
{
|
||||
return getAddress( uri.getHost(), uri.getPort(), ssl );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link InetSocketAddress} from the resolved {@code host} and port.
|
||||
*
|
||||
@@ -128,7 +144,7 @@ public final class NetworkUtils
|
||||
*/
|
||||
public static Options getOptions( String host, InetSocketAddress address ) throws HTTPRequestException
|
||||
{
|
||||
Options options = AddressRule.apply( ComputerCraft.httpRules, host, address.getAddress() );
|
||||
Options options = AddressRule.apply( ComputerCraft.httpRules, host, address );
|
||||
if( options.action == Action.DENY ) throw new HTTPRequestException( "Domain not permitted" );
|
||||
return options;
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@@ -52,17 +53,23 @@ public final class AddressRule
|
||||
|
||||
private final HostRange ip;
|
||||
private final Pattern domainPattern;
|
||||
private final Integer port;
|
||||
private final PartialOptions partial;
|
||||
|
||||
private AddressRule( @Nullable HostRange ip, @Nullable Pattern domainPattern, @Nonnull PartialOptions partial )
|
||||
private AddressRule(
|
||||
@Nullable HostRange ip,
|
||||
@Nullable Pattern domainPattern,
|
||||
@Nullable Integer port,
|
||||
@Nonnull PartialOptions partial )
|
||||
{
|
||||
this.ip = ip;
|
||||
this.domainPattern = domainPattern;
|
||||
this.partial = partial;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AddressRule parse( String filter, @Nonnull PartialOptions partial )
|
||||
public static AddressRule parse( String filter, @Nullable Integer port, @Nonnull PartialOptions partial )
|
||||
{
|
||||
int cidr = filter.indexOf( '/' );
|
||||
if( cidr >= 0 )
|
||||
@@ -117,24 +124,27 @@ public final class AddressRule
|
||||
size -= 8;
|
||||
}
|
||||
|
||||
return new AddressRule( new HostRange( minBytes, maxBytes ), null, partial );
|
||||
return new AddressRule( new HostRange( minBytes, maxBytes ), null, port, partial );
|
||||
}
|
||||
else
|
||||
{
|
||||
Pattern pattern = Pattern.compile( "^\\Q" + filter.replaceAll( "\\*", "\\\\E.*\\\\Q" ) + "\\E$" );
|
||||
return new AddressRule( null, pattern, partial );
|
||||
return new AddressRule( null, pattern, port, partial );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given address matches a series of patterns.
|
||||
*
|
||||
* @param domain The domain to match
|
||||
* @param address The address to check.
|
||||
* @param domain The domain to match
|
||||
* @param socketAddress The address to check.
|
||||
* @return Whether it matches any of these patterns.
|
||||
*/
|
||||
private boolean matches( String domain, InetAddress address )
|
||||
private boolean matches( String domain, InetSocketAddress socketAddress )
|
||||
{
|
||||
InetAddress address = socketAddress.getAddress();
|
||||
if( port != null && port != socketAddress.getPort() ) return false;
|
||||
|
||||
if( domainPattern != null )
|
||||
{
|
||||
if( domainPattern.matcher( domain ).matches() ) return true;
|
||||
@@ -155,7 +165,7 @@ public final class AddressRule
|
||||
return ip != null && ip.contains( address );
|
||||
}
|
||||
|
||||
public static Options apply( Iterable<? extends AddressRule> rules, String domain, InetAddress address )
|
||||
public static Options apply( Iterable<? extends AddressRule> rules, String domain, InetSocketAddress address )
|
||||
{
|
||||
PartialOptions options = null;
|
||||
boolean hasMany = false;
|
||||
|
@@ -49,12 +49,14 @@ public class AddressRuleConfig
|
||||
public static boolean checkRule( UnmodifiableConfig builder )
|
||||
{
|
||||
String hostObj = get( builder, "host", String.class ).orElse( null );
|
||||
Integer port = get( builder, "port", Number.class ).map( Number::intValue ).orElse( null );
|
||||
return hostObj != null && checkEnum( builder, "action", Action.class )
|
||||
&& check( builder, "port", Number.class )
|
||||
&& check( builder, "timeout", Number.class )
|
||||
&& check( builder, "max_upload", Number.class )
|
||||
&& check( builder, "max_download", Number.class )
|
||||
&& check( builder, "websocket_message", Number.class )
|
||||
&& AddressRule.parse( hostObj, PartialOptions.DEFAULT ) != null;
|
||||
&& AddressRule.parse( hostObj, port, PartialOptions.DEFAULT ) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -64,6 +66,7 @@ public class AddressRuleConfig
|
||||
if( hostObj == null ) return null;
|
||||
|
||||
Action action = getEnum( builder, "action", Action.class ).orElse( null );
|
||||
Integer port = get( builder, "port", Number.class ).map( Number::intValue ).orElse( null );
|
||||
Integer timeout = get( builder, "timeout", Number.class ).map( Number::intValue ).orElse( null );
|
||||
Long maxUpload = get( builder, "max_upload", Number.class ).map( Number::longValue ).orElse( null );
|
||||
Long maxDownload = get( builder, "max_download", Number.class ).map( Number::longValue ).orElse( null );
|
||||
@@ -77,7 +80,7 @@ public class AddressRuleConfig
|
||||
websocketMessage
|
||||
);
|
||||
|
||||
return AddressRule.parse( hostObj, options );
|
||||
return AddressRule.parse( hostObj, port, options );
|
||||
}
|
||||
|
||||
private static <T> boolean check( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
|
@@ -136,7 +136,7 @@ public class HttpRequest extends Resource<HttpRequest>
|
||||
try
|
||||
{
|
||||
boolean ssl = uri.getScheme().equalsIgnoreCase( "https" );
|
||||
InetSocketAddress socketAddress = NetworkUtils.getAddress( uri.getHost(), uri.getPort(), ssl );
|
||||
InetSocketAddress socketAddress = NetworkUtils.getAddress( uri, ssl );
|
||||
Options options = NetworkUtils.getOptions( uri.getHost(), socketAddress );
|
||||
SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null;
|
||||
|
||||
|
@@ -129,8 +129,7 @@ public class Websocket extends Resource<Websocket>
|
||||
try
|
||||
{
|
||||
boolean ssl = uri.getScheme().equalsIgnoreCase( "wss" );
|
||||
|
||||
InetSocketAddress socketAddress = NetworkUtils.getAddress( uri.getHost(), uri.getPort(), ssl );
|
||||
InetSocketAddress socketAddress = NetworkUtils.getAddress( uri, ssl );
|
||||
Options options = NetworkUtils.getOptions( uri.getHost(), socketAddress );
|
||||
SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null;
|
||||
|
||||
|
@@ -9,6 +9,7 @@ package dan200.computercraft.core.asm;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider;
|
||||
import dan200.computercraft.shared.util.ServiceUtil;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@@ -16,11 +17,12 @@ import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A generic source of {@link LuaMethod} functions. This allows for injecting methods onto objects you do not own.
|
||||
@@ -42,6 +44,18 @@ public interface GenericSource
|
||||
@Nonnull
|
||||
ResourceLocation id();
|
||||
|
||||
/**
|
||||
* Register a stream of generic sources.
|
||||
*
|
||||
* @param sources The source of generic methods.
|
||||
* @see ServiceUtil For ways to load this. Sadly {@link java.util.ServiceLoader} is broken under Forge, but we don't
|
||||
* want to add a hard-dep on Forge within core either.
|
||||
*/
|
||||
static void setup( Supplier<Stream<GenericSource>> sources )
|
||||
{
|
||||
GenericMethod.sources = sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* A generic method is a method belonging to a {@link GenericSource} with a known target.
|
||||
*/
|
||||
@@ -51,6 +65,7 @@ public interface GenericSource
|
||||
final LuaFunction annotation;
|
||||
final Class<?> target;
|
||||
|
||||
static Supplier<Stream<GenericSource>> sources;
|
||||
private static List<GenericMethod> cache;
|
||||
|
||||
GenericMethod( Method method, LuaFunction annotation, Class<?> target )
|
||||
@@ -68,10 +83,16 @@ public interface GenericSource
|
||||
static List<GenericMethod> all()
|
||||
{
|
||||
if( cache != null ) return cache;
|
||||
return cache = StreamSupport
|
||||
.stream( ServiceLoader.load( GenericSource.class, GenericSource.class.getClassLoader() ).spliterator(), false )
|
||||
if( sources == null )
|
||||
{
|
||||
ComputerCraft.log.warn( "Getting GenericMethods without a provider" );
|
||||
return cache = Collections.emptyList();
|
||||
}
|
||||
|
||||
return cache = sources.get()
|
||||
.flatMap( x -> Arrays.stream( x.getClass().getDeclaredMethods() ) )
|
||||
.map( method -> {
|
||||
.map( method ->
|
||||
{
|
||||
LuaFunction annotation = method.getAnnotation( LuaFunction.class );
|
||||
if( annotation == null ) return null;
|
||||
|
||||
|
@@ -395,14 +395,7 @@ public final class ComputerThread
|
||||
executor.timeout.hardAbort();
|
||||
executor.abort();
|
||||
|
||||
if( afterHardAbort >= ABORT_TIMEOUT )
|
||||
{
|
||||
// If we've hard aborted but we're still not dead, dump the stack trace and interrupt
|
||||
// the task.
|
||||
timeoutTask( executor, runner.owner, afterStart );
|
||||
runner.owner.interrupt();
|
||||
}
|
||||
else if( afterHardAbort >= ABORT_TIMEOUT * 2 )
|
||||
if( afterHardAbort >= ABORT_TIMEOUT * 2 )
|
||||
{
|
||||
// If we've hard aborted and interrupted, and we're still not dead, then mark the runner
|
||||
// as dead, finish off the task, and spawn a new runner.
|
||||
@@ -421,6 +414,13 @@ public final class ComputerThread
|
||||
}
|
||||
}
|
||||
}
|
||||
else if( afterHardAbort >= ABORT_TIMEOUT )
|
||||
{
|
||||
// If we've hard aborted but we're still not dead, dump the stack trace and interrupt
|
||||
// the task.
|
||||
timeoutTask( executor, runner.owner, afterStart );
|
||||
runner.owner.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -124,7 +124,7 @@ public class FileSystemWrapperMount implements IFileSystem
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.exists( path );
|
||||
return m_filesystem.isDir( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
|
@@ -83,9 +83,9 @@ class VarargArguments implements IArguments
|
||||
public ByteBuffer getBytes( int index ) throws LuaException
|
||||
{
|
||||
LuaValue value = varargs.arg( index + 1 );
|
||||
if( !(value instanceof LuaString) ) throw LuaValues.badArgument( index, "string", value.typeName() );
|
||||
if( !(value instanceof LuaBaseString) ) throw LuaValues.badArgument( index, "string", value.typeName() );
|
||||
|
||||
LuaString str = (LuaString) value;
|
||||
LuaString str = ((LuaBaseString) value).strvalue();
|
||||
return ByteBuffer.wrap( str.bytes, str.offset, str.length ).asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
@@ -94,9 +94,9 @@ class VarargArguments implements IArguments
|
||||
{
|
||||
LuaValue value = varargs.arg( index + 1 );
|
||||
if( value.isNil() ) return Optional.empty();
|
||||
if( !(value instanceof LuaString) ) throw LuaValues.badArgument( index, "string", value.typeName() );
|
||||
if( !(value instanceof LuaBaseString) ) throw LuaValues.badArgument( index, "string", value.typeName() );
|
||||
|
||||
LuaString str = (LuaString) value;
|
||||
LuaString str = ((LuaBaseString) value).strvalue();
|
||||
return Optional.of( ByteBuffer.wrap( str.bytes, str.offset, str.length ).asReadOnlyBuffer() );
|
||||
}
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.IBlockReader;
|
||||
import net.minecraft.world.IWorldReader;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraft.world.storage.loot.LootContext;
|
||||
@@ -180,4 +181,10 @@ public abstract class BlockComputerBase<T extends TileComputerBase> extends Bloc
|
||||
if( label != null ) computer.setLabel( label );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldCheckWeakPower( BlockState state, IWorldReader world, BlockPos pos, Direction side )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -13,10 +13,8 @@ import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Starts or stops a record on the client, depending on if {@link #soundEvent} is {@code null}.
|
||||
@@ -51,7 +49,7 @@ public class PlayRecordClientMessage implements NetworkMessage
|
||||
if( buf.readBoolean() )
|
||||
{
|
||||
name = buf.readString( Short.MAX_VALUE );
|
||||
soundEvent = ForgeRegistries.SOUND_EVENTS.getValue( buf.readResourceLocation() );
|
||||
soundEvent = buf.readRegistryIdSafe( SoundEvent.class );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -72,7 +70,7 @@ public class PlayRecordClientMessage implements NetworkMessage
|
||||
{
|
||||
buf.writeBoolean( true );
|
||||
buf.writeString( name );
|
||||
buf.writeResourceLocation( Objects.requireNonNull( soundEvent.getRegistryName(), "Sound is not registered" ) );
|
||||
buf.writeRegistryId( soundEvent );
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,6 +7,8 @@
|
||||
package dan200.computercraft.shared.peripheral.generic.data;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.util.NBTUtil;
|
||||
import net.minecraft.enchantment.Enchantment;
|
||||
import net.minecraft.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.item.EnchantedBookItem;
|
||||
@@ -22,13 +24,29 @@ import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Data providers for items.
|
||||
*
|
||||
* We guard using {@link ComputerCraft#genericPeripheral} in several places, as advanced functionality should not be
|
||||
* exposed for {@code turtle.getItemDetail} when generic peripehrals are disabled.
|
||||
*/
|
||||
public class ItemData
|
||||
{
|
||||
@Nonnull
|
||||
public static <T extends Map<? super String, Object>> T fillBasic( @Nonnull T data, @Nonnull ItemStack stack )
|
||||
public static <T extends Map<? super String, Object>> T fillBasicSafe( @Nonnull T data, @Nonnull ItemStack stack )
|
||||
{
|
||||
data.put( "name", DataHelpers.getId( stack.getItem() ) );
|
||||
data.put( "count", stack.getCount() );
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static <T extends Map<? super String, Object>> T fillBasic( @Nonnull T data, @Nonnull ItemStack stack )
|
||||
{
|
||||
fillBasicSafe( data, stack );
|
||||
String hash = NBTUtil.getNBTHash( stack.getTag() );
|
||||
if( hash != null ) data.put( "nbt", hash );
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -55,6 +73,8 @@ public class ItemData
|
||||
|
||||
data.put( "tags", DataHelpers.getTags( stack.getItem().getTags() ) );
|
||||
|
||||
if( !ComputerCraft.genericPeripheral ) return data;
|
||||
|
||||
CompoundNBT tag = stack.getTag();
|
||||
if( tag != null && tag.contains( "display", Constants.NBT.TAG_COMPOUND ) )
|
||||
{
|
||||
|
@@ -13,7 +13,6 @@ import net.minecraft.inventory.IInventory;
|
||||
import net.minecraft.inventory.Inventory;
|
||||
import net.minecraft.inventory.container.Container;
|
||||
import net.minecraft.inventory.container.Slot;
|
||||
import net.minecraft.item.DyeItem;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.IIntArray;
|
||||
import net.minecraft.util.IntArray;
|
||||
@@ -95,7 +94,7 @@ public class ContainerPrinter extends Container
|
||||
else
|
||||
{
|
||||
// Transfer from inventory to printer
|
||||
if( stack.getItem() instanceof DyeItem )
|
||||
if( TilePrinter.isInk( stack ) )
|
||||
{
|
||||
if( !mergeItemStack( stack, 0, 1, false ) ) return ItemStack.EMPTY;
|
||||
}
|
||||
|
@@ -300,9 +300,9 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isInk( @Nonnull ItemStack stack )
|
||||
static boolean isInk( @Nonnull ItemStack stack )
|
||||
{
|
||||
return stack.getItem() instanceof DyeItem;
|
||||
return ColourUtils.getStackColour( stack ) != null;
|
||||
}
|
||||
|
||||
private static boolean isPaper( @Nonnull ItemStack stack )
|
||||
@@ -321,7 +321,8 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
private boolean inputPage()
|
||||
{
|
||||
ItemStack inkStack = m_inventory.get( 0 );
|
||||
if( !isInk( inkStack ) ) return false;
|
||||
DyeColor dye = ColourUtils.getStackColour( inkStack );
|
||||
if( dye == null ) return false;
|
||||
|
||||
for( int i = 1; i < 7; i++ )
|
||||
{
|
||||
@@ -329,8 +330,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
if( paperStack.isEmpty() || !isPaper( paperStack ) ) continue;
|
||||
|
||||
// Setup the new page
|
||||
DyeColor dye = ColourUtils.getStackColour( inkStack );
|
||||
m_page.setTextColour( dye != null ? dye.getId() : 15 );
|
||||
m_page.setTextColour( dye.getId() );
|
||||
|
||||
m_page.clear();
|
||||
if( paperStack.getItem() instanceof ItemPrintout )
|
||||
|
@@ -605,7 +605,7 @@ public class TurtleAPI implements ILuaAPI
|
||||
|
||||
Map<String, Object> table = detailed
|
||||
? ItemData.fill( new HashMap<>(), stack )
|
||||
: ItemData.fillBasic( new HashMap<>(), stack );
|
||||
: ItemData.fillBasicSafe( new HashMap<>(), stack );
|
||||
|
||||
TurtleActionEvent event = new TurtleInspectItemEvent( turtle, stack, table, detailed );
|
||||
if( MinecraftForge.EVENT_BUS.post( event ) ) return new Object[] { false, event.getFailureMessage() };
|
||||
|
@@ -55,11 +55,10 @@ public final class TurtlePlayer extends FakePlayer
|
||||
|
||||
private void setState( ITurtleAccess turtle )
|
||||
{
|
||||
if( openContainer != null )
|
||||
if( openContainer != container )
|
||||
{
|
||||
ComputerCraft.log.warn( "Turtle has open container ({})", openContainer );
|
||||
openContainer.onContainerClosed( this );
|
||||
openContainer = null;
|
||||
closeContainer();
|
||||
}
|
||||
|
||||
BlockPos position = turtle.getPosition();
|
||||
|
@@ -40,6 +40,8 @@ public final class ColourUtils
|
||||
|
||||
public static DyeColor getStackColour( ItemStack stack )
|
||||
{
|
||||
if( stack.isEmpty() ) return null;
|
||||
|
||||
for( int i = 0; i < DYES.length; i++ )
|
||||
{
|
||||
Tag<Item> dye = DYES[i];
|
||||
|
@@ -42,8 +42,6 @@ public final class DropConsumer
|
||||
dropEntity = entity;
|
||||
dropWorld = entity.world;
|
||||
dropBounds = new AxisAlignedBB( entity.getPosition() ).grow( 2, 2, 2 );
|
||||
|
||||
entity.captureDrops( new ArrayList<>() );
|
||||
}
|
||||
|
||||
public static void set( World world, BlockPos pos, Function<ItemStack, ItemStack> consumer )
|
||||
@@ -86,7 +84,7 @@ public final class DropConsumer
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@SubscribeEvent( priority = EventPriority.LOW )
|
||||
public static void onLivingDrops( LivingDropsEvent drops )
|
||||
{
|
||||
if( dropEntity == null || drops.getEntity() != dropEntity ) return;
|
||||
|
@@ -5,9 +5,19 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import net.minecraft.nbt.*;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -159,4 +169,46 @@ public final class NBTUtil
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getNBTHash( @Nullable CompoundNBT tag )
|
||||
{
|
||||
if( tag == null ) return null;
|
||||
|
||||
try
|
||||
{
|
||||
MessageDigest digest = MessageDigest.getInstance( "MD5" );
|
||||
DataOutput output = new DataOutputStream( new DigestOutputStream( digest ) );
|
||||
CompressedStreamTools.write( tag, output );
|
||||
byte[] hash = digest.digest();
|
||||
return new String( Hex.encodeHex( hash ) );
|
||||
}
|
||||
catch( NoSuchAlgorithmException | IOException e )
|
||||
{
|
||||
ComputerCraft.log.error( "Cannot hash NBT", e );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DigestOutputStream extends OutputStream
|
||||
{
|
||||
private final MessageDigest digest;
|
||||
|
||||
DigestOutputStream( MessageDigest digest )
|
||||
{
|
||||
this.digest = digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write( @Nonnull byte[] b, int off, int len )
|
||||
{
|
||||
digest.update( b, off, len );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write( int b )
|
||||
{
|
||||
digest.update( (byte) b );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public final class ServiceUtil
|
||||
{
|
||||
private static final Type AUTO_SERVICE = Type.getType( "Lcom/google/auto/service/AutoService;" );
|
||||
|
||||
private ServiceUtil()
|
||||
{
|
||||
}
|
||||
|
||||
public static <T> Stream<T> loadServices( Class<T> target )
|
||||
{
|
||||
return StreamSupport.stream( ServiceLoader.load( target, ServiceUtil.class.getClassLoader() ).spliterator(), false );
|
||||
}
|
||||
|
||||
public static <T> Stream<T> loadServicesForge( Class<T> target )
|
||||
{
|
||||
Type type = Type.getType( target );
|
||||
ClassLoader loader = ComputerCraftAPI.class.getClassLoader();
|
||||
return ModList.get().getAllScanData().stream()
|
||||
.flatMap( x -> x.getAnnotations().stream() )
|
||||
.filter( x -> x.getAnnotationType().equals( AUTO_SERVICE ) )
|
||||
.filter( x -> {
|
||||
Object value = x.getAnnotationData().get( "value" );
|
||||
return value instanceof List<?> && ((List<?>) value).contains( type );
|
||||
} )
|
||||
.flatMap( x -> {
|
||||
try
|
||||
{
|
||||
Class<?> klass = loader.loadClass( x.getClassType().getClassName() );
|
||||
if( !target.isAssignableFrom( klass ) )
|
||||
{
|
||||
ComputerCraft.log.error( "{} is not a subtype of {}", x.getClassType().getClassName(), target.getName() );
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
Class<? extends T> casted = klass.asSubclass( target );
|
||||
return Stream.of( casted.newInstance() );
|
||||
}
|
||||
catch( ReflectiveOperationException e )
|
||||
{
|
||||
ComputerCraft.log.error( "Cannot load {}", x.getClassType(), e );
|
||||
return Stream.empty();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
@@ -19,6 +19,6 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a
|
||||
[[dependencies.computercraft]]
|
||||
modId="forge"
|
||||
mandatory=true
|
||||
versionRange="[31.0.13,32)"
|
||||
versionRange="[31.1.41,32)"
|
||||
ordering="NONE"
|
||||
side="BOTH"
|
||||
|
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"block.computercraft.computer_normal": "Dator",
|
||||
"block.computercraft.computer_advanced": "Avancerad Dator",
|
||||
"block.computercraft.computer_command": "Kommando Dator",
|
||||
"block.computercraft.computer_command": "Kommandodator",
|
||||
"block.computercraft.disk_drive": "Diskettläsare",
|
||||
"block.computercraft.printer": "Skrivare",
|
||||
"block.computercraft.speaker": "Högtalare",
|
||||
"block.computercraft.monitor_normal": "Skärm",
|
||||
"block.computercraft.monitor_advanced": "Avancerad Skärm",
|
||||
"block.computercraft.wireless_modem_normal": "Trådlöst Modem",
|
||||
"block.computercraft.wireless_modem_advanced": "Ender Modem",
|
||||
"block.computercraft.wireless_modem_advanced": "Endermodem",
|
||||
"block.computercraft.wired_modem": "Trådat Modem",
|
||||
"block.computercraft.cable": "Nätverkskabel",
|
||||
"block.computercraft.wired_modem_full": "Trådat Modem",
|
||||
@@ -38,5 +38,76 @@
|
||||
"upgrade.computercraft.speaker.adjective": "Högljudd",
|
||||
"chat.computercraft.wired_modem.peripheral_connected": "Kringutrustning \"%s\" är kopplad till nätverket",
|
||||
"chat.computercraft.wired_modem.peripheral_disconnected": "Kringutrustning \"%s\" är frånkopplad från nätverket",
|
||||
"gui.computercraft.tooltip.copy": "Kopiera till urklipp"
|
||||
"gui.computercraft.tooltip.copy": "Kopiera till urklipp",
|
||||
"gui.computercraft.tooltip.disk_id": "Diskett-ID: %s",
|
||||
"gui.computercraft.tooltip.computer_id": "Dator-ID: %s",
|
||||
"tracking_field.computercraft.coroutines_dead.name": "Coroutines borttagna",
|
||||
"tracking_field.computercraft.coroutines_created.name": "Coroutines skapade",
|
||||
"tracking_field.computercraft.websocket_outgoing.name": "Websocket utgående",
|
||||
"tracking_field.computercraft.websocket_incoming.name": "Websocket ingående",
|
||||
"tracking_field.computercraft.http_download.name": "HTTP-nedladdning",
|
||||
"tracking_field.computercraft.http_upload.name": "HTTP-uppladdning",
|
||||
"tracking_field.computercraft.http.name": "HTTP-förfrågningar",
|
||||
"tracking_field.computercraft.turtle.name": "Turtle-operationer",
|
||||
"tracking_field.computercraft.fs.name": "Filsystemoperationer",
|
||||
"tracking_field.computercraft.peripheral.name": "Samtal till kringutrustning",
|
||||
"tracking_field.computercraft.server_time.name": "Serveraktivitetstid",
|
||||
"tracking_field.computercraft.server_count.name": "Antal serveruppgifter",
|
||||
"tracking_field.computercraft.max.name": "Max tid",
|
||||
"tracking_field.computercraft.average.name": "Genomsnittlig tid",
|
||||
"tracking_field.computercraft.total.name": "Total tid",
|
||||
"tracking_field.computercraft.tasks.name": "Uppgifter",
|
||||
"argument.computercraft.argument_expected": "Argument förväntas",
|
||||
"argument.computercraft.tracking_field.no_field": "Okänt fält '%s'",
|
||||
"argument.computercraft.computer.many_matching": "Flera datorer matchar '%s' (%s träffar)",
|
||||
"argument.computercraft.computer.no_matching": "Inga datorer matchar '%s'",
|
||||
"commands.computercraft.generic.additional_rows": "%d ytterligare rader…",
|
||||
"commands.computercraft.generic.exception": "Ohanterat felfall (%s)",
|
||||
"commands.computercraft.generic.no": "N",
|
||||
"commands.computercraft.generic.yes": "J",
|
||||
"commands.computercraft.generic.position": "%s, %s, %s",
|
||||
"commands.computercraft.generic.no_position": "<no pos>",
|
||||
"commands.computercraft.queue.desc": "Skicka ett computer_command event till en kommandodator, skicka vidare ytterligare argument. Detta är mestadels utformat för kartmarkörer som fungerar som en mer datorvänlig version av /trigger. Alla spelare kan köra kommandot, vilket sannolikt skulle göras genom en textkomponents klick-event.",
|
||||
"commands.computercraft.queue.synopsis": "Skicka ett computer_command event till en kommandodator",
|
||||
"commands.computercraft.reload.done": "Konfiguration omladdad",
|
||||
"commands.computercraft.reload.desc": "Ladda om ComputerCrafts konfigurationsfil",
|
||||
"commands.computercraft.reload.synopsis": "Ladda om ComputerCrafts konfigurationsfil",
|
||||
"commands.computercraft.track.dump.computer": "Dator",
|
||||
"commands.computercraft.track.dump.no_timings": "Inga tidtagningar tillgängliga",
|
||||
"commands.computercraft.track.dump.desc": "Dumpa de senaste resultaten av datorspårning.",
|
||||
"commands.computercraft.track.dump.synopsis": "Dumpa de senaste spårningsresultaten",
|
||||
"commands.computercraft.track.stop.not_enabled": "Spårar för tillfället inga datorer",
|
||||
"commands.computercraft.track.stop.action": "Klicka för att stoppa spårning",
|
||||
"commands.computercraft.track.stop.desc": "Stoppa spårning av alla datorers körtider och eventräkningar",
|
||||
"commands.computercraft.track.stop.synopsis": "Stoppa spårning för alla datorer",
|
||||
"commands.computercraft.track.start.stop": "Kör %s för att stoppa spårning och visa resultaten",
|
||||
"commands.computercraft.track.start.desc": "Börja spåra alla dators körtider och eventräkningar. Detta kommer återställa resultaten från tidigare körningar.",
|
||||
"commands.computercraft.track.start.synopsis": "Starta spårning för alla datorer",
|
||||
"commands.computercraft.track.desc": "Spåra hur länge datorer exekverar, och även hur många event de hanterar. Detta presenterar information på liknande sätt som /forge track och kan vara användbart för att undersöka lagg.",
|
||||
"commands.computercraft.track.synopsis": "Spåra körningstider för denna dator.",
|
||||
"commands.computercraft.view.not_player": "Kan inte öppna terminalen för en ickespelare",
|
||||
"commands.computercraft.view.action": "Titta på denna dator",
|
||||
"commands.computercraft.view.desc": "Öppna datorns terminal för att möjligöra fjärrstyrning. Detta ger inte tillgång till turtlens inventory. Du kan ange en dators instans-id (t.ex. 123) eller dator-id (t.ex. #123).",
|
||||
"commands.computercraft.view.synopsis": "Titta på datorns terminal.",
|
||||
"commands.computercraft.tp.not_there": "Kan inte hitta datorn i världen",
|
||||
"commands.computercraft.tp.not_player": "Kan inte öppna terminalen för en ickespelare",
|
||||
"commands.computercraft.tp.action": "Teleportera till den här datorn",
|
||||
"commands.computercraft.tp.desc": "Teleportera till datorns position. Du kan ange en dators instans-id (t.ex. 123), dator-id (t.ex. #123) eller etikett (t.ex. \"@Min dator\").",
|
||||
"commands.computercraft.tp.synopsis": "Teleportera till en specifik dator.",
|
||||
"commands.computercraft.turn_on.done": "Startade %s/%s datorer",
|
||||
"commands.computercraft.turn_on.desc": "Starta de listade datorerna eller alla om ingen anges. Du kan ange en dators instans-id (t.ex. 123), dator-id (t.ex. #123) eller etikett (t.ex. \"@Min dator\").",
|
||||
"commands.computercraft.turn_on.synopsis": "Starta på datorer på distans.",
|
||||
"commands.computercraft.shutdown.done": "Stängde av %s/%s datorer",
|
||||
"commands.computercraft.dump.desc": "Visa status för alla datorer eller specifik information för en dator. Du kan ange en dators instans-id (t.ex. 123), dator-id (t.ex. #123) eller etikett (t.ex. \"@Min dator\").",
|
||||
"commands.computercraft.shutdown.desc": "Stäng av de listade datorerna eller alla om ingen anges. Du kan ange en dators instans-id (t.ex. 123), dator-id (t.ex. #123) eller etikett (t.ex. \"@Min dator\").",
|
||||
"commands.computercraft.shutdown.synopsis": "Stäng av datorer på distans.",
|
||||
"commands.computercraft.dump.action": "Visa mer information om den här datorn",
|
||||
"commands.computercraft.dump.synopsis": "Visa status för datorer.",
|
||||
"commands.computercraft.help.no_command": "Inget sådant kommando '%s'",
|
||||
"commands.computercraft.help.no_children": "%s har inget underkommando",
|
||||
"commands.computercraft.help.desc": "Visa detta hjälpmeddelande",
|
||||
"commands.computercraft.help.synopsis": "Tillhandahåll hjälp för ett specifikt kommando",
|
||||
"commands.computercraft.desc": "/computercraft kommandot tillhandahåller olika debugging- och administrationsverktyg för att kontrollera och interagera med datorer.",
|
||||
"commands.computercraft.synopsis": "Olika kommandon för att kontrollera datorer.",
|
||||
"itemGroup.computercraft": "ComputerCraft"
|
||||
}
|
||||
|
48
src/main/resources/assets/computercraft/lang/vi.json
Normal file
48
src/main/resources/assets/computercraft/lang/vi.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"gui.computercraft.tooltip.disk_id": "ID của đĩa: %s",
|
||||
"upgrade.computercraft.speaker.adjective": "Ồn ào",
|
||||
"upgrade.computercraft.wireless_modem_advanced.adjective": "Ender",
|
||||
"upgrade.computercraft.wireless_modem_normal.adjective": "Không dây",
|
||||
"upgrade.minecraft.crafting_table.adjective": "Chế tạo",
|
||||
"upgrade.minecraft.diamond_hoe.adjective": "Trồng trọt",
|
||||
"upgrade.minecraft.diamond_axe.adjective": "Đốn",
|
||||
"upgrade.minecraft.diamond_pickaxe.adjective": "Khai thác",
|
||||
"upgrade.minecraft.diamond_shovel.adjective": "Đào",
|
||||
"item.computercraft.pocket_computer_advanced.upgraded": "Máy tính bỏ túi tiên tiến %s",
|
||||
"item.computercraft.pocket_computer_advanced": "Máy tính bỏ túi tiên tiến",
|
||||
"item.computercraft.pocket_computer_normal.upgraded": "Máy tính bỏ túi %s",
|
||||
"item.computercraft.pocket_computer_normal": "Máy tính bỏ túi",
|
||||
"item.computercraft.printed_book": "Sách in",
|
||||
"item.computercraft.printed_page": "Trang in",
|
||||
"item.computercraft.treasure_disk": "Đĩa mềm",
|
||||
"item.computercraft.disk": "Đĩa mềm",
|
||||
"block.computercraft.turtle_advanced.upgraded_twice": "Rùa tiên tiến %s %s",
|
||||
"block.computercraft.turtle_advanced.upgraded": "Rùa tiên tiến %s",
|
||||
"block.computercraft.turtle_advanced": "Rùa tiên tiến",
|
||||
"block.computercraft.turtle_normal.upgraded_twice": "Rùa %s %s",
|
||||
"block.computercraft.turtle_normal.upgraded": "Rùa %s",
|
||||
"block.computercraft.turtle_normal": "Rùa",
|
||||
"block.computercraft.wired_modem_full": "Modem có dây",
|
||||
"block.computercraft.cable": "Dây cáp mạng",
|
||||
"block.computercraft.wired_modem": "Modem có dây",
|
||||
"block.computercraft.wireless_modem_advanced": "Modem Ender",
|
||||
"block.computercraft.wireless_modem_normal": "Modem không dây",
|
||||
"block.computercraft.monitor_advanced": "Màn hình tiên tiếng",
|
||||
"block.computercraft.monitor_normal": "Màn hình",
|
||||
"block.computercraft.speaker": "Loa",
|
||||
"block.computercraft.printer": "Máy in",
|
||||
"block.computercraft.disk_drive": "Ỗ đĩa",
|
||||
"block.computercraft.computer_command": "Máy tính điều khiển",
|
||||
"block.computercraft.computer_normal": "Máy tính",
|
||||
"itemGroup.computercraft": "ComputerCraft",
|
||||
"block.computercraft.computer_advanced": "Máy tính tiên tiến",
|
||||
"tracking_field.computercraft.websocket_incoming.name": "Websocket đến",
|
||||
"tracking_field.computercraft.websocket_outgoing.name": "Websocket đi",
|
||||
"gui.computercraft.tooltip.computer_id": "ID của máy tính: %s",
|
||||
"tracking_field.computercraft.coroutines_dead.name": "Coroutine bỏ đi",
|
||||
"tracking_field.computercraft.coroutines_created.name": "Coroutine đã tạo",
|
||||
"tracking_field.computercraft.http_download.name": "HTTP tải xuống",
|
||||
"tracking_field.computercraft.http_upload.name": "HTTP tải lên",
|
||||
"tracking_field.computercraft.http.name": "Yêu cầu HTTP",
|
||||
"gui.computercraft.tooltip.copy": "Sao chép vào clipboard"
|
||||
}
|
@@ -181,9 +181,6 @@ end
|
||||
--- Either calls @{colors.packRGB} or @{colors.unpackRGB}, depending on how many
|
||||
-- arguments it receives.
|
||||
--
|
||||
-- **Note:** This function is deprecated, and it is recommended you use the
|
||||
-- specific pack/unpack function directly.
|
||||
--
|
||||
-- @tparam[1] number r The red channel, as an argument to @{colors.packRGB}.
|
||||
-- @tparam[1] number g The green channel, as an argument to @{colors.packRGB}.
|
||||
-- @tparam[1] number b The blue channel, as an argument to @{colors.packRGB}.
|
||||
@@ -192,6 +189,7 @@ end
|
||||
-- @treturn[2] number The red channel, as returned by @{colors.unpackRGB}
|
||||
-- @treturn[2] number The green channel, as returned by @{colors.unpackRGB}
|
||||
-- @treturn[2] number The blue channel, as returned by @{colors.unpackRGB}
|
||||
-- @deprecated Use @{packRGB} or @{unpackRGB} directly.
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colors.rgb(0xb23399)
|
||||
|
@@ -289,7 +289,7 @@ end
|
||||
-- The `mode` string can be any of the following:
|
||||
-- - **"r"**: Read mode
|
||||
-- - **"w"**: Write mode
|
||||
-- - **"w"**: Append mode
|
||||
-- - **"a"**: Append mode
|
||||
--
|
||||
-- The mode may also have a `b` at the end, which opens the file in "binary
|
||||
-- mode". This allows you to read binary files, as well as seek within a file.
|
||||
|
@@ -77,7 +77,7 @@ function formatTime(nTime, bTwentyFourHour)
|
||||
local nHour = math.floor(nTime)
|
||||
local nMinute = math.floor((nTime - nHour) * 60)
|
||||
if sTOD then
|
||||
return string.format("%d:%02d %s", nHour, nMinute, sTOD)
|
||||
return string.format("%d:%02d %s", nHour == 0 and 12 or nHour, nMinute, sTOD)
|
||||
else
|
||||
return string.format("%d:%02d", nHour, nMinute)
|
||||
end
|
||||
@@ -335,6 +335,31 @@ empty_json_array = mk_tbl("[]", "empty_json_array")
|
||||
-- @see textutils.unserialiseJSON
|
||||
json_null = mk_tbl("null", "json_null")
|
||||
|
||||
local serializeJSONString
|
||||
do
|
||||
local function hexify(c)
|
||||
return ("\\u00%02X"):format(c:byte())
|
||||
end
|
||||
|
||||
local map = {
|
||||
["\""] = "\\\"",
|
||||
["\\"] = "\\\\",
|
||||
["\b"] = "\\b",
|
||||
["\f"] = "\\f",
|
||||
["\n"] = "\\n",
|
||||
["\r"] = "\\r",
|
||||
["\t"] = "\\t",
|
||||
}
|
||||
for i = 0, 0x1f do
|
||||
local c = string.char(i)
|
||||
if map[c] == nil then map[c] = hexify(c) end
|
||||
end
|
||||
|
||||
serializeJSONString = function(s)
|
||||
return ('"%s"'):format(s:gsub("[\0-\x1f\"\\]", map):gsub("[\x7f-\xff]", hexify))
|
||||
end
|
||||
end
|
||||
|
||||
local function serializeJSONImpl(t, tTracking, bNBTStyle)
|
||||
local sType = type(t)
|
||||
if t == empty_json_array then return "[]"
|
||||
@@ -361,7 +386,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
|
||||
if bNBTStyle then
|
||||
sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
|
||||
else
|
||||
sEntry = string.format("%q", k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
|
||||
sEntry = serializeJSONString(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
|
||||
end
|
||||
if nObjectSize == 0 then
|
||||
sObjectResult = sObjectResult .. sEntry
|
||||
@@ -390,7 +415,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
|
||||
end
|
||||
|
||||
elseif sType == "string" then
|
||||
return string.format("%q", t)
|
||||
return serializeJSONString(t)
|
||||
|
||||
elseif sType == "number" or sType == "boolean" then
|
||||
return tostring(t)
|
||||
@@ -407,7 +432,7 @@ do
|
||||
|
||||
--- Skip any whitespace
|
||||
local function skip(str, pos)
|
||||
local _, last = find(str, "^[ \n\r\v]+", pos)
|
||||
local _, last = find(str, "^[ \n\r\t]+", pos)
|
||||
if last then return last + 1 else return pos end
|
||||
end
|
||||
|
||||
@@ -447,7 +472,7 @@ do
|
||||
buf[n], n, pos = utf8.char(tonumber(num_str, 16)), n + 1, pos + 6
|
||||
else
|
||||
local unesc = escapes[c]
|
||||
if not unesc then error_at(pos + 1, "Unknown escape character %q.", unesc) end
|
||||
if not unesc then error_at(pos + 1, "Unknown escape character %q.", c) end
|
||||
buf[n], n, pos = unesc, n + 1, pos + 2
|
||||
end
|
||||
elseif c >= '\x20' then
|
||||
|
@@ -440,7 +440,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
end
|
||||
|
||||
--- Get the buffered contents of a line in this window.
|
||||
---
|
||||
--
|
||||
-- @tparam number y The y position of the line to get.
|
||||
-- @treturn string The textual content of this line.
|
||||
-- @treturn string The text colours of this line, suitable for use with @{term.blit}.
|
||||
|
@@ -1,3 +1,40 @@
|
||||
# New features in CC: Tweaked 1.93.0
|
||||
|
||||
* Update Swedish translations (Granddave).
|
||||
* Printers use item tags to check dyes.
|
||||
* HTTP rules may now be targetted for a specific port.
|
||||
* Don't propagate adjacent redstone signals through computers.
|
||||
|
||||
And several bug fixes:
|
||||
* Fix NPEs when turtles interact with containers.
|
||||
|
||||
# New features in CC: Tweaked 1.92.0
|
||||
|
||||
* Bump Cobalt version:
|
||||
* Add support for the __pairs metamethod.
|
||||
* string.format now uses the __tostring metamethod.
|
||||
* Add date-specific MOTDs (MCJack123).
|
||||
|
||||
And several bug fixes:
|
||||
* Correctly handle tabs within textutils.unserailizeJSON.
|
||||
* Fix sheep not dropping items when sheared by turtles.
|
||||
|
||||
# New features in CC: Tweaked 1.91.0
|
||||
|
||||
* [Generic peripherals] Expose NBT hashes of items to inventory methods.
|
||||
* Bump Cobalt version
|
||||
* Optimise handling of string concatenation.
|
||||
* Add string.{pack,unpack,packsize} (MCJack123)
|
||||
|
||||
And several bug fixes:
|
||||
* Escape non-ASCII characters in JSON strings (neumond)
|
||||
* Make field names in fs.attributes more consistent (abby)
|
||||
* Fix textutils.formatTime correctly handle 12 AM (R93950X)
|
||||
|
||||
# New features in CC: Tweaked 1.90.2
|
||||
|
||||
* Fix generic peripherals not being registered outside a dev environment.
|
||||
|
||||
# New features in CC: Tweaked 1.90.0
|
||||
|
||||
* Add cc.image.nft module, for working with nft files. (JakobDev)
|
||||
|
@@ -1,18 +1,11 @@
|
||||
New features in CC: Tweaked 1.90.0
|
||||
New features in CC: Tweaked 1.93.0
|
||||
|
||||
* Add cc.image.nft module, for working with nft files. (JakobDev)
|
||||
* [experimental] Provide a generic peripheral for any tile entity without an existing one. We currently provide methods for working with inventories, fluid tanks and energy storage. This is disabled by default, and must be turned on in the config.
|
||||
* Add configuration to control the sizes of monitors and terminals.
|
||||
* Add configuration to control maximum render distance of monitors.
|
||||
* Allow getting "detailed" information about an item, using `turtle.getItemDetail(slot, true)`. This will contain the same information that the generic peripheral supplies.
|
||||
* Update Swedish translations (Granddave).
|
||||
* Printers use item tags to check dyes.
|
||||
* HTTP rules may now be targetted for a specific port.
|
||||
* Don't propagate adjacent redstone signals through computers.
|
||||
|
||||
And several bug fixes:
|
||||
* Add back config for allowing interacting with command computers.
|
||||
* Fix write method missing from printers.
|
||||
* Fix dupe bug when killing an entity with a turtle.
|
||||
* Correctly supply port in the Host header (neumond).
|
||||
* Fix `turtle.craft` failing when missing an argument.
|
||||
* Fix deadlock when mistakenly "watching" an unloaded chunk.
|
||||
* Fix full path of files being leaked in some errors.
|
||||
* Fix NPEs when turtles interact with containers.
|
||||
|
||||
Type "help changelog" to see the full version history.
|
||||
|
@@ -1,15 +1,24 @@
|
||||
local tMotd = {}
|
||||
local date = os.date("*t")
|
||||
if date.month == 1 and date.day == 1 then
|
||||
print("Happy new year!")
|
||||
elseif date.month == 12 and date.day == 24 then
|
||||
print("Merry X-mas!")
|
||||
elseif date.month == 10 and date.day == 31 then
|
||||
print("OOoooOOOoooo! Spooky!")
|
||||
else
|
||||
local tMotd = {}
|
||||
|
||||
for sPath in string.gmatch(settings.get("motd.path"), "[^:]+") do
|
||||
if fs.exists(sPath) then
|
||||
for sLine in io.lines(sPath) do
|
||||
table.insert(tMotd, sLine)
|
||||
for sPath in string.gmatch(settings.get("motd.path"), "[^:]+") do
|
||||
if fs.exists(sPath) then
|
||||
for sLine in io.lines(sPath) do
|
||||
table.insert(tMotd, sLine)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #tMotd == 0 then
|
||||
print("missingno")
|
||||
else
|
||||
print(tMotd[math.random(1, #tMotd)])
|
||||
if #tMotd == 0 then
|
||||
print("missingno")
|
||||
else
|
||||
print(tMotd[math.random(1, #tMotd)])
|
||||
end
|
||||
end
|
||||
|
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis.http.options;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class AddressRuleTest
|
||||
{
|
||||
@Test
|
||||
public void matchesPort()
|
||||
{
|
||||
Iterable<AddressRule> rules = Collections.singletonList( AddressRule.parse(
|
||||
"127.0.0.1", 8080,
|
||||
new PartialOptions( Action.ALLOW, null, null, null, null )
|
||||
) );
|
||||
|
||||
assertEquals( apply( rules, "localhost", 8080 ).action, Action.ALLOW );
|
||||
assertEquals( apply( rules, "localhost", 8081 ).action, Action.DENY );
|
||||
}
|
||||
|
||||
private Options apply( Iterable<AddressRule> rules, String host, int port )
|
||||
{
|
||||
return AddressRule.apply( rules, host, new InetSocketAddress( host, port ) );
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
[ ]
|
@@ -208,9 +208,11 @@ describe("The fs library", function()
|
||||
fail(("Expected created time (%d) to be within 1000ms of now (%d"):format(attributes.created, now))
|
||||
end
|
||||
|
||||
if attributes.modification - now >= 1000 then
|
||||
fail(("Expected modification time (%d) to be within 1000ms of now (%d"):format(attributes.modification, now))
|
||||
if attributes.modified - now >= 1000 then
|
||||
fail(("Expected modified time (%d) to be within 1000ms of now (%d"):format(attributes.modified, now))
|
||||
end
|
||||
|
||||
expect(attributes.modification):eq(attributes.modified)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
@@ -12,6 +12,14 @@ describe("The textutils library", function()
|
||||
expect.error(textutils.formatTime, nil):eq("bad argument #1 (expected number, got nil)")
|
||||
expect.error(textutils.formatTime, 1, 1):eq("bad argument #2 (expected boolean, got number)")
|
||||
end)
|
||||
|
||||
it("correctly formats 12 o'clock", function()
|
||||
expect(textutils.formatTime(0, false)):eq("12:00 AM")
|
||||
expect(textutils.formatTime(0.1, false)):eq("12:06 AM")
|
||||
|
||||
expect(textutils.formatTime(0, true)):eq("0:00")
|
||||
expect(textutils.formatTime(0.1, true)):eq("0:06")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("textutils.pagedPrint", function()
|
||||
@@ -49,7 +57,7 @@ describe("The textutils library", function()
|
||||
describe("textutils.empty_json_array", function()
|
||||
it("is immutable", function()
|
||||
expect.error(function() textutils.empty_json_array[1] = true end)
|
||||
:str_match("^[^:]+:51: attempt to mutate textutils.empty_json_array$")
|
||||
:str_match("^[^:]+:%d+: attempt to mutate textutils.empty_json_array$")
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -78,6 +86,20 @@ describe("The textutils library", function()
|
||||
it("serializes null", function()
|
||||
expect(textutils.serializeJSON(textutils.json_null)):eq("null")
|
||||
end)
|
||||
|
||||
it("serializes strings", function()
|
||||
expect(textutils.serializeJSON('a')):eq('"a"')
|
||||
expect(textutils.serializeJSON('"')):eq('"\\""')
|
||||
expect(textutils.serializeJSON('\\')):eq('"\\\\"')
|
||||
expect(textutils.serializeJSON('/')):eq('"/"')
|
||||
expect(textutils.serializeJSON('\b')):eq('"\\b"')
|
||||
expect(textutils.serializeJSON('\n')):eq('"\\n"')
|
||||
expect(textutils.serializeJSON(string.char(0))):eq('"\\u0000"')
|
||||
expect(textutils.serializeJSON(string.char(0x0A))):eq('"\\n"')
|
||||
expect(textutils.serializeJSON(string.char(0x1D))):eq('"\\u001D"')
|
||||
expect(textutils.serializeJSON(string.char(0x81))):eq('"\\u0081"')
|
||||
expect(textutils.serializeJSON(string.char(0xFF))):eq('"\\u00FF"')
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("textutils.unserializeJSON", function()
|
||||
|
@@ -1,14 +1,36 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The motd program", function()
|
||||
local function setup_date(month, day)
|
||||
stub(os, "date", function() return { month = month, day = day } end)
|
||||
end
|
||||
|
||||
it("displays MODT", function()
|
||||
local file = fs.open("/modt_check.txt", "w")
|
||||
it("displays MOTD", function()
|
||||
setup_date(0, 0)
|
||||
local file = fs.open("/motd_check.txt", "w")
|
||||
file.write("Hello World!")
|
||||
file.close()
|
||||
settings.set("motd.path", "/modt_check.txt")
|
||||
settings.set("motd.path", "/motd_check.txt")
|
||||
|
||||
expect(capture(stub, "motd"))
|
||||
:matches { ok = true, output = "Hello World!\n", error = "" }
|
||||
end)
|
||||
|
||||
it("displays date-specific MOTD (1/1)", function()
|
||||
setup_date(1, 1)
|
||||
expect(capture(stub, "motd"))
|
||||
:matches { ok = true, output = "Happy new year!\n", error = "" }
|
||||
end)
|
||||
|
||||
it("displays date-specific MOTD (10/31)", function()
|
||||
setup_date(10, 31)
|
||||
expect(capture(stub, "motd"))
|
||||
:matches { ok = true, output = "OOoooOOOoooo! Spooky!\n", error = "" }
|
||||
end)
|
||||
|
||||
it("displays date-specific MOTD (12/24)", function()
|
||||
setup_date(12, 24)
|
||||
expect(capture(stub, "motd"))
|
||||
:matches { ok = true, output = "Merry X-mas!\n", error = "" }
|
||||
end)
|
||||
end)
|
||||
|
Reference in New Issue
Block a user