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

Compare commits

..

42 Commits

Author SHA1 Message Date
Merith-TK
1a52275dcb release 1.96.0 2021-06-04 16:09:43 -07:00
Merith
2fc2cf2e29 Merge pull request #49 from Toad-Dev/fabric-fix-48
Fix crash while playing a music disk in a drive on server.
2021-06-04 14:29:21 -07:00
ToadDev
80c2fc68aa Fix crash playing a music disc from drive on server.
#48
2021-06-03 21:54:28 -07:00
ToadDev
7c0664b9f2 Fix mixin from latest PR getting left behind in refactor.
I refactored the mixin package to be within a fabric package so other
fabric specific code could be kept nearby. But a mixin from a recent PR
got left behind in the merge. Github's empty merge conflict might have
been about this.
2021-06-03 21:32:37 -07:00
Merith
4d1a950fbf Merge pull request #42 from Toad-Dev/merith-buffer-fix
Fix: Monitor render buffers not being released properly.
2021-06-03 15:28:23 -07:00
ToadDev
135a3f56a5 Refactor: Begin consolidating fabric only classes.
Made a top-level fabric package so mixins and their ancillary classes
can be grouped together. Also removed a stub duck interface that got
left behind somewhere along the line (MixedFirstPersonRenderer).
2021-06-03 13:48:34 -07:00
ToadDev
1f117b7c47 Fix: Inform block entities when chunk is unloaded.
Forge's ITileEntity interface adds a onChunkUnloaded handler method into
block entities. The fabric port doesn't have a re-implementation of this
feature, which meant TileMonitors weren't releasing their buffers when
unloaded. This commit adds that handler method back into TileGeneric,
which all CC block entities inherit from. Handler logic for the four
block entities that use this feature were copied over from the forge
repo.
2021-06-03 13:48:34 -07:00
ToadDev
c820e051b3 Fix: Release monitor buffers on *client* thread
These buffers were being released on the server thread, causing the
logical server in a single player session to crash just before shutting
down. It seems the crash happened late enough to not cause world
corruption issues, and so went unnoticed. It was however causing a
memory leak when quitting/rejoining worlds, and it left the save file
lock in limbo.
2021-06-03 13:48:34 -07:00
Merith
01d7aaf062 Merge pull request #38 from Jummit/port-10
Port all recent commits
2021-06-03 07:58:58 -07:00
Merith-TK
1e75e4ff6e update issue templates, remove travis.yml
issue templates needed a revamp,
2021-06-03 06:45:32 -07:00
Merith
f967a70121 Merge pull request #45 from hugeblank/disk-use
Fix Disk Drive & Disk Interaction
2021-06-02 19:24:25 -07:00
Merith
ac5150a664 Merge pull request #46 from hugeblank/implement-details
Properly implement turtle.getItemDetails()
2021-06-02 18:03:32 -07:00
hugeblank
0318e62524 Implement tag functionality 2021-06-02 17:58:04 -07:00
hugeblank
ca69d755e6 Re-implement turtle.getItemDetails() detailed mode
Incomplete - Missing item "tags", otherwise fully functional.

- Fixes #41
2021-06-02 17:29:06 -07:00
Merith
3ba7acc414 Merge pull request #43 from Toad-Dev/merith-chat-fix
Fix: Display peripheral chat messages in multiplayer and improve /computercraft command.
2021-06-02 15:59:09 -07:00
hugeblank
4c27eea73b Fix Disk Drive & Disk Interaction
Inject Special case into ServerPlayerInteractionManager that handles disks being sneak + right clicked onto disk drives.

