mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-11-02 14:43:00 +00:00
Compare commits
28 Commits
v1.16.1-1.
...
v1.15.2-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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.92.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",
|
||||
@@ -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() );
|
||||
|
||||
@@ -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() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ) )
|
||||
{
|
||||
|
||||
@@ -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() };
|
||||
|
||||
@@ -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"
|
||||
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
# 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 sheered 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,12 @@
|
||||
New features in CC: Tweaked 1.90.0
|
||||
New features in CC: Tweaked 1.92.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.
|
||||
* 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:
|
||||
* 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.
|
||||
* Correctly handle tabs within textutils.unserailizeJSON.
|
||||
* Fix sheep not dropping items when sheered by turtles.
|
||||
|
||||
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 @@
|
||||
[ ]
|
||||
@@ -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