1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-06 00:22:58 +00:00

Compare commits

...

25 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
25 changed files with 286 additions and 55 deletions

View File

@@ -12,4 +12,5 @@ labels: bug
## Useful information to include: ## Useful information to include:
- Minecraft version - Minecraft version
- CC: Tweaked 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. - 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

@@ -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") [![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, 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! report exploits). You'll get a far quicker response if you ask the whole community!
## Using ## Using
If you want to depend on CC: Tweaked, we have a maven repo. However, you should be wary that some functionality is only 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)
exposed by CC:T's API and not vanilla ComputerCraft. If you wish to support all variations of ComputerCraft, I recommend dependency in your `mods.toml` file, with the appropriate version bounds, to ensure that API functionality you depend
using [cc.crzd.me's maven](https://cc.crzd.me/maven/) instead. on is present.
```groovy ```groovy
dependencies { repositories {
maven { url 'https://squiddev.cc/maven/' } maven { url 'https://squiddev.cc/maven/' }
} }

View File

@@ -1,5 +1,5 @@
# Mod properties # Mod properties
mod_version=1.90.2 mod_version=1.92.0
# Minecraft properties (update mods.toml when changing) # Minecraft properties (update mods.toml when changing)
mc_version=1.15.2 mc_version=1.15.2

View File

@@ -101,6 +101,9 @@
(linters -doc:unresolved-reference)) (linters -doc:unresolved-reference))
(at /src/test/resources/test-rom (at /src/test/resources/test-rom
; We should still be able to test deprecated members.
(linters -var:deprecated)
(lint (lint
(globals (globals
:max sleep write :max sleep write

View File

@@ -39,6 +39,7 @@ public final class ComputerCraft
public static final String[] DEFAULT_HTTP_ALLOW = new String[] { "*" }; public static final String[] DEFAULT_HTTP_ALLOW = new String[] { "*" };
public static final String[] DEFAULT_HTTP_DENY = new String[] { public static final String[] DEFAULT_HTTP_DENY = new String[] {
"127.0.0.0/8", "127.0.0.0/8",
"0.0.0.0/8",
"10.0.0.0/8", "10.0.0.0/8",
"172.16.0.0/12", "172.16.0.0/12",
"192.168.0.0/16", "192.168.0.0/16",

View File

@@ -479,6 +479,7 @@ public class FSAPI implements ILuaAPI
BasicFileAttributes attributes = fileSystem.getAttributes( path ); BasicFileAttributes attributes = fileSystem.getAttributes( path );
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put( "modification", getFileTime( attributes.lastModifiedTime() ) ); result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
result.put( "modified", getFileTime( attributes.lastModifiedTime() ) );
result.put( "created", getFileTime( attributes.creationTime() ) ); result.put( "created", getFileTime( attributes.creationTime() ) );
result.put( "size", attributes.isDirectory() ? 0 : attributes.size() ); result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
result.put( "isDir", attributes.isDirectory() ); result.put( "isDir", attributes.isDirectory() );

View File

@@ -395,14 +395,7 @@ public final class ComputerThread
executor.timeout.hardAbort(); executor.timeout.hardAbort();
executor.abort(); executor.abort();
if( afterHardAbort >= ABORT_TIMEOUT ) if( afterHardAbort >= ABORT_TIMEOUT * 2 )
{
// 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 we've hard aborted and interrupted, and we're still not dead, then mark the runner // 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. // 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 try
{ {
return m_filesystem.exists( path ); return m_filesystem.isDir( path );
} }
catch( FileSystemException e ) catch( FileSystemException e )
{ {

View File

@@ -83,9 +83,9 @@ class VarargArguments implements IArguments
public ByteBuffer getBytes( int index ) throws LuaException public ByteBuffer getBytes( int index ) throws LuaException
{ {
LuaValue value = varargs.arg( index + 1 ); 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(); return ByteBuffer.wrap( str.bytes, str.offset, str.length ).asReadOnlyBuffer();
} }
@@ -94,9 +94,9 @@ class VarargArguments implements IArguments
{ {
LuaValue value = varargs.arg( index + 1 ); LuaValue value = varargs.arg( index + 1 );
if( value.isNil() ) return Optional.empty(); 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() ); 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.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.network.NetworkEvent; import net.minecraftforge.fml.network.NetworkEvent;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.Objects;
/** /**
* Starts or stops a record on the client, depending on if {@link #soundEvent} is {@code null}. * 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() ) if( buf.readBoolean() )
{ {
name = buf.readString( Short.MAX_VALUE ); name = buf.readString( Short.MAX_VALUE );
soundEvent = ForgeRegistries.SOUND_EVENTS.getValue( buf.readResourceLocation() ); soundEvent = buf.readRegistryIdSafe( SoundEvent.class );
} }
else else
{ {
@@ -72,7 +70,7 @@ public class PlayRecordClientMessage implements NetworkMessage
{ {
buf.writeBoolean( true ); buf.writeBoolean( true );
buf.writeString( name ); 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; package dan200.computercraft.shared.peripheral.generic.data;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.item.EnchantedBookItem; import net.minecraft.item.EnchantedBookItem;
@@ -22,13 +24,29 @@ import javax.annotation.Nullable;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; 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 public class ItemData
{ {
@Nonnull @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( "name", DataHelpers.getId( stack.getItem() ) );
data.put( "count", stack.getCount() ); 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; return data;
} }
@@ -55,6 +73,8 @@ public class ItemData
data.put( "tags", DataHelpers.getTags( stack.getItem().getTags() ) ); data.put( "tags", DataHelpers.getTags( stack.getItem().getTags() ) );
if( !ComputerCraft.genericPeripheral ) return data;
CompoundNBT tag = stack.getTag(); CompoundNBT tag = stack.getTag();
if( tag != null && tag.contains( "display", Constants.NBT.TAG_COMPOUND ) ) 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 Map<String, Object> table = detailed
? ItemData.fill( new HashMap<>(), stack ) ? ItemData.fill( new HashMap<>(), stack )
: ItemData.fillBasic( new HashMap<>(), stack ); : ItemData.fillBasicSafe( new HashMap<>(), stack );
TurtleActionEvent event = new TurtleInspectItemEvent( turtle, stack, table, detailed ); TurtleActionEvent event = new TurtleInspectItemEvent( turtle, stack, table, detailed );
if( MinecraftForge.EVENT_BUS.post( event ) ) return new Object[] { false, event.getFailureMessage() }; if( MinecraftForge.EVENT_BUS.post( event ) ) return new Object[] { false, event.getFailureMessage() };

View File

@@ -42,8 +42,6 @@ public final class DropConsumer
dropEntity = entity; dropEntity = entity;
dropWorld = entity.world; dropWorld = entity.world;
dropBounds = new AxisAlignedBB( entity.getPosition() ).grow( 2, 2, 2 ); 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 ) 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 ) public static void onLivingDrops( LivingDropsEvent drops )
{ {
if( dropEntity == null || drops.getEntity() != dropEntity ) return; if( dropEntity == null || drops.getEntity() != dropEntity ) return;

View File

@@ -5,9 +5,19 @@
*/ */
package dan200.computercraft.shared.util; package dan200.computercraft.shared.util;
import dan200.computercraft.ComputerCraft;
import net.minecraft.nbt.*; import net.minecraft.nbt.*;
import net.minecraftforge.common.util.Constants; 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.HashMap;
import java.util.Map; import java.util.Map;
@@ -159,4 +169,46 @@ public final class NBTUtil
} }
return objects; 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

@@ -19,6 +19,6 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a
[[dependencies.computercraft]] [[dependencies.computercraft]]
modId="forge" modId="forge"
mandatory=true mandatory=true
versionRange="[31.0.13,32)" versionRange="[31.1.41,32)"
ordering="NONE" ordering="NONE"
side="BOTH" 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 --- Either calls @{colors.packRGB} or @{colors.unpackRGB}, depending on how many
-- arguments it receives. -- 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 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 g The green channel, as an argument to @{colors.packRGB}.
-- @tparam[1] number b The blue 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 red channel, as returned by @{colors.unpackRGB}
-- @treturn[2] number The green 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} -- @treturn[2] number The blue channel, as returned by @{colors.unpackRGB}
-- @deprecated Use @{packRGB} or @{unpackRGB} directly.
-- @usage -- @usage
-- ```lua -- ```lua
-- colors.rgb(0xb23399) -- colors.rgb(0xb23399)

View File

@@ -77,7 +77,7 @@ function formatTime(nTime, bTwentyFourHour)
local nHour = math.floor(nTime) local nHour = math.floor(nTime)
local nMinute = math.floor((nTime - nHour) * 60) local nMinute = math.floor((nTime - nHour) * 60)
if sTOD then 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 else
return string.format("%d:%02d", nHour, nMinute) return string.format("%d:%02d", nHour, nMinute)
end end
@@ -335,6 +335,31 @@ empty_json_array = mk_tbl("[]", "empty_json_array")
-- @see textutils.unserialiseJSON -- @see textutils.unserialiseJSON
json_null = mk_tbl("null", "json_null") 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 function serializeJSONImpl(t, tTracking, bNBTStyle)
local sType = type(t) local sType = type(t)
if t == empty_json_array then return "[]" if t == empty_json_array then return "[]"
@@ -361,7 +386,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
if bNBTStyle then if bNBTStyle then
sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle) sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
else else
sEntry = string.format("%q", k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle) sEntry = serializeJSONString(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
end end
if nObjectSize == 0 then if nObjectSize == 0 then
sObjectResult = sObjectResult .. sEntry sObjectResult = sObjectResult .. sEntry
@@ -390,7 +415,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
end end
elseif sType == "string" then elseif sType == "string" then
return string.format("%q", t) return serializeJSONString(t)
elseif sType == "number" or sType == "boolean" then elseif sType == "number" or sType == "boolean" then
return tostring(t) return tostring(t)
@@ -407,7 +432,7 @@ do
--- Skip any whitespace --- Skip any whitespace
local function skip(str, pos) 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 if last then return last + 1 else return pos end
end end
@@ -447,7 +472,7 @@ do
buf[n], n, pos = utf8.char(tonumber(num_str, 16)), n + 1, pos + 6 buf[n], n, pos = utf8.char(tonumber(num_str, 16)), n + 1, pos + 6
else else
local unesc = escapes[c] 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 buf[n], n, pos = unesc, n + 1, pos + 2
end end
elseif c >= '\x20' then elseif c >= '\x20' then

View File

@@ -1,3 +1,26 @@
# 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 # New features in CC: Tweaked 1.90.2
* Fix generic peripherals not being registered outside a dev environment. * Fix generic peripherals not being registered outside a dev environment.

View File

@@ -1,5 +1,12 @@
New features in CC: Tweaked 1.90.2 New features in CC: Tweaked 1.92.0
* Fix generic peripherals not being registered outside a dev environment. * 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.
Type "help changelog" to see the full version history. 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 for sPath in string.gmatch(settings.get("motd.path"), "[^:]+") do
if fs.exists(sPath) then if fs.exists(sPath) then
for sLine in io.lines(sPath) do for sLine in io.lines(sPath) do
table.insert(tMotd, sLine) table.insert(tMotd, sLine)
end
end end
end end
end
if #tMotd == 0 then if #tMotd == 0 then
print("missingno") print("missingno")
else else
print(tMotd[math.random(1, #tMotd)]) print(tMotd[math.random(1, #tMotd)])
end
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)) fail(("Expected created time (%d) to be within 1000ms of now (%d"):format(attributes.created, now))
end end
if attributes.modification - now >= 1000 then if attributes.modified - now >= 1000 then
fail(("Expected modification time (%d) to be within 1000ms of now (%d"):format(attributes.modification, now)) fail(("Expected modified time (%d) to be within 1000ms of now (%d"):format(attributes.modified, now))
end end
expect(attributes.modification):eq(attributes.modified)
end) end)
end) 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, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(textutils.formatTime, 1, 1):eq("bad argument #2 (expected boolean, got number)") expect.error(textutils.formatTime, 1, 1):eq("bad argument #2 (expected boolean, got number)")
end) 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) end)
describe("textutils.pagedPrint", function() describe("textutils.pagedPrint", function()
@@ -49,7 +57,7 @@ describe("The textutils library", function()
describe("textutils.empty_json_array", function() describe("textutils.empty_json_array", function()
it("is immutable", function() it("is immutable", function()
expect.error(function() textutils.empty_json_array[1] = true end) 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)
end) end)
@@ -78,6 +86,20 @@ describe("The textutils library", function()
it("serializes null", function() it("serializes null", function()
expect(textutils.serializeJSON(textutils.json_null)):eq("null") expect(textutils.serializeJSON(textutils.json_null)):eq("null")
end) 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) end)
describe("textutils.unserializeJSON", function() describe("textutils.unserializeJSON", function()

View File

@@ -1,14 +1,36 @@
local capture = require "test_helpers".capture_program local capture = require "test_helpers".capture_program
describe("The motd program", function() 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() it("displays MOTD", function()
local file = fs.open("/modt_check.txt", "w") setup_date(0, 0)
local file = fs.open("/motd_check.txt", "w")
file.write("Hello World!") file.write("Hello World!")
file.close() file.close()
settings.set("motd.path", "/modt_check.txt") settings.set("motd.path", "/motd_check.txt")
expect(capture(stub, "motd")) expect(capture(stub, "motd"))
:matches { ok = true, output = "Hello World!\n", error = "" } :matches { ok = true, output = "Hello World!\n", error = "" }
end) 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) end)