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

Compare commits

...

34 Commits

Author SHA1 Message Date
Jummit
2a290be248 Bump version to 1.95.0
As is tradition.
2021-05-16 01:26:56 +02:00
Jummit
c4a6c16330 Manually wrap strings for help (#602)
This saves us writing to a buffer multiple times, and so makes things much,
much faster.
2021-05-16 01:26:43 +02:00
Jummit
f8b3b544d1 Add functions to wrap text 2021-05-16 01:26:32 +02:00
Jonathan Coates
53ae689468 Fix licence issues
I knew I shouldn't do modding on things which aren't my main computer.

I actually did run checkstyleMain before committing, but entirely forgot
about this one. Go me.
2021-05-16 01:26:21 +02:00
Merith
73e460e533 Merge Commits from Jummit/cc-restitched PR20 2021-05-15 08:05:59 -07:00
Jummit
a4e68d637a Make upgrade recipe requirements a little more lax
- Move some common upgrade code to IUpgradeBase. 99% sure this this
   preserves binary compatibility (on the JVM at least).

 - Instead of requiring the share tag to match, allow upgrades to
   specify their own predicate. IMO this is a little ugly, but required
   to fix #614 as other mods chuck their own NBT on items.
2021-05-16 00:50:24 +02:00
Jonathan Coates
1f1b20c81e Fix overflow in os.epoch
Closes #611
2021-05-16 00:10:49 +02:00
Jonathan Coates
20a9ff7a3f Fix double URL decode
Closes #613
2021-05-16 00:10:40 +02:00
Jonathan Coates
58cd5e3df7 Fix copy-paste error in inventory docs
I'm a very silly squid.
2021-05-16 00:10:19 +02:00
Jonathan Coates
5922d7548c Merge pull request #606 from TheWireLord/numpadenter-support
Added Numpad Enter Support - bios.lua
2021-05-16 00:06:13 +02:00
Merith
386e01364e Merge Commits from Jummit/cc-restitched PR19 2021-05-15 06:53:14 -07:00
Jummit
70eff6aa63 Skip "Docs for energy and inventory methods" 2021-05-15 20:34:30 +02:00
SquidDev
45ac601946 Generate docs for generic peripherals
This was the easy bit. Now I've got to write them!
2021-05-15 20:21:15 +02:00
SquidDev
d6c5a5bd27 Some sanity checks for get{Direction,Orientation}
Silly bodge, but should fix #600.
2021-05-15 20:18:04 +02:00
SquidDev
02c7903cb7 Remove a couple of todos 2021-05-15 20:06:21 +02:00
Jummit
37e856efdc Skip 737b3cb5
Don't use capabilities for generic peripherals
2021-05-15 20:04:51 +02:00
Merith-TK
8a38097887 [Patchwork] fix github workflow 2021-05-15 06:46:23 -07:00
Merith
884b417ec2 Merge Commits from Jummit/cc-restitched
SquidDev-CC/CC-Tweaked@511eea3
SquidDev-CC/CC-Tweaked@826797c
SquidDev-CC/CC-Tweaked@24d3777
SquidDev-CC/CC-Tweaked@d83a68f
2021-05-14 06:16:56 -07:00
JackMacWindows
1d1d6227eb Added improved help viewer (#595)
- Pagination, with (page) up/down, q(uit) and scrolling support.
 - Render markdown style bullets ('-'/'*') using a '•' instead.
2021-05-14 21:36:08 +02:00
Jummit
d5eb82db60 Allow $private HTTP rule to block any private IP
This is a little magic compared with our previous approach of "list
every private IP range", but given then the sheer number we were
missing[1][2] this feels more reasonable.

Also refactor out some of the logic into separate classes, hopefully to
make things a little cleaner.
2021-05-14 21:31:03 +02:00
JackMacWindows
3575654d02 Added documentation for global functions (#592) 2021-05-14 19:37:54 +02:00
Jonathan Coates
79fcc7241b Remove <!-- -->s in usages
We fixed the bug in illuaminate, so this should be redundant now.
2021-05-14 18:51:10 +02:00
Merith-TK
b203d3aa0a Try to handle a turtle being broken while ticked
Hopefully fixes #585. Hopefully.
2021-05-07 15:05:00 -07:00
Merith-TK
f1176af9d1 Make fs.combine accept multiple arguments
Means we can now do fs.combine("a", "b", "c"). Of course, one may just
write "a/b/c" in this case, but it's definitely useful elsewhere.

This is /technically/ a breaking change as fs.combine(a, b:gsub(...))
will no longer function (as gsub returns multiple arguments). However,
I've done a quick search through GH and my Pastebin archives and can't
find any programs which would break. Fingers crossed.
2021-05-07 13:27:04 -07:00
Merith-TK
717686d855 Allow strings or numbers in textutils.*tabulate
Allow strings or numbers in textutils.*tabulate

A little dubious, but apparently CC used to support it. This means we're
consistent with methods like io.write or string.len which accept strings
or numbers.

Fixes #591
2021-05-07 11:26:24 -07:00
Merith-TK
f8a58dbcb1 Fixed length check on function name in expect (#589) 2021-04-22 11:54:56 -07:00
Merith-TK
98aabe2cfb Remove extra space (#586) 2021-04-22 11:43:58 -07:00
Merith-TK
f938ed9779 Strict Globals (#583) 2021-04-22 11:35:15 -07:00
Merith-TK
03c9274b27 More examples
Yay!
2021-04-22 11:23:31 -07:00
Merith-TK
92c94ac039 Cleanup examples for the various modules 2021-04-21 19:32:03 -07:00
Merith-TK
5c6fd80b0c Use term.blit on original paint render
This makes it super speedy, meaning an initial refresh doesn't take ages to load.
2021-04-21 08:57:32 -07:00
Merith-TK
f1ec59df15 Clear gets an option to reset the palette (#582)
Fixes #555.
2021-04-21 08:32:34 -07:00
Merith-TK
624b23c7ac Fix epoch documentation to use milliseconds (#580) 2021-04-09 20:45:59 -07:00
Merith-TK
9b77a4aaee Update Readme with Known Issues 2021-04-09 20:03:14 -07:00
46 changed files with 1203 additions and 430 deletions

View File

@@ -36,9 +36,6 @@ jobs:
run: bash <(curl -s https://codecov.io/bash)
continue-on-error: true
- name: Generate Java documentation stubs
run: ./gradlew luaJavadoc --no-daemon
- name: Lint Lua code
run: |
test -d bin || mkdir bin

View File

@@ -3,3 +3,15 @@
*it runs and works-ish*
PRs welcome
## Known Issues
Main Known issue
* Mods that add blocks that can be used as peripherals for CC:T On forge, dont work with CC:R.
* This is because of the differences between forge and fabric, and that mod devs, to my knowledge have not agreed upon a standard method in which to implement cross compatibility between mods,
* Storage Peripherals throw a java "StackOverflowError" when using `pushItems()`,
* Work around, you are probably using `pushItems(chest, 1)` or simular. please use `pushItems(chest, 1, nil, 1)`.
## Known Working mods that add Peripherals
* Please let me know of other mods that work with this one
* Better End
* Better Nether

View File

@@ -51,8 +51,9 @@ dependencies {
shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.1.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.1.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
modRuntime "me.shedaniel:RoughlyEnoughItems-api:5.2.10"
modRuntime "me.shedaniel:RoughlyEnoughItems:5.2.10"

View File

@@ -2,7 +2,7 @@
org.gradle.jvmargs=-Xmx1G
# Mod properties
mod_version=1.94.1-beta
mod_version=1.95.0-beta
# Minecraft properties
mc_version=1.16.2

View File

@@ -305,9 +305,235 @@ little more detail.
[1] https://github.com/SquidDev-CC/CC-Tweaked/wiki/Monitor-texture-reference
```
```
7f90f2f7cadce0d5b9177b16626979591bce8137
```
Clean up some examples a little bit
Would be good if they didn't crash and burn on entry :).
```
```
f194f4fa3a17c48ff1a9088d50063f4a675a23b6
Fix epoch documentation to use milliseconds (#580)
```
```
d2a1a00dc43e5b65f6b64111ce76dd3db16c919f
Clear gets an option to reset the palette (#582)
Fixes #555.
```
```
aab0cd34cd64fdf837ff1c3b91a957a25c2cf7f9
Use term.blit on original paint render
This makes it super speedy, meaning an initial refresh doesn't take ages
to load.
```
```
b0651082f472baee8f0fa8ec7ba95f433e2637bb
Cleanup examples for the various modules
```
Ignored Documentation Changes, these are locate
```
9a749642d294506095e697a3a4345dfe260bd68c
Strict Globals (#583)
```
```
fff8353451451be5ae31e0f63d8e529b127fd186
Remove extra space (#586)
```
```
486f41f08286ddcfad91d72b83a9361bd9c215cb
Fixed length check on function name in `expect` (#589)
```
```
04f9644ae75dafc72da4c4790f334d2e90b03e6f
Allow strings or numbers in textutils.*tabulate
A little dubious, but apparently CC used to support it. This means we're
consistent with methods like io.write or string.len which accept strings
or numbers.
Fixes #591
```
```
d4199064ae5ae8023c589f80f12d94e1c6bbc2b5
Make fs.combine accept multiple arguments
Means we can now do fs.combine("a", "b", "c"). Of course, one may just
write "a/b/c" in this case, but it's definitely useful elsewhere.
This is /technically/ a breaking change as fs.combine(a, b:gsub(...))
will no longer function (as gsub returns multiple arguments). However,
I've done a quick search through GH and my Pastebin archives and can't
find any programs which would break. Fingers crossed.
```
```
24af36743d08fcdb58439c52bf587b33ed828263
Try to handle a turtle being broken while ticked
Hopefully fixes #585. Hopefully.
```
```
511eea39a11956c82e2c11a47b2e7cad27f9887e
Remove <!-- -->s in usages
```
```
826797cbd579e867f0f35f0be44b6a28c8c094a9
Added documentation for global functions (#592)
```
Didn't port the docs over.
```
d83a68f3ff6e3833278a38798d06215293656e85
Allow $private HTTP rule to block any private IP
```
The config still uses a `blacklist` and `whitelist` array.
```
24d3777722812f975d2bc4594437fbbb0431d910
Added improved help viewer (#595)
```
Didn't port the lua tests over.
```
737b3cb57696fb5517252e7db38bc88ce960b4d8
Don't use capabilities for generic peripherals
```
Not ported, related to forges capability system which is not used in the port.
```
ea3a16036794357c3a44edffc90fdb652e03881e
Remove a couple of todos
```
```
bb8f4c624bf87169b73fb631d8250cfc38181e15
Some sanity checks for get{Direction,Orientation}
```
Use `getCachedState` instead of forge's `getBlockState` and `contains` instead of `has`.
```
05c3c8ad3269c9025757f9261e7f609889fb6bdc
Generate docs for generic peripherals
```
Skipped everything except some removed whitespace.
```
85cf2d5ff1b63010de4661301801aa504e5b9015
Docs for energy and inventory methods
```
and
```
5865e9c41a0140b9f1acdd2fb095353c467fbb45
Not sure what irritates me more
```
both skipped because the changes where already ported.
```
4ae370b9dbaf1de0ed32a5f32340b1448136c9cc
Merge pull request #606 from TheWireLord/numpadenter-support
```
Just lua changes.
```
f5eb6ce03e0d9bbbf77130452afd4b49e758f7bd
Fix copy-paste error in inventory docs
```
Skipped because it was already ported.
```
663859d2e5a97edefebf9ac36206903d7dd33a3e
Fix double URL decode
```
```
abf425dfb5553483cdc51c50a6b7d8b5e44814f4
Fix overflow in os.epoch
```
```
e3a672099c1b5d2c06f9fe4d8ccd024fef0873a2
Fix JEI integration with turtle/pocket upgrades
```
Skipped because there seems to be no REI integration.
```
2f0cae0bc1b038ac092bafa7f65a317537203cd8
Make upgrade recipe requirements a little more lax
```
[TODO] [JUMT-01] Crafting is still messed up, but this port didn't change the behavior.
[TODO] [JUMT-02] Tag comparison code doesn't need to be that verbose, a simple `isEqual` check would suffice.
```
7f9a707f75636d5816f752dc93d7b6b998c61a03
Bump version to 1.95.0
```
Changed the name from CC: Tweaked to CC: Restitched in the changelog and whatsnew files. New version is 1.95.0-beta.
```
4af5bcc0b0ff464e7e7428c389d47140580ea7a7
Fix serveral 1.15 -> 1.16 issues
```
Skipped, changes where already made.
```
b8d5a89446ac02fc5b38cc5c0b4805de9d11a7d5
Add explicit @module annotation
```
Tiny lua change.
```
8b17ec76a8e94251803e6f4ba4e65970c6a70b7f
Fixed missing argument names in file handle docs (#632)
```
A java doc change.
```
e4b0a5b3ce035eb23feb4191432fc49af5772c5b
2020 -> 2021
```
A huge amount of changes.

View File

@@ -10,12 +10,12 @@ import static dan200.computercraft.shared.ComputerCraftRegistry.ModBlocks;
import static dan200.computercraft.shared.ComputerCraftRegistry.init;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.core.apis.http.options.Action;
@@ -32,20 +32,10 @@ import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.media.recipes.DiskRecipe;
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
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.pocket.recipes.PocketComputerUpgradeRecipe;
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.turtle.upgrades.TurtleAxe;
import dan200.computercraft.shared.turtle.upgrades.TurtleCraftingTable;
import dan200.computercraft.shared.turtle.upgrades.TurtleHoe;
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
import dan200.computercraft.shared.turtle.upgrades.TurtleShovel;
import dan200.computercraft.shared.turtle.upgrades.TurtleSpeaker;
import dan200.computercraft.shared.turtle.upgrades.TurtleSword;
import dan200.computercraft.shared.turtle.upgrades.TurtleTool;
import dan200.computercraft.shared.util.Config;
import dan200.computercraft.shared.util.ImpostorRecipe;
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
@@ -65,15 +55,6 @@ import net.fabricmc.loader.api.FabricLoader;
public final class ComputerCraft implements ModInitializer {
public static final String MOD_ID = "computercraft";
// Configuration options
public static final String[] DEFAULT_HTTP_WHITELIST = new String[] {"*"};
public static final String[] DEFAULT_HTTP_BLACKLIST = new String[] {
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"fd00::/8",
"0.0.0.0/8"
};
public static final int terminalWidth_computer = 51;
public static final int terminalHeight_computer = 19;
public static final int terminalWidth_turtle = 39;
@@ -122,17 +103,10 @@ public final class ComputerCraft implements ModInitializer {
public static int monitorHeight = 6;
public static double monitorDistanceSq = 4096;
public static List<AddressRule> httpRules = buildHttpRulesFromConfig(DEFAULT_HTTP_BLACKLIST, DEFAULT_HTTP_WHITELIST);
public static List<AddressRule> buildHttpRulesFromConfig(String[] blacklist, String[] whitelist) {
return Stream.concat(Stream.of(blacklist)
.map( x -> AddressRule.parse( x, null, Action.DENY.toPartial()))
.filter(Objects::nonNull),
Stream.of(whitelist)
.map( x -> AddressRule.parse( x, null, Action.ALLOW.toPartial()))
.filter(Objects::nonNull))
.collect(Collectors.toList());
}
public static List<AddressRule> httpRules = Collections.unmodifiableList( Arrays.asList(
AddressRule.parse( "$private", null, Action.DENY.toPartial() ),
AddressRule.parse( "*", null, Action.ALLOW.toPartial() )
) );
@Override
public void onInitialize() {

View File

@@ -0,0 +1,81 @@
package dan200.computercraft.api;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.Identifier;
import javax.annotation.Nonnull;
/**
* Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}.
*/
public interface IUpgradeBase
{
/**
* Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem"
* or "my_mod:my_upgrade".
*
* You should use a unique resource domain to ensure this upgrade is uniquely identified.
* The upgrade will fail registration if an already used ID is specified.
*
* @return The unique ID for this upgrade.
*/
@Nonnull
Identifier getUpgradeID();
/**
* Return an unlocalised string to describe this type of computer in item names.
*
* Examples of built-in adjectives are "Wireless", "Mining" and "Crafty".
*
* @return The localisation key for this upgrade's adjective.
*/
@Nonnull
String getUnlocalisedAdjective();
/**
* Return an item stack representing the type of item that a computer must be crafted
* with to create a version which holds this upgrade. This item stack is also used
* to determine the upgrade given by {@code turtle.equipLeft()} or {@code pocket.equipBack()}
*
* This should be constant over a session (or at least a datapack reload). It is recommended
* that you cache the stack too, in order to prevent constructing it every time the method
* is called.
*
* @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted.
*/
@Nonnull
ItemStack getCraftingItem();
/**
* Determine if an item is suitable for being used for this upgrade.
*
* When un-equipping an upgrade, we return {@link #getCraftingItem()} rather than
* the original stack. In order to prevent people losing items with enchantments (or
* repairing items with non-0 damage), we impose additional checks on the item.
*
* The default check requires that any non-capability NBT is exactly the same as the
* crafting item, but this may be relaxed for your upgrade.
*
* @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
* {@link #getCraftingItem()}.
* @return If this stack may be used to equip this upgrade.
* @see net.minecraftforge.common.crafting.NBTIngredient#test(ItemStack) For the implementation of the default
* check.
*/
default boolean isItemSuitable( @Nonnull ItemStack stack )
{
ItemStack crafting = getCraftingItem();
// A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
// null one.
CompoundTag shareTag = stack.getTag();
CompoundTag craftingShareTag = crafting.getTag();
if( shareTag == craftingShareTag ) return true;
if( shareTag == null ) return craftingShareTag.isEmpty();
if( craftingShareTag == null ) return shareTag.isEmpty();
return shareTag.equals( craftingShareTag );
}
}

View File

@@ -10,56 +10,18 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.IUpgradeBase;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import net.minecraft.world.World;
/**
* Additional peripherals for pocket computers.
*
* This is similar to {@link ITurtleUpgrade}.
* @see ComputerCraftAPI#registerPocketUpgrade(IPocketUpgrade)
*/
public interface IPocketUpgrade {
/**
* Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem" or "my_mod:my_upgrade".
*
* You should use a unique resource domain to ensure this upgrade is uniquely identified. The upgrade will fail registration if an already used ID is
* specified.
*
* @return The upgrade's id.
* @see IPocketUpgrade#getUpgradeID()
* @see ComputerCraftAPI#registerPocketUpgrade(IPocketUpgrade)
*/
@Nonnull
Identifier getUpgradeID();
/**
* Return an unlocalised string to describe the type of pocket computer this upgrade provides.
*
* An example of a built-in adjectives is "Wireless" - this is converted to "Wireless Pocket Computer".
*
* @return The unlocalised adjective.
* @see ITurtleUpgrade#getUnlocalisedAdjective()
*/
@Nonnull
String getUnlocalisedAdjective();
/**
* Return an item stack representing the type of item that a pocket computer must be crafted with to create a pocket computer which holds this upgrade.
* This item stack is also used to determine the upgrade given by {@code pocket.equip()}/{@code pocket.unequip()}.
*
* Ideally this should be constant over a session. It is recommended that you cache the item too, in order to prevent constructing it every time the
* method is called.
*
* @return The item stack used for crafting. This can be {@link ItemStack#EMPTY} if crafting is disabled.
*/
@Nonnull
ItemStack getCraftingItem();
public interface IPocketUpgrade extends IUpgradeBase
{
/**
* Creates a peripheral for the pocket computer.
*

View File

@@ -10,11 +10,10 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.IUpgradeBase;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.fabricmc.api.EnvType;
@@ -25,27 +24,8 @@ import net.fabricmc.api.Environment;
*
* @see ComputerCraftAPI#registerTurtleUpgrade(ITurtleUpgrade)
*/
public interface ITurtleUpgrade {
/**
* Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem" or "my_mod:my_upgrade". You should use a unique
* resource domain to ensure this upgrade is uniquely identified. The turtle will fail registration if an already used ID is specified.
*
* @return The unique ID for this upgrade.
* @see ComputerCraftAPI#registerTurtleUpgrade(ITurtleUpgrade)
*/
@Nonnull
Identifier getUpgradeID();
/**
* Return an unlocalised string to describe this type of turtle in turtle item names.
*
* Examples of built-in adjectives are "Wireless", "Mining" and "Crafty".
*
* @return The localisation key for this upgrade's adjective.
*/
@Nonnull
String getUnlocalisedAdjective();
public interface ITurtleUpgrade extends IUpgradeBase
{
/**
* Return whether this turtle adds a tool or a peripheral to the turtle.
*
@@ -55,18 +35,6 @@ public interface ITurtleUpgrade {
@Nonnull
TurtleUpgradeType getType();
/**
* Return an item stack representing the type of item that a turtle must be crafted with to create a turtle which holds this upgrade. This item stack is
* also used to determine the upgrade given by {@code turtle.equip()}
*
* Ideally this should be constant over a session. It is recommended that you cache the item too, in order to prevent constructing it every time the
* method is called.
*
* @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted.
*/
@Nonnull
ItemStack getCraftingItem();
/**
* Will only be called for peripheral upgrades. Creates a peripheral for a turtle being placed using this upgrade.
*

View File

@@ -76,7 +76,6 @@ public final class ComputerCraftProxyClient implements ClientModInitializer {
BlockEntityRendererRegistry.INSTANCE.register(ComputerCraftRegistry.ModTiles.MONITOR_ADVANCED, TileEntityMonitorRenderer::new);
BlockEntityRendererRegistry.INSTANCE.register(ComputerCraftRegistry.ModTiles.TURTLE_NORMAL, TileEntityTurtleRenderer::new);
BlockEntityRendererRegistry.INSTANCE.register(ComputerCraftRegistry.ModTiles.TURTLE_ADVANCED, TileEntityTurtleRenderer::new);
// TODO: ClientRegistry.bindTileEntityRenderer( TileCable.FACTORY, x -> new TileEntityCableRenderer() );
ClientSpriteRegistryCallback.event(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE)
.register(ClientRegistry::onTextureStitchEvent);

View File

@@ -49,7 +49,6 @@ class MonitorTextureBufferShader {
RenderSystem.glUniform1i(uniformWidth, width);
RenderSystem.glUniform1i(uniformHeight, height);
// TODO: Cache this? Maybe??
PALETTE_BUFFER.rewind();
for (int i = 0; i < 16; i++) {
double[] colour = palette.getColour(i);

View File

@@ -17,6 +17,8 @@ import java.util.Map;
import java.util.OptionalLong;
import java.util.function.Function;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
@@ -75,15 +77,27 @@ public class FSAPI implements ILuaAPI {
}
/**
* Combines two parts of a path into one full path, adding separators as needed.
* Combines several parts of a path into one full path, adding separators as
* needed
*
* @param pathA The first part of the path. For example, a parent directory path.
* @param pathB The second part of the path. For example, a file name.
* @param arguments The paths to combine.
* @return The new path, with separators added between parts as needed.
* @cc.tparam string path The first part of the path. For example, a parent directory path.
* @cc.tparam string ... Additional parts of the path to combine.
* @throws LuaException On argument errors.
*/
@LuaFunction
public final String combine(String pathA, String pathB) {
return this.fileSystem.combine(pathA, pathB);
public final String combine( IArguments arguments ) throws LuaException {
StringBuilder result = new StringBuilder();
result.append(FileSystem.sanitizePath(arguments.getString(0), true));
for (int i = 1, n = arguments.count(); i < n; i++) {
String part = FileSystem.sanitizePath(arguments.getString(i), true);
if (result.length() != 0 && !part.isEmpty()) result.append('/');
result.append(part);
}
return FileSystem.sanitizePath(result.toString(), true);
}
/**

View File

@@ -326,14 +326,14 @@ public class OSAPI implements ILuaAPI {
}
/**
* Returns the number of seconds since an epoch depending on the locale.
* Returns the number of milliseconds since an epoch depending on the locale.
*
* * If called with {@code ingame}, returns the number of seconds since the world was created. This is the default. * If called with {@code utc},
* returns the number of seconds since 1 January 1970 in the UTC timezone. * If called with {@code local}, returns the number of seconds since 1 January
* * If called with {@code ingame}, returns the number of milliseconds since the world was created. This is the default. * If called with {@code utc},
* returns the number of milliseconds since 1 January 1970 in the UTC timezone. * If called with {@code local}, returns the number of seconds since 1 January
* 1970 in the server's local timezone.
*
* @param args The locale to get the seconds for. Defaults to {@code ingame} if not set.
* @return The seconds since the epoch depending on the selected locale.
* @param args The locale to get the milliseconds for. Defaults to {@code ingame} if not set.
* @return The milliseconds since the epoch depending on the selected locale.
* @throws LuaException If an invalid locale is passed.
*/
@LuaFunction
@@ -353,7 +353,7 @@ public class OSAPI implements ILuaAPI {
case "ingame":
// Get in-game epoch
synchronized (this.m_alarms) {
return this.m_day * 86400000 + (int) (this.m_time * 3600000.0f);
return this.m_day * 86400000L + (long) (this.m_time * 3600000.0);
}
default:
throw new LuaException("Unsupported operation");

View File

@@ -0,0 +1,148 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.http.options;
import com.google.common.net.InetAddresses;
import dan200.computercraft.ComputerCraft;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.regex.Pattern;
/**
* A predicate on an address. Matches against a domain and an ip address.
*
* @see AddressRule#apply(Iterable, String, InetSocketAddress) for the actual handling of this rule.
*/
interface AddressPredicate
{
default boolean matches( String domain )
{
return false;
}
default boolean matches( InetAddress socketAddress )
{
return false;
}
final class HostRange implements AddressPredicate
{
private final byte[] min;
private final byte[] max;
HostRange( byte[] min, byte[] max )
{
this.min = min;
this.max = max;
}
@Override
public boolean matches( InetAddress address )
{
byte[] entry = address.getAddress();
if( entry.length != min.length ) return false;
for( int i = 0; i < entry.length; i++ )
{
int value = 0xFF & entry[i];
if( value < (0xFF & min[i]) || value > (0xFF & max[i]) ) return false;
}
return true;
}
public static HostRange parse( String addressStr, String prefixSizeStr )
{
int prefixSize;
try
{
prefixSize = Integer.parseInt( prefixSizeStr );
}
catch( NumberFormatException e )
{
ComputerCraft.log.error(
"Malformed http whitelist/blacklist entry '{}': Cannot extract size of CIDR mask from '{}'.",
addressStr + '/' + prefixSizeStr, prefixSizeStr
);
return null;
}
InetAddress address;
try
{
address = InetAddresses.forString( addressStr );
}
catch( IllegalArgumentException e )
{
ComputerCraft.log.error(
"Malformed http whitelist/blacklist entry '{}': Cannot extract IP address from '{}'.",
addressStr + '/' + prefixSizeStr, prefixSizeStr
);
return null;
}
// Mask the bytes of the IP address.
byte[] minBytes = address.getAddress(), maxBytes = address.getAddress();
int size = prefixSize;
for( int i = 0; i < minBytes.length; i++ )
{
if( size <= 0 )
{
minBytes[i] &= 0;
maxBytes[i] |= 0xFF;
}
else if( size < 8 )
{
minBytes[i] &= 0xFF << (8 - size);
maxBytes[i] |= ~(0xFF << (8 - size));
}
size -= 8;
}
return new HostRange( minBytes, maxBytes );
}
}
final class DomainPattern implements AddressPredicate
{
private final Pattern pattern;
DomainPattern( Pattern pattern )
{
this.pattern = pattern;
}
@Override
public boolean matches( String domain )
{
return pattern.matcher( domain ).matches();
}
@Override
public boolean matches( InetAddress socketAddress )
{
return pattern.matcher( socketAddress.getHostAddress() ).matches();
}
}
final class PrivatePattern implements AddressPredicate
{
static final PrivatePattern INSTANCE = new PrivatePattern();
@Override
public boolean matches( InetAddress socketAddress )
{
return socketAddress.isAnyLocalAddress()
|| socketAddress.isLoopbackAddress()
|| socketAddress.isLinkLocalAddress()
|| socketAddress.isSiteLocalAddress();
}
}
}

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.core.apis.http.options;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -15,7 +16,10 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.net.InetAddresses;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.apis.http.options.AddressPredicate.DomainPattern;
import dan200.computercraft.core.apis.http.options.AddressPredicate.HostRange;
import dan200.computercraft.core.apis.http.options.AddressPredicate.PrivatePattern;
/**
* A pattern which matches an address, and controls whether it is accessible or not.
@@ -25,18 +29,12 @@ public final class AddressRule {
public static final long MAX_UPLOAD = 4 * 1024 * 1024;
public static final int TIMEOUT = 30_000;
public static final int WEBSOCKET_MESSAGE = 128 * 1024;
private final HostRange ip;
private final Pattern domainPattern;
private final AddressPredicate predicate;
private final Integer port;
private final PartialOptions partial;
private AddressRule(
@Nullable HostRange ip,
@Nullable Pattern domainPattern,
@Nullable Integer port,
@Nonnull PartialOptions partial )
{
this.ip = ip;
this.domainPattern = domainPattern;
private AddressRule( @Nonnull AddressPredicate predicate, @Nullable Integer port, @Nonnull PartialOptions partial ) {
this.predicate = predicate;
this.partial = partial;
this.port = port;
}
@@ -47,55 +45,29 @@ public final class AddressRule {
if (cidr >= 0) {
String addressStr = filter.substring(0, cidr);
String prefixSizeStr = filter.substring(cidr + 1);
int prefixSize;
try {
prefixSize = Integer.parseInt(prefixSizeStr);
} catch (NumberFormatException e) {
ComputerCraft.log.error("Malformed http whitelist/blacklist entry '{}': Cannot extract size of CIDR mask from '{}'.",
filter,
prefixSizeStr);
return null;
}
InetAddress address;
try {
address = InetAddresses.forString(addressStr);
} catch (IllegalArgumentException e) {
ComputerCraft.log.error("Malformed http whitelist/blacklist entry '{}': Cannot extract IP address from '{}'.", filter, prefixSizeStr);
return null;
}
// Mask the bytes of the IP address.
byte[] minBytes = address.getAddress(), maxBytes = address.getAddress();
int size = prefixSize;
for (int i = 0; i < minBytes.length; i++) {
if (size <= 0) {
minBytes[i] &= 0;
maxBytes[i] |= 0xFF;
} else if (size < 8) {
minBytes[i] &= 0xFF << (8 - size);
maxBytes[i] |= ~(0xFF << (8 - size));
}
size -= 8;
}
return new AddressRule(new HostRange(minBytes, maxBytes), null, port, partial);
HostRange range = HostRange.parse( addressStr, prefixSizeStr );
return range == null ? null : new AddressRule( range, port, partial );
}
else if( filter.equalsIgnoreCase( "$private" ) )
{
return new AddressRule( PrivatePattern.INSTANCE, port, partial );
} else {
Pattern pattern = Pattern.compile("^\\Q" + filter.replaceAll("\\*", "\\\\E.*\\\\Q") + "\\E$");
return new AddressRule(null, pattern, port, partial);
Pattern pattern = Pattern.compile( "^\\Q" + filter.replaceAll( "\\*", "\\\\E.*\\\\Q" ) + "\\E$", Pattern.CASE_INSENSITIVE );
return new AddressRule( new DomainPattern( pattern ), port, partial );
}
}
public static Options apply(Iterable<? extends AddressRule> rules, String domain, InetSocketAddress address) {
public static Options apply( Iterable<? extends AddressRule> rules, String domain, InetSocketAddress socketAddress ) {
PartialOptions options = null;
boolean hasMany = false;
int port = socketAddress.getPort();
InetAddress address = socketAddress.getAddress();
Inet4Address ipv4Address = address instanceof Inet6Address && InetAddresses.is6to4Address( (Inet6Address) address )
? InetAddresses.get6to4IPv4Address( (Inet6Address) address ) : null;
for (AddressRule rule : rules) {
if (!rule.matches(domain, address)) {
continue;
}
if( !rule.matches( domain, port, address, ipv4Address ) ) continue;
if (options == null) {
options = rule.partial;
@@ -116,65 +88,16 @@ public final class AddressRule {
/**
* Determine whether the given address matches a series of patterns.
*
* @param domain The domain to match
* @param socketAddress The address to check.
* @param domain The domain to match
* @param port The port of the address.
* @param address The address to check.
* @param ipv4Address An ipv4 version of the address, if the original was an ipv6 address.
* @return Whether it matches any of these patterns.
*/
private boolean matches(String domain, InetSocketAddress socketAddress) {
InetAddress address = socketAddress.getAddress();
if( port != null && port != socketAddress.getPort() ) return false;
if (this.domainPattern != null) {
if (this.domainPattern.matcher(domain)
.matches()) {
return true;
}
if (this.domainPattern.matcher(address.getHostName())
.matches()) {
return true;
}
}
// Match the normal address
if (this.matchesAddress(address)) {
return true;
}
// If we're an IPv4 address in disguise then let's check that.
return address instanceof Inet6Address && InetAddresses.is6to4Address((Inet6Address) address) && this.matchesAddress(InetAddresses.get6to4IPv4Address((Inet6Address) address));
}
private boolean matchesAddress(InetAddress address) {
if (this.domainPattern != null && this.domainPattern.matcher(address.getHostAddress())
.matches()) {
return true;
}
return this.ip != null && this.ip.contains(address);
}
private static final class HostRange {
private final byte[] min;
private final byte[] max;
private HostRange(byte[] min, byte[] max) {
this.min = min;
this.max = max;
}
public boolean contains(InetAddress address) {
byte[] entry = address.getAddress();
if (entry.length != this.min.length) {
return false;
}
for (int i = 0; i < entry.length; i++) {
int value = 0xFF & entry[i];
if (value < (0xFF & this.min[i]) || value > (0xFF & this.max[i])) {
return false;
}
}
return true;
}
private boolean matches(String domain, int port, InetAddress address, Inet4Address ipv4Address) {
if( this.port != null && this.port != port ) return false;
return predicate.matches( domain )
|| predicate.matches( address )
|| (ipv4Address != null && predicate.matches( ipv4Address ));
}
}

View File

@@ -8,7 +8,6 @@ package dan200.computercraft.core.apis.http.options;
import com.electronwill.nightconfig.core.CommentedConfig;
import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.InMemoryCommentedFormat;
import com.electronwill.nightconfig.core.UnmodifiableConfig;
import dan200.computercraft.ComputerCraft;

View File

@@ -9,10 +9,8 @@ package dan200.computercraft.core.apis.http.request;
import static dan200.computercraft.core.apis.http.request.HttpRequest.getHeaderSize;
import java.io.Closeable;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
@@ -228,8 +226,8 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
}
try {
return this.uri.resolve(new URI(URLDecoder.decode(location, "UTF-8")));
} catch (UnsupportedEncodingException | IllegalArgumentException | URISyntaxException e) {
return this.uri.resolve(new URI( location ));
} catch( IllegalArgumentException | URISyntaxException e ) {
return null;
}
}

View File

@@ -76,7 +76,7 @@ public class FileSystem {
this.mounts.put(location, wrapper);
}
private static String sanitizePath(String path, boolean allowWildcards) {
public static String sanitizePath(String path, boolean allowWildcards) {
// Allow windowsy slashes
path = path.replace('\\', '/');
@@ -231,7 +231,7 @@ public class FileSystem {
this.mounts.remove(sanitizePath(path));
}
public synchronized String combine(String path, String childPath) {
public String combine( String path, String childPath ) {
path = sanitizePath(path, true);
childPath = sanitizePath(childPath, true);

View File

@@ -59,7 +59,8 @@ public final class PocketUpgrades {
for (IPocketUpgrade upgrade : upgrades.values()) {
ItemStack craftingStack = upgrade.getCraftingItem();
if (!craftingStack.isEmpty() && InventoryUtil.areItemsSimilar(stack, craftingStack)) {
if( !craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && upgrade.isItemSuitable( stack ) )
{
return upgrade;
}
}

View File

@@ -19,7 +19,6 @@ import javax.annotation.Nullable;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.util.InventoryUtil;
import net.minecraft.item.ItemStack;
@@ -96,7 +95,8 @@ public final class TurtleUpgrades {
}
ItemStack craftingStack = wrapper.upgrade.getCraftingItem();
if (!craftingStack.isEmpty() && InventoryUtil.areItemsSimilar(stack, craftingStack)) {
if( !craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade.isItemSuitable( stack ) )
{
return wrapper.upgrade;
}
}

View File

@@ -36,8 +36,6 @@ import java.util.Optional;
import static dan200.computercraft.shared.peripheral.generic.methods.ArgumentHelpers.assertBetween;
/**
* Methods for interacting with inventories.
*

View File

@@ -218,7 +218,10 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile {
// region Sizing and placement stuff
public Direction getDirection() {
return this.getCachedState().get(BlockMonitor.FACING);
// Ensure we're actually a monitor block. This _should_ always be the case, but sometimes there's
// fun problems with the block being missing on the client.
BlockState state = getCachedState();
return state.contains( BlockMonitor.FACING ) ? state.get( BlockMonitor.FACING ) : Direction.NORTH;
}
public Direction getOrientation() {

View File

@@ -66,6 +66,9 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
}
public static ServerComputer getServerComputer(@Nonnull ItemStack stack) {
int session = getSessionID( stack );
if( session != ComputerCraft.serverComputerRegistry.getSessionID() ) return null;
int instanceID = getInstanceID(stack);
return instanceID >= 0 ? ComputerCraft.serverComputerRegistry.get(instanceID) : null;
}

View File

@@ -505,7 +505,7 @@ public class TurtleAPI implements ILuaAPI {
}
/**
* Get the currently sleected slot.
* Get the currently selected slot.
*
* @return The current slot.
* @see #select

View File

@@ -163,6 +163,10 @@ public class TurtleBrain implements ITurtleAccess {
if (!world.isClient) {
// Advance movement
this.updateCommands();
// The block may have been broken while the command was executing (for instance, if a block explodes
// when being mined). If so, abort.
if( m_owner.isRemoved() ) return;
}
// Advance animation

View File

@@ -11,6 +11,8 @@ import java.util.function.Function;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.tuple.Pair;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.AbstractTurtleUpgrade;
@@ -23,13 +25,12 @@ import dan200.computercraft.api.turtle.event.TurtleAttackEvent;
import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
import dan200.computercraft.api.turtle.event.TurtleEvent;
import dan200.computercraft.shared.TurtlePermissions;
import dan200.computercraft.shared.turtle.core.TurtleBrain;
import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand;
import dan200.computercraft.shared.turtle.core.TurtlePlayer;
import dan200.computercraft.shared.util.DropConsumer;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.WorldUtil;
import org.apache.commons.lang3.tuple.Pair;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
@@ -40,9 +41,9 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.decoration.ArmorStandEntity;
import net.minecraft.fluid.FluidState;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.Identifier;
@@ -58,6 +59,9 @@ import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
public class TurtleTool extends AbstractTurtleUpgrade {
protected final ItemStack item;
private static final int TAG_LIST = 9;
private static final int TAG_COMPOUND = 10;
public TurtleTool(Identifier id, String adjective, Item item) {
super(id, TurtleUpgradeType.TOOL, adjective, item);
this.item = new ItemStack(item);
@@ -73,6 +77,24 @@ public class TurtleTool extends AbstractTurtleUpgrade {
this.item = toolItem;
}
@Override
public boolean isItemSuitable( @Nonnull ItemStack stack )
{
CompoundTag tag = stack.getTag();
if( tag == null || tag.isEmpty() ) return true;
// Check we've not got anything vaguely interesting on the item. We allow other mods to add their
// own NBT, with the understanding such details will be lost to the mist of time.
if( stack.isDamaged() || stack.hasEnchantments() || stack.hasCustomName() ) return false;
if( tag.contains( "AttributeModifiers", TAG_LIST ) &&
!tag.getList( "AttributeModifiers", TAG_COMPOUND ).isEmpty() )
{
return false;
}
return true;
}
@Nonnull
@Override
public TurtleCommandResult useTool(@Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side, @Nonnull TurtleVerb verb, @Nonnull Direction direction) {
@@ -94,10 +116,13 @@ public class TurtleTool extends AbstractTurtleUpgrade {
return TransformedModel.of(this.getCraftingItem(), new AffineTransformation(new Vector3f(xOffset + 1, 0, 1), Vector3f.POSITIVE_Y.getDegreesQuaternion(270), new Vector3f(1, 1, 1), Vector3f.POSITIVE_Z.getDegreesQuaternion(90)));
}
private TurtleCommandResult attack(final ITurtleAccess turtle, Direction direction, TurtleSide side) {
private TurtleCommandResult attack( ITurtleAccess turtle, Direction direction, TurtleSide side ) {
// Create a fake player, and orient it appropriately
final World world = turtle.getWorld();
final BlockPos position = turtle.getPosition();
World world = turtle.getWorld();
BlockPos position = turtle.getPosition();
BlockEntity turtleBlock = turtle instanceof TurtleBrain ? ((TurtleBrain) turtle).getOwner() : world.getBlockEntity( position );
if( turtleBlock == null ) return TurtleCommandResult.failure( "Turtle has vanished from existence." );
final TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer(turtle, position, direction);
// See if there is an entity present
@@ -105,7 +130,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
Vec3d rayDir = turtlePlayer.getRotationVec(1.0f);
Pair<Entity, Vec3d> hit = WorldUtil.rayTraceEntities(world, turtlePos, rayDir, 1.5);
if (hit != null) {
// Load up the turtle's inventory
// Load up the turtle's inventoryf
ItemStack stackCopy = this.item.copy();
turtlePlayer.loadInventory(stackCopy);
@@ -127,7 +152,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
}
// Start claiming entity drops
DropConsumer.set(hitEntity, turtleDropConsumer(turtle));
DropConsumer.set( hitEntity, turtleDropConsumer( turtleBlock, turtle ) );
// Attack the entity
boolean attacked = false;
@@ -152,7 +177,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
}
// Stop claiming drops
stopConsuming(turtle);
stopConsuming( turtleBlock, turtle );
// Put everything we collected into the turtles inventory, then return
if (attacked) {
@@ -168,6 +193,10 @@ public class TurtleTool extends AbstractTurtleUpgrade {
// Get ready to dig
World world = turtle.getWorld();
BlockPos turtlePosition = turtle.getPosition();
BlockEntity turtleBlock = turtle instanceof TurtleBrain ? ((TurtleBrain) turtle).getOwner() : world.getBlockEntity( turtlePosition );
if( turtleBlock == null ) return TurtleCommandResult.failure( "Turtle has vanished from existence." );
BlockPos blockPosition = turtlePosition.offset(direction);
if (world.isAir(blockPosition) || WorldUtil.isLiquidBlock(world, blockPosition)) {
@@ -175,7 +204,6 @@ public class TurtleTool extends AbstractTurtleUpgrade {
}
BlockState state = world.getBlockState(blockPosition);
FluidState fluidState = world.getFluidState(blockPosition);
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer(turtle, turtlePosition, direction);
turtlePlayer.loadInventory(this.item.copy());
@@ -198,7 +226,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
}
// Consume the items the block drops
DropConsumer.set(world, blockPosition, turtleDropConsumer(turtle));
DropConsumer.set( world, blockPosition, turtleDropConsumer( turtleBlock, turtle ) );
BlockEntity tile = world.getBlockEntity(blockPosition);
@@ -220,28 +248,28 @@ public class TurtleTool extends AbstractTurtleUpgrade {
}
}
stopConsuming(turtle);
stopConsuming(turtleBlock, turtle);
return TurtleCommandResult.success();
}
private static Function<ItemStack, ItemStack> turtleDropConsumer(ITurtleAccess turtle) {
return drop -> InventoryUtil.storeItems(drop, turtle.getItemHandler(), turtle.getSelectedSlot());
private static Function<ItemStack, ItemStack> turtleDropConsumer(BlockEntity turtleBlock, ITurtleAccess turtle) {
return drop -> turtleBlock.isRemoved() ? drop : InventoryUtil.storeItems(drop, turtle.getItemHandler(), turtle.getSelectedSlot());
}
protected float getDamageMultiplier() {
return 3.0f;
}
private static void stopConsuming(ITurtleAccess turtle) {
private static void stopConsuming(BlockEntity turtleBlock, ITurtleAccess turtle) {
Direction direction = turtleBlock.isRemoved() ? null : turtle.getDirection().getOpposite();
List<ItemStack> extra = DropConsumer.clear();
for (ItemStack remainder : extra) {
WorldUtil.dropItemStack(remainder,
turtle.getWorld(),
turtle.getPosition(),
turtle.getDirection()
.getOpposite());
direction);
}
}

View File

@@ -3,7 +3,10 @@ package dan200.computercraft.shared.util;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import blue.endless.jankson.Comment;
import blue.endless.jankson.Jankson;
@@ -13,6 +16,8 @@ import com.google.common.base.CaseFormat;
import com.google.common.base.Converter;
import dan200.computercraft.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.apis.http.websocket.Websocket;
public class Config {
@@ -83,7 +88,13 @@ public class Config {
// HTTP
ComputerCraft.http_enable = config.http.enabled;
ComputerCraft.http_websocket_enable = config.http.websocket_enabled;
ComputerCraft.httpRules = ComputerCraft.buildHttpRulesFromConfig(config.http.blacklist, config.http.whitelist);
ComputerCraft.httpRules = Stream.concat(Stream.of(config.http.blacklist)
.map( x -> AddressRule.parse( x, null, Action.DENY.toPartial()))
.filter(Objects::nonNull),
Stream.of(config.http.whitelist)
.map( x -> AddressRule.parse( x, null, Action.ALLOW.toPartial()))
.filter(Objects::nonNull))
.collect(Collectors.toList());
ComputerCraft.httpTimeout = Math.max(0, config.http.timeout);
ComputerCraft.httpMaxRequests = Math.max(1, config.http.max_requests);
@@ -158,9 +169,9 @@ public class Config {
ComputerCraft.http_websocket_enable;
@Comment ("\nA list of wildcards for domains or IP ranges that can be accessed through the " + "\"http\" API on Computers.\n" + "Set this to " +
"\"*\" to access to the entire internet. Example: \"*.pastebin.com\" will restrict access to " + "just subdomains of pastebin.com.\n" + "You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\").") public String[] whitelist = ComputerCraft.DEFAULT_HTTP_WHITELIST.clone();
"\"*\" to access to the entire internet. Example: \"*.pastebin.com\" will restrict access to " + "just subdomains of pastebin.com.\n" + "You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\").") public String[] whitelist = new String[] {"*"};
@Comment ("\nA list of wildcards for domains or IP ranges that cannot be accessed through the " + "\"http\" API on Computers.\n" + "If this is " + "empty then all whitelisted domains will be accessible. Example: \"*.github.com\" will block " + "access to all subdomains of github" + ".com.\n" + "You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\").") public String[] blacklist = ComputerCraft.DEFAULT_HTTP_BLACKLIST.clone();
@Comment ("\nA list of wildcards for domains or IP ranges that cannot be accessed through the " + "\"http\" API on Computers.\n" + "If this is " + "empty then all whitelisted domains will be accessible. Example: \"*.github.com\" will block " + "access to all subdomains of github" + ".com.\n" + "You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\").") public String[] blacklist = new String[] {"$private"};
@Comment ("\nThe period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited.") public int timeout =
ComputerCraft.httpTimeout;

View File

@@ -14,7 +14,6 @@ import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
@@ -28,53 +27,6 @@ public final class InventoryUtil {
return a == b || (a.getItem() == b.getItem() && ItemStack.areTagsEqual(a, b));
}
/**
* Determines if two items are "mostly" equivalent. Namely, they have the same item and damage, and identical share stacks.
*
* sufficient to ensure basic information (such as enchantments) are the same, while not having to worry about capabilities.
*
* @param a The first stack to check
* @param b The second stack to check
* @return If these items are largely the same.
*/
public static boolean areItemsSimilar(@Nonnull ItemStack a, @Nonnull ItemStack b) {
if (a == b) {
return true;
}
if (a.isEmpty()) {
return !b.isEmpty();
}
if (a.getItem() != b.getItem()) {
return false;
}
// A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
// null one.
CompoundTag shareTagA = a.getTag();
CompoundTag shareTagB = b.getTag();
if (shareTagA == shareTagB) {
return true;
}
if (shareTagA == null) {
return shareTagB.isEmpty();
}
if (shareTagB == null) {
return shareTagA.isEmpty();
}
return shareTagA.equals(shareTagB);
}
@Nonnull
public static ItemStack copyItem(@Nonnull ItemStack a) {
return a.copy();
}
public static ItemStorage getStorage(World world, BlockPos pos, Direction side) {
Inventory inventory = getInventory(world, pos, side);
return inventory == null ? null : ItemStorage.wrap(inventory, side);
}
// Methods for finding inventories:
public static Inventory getInventory(World world, BlockPos pos, Direction side) {

View File

@@ -338,8 +338,8 @@ function read(_sReplaceChar, _tHistory, _fnComplete, _sDefault)
redraw()
elseif sEvent == "key" then
if param == keys.enter then
-- Enter
if param == keys.enter or param == keys.numPadEnter then
-- Enter/Numpad Enter
if nCompletion then
clear()
uncomplete()
@@ -523,6 +523,16 @@ function os.run(_tEnv, _sPath, ...)
local tEnv = _tEnv
setmetatable(tEnv, { __index = _G })
if settings.get("bios.strict_globals", false) then
-- load will attempt to set _ENV on this environment, which
-- throws an error with this protection enabled. Thus we set it here first.
tEnv._ENV = tEnv
getmetatable(tEnv).__newindex = function(_, name)
error("Attempt to create global " .. tostring(name), 2)
end
end
local fnFile, err = loadfile(_sPath, nil, tEnv)
if fnFile then
local ok, err = pcall(fnFile, ...)
@@ -954,6 +964,11 @@ settings.define("lua.function_source", {
description = "Show where a function was defined when printing functions.",
type = "boolean",
})
settings.define("bios.strict_globals", {
default = false,
description = "Prevents assigning variables into a program's environment. Make sure you use the local keyword or assign to _G explicitly.",
type = "boolean",
})
if term.isColour() then
settings.define("bios.use_multishell", {

View File

@@ -64,6 +64,10 @@ end
--
-- @treturn table|nil The parsed image data, suitable for use with
-- @{paintutils.drawImage}, or `nil` if the file does not exist.
-- @usage Load an image and draw it.
--
-- local image = paintutils.loadImage("test-image.nfp")
-- paintutils.drawImage(image, term.getCursorPos())
function loadImage(path)
expect(1, path, "string")
@@ -107,6 +111,7 @@ end
-- @tparam number endY The end y position of the line.
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
-- the current background colour if not specified.
-- @usage paintutils.drawLine(2, 3, 30, 7, colors.red)
function drawLine(startX, startY, endX, endY, colour)
expect(1, startX, "number")
expect(2, startY, "number")
@@ -170,6 +175,7 @@ end
-- @tparam number endY The end y position of the line.
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
-- the current background colour if not specified.
-- @usage paintutils.drawBox(2, 3, 30, 7, colors.red)
function drawBox(startX, startY, endX, endY, nColour)
expect(1, startX, "number")
expect(2, startY, "number")
@@ -222,6 +228,7 @@ end
-- @tparam number endY The end y position of the line.
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
-- the current background colour if not specified.
-- @usage paintutils.drawFilledBox(2, 3, 30, 7, colors.red)
function drawFilledBox(startX, startY, endX, endY, nColour)
expect(1, startX, "number")
expect(2, startY, "number")

View File

@@ -1,18 +1,19 @@
--- Provides a simple implementation of multitasking.
--
-- Functions are not actually executed simultaniously, but rather this API will
-- automatically switch between them whenever they yield (eg whenever they call
-- @{coroutine.yield}, or functions that call that - eg `os.pullEvent` - or
-- functions that call that, etc - basically, anything that causes the function
-- to "pause").
--
-- Each function executed in "parallel" gets its own copy of the event queue,
-- and so "event consuming" functions (again, mostly anything that causes the
-- script to pause - eg `sleep`, `rednet.receive`, most of the `turtle` API,
-- etc) can safely be used in one without affecting the event queue accessed by
-- the other.
--
-- @module parallel
--[[- Provides a simple implementation of multitasking.
Functions are not actually executed simultaniously, but rather this API will
automatically switch between them whenever they yield (eg whenever they call
@{coroutine.yield}, or functions that call that - eg `os.pullEvent` - or
functions that call that, etc - basically, anything that causes the function
to "pause").
Each function executed in "parallel" gets its own copy of the event queue,
and so "event consuming" functions (again, mostly anything that causes the
script to pause - eg `sleep`, `rednet.receive`, most of the `turtle` API,
etc) can safely be used in one without affecting the event queue accessed by
the other.
@module parallel
]]
local function create(...)
local tFns = table.pack(...)
@@ -70,21 +71,53 @@ local function runUntilLimit(_routines, _limit)
end
end
--- Switches between execution of the functions, until any of them
-- finishes. If any of the functions errors, the message is propagated upwards
-- from the @{parallel.waitForAny} call.
--
-- @tparam function ... The functions this task will run
--[[- Switches between execution of the functions, until any of them
finishes. If any of the functions errors, the message is propagated upwards
from the @{parallel.waitForAny} call.
@tparam function ... The functions this task will run
@usage Print a message every second until the `q` key is pressed.
local function tick()
while true do
os.sleep(1)
print("Tick")
end
end
local function wait_for_q()
repeat
local _, key = os.pullEvent("key")
until key == keys.q
print("Q was pressed!")
end
parallel.waitForAny(tick, wait_for_q)
print("Everything done!")
]]
function waitForAny(...)
local routines = create(...)
return runUntilLimit(routines, #routines - 1)
end
--- Switches between execution of the functions, until all of them are
-- finished. If any of the functions errors, the message is propagated upwards
-- from the @{parallel.waitForAll} call.
--
-- @tparam function ... The functions this task will run
--[[- Switches between execution of the functions, until all of them are
finished. If any of the functions errors, the message is propagated upwards
from the @{parallel.waitForAll} call.
@tparam function ... The functions this task will run
@usage Start off two timers and wait for them both to run.
local function a()
os.sleep(1)
print("A is done")
end
local function b()
os.sleep(3)
print("B is done")
end
parallel.waitForAll(a, b)
print("Everything done!")
]]
function waitForAll(...)
local routines = create(...)
return runUntilLimit(routines, 0)

View File

@@ -163,10 +163,11 @@ local function tabulateCommon(bPaged, ...)
for n, t in ipairs(tAll) do
if type(t) == "table" then
for nu, sItem in pairs(t) do
if type(sItem) ~= "string" then
error("bad argument #" .. n .. "." .. nu .. " (expected string, got " .. type(sItem) .. ")", 3)
local ty = type(sItem)
if ty ~= "string" and ty ~= "number" then
error("bad argument #" .. n .. "." .. nu .. " (expected string, got " .. ty .. ")", 3)
end
nMaxLen = math.max(#sItem + 1, nMaxLen)
nMaxLen = math.max(#tostring(sItem) + 1, nMaxLen)
end
end
end

View File

@@ -1,3 +1,63 @@
# New features in CC: Restitched 1.95.0
* Optimise the paint program's initial render.
* Several documentation improvments (Gibbo3771, MCJack123).
* `fs.combine` now accepts multiple arguments.
* Add a setting (`bios.strict_globals`) to error when accidentally declaring a global. (Lupus590).
* Add an improved help viewer which allows scrolling up and down (MCJack123).
* Add `cc.strings` module, with utilities for wrapping text (Lupus590).
* The `clear` program now allows resetting the palette too (Luca0208).
And several bug fixes:
* Fix memory leak in generic peripherals.
* Fix crash when a turtle is broken while being ticked.
* `textutils.*tabulate` now accepts strings _or_ numbers.
* We now deny _all_ local IPs, using the magic `$private` host. Previously the IPv6 loopback interface was not blocked.
* Fix crash when rendering monitors if the block has not yet been synced. You will need to regenerate the config file to apply this change.
* `read` now supports numpad enter (TheWireLord)
* Correctly handle HTTP redirects to URLs containing escape characters.
* Fix integer overflow in `os.epoch`.
* Allow using pickaxes (and other items) for turtle upgrades which have mod-specific NBT.
* Fix duplicate turtle/pocket upgrade recipes appearing in JEI.
# New features in CC: Tweaked 1.94.0
* Add getter for window visibility (devomaa)
* Generic peripherals are no longer experimental, and on by default.
* Use term.blit to draw boxes in paintutils (Lemmmy).
And several bug fixes:
* Fix turtles not getting advancements when turtles are on.
* Draw in-hand pocket computers with the correct transparent flags enabled.
* Several bug fixes to SNBT parsing.
* Fix several programs using their original name instead of aliases in usage hints (Lupus590).
# New features in CC: Tweaked 1.93.1
* Various documentation improvements (Lemmmy).
* Fix TBO monitor renderer on some older graphics cards (Lemmmy).
# New features in CC: Tweaked 1.93.0
* Update Swedish translations (Granddave).
* Printers use item tags to check dyes.
* HTTP rules may now be targetted for a specific port.
* Don't propagate adjacent redstone signals through computers.
And several bug fixes:
* Fix NPEs when turtles interact with containers.
# New features in CC: Tweaked 1.92.0
* Bump Cobalt version:
* Add support for the __pairs metamethod.
* string.format now uses the __tostring metamethod.
* Add date-specific MOTDs (MCJack123).
And several bug fixes:
* Correctly handle tabs within textutils.unserailizeJSON.
* Fix sheep not dropping items when sheared by turtles.
# New features in CC: Tweaked 1.91.0
* [Generic peripherals] Expose NBT hashes of items to inventory methods.

View File

@@ -1 +1,6 @@
clear clears the screen.
clear clears the screen and/or resets the palette.
ex:
"clear" clears the screen, but keeps the palette.
"clear screen" does the same as "clear"
"clear palette" resets the palette, but doesn't clear the screen
"clear all" clears the screen and resets the palette

View File

@@ -1,15 +1,23 @@
New features in CC: Tweaked 1.91.0
New features in CC: Restitched 1.95.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)
* Update to 1.16.2
* Optimise the paint program's initial render.
* Several documentation improvments (Gibbo3771, MCJack123).
* `fs.combine` now accepts multiple arguments.
* Add a setting (`bios.strict_globals`) to error when accidentally declaring a global. (Lupus590).
* Add an improved help viewer which allows scrolling up and down (MCJack123).
* Add `cc.strings` module, with utilities for wrapping text (Lupus590).
* The `clear` program now allows resetting the palette too (Luca0208).
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)
* Fix turtles placing buckets multiple times.
* Fix memory leak in generic peripherals.
* Fix crash when a turtle is broken while being ticked.
* `textutils.*tabulate` now accepts strings _or_ numbers.
* We now deny _all_ local IPs, using the magic `$private` host. Previously the IPv6 loopback interface was not blocked.
* Fix crash when rendering monitors if the block has not yet been synced. You will need to regenerate the config file to apply this change.
* `read` now supports numpad enter (TheWireLord)
* Correctly handle HTTP redirects to URLs containing escape characters.
* Fix integer overflow in `os.epoch`.
* Allow using pickaxes (and other items) for turtle upgrades which have mod-specific NBT.
* Fix duplicate turtle/pocket upgrade recipes appearing in JEI.
Type "help changelog" to see the full version history.

View File

@@ -45,7 +45,9 @@ end
-- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed name.
-- @treturn { string... } A list of suffixes of matching peripherals.
-- @usage read(nil, nil, peripheral)
-- @usage
-- local completion = require "cc.completion"
-- read(nil, nil, completion.peripheral)
local function peripheral_(text, add_space)
expect(1, text, "string")
expect(2, add_space, "boolean", "nil")
@@ -59,7 +61,9 @@ local sides = redstone.getSides()
-- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed side.
-- @treturn { string... } A list of suffixes of matching sides.
-- @usage read(nil, nil, side)
-- @usage
-- local completion = require "cc.completion"
-- read(nil, nil, completion.side)
local function side(text, add_space)
expect(1, text, "string")
expect(2, add_space, "boolean", "nil")
@@ -71,7 +75,9 @@ end
-- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed settings.
-- @treturn { string... } A list of suffixes of matching settings.
-- @usage read(nil, nil, setting)
-- @usage
-- local completion = require "cc.completion"
-- read(nil, nil, completion.setting)
local function setting(text, add_space)
expect(1, text, "string")
expect(2, add_space, "boolean", "nil")
@@ -85,7 +91,9 @@ local command_list
-- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed command.
-- @treturn { string... } A list of suffixes of matching commands.
-- @usage read(nil, nil, command)
-- @usage
-- local completion = require "cc.completion"
-- read(nil, nil, completion.command)
local function command(text, add_space)
expect(1, text, "string")
expect(2, add_space, "boolean", "nil")

View File

@@ -1,7 +1,26 @@
--- The @{cc.expect} library provides helper functions for verifying that
-- function arguments are well-formed and of the correct type.
--
-- @module cc.expect
--[[- The @{cc.expect} library provides helper functions for verifying that
function arguments are well-formed and of the correct type.
@module cc.expect
@usage Define a basic function and check it has the correct arguments.
local expect = require "cc.expect"
local expect, field = expect.expect, expect.field
local function add_person(name, info)
expect(1, name, "string")
expect(2, info, "table", "nil")
if info then
print("Got age=", field(info, "age", "number"))
print("Got gender=", field(info, "gender", "string", "nil"))
end
end
add_person("Anastazja") -- `info' is optional
add_person("Kion", { age = 23 }) -- `gender' is optional
add_person("Caoimhin", { age = 23, gender = true }) -- error!
]]
local native_select, native_type = select, type
@@ -34,7 +53,7 @@ local function expect(index, value, ...)
local name
if native_type(debug) == "table" and native_type(debug.getinfo) == "function" then
local ok, info = pcall(debug.getinfo, 3, "nS")
if ok and info.name and #info.name ~= "" and info.what ~= "C" then name = info.name end
if ok and info.name and info.name ~= "" and info.what ~= "C" then name = info.name end
end
local type_names = get_type_names(...)

View File

@@ -67,6 +67,9 @@ end
-- @tparam[opt] number colour The colour this text should be printed with. If not given, we default to the current
-- colour.
-- @treturn Doc The document with the provided text.
-- @usage Write some blue text.
-- local pretty = require "cc.pretty"
-- pretty.print(pretty.text("Hello!", colours.blue))
local function text(text, colour)
expect(1, text, "string")
expect(2, colour, "number", "nil")
@@ -101,8 +104,11 @@ end
--
-- @tparam Doc|string ... The documents to concatenate.
-- @treturn Doc The concatenated documents.
-- @usage pretty.concat(doc1, " - ", doc2)
-- @usage doc1 .. " - " .. doc2
-- @usage
-- local pretty = require "cc.pretty"
-- local doc1, doc2 = pretty.text("doc1"), pretty.text("doc2")
-- print(pretty.concat(doc1, " - ", doc2))
-- print(doc1 .. " - " .. doc2) -- Also supports ..
local function concat(...)
local args = table.pack(...)
for i = 1, args.n do
@@ -135,7 +141,9 @@ Doc.__concat = concat --- @local
-- @tparam number depth The number of spaces with which the document should be indented.
-- @tparam Doc doc The document to indent.
-- @treturn Doc The nested document.
-- @usage pretty.nest(2, pretty.text("foo\nbar"))
-- @usage
-- local pretty = require "cc.pretty"
-- print(pretty.nest(2, pretty.text("foo\nbar")))
local function nest(depth, doc)
expect(1, depth, "number")
if getmetatable(doc) ~= Doc then expect(2, doc, "document") end
@@ -169,6 +177,12 @@ end
--
-- @tparam Doc doc The document to group.
-- @treturn Doc The grouped document.
-- @usage Uses group to show things being displayed on one or multiple lines.
--
-- local pretty = require "cc.pretty"
-- local doc = pretty.group("Hello" .. pretty.space_line .. "World")
-- print(pretty.render(doc, 5)) -- On multiple lines
-- print(pretty.render(doc, 20)) -- Collapsed onto one.
local function group(doc)
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end

View File

@@ -9,8 +9,8 @@
-- @usage Construct the package and require function, and insert them into a
-- custom environment.
--
-- local env = setmetatable({}, { __index = _ENV })
-- local r = require "cc.require"
-- local env = setmetatable({}, { __index = _ENV })
-- env.require, env.package = r.make(env, "/")
local expect = require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua")

View File

@@ -1,15 +1,28 @@
--- A collection of helper methods for working with shell completion.
--
-- Most programs may be completed using the @{build} helper method, rather than
-- manually switching on the argument index.
--
-- Note, the helper functions within this module do not accept an argument index,
-- and so are not directly usable with the @{shell.setCompletionFunction}. Instead,
-- wrap them using @{build}, or your own custom function.
--
-- @module cc.shell.completion
-- @see cc.completion For more general helpers, suitable for use with @{_G.read}.
-- @see shell.setCompletionFunction
--[[- A collection of helper methods for working with shell completion.
Most programs may be completed using the @{build} helper method, rather than
manually switching on the argument index.
Note, the helper functions within this module do not accept an argument index,
and so are not directly usable with the @{shell.setCompletionFunction}. Instead,
wrap them using @{build}, or your own custom function.
@module cc.shell.completion
@see cc.completion For more general helpers, suitable for use with @{_G.read}.
@see shell.setCompletionFunction
@usage Register a completion handler for example.lua which prompts for a
choice of options, followed by a directory, and then multiple files.
local completion = require "cc.shell.completion"
local complete = completion.build(
{ completion.choice, { "get", "put" } },
completion.dir,
{ completion.file, many = true }
)
shell.setCompletionFunction("example.lua", complete)
read(nil, nil, shell.complete, "example ")
]]
local expect = require "cc.expect".expect
local completion = require "cc.completion"
@@ -69,37 +82,29 @@ local function program(shell, text)
return shell.completeProgram(text)
end
--- A helper function for building shell completion arguments.
--
-- This accepts a series of single-argument completion functions, and combines
-- them into a function suitable for use with @{shell.setCompletionFunction}.
--
-- @tparam nil|table|function ... Every argument to @{build} represents an argument
-- to the program you wish to complete. Each argument can be one of three types:
--
-- - `nil`: This argument will not be completed.
--
-- - A function: This argument will be completed with the given function. It is
-- called with the @{shell} object, the string to complete and the arguments
-- before this one.
--
-- - A table: This acts as a more powerful version of the function case. The table
-- must have a function as the first item - this will be called with the shell,
-- string and preceding arguments as above, but also followed by any additional
-- items in the table. This provides a more convenient interface to pass
-- options to your completion functions.
--
-- If this table is the last argument, it may also set the `many` key to true,
-- which states this function should be used to complete any remaining arguments.
--
-- @usage Prompt for a choice of options, followed by a directory, and then multiple
-- files.
--
-- complete.build(
-- { complete.choice, { "get", "put" } },
-- complete.dir,
-- { complete.file, many = true }
-- )
--[[- A helper function for building shell completion arguments.
This accepts a series of single-argument completion functions, and combines
them into a function suitable for use with @{shell.setCompletionFunction}.
@tparam nil|table|function ... Every argument to @{build} represents an argument
to the program you wish to complete. Each argument can be one of three types:
- `nil`: This argument will not be completed.
- A function: This argument will be completed with the given function. It is
called with the @{shell} object, the string to complete and the arguments
before this one.
- A table: This acts as a more powerful version of the function case. The table
must have a function as the first item - this will be called with the shell,
string and preceding arguments as above, but also followed by any additional
items in the table. This provides a more convenient interface to pass
options to your completion functions.
If this table is the last argument, it may also set the `many` key to true,
which states this function should be used to complete any remaining arguments.
]]
local function build(...)
local arguments = table.pack(...)
for i = 1, arguments.n do

View File

@@ -0,0 +1,102 @@
--- Various utilities for working with strings and text.
--
-- @see textutils For additional string related utilities.
local expect = require "cc.expect".expect
--- Wraps a block of text, so that each line fits within the given width.
--
-- This may be useful if you want to wrap text before displaying it to a
-- @{monitor} or @{printer} without using @{_G.print|print}.
--
-- @tparam string text The string to wrap.
-- @tparam[opt] number width The width to constrain to, defaults to the width of
-- the terminal.
--
-- @treturn { string... } The wrapped input string.
-- @usage require "cc.strings".wrap("This is a long piece of text", 10)
local function wrap(text, width)
expect(1, text, "string")
expect(2, width, "number", "nil")
width = width or term.getSize()
local lines, lines_n, current_line = {}, 0, ""
local function push_line()
lines_n = lines_n + 1
lines[lines_n] = current_line
current_line = ""
end
local pos, length = 1, #text
local sub, match = string.sub, string.match
while pos <= length do
local head = sub(text, pos, pos)
if head == " " or head == "\t" then
local whitespace = match(text, "^[ \t]+", pos)
current_line = current_line .. whitespace
pos = pos + #whitespace
elseif head == "\n" then
push_line()
pos = pos + 1
else
local word = match(text, "^[^ \t\n]+", pos)
pos = pos + #word
if #word > width then
-- Print a multiline word
while #word > 0 do
local space_remaining = width - #current_line - 1
if space_remaining <= 0 then
push_line()
space_remaining = width
end
current_line = current_line .. sub(word, 1, space_remaining)
word = sub(word, space_remaining + 1)
end
else
-- Print a word normally
if width - #current_line < #word then push_line() end
current_line = current_line .. word
end
end
end
push_line()
-- Trim whitespace longer than width.
for k, line in pairs(lines) do
line = line:sub(1, width)
lines[k] = line
end
return lines
end
--- Makes the input string a fixed width. This either truncates it, or pads it
-- with spaces.
--
-- @tparam string line The string to normalise.
-- @tparam[opt] number width The width to constrain to, defaults to the width of
-- the terminal.
--
-- @treturn string The string with a specific width.
-- @usage require "cc.strings".ensure_width("a short string", 20)
-- @usage require "cc.strings".ensure_width("a rather long string which is truncated", 20)
local function ensure_width(line, width)
expect(1, line, "string")
expect(2, width, "number", "nil")
width = width or term.getSize()
line = line:sub(1, width)
if #line < width then
line = line .. (" "):rep(width - #line)
end
return line
end
return {
wrap = wrap,
ensure_width = ensure_width,
}

View File

@@ -1,2 +1,33 @@
term.clear()
term.setCursorPos(1, 1)
local tArgs = { ... }
local function printUsage()
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usages:")
print(programName)
print(programName .. " screen")
print(programName .. " palette")
print(programName .. " all")
end
local function clear()
term.clear()
term.setCursorPos(1, 1)
end
local function resetPalette()
for i = 0, 15 do
term.setPaletteColour(math.pow(2, i), term.nativePaletteColour(math.pow(2, i)))
end
end
local sCommand = tArgs[1] or "screen"
if sCommand == "screen" then
clear()
elseif sCommand == "palette" then
resetPalette()
elseif sCommand == "all" then
clear()
resetPalette()
else
printUsage()
end

View File

@@ -233,14 +233,31 @@ local function drawCanvasPixel(x, y)
end
end
local color_hex_lookup = {}
for i = 0, 15 do
color_hex_lookup[2 ^ i] = string.format("%x", i)
end
--[[
Converts each colour in a single line of the canvas and draws it
returns: nil
]]
local text, fg, bg = "", "", ""
local function drawCanvasLine(y)
for x = 1, w - 2 do
drawCanvasPixel(x, y)
local pixel = getCanvasPixel(x, y)
if pixel then
text = text .. " "
fg = fg .. "0"
bg = bg .. color_hex_lookup[pixel or canvasColour]
else
text = text .. "\127"
fg = fg .. color_hex_lookup[colours.grey]
bg = bg .. color_hex_lookup[canvasColour]
end
end
term.setCursorPos(1, y)
term.blit(text, fg, bg)
end
--[[

View File

@@ -13,14 +13,119 @@ if sTopic == "index" then
return
end
local strings = require "cc.strings"
local function word_wrap(text, width)
local lines = strings.wrap(text, width)
-- Normalise the strings suitable for use with blit. We could skip this and
-- just use term.write, but saves us a clearLine call.
for k, line in pairs(lines) do
lines[k] = strings.ensure_width(line, width)
end
return lines
end
local sFile = help.lookup(sTopic)
local file = sFile ~= nil and io.open(sFile) or nil
if file then
local sContents = file:read("*a")
file:close()
local _, nHeight = term.getSize()
textutils.pagedPrint(sContents, nHeight - 3)
else
print("No help available")
if not file then
printError("No help available")
return
end
local contents = file:read("*a"):gsub("(\n *)[-*]( +)", "%1\7%2")
file:close()
local width, height = term.getSize()
local lines = word_wrap(contents, width)
local print_height = #lines
-- If we fit within the screen, just display without pagination.
if print_height <= height then
print(contents)
return
end
local offset = 0
local function draw()
local fg, bg = ("0"):rep(width), ("f"):rep(width)
for y = 1, height - 1 do
term.setCursorPos(1, y)
if y + offset > print_height then
-- Should only happen if we resize the terminal to a larger one
-- than actually needed for the current text.
term.clearLine()
else
term.blit(lines[y + offset], fg, bg)
end
end
end
local function draw_menu()
term.setTextColor(colors.yellow)
term.setCursorPos(1, height)
term.clearLine()
local tag = "Help: " .. sTopic
term.write("Help: " .. sTopic)
if width >= #tag + 16 then
term.setCursorPos(width - 14, height)
term.write("Press Q to exit")
end
end
draw()
draw_menu()
while true do
local event, param = os.pullEvent()
if event == "key" then
if param == keys.up and offset > 0 then
offset = offset - 1
draw()
elseif param == keys.down and offset < print_height - height then
offset = offset + 1
draw()
elseif param == keys.pageUp and offset > 0 then
offset = math.max(offset - height + 2, 0)
draw()
elseif param == keys.pageDown and offset < print_height - height then
offset = math.min(offset + height - 2, print_height - height)
draw()
elseif param == keys.home then
offset = 0
draw()
elseif param == keys["end"] then
offset = print_height - height
draw()
elseif param == keys.q then
sleep(0) -- Super janky, but consumes stray "char" events.
break
end
elseif event == "mouse_scroll" then
if param < 0 and offset > 0 then
offset = offset - 1
draw()
elseif param > 0 and offset < print_height - height then
offset = offset + 1
draw()
end
elseif event == "term_resize" then
local new_width, new_height = term.getSize()
if new_width ~= width then
lines = word_wrap(contents, new_width)
print_height = #lines
end
width, height = new_width, new_height
offset = math.max(math.min(offset, print_height - height), 0)
draw()
draw_menu()
end
end
term.setCursorPos(1, 1)
term.clear()

View File

@@ -49,6 +49,7 @@ end
shell.setCompletionFunction("rom/programs/alias.lua", completion.build(nil, completion.program))
shell.setCompletionFunction("rom/programs/cd.lua", completion.build(completion.dir))
shell.setCompletionFunction("rom/programs/clear.lua", completion.build({ completion.choice, { "screen", "palette", "all" } }))
shell.setCompletionFunction("rom/programs/copy.lua", completion.build(
{ completion.dirOrFile, true },
completion.dirOrFile

View File

@@ -0,0 +1,41 @@
describe("cc.pretty", function()
local str = require("cc.strings")
describe("wrap", function()
it("validates arguments", function()
str.wrap("test string is long")
str.wrap("test string is long", 11)
expect.error(str.wrap, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(str.wrap, "", false):eq("bad argument #2 (expected number, got boolean)")
end)
it("wraps lines", function()
expect(str.wrap("test string is long")[1]):eq("test string is long")
expect(str.wrap("test string is long", 15)[1]):eq("test string is ")
expect(str.wrap("test string is long", 15)[2]):eq("long")
expect(str.wrap("test string is long", 12)[1]):eq("test string ")
expect(str.wrap("test string is long", 12)[2]):eq("is long")
expect(str.wrap("test string is long", 11)[1]):eq("test string")
expect(str.wrap("test string is long", 11)[2]):eq("is long")
end)
end)
describe("ensure_width", function()
it("validates arguments", function()
str.wrap("test string is long")
str.wrap("test string is long", 11)
expect.error(str.ensure_width, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(str.ensure_width, "", false):eq("bad argument #2 (expected number, got boolean)")
end)
it("pads lines", function()
expect(str.ensure_width("test string is long", 25)):eq("test string is long ")
end)
it("truncates lines", function()
expect(str.ensure_width("test string is long", 15)):eq("test string is ")
end)
end)
end)