1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-02 14:43:00 +00:00

Compare commits

...

28 Commits

Author SHA1 Message Date
SquidDev
748ebbe66b Bump to 1.92.0
A tiny release, but there's new features so it's technically a minor
bump.
2020-09-12 09:27:47 +01:00
SquidDev
59de21eae2 Handle tabs when parsing JSON
Fixes #539
2020-09-11 18:02:23 +01:00
SquidDev
50473afea8 Fix Gradle example for depending on CC:T
See #405
2020-09-09 08:39:28 +01:00
SquidDev
37f925de0a Remove references to cc.crzd.me's maven
As CC only exists on 1.12.2, it's a bit meaningless to talk about it on
1.15+!
2020-09-08 18:37:40 +01:00
SquidDev
cefde3f003 Also block 0.0.0.0/8
See MightyPirates/OpenComputers#3356
2020-09-08 18:12:20 +01:00
Weblate
ae6124d1f4 Translations for Vietnamese
Co-authored-by: Boom <boom@flyingpackets.net>
2020-09-08 03:57:52 +00:00
Weblate
7e121ff72f Translations for Vietnamese
Co-authored-by: Boom <boom@flyingpackets.net>
2020-09-07 06:37:58 +00:00
Weblate
5155e18de2 Added translation for Vietnamese
Co-authored-by: Boom <boom@flyingpackets.net>
2020-09-07 03:33:36 +00:00
SquidDev
7365741088 Don't use entity.captureDrops at all.
This really should have been removed in 9e2232d240.
Otherwise we don't drop these items into the world at all. Fixes #537.
2020-09-05 11:02:24 +01:00
JackMacWindows
d5368d0719 Add date-specific MOTDs (like Minecraft) (#533) 2020-09-04 17:35:46 +01:00
Jonathan Coates
04509cefec Merge pull request #529 from Bluenaxela/mc-1.15.x
Pretty sure FileSystemWrapperMount.isDirectory() should call Filesystem.isDir(), not Filesystem.exists()
2020-08-25 08:11:13 +01:00
Bluenaxela
74b9f5dcb0 Fix FileSystemWrapperMount.isDirectory()
Pretty sure FileSystemWrapperMount.isDirectory() should call Filesystem.isDir(), not Filesystem.exists()
2020-08-24 23:55:24 -07:00
SquidDev
183b342071 Bump for 1.91.0 2020-08-23 15:35:58 +01:00
SquidDev
0bb5515055 Fix checkstyle problems 2020-08-22 19:31:49 +01:00
SquidDev
9acfc0316f Expose NBT hashes of items to users
This just uses the same approach as Plethora, so we should have aparity
for .list() now.
2020-08-22 16:09:35 +01:00
SquidDev
29fb0baa09 Use Forge's packet methods for sending SoundEvents
Doesn't fix #515 (arguably makes it worse in the sense that it's more
likely to throw). However it should provide better error reporting, and
make it more clear that it's not CC:T's fault.
2020-08-22 15:31:48 +01:00
R93950X
d5de39ebd4 Fix time formatting (#527)
Fixes #525.

Co-authored-by: R93950X <R93950X@users.noreply.github.com>
2020-08-22 15:17:12 +01:00
SquidDev
0faf76e4bd Fix if statement never being hit
Let's been honest, this bit's probably never been tested, because it
should never happen. Fixes #528.
2020-08-22 15:03:38 +01:00
SquidDev
e8e2ed9fe5 Fix incorrect lower bound in mods.toml
It appears I had failed to update this when last bumping the Forge
version. Closes #521 - we're relying on a feature only added in Forge
31.1.16, and they're using 3.1.14.
2020-08-09 21:51:55 +01:00
SquidDev
9f72448ecd Properly deprecate colors.rgb8 2020-08-04 19:50:36 +01:00
SquidDev
3da3f16deb Fix link to logo 2020-08-04 18:30:22 +01:00
hydraz
0e2ce3c634 Make the key for mtime "modified" in fs.attributes (#512) 2020-07-31 18:39:09 +01:00
SquidDev
fe00e00537 Mention people should include logs 2020-07-31 18:31:47 +01:00
Jonathan Coates
cd879b067f Merge pull request #508 from neumond/mc-1.15.x
Fix JSON serialization of strings
2020-07-25 17:51:46 +01:00
neumond
053cb1b53c Fix JSON serialization of strings
Control characters become escaped as JSON requires
Non-ASCII characters get escaped as well for better interoperability
We assume here that lua strings represent only first 256 code points of unicode
2020-07-25 19:40:06 +03:00
SquidDev
ac7979fb46 Bump for 1.90.2 2020-07-25 11:53:46 +01:00
SquidDev
d51851e763 Use FML's scan data to gather annotations
We can just scrape them from the @AutoService annotation, which saves us
having to duplicate any work. Hopefully fixes #501, but I haven't tested
in a non-dev environment yet.
2020-07-23 22:41:20 +01:00
BlackDragon-B
fb70a1a998 Added Windows thing. (#500) 2020-07-18 18:59:52 +01:00
28 changed files with 381 additions and 73 deletions

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
# ![CC: Tweaked](logo.png)
# ![CC: Tweaked](doc/logo.png)
[![Current build status](https://github.com/SquidDev-CC/CC-Tweaked/workflows/Build/badge.svg)](https://github.com/SquidDev-CC/CC-Tweaked/actions "Current build status") [![Download CC: Tweaked on CurseForge](http://cf.way2muchnoise.eu/title/cc-tweaked.svg)](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/' }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -124,7 +124,7 @@ public class FileSystemWrapperMount implements IFileSystem
{
try
{
return m_filesystem.exists( path );
return m_filesystem.isDir( path );
}
catch( FileSystemException e )
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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"
}

View File

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

View 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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
[ ]

View File

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

View File

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

View File

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