Fixes  #12
2021-06-02 14:23:19 -07:00
ToadDev
225a64dd6b Send terminal state with /computercraft view 2021-05-31 16:40:09 -07:00
Jonathan Coates
1831e81dd4 Add button to view computer's folder
Implementation is a little awkward, as we can't send OPEN_FILE links
from the server, so we ensure the client runs a
/computercraft open-computer ID command instead. We then intercept this
on the client side and use that to open the folder.
2021-05-30 23:59:39 -07:00
Merith
a4dd6c24e5 Update README.md 2021-05-29 07:17:19 -07:00
Jonathan Coates
936742895b Bump version to 1.94.0 2021-05-25 22:16:57 +02:00
Jonathan Coates
b5d1e618b9 Set stack size when crafting coloured items
Fixes #793
2021-05-25 22:16:57 +02:00
JackMacWindows
10aa6c5297 Add the ability to call cc.expect directly 2021-05-25 22:16:57 +02:00
Jonathan Coates
010ebacd1a Fix mount suggestion always being printed out 2021-05-25 22:16:57 +02:00
Lupus590
9e002beed7 Add cc.expect.range (#790) 2021-05-25 22:16:57 +02:00
Jonathan Coates
6029defb20 Use a separate region for the bottom pocket computer border
This is definitely not a good solution, but it's probably the best we
can do right now given resizable computers are a thing.

Fixes #775, closes #776
2021-05-25 22:16:57 +02:00
Jonathan Coates
c16aa5f247 Fix build failures
- Add license headers. Also check these during pre-commit.
 - Fix javadoc issue.
2021-05-25 22:16:57 +02:00
Jummit
8d27bdca7b Expose GenericSource to the public API
- Remove the service provider code and require people to explicitly
   register these. This is definitely more ugly, but easier than people
   pulling in AutoService or similar!
 - Add an API for registering capabilities.
 - Expand the doc comments a little. Not sure how useful they'll be, but
   let's see!

There's still so much work to be done on this, but it's a "good enough"
first step.
2021-05-25 22:16:57 +02:00
Jonathan Coates
41aa8fa163 Add example to turtle.inspect
A pretty common but non-trivial API, so worth having something. Even if
not perfect.
2021-05-25 22:16:57 +02:00
Jummit
e40fb67b50 Add inventory.getItemLimit
Closes #781
2021-05-25 22:16:57 +02:00
Jonathan Coates
96d46ffd2c Bump Cobalt version 2021-05-25 22:16:57 +02:00
Jonathan Coates
652b7ebba6 Use lightGrey for folders on normal computers
This way we still get some differences between files and folders on
normal computers. I did try with just green, but I think the contrast is
too low.

Closes #656
2021-05-25 22:16:57 +02:00
Jonathan Coates
f0d7a1165d Don't close file handles from ResourceMounts
Unlike short handles, we don't read these immediately, and so we can't
close it right away. Otherwise the file is considered empty!

Fixes SquidDev-CC/treasure-programs#1
2021-05-25 22:16:57 +02:00
Euric
4a20eea852 Defer monitor tile update when placed by another TE 2021-05-25 22:16:57 +02:00
Jonathan Coates
ccdd2bf477 Add some examples to inventory methods
Closes #761. It's not perfect, but it's a little better. Maybe??
2021-05-25 22:16:57 +02:00
Jonathan Coates
8d8f41a50b Simplify our overrides of load/loadstring
- Remove auto-prefixing of load/loadstring
 - Use Cobalt's normal load implementation, with a simple hook to
   set _ENV on the environment.
2021-05-25 22:16:57 +02:00
Jonathan Coates
72c78db28b Correctly handle sparse arrays in cc.pretty
This also swaps the order we display mixed array/maps in, so that the
array part comes first. I think this is more sensible.

Closes #777
2021-05-25 22:16:57 +02:00
Merith-TK
bf8a992273 update readme with tasks to be done 2021-05-25 12:24:50 -07:00
Merith
9c214fcd8a Merge pull request #40 from Jummit/remove-test
Remove the Java and Lua test suites
2021-05-25 12:08:48 -07:00
Merith
082cc9d2d8 Merge pull request #39 from o-iM-nI/readme
Merge 3prm3 Readme into repo
2021-05-25 12:07:23 -07:00
Jummit
bff2e81713 remove partial java and lua test suites 2021-05-25 18:17:44 +02:00
Merith
63aa3d8877 add known issue "file not found" 2021-05-24 11:15:48 -07:00
ƐqɿmƐ
74c5d7e719 Readme update
readme sourced from https://github.com/3prm3/cc-restitched
2021-05-23 09:06:34 -07:00
126 changed files with 1145 additions and 7527 deletions

View File

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

@@ -0,0 +1,12 @@
---
name: Peripheral Shoutout
about: Made a Peripheral mod for CC:R? Let us know so we can give it a shoutout
labels: peripheralShoutout
---
## What to include?
- Link to the mod's Icon
- Link to the mod
- Mod Name
- Basic description of the mod
- Link to CC:R Peripheral Documentation for the mod

View File

@@ -1,14 +0,0 @@
language: java
script: ./gradlew build --no-daemon
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/s
jdk:
- oraclejdk8

View File

@@ -1,25 +1,35 @@
# CC:Restitched Patchwork
# This is a Work In Progress Port
*it runs and works-ish*
# CC:R Version VS CC:T Version
CC:R Strives to maintaim perfect pairity with CC:R, however in some cases this is not possible, or atleast, not yet, so just because you might be on CC:R 1.96.6, doesnt mean you are on CC:T 1.96.6.
<img src="logo.png" alt="CC: Restitched" width="100%"/>
[![Current build status](https://github.com/Merith-TK/cc-restitched/workflows/Build/badge.svg)](https://github.com/o-iM-nI/cc-restitched/actions "Current build status") [![Download CC: Restitched on CurseForge](http://cf.way2muchnoise.eu/title/cc-restitched.svg)](https://www.curseforge.com/minecraft/mc-mods/cc-restitched-updated "Download CC: Restitched on CurseForge")
*it works and runs-ish*
This is an fork of [Zundrel/cc-tweaked-fabric](https://github.com/Zundrel/cc-tweaked-fabric) who's goal was to port [SquidDev-CC/CC-Tweaked](https://github.com/SquidDev-CC/CC-Tweaked) to fabric. I picked up maintaining the mod because the team working on Zundrel's fork, admitted they had gotten lazy so I picked it up to make it up to snuff with CC:T
[![Current build status](https://github.com/Merith-TK/cc-restitched/workflows/Build/badge.svg)](https://github.com/Merith-TK/cc-restitched/actions "Current build status") [![Download CC: Restitched on CurseForge](http://cf.way2muchnoise.eu/title/cc-restitched.svg)](https://www.curseforge.com/minecraft/mc-mods/cc-restitched-updated "Download CC: Restitched on CurseForge")
## What?
This is an fork of [Zundrel/cc-tweaked-fabric](https://github.com/Zundrel/cc-tweaked-fabric) who's goal was to port [SquidDev-CC/CC-Tweaked](https://github.com/SquidDev-CC/CC-Tweaked) to the [Fabric](https://fabricmc.net/) modloader. I picked up maintaining the mod because the team working on Zundrel's fork, admitted they had gotten lethargic so I picked it up to make it equal with CC:T
## Resource Packs
This mod includes textures that are more in-line with the style of Mojang's new texture-artist, Jappa. If you prefer the original textures, enable the "Classic" resource pack provided by the mod.
<img src="https://raw.githubusercontent.com/3prm3/cc-pack/main/pack.png" alt="CC: Restitched" width="16" height="16"/> [3prm3/cc-pack](https://github.com/3prm3/cc-pack/)
We also have a second resourcepack made by [3prm3](https://github.com/3prm3), it features a complete overhaul and can be enabled by enabling the `overhaul` resource pack, go check out his resource pack over here!
<img src="https://raw.githubusercontent.com/3prm3/cc-pack/main/pack.png" alt="CC: Restitched" width="16" height="16"/> [3prm3/cc-pack](https://github.com/3prm3/cc-pack/)
## Major Tasks Planned
* Rewrite the config system
* **Planned for 1.96.0 release**
* it currently sets the values that would normally be read from `config/computercraft.json` to the default values, because it does not read the file at all
* Fixing `/computercraft` commands,
* No clue why they broken, they just are
## Contributing
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. In order to start helping develop CC:R there are a few rules
1) Any updates that port commits from CC:T, ***MUST*** follow the format defined in [patchwork.md](patchwork.md) otherwise they will not be accepted,
* Commit Message must be the same as it is in CC:T,
* patchwork.md must be updated in the following format
> Comments, optional but useful if you had to do something differently than in CC:T (outside of Fabric/forge differences
> Comments, optional but useful if you had to do something differently than in CC:T outside of [Fabric](https://fabricmc.net/)/[Forge](https://mcforge.readthedocs.io/en/1.16.x/) differences
>
> \`\`\`
>
@@ -30,23 +40,29 @@ Any contribution is welcome, be that using the mod, reporting bugs or contributi
> commit desc
>
> \`\`\`
2) Follow the fabirc programming guidelines as close as possible. This means you have to use `loom` mappings,
2) Follow the [Fabric](https://fabricmc.net/) programming guidelines as close as possible. This means you have to use [`loom`](https://fabricmc.net/wiki/tutorial:mappings) mappings,
3) You cannot intentionally implement bugs and security vulnerabilities
4) Unless the commit is a "patchwork" compliant commit, (IE: taken from CC:T), the lua code is off limits,s
4) Unless the commit is a ["patchwork"](https://github.com/Merith-TK/cc-restitched/blob/fabric/patchwork.md) compliant commit, (IE: taken from CC:T), the lua code is off limits
## Bleeding Edge Builds
Bleeding edge builds can be found [here](https://github.com/Merith-TK/cc-restitched/actions) at github actions to simplify things
Bleeding edge builds can be found [here](https://github.com/Merith-TK/cc-restitched/actions) at Github Actions.
## Community
If you need help getting started with CC: Tweaked, want to show off your latest project, or just want to chat about ComputerCraft, here is the [Forum](https://forums.computercraft.cc/) and the [Discord](https://discord.gg/H2UyJXe)
## 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.
* Mods that add blocks that can be used as peripherals for CC:T On forge, don't 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,
* [Fixed (d10f297c): please report if bug persists]</br> ~~Storage Peripherals throw a java "StackOverflowError" when using `pushItems()`,~~
* ~~Work around, you are probably using `pushItems(chest, 1)` or similar. please use `pushItems(chest, 1, nil, 1)`.~~
* Computers will not run built in commands, saying "File not found"
* This is a know bug, dont know what causes it, but just restart the computer (`ctrl+r` for one second) and it will work again
* Occurs when server runs `/reload` or a datapack is updated
## Perpherals
Unfortunately, unlike the original CC:Tweaked project, CC:Restitched, does not have any actual peripheral mods, currently the only one we have is an example for mod devs to get started with making/adding the peripheral API to their mods!
## Known Working mods that add Peripherals
* Please let me know of other mods that work with this one
* Better End
* Better Nether
If your a mod dev made a mod with CC:R peripheral support, OR if your a player who found a mod with CC:R support, please open an [issue here](https://github.com/Merith-TK/cc-restitched/issues/new?assignees=&labels=peripheralShoutout&template=peripheral_shoutout.md) to let us know so we can add it to the list!
* ![icon](https://raw.githubusercontent.com/Toad-Dev/cc-peripheral-test/master/src/main/resources/assets/cc_test/textures/block/test_peripheral.png) [CC Peripheral Test](https://github.com/Toad-Dev/cc-peripheral-test)
* This is an example mod for how to make peripherals that work as a block, or as a turtle upgrade!

View File

@@ -50,11 +50,7 @@ dependencies {
compile 'javax.vecmath:vecmath:1.5.2'
shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT'
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'
shade 'org.squiddev:Cobalt:0.5.2-SNAPSHOT'
modRuntime "me.shedaniel:RoughlyEnoughItems-api:5.8.9"
modRuntime "me.shedaniel:RoughlyEnoughItems:5.8.9"

View File

@@ -2,7 +2,7 @@
org.gradle.jvmargs=-Xmx1G
# Mod properties
mod_version=1.95.3-beta
mod_version=1.96.0
# Minecraft properties
mc_version=1.16.5

View File

@@ -21,7 +21,6 @@ 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;
import dan200.computercraft.core.asm.GenericSource;
import dan200.computercraft.shared.common.ColourableRecipe;
import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
@@ -39,7 +38,6 @@ import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.util.Config;
import dan200.computercraft.shared.util.ImpostorRecipe;
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
import dan200.computercraft.shared.util.ServiceUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -132,7 +130,6 @@ public final class ComputerCraft implements ModInitializer {
Registry.register(Registry.LOOT_CONDITION_TYPE, new Identifier(ComputerCraft.MOD_ID, "player_creative"), PlayerCreativeLootCondition.TYPE);
Registry.register(Registry.LOOT_CONDITION_TYPE, new Identifier(ComputerCraft.MOD_ID, "has_id"), HasComputerIdLootCondition.TYPE);
init();
GenericSource.setup( () -> ServiceUtil.loadServices( GenericSource.class ));
FabricLoader.getInstance().getModContainer(MOD_ID).ifPresent(modContainer -> {
ResourceManagerHelper.registerBuiltinResourcePack(new Identifier(MOD_ID, "classic"), modContainer, ResourcePackActivationType.NORMAL);
ResourceManagerHelper.registerBuiltinResourcePack(new Identifier(MOD_ID, "overhaul"), modContainer, ResourcePackActivationType.NORMAL);

View File

@@ -16,6 +16,7 @@ import javax.annotation.Nullable;
import dan200.computercraft.api.ComputerCraftAPI.IComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork;
@@ -26,9 +27,10 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.core.apis.ApiFactories;
import dan200.computercraft.core.asm.GenericMethod;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.ResourceMount;
import dan200.computercraft.mixin.MinecraftServerAccess;
import dan200.computercraft.fabric.mixin.MinecraftServerAccess;
import dan200.computercraft.shared.BundledRedstone;
import dan200.computercraft.shared.MediaProviders;
import dan200.computercraft.shared.Peripherals;
@@ -143,6 +145,12 @@ public final class ComputerCraftAPIImpl implements IComputerCraftAPI {
PocketUpgrades.register(upgrade);
}
@Override
public void registerGenericSource( @Nonnull GenericSource source )
{
GenericMethod.register( source );
}
@Nonnull
@Override
public IPacketNetwork getWirelessNetwork() {

View File

@@ -11,6 +11,7 @@ import javax.annotation.Nullable;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.IMediaProvider;
@@ -136,6 +137,17 @@ public final class ComputerCraftAPI {
getInstance().registerPeripheralProvider(provider);
}
/**
* Registers a method source for generic peripherals.
*
* @param source The method source to register.
* @see GenericSource
*/
public static void registerGenericSource( @Nonnull GenericSource source )
{
getInstance().registerGenericSource( source );
}
/**
* Registers a new turtle turtle for use in ComputerCraft. After calling this, users should be able to craft Turtles with your new turtle. It is
* recommended to call this during the load() method of your mod.
@@ -238,6 +250,8 @@ public final class ComputerCraftAPI {
void registerPeripheralProvider(@Nonnull IPeripheralProvider provider);
void registerGenericSource( @Nonnull GenericSource source );
void registerTurtleUpgrade(@Nonnull ITurtleUpgrade upgrade);
void registerBundledRedstoneProvider(@Nonnull IBundledRedstoneProvider provider);

View File

@@ -10,7 +10,7 @@ import java.util.Objects;
import javax.annotation.Nonnull;
import dan200.computercraft.mixin.AffineTransformationAccess;
import dan200.computercraft.fabric.mixin.AffineTransformationAccess;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.model.BakedModel;

View File

@@ -0,0 +1,58 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.core.asm.LuaMethod;
import net.minecraft.util.Identifier;
import javax.annotation.Nonnull;
/**
* A generic source of {@link LuaMethod} functions.
*
* Unlike normal objects ({@link IDynamicLuaObject} or {@link IPeripheral}), methods do not target this object but
* instead are defined as {@code static} and accept their target as the first parameter. This allows you to inject
* methods onto objects you do not own, as well as declaring methods for a specific "trait" (for instance, a
* {@link Capability}).
*
* Currently the "generic peripheral" system is incompatible with normal peripherals. Normal {@link IPeripheralProvider}
* or {@link IPeripheral} implementations take priority. Tile entities which use this system are given a peripheral name
* determined by their id, rather than any peripheral provider. This will hopefully change in the future, once a suitable
* design has been established.
*
* For example, the main CC: Tweaked mod defines a generic source for inventories, which works on {@link IItemHandler}s:
*
* <pre>{@code
* public class InventoryMethods implements GenericSource {
* \@LuaFunction( mainThread = true )
* public static int size(IItemHandler inventory) {
* return inventory.getSlots();
* }
*
* // ...
* }
* }</pre>
*
* @see ComputerCraftAPI#registerGenericSource(GenericSource)
* @see ComputerCraftAPI#registerGenericCapability(Capability) New capabilities (those not built into Forge) must be
* explicitly given to the generic peripheral system, as there is no way to enumerate all capabilities.
*/
public interface GenericSource
{
/**
* A unique identifier for this generic source.
*
* This is currently unused, but may be used in the future to allow disabling specific sources. It is recommended
* to return an identifier using your mod's ID.
*
* @return This source's identifier.
*/
@Nonnull
Identifier id();
}

View File

@@ -8,7 +8,7 @@ package dan200.computercraft.client;
import javax.annotation.Nullable;
import dan200.computercraft.mixin.ChatHudAccess;
import dan200.computercraft.fabric.mixin.ChatHudAccess;
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.command.text.TableFormatter;

View File

@@ -20,12 +20,13 @@ import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.client.render.TurtleModelLoader;
import dan200.computercraft.client.render.TurtlePlayerRenderer;
import dan200.computercraft.fabric.events.ClientUnloadWorldEvent;
import dan200.computercraft.shared.ComputerCraftRegistry;
import dan200.computercraft.shared.common.ContainerHeldItem;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.network.container.ViewComputerContainerData;
import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.printer.ContainerPrinter;
@@ -33,6 +34,7 @@ import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents;
import net.minecraft.client.item.ModelPredicateProvider;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.item.Item;
@@ -48,14 +50,19 @@ import net.fabricmc.fabric.api.client.rendereregistry.v1.BlockEntityRendererRegi
import net.fabricmc.fabric.api.client.rendereregistry.v1.EntityRendererRegistry;
import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry;
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents;
import net.fabricmc.fabric.mixin.object.builder.ModelPredicateProviderRegistrySpecificAccessor;
@Environment (EnvType.CLIENT)
public final class ComputerCraftProxyClient implements ClientModInitializer {
public static void initEvents() {
private static void initEvents() {
ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register( ( blockEntity, world ) -> {
if(blockEntity instanceof TileGeneric ) {
((TileGeneric)blockEntity).onChunkUnloaded();
}
});
ClientUnloadWorldEvent.EVENT.register( () -> ClientMonitor.destroyAll() );
}
@Override
@@ -97,13 +104,9 @@ public final class ComputerCraftProxyClient implements ClientModInitializer {
() -> ComputerCraftRegistry.ModItems.POCKET_COMPUTER_ADVANCED);
ClientRegistry.onItemColours();
// TODO Verify this does things properly
ServerWorldEvents.UNLOAD.register(((minecraftServer, serverWorld) -> {
ClientMonitor.destroyAll();
}));
initEvents();
}
// My IDE doesn't think so, but we do actually need these generics.
private static void registerContainers() {
ScreenRegistry.<ContainerComputer, GuiComputer<ContainerComputer>>register(ComputerCraftRegistry.ModContainers.COMPUTER, GuiComputer::create);
@@ -126,15 +129,4 @@ public final class ComputerCraftProxyClient implements ClientModInitializer {
ModelPredicateProviderRegistrySpecificAccessor.callRegister(item.get(), id, getter);
}
}
// @Mod.EventBusSubscriber (modid = ComputerCraft.MOD_ID, value = Dist.CLIENT)
// public static final class ForgeHandlers {
// @SubscribeEvent
// public static void onWorldUnload(WorldEvent.Unload event) {
// if (event.getWorld()
// .isClient()) {
// ClientMonitor.destroyAll();
// }
// }
// }
}

View File

@@ -38,7 +38,10 @@ public class ComputerBorderRenderer {
private static final int CORNER_LEFT_X = BORDER;
private static final int CORNER_RIGHT_X = CORNER_LEFT_X + BORDER;
private static final int BORDER_RIGHT_X = 36;
private static final int GAP = 4;
private static final int LIGHT_BORDER_Y = 56;
private static final int LIGHT_CORNER_Y = 80;
public static final int LIGHT_HEIGHT = 8;
private static final float TEX_SCALE = 1 / 256.0f;
static {
@@ -89,15 +92,16 @@ public class ComputerBorderRenderer {
}
public static void render(Matrix4f transform, VertexConsumer buffer, int x, int y, int z, int width, int height, float r, float g, float b) {
render(transform, buffer, x, y, z, width, height, 0, r, g, b);
render( transform, buffer, x, y, z, width, height, false, r, g, b );
}
public static void render(Matrix4f transform, VertexConsumer buffer, int x, int y, int z, int width, int height, int borderHeight, float r, float g,
float b) {
new ComputerBorderRenderer(transform, buffer, z, r, g, b).doRender(x, y, width, height, borderHeight);
public static void render( Matrix4f transform, VertexConsumer buffer, int x, int y, int z, int width, int height, boolean withLight, float r, float g, float b )
{
new ComputerBorderRenderer( transform, buffer, z, r, g, b ).doRender( x, y, width, height, withLight );
}
public void doRender(int x, int y, int width, int height, int bottomHeight) {
public void doRender( int x, int y, int width, int height, boolean withLight )
{
int endX = x + width;
int endY = y + height;
@@ -112,24 +116,17 @@ public class ComputerBorderRenderer {
// Bottom bar. We allow for drawing a stretched version, which allows for additional elements (such as the
// pocket computer's lights).
if (bottomHeight <= 0) {
if( withLight )
{
renderTexture( x, endY, 0, LIGHT_BORDER_Y, endX - x, BORDER + LIGHT_HEIGHT, BORDER, BORDER + LIGHT_HEIGHT );
renderTexture( x - BORDER, endY, CORNER_LEFT_X, LIGHT_CORNER_Y, BORDER, BORDER + LIGHT_HEIGHT );
renderTexture( endX, endY, CORNER_RIGHT_X, LIGHT_CORNER_Y, BORDER, BORDER + LIGHT_HEIGHT );
}
else
{
this.renderLine(x, endY, 0, BORDER, endX - x, BORDER);
this.renderCorner(x - BORDER, endY, CORNER_LEFT_X, CORNER_BOTTOM_Y);
this.renderCorner(endX, endY, CORNER_RIGHT_X, CORNER_BOTTOM_Y);
} else {
// Bottom left, middle, right. We do this in three portions: the top inner corners, an extended region for
// lights, and then the bottom outer corners.
this.renderTexture(x - BORDER, endY, CORNER_LEFT_X, CORNER_BOTTOM_Y, BORDER, BORDER / 2);
this.renderTexture(x, endY, 0, BORDER, width, BORDER / 2, BORDER, BORDER / 2);
this.renderTexture(endX, endY, CORNER_RIGHT_X, CORNER_BOTTOM_Y, BORDER, BORDER / 2);
this.renderTexture(x - BORDER, endY + BORDER / 2, CORNER_LEFT_X, CORNER_BOTTOM_Y + GAP, BORDER, bottomHeight, BORDER, GAP);
this.renderTexture(x, endY + BORDER / 2, 0, BORDER + GAP, width, bottomHeight, BORDER, GAP);
this.renderTexture(endX, endY + BORDER / 2, CORNER_RIGHT_X, CORNER_BOTTOM_Y + GAP, BORDER, bottomHeight, BORDER, GAP);
this.renderTexture(x - BORDER, endY + bottomHeight + BORDER / 2, CORNER_LEFT_X, CORNER_BOTTOM_Y + BORDER / 2, BORDER, BORDER / 2);
this.renderTexture(x, endY + bottomHeight + BORDER / 2, 0, BORDER + BORDER / 2, width, BORDER / 2);
this.renderTexture(endX, endY + bottomHeight + BORDER / 2, CORNER_RIGHT_X, CORNER_BOTTOM_Y + BORDER / 2, BORDER, BORDER / 2);
}
}

View File

@@ -6,7 +6,7 @@
package dan200.computercraft.client.render;
import dan200.computercraft.mixin.HeldItemRendererAccess;
import dan200.computercraft.fabric.mixin.HeldItemRendererAccess;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.VertexConsumerProvider;

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.client.render;
import static dan200.computercraft.client.render.ComputerBorderRenderer.*;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
import static dan200.computercraft.client.render.ComputerBorderRenderer.BORDER;
@@ -31,12 +32,13 @@ import net.minecraft.client.util.math.Vector3f;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.Matrix4f;
import static dan200.computercraft.client.render.ComputerBorderRenderer.*;
/**
* Emulates map rendering for pocket computers.
*/
public final class ItemPocketRenderer extends ItemMapLikeRenderer {
public static final ItemPocketRenderer INSTANCE = new ItemPocketRenderer();
private static final int LIGHT_HEIGHT = 8;
private ItemPocketRenderer() {
}
@@ -108,7 +110,7 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer {
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin(GL11.GL_QUADS, VertexFormats.POSITION_COLOR_TEXTURE);
ComputerBorderRenderer.render(transform, buffer, 0, 0, 0, width, height, LIGHT_HEIGHT, r, g, b);
ComputerBorderRenderer.render(transform, buffer, 0, 0, 0, width, height, true, r, g, b);
tessellator.draw();
}

View File

@@ -8,7 +8,7 @@ package dan200.computercraft.client.render;
import java.util.List;
import dan200.computercraft.mixin.BakedQuadAccess;
import dan200.computercraft.fabric.mixin.BakedQuadAccess;
import net.minecraft.client.render.VertexFormat;
import net.minecraft.client.render.VertexFormatElement;

View File

@@ -36,10 +36,7 @@ import com.google.common.cache.LoadingCache;
import com.google.common.primitives.Primitives;
import com.google.common.reflect.TypeToken;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.lua.*;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
@@ -120,10 +117,9 @@ public final class Generator<T> {
this.addMethod(methods, method, annotation, instance);
}
for (GenericSource.GenericMethod method : GenericSource.GenericMethod.all()) {
if (!method.target.isAssignableFrom(klass)) {
continue;
}
for( GenericMethod method : GenericMethod.all() )
{
if( !method.target.isAssignableFrom( klass ) ) continue;
T instance = this.methodCache.getUnchecked(method.method)
.orElse(null);

View File

@@ -0,0 +1,90 @@
/*
* 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.asm;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.LuaFunction;
import javax.annotation.Nonnull;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* A generic method is a method belonging to a {@link GenericSource} with a known target.
*/
public class GenericMethod
{
final Method method;
final LuaFunction annotation;
final Class<?> target;
private static final List<GenericSource> sources = new ArrayList<>();
private static List<GenericMethod> cache;
GenericMethod( Method method, LuaFunction annotation, Class<?> target )
{
this.method = method;
this.annotation = annotation;
this.target = target;
}
/**
* Find all public static methods annotated with {@link LuaFunction} which belong to a {@link GenericSource}.
*
* @return All available generic methods.
*/
static List<GenericMethod> all()
{
if( cache != null ) return cache;
return cache = sources.stream()
.flatMap( x -> Arrays.stream( x.getClass().getDeclaredMethods() ) )
.map( method ->
{
LuaFunction annotation = method.getAnnotation( LuaFunction.class );
if( annotation == null ) return null;
if( !Modifier.isStatic( method.getModifiers() ) )
{
ComputerCraft.log.error( "GenericSource method {}.{} should be static.", method.getDeclaringClass(), method.getName() );
return null;
}
Type[] types = method.getGenericParameterTypes();
if( types.length == 0 )
{
ComputerCraft.log.error( "GenericSource method {}.{} has no parameters.", method.getDeclaringClass(), method.getName() );
return null;
}
Class<?> target = Reflect.getRawType( method, types[0], false );
if( target == null ) return null;
return new GenericMethod( method, annotation, target );
} )
.filter( Objects::nonNull )
.collect( Collectors.toList() );
}
public static synchronized void register( @Nonnull GenericSource source )
{
Objects.requireNonNull( source, "Source cannot be null" );
if( cache != null )
{
ComputerCraft.log.warn( "Registering a generic source {} after cache has been built. This source will be ignored.", cache );
}
sources.add( source );
}
}

View File

@@ -1,114 +0,0 @@
/*
* 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.asm;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.LuaFunction;
import net.minecraft.util.Identifier;
/**
* A generic source of {@link LuaMethod} functions. This allows for injecting methods onto objects you do not own.
*
* Unlike conventional Lua objects, the annotated methods should be {@code static}, with their target as the first parameter.
*/
public interface GenericSource {
/**
* Register a stream of generic sources.
*
* @param sources The source of generic methods.
*/
static void setup(Supplier<Stream<GenericSource>> sources) {
GenericMethod.sources = sources;
}
/**
* A unique identifier for this generic source. This may be used in the future to allow disabling specific sources.
*
* @return This source's identifier.
*/
@Nonnull
Identifier id();
/**
* A generic method is a method belonging to a {@link GenericSource} with a known target.
*/
class GenericMethod {
static Supplier<Stream<GenericSource>> sources;
private static List<GenericMethod> cache;
final Method method;
final LuaFunction annotation;
final Class<?> target;
GenericMethod(Method method, LuaFunction annotation, Class<?> target) {
this.method = method;
this.annotation = annotation;
this.target = target;
}
/**
* Find all public static methods annotated with {@link LuaFunction} which belong to a {@link GenericSource}.
*
* @return All available generic methods.
*/
static List<GenericMethod> all() {
if (cache != null) {
return cache;
}
if (sources == null) {
ComputerCraft.log.warn("Getting GenericMethods without a provider");
return cache = Collections.emptyList();
}
return cache = sources.get()
.flatMap(x -> Arrays.stream(x.getClass()
.getDeclaredMethods()))
.map(method -> {
LuaFunction annotation = method.getAnnotation(LuaFunction.class);
if (annotation == null) {
return null;
}
if (!Modifier.isStatic(method.getModifiers())) {
ComputerCraft.log.error("GenericSource method {}.{} should be static.",
method.getDeclaringClass(),
method.getName());
return null;
}
Type[] types = method.getGenericParameterTypes();
if (types.length == 0) {
ComputerCraft.log.error("GenericSource method {}.{} has no parameters.",
method.getDeclaringClass(),
method.getName());
return null;
}
Class<?> target = Reflect.getRawType(method, types[0], false);
if (target == null) {
return null;
}
return new GenericMethod(method, annotation, target);
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
}

View File

@@ -31,7 +31,7 @@ import com.google.common.io.ByteStreams;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
import dan200.computercraft.shared.util.IoUtil;
import net.minecraft.resource.ReloadableResourceManager;
import net.minecraft.resource.Resource;
import net.minecraft.resource.ResourceManager;
@@ -108,8 +108,8 @@ public final class ResourceMount implements IMount {
if( !hasAny )
{
ComputerCraft.log.warn("Cannot find any files under /data/{}/{} for resource mount.", namespace, subPath);
if( newRoot != null )
ComputerCraft.log.warn( "Cannot find any files under /data/{}/{} for resource mount.", namespace, subPath );
if( existingNamespace != null )
{
ComputerCraft.log.warn("There are files under /data/{}/{} though. Did you get the wrong namespace?", existingNamespace, subPath);
}
@@ -186,13 +186,19 @@ public final class ResourceMount implements IMount {
return new ArrayByteChannel(contents);
}
try (InputStream stream = this.manager.getResource(file.identifier)
.getInputStream()) {
if (stream.available() > MAX_CACHED_SIZE) {
return Channels.newChannel(stream);
}
try
{
InputStream stream = manager.getResource( file.identifier ).getInputStream();
if( stream.available() > MAX_CACHED_SIZE ) return Channels.newChannel( stream );
contents = ByteStreams.toByteArray(stream);
try
{
contents = ByteStreams.toByteArray( stream );
}
finally
{
IoUtil.closeQuietly( stream );
}
CONTENTS_CACHE.put(file, contents);
return new ArrayByteChannel(contents);
} catch (FileNotFoundException ignored) {

View File

@@ -0,0 +1,17 @@
package dan200.computercraft.fabric.events;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
@FunctionalInterface
public interface ClientUnloadWorldEvent
{
Event<ClientUnloadWorldEvent> EVENT = EventFactory.createArrayBacked( ClientUnloadWorldEvent.class,
callbacks -> () -> {
for( ClientUnloadWorldEvent callback : callbacks) {
callback.onClientUnloadWorld();
}
});
void onClientUnloadWorld();
}

View File

@@ -1,4 +1,4 @@
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

View File

@@ -1,4 +1,4 @@
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

View File

@@ -1,4 +1,4 @@
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;

View File

@@ -1,4 +1,4 @@
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;

View File

@@ -1,4 +1,4 @@
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

View File

@@ -4,7 +4,7 @@
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import dan200.computercraft.shared.util.DropConsumer;
import org.spongepowered.asm.mixin.Mixin;

View File

@@ -4,7 +4,7 @@
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import dan200.computercraft.shared.util.DropConsumer;
import org.spongepowered.asm.mixin.Mixin;

View File

@@ -4,12 +4,11 @@
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import dan200.computercraft.client.render.ItemPocketRenderer;
import dan200.computercraft.client.render.ItemPrintoutRenderer;
import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.mixed.MixedFirstPersonRenderer;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -30,22 +29,12 @@ import net.fabricmc.api.Environment;
@Mixin (HeldItemRenderer.class)
@Environment (EnvType.CLIENT)
public class MixinHeldItemRenderer implements MixedFirstPersonRenderer {
@Override
public void renderArmFirstPerson_CC(MatrixStack stack, VertexConsumerProvider consumerProvider, int light, float equip, float swing, Arm hand) {
this.renderArmHoldingItem(stack, consumerProvider, light, equip, swing, hand);
}
public class MixinHeldItemRenderer {
@Shadow
private void renderArmHoldingItem(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, float equipProgress, float swingProgress,
Arm arm) {
}
@Override
public float getMapAngleFromPitch_CC(float pitch) {
return this.getMapAngle(pitch);
}
@Shadow
private float getMapAngle(float pitch) {
return 0;

View File

@@ -4,7 +4,7 @@
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import dan200.computercraft.client.render.ItemPrintoutRenderer;
import dan200.computercraft.shared.media.items.ItemPrintout;

View File

@@ -0,0 +1,37 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.fabric.mixin;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.fabric.events.ClientUnloadWorldEvent;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.world.ClientWorld;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.MinecraftClient;
@Mixin (MinecraftClient.class)
public abstract class MixinMinecraftClient
{
@Inject (method = "render", at = @At ("HEAD"))
private void onRender(CallbackInfo info) {
FrameInfo.onRenderFrame();
}
@Inject(method = "disconnect(Lnet/minecraft/client/gui/screen/Screen;)V", at = @At ("RETURN"))
private void disconnectAfter(Screen screen, CallbackInfo info) {
ClientUnloadWorldEvent.EVENT.invoker().onClientUnloadWorld();
}
@Inject(method = "joinWorld", at = @At("RETURN"))
private void joinWorldAfter(ClientWorld world, CallbackInfo info) {
ClientUnloadWorldEvent.EVENT.invoker().onClientUnloadWorld();
}
}

View File

@@ -4,9 +4,9 @@
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import dan200.computercraft.shared.command.CommandCopy;
import dan200.computercraft.shared.command.ClientCommands;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -22,7 +22,7 @@ import net.fabricmc.api.Environment;
public class MixinScreen {
@Inject (method = "sendMessage(Ljava/lang/String;Z)V", at = @At ("HEAD"), cancellable = true)
public void sendClientCommand(String message, boolean add, CallbackInfo info) {
if (CommandCopy.onClientSendMessage(message)) {
if (ClientCommands.onClientSendMessage(message)) {
info.cancel();
}
}

View File

@@ -0,0 +1,33 @@
package dan200.computercraft.fabric.mixin;
import dan200.computercraft.shared.ComputerCraftRegistry;
import net.minecraft.advancement.criterion.Criteria;
import net.minecraft.block.BlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.network.ServerPlayerInteractionManager;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ServerPlayerInteractionManager.class)
public class MixinServerPlayerInteractionManager {
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;copy()Lnet/minecraft/item/ItemStack;", ordinal = 0), method = "interactBlock(Lnet/minecraft/server/network/ServerPlayerEntity;Lnet/minecraft/world/World;Lnet/minecraft/item/ItemStack;Lnet/minecraft/util/Hand;Lnet/minecraft/util/hit/BlockHitResult;)Lnet/minecraft/util/ActionResult;", cancellable = true)
private void interact(ServerPlayerEntity player, World world, ItemStack stack, Hand hand, BlockHitResult hitResult, CallbackInfoReturnable<ActionResult> cir) {
BlockPos pos = hitResult.getBlockPos();
BlockState state = world.getBlockState(pos);
if (player.getMainHandStack().getItem() == ComputerCraftRegistry.ModItems.DISK && state.getBlock() == ComputerCraftRegistry.ModBlocks.DISK_DRIVE) {
ActionResult actionResult = state.onUse(world, player, hand, hitResult);
if (actionResult.isAccepted()) {
Criteria.ITEM_USED_ON_BLOCK.test(player, pos, stack);
cir.setReturnValue(actionResult);
}
}
}
}

View File

@@ -4,7 +4,7 @@
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import dan200.computercraft.shared.util.DropConsumer;
import org.spongepowered.asm.mixin.Mixin;

View File

@@ -4,7 +4,7 @@
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import java.util.Collection;

View File

@@ -4,7 +4,7 @@
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import dan200.computercraft.client.render.CableHighlightRenderer;
import dan200.computercraft.client.render.MonitorHighlightRenderer;

View File

@@ -0,0 +1,12 @@
package dan200.computercraft.fabric.mixin;
import net.minecraft.item.MusicDiscItem;
import net.minecraft.sound.SoundEvent;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(MusicDiscItem.class)
public interface MusicDiscItemAccessor {
@Accessor
SoundEvent getSound();
}

View File

@@ -1,4 +1,4 @@
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

View File

@@ -1,4 +1,4 @@
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

View File

@@ -1,4 +1,4 @@
package dan200.computercraft.mixin;
package dan200.computercraft.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;

View File

@@ -1,23 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.mixin;
import dan200.computercraft.client.FrameInfo;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.MinecraftClient;
@Mixin (MinecraftClient.class)
public abstract class MixinMinecraftGame {
@Inject (method = "render", at = @At ("HEAD"))
private void onRender(CallbackInfo info) {
FrameInfo.onRenderFrame();
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.shared.command;
import dan200.computercraft.shared.util.IDAssigner;
import net.minecraft.util.Util;
import java.io.File;
/**
* Basic client-side commands.
*
* Simply hooks into client chat messages and intercepts matching strings.
*/
public final class ClientCommands
{
public static final String OPEN_COMPUTER = "/computercraft open-computer ";
private ClientCommands()
{
}
public static boolean onClientSendMessage( String message )
{
// Emulate the command on the client side
if( message.startsWith( OPEN_COMPUTER ) )
{
String idStr = message.substring( OPEN_COMPUTER.length() ).trim();
int id;
try
{
id = Integer.parseInt( idStr );
}
catch( NumberFormatException ignore )
{
return true;
}
File file = new File( IDAssigner.getDir(), "computer/" + id );
if( !file.isDirectory() ) return true;
Util.getOperatingSystem().open( file );
return true;
}
return false;
}
}

View File

@@ -3,44 +3,8 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.command;
import static dan200.computercraft.shared.command.CommandUtils.isPlayer;
import static dan200.computercraft.shared.command.Exceptions.NOT_TRACKING_EXCEPTION;
import static dan200.computercraft.shared.command.Exceptions.NO_TIMINGS_EXCEPTION;
import static dan200.computercraft.shared.command.Exceptions.TP_NOT_PLAYER;
import static dan200.computercraft.shared.command.Exceptions.TP_NOT_THERE;
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.getComputerArgument;
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.oneComputer;
import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.getComputersArgument;
import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.manyComputers;
import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.unwrap;
import static dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType.trackingField;
import static dan200.computercraft.shared.command.builder.CommandBuilder.args;
import static dan200.computercraft.shared.command.builder.CommandBuilder.command;
import static dan200.computercraft.shared.command.builder.HelpingArgumentBuilder.choice;
import static dan200.computercraft.shared.command.text.ChatHelpers.bool;
import static dan200.computercraft.shared.command.text.ChatHelpers.header;
import static dan200.computercraft.shared.command.text.ChatHelpers.link;
import static dan200.computercraft.shared.command.text.ChatHelpers.position;
import static dan200.computercraft.shared.command.text.ChatHelpers.text;
import static dan200.computercraft.shared.command.text.ChatHelpers.translate;
import static net.minecraft.server.command.CommandManager.literal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nonnull;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@@ -57,10 +21,12 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.network.container.ViewComputerContainerData;
import dan200.computercraft.shared.util.IDAssigner;
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket;
import net.minecraft.screen.NamedScreenHandlerFactory;
import net.minecraft.screen.ScreenHandler;
@@ -69,345 +35,389 @@ import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.LiteralText;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public final class CommandComputerCraft {
public static final UUID SYSTEM_UUID = new UUID(0, 0);
import javax.annotation.Nonnull;
import java.io.File;
import java.util.*;
import static dan200.computercraft.shared.command.CommandUtils.isPlayer;
import static dan200.computercraft.shared.command.Exceptions.*;
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.getComputerArgument;
import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.oneComputer;
import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.*;
import static dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType.trackingField;
import static dan200.computercraft.shared.command.builder.CommandBuilder.args;
import static dan200.computercraft.shared.command.builder.CommandBuilder.command;
import static dan200.computercraft.shared.command.builder.HelpingArgumentBuilder.choice;
import static dan200.computercraft.shared.command.text.ChatHelpers.*;
import static net.minecraft.server.command.CommandManager.literal;
public final class CommandComputerCraft
{
public static final UUID SYSTEM_UUID = new UUID( 0, 0 );
private static final int DUMP_LIST_ID = 5373952;
private static final int DUMP_SINGLE_ID = 1844510720;
private static final int TRACK_ID = 373882880;
private static final List<TrackingField> DEFAULT_FIELDS = Arrays.asList(TrackingField.TASKS,
TrackingField.TOTAL_TIME,
TrackingField.AVERAGE_TIME,
TrackingField.MAX_TIME);
public CommandComputerCraft() {
private CommandComputerCraft()
{
}
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, boolean bool) {
dispatcher.register(choice("computercraft").then(literal("dump").requires(UserLevel.OWNER_OP)
.executes(context -> {
TableBuilder table = new TableBuilder(DUMP_LIST_ID,
"Computer",
"On",
"Position");
public static void register( CommandDispatcher<ServerCommandSource> dispatcher, Boolean dedicated )
{
dispatcher.register( choice( "computercraft" )
.then( literal( "dump" )
.requires( UserLevel.OWNER_OP )
.executes( context -> {
TableBuilder table = new TableBuilder( DUMP_LIST_ID, "Computer", "On", "Position" );
ServerCommandSource source = context.getSource();
List<ServerComputer> computers =
new ArrayList<>(ComputerCraft.serverComputerRegistry.getComputers());
ServerCommandSource source = context.getSource();
List<ServerComputer> computers = new ArrayList<>( ComputerCraft.serverComputerRegistry.getComputers() );
// Unless we're on a server, limit the number of rows we can send.
World world = source.getWorld();
BlockPos pos = new BlockPos(source.getPosition());
// Unless we're on a server, limit the number of rows we can send.
World world = source.getWorld();
BlockPos pos = new BlockPos( source.getPosition() );
computers.sort((a, b) -> {
if (a.getWorld() == b.getWorld() && a.getWorld() == world) {
return Double.compare(a.getPosition()
.getSquaredDistance(pos),
b.getPosition()
.getSquaredDistance(pos));
} else if (a.getWorld() == world) {
return -1;
} else if (b.getWorld() == world) {
return 1;
} else {
return Integer.compare(a.getInstanceID(), b.getInstanceID());
}
});
computers.sort( ( a, b ) -> {
if( a.getWorld() == b.getWorld() && a.getWorld() == world )
{
return Double.compare( a.getPosition().getSquaredDistance( pos ), b.getPosition().getSquaredDistance( pos ) );
}
else if( a.getWorld() == world )
{
return -1;
}
else if( b.getWorld() == world )
{
return 1;
}
else
{
return Integer.compare( a.getInstanceID(), b.getInstanceID() );
}
} );
for (ServerComputer computer : computers) {
table.row(linkComputer(source, computer, computer.getID()),
bool(computer.isOn()),
linkPosition(source, computer));
}
for( ServerComputer computer : computers )
{
table.row(
linkComputer( source, computer, computer.getID() ),
bool( computer.isOn() ),
linkPosition( source, computer )
);
}
table.display(context.getSource());
return computers.size();
})
.then(args().arg("computer", oneComputer())
.executes(context -> {
ServerComputer computer = getComputerArgument(context, "computer");
table.display( context.getSource() );
return computers.size();
} )
.then( args()
.arg( "computer", oneComputer() )
.executes( context -> {
ServerComputer computer = getComputerArgument( context, "computer" );
TableBuilder table = new TableBuilder(DUMP_SINGLE_ID);
table.row(header("Instance"),
text(Integer.toString(computer.getInstanceID())));
table.row(header("Id"), text(Integer.toString(computer.getID())));
table.row(header("Label"), text(computer.getLabel()));
table.row(header("On"), bool(computer.isOn()));
table.row(header("Position"),
linkPosition(context.getSource(), computer));
table.row(header("Family"),
text(computer.getFamily()
.toString()));
TableBuilder table = new TableBuilder( DUMP_SINGLE_ID );
table.row( header( "Instance" ), text( Integer.toString( computer.getInstanceID() ) ) );
table.row( header( "Id" ), text( Integer.toString( computer.getID() ) ) );
table.row( header( "Label" ), text( computer.getLabel() ) );
table.row( header( "On" ), bool( computer.isOn() ) );
table.row( header( "Position" ), linkPosition( context.getSource(), computer ) );
table.row( header( "Family" ), text( computer.getFamily().toString() ) );
for (ComputerSide side : ComputerSide.values()) {
IPeripheral peripheral = computer.getPeripheral(side);
if (peripheral != null) {
table.row(header("Peripheral " + side.getName()),
text(peripheral.getType()));
}
}
for( ComputerSide side : ComputerSide.values() )
{
IPeripheral peripheral = computer.getPeripheral( side );
if( peripheral != null )
{
table.row( header( "Peripheral " + side.getName() ), text( peripheral.getType() ) );
}
}
table.display(context.getSource());
return 1;
})))
table.display( context.getSource() );
return 1;
} ) ) )
.then(command("shutdown").requires(UserLevel.OWNER_OP)
.argManyValue("computers",
manyComputers(),
s -> ComputerCraft.serverComputerRegistry.getComputers())
.executes((context, computers) -> {
int shutdown = 0;
for (ServerComputer computer : unwrap(context.getSource(), computers)) {
if (computer.isOn()) {
shutdown++;
}
computer.shutdown();
}
context.getSource()
.sendFeedback(translate("commands.computercraft.shutdown.done",
shutdown,
computers.size()), false);
return shutdown;
}))
.then( command( "shutdown" )
.requires( UserLevel.OWNER_OP )
.argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() )
.executes( ( context, computers ) -> {
int shutdown = 0;
for( ServerComputer computer : unwrap( context.getSource(), computers ) )
{
if( computer.isOn() ) shutdown++;
computer.shutdown();
}
context.getSource().sendFeedback( translate( "commands.computercraft.shutdown.done", shutdown, computers.size() ), false );
return shutdown;
} ) )
.then(command("turn-on").requires(UserLevel.OWNER_OP)
.argManyValue("computers",
manyComputers(),
s -> ComputerCraft.serverComputerRegistry.getComputers())
.executes((context, computers) -> {
int on = 0;
for (ServerComputer computer : unwrap(context.getSource(), computers)) {
if (!computer.isOn()) {
on++;
}
computer.turnOn();
}
context.getSource()
.sendFeedback(translate("commands.computercraft.turn_on.done",
on,
computers.size()), false);
return on;
}))
.then( command( "turn-on" )
.requires( UserLevel.OWNER_OP )
.argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() )
.executes( ( context, computers ) -> {
int on = 0;
for( ServerComputer computer : unwrap( context.getSource(), computers ) )
{
if( !computer.isOn() ) on++;
computer.turnOn();
}
context.getSource().sendFeedback( translate( "commands.computercraft.turn_on.done", on, computers.size() ), false );
return on;
} ) )
.then(command("tp").requires(UserLevel.OP)
.arg("computer", oneComputer())
.executes(context -> {
ServerComputer computer = getComputerArgument(context, "computer");
World world = computer.getWorld();
BlockPos pos = computer.getPosition();
.then( command( "tp" )
.requires( UserLevel.OP )
.arg( "computer", oneComputer() )
.executes( context -> {
ServerComputer computer = getComputerArgument( context, "computer" );
World world = computer.getWorld();
BlockPos pos = computer.getPosition();
if (world == null || pos == null) {
throw TP_NOT_THERE.create();
}
if( world == null || pos == null ) throw TP_NOT_THERE.create();
Entity entity = context.getSource()
.getEntityOrThrow();
if (!(entity instanceof ServerPlayerEntity)) {
throw TP_NOT_PLAYER.create();
}
Entity entity = context.getSource().getEntityOrThrow();
if( !(entity instanceof ServerPlayerEntity) ) throw TP_NOT_PLAYER.create();
ServerPlayerEntity player = (ServerPlayerEntity) entity;
if (player.getEntityWorld() == world) {
player.networkHandler.teleportRequest(pos.getX() + 0.5,
pos.getY(),
pos.getZ() + 0.5,
0,
0,
EnumSet.noneOf(
PlayerPositionLookS2CPacket.Flag.class));
} else {
player.teleport((ServerWorld) world,
pos.getX() + 0.5,
pos.getY(),
pos.getZ() + 0.5,
0,
0);
}
ServerPlayerEntity player = (ServerPlayerEntity) entity;
if( player.getEntityWorld() == world )
{
player.networkHandler.teleportRequest(
pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0,
EnumSet.noneOf( PlayerPositionLookS2CPacket.Flag.class )
);
}
else
{
player.teleport( (ServerWorld) world,
pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0
);
}
return 1;
}))
return 1;
} ) )
.then(command("queue").requires(UserLevel.ANYONE)
.arg("computer", manyComputers())
.argManyValue("args", StringArgumentType.string(), Collections.emptyList())
.executes((ctx, args) -> {
Collection<ServerComputer> computers = getComputersArgument(ctx, "computer");
Object[] rest = args.toArray();
.then( command( "queue" )
.requires( UserLevel.ANYONE )
.arg( "computer", manyComputers() )
.argManyValue( "args", StringArgumentType.string(), Collections.emptyList() )
.executes( ( ctx, args ) -> {
Collection<ServerComputer> computers = getComputersArgument( ctx, "computer" );
Object[] rest = args.toArray();
int queued = 0;
for (ServerComputer computer : computers) {
if (computer.getFamily() == ComputerFamily.COMMAND && computer.isOn()) {
computer.queueEvent("computer_command", rest);
queued++;
}
}
int queued = 0;
for( ServerComputer computer : computers )
{
if( computer.getFamily() == ComputerFamily.COMMAND && computer.isOn() )
{
computer.queueEvent( "computer_command", rest );
queued++;
}
}
return queued;
}))
return queued;
} ) )
.then(command("view").requires(UserLevel.OP)
.arg("computer", oneComputer())
.executes(context -> {
ServerPlayerEntity player = context.getSource()
.getPlayer();
ServerComputer computer = getComputerArgument(context, "computer");
new ViewComputerContainerData(computer).open(player,
new NamedScreenHandlerFactory() {
@Nonnull
@Override
public Text getDisplayName() {
return new TranslatableText(
"gui.computercraft.view_computer");
}
.then( command( "view" )
.requires( UserLevel.OP )
.arg( "computer", oneComputer() )
.executes( context -> {
ServerPlayerEntity player = context.getSource().getPlayer();
ServerComputer computer = getComputerArgument( context, "computer" );
computer.sendTerminalState( player );
ViewComputerContainerData container = new ViewComputerContainerData( computer );
container.open( player, new ExtendedScreenHandlerFactory()
{
@Override
public void writeScreenOpeningData( ServerPlayerEntity player, PacketByteBuf buf )
{
container.toBytes( buf );
}
@Nonnull
@Override
public ScreenHandler createMenu(int id, @Nonnull PlayerInventory player, @Nonnull PlayerEntity entity) {
return new ContainerViewComputer(
id,
computer);
}
});
return 1;
}))
@Nonnull
@Override
public MutableText getDisplayName()
{
return new TranslatableText( "gui.computercraft.view_computer" );
}
.then(choice("track").then(command("start").requires(UserLevel.OWNER_OP)
.executes(context -> {
getTimingContext(context.getSource()).start();
@Nonnull
@Override
public ScreenHandler createMenu( int id, @Nonnull PlayerInventory player, @Nonnull PlayerEntity entity )
{
return new ContainerViewComputer( id, computer );
}
} );
return 1;
} ) )
String stopCommand = "/computercraft track stop";
context.getSource()
.sendFeedback(translate(
"commands.computercraft.track.start.stop",
link(text(stopCommand),
stopCommand,
translate(
"commands.computercraft.track.stop.action"))),
false);
return 1;
}))
.then( choice( "track" )
.then( command( "start" )
.requires( UserLevel.OWNER_OP )
.executes( context -> {
getTimingContext( context.getSource() ).start();
.then(command("stop").requires(UserLevel.OWNER_OP)
.executes(context -> {
TrackingContext timings = getTimingContext(context.getSource());
if (!timings.stop()) {
throw NOT_TRACKING_EXCEPTION.create();
}
displayTimings(context.getSource(),
timings.getImmutableTimings(),
TrackingField.AVERAGE_TIME,
DEFAULT_FIELDS);
return 1;
}))
String stopCommand = "/computercraft track stop";
context.getSource().sendFeedback( translate( "commands.computercraft.track.start.stop",
link( text( stopCommand ), stopCommand, translate( "commands.computercraft.track.stop.action" ) ) ), false );
return 1;
} ) )
.then(command("dump").requires(UserLevel.OWNER_OP)
.argManyValue("fields", trackingField(), DEFAULT_FIELDS)
.executes((context, fields) -> {
TrackingField sort;
if (fields.size() == 1 && DEFAULT_FIELDS.contains(fields.get(
0))) {
sort = fields.get(0);
fields = DEFAULT_FIELDS;
} else {
sort = fields.get(0);
}
.then( command( "stop" )
.requires( UserLevel.OWNER_OP )
.executes( context -> {
TrackingContext timings = getTimingContext( context.getSource() );
if( !timings.stop() ) throw NOT_TRACKING_EXCEPTION.create();
displayTimings( context.getSource(), timings.getImmutableTimings(), TrackingField.AVERAGE_TIME, DEFAULT_FIELDS );
return 1;
} ) )
return displayTimings(context.getSource(), sort, fields);
}))));
.then( command( "dump" )
.requires( UserLevel.OWNER_OP )
.argManyValue( "fields", trackingField(), DEFAULT_FIELDS )
.executes( ( context, fields ) -> {
TrackingField sort;
if( fields.size() == 1 && DEFAULT_FIELDS.contains( fields.get( 0 ) ) )
{
sort = fields.get( 0 );
fields = DEFAULT_FIELDS;
}
else
{
sort = fields.get( 0 );
}
return displayTimings( context.getSource(), sort, fields );
} ) ) )
);
}
private static Text linkComputer(ServerCommandSource source, ServerComputer serverComputer, int computerId) {
MutableText out = new LiteralText("");
private static MutableText linkComputer( ServerCommandSource source, ServerComputer serverComputer, int computerId )
{
MutableText out = new LiteralText( "" );
// Append the computer instance
if (serverComputer == null) {
out.append(text("?"));
} else {
out.append(link(text(Integer.toString(serverComputer.getInstanceID())),
"/computercraft dump " + serverComputer.getInstanceID(),
translate("commands.computercraft.dump.action")));
if( serverComputer == null )
{
out.append( text( "?" ) );
}
else
{
out.append( link(
text( Integer.toString( serverComputer.getInstanceID() ) ),
"/computercraft dump " + serverComputer.getInstanceID(),
translate( "commands.computercraft.dump.action" )
) );
}
// And ID
out.append(" (id " + computerId + ")");
out.append( " (id " + computerId + ")" );
// And, if we're a player, some useful links
if (serverComputer != null && UserLevel.OP.test(source) && isPlayer(source)) {
out.append(" ")
.append(link(text("\u261b"), "/computercraft tp " + serverComputer.getInstanceID(), translate("commands.computercraft.tp.action")))
.append(" ")
.append(link(text("\u20e2"), "/computercraft view " + serverComputer.getInstanceID(), translate("commands.computercraft.view.action")));
if( serverComputer != null && UserLevel.OP.test( source ) && isPlayer( source ) )
{
out
.append( " " )
.append( link(
text( "\u261b" ),
"/computercraft tp " + serverComputer.getInstanceID(),
translate( "commands.computercraft.tp.action" )
) )
.append( " " )
.append( link(
text( "\u20e2" ),
"/computercraft view " + serverComputer.getInstanceID(),
translate( "commands.computercraft.view.action" )
) );
}
if( UserLevel.OWNER.test( source ) && isPlayer( source ) )
{
MutableText linkPath = linkStorage( computerId );
if( linkPath != null ) out.append( " " ).append( linkPath );
}
return out;
}
private static Text linkPosition(ServerCommandSource context, ServerComputer computer) {
if (UserLevel.OP.test(context)) {
return link(position(computer.getPosition()), "/computercraft tp " + computer.getInstanceID(), translate("commands.computercraft.tp.action"));
} else {
return position(computer.getPosition());
private static MutableText linkPosition( ServerCommandSource context, ServerComputer computer )
{
if( UserLevel.OP.test( context ) )
{
return link(
position( computer.getPosition() ),
"/computercraft tp " + computer.getInstanceID(),
translate( "commands.computercraft.tp.action" )
);
}
else
{
return position( computer.getPosition() );
}
}
private static MutableText linkStorage( int id )
{
File file = new File( IDAssigner.getDir(), "computer/" + id );
if( !file.isDirectory() ) return null;
return link(
text( "\u270E" ),
ClientCommands.OPEN_COMPUTER + id,
translate( "commands.computercraft.dump.open_path" )
);
}
@Nonnull
private static TrackingContext getTimingContext(ServerCommandSource source) {
private static TrackingContext getTimingContext( ServerCommandSource source )
{
Entity entity = source.getEntity();
return entity instanceof PlayerEntity ? Tracking.getContext(entity.getUuid()) : Tracking.getContext(SYSTEM_UUID);
return entity instanceof PlayerEntity ? Tracking.getContext( entity.getUuid() ) : Tracking.getContext( SYSTEM_UUID );
}
private static int displayTimings(ServerCommandSource source, @Nonnull List<ComputerTracker> timings, @Nonnull TrackingField sortField, @Nonnull List<TrackingField> fields) throws CommandSyntaxException {
if (timings.isEmpty()) {
throw NO_TIMINGS_EXCEPTION.create();
}
private static final List<TrackingField> DEFAULT_FIELDS = Arrays.asList( TrackingField.TASKS, TrackingField.TOTAL_TIME, TrackingField.AVERAGE_TIME, TrackingField.MAX_TIME );
private static int displayTimings( ServerCommandSource source, TrackingField sortField, List<TrackingField> fields ) throws CommandSyntaxException
{
return displayTimings( source, getTimingContext( source ).getTimings(), sortField, fields );
}
private static int displayTimings( ServerCommandSource source, @Nonnull List<ComputerTracker> timings, @Nonnull TrackingField sortField, @Nonnull List<TrackingField> fields ) throws CommandSyntaxException
{
if( timings.isEmpty() ) throw NO_TIMINGS_EXCEPTION.create();
Map<Computer, ServerComputer> lookup = new HashMap<>();
int maxId = 0, maxInstance = 0;
for (ServerComputer server : ComputerCraft.serverComputerRegistry.getComputers()) {
lookup.put(server.getComputer(), server);
for( ServerComputer server : ComputerCraft.serverComputerRegistry.getComputers() )
{
lookup.put( server.getComputer(), server );
if (server.getInstanceID() > maxInstance) {
maxInstance = server.getInstanceID();
}
if (server.getID() > maxId) {
maxId = server.getID();
}
if( server.getInstanceID() > maxInstance ) maxInstance = server.getInstanceID();
if( server.getID() > maxId ) maxId = server.getID();
}
timings.sort(Comparator.<ComputerTracker, Long>comparing(x -> x.get(sortField)).reversed());
timings.sort( Comparator.<ComputerTracker, Long>comparing( x -> x.get( sortField ) ).reversed() );
Text[] headers = new Text[1 + fields.size()];
headers[0] = translate("commands.computercraft.track.dump.computer");
for (int i = 0; i < fields.size(); i++) {
headers[i + 1] = translate(fields.get(i)
.translationKey());
}
TableBuilder table = new TableBuilder(TRACK_ID, headers);
MutableText[] headers = new MutableText[1 + fields.size()];
headers[0] = translate( "commands.computercraft.track.dump.computer" );
for( int i = 0; i < fields.size(); i++ ) headers[i + 1] = translate( fields.get( i ).translationKey() );
TableBuilder table = new TableBuilder( TRACK_ID, headers );
for (ComputerTracker entry : timings) {
for( ComputerTracker entry : timings )
{
Computer computer = entry.getComputer();
ServerComputer serverComputer = computer == null ? null : lookup.get(computer);
ServerComputer serverComputer = computer == null ? null : lookup.get( computer );
Text computerComponent = linkComputer(source, serverComputer, entry.getComputerId());
MutableText computerComponent = linkComputer( source, serverComputer, entry.getComputerId() );
Text[] row = new Text[1 + fields.size()];
MutableText[] row = new MutableText[1 + fields.size()];
row[0] = computerComponent;
for (int i = 0; i < fields.size(); i++) {
row[i + 1] = text(entry.getFormatted(fields.get(i)));
}
table.row(row);
for( int i = 0; i < fields.size(); i++ ) row[i + 1] = text( entry.getFormatted( fields.get( i ) ) );
table.row( row );
}
table.display(source);
table.display( source );
return timings.size();
}
private static int displayTimings(ServerCommandSource source, TrackingField sortField, List<TrackingField> fields) throws CommandSyntaxException {
return displayTimings(source, getTimingContext(source).getTimings(), sortField, fields);
}
}

View File

@@ -1,57 +0,0 @@
/*
* 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.shared.command;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.client.MinecraftClient;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
@Environment (EnvType.CLIENT)
public final class CommandCopy {
private static final String PREFIX = "/computercraft copy ";
private CommandCopy() {
}
public static void register(CommandDispatcher<ServerCommandSource> registry) {
registry.register(literal("computercraft").then(literal("copy"))
.then(argument("message", StringArgumentType.greedyString()))
.executes(context -> {
MinecraftClient.getInstance().keyboard.setClipboard(context.getArgument("message", String.class));
return 1;
}));
}
public static boolean onClientSendMessage(String message) {
// Emulate the command on the client side
if (message.startsWith(PREFIX)) {
MinecraftClient.getInstance().keyboard.setClipboard(message.substring(PREFIX.length()));
return true;
}
return false;
}
public static Text createCopyText(String text) {
return new LiteralText(text).fillStyle(Style.EMPTY.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, PREFIX + text))
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
new TranslatableText("gui.computercraft.tooltip.copy"))));
}
}

View File

@@ -3,20 +3,20 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.command;
import java.util.function.Predicate;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import java.util.function.Predicate;
/**
* The level a user must be at in order to execute a command.
*/
public enum UserLevel implements Predicate<ServerCommandSource> {
public enum UserLevel implements Predicate<ServerCommandSource>
{
/**
* Only can be used by the owner of the server: namely the server console or the player in SSP.
*/
@@ -37,37 +37,37 @@ public enum UserLevel implements Predicate<ServerCommandSource> {
*/
ANYONE;
@Override
public boolean test(ServerCommandSource source) {
if (this == ANYONE) {
return true;
public int toLevel()
{
switch( this )
{
case OWNER:
return 4;
case OP:
case OWNER_OP:
return 2;
case ANYONE:
default:
return 0;
}
}
// We *always* allow level 0 stuff, even if the
MinecraftServer server = source.getMinecraftServer();
Entity sender = source.getEntity();
@Override
public boolean test( ServerCommandSource source )
{
if( this == ANYONE ) return true;
if (server.isSinglePlayer() && sender instanceof PlayerEntity && ((PlayerEntity) sender).getGameProfile()
.getName()
.equalsIgnoreCase(server.getServerModName())) {
if (this == OWNER || this == OWNER_OP) {
if( this == OWNER || this == OWNER_OP )
{
MinecraftServer server = source.getMinecraftServer();
Entity sender = source.getEntity();
if( server.isSinglePlayer() && sender instanceof PlayerEntity &&
((PlayerEntity) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerModName() ) )
{
return true;
}
}
return source.hasPermissionLevel(this.toLevel());
}
public int toLevel() {
switch (this) {
case OWNER:
return 4;
case OP:
case OWNER_OP:
return 2;
case ANYONE:
default:
return 0;
}
return source.hasPermissionLevel( toLevel() );
}
}

View File

@@ -3,81 +3,101 @@
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.command.text;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.LiteralText;
import net.minecraft.text.MutableText;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.minecraft.text.*;
import net.minecraft.util.Formatting;
import net.minecraft.util.math.BlockPos;
/**
* Various helpers for building chat messages.
*/
public final class ChatHelpers {
public final class ChatHelpers
{
private static final Formatting HEADER = Formatting.LIGHT_PURPLE;
private ChatHelpers() {}
public static MutableText text(String text) {
return new LiteralText(text == null ? "" : text);
public static MutableText coloured( String text, Formatting colour )
{
MutableText component = new LiteralText( text == null ? "" : text );
component.setStyle( component.getStyle().withColor( colour ) );
return component;
}
public static MutableText list(Text... children) {
MutableText component = new LiteralText("");
for (Text child : children) {
component.append(child);
public static <T extends MutableText> T coloured( T component, Formatting colour )
{
component.setStyle( component.getStyle().withColor( colour ) );
return component;
}
public static MutableText text( String text )
{
return new LiteralText( text == null ? "" : text );
}
public static MutableText translate( String text )
{
return new TranslatableText( text == null ? "" : text );
}
public static MutableText translate( String text, Object... args )
{
return new TranslatableText( text == null ? "" : text, args );
}
public static MutableText list( MutableText... children )
{
MutableText component = new LiteralText( "" );
for( MutableText child : children )
{
component.append( child );
}
return component;
}
public static MutableText position(BlockPos pos) {
if (pos == null) {
return translate("commands.computercraft.generic.no_position");
}
return translate("commands.computercraft.generic.position", pos.getX(), pos.getY(), pos.getZ());
public static MutableText position( BlockPos pos )
{
if( pos == null ) return translate( "commands.computercraft.generic.no_position" );
return translate( "commands.computercraft.generic.position", pos.getX(), pos.getY(), pos.getZ() );
}
public static MutableText translate(String text) {
return new TranslatableText(text == null ? "" : text);
public static MutableText bool( boolean value )
{
return value
? coloured( translate( "commands.computercraft.generic.yes" ), Formatting.GREEN )
: coloured( translate( "commands.computercraft.generic.no" ), Formatting.RED );
}
public static MutableText translate(String text, Object... args) {
return new TranslatableText(text == null ? "" : text, args);
public static MutableText link( MutableText component, String command, MutableText toolTip )
{
return link( component, new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ), toolTip );
}
public static MutableText bool(boolean value) {
return value ? coloured(translate("commands.computercraft.generic.yes"),
Formatting.GREEN) : coloured(translate("commands.computercraft.generic.no"), Formatting.RED);
}
public static <T extends MutableText> T coloured(T component, Formatting colour) {
component.formatted(colour);
return component;
}
public static MutableText link(MutableText component, String command, Text toolTip) {
public static MutableText link( MutableText component, ClickEvent click, MutableText toolTip )
{
Style style = component.getStyle();
if (style.getColor() == null) {
style = style.withColor(Formatting.YELLOW);
}
style = style.withClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command));
style = style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, toolTip));
if( style.getColor() == null ) style = style.withColor( Formatting.YELLOW );
style = style.withClickEvent( click );
style = style.withHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, toolTip ) );
return component.setStyle(style);
component.setStyle(style);
return component;
}
public static MutableText header(String text) {
return coloured(text, HEADER);
public static MutableText header( String text )
{
return coloured( text, HEADER );
}
public static MutableText coloured(String text, Formatting colour) {
return new LiteralText(text == null ? "" : text).formatted(colour);
public static MutableText copy( String text )
{
LiteralText name = new LiteralText( text );
name.setStyle( name.getStyle()
.withClickEvent( new ClickEvent( ClickEvent.Action.COPY_TO_CLIPBOARD, text ) )
.withHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new TranslatableText( "gui.computercraft.tooltip.copy" ) ) ) );
return name;
}
}

View File

@@ -72,10 +72,11 @@ public final class ColourableRecipe extends SpecialCraftingRecipe {
}
}
if (colourable.isEmpty()) {
return ItemStack.EMPTY;
}
return ((IColouredItem) colourable.getItem()).withColour(colourable, tracker.getColour());
if( colourable.isEmpty() ) return ItemStack.EMPTY;
ItemStack stack = ((IColouredItem) colourable.getItem()).withColour( colourable, tracker.getColour() );
stack.setCount( 1 );
return stack;
}
@Override

View File

@@ -28,6 +28,9 @@ public abstract class TileGeneric extends BlockEntity implements BlockEntityClie
public void destroy() {
}
public void onChunkUnloaded() {
}
public final void updateBlock() {
this.markDirty();
BlockPos pos = this.getPos();

View File

@@ -77,6 +77,12 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
}
}
@Override
public void onChunkUnloaded()
{
unload();
}
protected void unload() {
if (this.m_instanceID >= 0) {
if (!this.getWorld().isClient) {

View File

@@ -37,7 +37,7 @@ public class ContainerComputerBase extends ScreenHandler implements IContainerCo
id,
x -> true,
getComputer(player, new ComputerContainerData(new PacketByteBuf(packetByteBuf.copy()))),
new ComputerContainerData(packetByteBuf).getFamily());
new ComputerContainerData(new PacketByteBuf(packetByteBuf.copy())).getFamily());
}
protected ContainerComputerBase(ScreenHandlerType<? extends ContainerComputerBase> type, int id, Predicate<PlayerEntity> canUse, IComputer computer,

View File

@@ -29,6 +29,13 @@ public class ContainerViewComputer extends ContainerComputerBase implements ICon
this.width = this.height = 0;
}
public ContainerViewComputer(int id, PlayerInventory player, PacketByteBuf packetByteBuf) {
super(ComputerCraftRegistry.ModContainers.VIEW_COMPUTER, id, player, packetByteBuf);
ViewComputerContainerData data = new ViewComputerContainerData(new PacketByteBuf(packetByteBuf.copy()));
this.width = data.getWidth();
this.height = data.getHeight();
}
private static boolean canInteractWith(@Nonnull ServerComputer computer, @Nonnull PlayerEntity player) {
// If this computer no longer exists then discard it.
if (ComputerCraft.serverComputerRegistry.get(computer.getInstanceID()) != computer) {
@@ -39,13 +46,6 @@ public class ContainerViewComputer extends ContainerComputerBase implements ICon
return computer.getFamily() != ComputerFamily.COMMAND || TileCommandComputer.isUsable(player);
}
public ContainerViewComputer(int id, PlayerInventory player, PacketByteBuf packetByteBuf) {
super(ComputerCraftRegistry.ModContainers.VIEW_COMPUTER, id, player, packetByteBuf);
ViewComputerContainerData data = new ViewComputerContainerData((PacketByteBuf) packetByteBuf.copy());
this.width = data.getWidth();
this.height = data.getHeight();
}
public int getWidth() {
return this.width;
}

View File

@@ -10,6 +10,7 @@ import javax.annotation.Nonnull;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.fabric.mixin.MusicDiscItemAccessor;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.MusicDiscItem;
@@ -42,6 +43,10 @@ public final class RecordMedia implements IMedia {
@Override
public SoundEvent getAudio(@Nonnull ItemStack stack) {
return ((MusicDiscItem) stack.getItem()).getSound();
Item item = stack.getItem();
if (!(item instanceof MusicDiscItem)) {
return null;
}
return ((MusicDiscItemAccessor) item).getSound();
}
}

View File

@@ -1,17 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.mixed;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Arm;
public interface MixedFirstPersonRenderer {
void renderArmFirstPerson_CC(MatrixStack stack, VertexConsumerProvider consumerProvider, int light, float equip, float swing, Arm hand);
float getMapAngleFromPitch_CC(float pitch);
}

View File

@@ -8,7 +8,7 @@ package dan200.computercraft.shared.network.client;
import javax.annotation.Nonnull;
import dan200.computercraft.mixin.SoundEventAccess;
import dan200.computercraft.fabric.mixin.SoundEventAccess;
import dan200.computercraft.shared.network.NetworkMessage;
import net.minecraft.client.MinecraftClient;

View File

@@ -37,8 +37,8 @@ public class ViewComputerContainerData extends ComputerContainerData {
}
public ViewComputerContainerData(PacketByteBuf packetByteBuf) {
super(packetByteBuf);
this.toBytes(packetByteBuf);
super(new PacketByteBuf(packetByteBuf.copy()));
this.fromBytes(packetByteBuf);
}
@Override

View File

@@ -7,14 +7,20 @@ package dan200.computercraft.shared.peripheral.generic.data;
import com.google.gson.JsonParseException;
import dan200.computercraft.shared.util.NBTUtil;
import net.fabricmc.fabric.api.tag.TagRegistry;
import net.fabricmc.fabric.impl.tag.extension.TagDelegate;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.item.EnchantedBookItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.ListTag;
import net.minecraft.tag.ServerTagManagerHolder;
import net.minecraft.tag.Tag;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -51,7 +57,7 @@ public class ItemData
fillBasic( data, stack );
data.put( "displayName", stack.toHoverableText().getString() );
data.put( "displayName", stack.getName().getString() );
data.put( "maxCount", stack.getMaxCount() );
if( stack.isDamageable() )
@@ -81,6 +87,8 @@ public class ItemData
data.put( "unbreakable", true );
}
data.put("tags", DataHelpers.getTags( ServerTagManagerHolder.getTagManager().getItems().getTagsFor(stack.getItem()) )); // chaos
return data;
}

View File

@@ -5,30 +5,20 @@
*/
package dan200.computercraft.shared.peripheral.generic.methods;
import com.google.auto.service.AutoService;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.GenericSource;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.asm.GenericSource;
import dan200.computercraft.shared.peripheral.generic.data.ItemData;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.ItemStorage;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.ChestBlock;
import net.minecraft.block.InventoryProvider;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.ChestBlockEntity;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.ItemStack;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Identifier;
import net.minecraft.util.Nameable;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -43,14 +33,13 @@ import static dan200.computercraft.shared.peripheral.generic.methods.ArgumentHel
*
* @cc.module inventory
*/
@AutoService( GenericSource.class )
public class InventoryMethods implements GenericSource
{
@Nonnull
@Override
public Identifier id()
{
return new Identifier(ComputerCraft.MOD_ID, "inventory" );
return new Identifier( ComputerCraft.MOD_ID, "inventory" );
}
/**
@@ -85,8 +74,9 @@ public class InventoryMethods implements GenericSource
* List all items in this inventory. This returns a table, with an entry for each slot.
*
* Each item in the inventory is represented by a table containing some basic information, much like
* @link dan200.computercraft.shared.turtle.apis.TurtleAPI#getItemDetail includes. More information can be fetched
* with {@link #getItemDetail}.
* {@link dan200.computercraft.shared.turtle.apis.TurtleAPI#getItemDetail} includes. More information can be fetched
* with {@link #getItemDetail}. The table contains the item `name`, the `count` and an a (potentially nil) hash of
* the item's `nbt.` This NBT data doesn't contain anything useful, but allows you to distinguish identical items.
*
* The returned table is sparse, and so empty slots will be `nil` - it is recommended to loop over using `pairs`
* rather than `ipairs`.
@@ -94,6 +84,14 @@ public class InventoryMethods implements GenericSource
* @param inventory The current inventory.
* @return All items in this inventory.
* @cc.treturn { (table|nil)... } All items in this inventory.
* @cc.usage Find an adjacent chest and print all items in it.
*
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* for slot, item in pairs(chest.list()) do
* print(("%d x %s in slot %d"):format(item.count, item.name, slot))
* end
* }</pre>
*/
@LuaFunction( mainThread = true )
public static Map<Integer, Map<String, ?>> list( Inventory inventory )
@@ -114,11 +112,32 @@ public class InventoryMethods implements GenericSource
/**
* Get detailed information about an item.
*
* The returned information contains the same information as each item in
* {@link #list}, as well as additional details like the display name
* (`displayName`) and item durability (`damage`, `maxDamage`, `durability`).
*
* Some items include more information (such as enchantments) - it is
* recommended to print it out using @{textutils.serialize} or in the Lua
* REPL, to explore what is available.
*
* @param inventory The current inventory.
* @param slot The slot to get information about.
* @return Information about the item in this slot, or {@code nil} if not present.
* @throws LuaException If the slot is out of range.
* @cc.treturn table Information about the item in this slot, or {@code nil} if not present.
* @cc.usage Print some information about the first in a chest.
*
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* local item = chest.getItemDetail(1)
* if not item then print("No item") return end
*
* print(("%s (%s)"):format(item.displayName, item.name))
* print(("Count: %d/%d"):format(item.count, item.maxCount))
* if item.damage then
* print(("Damage: %d/%d"):format(item.damage, item.maxDamage))
* end
* }</pre>
*/
@Nullable
@LuaFunction( mainThread = true )
@@ -132,6 +151,33 @@ public class InventoryMethods implements GenericSource
return stack.isEmpty() ? null : ItemData.fill( new HashMap<>(), stack );
}
/**
* Get the maximum number of items which can be stored in this slot.
*
* Typically this will be limited to 64 items. However, some inventories (such as barrels or caches) can store
* hundreds or thousands of items in one slot.
*
* @param inventory Inventory to probe.
* @param slot The slot
* @return The maximum number of items in this slot.
* @throws LuaException If the slot is out of range.
* @cc.usage Count the maximum number of items an adjacent chest can hold.
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* local total = 0
* for i = 1, chest.size() do
* total = total + chest.getItemLimit(i)
* end
* print(total)
* }</pre>
*/
@LuaFunction( mainThread = true )
public static int getItemLimit( Inventory inventory, int slot ) throws LuaException
{
assertBetween( slot, 1, inventory.size(), "Slot out of range (%s)" );
return inventory.getMaxCountPerStack();
}
/**
* Push items from one inventory to another connected one.
*

View File

@@ -19,7 +19,7 @@ import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralTile;
import dan200.computercraft.shared.ComputerCraftRegistry;
import dan200.computercraft.shared.command.CommandCopy;
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.util.DirectionUtil;
@@ -83,6 +83,13 @@ public class TileCable extends TileGeneric implements IPeripheralTile {
}
}
@Override
public void onChunkUnloaded()
{
super.onChunkUnloaded();
onRemove();
}
private void onRemove() {
if (this.world == null || !this.world.isClient) {
this.m_node.remove();
@@ -109,11 +116,12 @@ public class TileCable extends TileGeneric implements IPeripheralTile {
String newName = this.m_peripheral.getConnectedName();
if (!Objects.equal(newName, oldName)) {
if (oldName != null) {
player.sendMessage(new TranslatableText("chat.computercraft.wired_modem.peripheral_disconnected", CommandCopy.createCopyText(oldName)),
false);
player.sendMessage(new TranslatableText("chat.computercraft.wired_modem.peripheral_disconnected",
ChatHelpers.copy(oldName)), false);
}
if (newName != null) {
player.sendMessage(new TranslatableText("chat.computercraft.wired_modem.peripheral_connected", CommandCopy.createCopyText(newName)), false);
player.sendMessage(new TranslatableText("chat.computercraft.wired_modem.peripheral_connected",
ChatHelpers.copy(newName)), false);
}
}

View File

@@ -27,7 +27,7 @@ import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralTile;
import dan200.computercraft.shared.command.CommandCopy;
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.util.DirectionUtil;
@@ -74,6 +74,13 @@ public class TileWiredModemFull extends TileGeneric implements IPeripheralTile {
super.destroy();
}
@Override
public void onChunkUnloaded()
{
super.onChunkUnloaded();
doRemove();
}
private void doRemove() {
if (this.world == null || !this.world.isClient) {
this.m_node.remove();
@@ -260,7 +267,7 @@ public class TileWiredModemFull extends TileGeneric implements IPeripheralTile {
if (i > 0) {
base.append(", ");
}
base.append(CommandCopy.createCopyText(names.get(i)));
base.append(ChatHelpers.copy(names.get(i)));
}
player.sendMessage(new TranslatableText(kind, base), false);

View File

@@ -9,6 +9,7 @@ package dan200.computercraft.shared.peripheral.monitor;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import dan200.computercraft.api.turtle.FakePlayer;
import dan200.computercraft.shared.common.BlockGeneric;
import net.minecraft.block.Block;
@@ -71,9 +72,14 @@ public class BlockMonitor extends BlockGeneric {
BlockEntity entity = world.getBlockEntity(pos);
if (entity instanceof TileMonitor && !world.isClient) {
TileMonitor monitor = (TileMonitor) entity;
monitor.contractNeighbours();
monitor.contract();
monitor.expand();
// Defer the block update if we're being placed by another TE. See #691
if ( livingEntity == null || livingEntity instanceof FakePlayer )
{
monitor.updateNeighborsDeferred();
return;
}
monitor.updateNeighbors();
}
}

View File

@@ -54,6 +54,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile {
private ServerMonitor m_serverMonitor;
private ClientMonitor m_clientMonitor;
private MonitorPeripheral peripheral;
private boolean needsUpdate = false;
private boolean m_destroyed = false;
private boolean visiting = false;
private int m_width = 1;
@@ -78,6 +79,23 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile {
}
}
@Override
public void markRemoved() {
super.markRemoved();
if (this.m_clientMonitor != null && this.m_xIndex == 0 && this.m_yIndex == 0) {
this.m_clientMonitor.destroy();
}
}
@Override
public void onChunkUnloaded() {
super.onChunkUnloaded();
if (this.m_clientMonitor != null && this.m_xIndex == 0 && this.m_yIndex == 0) {
this.m_clientMonitor.destroy();
}
this.m_clientMonitor = null;
}
@Nonnull
@Override
public ActionResult onActivate(PlayerEntity player, Hand hand, BlockHitResult hit) {
@@ -98,6 +116,12 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile {
@Override
public void blockTick() {
if ( needsUpdate )
{
needsUpdate = false;
updateNeighbors();
}
if (this.m_xIndex != 0 || this.m_yIndex != 0 || this.m_serverMonitor == null) {
return;
}
@@ -255,15 +279,6 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile {
return ComputerCraft.monitorDistanceSq;
}
@Override
@Environment (EnvType.CLIENT)
public void markRemoved() {
super.markRemoved();
if (this.m_clientMonitor != null && this.m_xIndex == 0 && this.m_yIndex == 0) {
this.m_clientMonitor.destroy();
}
}
// Sizing and placement stuff
@Override
@@ -530,6 +545,18 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile {
return true;
}
void updateNeighborsDeferred()
{
needsUpdate = true;
}
void updateNeighbors()
{
contractNeighbours();
contract();
expand();
}
@SuppressWarnings ("StatementWithEmptyBody")
void expand() {
while (this.mergeLeft() || this.mergeRight() || this.mergeUp() || this.mergeDown()) {

View File

@@ -18,7 +18,7 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.mixin.SoundEventAccess;
import dan200.computercraft.fabric.mixin.SoundEventAccess;
import net.minecraft.block.enums.Instrument;
import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket;
@@ -100,13 +100,13 @@ public abstract class SpeakerPeripheral implements IPeripheral {
float adjVolume = Math.min(volume, 3.0f);
server.getPlayerManager()
.sendToAround(null,
pos.x,
pos.y,
pos.z,
adjVolume > 1.0f ? 16 * adjVolume : 16.0,
world.getRegistryKey(),
new PlaySoundIdS2CPacket(name, SoundCategory.RECORDS, pos, adjVolume, pitch));
.sendToAround(null,
pos.x,
pos.y,
pos.z,
adjVolume > 1.0f ? 16 * adjVolume : 16.0,
world.getRegistryKey(),
new PlaySoundIdS2CPacket(name, SoundCategory.RECORDS, pos, adjVolume, pitch));
return null;
});
@@ -141,7 +141,7 @@ public abstract class SpeakerPeripheral implements IPeripheral {
Instrument instrument = null;
for (Instrument testInstrument : Instrument.values()) {
if (testInstrument.asString()
.equalsIgnoreCase(name)) {
.equalsIgnoreCase(name)) {
instrument = testInstrument;
break;
}
@@ -154,10 +154,10 @@ public abstract class SpeakerPeripheral implements IPeripheral {
// If the resource location for note block notes changes, this method call will need to be updated
boolean success = this.playSound(context,
((SoundEventAccess)instrument.getSound()).getId(),
volume,
(float) Math.pow(2.0, (pitch - 12.0) / 12.0),
true);
((SoundEventAccess)instrument.getSound()).getId(),
volume,
(float) Math.pow(2.0, (pitch - 12.0) / 12.0),
true);
if (success) {
this.m_notesThisTick.incrementAndGet();
}

View File

@@ -17,6 +17,7 @@ import dan200.computercraft.shared.TurtlePermissions;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.command.arguments.ArgumentSerializers;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
@@ -28,6 +29,7 @@ import dan200.computercraft.shared.turtle.FurnaceRefuelHandler;
import dan200.computercraft.shared.turtle.SignInspectHandler;
import dan200.computercraft.shared.util.TickScheduler;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.CommandBlockBlockEntity;
import net.minecraft.item.Item;
@@ -106,6 +108,12 @@ public final class ComputerCraftProxyCommon {
ComputerCraftProxyCommon.server = null;
});
ServerBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register( ( blockEntity, world ) -> {
if(blockEntity instanceof TileGeneric ) {
((TileGeneric)blockEntity).onChunkUnloaded();
}
});
TurtleEvent.EVENT_BUS.register(FurnaceRefuelHandler.INSTANCE);
TurtleEvent.EVENT_BUS.register(new TurtlePermissions());
TurtleEvent.EVENT_BUS.register(new SignInspectHandler());

View File

@@ -6,7 +6,7 @@ import java.util.Map;
import com.google.common.eventbus.Subscribe;
import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
import dan200.computercraft.mixin.SignBlockEntityAccess;
import dan200.computercraft.fabric.mixin.SignBlockEntityAccess;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.SignBlockEntity;

View File

@@ -26,6 +26,7 @@ import dan200.computercraft.api.turtle.event.TurtleInspectItemEvent;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.asm.TaskCallback;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.peripheral.generic.data.ItemData;
import dan200.computercraft.shared.turtle.core.InteractDirection;
import dan200.computercraft.shared.turtle.core.MoveDirection;
import dan200.computercraft.shared.turtle.core.TurnDirection;
@@ -660,6 +661,18 @@ public class TurtleAPI implements ILuaAPI {
* @return The turtle command result.
* @cc.treturn boolean Whether there is a block in front of the turtle.
* @cc.treturn table|string Information about the block in front, or a message explaining that there is no block.
* @cc.usage <pre>{@code
* local has_block, data = turtle.inspect()
* if has_block then
* print(textutils.serialize(data))
* -- {
* -- name = "minecraft:oak_log",
* -- state = { axis = "x" },
* -- tags = { ["minecraft:logs"] = true, ... },
* -- }
* else
* print("No block in front of the turtle")
* end}</pre>
*/
@LuaFunction
public final MethodResult inspect() {
@@ -699,6 +712,7 @@ public class TurtleAPI implements ILuaAPI {
* cost of taking longer to run.
* @return The command result.
* @throws LuaException If the slot is out of range.
* @see InventoryMethods#getItemDetail Describes the information returned by a detailed query.
* @cc.treturn nil|table Information about the given slot, or {@code nil} if it is empty.
* @cc.usage Print the current slot, assuming it contains 13 dirt.
*
@@ -724,13 +738,9 @@ public class TurtleAPI implements ILuaAPI {
return new Object[] {null};
}
Item item = stack.getItem();
String name = Registry.ITEM.getId(item)
.toString();
int count = stack.getCount();
Map<String, Object> table = new HashMap<>();
table.put("name", name);
table.put("count", count);
Map<String, Object> table = detailed
? ItemData.fill( new HashMap<>(), stack )
: ItemData.fillBasicSafe( new HashMap<>(), stack );
TurtleActionEvent event = new TurtleInspectItemEvent(this.turtle, stack, table, detailed);
if (TurtleEvent.post(event)) {

View File

@@ -21,7 +21,7 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.mixin.WorldSavePathAccess;
import dan200.computercraft.fabric.mixin.WorldSavePathAccess;
import me.shedaniel.cloth.api.utils.v1.GameInstanceUtils;
import net.minecraft.server.MinecraftServer;

View File

@@ -1,29 +0,0 @@
/*
* 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.shared.util;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import org.objectweb.asm.Type;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public final class ServiceUtil
{
private static final Type AUTO_SERVICE = Type.getType( "Lcom/google/auto/service/AutoService;" );
private ServiceUtil()
{
}
public static <T> Stream<T> loadServices( Class<T> target )
{
return StreamSupport.stream( ServiceLoader.load( target, ServiceUtil.class.getClassLoader() ).spliterator(), false );
}
}

View File

@@ -49,6 +49,7 @@
"commands.computercraft.dump.synopsis": "Display the status of computers.",
"commands.computercraft.dump.desc": "Display the status of all computers or specific information about one computer. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\").",
"commands.computercraft.dump.action": "View more info about this computer",
"commands.computercraft.dump.open_path": "View this computer's files",
"commands.computercraft.shutdown.synopsis": "Shutdown computers remotely.",
"commands.computercraft.shutdown.desc": "Shutdown the listed computers or all if none are specified. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\").",
"commands.computercraft.shutdown.done": "Shutdown %s/%s computers",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 B

After

Width:  |  Height:  |  Size: 347 B

View File

@@ -1,6 +1,6 @@
{
"required": true,
"package": "dan200.computercraft.mixin",
"package": "dan200.computercraft.fabric.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"MinecraftServerAccess",
@@ -10,7 +10,9 @@
"MixinWorld",
"SignBlockEntityAccess",
"SoundEventAccess",
"WorldSavePathAccess"
"WorldSavePathAccess",
"MixinServerPlayerInteractionManager",
"MusicDiscItemAccessor"
],
"client": [
"AffineTransformationAccess",
@@ -19,7 +21,7 @@
"HeldItemRendererAccess",
"MixinHeldItemRenderer",
"MixinItemFrameEntityRenderer",
"MixinMinecraftGame",
"MixinMinecraftClient",
"MixinScreen",
"MixinWorldRenderer"
],

View File

@@ -16,22 +16,7 @@ end
if _VERSION == "Lua 5.1" then
-- If we're on Lua 5.1, install parts of the Lua 5.2/5.3 API so that programs can be written against it
local type = type
local nativeload = load
local nativeloadstring = loadstring
local nativesetfenv = setfenv
-- Historically load/loadstring would handle the chunk name as if it has
-- been prefixed with "=". We emulate that behaviour here.
local function prefix(chunkname)
if type(chunkname) ~= "string" then return chunkname end
local head = chunkname:sub(1, 1)
if head == "=" or head == "@" then
return chunkname
else
return "=" .. chunkname
end
end
function load(x, name, mode, env)
expect(1, x, "function", "string")
@@ -40,29 +25,11 @@ if _VERSION == "Lua 5.1" then
expect(4, env, "table", "nil")
local ok, p1, p2 = pcall(function()
if type(x) == "string" then
local result, err = nativeloadstring(x, name)
if result then
if env then
env._ENV = env
nativesetfenv(result, env)
end
return result
else
return nil, err
end
else
local result, err = nativeload(x, name)
if result then
if env then
env._ENV = env
nativesetfenv(result, env)
end
return result
else
return nil, err
end
local result, err = nativeload(x, name, mode, env)
if result and env then
env._ENV = env
end
return result, err
end)
if ok then
return p1, p2
@@ -81,7 +48,7 @@ if _VERSION == "Lua 5.1" then
math.log10 = nil
table.maxn = nil
else
loadstring = function(string, chunkname) return nativeloadstring(string, prefix(chunkname)) end
loadstring = function(string, chunkname) return nativeload(string, chunkname) end
-- Inject a stub for the old bit library
_G.bit = {

View File

@@ -1,4 +1,22 @@
New features in CC: Restitched 1.95.3
# New features in CC: Restitched 1.96.0
* Use lightGrey for folders within the "list" program.
* Add getLimit to inventory peripherals.
* Expose the generic peripheral system to the public API.
* Add cc.expect.range (Lupus590).
* Allow calling cc.expect directly (MCJack123).
* Numerous improvements to documentation.
And several bug fixes:
* Fix paintutils.drawLine incorrectly sorting coordinates (lilyzeiset).
* Improve JEI's handling of turtle/pocket upgrade recipes.
* Correctly handle sparse arrays in cc.pretty.
* Fix crashes when a turtle places a monitor (baeuric).
* Fix very large resource files being considered empty.
* Allow turtles to use compostors.
* Fix dupe bug when colouring turtles.
# New features in CC: Restitched 1.95.3
Several bug fixes:
* Correctly serialise sparse arrays into JSON (livegamer999)

View File

@@ -1,9 +1,16 @@
New features in CC: Restitched 1.95.3
New features in CC: Restitched 1.96.0
Several bug fixes:
* Correctly serialise sparse arrays into JSON (livegamer999)
* Fix rs.getBundledInput returning the output instead (SkyTheCodeMaster)
* Programs run via edit are now a little better behaved (Wojbie)
* Add User-Agent to a websocket's headers.
* Use lightGrey for folders within the "list" program.
* Add getLimit to inventory peripherals.
* Expose the generic peripheral system to the public API.
* Add cc.expect.range (Lupus590).
* Allow calling cc.expect directly (MCJack123).
* Shift Right clicking a disk drive while holding a disk inserts disk
* `getItemDetails()` now returns full output as one would expect from CC:T
And several bug fixes:
* Fix paintutils.drawLine incorrectly sorting coordinates (lilyzeiset).
* Fix crash with music disk
* Fix Display peripheral chat messages in multiplayer and improve /computercraft command.
Type "help changelog" to see the full version history.

View File

@@ -88,7 +88,34 @@ local function field(tbl, index, ...)
end
end
return {
local function is_nan(num)
return num ~= num
end
--- Expect a number to be within a specific range.
--
-- @tparam number num, The value to check.
-- @tparam number min The minimum value, if nil then `-math.huge` is used.
-- @tparam number max The maximum value, if nil then `math.huge` is used.
-- @return The given `value`.
-- @throws If the value is outside of the allowed range.
local function range(num, min, max)
expect(1, num, "number")
min = expect(2, min, "number", "nil") or -math.huge
max = expect(3, max, "number", "nil") or math.huge
if min > max then
error("min must be less than or equal to max)", 2)
end
if is_nan(num) or num < min or num > max then
error(("number outside of range (expected %s to be within %s and %s)"):format(num, min, max), 3)
end
return num
end
return setmetatable({
expect = expect,
field = field,
}
range = range,
}, { __call = function(_, ...) return expect(...) end })

View File

@@ -409,18 +409,24 @@ local function pretty_impl(obj, options, tracking)
local doc = setmetatable({ tag = "concat", n = 1, space_line }, Doc)
local length, keys, keysn = #obj, {}, 1
for k in pairs(obj) do keys[keysn], keysn = k, keysn + 1 end
for k in pairs(obj) do
if type(k) ~= "number" or k % 1 ~= 0 or k < 1 or k > length then
keys[keysn], keysn = k, keysn + 1
end
end
table.sort(keys, key_compare)
for i = 1, keysn - 1 do
for i = 1, length do
if i > 1 then append(doc, comma) append(doc, space_line) end
append(doc, pretty_impl(obj[i], options, tracking))
end
for i = 1, keysn - 1 do
if i > 1 or length >= 1 then append(doc, comma) append(doc, space_line) end
local k = keys[i]
local v = obj[k]
local ty = type(k)
if ty == "number" and k % 1 == 0 and k >= 1 and k <= length then
append(doc, pretty_impl(v, options, tracking))
elseif ty == "string" and not keywords[k] and k:match("^[%a_][%a%d_]*$") then
if type(k) == "string" and not keywords[k] and k:match("^[%a_][%a%d_]*$") then
append(doc, text(k .. " = "))
append(doc, pretty_impl(v, options, tracking))
else

View File

@@ -33,5 +33,5 @@ table.sort(tFiles)
if term.isColour() then
textutils.pagedTabulate(colors.green, tDirs, colors.white, tFiles)
else
textutils.pagedTabulate(tDirs, tFiles)
textutils.pagedTabulate(colors.lightGray, tDirs, colors.white, tFiles)
end

View File

@@ -16,7 +16,8 @@
"SquidDev",
"parly",
"Merith.TK",
"Jummit"
"Jummit",
"Toad-Dev"
],
"depends": {
"fabricloader": ">=0.4.0",

View File

@@ -1,59 +0,0 @@
package badhtmlparser;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Iterator;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Multimap;
import com.google.common.collect.PeekingIterator;
public class BadHtmlParser {
private static final Multimap<String, String> IDS = HashMultimap.create();
public static void main(String[] args) throws FileNotFoundException {
BufferedReader reader = new BufferedReader(new InputStreamReader(BadHtmlParser.class.getResourceAsStream("/tags.html")));
PeekingIterator<String> iterator = Iterators.peekingIterator(reader.lines()
.iterator());
while (iterator.hasNext()) {
String str = iterator.peek();
try {
int count = Integer.parseInt(str);
iterator.next();
write(iterator, count);
} catch (NumberFormatException e) {
write(iterator, 1);
}
}
System.out.println(IDS);
print("iron_ingots");
// todo replace dyes by hand
}
private static void print(String val) throws FileNotFoundException {
FileOutputStream fos = new FileOutputStream("src/main/resources/data/c/tags/items/" + val + ".json");
PrintStream stream = new PrintStream(fos);
stream.println("{\"replace\": false,\"values\":[");
Iterator<String> iterator = IDS.get("c:" + val)
.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
stream.printf("\"%s\"%s", s, iterator.hasNext() ? ',' : "");
}
stream.println("]}");
}
private static void write(Iterator<String> str, int entries) {
String tag = str.next();
for (int i = 0; i < entries; i++) {
IDS.put(tag, str.next());
}
}
}

View File

@@ -1,489 +0,0 @@
/*
* 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;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.BasicEnvironment;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.function.Executable;
import org.opentest4j.AssertionFailedError;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Stream;
import static dan200.computercraft.api.lua.LuaValues.getType;
/**
* Loads tests from {@code test-rom/spec} and executes them.
*
* This spins up a new computer and runs the {@code mcfly.lua} script. This will then load all files in the {@code spec}
* directory and register them with {@code cct_test.start}.
*
* From the test names, we generate a tree of {@link DynamicNode}s which queue an event and wait for
* {@code cct_test.submit} to be called. McFly pulls these events, executes the tests and then calls the submit method.
*
* Once all tests are done, we invoke {@code cct_test.finish} in order to mark everything as complete.
*/
public class ComputerTestDelegate
{
private static final File REPORT_PATH = new File( "test-files/luacov.report.out" );
private static final Logger LOG = LogManager.getLogger( ComputerTestDelegate.class );
private static final long TICK_TIME = TimeUnit.MILLISECONDS.toNanos( 50 );
private static final long TIMEOUT = TimeUnit.SECONDS.toNanos( 10 );
private final ReentrantLock lock = new ReentrantLock();
private Computer computer;
private final Condition hasTests = lock.newCondition();
private DynamicNodeBuilder tests;
private final Condition hasRun = lock.newCondition();
private String currentTest;
private boolean runFinished;
private Throwable runResult;
private final Condition hasFinished = lock.newCondition();
private boolean finished = false;
private Map<String, Map<Double, Double>> finishedWith;
@BeforeEach
public void before() throws IOException
{
ComputerCraft.logPeripheralErrors = true;
if( REPORT_PATH.delete() ) ComputerCraft.log.info( "Deleted previous coverage report." );
Terminal term = new Terminal( 78, 20 );
IWritableMount mount = new FileMount( new File( "test-files/mount" ), 10_000_000 );
// Remove any existing files
List<String> children = new ArrayList<>();
mount.list( "", children );
for( String child : children ) mount.delete( child );
// And add our startup file
try( WritableByteChannel channel = mount.openForWrite( "startup.lua" );
Writer writer = Channels.newWriter( channel, StandardCharsets.UTF_8.newEncoder(), -1 ) )
{
writer.write( "loadfile('test-rom/mcfly.lua', nil, _ENV)('test-rom/spec') cct_test.finish()" );
}
computer = new Computer( new BasicEnvironment( mount ), term, 0 );
computer.getEnvironment().setPeripheral( ComputerSide.TOP, new FakeModem() );
computer.addApi( new CctTestAPI() );
computer.turnOn();
}
@AfterEach
public void after() throws InterruptedException, IOException
{
try
{
LOG.info( "Finished execution" );
computer.queueEvent( "cct_test_run", null );
// Wait for test execution to fully finish
lock.lockInterruptibly();
try
{
long remaining = TIMEOUT;
while( remaining > 0 && !finished )
{
tick();
if( hasFinished.awaitNanos( TICK_TIME ) > 0 ) break;
remaining -= TICK_TIME;
}
if( remaining <= 0 ) throw new IllegalStateException( "Timed out waiting for finish." + dump() );
if( !finished ) throw new IllegalStateException( "Computer did not finish." + dump() );
}
finally
{
lock.unlock();
}
}
finally
{
// Show a dump of computer output
System.out.println( dump() );
// And shutdown
computer.shutdown();
}
if( finishedWith != null )
{
REPORT_PATH.getParentFile().mkdirs();
try( BufferedWriter writer = Files.newBufferedWriter( REPORT_PATH.toPath() ) )
{
// new LuaCoverage( finishedWith ).write( writer );
}
}
}
@TestFactory
public Stream<DynamicNode> get() throws InterruptedException
{
lock.lockInterruptibly();
try
{
long remaining = TIMEOUT;
while( remaining > 0 & tests == null )
{
tick();
if( hasTests.awaitNanos( TICK_TIME ) > 0 ) break;
remaining -= TICK_TIME;
}
if( remaining <= 0 ) throw new IllegalStateException( "Timed out waiting for tests. " + dump() );
if( tests == null ) throw new IllegalStateException( "Computer did not provide any tests. " + dump() );
}
finally
{
lock.unlock();
}
return tests.buildChildren();
}
private static class DynamicNodeBuilder
{
private final String name;
private final Map<String, DynamicNodeBuilder> children;
private final Executable executor;
DynamicNodeBuilder( String name )
{
this.name = name;
this.children = new HashMap<>();
this.executor = null;
}
DynamicNodeBuilder( String name, Executable executor )
{
this.name = name;
this.children = Collections.emptyMap();
this.executor = executor;
}
DynamicNodeBuilder get( String name )
{
DynamicNodeBuilder child = children.get( name );
if( child == null ) children.put( name, child = new DynamicNodeBuilder( name ) );
return child;
}
void runs( String name, Executable executor )
{
DynamicNodeBuilder child = children.get( name );
int id = 0;
while( child != null )
{
id++;
String subName = name + "_" + id;
child = children.get( subName );
}
children.put( name, new DynamicNodeBuilder( name, executor ) );
}
DynamicNode build()
{
return executor == null
? DynamicContainer.dynamicContainer( name, buildChildren() )
: DynamicTest.dynamicTest( name, executor );
}
Stream<DynamicNode> buildChildren()
{
return children.values().stream().map( DynamicNodeBuilder::build );
}
}
private String dump()
{
if( !computer.isOn() ) return "Computer is currently off.";
Terminal term = computer.getAPIEnvironment().getTerminal();
StringBuilder builder = new StringBuilder().append( "Computer is currently on.\n" );
for( int line = 0; line < term.getHeight(); line++ )
{
builder.append( String.format( "%2d | %" + term.getWidth() + "s |\n", line + 1, term.getLine( line ) ) );
}
computer.shutdown();
return builder.toString();
}
private void tick()
{
computer.tick();
MainThread.executePendingTasks();
}
private static String formatName( String name )
{
return name.replace( "\0", " -> " );
}
private static class FakeModem extends WirelessModemPeripheral
{
FakeModem()
{
super( new ModemState(), true );
}
@Nonnull
@Override
@SuppressWarnings( "ConstantConditions" )
public World getWorld()
{
return null;
}
@Nonnull
@Override
public Vec3d getPosition()
{
return Vec3d.ZERO;
}
@Override
public boolean equals( @Nullable IPeripheral other )
{
return this == other;
}
}
public class CctTestAPI implements ILuaAPI
{
@Override
public String[] getNames()
{
return new String[] { "cct_test" };
}
@Override
public void startup()
{
try
{
computer.getAPIEnvironment().getFileSystem().mount(
"test-rom", "test-rom",
BasicEnvironment.createMount( ComputerTestDelegate.class, "test-rom", "test" )
);
}
catch( FileSystemException e )
{
throw new IllegalStateException( e );
}
}
@LuaFunction
public final void start( Map<?, ?> tests ) throws LuaException
{
// Submit several tests and signal for #get to run
LOG.info( "Received tests from computer" );
DynamicNodeBuilder root = new DynamicNodeBuilder( "" );
for( Object key : tests.keySet() )
{
if( !(key instanceof String) ) throw new LuaException( "Non-key string " + getType( key ) );
String name = (String) key;
String[] parts = name.split( "\0" );
DynamicNodeBuilder builder = root;
for( int i = 0; i < parts.length - 1; i++ ) builder = builder.get( parts[i] );
builder.runs( parts[parts.length - 1], () -> {
// Run it
lock.lockInterruptibly();
try
{
// Set the current test
runResult = null;
runFinished = false;
currentTest = name;
// Tell the computer to run it
LOG.info( "Starting '{}'", formatName( name ) );
computer.queueEvent( "cct_test_run", new Object[] { name } );
long remaining = TIMEOUT;
while( remaining > 0 && computer.isOn() && !runFinished )
{
tick();
long waiting = hasRun.awaitNanos( TICK_TIME );
if( waiting > 0 ) break;
remaining -= TICK_TIME;
}
LOG.info( "Finished '{}'", formatName( name ) );
if( remaining <= 0 )
{
throw new IllegalStateException( "Timed out waiting for test" );
}
else if( !computer.isOn() )
{
throw new IllegalStateException( "Computer turned off mid-execution" );
}
if( runResult != null ) throw runResult;
}
finally
{
lock.unlock();
currentTest = null;
}
} );
}
try
{
lock.lockInterruptibly();
}
catch( InterruptedException e )
{
throw new RuntimeException( e );
}
try
{
ComputerTestDelegate.this.tests = root;
hasTests.signal();
}
finally
{
lock.unlock();
}
}
@LuaFunction
public final void submit( Map<?, ?> tbl )
{
// Submit the result of a test, allowing the test executor to continue
String name = (String) tbl.get( "name" );
if( name == null )
{
ComputerCraft.log.error( "Oh no: {}", tbl );
}
String status = (String) tbl.get( "status" );
String message = (String) tbl.get( "message" );
String trace = (String) tbl.get( "trace" );
StringBuilder wholeMessage = new StringBuilder();
if( message != null ) wholeMessage.append( message );
if( trace != null )
{
if( wholeMessage.length() != 0 ) wholeMessage.append( '\n' );
wholeMessage.append( trace );
}
try
{
lock.lockInterruptibly();
}
catch( InterruptedException e )
{
throw new RuntimeException( e );
}
try
{
LOG.info( "'{}' finished with {}", formatName( name ), status );
// Skip if a test mismatch
if( !name.equals( currentTest ) )
{
LOG.warn( "Skipping test '{}', as we're currently executing '{}'", formatName( name ), formatName( currentTest ) );
return;
}
switch( status )
{
case "ok":
case "pending":
break;
case "fail":
runResult = new AssertionFailedError( wholeMessage.toString() );
break;
case "error":
runResult = new IllegalStateException( wholeMessage.toString() );
break;
}
runFinished = true;
hasRun.signal();
}
finally
{
lock.unlock();
}
}
@LuaFunction
public final void finish( Optional<Map<?, ?>> result )
{
@SuppressWarnings( "unchecked" )
Map<String, Map<Double, Double>> finishedResult = (Map<String, Map<Double, Double>>) result.orElse( null );
LOG.info( "Finished" );
// Signal to after that execution has finished
try
{
lock.lockInterruptibly();
}
catch( InterruptedException e )
{
throw new RuntimeException( e );
}
try
{
finished = true;
if( finishedResult != null ) finishedWith = finishedResult;
hasFinished.signal();
}
finally
{
lock.unlock();
}
}
}
}

View File

@@ -1,67 +0,0 @@
/*
* 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;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.asm.LuaMethod;
import dan200.computercraft.core.asm.NamedMethod;
import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class ObjectWrapper implements ILuaContext
{
private final Object object;
private final Map<String, LuaMethod> methodMap;
public ObjectWrapper( Object object )
{
this.object = object;
String[] dynamicMethods = object instanceof IDynamicLuaObject
? Objects.requireNonNull( ((IDynamicLuaObject) object).getMethodNames(), "Methods cannot be null" )
: LuaMethod.EMPTY_METHODS;
List<NamedMethod<LuaMethod>> methods = LuaMethod.GENERATOR.getMethods( object.getClass() );
Map<String, LuaMethod> methodMap = this.methodMap = new HashMap<>( methods.size() + dynamicMethods.length );
for( int i = 0; i < dynamicMethods.length; i++ )
{
methodMap.put( dynamicMethods[i], LuaMethod.DYNAMIC.get( i ) );
}
for( NamedMethod<LuaMethod> method : methods )
{
methodMap.put( method.getName(), method.getMethod() );
}
}
public Object[] call( String name, Object... args ) throws LuaException
{
LuaMethod method = methodMap.get( name );
if( method == null ) throw new IllegalStateException( "No such method '" + name + "'" );
return method.apply( object, this, new ObjectArguments( args ) ).getResult();
}
@SuppressWarnings( "unchecked" )
public <T> T callOf( String name, Object... args ) throws LuaException
{
return (T) call( name, args )[0];
}
public <T> T callOf( Class<T> klass, String name, Object... args ) throws LuaException
{
return klass.cast( call( name, args )[0] );
}
@Override
public long issueMainThreadTask( @Nonnull ILuaTask task )
{
throw new IllegalStateException( "Method should never queue events" );
}
}

View File

@@ -1,86 +0,0 @@
/*
* 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.handles;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ObjectWrapper;
import org.junit.jupiter.api.Test;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
public class BinaryReadableHandleTest
{
@Test
public void testReadChar() throws LuaException
{
ObjectWrapper wrapper = fromLength( 5 );
assertEquals( 'A', (int) wrapper.callOf( Integer.class, "read" ) );
}
@Test
public void testReadShortComplete() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10 );
assertEquals( 5, wrapper.<ByteBuffer>callOf( "read", 5 ).remaining() );
}
@Test
public void testReadShortPartial() throws LuaException
{
ObjectWrapper wrapper = fromLength( 5 );
assertEquals( 5, wrapper.<ByteBuffer>callOf( "read", 10 ).remaining() );
}
@Test
public void testReadLongComplete() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10000 );
assertEquals( 9000, wrapper.<byte[]>callOf( "read", 9000 ).length );
}
@Test
public void testReadLongPartial() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10000 );
assertEquals( 10000, wrapper.<byte[]>callOf( "read", 11000 ).length );
}
@Test
public void testReadLongPartialSmaller() throws LuaException
{
ObjectWrapper wrapper = fromLength( 1000 );
assertEquals( 1000, wrapper.<ByteBuffer>callOf( "read", 11000 ).remaining() );
}
@Test
public void testReadLine() throws LuaException
{
ObjectWrapper wrapper = new ObjectWrapper( BinaryReadableHandle.of( new ArrayByteChannel( "hello\r\nworld\r!".getBytes( StandardCharsets.UTF_8 ) ) ) );
assertArrayEquals( "hello".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine" ) );
assertArrayEquals( "world\r!".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine" ) );
assertNull( wrapper.call( "readLine" ) );
}
@Test
public void testReadLineTrailing() throws LuaException
{
ObjectWrapper wrapper = new ObjectWrapper( BinaryReadableHandle.of( new ArrayByteChannel( "hello\r\nworld\r!".getBytes( StandardCharsets.UTF_8 ) ) ) );
assertArrayEquals( "hello\r\n".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine", true ) );
assertArrayEquals( "world\r!".getBytes( StandardCharsets.UTF_8 ), wrapper.callOf( "readLine", true ) );
assertNull( wrapper.call( "readLine", true ) );
}
private static ObjectWrapper fromLength( int length )
{
byte[] input = new byte[length];
Arrays.fill( input, (byte) 'A' );
return new ObjectWrapper( BinaryReadableHandle.of( new ArrayByteChannel( input ) ) );
}
}

View File

@@ -1,69 +0,0 @@
/*
* 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.handles;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ObjectWrapper;
import org.junit.jupiter.api.Test;
import java.io.BufferedReader;
import java.io.CharArrayReader;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class EncodedReadableHandleTest
{
@Test
public void testReadChar() throws LuaException
{
ObjectWrapper wrapper = fromLength( 5 );
assertEquals( "A", wrapper.callOf( "read" ) );
}
@Test
public void testReadShortComplete() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10 );
assertEquals( "AAAAA", wrapper.callOf( "read", 5 ) );
}
@Test
public void testReadShortPartial() throws LuaException
{
ObjectWrapper wrapper = fromLength( 5 );
assertEquals( "AAAAA", wrapper.callOf( "read", 10 ) );
}
@Test
public void testReadLongComplete() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10000 );
assertEquals( 9000, wrapper.<String>callOf( "read", 9000 ).length() );
}
@Test
public void testReadLongPartial() throws LuaException
{
ObjectWrapper wrapper = fromLength( 10000 );
assertEquals( 10000, wrapper.<String>callOf( "read", 11000 ).length() );
}
@Test
public void testReadLongPartialSmaller() throws LuaException
{
ObjectWrapper wrapper = fromLength( 1000 );
assertEquals( 1000, wrapper.<String>callOf( "read", 11000 ).length() );
}
private static ObjectWrapper fromLength( int length )
{
char[] input = new char[length];
Arrays.fill( input, 'A' );
return new ObjectWrapper( new EncodedReadableHandle( new BufferedReader( new CharArrayReader( input ) ) ) );
}
}

View File

@@ -1,33 +0,0 @@
/*
* 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 org.junit.jupiter.api.Test;
import java.net.InetSocketAddress;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class AddressRuleTest
{
@Test
public void matchesPort()
{
Iterable<AddressRule> rules = Collections.singletonList( AddressRule.parse(
"127.0.0.1", 8080,
new PartialOptions( Action.ALLOW, null, null, null, null )
) );
assertEquals( apply( rules, "localhost", 8080 ).action, Action.ALLOW );
assertEquals( apply( rules, "localhost", 8081 ).action, Action.DENY );
}
private Options apply( Iterable<AddressRule> rules, String host, int port )
{
return AddressRule.apply( rules, host, new InetSocketAddress( host, port ) );
}
}

View File

@@ -1,165 +0,0 @@
/*
* 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.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.JarMount;
import dan200.computercraft.core.filesystem.MemoryMount;
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
/**
* A very basic environment.
*/
public class BasicEnvironment implements IComputerEnvironment
{
private final IWritableMount mount;
public BasicEnvironment()
{
this( new MemoryMount() );
}
public BasicEnvironment( IWritableMount mount )
{
this.mount = mount;
}
@Override
public int assignNewID()
{
return 0;
}
@Override
public IWritableMount createSaveDirMount( String path, long space )
{
return mount;
}
@Override
public int getDay()
{
return 0;
}
@Override
public double getTimeOfDay()
{
return 0;
}
@Override
public boolean isColour()
{
return true;
}
@Override
public long getComputerSpaceLimit()
{
return ComputerCraft.computerSpaceLimit;
}
@Nonnull
@Override
public String getHostString()
{
return "ComputerCraft 1.0 (Test environment)";
}
@Nonnull
@Override
public String getUserAgent()
{
return "ComputerCraft/1.0";
}
@Override
public IMount createResourceMount( String domain, String subPath )
{
return createMount( ComputerCraft.class, "data/" + domain + "/" + subPath, "main" );
}
@Override
public InputStream createResourceFile( String domain, String subPath )
{
return ComputerCraft.class.getClassLoader().getResourceAsStream( "data/" + domain + "/" + subPath );
}
public static IMount createMount( Class<?> klass, String path, String fallback )
{
File file = getContainingFile( klass );
if( file.isFile() )
{
try
{
return new JarMount( file, path );
}
catch( IOException e )
{
throw new UncheckedIOException( e );
}
}
else
{
File wholeFile = new File( file, path );
// If we don't exist, walk up the tree looking for resource folders
File baseFile = file;
while( baseFile != null && !wholeFile.exists() )
{
baseFile = baseFile.getParentFile();
wholeFile = new File( baseFile, "src/" + fallback + "/resources/" + path );
}
if( !wholeFile.exists() ) throw new IllegalStateException( "Cannot find ROM mount at " + file );
return new FileMount( wholeFile, 0 );
}
}
private static File getContainingFile( Class<?> klass )
{
String path = klass.getProtectionDomain().getCodeSource().getLocation().getPath();
int bangIndex = path.indexOf( "!" );
// Plain old file, so step up from dan200.computercraft.
if( bangIndex < 0 ) return new File( path );
path = path.substring( 0, bangIndex );
URL url;
try
{
url = new URL( path );
}
catch( MalformedURLException e )
{
throw new IllegalStateException( e );
}
try
{
return new File( url.toURI() );
}
catch( URISyntaxException e )
{
return new File( url.getPath() );
}
}
}

View File

@@ -1,138 +0,0 @@
/*
* 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.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.filesystem.MemoryMount;
import dan200.computercraft.core.terminal.Terminal;
import org.junit.jupiter.api.Assertions;
import java.util.Arrays;
import java.util.function.Consumer;
/**
* Helper class to run a program on a computer.
*/
public class ComputerBootstrap
{
private static final int TPS = 20;
public static final int MAX_TIME = 10;
public static void run( String program, Consumer<Computer> setup, int maxTimes )
{
MemoryMount mount = new MemoryMount()
.addFile( "test.lua", program )
.addFile( "startup.lua", "assertion.assert(pcall(loadfile('test.lua', nil, _ENV))) os.shutdown()" );
run( mount, setup, maxTimes );
}
public static void run( String program, int maxTimes )
{
run( program, x -> { }, maxTimes );
}
public static void run( IWritableMount mount, Consumer<Computer> setup, int maxTicks )
{
ComputerCraft.logPeripheralErrors = true;
ComputerCraft.maxMainComputerTime = ComputerCraft.maxMainGlobalTime = Integer.MAX_VALUE;
Terminal term = new Terminal( ComputerCraft.terminalWidth_computer, ComputerCraft.terminalHeight_computer );
final Computer computer = new Computer( new BasicEnvironment( mount ), term, 0 );
AssertApi api = new AssertApi();
computer.addApi( api );
setup.accept( computer );
try
{
computer.turnOn();
boolean everOn = false;
for( int tick = 0; tick < TPS * maxTicks; tick++ )
{
long start = System.currentTimeMillis();
computer.tick();
MainThread.executePendingTasks();
if( api.message != null )
{
ComputerCraft.log.debug( "Shutting down due to error" );
computer.shutdown();
Assertions.fail( api.message );
return;
}
long remaining = (1000 / TPS) - (System.currentTimeMillis() - start);
if( remaining > 0 ) Thread.sleep( remaining );
// Break if the computer was once on, and is now off.
everOn |= computer.isOn();
if( (everOn || tick > TPS) && !computer.isOn() ) break;
}
if( computer.isOn() || !api.didAssert )
{
StringBuilder builder = new StringBuilder().append( "Did not correctly [" );
if( !api.didAssert ) builder.append( " assert" );
if( computer.isOn() ) builder.append( " shutdown" );
builder.append( " ]\n" );
for( int line = 0; line < 19; line++ )
{
builder.append( String.format( "%2d | %" + term.getWidth() + "s |\n", line + 1, term.getLine( line ) ) );
}
computer.shutdown();
Assertions.fail( builder.toString() );
}
}
catch( InterruptedException ignored )
{
Thread.currentThread().interrupt();
}
}
public static class AssertApi implements ILuaAPI
{
boolean didAssert;
String message;
@Override
public String[] getNames()
{
return new String[] { "assertion" };
}
@LuaFunction
public final void log( IArguments arguments )
{
ComputerCraft.log.info( "[Computer] {}", Arrays.toString( arguments.getAll() ) );
}
@LuaFunction( "assert" )
public final Object[] doAssert( IArguments arguments ) throws LuaException
{
didAssert = true;
Object arg = arguments.get( 0 );
if( arg == null || arg == Boolean.FALSE )
{
message = arguments.optString( 1, "Assertion failed" );
throw new LuaException( message );
}
return arguments.getAll();
}
}
}

View File

@@ -1,49 +0,0 @@
/*
* 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.computer;
import com.google.common.io.CharStreams;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import static java.time.Duration.ofSeconds;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
public class ComputerTest
{
@Test
public void testTimeout()
{
assertTimeoutPreemptively( ofSeconds( 20 ), () -> {
try
{
ComputerBootstrap.run( "print('Hello') while true do end", ComputerBootstrap.MAX_TIME );
}
catch( AssertionError e )
{
if( e.getMessage().equals( "test.lua:1: Too long without yielding" ) ) return;
throw e;
}
Assertions.fail( "Expected computer to timeout" );
} );
}
public static void main( String[] args ) throws Exception
{
InputStream stream = ComputerTest.class.getClassLoader().getResourceAsStream( "benchmark.lua" );
try( InputStreamReader reader = new InputStreamReader( Objects.requireNonNull( stream ), StandardCharsets.UTF_8 ) )
{
String contents = CharStreams.toString( reader );
ComputerBootstrap.run( contents, 1000 );
}
}
}

View File

@@ -1,81 +0,0 @@
/*
* 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.filesystem;
import com.google.common.io.Files;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ObjectWrapper;
import dan200.computercraft.core.apis.handles.EncodedWritableHandle;
import org.junit.jupiter.api.Test;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class FileSystemTest
{
private static final File ROOT = new File( "test-files/filesystem" );
private static final long CAPACITY = 1000000;
private static FileSystem mkFs() throws FileSystemException
{
IWritableMount writableMount = new FileMount( ROOT, CAPACITY );
return new FileSystem( "hdd", writableMount );
}
/**
* Ensures writing a file truncates it.
*
* @throws FileSystemException When the file system cannot be constructed.
* @throws LuaException When Lua functions fail.
* @throws IOException When reading and writing from strings
*/
@Test
public void testWriteTruncates() throws FileSystemException, LuaException, IOException
{
FileSystem fs = mkFs();
{
FileSystemWrapper<BufferedWriter> writer = fs.openForWrite( "out.txt", false, EncodedWritableHandle::openUtf8 );
ObjectWrapper wrapper = new ObjectWrapper( new EncodedWritableHandle( writer.get(), writer ) );
wrapper.call( "write", "This is a long line" );
wrapper.call( "close" );
}
assertEquals( "This is a long line", Files.asCharSource( new File( ROOT, "out.txt" ), StandardCharsets.UTF_8 ).read() );
{
FileSystemWrapper<BufferedWriter> writer = fs.openForWrite( "out.txt", false, EncodedWritableHandle::openUtf8 );
ObjectWrapper wrapper = new ObjectWrapper( new EncodedWritableHandle( writer.get(), writer ) );
wrapper.call( "write", "Tiny line" );
wrapper.call( "close" );
}
assertEquals( "Tiny line", Files.asCharSource( new File( ROOT, "out.txt" ), StandardCharsets.UTF_8 ).read() );
}
@Test
public void testUnmountCloses() throws FileSystemException
{
FileSystem fs = mkFs();
IWritableMount mount = new FileMount( new File( ROOT, "child" ), CAPACITY );
fs.mountWritable( "disk", "disk", mount );
FileSystemWrapper<BufferedWriter> writer = fs.openForWrite( "disk/out.txt", false, EncodedWritableHandle::openUtf8 );
ObjectWrapper wrapper = new ObjectWrapper( new EncodedWritableHandle( writer.get(), writer ) );
fs.unmount( "disk" );
LuaException err = assertThrows( LuaException.class, () -> wrapper.call( "write", "Tiny line" ) );
assertEquals( "attempt to use a closed file", err.getMessage() );
}
}

View File

@@ -1,108 +0,0 @@
/*
* 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.filesystem;
import com.google.common.io.ByteStreams;
import dan200.computercraft.api.filesystem.IMount;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import static org.junit.jupiter.api.Assertions.*;
public class JarMountTest
{
private static final File ZIP_FILE = new File( "test-files/jar-mount.zip" );
private static final FileTime MODIFY_TIME = FileTime.from( Instant.EPOCH.plus( 2, ChronoUnit.DAYS ) );
@BeforeAll
public static void before() throws IOException
{
ZIP_FILE.getParentFile().mkdirs();
try( ZipOutputStream stream = new ZipOutputStream( new FileOutputStream( ZIP_FILE ) ) )
{
stream.putNextEntry( new ZipEntry( "dir/" ) );
stream.closeEntry();
stream.putNextEntry( new ZipEntry( "dir/file.lua" ).setLastModifiedTime( MODIFY_TIME ) );
stream.write( "print('testing')".getBytes( StandardCharsets.UTF_8 ) );
stream.closeEntry();
}
}
@Test
public void mountsDir() throws IOException
{
IMount mount = new JarMount( ZIP_FILE, "dir" );
assertTrue( mount.isDirectory( "" ), "Root should be directory" );
assertTrue( mount.exists( "file.lua" ), "File should exist" );
}
@Test
public void mountsFile() throws IOException
{
IMount mount = new JarMount( ZIP_FILE, "dir/file.lua" );
assertTrue( mount.exists( "" ), "Root should exist" );
assertFalse( mount.isDirectory( "" ), "Root should be a file" );
}
@Test
public void opensFileFromFile() throws IOException
{
IMount mount = new JarMount( ZIP_FILE, "dir/file.lua" );
byte[] contents;
try( ReadableByteChannel stream = mount.openForRead( "" ) )
{
contents = ByteStreams.toByteArray( Channels.newInputStream( stream ) );
}
assertEquals( new String( contents, StandardCharsets.UTF_8 ), "print('testing')" );
}
@Test
public void opensFileFromDir() throws IOException
{
IMount mount = new JarMount( ZIP_FILE, "dir" );
byte[] contents;
try( ReadableByteChannel stream = mount.openForRead( "file.lua" ) )
{
contents = ByteStreams.toByteArray( Channels.newInputStream( stream ) );
}
assertEquals( new String( contents, StandardCharsets.UTF_8 ), "print('testing')" );
}
@Test
public void fileAttributes() throws IOException
{
BasicFileAttributes attributes = new JarMount( ZIP_FILE, "dir" ).getAttributes( "file.lua" );
assertFalse( attributes.isDirectory() );
assertEquals( "print('testing')".length(), attributes.size() );
assertEquals( MODIFY_TIME, attributes.lastModifiedTime() );
}
@Test
public void directoryAttributes() throws IOException
{
BasicFileAttributes attributes = new JarMount( ZIP_FILE, "dir" ).getAttributes( "" );
assertTrue( attributes.isDirectory() );
assertEquals( 0, attributes.size() );
}
}

View File

@@ -1,148 +0,0 @@
/*
* 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.filesystem;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.*;
/**
* In-memory file mounts.
*/
public class MemoryMount implements IWritableMount
{
private final Map<String, byte[]> files = new HashMap<>();
private final Set<String> directories = new HashSet<>();
public MemoryMount()
{
directories.add( "" );
}
@Override
public void makeDirectory( @Nonnull String path )
{
File file = new File( path );
while( file != null )
{
directories.add( file.getPath() );
file = file.getParentFile();
}
}
@Override
public void delete( @Nonnull String path )
{
if( files.containsKey( path ) )
{
files.remove( path );
}
else
{
directories.remove( path );
for( String file : files.keySet().toArray( new String[0] ) )
{
if( file.startsWith( path ) )
{
files.remove( file );
}
}
File parent = new File( path ).getParentFile();
if( parent != null ) delete( parent.getPath() );
}
}
@Nonnull
@Override
public WritableByteChannel openForWrite( @Nonnull final String path )
{
return Channels.newChannel( new ByteArrayOutputStream()
{
@Override
public void close() throws IOException
{
super.close();
files.put( path, toByteArray() );
}
} );
}
@Nonnull
@Override
public WritableByteChannel openForAppend( @Nonnull final String path ) throws IOException
{
ByteArrayOutputStream stream = new ByteArrayOutputStream()
{
@Override
public void close() throws IOException
{
super.close();
files.put( path, toByteArray() );
}
};
byte[] current = files.get( path );
if( current != null ) stream.write( current );
return Channels.newChannel( stream );
}
@Override
public long getRemainingSpace()
{
return 1000000L;
}
@Override
public boolean exists( @Nonnull String path )
{
return files.containsKey( path ) || directories.contains( path );
}
@Override
public boolean isDirectory( @Nonnull String path )
{
return directories.contains( path );
}
@Override
public void list( @Nonnull String path, @Nonnull List<String> files )
{
for( String file : this.files.keySet() )
{
if( file.startsWith( path ) ) files.add( file.substring( path.length() + 1 ) );
}
}
@Override
public long getSize( @Nonnull String path )
{
throw new RuntimeException( "Not implemented" );
}
@Nonnull
@Override
public ReadableByteChannel openForRead( @Nonnull String path )
{
return new ArrayByteChannel( files.get( path ) );
}
public MemoryMount addFile( String file, String contents )
{
files.put( file, contents.getBytes() );
return this;
}
}

View File

@@ -1,75 +0,0 @@
/*
* 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.filesystem;
import dan200.computercraft.api.filesystem.IMount;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class ResourceMountTest
{
private IMount mount;
@BeforeEach
public void before()
{
// ReloadableResourceManager manager = new SimpleResourceReloadListener(ResourceType.SERVER_DATA);
// manager.addResourcePack( new FolderPack( new File( "src/main/resources" ) ) );
//
// mount = ResourceMount.get( "computercraft", "lua/rom", manager );
}
@Test
public void testList() throws IOException
{
List<String> files = new ArrayList<>();
mount.list( "", files );
files.sort( Comparator.naturalOrder() );
assertEquals(
Arrays.asList( "apis", "autorun", "help", "modules", "motd.txt", "programs", "startup.lua" ),
files
);
}
@Test
public void testExists() throws IOException
{
assertTrue( mount.exists( "" ) );
assertTrue( mount.exists( "startup.lua" ) );
assertTrue( mount.exists( "programs/fun/advanced/paint.lua" ) );
assertFalse( mount.exists( "programs/fun/advance/paint.lua" ) );
assertFalse( mount.exists( "programs/fun/advanced/paint.lu" ) );
}
@Test
public void testIsDir() throws IOException
{
assertTrue( mount.isDirectory( "" ) );
}
@Test
public void testIsFile() throws IOException
{
assertFalse( mount.isDirectory( "startup.lua" ) );
}
@Test
public void testSize() throws IOException
{
assertNotEquals( mount.getSize( "startup.lua" ), 0 );
}
}

View File

@@ -1,150 +0,0 @@
/*
* 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.terminal;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TextBufferTest
{
@Test
void testStringConstructor()
{
TextBuffer textBuffer = new TextBuffer( "test" );
assertEquals( "test", textBuffer.toString() );
}
@Test
void testCharRepetitionConstructor()
{
TextBuffer textBuffer = new TextBuffer( 'a', 5 );
assertEquals( "aaaaa", textBuffer.toString() );
}
@Test
void testLength()
{
TextBuffer textBuffer = new TextBuffer( "test" );
assertEquals( 4, textBuffer.length() );
}
@Test
void testWrite()
{
TextBuffer textBuffer = new TextBuffer( ' ', 4 );
textBuffer.write( "test" );
assertEquals( "test", textBuffer.toString() );
}
@Test
void testWriteTextBuffer()
{
TextBuffer source = new TextBuffer( "test" );
TextBuffer target = new TextBuffer( " " );
target.write( source );
assertEquals( "test", target.toString() );
}
@Test
void testWriteFromPos()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.write( "il", 1 );
assertEquals( "tilt", textBuffer.toString() );
}
@Test
void testWriteOutOfBounds()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.write( "abcdefghijklmnop", -5 );
assertEquals( "fghi", textBuffer.toString() );
}
@Test
void testWriteOutOfBounds2()
{
TextBuffer textBuffer = new TextBuffer( " " );
textBuffer.write( "Hello, world!", -3 );
assertEquals( "lo, world! ", textBuffer.toString() );
}
@Test
void testFill()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.fill( 'c' );
assertEquals( "cccc", textBuffer.toString() );
}
@Test
void testFillSubstring()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.fill( 'c', 1, 3 );
assertEquals( "tcct", textBuffer.toString() );
}
@Test
void testFillOutOfBounds()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.fill( 'c', -5, 5 );
assertEquals( "cccc", textBuffer.toString() );
}
@Test
void testCharAt()
{
TextBuffer textBuffer = new TextBuffer( "test" );
assertEquals( 'e', textBuffer.charAt( 1 ) );
}
@Test
void testSetChar()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.setChar( 2, 'n' );
assertEquals( "tent", textBuffer.toString() );
}
@Test
void testSetCharWithNegativeIndex()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.setChar( -5, 'n' );
assertEquals( "test", textBuffer.toString(), "Buffer should not change after setting char with negative index." );
}
@Test
void testSetCharWithIndexBeyondBufferEnd()
{
TextBuffer textBuffer = new TextBuffer( "test" );
textBuffer.setChar( 10, 'n' );
assertEquals( "test", textBuffer.toString(), "Buffer should not change after setting char beyond buffer end." );
}
@Test
void testMultipleOperations()
{
TextBuffer textBuffer = new TextBuffer( ' ', 5 );
textBuffer.setChar( 0, 'H' );
textBuffer.setChar( 1, 'e' );
textBuffer.setChar( 2, 'l' );
textBuffer.write( "lo", 3 );
assertEquals( "Hello", textBuffer.toString(), "TextBuffer failed to persist over multiple operations." );
}
@Test
void testEmptyBuffer()
{
TextBuffer textBuffer = new TextBuffer( "" );
// exception on writing to empty buffer would fail the test
textBuffer.write( "test" );
assertEquals( "", textBuffer.toString() );
}
}

View File

@@ -1,463 +0,0 @@
/*
* 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.shared.wired;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNetwork;
import dan200.computercraft.api.network.wired.IWiredNetworkChange;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.util.DirectionUtil;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import static org.junit.jupiter.api.Assertions.*;
public class NetworkTest
{
@Test
public void testConnect()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
assertNotEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must be different" );
assertNotEquals( aN.getNetwork(), cN.getNetwork(), "A's and C's network must be different" );
assertNotEquals( bN.getNetwork(), cN.getNetwork(), "B's and C's network must be different" );
assertTrue( aN.getNetwork().connect( aN, bN ), "Must be able to add connection" );
assertFalse( aN.getNetwork().connect( aN, bN ), "Cannot add connection twice" );
assertEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal" );
assertEquals( Sets.newHashSet( aN, bN ), nodes( aN.getNetwork() ), "A's network should be A and B" );
assertEquals( Sets.newHashSet( "a", "b" ), aE.allPeripherals().keySet(), "A's peripheral set should be A, B" );
assertEquals( Sets.newHashSet( "a", "b" ), bE.allPeripherals().keySet(), "B's peripheral set should be A, B" );
aN.getNetwork().connect( aN, cN );
assertEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal" );
assertEquals( aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal" );
assertEquals( Sets.newHashSet( aN, bN, cN ), nodes( aN.getNetwork() ), "A's network should be A, B and C" );
assertEquals( Sets.newHashSet( bN, cN ), neighbours( aN ), "A's neighbour set should be B, C" );
assertEquals( Sets.newHashSet( aN ), neighbours( bN ), "B's neighbour set should be A" );
assertEquals( Sets.newHashSet( aN ), neighbours( cN ), "C's neighbour set should be A" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C" );
}
@Test
public void testDisconnectNoChange()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, bN );
aN.getNetwork().connect( aN, cN );
aN.getNetwork().connect( bN, cN );
aN.getNetwork().disconnect( aN, bN );
assertEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must be equal" );
assertEquals( aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal" );
assertEquals( Sets.newHashSet( aN, bN, cN ), nodes( aN.getNetwork() ), "A's network should be A, B and C" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), aE.allPeripherals().keySet(), "A's peripheral set should be A, B, C" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), bE.allPeripherals().keySet(), "B's peripheral set should be A, B, C" );
assertEquals( Sets.newHashSet( "a", "b", "c" ), cE.allPeripherals().keySet(), "C's peripheral set should be A, B, C" );
}
@Test
public void testDisconnectLeaf()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, bN );
aN.getNetwork().connect( aN, cN );
aN.getNetwork().disconnect( aN, bN );
assertNotEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal" );
assertEquals( aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal" );
assertEquals( Sets.newHashSet( aN, cN ), nodes( aN.getNetwork() ), "A's network should be A and C" );
assertEquals( Sets.newHashSet( bN ), nodes( bN.getNetwork() ), "B's network should be B" );
assertEquals( Sets.newHashSet( "a", "c" ), aE.allPeripherals().keySet(), "A's peripheral set should be A, C" );
assertEquals( Sets.newHashSet( "b" ), bE.allPeripherals().keySet(), "B's peripheral set should be B" );
assertEquals( Sets.newHashSet( "a", "c" ), cE.allPeripherals().keySet(), "C's peripheral set should be A, C" );
}
@Test
public void testDisconnectSplit()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
aaE = new NetworkElement( null, null, "a_" ),
bE = new NetworkElement( null, null, "b" ),
bbE = new NetworkElement( null, null, "b_" );
IWiredNode
aN = aE.getNode(),
aaN = aaE.getNode(),
bN = bE.getNode(),
bbN = bbE.getNode();
aN.getNetwork().connect( aN, aaN );
bN.getNetwork().connect( bN, bbN );
aN.getNetwork().connect( aN, bN );
aN.getNetwork().disconnect( aN, bN );
assertNotEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal" );
assertEquals( aN.getNetwork(), aaN.getNetwork(), "A's and A_'s network must be equal" );
assertEquals( bN.getNetwork(), bbN.getNetwork(), "B's and B_'s network must be equal" );
assertEquals( Sets.newHashSet( aN, aaN ), nodes( aN.getNetwork() ), "A's network should be A and A_" );
assertEquals( Sets.newHashSet( bN, bbN ), nodes( bN.getNetwork() ), "B's network should be B and B_" );
assertEquals( Sets.newHashSet( "a", "a_" ), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_" );
assertEquals( Sets.newHashSet( "b", "b_" ), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_" );
}
@Test
public void testRemoveSingle()
{
NetworkElement aE = new NetworkElement( null, null, "a" );
IWiredNode aN = aE.getNode();
IWiredNetwork network = aN.getNetwork();
assertFalse( aN.remove(), "Cannot remove node from an empty network" );
assertEquals( network, aN.getNetwork(), "Networks are same before and after" );
}
@Test
public void testRemoveLeaf()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
bE = new NetworkElement( null, null, "b" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
bN = bE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, bN );
aN.getNetwork().connect( aN, cN );
assertTrue( aN.getNetwork().remove( bN ), "Must be able to remove node" );
assertFalse( aN.getNetwork().remove( bN ), "Cannot remove a second time" );
assertNotEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal" );
assertEquals( aN.getNetwork(), cN.getNetwork(), "A's and C's network must be equal" );
assertEquals( Sets.newHashSet( aN, cN ), nodes( aN.getNetwork() ), "A's network should be A and C" );
assertEquals( Sets.newHashSet( bN ), nodes( bN.getNetwork() ), "B's network should be B" );
assertEquals( Sets.newHashSet( "a", "c" ), aE.allPeripherals().keySet(), "A's peripheral set should be A, C" );
assertEquals( Sets.newHashSet(), bE.allPeripherals().keySet(), "B's peripheral set should be empty" );
assertEquals( Sets.newHashSet( "a", "c" ), cE.allPeripherals().keySet(), "C's peripheral set should be A, C" );
}
@Test
public void testRemoveSplit()
{
NetworkElement
aE = new NetworkElement( null, null, "a" ),
aaE = new NetworkElement( null, null, "a_" ),
bE = new NetworkElement( null, null, "b" ),
bbE = new NetworkElement( null, null, "b_" ),
cE = new NetworkElement( null, null, "c" );
IWiredNode
aN = aE.getNode(),
aaN = aaE.getNode(),
bN = bE.getNode(),
bbN = bbE.getNode(),
cN = cE.getNode();
aN.getNetwork().connect( aN, aaN );
bN.getNetwork().connect( bN, bbN );
cN.getNetwork().connect( aN, cN );
cN.getNetwork().connect( bN, cN );
cN.getNetwork().remove( cN );
assertNotEquals( aN.getNetwork(), bN.getNetwork(), "A's and B's network must not be equal" );
assertEquals( aN.getNetwork(), aaN.getNetwork(), "A's and A_'s network must be equal" );
assertEquals( bN.getNetwork(), bbN.getNetwork(), "B's and B_'s network must be equal" );
assertEquals( Sets.newHashSet( aN, aaN ), nodes( aN.getNetwork() ), "A's network should be A and A_" );
assertEquals( Sets.newHashSet( bN, bbN ), nodes( bN.getNetwork() ), "B's network should be B and B_" );
assertEquals( Sets.newHashSet( cN ), nodes( cN.getNetwork() ), "C's network should be C" );
assertEquals( Sets.newHashSet( "a", "a_" ), aE.allPeripherals().keySet(), "A's peripheral set should be A and A_" );
assertEquals( Sets.newHashSet( "b", "b_" ), bE.allPeripherals().keySet(), "B's peripheral set should be B and B_" );
assertEquals( Sets.newHashSet(), cE.allPeripherals().keySet(), "C's peripheral set should be empty" );
}
private static final int BRUTE_SIZE = 16;
private static final int TOGGLE_CONNECTION_TIMES = 5;
private static final int TOGGLE_NODE_TIMES = 5;
@Test
@Disabled( "Takes a long time to run, mostly for stress testing" )
public void testLarge()
{
Grid<IWiredNode> grid = new Grid<>( BRUTE_SIZE );
grid.map( ( existing, pos ) -> new NetworkElement( null, null, "n_" + pos ).getNode() );
// Test connecting
{
long start = System.nanoTime();
grid.forEach( ( existing, pos ) -> {
for( Direction facing : DirectionUtil.FACINGS )
{
BlockPos offset = pos.offset( facing );
if( offset.getX() > BRUTE_SIZE / 2 == pos.getX() > BRUTE_SIZE / 2 )
{
IWiredNode other = grid.get( offset );
if( other != null ) existing.getNetwork().connect( existing, other );
}
}
} );
long end = System.nanoTime();
System.out.printf( "Connecting %s³ nodes took %s seconds\n", BRUTE_SIZE, (end - start) * 1e-9 );
}
// Test toggling
{
IWiredNode left = grid.get( new BlockPos( BRUTE_SIZE / 2, 0, 0 ) );
IWiredNode right = grid.get( new BlockPos( BRUTE_SIZE / 2 + 1, 0, 0 ) );
assertNotEquals( left.getNetwork(), right.getNetwork() );
long start = System.nanoTime();
for( int i = 0; i < TOGGLE_CONNECTION_TIMES; i++ )
{
left.getNetwork().connect( left, right );
left.getNetwork().disconnect( left, right );
}
long end = System.nanoTime();
System.out.printf( "Toggling connection %s times took %s seconds\n", TOGGLE_CONNECTION_TIMES, (end - start) * 1e-9 );
}
{
IWiredNode left = grid.get( new BlockPos( BRUTE_SIZE / 2, 0, 0 ) );
IWiredNode right = grid.get( new BlockPos( BRUTE_SIZE / 2 + 1, 0, 0 ) );
IWiredNode centre = new NetworkElement( null, null, "c" ).getNode();
assertNotEquals( left.getNetwork(), right.getNetwork() );
long start = System.nanoTime();
for( int i = 0; i < TOGGLE_NODE_TIMES; i++ )
{
left.getNetwork().connect( left, centre );
right.getNetwork().connect( right, centre );
left.getNetwork().remove( centre );
}
long end = System.nanoTime();
System.out.printf( "Toggling node %s times took %s seconds\n", TOGGLE_NODE_TIMES, (end - start) * 1e-9 );
}
}
private static final class NetworkElement implements IWiredElement
{
private final World world;
private final Vec3d position;
private final String id;
private final IWiredNode node;
private final Map<String, IPeripheral> localPeripherals = Maps.newHashMap();
private final Map<String, IPeripheral> remotePeripherals = Maps.newHashMap();
private NetworkElement( World world, Vec3d position, String id )
{
this.world = world;
this.position = position;
this.id = id;
this.node = ComputerCraftAPI.createWiredNodeForElement( this );
this.addPeripheral( id );
}
@Nonnull
@Override
public World getWorld()
{
return world;
}
@Nonnull
@Override
public Vec3d getPosition()
{
return position;
}
@Nonnull
@Override
public String getSenderID()
{
return id;
}
@Override
public String toString()
{
return "NetworkElement{" + id + "}";
}
@Nonnull
@Override
public IWiredNode getNode()
{
return node;
}
@Override
public void networkChanged( @Nonnull IWiredNetworkChange change )
{
remotePeripherals.keySet().removeAll( change.peripheralsRemoved().keySet() );
remotePeripherals.putAll( change.peripheralsAdded() );
}
public NetworkElement addPeripheral( String name )
{
localPeripherals.put( name, new NetworkPeripheral() );
getNode().updatePeripherals( localPeripherals );
return this;
}
@Nonnull
public Map<String, IPeripheral> allPeripherals()
{
return remotePeripherals;
}
}
private static class NetworkPeripheral implements IPeripheral
{
@Nonnull
@Override
public String getType()
{
return "test";
}
@Override
public boolean equals( @Nullable IPeripheral other )
{
return this == other;
}
}
private static class Grid<T>
{
private final int size;
private final T[] box;
@SuppressWarnings( "unchecked" )
Grid( int size )
{
this.size = size;
this.box = (T[]) new Object[size * size * size];
}
public T get( BlockPos pos )
{
int x = pos.getX(), y = pos.getY(), z = pos.getZ();
return x >= 0 && x < size && y >= 0 && y < size && z >= 0 && z < size
? box[x * size * size + y * size + z]
: null;
}
public void forEach( BiConsumer<T, BlockPos> transform )
{
for( int x = 0; x < size; x++ )
{
for( int y = 0; y < size; y++ )
{
for( int z = 0; z < size; z++ )
{
transform.accept( box[x * size * size + y * size + z], new BlockPos( x, y, z ) );
}
}
}
}
public void map( BiFunction<T, BlockPos, T> transform )
{
for( int x = 0; x < size; x++ )
{
for( int y = 0; y < size; y++ )
{
for( int z = 0; z < size; z++ )
{
box[x * size * size + y * size + z] = transform.apply( box[x * size * size + y * size + z], new BlockPos( x, y, z ) );
}
}
}
}
}
private static Set<WiredNode> nodes( IWiredNetwork network )
{
return ((WiredNetwork) network).nodes;
}
private static Set<WiredNode> neighbours( IWiredNode node )
{
return ((WiredNode) node).neighbours;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,504 +0,0 @@
--- A very basic test framework for ComputerCraft
--
-- Like Busted (http://olivinelabs.com/busted/), but more memorable.
--
-- @usage
-- describe("something to test", function()
-- it("some property", function()
-- expect(some_function()):equals("What it should equal")
-- end)
-- end)
--- Assert an argument to the given function has the specified type.
--
-- @tparam string The function's name
-- @tparam int The argument index to this function
-- @tparam string The type this argument should have. May be 'value' for any
-- non-nil value.
-- @param val The value to check
-- @raise If this value doesn't match the expected type.
local function check(func, arg, ty, val)
if ty == 'value' then
if val == nil then
error(('%s: bad argument #%d (got nil)'):format(func, arg), 3)
end
elseif type(val) ~= ty then
return error(('%s: bad argument #%d (expected %s, got %s)'):format(func, arg, ty, type(val)), 3)
end
end
local active_stubs = {}
--- Stub a global variable with a specific value
--
-- @tparam string var The variable to stub
-- @param value The value to stub it with
local function stub(tbl, var, value)
table.insert(active_stubs, { tbl = tbl, var = var, value = tbl[var] })
_G[var] = value
end
--- Capture the current global state of the computer
local function push_state()
local stubs = active_stubs
active_stubs = {}
return {
term = term.current(),
input = io.input(),
output = io.output(),
stubs = stubs,
}
end
--- Restore the global state of the computer to a previous version
local function pop_state(state)
for i = #active_stubs, 1, -1 do
local stub = active_stubs[i]
stub.tbl[stub.var] = stub.value
end
active_stubs = state.stubs
term.redirect(state.term)
io.input(state.input)
io.output(state.output)
end
local error_mt = { __tostring = function(self) return self.message end }
--- Attempt to execute the provided function, gathering a stack trace when it
-- errors.
--
-- @tparam
-- @return[1] true
-- @return[2] false
-- @return[2] The error object
local function try(fn)
if not debug or not debug.traceback then
local ok, err = pcall(fn)
if ok or getmetatable(err) == error_mt then
return ok, err
else
return ok, setmetatable({ message = tostring(err) }, error_mt)
end
end
local ok, err = xpcall(fn, function(err)
return { message = err, trace = debug.traceback(nil, 2) }
end)
-- If we succeeded, propagate it
if ok then return ok, err end
-- Error handling failed for some reason - just return a simpler error
if type(err) ~= "table" then
return ok, setmetatable({ message = tostring(err) }, error_mt)
end
-- Find the common substring the errors' trace and the current one. Then
-- eliminate it.
local trace = debug.traceback()
for i = 1, #trace do
if trace:sub(-i) ~= err.trace:sub(-i) then
err.trace = err.trace:sub(1, -i)
break
end
end
-- If we've received a rethrown error, copy
if getmetatable(err.message) == error_mt then
for k, v in pairs(err.message) do err[k] = v end
return ok, err
end
return ok, setmetatable(err, error_mt)
end
--- Fail a test with the given message
--
-- @tparam string message The message to fail with
-- @raises An error with the given message
local function fail(message)
check('fail', 1, 'string', message)
error(setmetatable({ message = message, fail = true }, error_mt))
end
--- Format an object in order to make it more readable
--
-- @param value The value to format
-- @treturn string The formatted value
local function format(value)
-- TODO: Look into something like mbs's pretty printer.
local ok, res = pcall(textutils.serialise, value)
if ok then return res else return tostring(value) end
end
local expect_mt = {}
expect_mt.__index = expect_mt
--- Assert that this expectation has the provided value
--
-- @param value The value to require this expectation to be equal to
-- @raises If the values are not equal
function expect_mt:equals(value)
if value ~= self.value then
fail(("Expected %s\n but got %s"):format(format(value), format(self.value)))
end
return self
end
expect_mt.equal = expect_mt.equals
expect_mt.eq = expect_mt.equals
--- Assert that this expectation does not equal the provided value
--
-- @param value The value to require this expectation to not be equal to
-- @raises If the values are equal
function expect_mt:not_equals(value)
if value == self.value then
fail(("Expected any value but %s"):format(format(value)))
end
return self
end
expect_mt.not_equal = expect_mt.not_equals
expect_mt.ne = expect_mt.not_equals
--- Assert that this expectation has something of the provided type
--
-- @tparam string exp_type The type to require this expectation to have
-- @raises If it does not have that thpe
function expect_mt:type(exp_type)
local actual_type = type(self.value)
if exp_type ~= actual_type then
fail(("Expected value of type %s\n got %s"):format(exp_type, actual_type))
end
return self
end
local function matches(eq, exact, left, right)
if left == right then return true end
local ty = type(left)
if ty ~= type(right) or ty ~= "table" then return false end
-- If we've already explored/are exploring the left and right then return
if eq[left] and eq[left][right] then return true end
if not eq[left] then eq[left] = {[right] = true} else eq[left][right] = true end
if not eq[right] then eq[right] = {[left] = true} else eq[right][left] = true end
-- Verify all pairs in left are equal to those in right
for k, v in pairs(left) do
if not matches(eq, exact, v, right[k]) then return false end
end
if exact then
-- And verify all pairs in right are present in left
for k in pairs(right) do
if left[k] == nil then return false end
end
end
return true
end
--- Assert that this expectation is structurally equivalent to
-- the provided object.
--
-- @param value The value to check for structural equivalence
-- @raises If they are not equivalent
function expect_mt:same(value)
if not matches({}, true, self.value, value) then
fail(("Expected %s\n but got %s"):format(format(value), format(self.value)))
end
return self
end
--- Assert that this expectation contains all fields mentioned
-- in the provided object.
--
-- @param value The value to check against
-- @raises If this does not match the provided value
function expect_mt:matches(value)
if not matches({}, false, value, self.value) then
fail(("Expected %s\nto match %s"):format(format(self.value), format(value)))
end
return self
end
local expect = setmetatable( {
--- Construct an expectation on the error message calling this function
-- produces
--
-- @tparam fun The function to call
-- @param ... The function arguments
-- @return The new expectation
error = function(fun, ...)
local ok, res = pcall(fun, ...) local _, line = pcall(error, "", 2)
if ok then fail("expected function to error") end
if res:sub(1, #line) == line then
res = res:sub(#line + 1)
elseif res:sub(1, 7) == "pcall: " then
res = res:sub(8)
end
return setmetatable({ value = res }, expect_mt)
end,
}, {
--- Construct a new expectation from the provided value
--
-- @param value The value to apply assertions to
-- @return The new expectation
__call = function(_, value)
return setmetatable({ value = value }, expect_mt)
end
})
--- The stack of "describe"s.
local test_stack = { n = 0 }
--- Whether we're now running tests, and so cannot run any more.
local tests_locked = false
--- The list of tests that we'll run
local test_list, test_map, test_count = { }, { }, 0
--- Add a new test to our queue.
--
-- @param test The descriptor of this test
local function do_test(test)
-- Set the name if it doesn't already exist
if not test.name then test.name = table.concat(test_stack, "\0", 1, test_stack.n) end
test_count = test_count + 1
test_list[test_count] = test
test_map[test.name] = test_count
end
--- Get the "friendly" name of this test.
--
-- @treturn string This test's friendly name
local function test_name(test) return (test.name:gsub("\0", " \26 ")) end
--- Describe something which will be tested, such as a function or situation
--
-- @tparam string name The name of the object to test
-- @tparam function body A function which describes the tests for this object.
local function describe(name, body)
check('describe', 1, 'string', name)
check('describe', 2, 'function', body)
if tests_locked then error("Cannot describe something while running tests", 2) end
-- Push our name onto the stack, eval and pop it
local n = test_stack.n + 1
test_stack[n], test_stack.n = name, n
local ok, err = try(body)
-- We count errors as a (failing) test.
if not ok then do_test { error = err } end
test_stack.n = n - 1
end
--- Declare a single test within a context
--
-- @tparam string name What you are testing
-- @tparam function body A function which runs the test, failing if it does
-- the assertions are not met.
local function it(name, body)
check('it', 1, 'string', name)
check('it', 2, 'function', body)
if tests_locked then error("Cannot create test while running tests", 2) end
-- Push name onto the stack
local n = test_stack.n + 1
test_stack[n], test_stack.n, tests_locked = name, n, true
do_test { action = body }
-- Pop the test from the stack
test_stack.n, tests_locked = n - 1, false
end
--- Declare a single not-yet-implemented test
--
-- @tparam string name What you really should be testing but aren't
local function pending(name)
check('it', 1, 'string', name)
if tests_locked then error("Cannot create test while running tests", 2) end
local _, loc = pcall(error, "", 3)
loc = loc:gsub(":%s*$", "")
local n = test_stack.n + 1
test_stack[n], test_stack.n = name, n
do_test { pending = true, trace = loc }
test_stack.n = n - 1
end
local arg = ...
if arg == "--help" or arg == "-h" then
io.write("Usage: mcfly [DIR]\n")
io.write("\n")
io.write("Run tests in the provided DIRectory, or `spec` if not given.")
return
end
local root_dir = shell.resolve(arg or "spec")
if not fs.isDir(root_dir) then
io.stderr:write(("%q is not a directory.\n"):format(root_dir))
error()
end
do
-- Load in the tests from all our files
local env = setmetatable({}, { __index = _ENV })
local function set_env(tbl)
for k in pairs(env) do env[k] = nil end
for k, v in pairs(tbl) do env[k] = v end
end
-- When declaring tests, you shouldn't be able to use test methods
set_env { describe = describe, it = it, pending = pending }
local suffix = "_spec.lua"
local function run_in(sub_dir)
for _, name in ipairs(fs.list(sub_dir)) do
local file = fs.combine(sub_dir, name)
if fs.isDir(file) then
run_in(file)
elseif file:sub(-#suffix) == suffix then
local fun, err = loadfile(file, env)
if not fun then
do_test { name = file:sub(#root_dir + 2), error = { message = err } }
else
local ok, err = try(fun)
if not ok then do_test { name = file:sub(#root_dir + 2), error = err } end
end
end
end
end
run_in(root_dir)
-- When running tests, you shouldn't be able to declare new ones.
set_env { expect = expect, fail = fail, stub = stub }
end
-- Error if we've found no tests
if test_count == 0 then
io.stderr:write(("Could not find any tests in %q\n"):format(root_dir))
error()
end
-- The results of each test, as well as how many passed and the count.
local test_results, test_status, tests_run = { n = 0 }, {}, 0
-- All possible test statuses
local statuses = {
pass = { desc = "Pass", col = colours.green, dot = "\7" }, -- Circle
fail = { desc = "Failed", col = colours.red, dot = "\4" }, -- Diamond
error = { desc = "Error", col = colours.magenta, dot = "\4" },
pending = { desc = "Pending", col = colours.yellow, dot = "\186" }, -- Hollow circle
}
-- Set up each test status count.
for k in pairs(statuses) do test_status[k] = 0 end
--- Do the actual running of our test
local function do_run(test)
-- If we're a pre-computed test, determine our status message. Otherwise,
-- skip.
local status, err
if test.pending then
status = "pending"
elseif test.error then
err = test.error
status = "error"
elseif test.action then
local state = push_state()
local ok
ok, err = try(test.action)
status = ok and "pass" or (err.fail and "fail" or "error")
pop_state(state)
end
-- If we've a boolean status, then convert it into a string
if status == true then status = "pass"
elseif status == false then status = err.fail and "fail" or "error"
end
tests_run = tests_run + 1
test_status[status] = test_status[status] + 1
test_results[tests_run] = {
status = status, name = test.name,
message = test.message or err and err.message,
trace = test.trace or err and err.trace,
}
-- If we're running under howlci, then log some info.
if howlci then howlci.status(status, test_name(test)) end
if cct_test then cct_test.submit(test_results[tests_run]) end
-- Print our progress dot
local data = statuses[status]
term.setTextColour(data.col) io.write(data.dot)
term.setTextColour(colours.white)
end
-- Loop over all our tests, running them as required.
if cct_test then
-- If we're within a cct_test environment, then submit them and wait on tests
-- to be run.
cct_test.start(test_map)
while true do
local _, name = os.pullEvent("cct_test_run")
if not name then break end
do_run(test_list[test_map[name]])
end
else
for _, test in pairs(test_list) do do_run(test) end
end
-- Otherwise, display the results of each failure
io.write("\n\n")
for i = 1, tests_run do
local test = test_results[i]
if test.status ~= "pass" then
local status_data = statuses[test.status]
term.setTextColour(status_data.col)
io.write(status_data.desc)
term.setTextColour(colours.white)
io.write(" \26 " .. test_name(test) .. "\n")
if test.message then
io.write(" " .. test.message:gsub("\n", "\n ") .. "\n")
end
if test.trace then
term.setTextColour(colours.lightGrey)
io.write(" " .. test.trace:gsub("\n", "\n ") .. "\n")
end
io.write("\n")
end
end
-- And some summary statistics
local actual_count = tests_run - test_status.pending
local info = ("Ran %s test(s), of which %s passed (%g%%).")
:format(actual_count, test_status.pass, (test_status.pass / actual_count) * 100)
if test_status.pending > 0 then
info = info .. (" Skipped %d pending test(s)."):format(test_status.pending)
end
term.setTextColour(colours.white) io.write(info .. "\n")
if howlci then howlci.log("debug", info) sleep(3) end

View File

@@ -1,92 +0,0 @@
describe("The colors library", function()
describe("colors.combine", function()
it("validates arguments", function()
expect.error(colors.combine, 1, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(colors.combine, 1, 1, nil):eq("bad argument #3 (expected number, got nil)")
end)
it("combines colours", function()
expect(colors.combine()):eq(0)
expect(colors.combine(colors.red, colors.brown, colors.green)):eq(0x7000)
end)
end)
describe("colors.subtract", function()
it("validates arguments", function()
expect.error(colors.subtract, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(colors.subtract, 1, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(colors.subtract, 1, 1, nil):eq("bad argument #3 (expected number, got nil)")
end)
it("subtracts colours", function()
expect(colors.subtract(0x7000, colors.green)):equals(0x5000)
expect(colors.subtract(0x5000, colors.red)):equals(0x1000)
end)
it("does nothing when color is not present", function()
expect(colors.subtract(0x1000, colors.red)):equals(0x1000)
end)
it("accepts multiple arguments", function()
expect(colors.subtract(0x7000, colors.red, colors.green, colors.red)):equals(0x1000)
end)
end)
describe("colors.test", function()
it("validates arguments", function()
expect.error(colors.test, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(colors.test, 1, nil):eq("bad argument #2 (expected number, got nil)")
end)
it("returns true when present", function()
expect(colors.test(0x7000, colors.green)):equals(true)
end)
it("returns false when absent", function()
expect(colors.test(0x5000, colors.green)):equals(false)
end)
it("allows multiple colors", function()
expect(colors.test(0x7000, 0x5000)):equals(true)
end)
end)
describe("colors.packRGB", function()
it("validates arguments", function()
expect.error(colors.packRGB, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(colors.packRGB, 1, nil):eq("bad argument #2 (expected number, got nil)")
expect.error(colors.packRGB, 1, 1, nil):eq("bad argument #3 (expected number, got nil)")
end)
it("packs colours", function()
expect(colors.packRGB(0.3, 0.5, 0.6)):equals(0x4c7f99)
end)
end)
describe("colors.unpackRGB", function()
it("validates arguments", function()
expect.error(colors.unpackRGB, nil):eq("bad argument #1 (expected number, got nil)")
end)
it("unpacks colours", function()
expect({ colors.unpackRGB(0x4c7f99) }):same { 0x4c / 0xFF, 0x7f / 0xFF, 0.6 }
end)
end)
it("colors.rgb8", function()
expect(colors.rgb8(0.3, 0.5, 0.6)):equals(0x4c7f99)
expect({ colors.rgb8(0x4c7f99) }):same { 0x4c / 0xFF, 0x7f / 0xFF, 0.6 }
end)
describe("colors.toBlit", function()
it("validates arguments", function()
expect.error(colors.toBlit, nil):eq("bad argument #1 (expected number, got nil)")
end)
it("converts all colors", function()
for i = 0, 15 do
expect(colors.toBlit(2 ^ i)):eq(string.format("%x", i))
end
end)
it("floors colors", function()
expect(colors.toBlit(16385)):eq("e")
end)
end)
end)

View File

@@ -1,222 +0,0 @@
describe("The fs library", function()
describe("fs.complete", function()
it("validates arguments", function()
fs.complete("", "")
fs.complete("", "", true)
fs.complete("", "", nil, true)
expect.error(fs.complete, nil):eq("bad argument #1 (expected string, got nil)")
expect.error(fs.complete, "", nil):eq("bad argument #2 (expected string, got nil)")
expect.error(fs.complete, "", "", 1):eq("bad argument #3 (expected boolean, got number)")
expect.error(fs.complete, "", "", true, 1):eq("bad argument #4 (expected boolean, got number)")
end)
end)
describe("fs.isDriveRoot", function()
it("validates arguments", function()
fs.isDriveRoot("")
expect.error(fs.isDriveRoot, nil):eq("bad argument #1 (expected string, got nil)")
end)
it("correctly identifies drive roots", function()
expect(fs.isDriveRoot("/rom")):eq(true)
expect(fs.isDriveRoot("/")):eq(true)
expect(fs.isDriveRoot("/rom/startup.lua")):eq(false)
expect(fs.isDriveRoot("/rom/programs/delete.lua")):eq(false)
end)
end)
describe("fs.list", function()
it("fails on files", function()
expect.error(fs.list, "rom/startup.lua"):eq("/rom/startup.lua: Not a directory")
expect.error(fs.list, "startup.lua"):eq("/startup.lua: Not a directory")
end)
it("fails on non-existent nodes", function()
expect.error(fs.list, "rom/x"):eq("/rom/x: Not a directory")
expect.error(fs.list, "x"):eq("/x: Not a directory")
end)
end)
describe("fs.combine", function()
it("removes . and ..", function()
expect(fs.combine("./a/b")):eq("a/b")
expect(fs.combine("a/b", "../c")):eq("a/c")
expect(fs.combine("a", "../c")):eq("c")
expect(fs.combine("a", "../../c")):eq("../c")
end)
it("combines empty paths", function()
expect(fs.combine("a")):eq("a")
expect(fs.combine("a", "")):eq("a")
expect(fs.combine("", "a")):eq("a")
expect(fs.combine("a", "", "b", "c")):eq("a/b/c")
end)
end)
describe("fs.getSize", function()
it("fails on non-existent nodes", function()
expect.error(fs.getSize, "rom/x"):eq("/rom/x: No such file")
expect.error(fs.getSize, "x"):eq("/x: No such file")
end)
end)
describe("fs.open", function()
describe("reading", function()
it("fails on directories", function()
expect { fs.open("rom", "r") }:same { nil, "/rom: No such file" }
expect { fs.open("", "r") }:same { nil, "/: No such file" }
end)
it("fails on non-existent nodes", function()
expect { fs.open("rom/x", "r") }:same { nil, "/rom/x: No such file" }
expect { fs.open("x", "r") }:same { nil, "/x: No such file" }
end)
it("errors when closing twice", function()
local handle = fs.open("rom/startup.lua", "r")
handle.close()
expect.error(handle.close):eq("attempt to use a closed file")
end)
end)
describe("reading in binary mode", function()
it("errors when closing twice", function()
local handle = fs.open("rom/startup.lua", "rb")
handle.close()
expect.error(handle.close):eq("attempt to use a closed file")
end)
end)
describe("writing", function()
it("fails on directories", function()
expect { fs.open("", "w") }:same { nil, "/: Cannot write to directory" }
end)
it("fails on read-only mounts", function()
expect { fs.open("rom/x", "w") }:same { nil, "/rom/x: Access denied" }
end)
it("errors when closing twice", function()
local handle = fs.open("test-files/out.txt", "w")
handle.close()
expect.error(handle.close):eq("attempt to use a closed file")
end)
it("fails gracefully when opening 'CON' on Windows", function()
local ok, err = fs.open("test-files/con", "w")
if ok then fs.delete("test-files/con") return end
-- On my Windows/Java version the message appears to be "Incorrect function.". It may not be
-- consistent though, and honestly doesn't matter too much.
expect(err):str_match("^/test%-files/con: .*")
end)
end)
describe("writing in binary mode", function()
it("errors when closing twice", function()
local handle = fs.open("test-files/out.txt", "wb")
handle.close()
expect.error(handle.close):eq("attempt to use a closed file")
end)
end)
describe("appending", function()
it("fails on directories", function()
expect { fs.open("", "a") }:same { nil, "/: Cannot write to directory" }
end)
it("fails on read-only mounts", function()
expect { fs.open("rom/x", "a") }:same { nil, "/rom/x: Access denied" }
end)
end)
end)
describe("fs.makeDir", function()
it("fails on files", function()
expect.error(fs.makeDir, "startup.lua"):eq("/startup.lua: File exists")
end)
it("fails on read-only mounts", function()
expect.error(fs.makeDir, "rom/x"):eq("/rom/x: Access denied")
end)
end)
describe("fs.delete", function()
it("fails on read-only mounts", function()
expect.error(fs.delete, "rom/x"):eq("/rom/x: Access denied")
end)
end)
describe("fs.copy", function()
it("fails on read-only mounts", function()
expect.error(fs.copy, "rom", "rom/startup"):eq("/rom/startup: Access denied")
end)
it("fails to copy a folder inside itself", function()
fs.makeDir("some-folder")
expect.error(fs.copy, "some-folder", "some-folder/x"):eq("/some-folder: Can't copy a directory inside itself")
expect.error(fs.copy, "some-folder", "Some-Folder/x"):eq("/some-folder: Can't copy a directory inside itself")
end)
it("copies folders", function()
fs.delete("some-folder")
fs.delete("another-folder")
fs.makeDir("some-folder")
fs.copy("some-folder", "another-folder")
expect(fs.isDir("another-folder")):eq(true)
end)
end)
describe("fs.move", function()
it("fails on read-only mounts", function()
expect.error(fs.move, "rom", "rom/move"):eq("Access denied")
expect.error(fs.move, "test-files", "rom/move"):eq("Access denied")
expect.error(fs.move, "rom", "test-files"):eq("Access denied")
end)
end)
describe("fs.getCapacity", function()
it("returns nil on read-only mounts", function()
expect(fs.getCapacity("rom")):eq(nil)
end)
it("returns the capacity on the root mount", function()
expect(fs.getCapacity("")):eq(10000000)
end)
end)
describe("fs.attributes", function()
it("errors on non-existent files", function()
expect.error(fs.attributes, "xuxu_nao_existe"):eq("/xuxu_nao_existe: No such file")
end)
it("returns information about read-only mounts", function()
expect(fs.attributes("rom")):matches { isDir = true, size = 0, isReadOnly = true }
end)
it("returns information about files", function()
local now = os.epoch("utc")
fs.delete("/tmp/basic-file")
local h = fs.open("/tmp/basic-file", "w")
h.write("A reasonably sized string")
h.close()
local attributes = fs.attributes("tmp/basic-file")
expect(attributes):matches { isDir = false, size = 25, isReadOnly = false }
if attributes.created - now >= 1000 then
fail(("Expected created time (%d) to be within 1000ms of now (%d"):format(attributes.created, now))
end
if attributes.modified - now >= 1000 then
fail(("Expected modified time (%d) to be within 1000ms of now (%d"):format(attributes.modified, now))
end
expect(attributes.modification):eq(attributes.modified)
end)
end)
end)

View File

@@ -1,15 +0,0 @@
describe("The gps library", function()
describe("gps.locate", function()
it("validates arguments", function()
stub(_G, "commands", { getBlockPosition = function()
end })
gps.locate()
gps.locate(1)
gps.locate(1, true)
expect.error(gps.locate, ""):eq("bad argument #1 (expected number, got string)")
expect.error(gps.locate, 1, ""):eq("bad argument #2 (expected boolean, got string)")
end)
end)
end)

Some files were not shown because too many files have changed in this diff Show More