mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-15 22:17:39 +00:00
Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1a52275dcb | ||
![]() |
2fc2cf2e29 | ||
![]() |
80c2fc68aa | ||
![]() |
7c0664b9f2 | ||
![]() |
4d1a950fbf | ||
![]() |
135a3f56a5 | ||
![]() |
1f117b7c47 | ||
![]() |
c820e051b3 | ||
![]() |
01d7aaf062 | ||
![]() |
1e75e4ff6e | ||
![]() |
f967a70121 | ||
![]() |
ac5150a664 | ||
![]() |
0318e62524 | ||
![]() |
ca69d755e6 | ||
![]() |
3ba7acc414 | ||
![]() |
4c27eea73b | ||
![]() |
225a64dd6b | ||
![]() |
1831e81dd4 | ||
![]() |
a4dd6c24e5 | ||
![]() |
936742895b | ||
![]() |
b5d1e618b9 | ||
![]() |
10aa6c5297 | ||
![]() |
010ebacd1a | ||
![]() |
9e002beed7 | ||
![]() |
6029defb20 | ||
![]() |
c16aa5f247 | ||
![]() |
8d27bdca7b | ||
![]() |
41aa8fa163 | ||
![]() |
e40fb67b50 | ||
![]() |
96d46ffd2c | ||
![]() |
652b7ebba6 | ||
![]() |
f0d7a1165d | ||
![]() |
4a20eea852 | ||
![]() |
ccdd2bf477 | ||
![]() |
8d8f41a50b | ||
![]() |
72c78db28b | ||
![]() |
bf8a992273 | ||
![]() |
9c214fcd8a | ||
![]() |
082cc9d2d8 | ||
![]() |
bff2e81713 | ||
![]() |
63aa3d8877 | ||
![]() |
74c5d7e719 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -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.
|
||||
|
12
.github/ISSUE_TEMPLATE/peripheral_shoutout.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE/peripheral_shoutout.md
vendored
Normal 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
|
14
.travis.yml
14
.travis.yml
@@ -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
|
46
README.md
46
README.md
@@ -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%"/>
|
||||
|
||||
[](https://github.com/o-iM-nI/cc-restitched/actions "Current build status") [](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
|
||||
[](https://github.com/Merith-TK/cc-restitched/actions "Current build status") [](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!
|
||||
|
||||
*  [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!
|
@@ -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"
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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() {
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
}
|
@@ -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;
|
||||
|
@@ -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();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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 );
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -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) {
|
||||
|
@@ -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();
|
||||
}
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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;
|
@@ -4,7 +4,7 @@
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.mixin;
|
||||
package dan200.computercraft.fabric.mixin;
|
||||
|
||||
import java.util.Collection;
|
||||
|
@@ -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;
|
@@ -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();
|
||||
}
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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"))));
|
||||
}
|
||||
}
|
@@ -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() );
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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();
|
||||
|
@@ -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) {
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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.
|
||||
*
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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()) {
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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());
|
||||
|
@@ -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;
|
||||
|
@@ -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)) {
|
||||
|
@@ -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;
|
||||
|
@@ -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 );
|
||||
}
|
||||
}
|
@@ -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 |
@@ -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"
|
||||
],
|
||||
|
@@ -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 = {
|
||||
|
@@ -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)
|
||||
|
@@ -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.
|
||||
|
@@ -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 })
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -16,7 +16,8 @@
|
||||
"SquidDev",
|
||||
"parly",
|
||||
"Merith.TK",
|
||||
"Jummit"
|
||||
"Jummit",
|
||||
"Toad-Dev"
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.4.0",
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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" );
|
||||
}
|
||||
}
|
@@ -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 ) ) );
|
||||
}
|
||||
}
|
@@ -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 ) ) ) );
|
||||
}
|
||||
}
|
@@ -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 ) );
|
||||
}
|
||||
}
|
@@ -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() );
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@@ -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() );
|
||||
}
|
||||
}
|
@@ -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() );
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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 );
|
||||
}
|
||||
}
|
@@ -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() );
|
||||
}
|
||||
}
|
@@ -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
@@ -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
|
@@ -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)
|
@@ -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)
|
@@ -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
Reference in New Issue
Block a user