mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-16 06:27:39 +00:00
Compare commits
163 Commits
v1.80pr1.2
...
v1.80pr1.9
Author | SHA1 | Date | |
---|---|---|---|
![]() |
51e787f631 | ||
![]() |
8080699030 | ||
![]() |
e555f9f7f0 | ||
![]() |
822db6e9b5 | ||
![]() |
ac1f30ef43 | ||
![]() |
0fc1b8c46b | ||
![]() |
e7c19bcf55 | ||
![]() |
63ca8aca4c | ||
![]() |
2caa9c57fc | ||
![]() |
33fad2da15 | ||
![]() |
518eefbe10 | ||
![]() |
1ba73454c1 | ||
![]() |
ee4735c17c | ||
![]() |
b008edae90 | ||
![]() |
c6bd88f3ad | ||
![]() |
efa57521c7 | ||
![]() |
4700f8831b | ||
![]() |
9428bee316 | ||
![]() |
89c7183a1d | ||
![]() |
d2a9e7e458 | ||
![]() |
1774f1a079 | ||
![]() |
de1307913b | ||
![]() |
093132533d | ||
![]() |
0685be6bfa | ||
![]() |
f40733e9a6 | ||
![]() |
a3d1cff298 | ||
![]() |
b8957cab5c | ||
![]() |
3ac8dde779 | ||
![]() |
17dace979a | ||
![]() |
d405316a4b | ||
![]() |
7e18f2cead | ||
![]() |
000786a1a7 | ||
![]() |
0bf13562b9 | ||
![]() |
45a189e834 | ||
![]() |
0ce6f34a09 | ||
![]() |
4d984dc5ee | ||
![]() |
984d358930 | ||
![]() |
a95893b823 | ||
![]() |
81aaead032 | ||
![]() |
a2083bcff1 | ||
![]() |
052f2a16dc | ||
![]() |
fd10ed6f62 | ||
![]() |
c0bdd4ff1d | ||
![]() |
5f0addbc3e | ||
![]() |
c9589ad0e7 | ||
![]() |
b21c495815 | ||
![]() |
18429c50f9 | ||
![]() |
7ec8ddcf7d | ||
![]() |
5bf9f9e3c5 | ||
![]() |
e4164ee9a1 | ||
![]() |
b522af3075 | ||
![]() |
8775052dee | ||
![]() |
c0c5d57e10 | ||
![]() |
4c2e97b1af | ||
![]() |
f17df15117 | ||
![]() |
bfbb18bdfc | ||
![]() |
cac65ef755 | ||
![]() |
a42793024b | ||
![]() |
7dbc4e6455 | ||
![]() |
a0d71cb3ad | ||
![]() |
83546d0acb | ||
![]() |
e2f9ddd534 | ||
![]() |
911e404bfa | ||
![]() |
bfeafe163f | ||
![]() |
6cf32f1f74 | ||
![]() |
04f162ef25 | ||
![]() |
b2aa390ae1 | ||
![]() |
6ca61f000f | ||
![]() |
20a47a7f88 | ||
![]() |
e2e6946c92 | ||
![]() |
abe917cd54 | ||
![]() |
a1d77ab8e7 | ||
![]() |
c8db671409 | ||
![]() |
52641b7bea | ||
![]() |
3e751ee94a | ||
![]() |
2b28cc3558 | ||
![]() |
f9761388b1 | ||
![]() |
d28694eb57 | ||
![]() |
d758895578 | ||
![]() |
043d5f00ca | ||
![]() |
36878e75b7 | ||
![]() |
ebb50cba48 | ||
![]() |
7c218361d9 | ||
![]() |
bb2eab0bed | ||
![]() |
e8c0cf3857 | ||
![]() |
db825a7aab | ||
![]() |
dbcae810f0 | ||
![]() |
914df8b0c7 | ||
![]() |
f753513289 | ||
![]() |
7bb8efed1d | ||
![]() |
0cec4aee8c | ||
![]() |
e0c9dc24e7 | ||
![]() |
244907a39a | ||
![]() |
9be61abd6b | ||
![]() |
922f424a78 | ||
![]() |
5c7828dd79 | ||
![]() |
74f5093d2a | ||
![]() |
4651e362c9 | ||
![]() |
a2e2a5cb37 | ||
![]() |
15a3882016 | ||
![]() |
d3ecd5214b | ||
![]() |
0a8a8a742e | ||
![]() |
ecff23d027 | ||
![]() |
20dcb32bae | ||
![]() |
678462d2db | ||
![]() |
61fdfec09b | ||
![]() |
5eadf5533d | ||
![]() |
2d3cd5dc80 | ||
![]() |
5208ad0b98 | ||
![]() |
662fb96beb | ||
![]() |
4c14431a3d | ||
![]() |
5ae38a3f18 | ||
![]() |
94e10d1f67 | ||
![]() |
0a50676884 | ||
![]() |
41cce78fcb | ||
![]() |
4c0fa1fabe | ||
![]() |
54e1dafa3f | ||
![]() |
3ac76bc05b | ||
![]() |
83030df3ee | ||
![]() |
07d15caf6f | ||
![]() |
3298efe652 | ||
![]() |
01d9919a3e | ||
![]() |
80b1170b63 | ||
![]() |
2e7302e654 | ||
![]() |
ca7fb8a0b4 | ||
![]() |
c9b0894f26 | ||
![]() |
c3454a195d | ||
![]() |
3b3dd8071b | ||
![]() |
0d28c67534 | ||
![]() |
d0af85754a | ||
![]() |
3e265c27ff | ||
![]() |
8d356f50c4 | ||
![]() |
f30c4f16c0 | ||
![]() |
8bb8caa315 | ||
![]() |
0f17a3d72e | ||
![]() |
7647369e2d | ||
![]() |
4b4208e724 | ||
![]() |
2a16a1df85 | ||
![]() |
25f7c58400 | ||
![]() |
c3db91f11f | ||
![]() |
8c66ce03d4 | ||
![]() |
2be2a0625e | ||
![]() |
c904d5041b | ||
![]() |
632762768e | ||
![]() |
c69ba205f8 | ||
![]() |
019f4dbea9 | ||
![]() |
259ea41ce3 | ||
![]() |
11290f7204 | ||
![]() |
abd06133fb | ||
![]() |
29a3a0c48f | ||
![]() |
2728c63512 | ||
![]() |
f3b11bc1c2 | ||
![]() |
04590befb3 | ||
![]() |
4e9034f910 | ||
![]() |
ba9cfa3764 | ||
![]() |
4fb0240a36 | ||
![]() |
f20a7afa7f | ||
![]() |
5be2202b2e | ||
![]() |
b8630f739a | ||
![]() |
1ef7c8e8db | ||
![]() |
1415dd0dae | ||
![]() |
5989d021c7 | ||
![]() |
90c4ebd208 |
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report some misbehaviour in the mod
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
## Before reporting
|
||||
- Search for the bug both here and [on the ComputerCraft issues page](https://github.com/dan200/ComputerCraft/issues?utf8=%E2%9C%93&q=is%3Aissue+)
|
||||
- If possible, try to reproduce on vanilla ComputerCraft. If it still occurs, [report on the ComputerCraft repo](https://github.com/dan200/ComputerCraft/issues/new) instead.
|
||||
-->
|
||||
|
||||
## Useful information to include:
|
||||
- Minecraft version
|
||||
- CC: Tweaked version
|
||||
- Detailed reproduction steps!** Sometimes I can spot a bug pretty easily, but often it's much more obscure. Anything you can give which will help reproduce it means it'll get fixed quicker.
|
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
15
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea or improvement
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
## Before reporting
|
||||
- Search for the suggestion both here and [on the ComputerCraft issues page](https://github.com/dan200/ComputerCraft/issues?utf8=%E2%9C%93&q=is%3Aissue+). It's possible someone's suggested it before!
|
||||
- Unless something is specific to CC:Tweaked, try to [suggest them on the ComputerCraft repo](https://github.com/dan200/ComputerCraft/issues/new). There's a lot more people watching it, so it allows the wider community to contribute.
|
||||
-->
|
||||
|
||||
## Useful information to include:
|
||||
- Explanation of how the feature/change chould work.
|
||||
- Some rationale/use case for a feature. I'd like to keep CC:T as minimal
|
9
.github/pull_request_template.md
vendored
Normal file
9
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<!--
|
||||
Unless this feature is specific to CC:Tweaked, try to [target the original ComputerCraft repo](https://github.com/dan200/ComputerCraft/) instead. There's a lot more people watching it, so it allows the wider community to contribute.
|
||||
-->
|
||||
|
||||
## Useful information to include:
|
||||
- Brief explanation of the changes you've made.
|
||||
- Rationale of why this change has been made/reasoning behind it.
|
||||
|
||||
The more information you can provide, the easier it is to review something now _and_ to see why a change was made, when the code needs updating in the future.
|
40
README.md
40
README.md
@@ -6,26 +6,44 @@ features of the mod. For a more stable experience, I recommend checking out the
|
||||
[original mod](https://github.com/dan200/ComputerCraft).
|
||||
|
||||
## What?
|
||||
CC: Tweaked does not aim to create a competing fork of ComputerCraft, nor am I planning to take it in in a vastly
|
||||
different direction to the original mod. In fact, CC: Tweaked aims to be a nurturing ground for various features, with
|
||||
a pull request against the original mod being the end goal.
|
||||
CC: Tweaked (or CC:T for short) does not aim to create a competing fork of ComputerCraft, nor am I planning to take it
|
||||
in in a vastly different direction to the original mod. In fact, CC:T aims to be a nurturing ground for various
|
||||
features, with a pull request against the original mod being the end goal.
|
||||
|
||||
CC: Tweaked also includes many pull requests from the community which have not yet been merged, offering a large number
|
||||
CC:T also includes many pull requests from the community which have not yet been merged, offering a large number
|
||||
of additional bug fixes and features over the original mod.
|
||||
|
||||
## Features
|
||||
CC: Tweaked contains the all features of the latest alpha, as well as numerous fixes, performance improvements and
|
||||
several additional features. I'd recommend checking out [the releases page](https://github.com/SquidDev-CC/CC-Tweaked/releases)
|
||||
to see the full changes, but here's a couple of the more interesting changes:
|
||||
|
||||
- Replace LuaJ with Cobalt.
|
||||
- Allow running multiple computers at the same time.
|
||||
- Websocket support in the HTTP library.
|
||||
- Wired modems and cables act more like multiparts.
|
||||
- Add map-like rendering for pocket computers and printed pages/books.
|
||||
- Adds the `/computercraft` command, offering various diagnostic tools for server owners. This allows operators to
|
||||
track which computers are hogging resources, turn on and shutdown multiple computers at once and interact with
|
||||
computers remotely.
|
||||
- Add full-block wired modems, allowing one to wrap non-solid peripherals (such as turtles, or chests if Plethora is
|
||||
installed).
|
||||
- Extended binary file handles. They support file seeking, and reading new lines, allowing full (and accurate)
|
||||
emulation of the standard Lua `io` library.
|
||||
|
||||
## Relation to CCTweaks?
|
||||
This mod has nothing to do with CCTweaks, though there is no denying the name is a throwback to it. However, I do plan
|
||||
to migrate some features of CCTweaks into CC: Tweaked.
|
||||
This mod has nothing to do with CCTweaks, though there is no denying the name is a throwback to it. That being said,
|
||||
several features have been included, such as full block modems, the Cobalt runtime and map-like rendering for pocket
|
||||
computers.
|
||||
|
||||
## Contributing
|
||||
Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you do wish to contribute
|
||||
code, do consider submitting it to the ComputerCraft repository instead.
|
||||
code, do consider submitting it to the ComputerCraft repository first.
|
||||
|
||||
That being said, in order to start helping develop CC: Tweaked, you'll need to follow these steps:
|
||||
That being said, in order to start helping develop CC:T, you'll need to follow these steps:
|
||||
|
||||
- **Clone the repository:** `git clone https://github.com/SquidDev-CC/CC-Tweaked.git && cd CC-Tweaked`
|
||||
- **Setup Forge:** `./gradlew setupDecompWorkspace`
|
||||
- **Test your changes:** `./gradlew runClient` (or run the `GradleStart` class from your IDE).
|
||||
|
||||
If you want to run CC: Tweaked in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from
|
||||
`build/libs`.
|
||||
|
||||
If you want to run CC:T in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from `build/libs`.
|
||||
|
99
build.gradle
99
build.gradle
@@ -13,22 +13,22 @@ buildscript {
|
||||
classpath 'org.ajoberstar:gradle-git:1.6.0'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.matthewprenger.cursegradle' version '1.0.10'
|
||||
}
|
||||
|
||||
apply plugin: 'net.minecraftforge.gradle.forge'
|
||||
apply plugin: 'org.ajoberstar.grgit'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'maven'
|
||||
|
||||
/*
|
||||
// for people who want stable - not yet functional for MC 1.8.8 - we require the forgegradle 2.1 snapshot
|
||||
plugins {
|
||||
id "net.minecraftforge.gradle.forge" version "2.0.2"
|
||||
}
|
||||
*/
|
||||
|
||||
version = "1.80pr1.1"
|
||||
version = "1.80pr1.9"
|
||||
group = "org.squiddev"
|
||||
archivesBaseName = "cc-tweaked"
|
||||
|
||||
minecraft {
|
||||
version = "1.12-14.21.1.2387"
|
||||
version = "1.12.2-14.23.4.2749"
|
||||
runDir = "run"
|
||||
replace '${version}', project.version
|
||||
|
||||
@@ -37,7 +37,7 @@ minecraft {
|
||||
// stable_# stables are built at the discretion of the MCP team.
|
||||
// Use non-default mappings at your own risk. they may not allways work.
|
||||
// simply re-run your setup task after changing the mappings to update your workspace.
|
||||
mappings = "snapshot_20170629"
|
||||
mappings = "snapshot_20180724"
|
||||
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
|
||||
}
|
||||
|
||||
@@ -50,17 +50,27 @@ repositories {
|
||||
name = "squiddev"
|
||||
url = "https://dl.bintray.com/squiddev/maven"
|
||||
}
|
||||
|
||||
ivy { artifactPattern "https://asie.pl/files/mods/Charset/LibOnly/[module]-[revision](-[classifier]).[ext]" }
|
||||
}
|
||||
|
||||
configurations {
|
||||
shade
|
||||
compile.extendsFrom shade
|
||||
deployerJars
|
||||
}
|
||||
|
||||
dependencies {
|
||||
deobfProvided "mezz.jei:jei_1.12:4.7.5.86:api"
|
||||
runtime "mezz.jei:jei_1.12:4.7.5.86"
|
||||
shade 'org.squiddev:Cobalt:0.3.0'
|
||||
deobfProvided "mezz.jei:jei_1.12.2:4.8.5.159:api"
|
||||
deobfProvided "pl.asie:Charset-Lib:0.5.4.6"
|
||||
|
||||
runtime "mezz.jei:jei_1.12.2:4.8.5.159"
|
||||
|
||||
shade 'org.squiddev:Cobalt:0.3.2'
|
||||
|
||||
testCompile 'junit:junit:4.11'
|
||||
|
||||
deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0"
|
||||
}
|
||||
|
||||
javadoc {
|
||||
@@ -115,6 +125,69 @@ processResources {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
curseforge {
|
||||
apiKey = project.hasProperty('curseForgeApiKey') ? project.curseForgeApiKey : ''
|
||||
project {
|
||||
id = '282001'
|
||||
releaseType = 'beta'
|
||||
changelog = "Release notes can be found on the GitHub repository (https://github.com/SquidDev-CC/CC-Tweaked/releases/tag/v${project.version})."
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
artifact sourceJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uploadArchives {
|
||||
repositories {
|
||||
if(project.hasProperty('mavenUploadUrl')) {
|
||||
mavenDeployer {
|
||||
configuration = configurations.deployerJars
|
||||
|
||||
repository(url: project.property('mavenUploadUrl')) {
|
||||
authentication(
|
||||
userName: project.property('mavenUploadUser'),
|
||||
privateKey: project.property('mavenUploadKey'))
|
||||
}
|
||||
|
||||
pom.project {
|
||||
name 'CC: Tweaked'
|
||||
packaging 'jar'
|
||||
description 'A fork of ComputerCraft which aims to provide earlier access to the more experimental and in-development features of the mod.'
|
||||
url 'https://github.com/SquidDev-CC/CC-Tweaked'
|
||||
|
||||
scm {
|
||||
url 'https://github.com/dan200/ComputerCraft.git'
|
||||
}
|
||||
|
||||
issueManagement {
|
||||
system 'github'
|
||||
url 'https://github.com/dan200/ComputerCraft/issues'
|
||||
}
|
||||
|
||||
licenses {
|
||||
license {
|
||||
name 'ComputerCraft Public License, Version 1.0'
|
||||
url 'https://github.com/dan200/ComputerCraft/blob/master/LICENSE'
|
||||
distribution 'repo'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pom.whenConfigured { pom ->
|
||||
pom.dependencies.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << "-Xlint"
|
||||
|
1
settings.gradle
Normal file
1
settings.gradle
Normal file
@@ -0,0 +1 @@
|
||||
rootProject.name = 'cc-tweaked'
|
@@ -7,28 +7,37 @@
|
||||
package dan200.computercraft;
|
||||
|
||||
import com.google.common.base.CaseFormat;
|
||||
import com.google.common.base.Converter;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
import dan200.computercraft.api.media.IMediaProvider;
|
||||
import dan200.computercraft.api.network.IPacketNetwork;
|
||||
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.IPeripheralProvider;
|
||||
import dan200.computercraft.api.permissions.ITurtlePermissionProvider;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.event.TurtleAction;
|
||||
import dan200.computercraft.core.apis.AddressPredicate;
|
||||
import dan200.computercraft.core.filesystem.ComboMount;
|
||||
import dan200.computercraft.core.filesystem.FileMount;
|
||||
import dan200.computercraft.core.filesystem.JarMount;
|
||||
import dan200.computercraft.core.filesystem.FileSystemMount;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.tracking.Tracking;
|
||||
import dan200.computercraft.shared.command.CommandComputer;
|
||||
import dan200.computercraft.shared.command.CommandComputerCraft;
|
||||
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
|
||||
import dan200.computercraft.shared.computer.blocks.BlockCommandComputer;
|
||||
import dan200.computercraft.shared.computer.blocks.BlockComputer;
|
||||
import dan200.computercraft.shared.computer.blocks.TileComputer;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
|
||||
import dan200.computercraft.shared.media.items.ItemDiskExpanded;
|
||||
import dan200.computercraft.shared.media.items.ItemDiskLegacy;
|
||||
@@ -38,6 +47,7 @@ import dan200.computercraft.shared.network.ComputerCraftPacket;
|
||||
import dan200.computercraft.shared.network.PacketHandler;
|
||||
import dan200.computercraft.shared.peripheral.common.BlockCable;
|
||||
import dan200.computercraft.shared.peripheral.common.BlockPeripheral;
|
||||
import dan200.computercraft.shared.peripheral.common.BlockWiredModemFull;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
|
||||
import dan200.computercraft.shared.peripheral.modem.BlockAdvancedModem;
|
||||
import dan200.computercraft.shared.peripheral.modem.WirelessNetwork;
|
||||
@@ -51,6 +61,8 @@ import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
|
||||
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
|
||||
import dan200.computercraft.shared.turtle.upgrades.*;
|
||||
import dan200.computercraft.shared.util.*;
|
||||
import dan200.computercraft.shared.wired.CapabilityWiredElement;
|
||||
import dan200.computercraft.shared.wired.WiredNode;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -58,11 +70,13 @@ import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.NonNullList;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.config.ConfigCategory;
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
@@ -83,7 +97,11 @@ import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.ProviderNotFoundException;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
@@ -108,6 +126,7 @@ public class ComputerCraft
|
||||
// ComputerCraftEdu uses ID 104
|
||||
public static final int printoutGUIID = 105;
|
||||
public static final int pocketComputerGUIID = 106;
|
||||
public static final int viewComputerGUIID = 110;
|
||||
|
||||
// Configuration options
|
||||
private static final String[] DEFAULT_HTTP_WHITELIST = new String[] { "*" };
|
||||
@@ -135,6 +154,7 @@ public class ComputerCraft
|
||||
public static int advancedTurtleFuelLimit = 100000;
|
||||
public static boolean turtlesObeyBlockProtection = true;
|
||||
public static boolean turtlesCanPush = true;
|
||||
public static EnumSet<TurtleAction> turtleDisabledActions = EnumSet.noneOf( TurtleAction.class );
|
||||
|
||||
public static final int terminalWidth_computer = 51;
|
||||
public static final int terminalHeight_computer = 19;
|
||||
@@ -167,6 +187,7 @@ public class ComputerCraft
|
||||
public static BlockTurtle turtleAdvanced;
|
||||
public static BlockCommandComputer commandComputer;
|
||||
public static BlockAdvancedModem advancedModem;
|
||||
public static BlockWiredModemFull wiredModemFull;
|
||||
}
|
||||
|
||||
public static class Items
|
||||
@@ -217,6 +238,7 @@ public class ComputerCraft
|
||||
public static Property advancedTurtleFuelLimit;
|
||||
public static Property turtlesObeyBlockProtection;
|
||||
public static Property turtlesCanPush;
|
||||
public static Property turtleDisabledActions;
|
||||
|
||||
public static Property modem_range;
|
||||
public static Property modem_highAltitudeRange;
|
||||
@@ -272,6 +294,18 @@ public class ComputerCraft
|
||||
|
||||
// Load config
|
||||
Config.config = new Configuration( event.getSuggestedConfigurationFile() );
|
||||
loadConfig();
|
||||
|
||||
// Setup network
|
||||
networkEventChannel = NetworkRegistry.INSTANCE.newEventDrivenChannel( "CC" );
|
||||
networkEventChannel.register( new PacketHandler() );
|
||||
|
||||
proxy.preInit();
|
||||
turtleProxy.preInit();
|
||||
}
|
||||
|
||||
public static void loadConfig()
|
||||
{
|
||||
Config.config.load();
|
||||
|
||||
Config.http_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "http_enable", http_enable );
|
||||
@@ -360,6 +394,9 @@ public class ComputerCraft
|
||||
Config.turtlesCanPush = Config.config.get( Configuration.CATEGORY_GENERAL, "turtlesCanPush", turtlesCanPush );
|
||||
Config.turtlesCanPush.setComment( "If set to true, Turtles will push entities out of the way instead of stopping if there is space to do so" );
|
||||
|
||||
Config.turtleDisabledActions = Config.config.get( Configuration.CATEGORY_GENERAL, "turtle_disabled_actions", new String[ 0 ] );
|
||||
Config.turtleDisabledActions.setComment( "A list of turtle actions which are disabled." );
|
||||
|
||||
Config.maxNotesPerTick = Config.config.get( Configuration.CATEGORY_GENERAL, "maxNotesPerTick", maxNotesPerTick );
|
||||
Config.maxNotesPerTick.setComment( "Maximum amount of notes a speaker can play at once" );
|
||||
|
||||
@@ -369,13 +406,6 @@ public class ComputerCraft
|
||||
}
|
||||
|
||||
syncConfig();
|
||||
|
||||
// Setup network
|
||||
networkEventChannel = NetworkRegistry.INSTANCE.newEventDrivenChannel( "CC" );
|
||||
networkEventChannel.register( new PacketHandler() );
|
||||
|
||||
proxy.preInit();
|
||||
turtleProxy.preInit();
|
||||
}
|
||||
|
||||
public static void syncConfig() {
|
||||
@@ -407,6 +437,20 @@ public class ComputerCraft
|
||||
turtlesObeyBlockProtection = Config.turtlesObeyBlockProtection.getBoolean();
|
||||
turtlesCanPush = Config.turtlesCanPush.getBoolean();
|
||||
|
||||
turtleDisabledActions.clear();
|
||||
Converter<String, String> converter = CaseFormat.LOWER_CAMEL.converterTo( CaseFormat.UPPER_UNDERSCORE );
|
||||
for( String value : Config.turtleDisabledActions.getStringList() )
|
||||
{
|
||||
try
|
||||
{
|
||||
turtleDisabledActions.add( TurtleAction.valueOf( converter.convert( value ) ) );
|
||||
}
|
||||
catch( IllegalArgumentException e )
|
||||
{
|
||||
ComputerCraft.log.error( "Unknown turtle action " + value );
|
||||
}
|
||||
}
|
||||
|
||||
maxNotesPerTick = Math.max(1, Config.maxNotesPerTick.getInt());
|
||||
|
||||
Config.config.save();
|
||||
@@ -422,7 +466,7 @@ public class ComputerCraft
|
||||
@Mod.EventHandler
|
||||
public void onServerStarting( FMLServerStartingEvent event )
|
||||
{
|
||||
event.registerServerCommand( new CommandComputer() );
|
||||
proxy.initServer( event.getServer() );
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
@@ -432,6 +476,7 @@ public class ComputerCraft
|
||||
{
|
||||
ComputerCraft.serverComputerRegistry.reset();
|
||||
WirelessNetwork.resetNetworks();
|
||||
Tracking.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,6 +487,7 @@ public class ComputerCraft
|
||||
{
|
||||
ComputerCraft.serverComputerRegistry.reset();
|
||||
WirelessNetwork.resetNetworks();
|
||||
Tracking.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,11 +511,6 @@ public class ComputerCraft
|
||||
return proxy.getRenderFrame();
|
||||
}
|
||||
|
||||
public static void deleteDisplayLists( int list, int range )
|
||||
{
|
||||
proxy.deleteDisplayLists( list, range );
|
||||
}
|
||||
|
||||
public static Object getFixedWidthFontRenderer()
|
||||
{
|
||||
return proxy.getFixedWidthFontRenderer();
|
||||
@@ -519,6 +560,24 @@ public class ComputerCraft
|
||||
player.openGui( ComputerCraft.instance, ComputerCraft.pocketComputerGUIID, player.getEntityWorld(), hand.ordinal(), 0, 0 );
|
||||
}
|
||||
|
||||
public static void openComputerGUI( EntityPlayer player, ServerComputer computer )
|
||||
{
|
||||
ComputerFamily family = computer.getFamily();
|
||||
int width = 0, height = 0;
|
||||
Terminal terminal = computer.getTerminal();
|
||||
if( terminal != null )
|
||||
{
|
||||
width = terminal.getWidth();
|
||||
height = terminal.getHeight();
|
||||
}
|
||||
|
||||
// Pack useful terminal information into the various coordinate bits.
|
||||
// These are extracted in ComputerCraftProxyCommon.getClientGuiElement
|
||||
player.openGui( ComputerCraft.instance, ComputerCraft.viewComputerGUIID, player.getEntityWorld(),
|
||||
computer.getInstanceID(), family.ordinal(), (width & 0xFFFF) << 16 | (height & 0xFFFF)
|
||||
);
|
||||
}
|
||||
|
||||
public static File getBaseDir()
|
||||
{
|
||||
return FMLCommonHandler.instance().getMinecraftServerInstance().getFile(".");
|
||||
@@ -680,6 +739,11 @@ public class ComputerCraft
|
||||
}
|
||||
}
|
||||
|
||||
public static IWiredNode createWiredNodeForElement( IWiredElement element )
|
||||
{
|
||||
return new WiredNode( element );
|
||||
}
|
||||
|
||||
public static IPeripheral getPeripheralAt( World world, BlockPos pos, EnumFacing side )
|
||||
{
|
||||
// Try the handlers in order:
|
||||
@@ -701,6 +765,14 @@ public class ComputerCraft
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IWiredElement getWiredElementAt( IBlockAccess world, BlockPos pos, EnumFacing side )
|
||||
{
|
||||
TileEntity tile = world.getTileEntity( pos );
|
||||
return tile != null && tile.hasCapability( CapabilityWiredElement.CAPABILITY, side )
|
||||
? tile.getCapability( CapabilityWiredElement.CAPABILITY, side )
|
||||
: null;
|
||||
}
|
||||
|
||||
public static int getDefaultBundledRedstoneOutput( World world, BlockPos pos, EnumFacing side )
|
||||
{
|
||||
if( WorldUtil.isBlockInWorld( world, pos ) )
|
||||
@@ -802,7 +874,7 @@ public class ComputerCraft
|
||||
return upgrades;
|
||||
}
|
||||
|
||||
public IPacketNetwork getWirelessNetwork()
|
||||
public static IPacketNetwork getWirelessNetwork()
|
||||
{
|
||||
return WirelessNetwork.getUniversal();
|
||||
}
|
||||
@@ -853,11 +925,12 @@ public class ComputerCraft
|
||||
{
|
||||
try
|
||||
{
|
||||
IMount jarMount = new JarMount( modJar, subPath );
|
||||
mounts.add( jarMount );
|
||||
FileSystem fs = FileSystems.newFileSystem( modJar.toPath(), ComputerCraft.class.getClassLoader() );
|
||||
mounts.add( new FileSystemMount( fs, subPath ) );
|
||||
}
|
||||
catch( IOException e )
|
||||
catch( IOException | ProviderNotFoundException | ServiceConfigurationError e )
|
||||
{
|
||||
ComputerCraft.log.error( "Could not load mount from mod jar", e );
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
@@ -875,7 +948,7 @@ public class ComputerCraft
|
||||
if( !resourcePack.isDirectory() )
|
||||
{
|
||||
// Mount a resource pack from a jar
|
||||
IMount resourcePackMount = new JarMount( resourcePack, subPath );
|
||||
IMount resourcePackMount = new FileSystemMount( FileSystems.getFileSystem( resourcePack.toURI() ), subPath );
|
||||
mounts.add( resourcePackMount );
|
||||
}
|
||||
else
|
||||
@@ -891,7 +964,7 @@ public class ComputerCraft
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
// Ignore
|
||||
ComputerCraft.log.error( "Could not load resource pack '" + resourcePack1 + "'", e );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1040,7 +1113,7 @@ public class ComputerCraft
|
||||
{
|
||||
return turtleProxy.getTurtleUpgrade( id );
|
||||
}
|
||||
|
||||
|
||||
public static ITurtleUpgrade getTurtleUpgrade( int legacyID )
|
||||
{
|
||||
return turtleProxy.getTurtleUpgrade( legacyID );
|
||||
@@ -1056,13 +1129,18 @@ public class ComputerCraft
|
||||
turtleProxy.addAllUpgradedTurtles( list );
|
||||
}
|
||||
|
||||
public static void setEntityDropConsumer( Entity entity, IEntityDropConsumer consumer )
|
||||
public static void setDropConsumer( Entity entity, Function<ItemStack, ItemStack> consumer )
|
||||
{
|
||||
turtleProxy.setEntityDropConsumer( entity, consumer );
|
||||
turtleProxy.setDropConsumer( entity, consumer );
|
||||
}
|
||||
|
||||
public static void clearEntityDropConsumer( Entity entity )
|
||||
public static void setDropConsumer( World world, BlockPos pos, Function<ItemStack, ItemStack> consumer )
|
||||
{
|
||||
turtleProxy.clearEntityDropConsumer( entity );
|
||||
turtleProxy.setDropConsumer( world, pos, consumer );
|
||||
}
|
||||
|
||||
public static List<ItemStack> clearDropConsumer( )
|
||||
{
|
||||
return turtleProxy.clearDropConsumer();
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,8 @@ import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
import dan200.computercraft.api.media.IMediaProvider;
|
||||
import dan200.computercraft.api.network.IPacketNetwork;
|
||||
import dan200.computercraft.api.network.wired.IWiredElement;
|
||||
import dan200.computercraft.api.network.wired.IWiredNode;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheralProvider;
|
||||
@@ -21,6 +23,7 @@ import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@@ -328,6 +331,61 @@ public final class ComputerCraftAPI
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new wired node for a given wired element
|
||||
*
|
||||
* @param element The element to construct it for
|
||||
* @return The element's node
|
||||
* @see IWiredElement#getNode()
|
||||
*/
|
||||
@Nonnull
|
||||
public static IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element )
|
||||
{
|
||||
findCC();
|
||||
if( computerCraft_createWiredNodeForElement != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
return (IWiredNode) computerCraft_createWiredNodeForElement.invoke( null, element );
|
||||
}
|
||||
catch( ReflectiveOperationException e )
|
||||
{
|
||||
throw new IllegalStateException( "Error creating wired node", e );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException( "ComputerCraft cannot be found" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wired network element for a block in world
|
||||
*
|
||||
* @param world The world the block exists in
|
||||
* @param pos The position the block exists in
|
||||
* @param side The side to extract the network element from
|
||||
* @return The element's node
|
||||
* @see IWiredElement#getNode()
|
||||
*/
|
||||
@Nullable
|
||||
public static IWiredElement getWiredElementAt( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side )
|
||||
{
|
||||
findCC();
|
||||
if( computerCraft_getWiredElementAt != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
return (IWiredElement) computerCraft_getWiredElementAt.invoke( null, world, pos, side );
|
||||
}
|
||||
catch( ReflectiveOperationException ignored )
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// The functions below here are private, and are used to interface with the non-API ComputerCraft classes.
|
||||
// Reflection is used here so you can develop your mod without decompiling ComputerCraft and including
|
||||
// it in your solution, and so your mod won't crash if ComputerCraft is installed.
|
||||
@@ -374,6 +432,12 @@ public final class ComputerCraftAPI
|
||||
computerCraft_registerAPIFactory = findCCMethod( "registerAPIFactory", new Class<?>[] {
|
||||
ILuaAPIFactory.class
|
||||
} );
|
||||
computerCraft_createWiredNodeForElement = findCCMethod( "createWiredNodeForElement", new Class<?>[] {
|
||||
IWiredElement.class
|
||||
} );
|
||||
computerCraft_getWiredElementAt = findCCMethod( "getWiredElementAt", new Class<?>[]{
|
||||
IBlockAccess.class, BlockPos.class, EnumFacing.class
|
||||
} );
|
||||
} catch( Exception e ) {
|
||||
System.out.println( "ComputerCraftAPI: ComputerCraft not found." );
|
||||
} finally {
|
||||
@@ -411,4 +475,6 @@ public final class ComputerCraftAPI
|
||||
private static Method computerCraft_registerPocketUpgrade = null;
|
||||
private static Method computerCraft_getWirelessNetwork = null;
|
||||
private static Method computerCraft_registerAPIFactory = null;
|
||||
private static Method computerCraft_createWiredNodeForElement = null;
|
||||
private static Method computerCraft_getWiredElementAt = null;
|
||||
}
|
||||
|
@@ -13,6 +13,8 @@ import net.minecraft.world.World;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -72,7 +74,25 @@ public interface IMount
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @return A stream representing the contents of the file.
|
||||
* @throws IOException If the file does not exist, or could not be opened.
|
||||
* @deprecated Use {@link #openChannelForRead(String)} instead
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
InputStream openForRead( @Nonnull String path ) throws IOException;
|
||||
|
||||
/**
|
||||
* Opens a file with a given path, and returns an {@link ReadableByteChannel} representing its contents.
|
||||
*
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @return A channel representing the contents of the file. If the channel implements
|
||||
* {@link java.nio.channels.SeekableByteChannel}, one will be able to seek to arbitrary positions when using binary
|
||||
* mode.
|
||||
* @throws IOException If the file does not exist, or could not be opened.
|
||||
*/
|
||||
@Nonnull
|
||||
@SuppressWarnings("deprecation")
|
||||
default ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return Channels.newChannel( openForRead( path ) );
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,8 @@ import net.minecraft.world.World;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
/**
|
||||
* Represents a part of a virtual filesystem that can be mounted onto a computer using {@link IComputerAccess#mount(String, IMount)}
|
||||
@@ -50,20 +52,54 @@ public interface IWritableMount extends IMount
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @return A stream for writing to
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
* @deprecated Use {@link #openChannelForWrite(String)} instead.
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
OutputStream openForWrite( @Nonnull String path ) throws IOException;
|
||||
|
||||
/**
|
||||
* Opens a file with a given path, and returns an {@link OutputStream} for writing to it.
|
||||
*
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @return A stream for writing to. If the channel implements {@link java.nio.channels.SeekableByteChannel}, one
|
||||
* will be able to seek to arbitrary positions when using binary mode.
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
*/
|
||||
@Nonnull
|
||||
@SuppressWarnings("deprecation")
|
||||
default WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return Channels.newChannel( openForWrite( path ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
|
||||
*
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @return A stream for writing to.
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
* @deprecated Use {@link #openChannelForAppend(String)} instead.
|
||||
*/
|
||||
@Nonnull
|
||||
@Deprecated
|
||||
OutputStream openForAppend( @Nonnull String path ) throws IOException;
|
||||
|
||||
/**
|
||||
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
|
||||
*
|
||||
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
|
||||
* @return A stream for writing to. If the channel implements {@link java.nio.channels.SeekableByteChannel}, one
|
||||
* will be able to seek to arbitrary positions when using binary mode.
|
||||
* @throws IOException If the file could not be opened for writing.
|
||||
*/
|
||||
@Nonnull
|
||||
@SuppressWarnings("deprecation")
|
||||
default WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return Channels.newChannel( openForAppend( path ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of free space on the mount, in bytes. You should decrease this value as the user writes to the
|
||||
* mount, and write operations should fail once it reaches zero.
|
||||
|
@@ -41,7 +41,8 @@ public interface IPacketNetwork
|
||||
* to all receivers within range (or any interdimensional ones).
|
||||
*
|
||||
* @param packet The packet to send.
|
||||
* @see #transmitInterdimensional(Packet)
|
||||
* @param range The maximum distance this packet will be sent.
|
||||
* @see #transmitInterdimensional(Packet)
|
||||
* @see IPacketReceiver#receiveSameDimension(Packet, double)
|
||||
*/
|
||||
void transmitSameDimension( @Nonnull Packet packet, double range );
|
||||
@@ -51,7 +52,7 @@ public interface IPacketNetwork
|
||||
* to all receivers across all dimensions.
|
||||
*
|
||||
* @param packet The packet to send.
|
||||
* @see #transmitSameDimension(Packet, double)
|
||||
* @see #transmitSameDimension(Packet, double)
|
||||
* @see IPacketReceiver#receiveDifferentDimension(Packet)
|
||||
*/
|
||||
void transmitInterdimensional( @Nonnull Packet packet );
|
||||
|
@@ -0,0 +1,29 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* An object which may be part of a wired network.
|
||||
*
|
||||
* Elements should construct a node using {@link ComputerCraftAPI#createWiredNodeForElement(IWiredElement)}. This acts
|
||||
* as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
|
||||
* for its lifespan.
|
||||
*
|
||||
* Elements are generally tied to a block or tile entity in world. In such as case, one should provide the
|
||||
* {@link IWiredElement} capability for the appropriate sides.
|
||||
*/
|
||||
public interface IWiredElement extends IWiredSender
|
||||
{
|
||||
/**
|
||||
* Called when objects on the network change. This may occur when network nodes are added or removed, or when
|
||||
* peripherals change.
|
||||
*
|
||||
* @param change The change which occurred.
|
||||
* @see IWiredNetworkChange
|
||||
*/
|
||||
default void networkChanged( @Nonnull IWiredNetworkChange change )
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A wired network is composed of one of more {@link IWiredNode}s, a set of connections between them, and a series
|
||||
* of peripherals.
|
||||
*
|
||||
* Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if
|
||||
* there is some path between two nodes then they must be on the same network. {@link IWiredNetwork} will automatically
|
||||
* handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections
|
||||
* change.
|
||||
*
|
||||
* This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently,
|
||||
* it is generally preferred to use the methods provided by {@link IWiredNode}.
|
||||
*
|
||||
* @see IWiredNode#getNetwork()
|
||||
*/
|
||||
public interface IWiredNetwork
|
||||
{
|
||||
/**
|
||||
* Create a connection between two nodes.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param left The first node to connect
|
||||
* @param right The second node to connect
|
||||
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
|
||||
* @throws IllegalStateException If neither node is on the network.
|
||||
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
|
||||
* @see IWiredNode#connectTo(IWiredNode)
|
||||
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
|
||||
*/
|
||||
boolean connect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
|
||||
|
||||
/**
|
||||
* Destroy a connection between this node and another.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param left The first node in the connection.
|
||||
* @param right The second node in the connection.
|
||||
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
|
||||
* @throws IllegalArgumentException If either node is not on the network.
|
||||
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
|
||||
* @see IWiredNode#disconnectFrom(IWiredNode)
|
||||
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
|
||||
*/
|
||||
boolean disconnect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
|
||||
|
||||
/**
|
||||
* Sever all connections this node has, removing it from this network.
|
||||
*
|
||||
* This should only be used on the server thread. You should only call this on nodes
|
||||
* that your network element owns.
|
||||
*
|
||||
* @param node The node to remove
|
||||
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
|
||||
* only element.
|
||||
* @throws IllegalArgumentException If the node is not in the network.
|
||||
* @see IWiredNode#remove()
|
||||
*/
|
||||
boolean remove( @Nonnull IWiredNode node );
|
||||
|
||||
/**
|
||||
* Update the peripherals a node provides.
|
||||
*
|
||||
* This should only be used on the server thread. You should only call this on nodes
|
||||
* that your network element owns.
|
||||
*
|
||||
* @param node The node to attach peripherals for.
|
||||
* @param peripherals The new peripherals for this node.
|
||||
* @throws IllegalArgumentException If the node is not in the network.
|
||||
* @see IWiredNode#updatePeripherals(Map)
|
||||
*/
|
||||
void updatePeripherals( @Nonnull IWiredNode node, @Nonnull Map<String, IPeripheral> peripherals );
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a change to the objects on a wired network.
|
||||
*
|
||||
* @see IWiredElement#networkChanged(IWiredNetworkChange)
|
||||
*/
|
||||
public interface IWiredNetworkChange
|
||||
{
|
||||
/**
|
||||
* A set of peripherals which have been removed. Note that there may be entries with the same name
|
||||
* in the added and removed set, but with a different peripheral.
|
||||
*
|
||||
* @return The set of removed peripherals.
|
||||
*/
|
||||
@Nonnull
|
||||
Map<String, IPeripheral> peripheralsRemoved();
|
||||
|
||||
/**
|
||||
* A set of peripherals which have been added. Note that there may be entries with the same name
|
||||
* in the added and removed set, but with a different peripheral.
|
||||
*
|
||||
* @return The set of added peripherals.
|
||||
*/
|
||||
@Nonnull
|
||||
Map<String, IPeripheral> peripheralsAdded();
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import dan200.computercraft.api.network.IPacketNetwork;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Wired nodes act as a layer between {@link IWiredElement}s and {@link IWiredNetwork}s.
|
||||
*
|
||||
* Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These
|
||||
* methods may be safely used on any thread.
|
||||
*
|
||||
* When sending a packet, the system will attempt to find the shortest path between the two nodes based on their
|
||||
* element's position. Note that packet senders and receivers can have different locations from their associated
|
||||
* element: the distance between the two will be added to the total packet's distance.
|
||||
*
|
||||
* Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever
|
||||
* be used on the main server thread.
|
||||
*/
|
||||
public interface IWiredNode extends IPacketNetwork
|
||||
{
|
||||
/**
|
||||
* The associated element for this network node.
|
||||
*
|
||||
* @return This node's element.
|
||||
*/
|
||||
@Nonnull
|
||||
IWiredElement getElement();
|
||||
|
||||
/**
|
||||
* The network this node is currently connected to. Note that this may change
|
||||
* after any network operation, so it should not be cached.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @return This node's network.
|
||||
*/
|
||||
@Nonnull
|
||||
IWiredNetwork getNetwork();
|
||||
|
||||
/**
|
||||
* Create a connection from this node to another.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param node The other node to connect to.
|
||||
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
|
||||
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
|
||||
* @see IWiredNode#disconnectFrom(IWiredNode)
|
||||
*/
|
||||
default boolean connectTo( @Nonnull IWiredNode node )
|
||||
{
|
||||
return getNetwork().connect( this, node );
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a connection between this node and another.
|
||||
*
|
||||
* This should only be used on the server thread.
|
||||
*
|
||||
* @param node The other node to disconnect from.
|
||||
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
|
||||
* @throws IllegalArgumentException If {@code node} is not on the same network.
|
||||
* @see IWiredNetwork#disconnect(IWiredNode, IWiredNode)
|
||||
* @see IWiredNode#connectTo(IWiredNode)
|
||||
*/
|
||||
default boolean disconnectFrom( @Nonnull IWiredNode node )
|
||||
{
|
||||
return getNetwork().disconnect( this, node );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sever all connections this node has, removing it from this network.
|
||||
*
|
||||
* This should only be used on the server thread. You should only call this on nodes
|
||||
* that your network element owns.
|
||||
*
|
||||
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
|
||||
* only element.
|
||||
* @throws IllegalArgumentException If the node is not in the network.
|
||||
* @see IWiredNetwork#remove(IWiredNode)
|
||||
*/
|
||||
default boolean remove()
|
||||
{
|
||||
return getNetwork().remove( this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this node's peripherals as having changed.
|
||||
*
|
||||
* This should only be used on the server thread. You should only call this on nodes
|
||||
* that your network element owns.
|
||||
*
|
||||
* @param peripherals The new peripherals for this node.
|
||||
* @see IWiredNetwork#updatePeripherals(IWiredNode, Map)
|
||||
*/
|
||||
default void updatePeripherals( @Nonnull Map<String, IPeripheral> peripherals )
|
||||
{
|
||||
getNetwork().updatePeripherals( this, peripherals );
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import dan200.computercraft.api.network.IPacketSender;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* An object on a {@link IWiredNetwork} capable of sending packets.
|
||||
*
|
||||
* Unlike a regular {@link IPacketSender}, this must be associated with the node you are attempting to
|
||||
* to send the packet from.
|
||||
*/
|
||||
public interface IWiredSender extends IPacketSender
|
||||
{
|
||||
/**
|
||||
* The node in the network representing this object.
|
||||
*
|
||||
* This should be used as a proxy for the main network. One should send packets
|
||||
* and register receivers through this object.
|
||||
*
|
||||
* @return The node for this element.
|
||||
*/
|
||||
@Nonnull
|
||||
IWiredNode getNode();
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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.
|
||||
*/
|
||||
|
||||
@API( owner="ComputerCraft", provides="ComputerCraft|API|Network|Wired", apiVersion="${version}" )
|
||||
package dan200.computercraft.api.network.wired;
|
||||
|
||||
import net.minecraftforge.fml.common.API;
|
@@ -13,6 +13,8 @@ import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The interface passed to peripherals by computers or turtles, providing methods
|
||||
@@ -154,4 +156,33 @@ public interface IComputerAccess
|
||||
*/
|
||||
@Nonnull
|
||||
String getAttachmentName();
|
||||
|
||||
/**
|
||||
* Get a set of peripherals that this computer access can "see", along with their attachment name.
|
||||
*
|
||||
* This may include other peripherals on the wired network or peripherals on other sides of the computer.
|
||||
*
|
||||
* @return All reachable peripherals
|
||||
* @see #getAttachmentName()
|
||||
* @see #getAvailablePeripheral(String)
|
||||
*/
|
||||
@Nonnull
|
||||
default Map<String, IPeripheral> getAvailablePeripherals()
|
||||
{
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a reachable peripheral with the given attachement name. This is a equivalent to
|
||||
* {@link #getAvailablePeripherals()}{@code .get(name)}, though may be more performant.
|
||||
*
|
||||
* @param name The peripheral's attached name
|
||||
* @return The reachable peripheral, or {@code null} if none can be found.
|
||||
* @see #getAvailablePeripherals()
|
||||
*/
|
||||
@Nullable
|
||||
default IPeripheral getAvailablePeripheral( @Nonnull String name )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -114,6 +114,18 @@ public interface IPeripheral
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object that this peripheral provides methods for. This will generally be the tile entity
|
||||
* or block, but may be an inventory, entity, etc...
|
||||
*
|
||||
* @return The object this peripheral targets
|
||||
*/
|
||||
@Nonnull
|
||||
default Object getTarget()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this peripheral is equivalent to another one.
|
||||
*
|
||||
|
@@ -6,6 +6,7 @@
|
||||
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
@@ -135,6 +136,14 @@ public interface ITurtleAccess
|
||||
*/
|
||||
int getColour();
|
||||
|
||||
/**
|
||||
* Get the player who owns this turtle, namely whoever placed it.
|
||||
*
|
||||
* @return This turtle's owner.
|
||||
*/
|
||||
@Nonnull
|
||||
GameProfile getOwningPlayer();
|
||||
|
||||
/**
|
||||
* Get the inventory of this turtle
|
||||
*
|
||||
@@ -148,7 +157,7 @@ public interface ITurtleAccess
|
||||
* Get the inventory of this turtle as an {@link IItemHandlerModifiable}.
|
||||
*
|
||||
* @return This turtle's inventory
|
||||
* @see #getInventory()
|
||||
* @see #getInventory()
|
||||
* @see IItemHandlerModifiable
|
||||
* @see net.minecraftforge.items.CapabilityItemHandler#ITEM_HANDLER_CAPABILITY
|
||||
*/
|
||||
|
@@ -8,11 +8,15 @@ package dan200.computercraft.api.turtle;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.turtle.event.TurtleAttackEvent;
|
||||
import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
|
||||
import net.minecraft.client.renderer.block.model.IBakedModel;
|
||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.event.entity.player.AttackEntityEvent;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.fml.relauncher.Side;
|
||||
import net.minecraftforge.fml.relauncher.SideOnly;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@@ -100,6 +104,9 @@ public interface ITurtleUpgrade
|
||||
* Will only be called for Tool turtle. Called when turtle.dig() or turtle.attack() is called
|
||||
* by the turtle, and the tool is required to do some work.
|
||||
*
|
||||
* Conforming implementations should fire {@link BlockEvent.BreakEvent} and {@link TurtleBlockEvent.Dig}for digging,
|
||||
* {@link AttackEntityEvent} and {@link TurtleAttackEvent} for attacking.
|
||||
*
|
||||
* @param turtle Access to the turtle that the tool resides on.
|
||||
* @param side Which side of the turtle (left or right) the tool resides on.
|
||||
* @param verb Which action (dig or attack) the turtle is being called on to perform.
|
||||
|
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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.turtle.event;
|
||||
|
||||
/**
|
||||
* A basic action that a turtle may perform, as accessed by the {@code turtle} API.
|
||||
*
|
||||
* @see TurtleActionEvent
|
||||
*/
|
||||
public enum TurtleAction
|
||||
{
|
||||
/**
|
||||
* A turtle moves to a new position.
|
||||
*
|
||||
* @see TurtleBlockEvent.Move
|
||||
*/
|
||||
MOVE,
|
||||
|
||||
/**
|
||||
* A turtle turns in a specific direction.
|
||||
*/
|
||||
TURN,
|
||||
|
||||
/**
|
||||
* A turtle attempts to dig a block.
|
||||
*
|
||||
* @see TurtleBlockEvent.Dig
|
||||
*/
|
||||
DIG,
|
||||
|
||||
/**
|
||||
* A turtle attempts to place a block or item in the world.
|
||||
*
|
||||
* @see TurtleBlockEvent.Place
|
||||
*/
|
||||
PLACE,
|
||||
|
||||
/**
|
||||
* A turtle attempts to attack an entity.
|
||||
*
|
||||
* @see TurtleActionEvent
|
||||
*/
|
||||
ATTACK,
|
||||
|
||||
/**
|
||||
* Drop an item into an inventory/the world.
|
||||
*
|
||||
* @see TurtleInventoryEvent.Drop
|
||||
*/
|
||||
DROP,
|
||||
|
||||
/**
|
||||
* Suck an item from an inventory or the world.
|
||||
*
|
||||
* @see TurtleInventoryEvent.Suck
|
||||
*/
|
||||
SUCK,
|
||||
|
||||
/**
|
||||
* Refuel the turtle's fuel levels.
|
||||
*/
|
||||
REFUEL,
|
||||
|
||||
/**
|
||||
* Equip or unequip an item.
|
||||
*/
|
||||
EQUIP,
|
||||
|
||||
/**
|
||||
* Inspect a block in world
|
||||
*
|
||||
* @see TurtleBlockEvent.Inspect
|
||||
*/
|
||||
INSPECT,
|
||||
|
||||
/**
|
||||
* Gather metdata about an item in the turtle's inventory.
|
||||
*/
|
||||
INSPECT_ITEM,
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
package dan200.computercraft.api.turtle.event;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.TurtleCommandResult;
|
||||
import net.minecraftforge.fml.common.eventhandler.Cancelable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An event fired when a turtle is performing a known action.
|
||||
*/
|
||||
@Cancelable
|
||||
public class TurtleActionEvent extends TurtleEvent
|
||||
{
|
||||
private final TurtleAction action;
|
||||
private String failureMessage;
|
||||
|
||||
public TurtleActionEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action )
|
||||
{
|
||||
super( turtle );
|
||||
|
||||
Preconditions.checkNotNull( action, "action cannot be null" );
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public TurtleAction getAction()
|
||||
{
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cancellation state of this action.
|
||||
*
|
||||
* If {@code cancel} is {@code true}, this action will not be carried out.
|
||||
*
|
||||
* @param cancel The new canceled value.
|
||||
* @see TurtleCommandResult#failure()
|
||||
* @deprecated Use {@link #setCanceled(boolean, String)} instead.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setCanceled( boolean cancel )
|
||||
{
|
||||
setCanceled( cancel, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cancellation state of this action, setting a failure message if required.
|
||||
*
|
||||
* If {@code cancel} is {@code true}, this action will not be carried out.
|
||||
*
|
||||
* @param cancel The new canceled value.
|
||||
* @param failureMessage The message to return to the user explaining the failure.
|
||||
* @see TurtleCommandResult#failure(String)
|
||||
*/
|
||||
public void setCanceled( boolean cancel, @Nullable String failureMessage )
|
||||
{
|
||||
super.setCanceled( cancel );
|
||||
this.failureMessage = cancel ? failureMessage : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message with which this will fail.
|
||||
*
|
||||
* @return The failure message.
|
||||
* @see TurtleCommandResult#failure()
|
||||
* @see #setCanceled(boolean, String)
|
||||
*/
|
||||
@Nullable
|
||||
public String getFailureMessage()
|
||||
{
|
||||
return failureMessage;
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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.turtle.event;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.turtle.TurtleVerb;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import net.minecraftforge.event.entity.player.AttackEntityEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to attack an entity.
|
||||
*
|
||||
* This must be fired by {@link ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, EnumFacing)},
|
||||
* as the base {@code turtle.attack()} command does not fire it.
|
||||
*
|
||||
* Note that such commands should also fire {@link AttackEntityEvent}, so you do not need to listen to both.
|
||||
*
|
||||
* @see TurtleAction#ATTACK
|
||||
*/
|
||||
public class TurtleAttackEvent extends TurtlePlayerEvent
|
||||
{
|
||||
private final Entity target;
|
||||
private final ITurtleUpgrade upgrade;
|
||||
private final TurtleSide side;
|
||||
|
||||
public TurtleAttackEvent( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull Entity target, @Nonnull ITurtleUpgrade upgrade, @Nonnull TurtleSide side )
|
||||
{
|
||||
super( turtle, TurtleAction.ATTACK, player );
|
||||
Preconditions.checkNotNull( target, "target cannot be null" );
|
||||
Preconditions.checkNotNull( upgrade, "upgrade cannot be null" );
|
||||
Preconditions.checkNotNull( side, "side cannot be null" );
|
||||
this.target = target;
|
||||
this.upgrade = upgrade;
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity being attacked by this turtle.
|
||||
*
|
||||
* @return The entity being attacked.
|
||||
*/
|
||||
@Nonnull
|
||||
public Entity getTarget()
|
||||
{
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the upgrade responsible for attacking.
|
||||
*
|
||||
* @return The upgrade responsible for attacking.
|
||||
*/
|
||||
@Nonnull
|
||||
public ITurtleUpgrade getUpgrade()
|
||||
{
|
||||
return upgrade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the side the attacking upgrade is on.
|
||||
*
|
||||
* @return The upgrade's side.
|
||||
*/
|
||||
@Nonnull
|
||||
public TurtleSide getSide()
|
||||
{
|
||||
return side;
|
||||
}
|
||||
}
|
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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.turtle.event;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.turtle.TurtleVerb;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.fml.common.eventhandler.Cancelable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A general event for when a turtle interacts with a block or region.
|
||||
*
|
||||
* You should generally listen to one of the sub-events instead, cancelling them where
|
||||
* appropriate.
|
||||
*
|
||||
* Note that you are not guaranteed to receive this event, if it has been cancelled by other
|
||||
* mechanisms, such as block protection systems.
|
||||
*
|
||||
* Be aware that some events (such as {@link TurtleInventoryEvent}) do not necessarily interact
|
||||
* with a block, simply objects within that block space.
|
||||
*/
|
||||
@Cancelable
|
||||
public abstract class TurtleBlockEvent extends TurtlePlayerEvent
|
||||
{
|
||||
private final World world;
|
||||
private final BlockPos pos;
|
||||
|
||||
protected TurtleBlockEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos )
|
||||
{
|
||||
super( turtle, action, player );
|
||||
|
||||
Preconditions.checkNotNull( world, "world cannot be null" );
|
||||
Preconditions.checkNotNull( pos, "pos cannot be null" );
|
||||
this.world = world;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the world the turtle is interacting in.
|
||||
*
|
||||
* @return The world the turtle is interacting in.
|
||||
*/
|
||||
public World getWorld()
|
||||
{
|
||||
return world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position the turtle is interacting with. Note that this is different
|
||||
* to {@link ITurtleAccess#getPosition()}.
|
||||
*
|
||||
* @return The position the turtle is interacting with.
|
||||
*/
|
||||
public BlockPos getPos()
|
||||
{
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to dig a block.
|
||||
*
|
||||
* This must be fired by {@link ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, EnumFacing)},
|
||||
* as the base {@code turtle.dig()} command does not fire it.
|
||||
*
|
||||
* Note that such commands should also fire {@link BlockEvent.BreakEvent}, so you do not need to listen to both.
|
||||
*
|
||||
* @see TurtleAction#DIG
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Dig extends TurtleBlockEvent
|
||||
{
|
||||
private final IBlockState block;
|
||||
private final ITurtleUpgrade upgrade;
|
||||
private final TurtleSide side;
|
||||
|
||||
public Dig( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState block, @Nonnull ITurtleUpgrade upgrade, @Nonnull TurtleSide side )
|
||||
{
|
||||
super( turtle, TurtleAction.DIG, player, world, pos );
|
||||
|
||||
Preconditions.checkNotNull( block, "block cannot be null" );
|
||||
Preconditions.checkNotNull( upgrade, "upgrade cannot be null" );
|
||||
Preconditions.checkNotNull( side, "side cannot be null" );
|
||||
this.block = block;
|
||||
this.upgrade = upgrade;
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block which is about to be broken.
|
||||
*
|
||||
* @return The block which is going to be broken.
|
||||
*/
|
||||
@Nonnull
|
||||
public IBlockState getBlock()
|
||||
{
|
||||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the upgrade doing the digging
|
||||
*
|
||||
* @return The upgrade doing the digging.
|
||||
*/
|
||||
@Nonnull
|
||||
public ITurtleUpgrade getUpgrade()
|
||||
{
|
||||
return upgrade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the side the upgrade doing the digging is on.
|
||||
*
|
||||
* @return The upgrade's side.
|
||||
*/
|
||||
@Nonnull
|
||||
public TurtleSide getSide()
|
||||
{
|
||||
return side;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to move into a block.
|
||||
*
|
||||
* @see TurtleAction#MOVE
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Move extends TurtleBlockEvent
|
||||
{
|
||||
public Move( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos )
|
||||
{
|
||||
super( turtle, TurtleAction.MOVE, player, world, pos );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to place a block in the world.
|
||||
*
|
||||
* @see TurtleAction#PLACE
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Place extends TurtleBlockEvent
|
||||
{
|
||||
private final ItemStack stack;
|
||||
|
||||
public Place( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull ItemStack stack )
|
||||
{
|
||||
super( turtle, TurtleAction.PLACE, player, world, pos );
|
||||
|
||||
Preconditions.checkNotNull( stack, "stack cannot be null" );
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item stack that will be placed. This should not be modified.
|
||||
*
|
||||
* @return The item stack to be placed.
|
||||
*/
|
||||
@Nonnull
|
||||
public ItemStack getStack()
|
||||
{
|
||||
return stack;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle gathers data on a block in world.
|
||||
*
|
||||
* You may prevent blocks being inspected, or add additional information to the result.
|
||||
*
|
||||
* @see TurtleAction#INSPECT
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Inspect extends TurtleBlockEvent
|
||||
{
|
||||
private final IBlockState state;
|
||||
private final Map<String, Object> data;
|
||||
|
||||
public Inspect( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState state, @Nonnull Map<String, Object> data )
|
||||
{
|
||||
super( turtle, TurtleAction.INSPECT, player, world, pos );
|
||||
|
||||
Preconditions.checkNotNull( state, "state cannot be null" );
|
||||
Preconditions.checkNotNull( data, "data cannot be null" );
|
||||
this.data = data;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block state which is being inspected.
|
||||
*
|
||||
* @return The inspected block state.
|
||||
*/
|
||||
@Nonnull
|
||||
public IBlockState getState()
|
||||
{
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "inspection data" from this block, which will be returned to the user.
|
||||
*
|
||||
* @return This block's inspection data.
|
||||
*/
|
||||
@Nonnull
|
||||
public Map<String, Object> getData()
|
||||
{
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new information to the inspection result. Note this will override fields with the same name.
|
||||
*
|
||||
* @param newData The data to add. Note all values should be convertable to Lua (see
|
||||
* {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
|
||||
*/
|
||||
public void addData( @Nonnull Map<String, ?> newData )
|
||||
{
|
||||
Preconditions.checkNotNull( newData, "newData cannot be null" );
|
||||
data.putAll( newData );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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.turtle.event;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import net.minecraftforge.fml.common.eventhandler.Event;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* A base class for all events concerning a turtle. This will only ever constructed and fired on the server side,
|
||||
* so sever specific methods on {@link ITurtleAccess} are safe to use.
|
||||
*
|
||||
* You should generally not need to subscribe to this event, preferring one of the more specific classes.
|
||||
*
|
||||
* @see TurtleActionEvent
|
||||
*/
|
||||
public abstract class TurtleEvent extends Event
|
||||
{
|
||||
private final ITurtleAccess turtle;
|
||||
|
||||
protected TurtleEvent( @Nonnull ITurtleAccess turtle )
|
||||
{
|
||||
Preconditions.checkNotNull( turtle, "turtle cannot be null" );
|
||||
this.turtle = turtle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the turtle which is performing this action.
|
||||
*
|
||||
* @return The access for this turtle.
|
||||
*/
|
||||
@Nonnull
|
||||
public ITurtleAccess getTurtle()
|
||||
{
|
||||
return turtle;
|
||||
}
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
package dan200.computercraft.api.turtle.event;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import net.minecraftforge.fml.common.eventhandler.Cancelable;
|
||||
import net.minecraftforge.items.IItemHandler;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to interact with an inventory.
|
||||
*/
|
||||
@Cancelable
|
||||
public abstract class TurtleInventoryEvent extends TurtleBlockEvent
|
||||
{
|
||||
private final IItemHandler handler;
|
||||
|
||||
protected TurtleInventoryEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler )
|
||||
{
|
||||
super( turtle, action, player, world, pos );
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the inventory being interacted with
|
||||
*
|
||||
* @return The inventory being interacted with, {@code null} if the item will be dropped to/sucked from the world.
|
||||
*/
|
||||
@Nullable
|
||||
public IItemHandler getItemHandler()
|
||||
{
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to suck from an inventory.
|
||||
*
|
||||
* @see TurtleAction#SUCK
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Suck extends TurtleInventoryEvent
|
||||
{
|
||||
public Suck( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler )
|
||||
{
|
||||
super( turtle, TurtleAction.SUCK, player, world, pos, handler );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a turtle attempts to drop an item into an inventory.
|
||||
*
|
||||
* @see TurtleAction#DROP
|
||||
*/
|
||||
@Cancelable
|
||||
public static class Drop extends TurtleInventoryEvent
|
||||
{
|
||||
private final ItemStack stack;
|
||||
|
||||
public Drop( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler, @Nonnull ItemStack stack )
|
||||
{
|
||||
super( turtle, TurtleAction.DROP, player, world, pos, handler );
|
||||
|
||||
Preconditions.checkNotNull( stack, "stack cannot be null" );
|
||||
this.stack = stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* The item which will be inserted into the inventory/dropped on the ground.
|
||||
*
|
||||
* Note that this is a copy of the original stack, and so should not be modified, as that will have no effect.
|
||||
*
|
||||
* @return The item stack which will be dropped.
|
||||
*/
|
||||
public ItemStack getStack()
|
||||
{
|
||||
return stack.copy();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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.turtle.event;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* An action done by a turtle which is normally done by a player.
|
||||
*
|
||||
* {@link #getPlayer()} may be used to modify the player's attributes or perform permission checks.
|
||||
*/
|
||||
public abstract class TurtlePlayerEvent extends TurtleActionEvent
|
||||
{
|
||||
private final FakePlayer player;
|
||||
|
||||
protected TurtlePlayerEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player )
|
||||
{
|
||||
super( turtle, action );
|
||||
|
||||
Preconditions.checkNotNull( player, "player cannot be null" );
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
/**
|
||||
* A fake player, representing this turtle.
|
||||
*
|
||||
* This may be used for triggering permission checks.
|
||||
*
|
||||
* @return A {@link FakePlayer} representing this turtle.
|
||||
*/
|
||||
@Nonnull
|
||||
public FakePlayer getPlayer()
|
||||
{
|
||||
return player;
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* This file is part of the public ComputerCraft API - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. 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.
|
||||
*/
|
||||
|
||||
@API(owner = "ComputerCraft", provides = "ComputerCraft|API|Turtle|Event", apiVersion = "${version}")
|
||||
package dan200.computercraft.api.turtle.event;
|
||||
|
||||
import net.minecraftforge.fml.common.API;
|
@@ -33,7 +33,7 @@ public class GuiComputer extends GuiContainer
|
||||
private final int m_termHeight;
|
||||
private WidgetTerminal m_terminal;
|
||||
|
||||
protected GuiComputer( Container container, ComputerFamily family, IComputer computer, int termWidth, int termHeight )
|
||||
public GuiComputer( Container container, ComputerFamily family, IComputer computer, int termWidth, int termHeight )
|
||||
{
|
||||
super( container );
|
||||
m_family = family;
|
||||
|
@@ -6,25 +6,19 @@
|
||||
|
||||
package dan200.computercraft.client.gui;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.media.inventory.ContainerHeldItem;
|
||||
import dan200.computercraft.shared.media.items.ItemPrintout;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.client.gui.inventory.GuiContainer;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.input.Mouse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
||||
|
||||
public class GuiPrintout extends GuiContainer
|
||||
{
|
||||
private static final ResourceLocation background = new ResourceLocation( "computercraft", "textures/gui/printout.png" );
|
||||
|
||||
private static final int xSize = 172;
|
||||
private static final int ySize = 209;
|
||||
|
||||
private final boolean m_book;
|
||||
private final int m_pages;
|
||||
private final TextBuffer[] m_text;
|
||||
@@ -34,23 +28,18 @@ public class GuiPrintout extends GuiContainer
|
||||
public GuiPrintout( ContainerHeldItem container )
|
||||
{
|
||||
super( container );
|
||||
m_book = (ItemPrintout.getType( container.getStack() ) == ItemPrintout.Type.Book);
|
||||
|
||||
String[] text = ItemPrintout.getText( container.getStack() );
|
||||
m_text = new TextBuffer[ text.length ];
|
||||
for( int i=0; i<m_text.length; ++i )
|
||||
{
|
||||
m_text[i] = new TextBuffer( text[i] );
|
||||
}
|
||||
for( int i = 0; i < m_text.length; ++i ) m_text[ i ] = new TextBuffer( text[ i ] );
|
||||
|
||||
String[] colours = ItemPrintout.getColours( container.getStack() );
|
||||
m_colours = new TextBuffer[ colours.length ];
|
||||
for( int i=0; i<m_colours.length; ++i )
|
||||
{
|
||||
m_colours[i] = new TextBuffer( colours[i] );
|
||||
}
|
||||
for( int i = 0; i < m_colours.length; ++i ) m_colours[ i ] = new TextBuffer( colours[ i ] );
|
||||
|
||||
m_pages = Math.max( m_text.length / ItemPrintout.LINES_PER_PAGE, 1 );
|
||||
m_page = 0;
|
||||
m_pages = Math.max( m_text.length / ItemPrintout.LINES_PER_PAGE, 1 );
|
||||
m_book = ItemPrintout.getType( container.getStack() ) == ItemPrintout.Type.Book;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,25 +67,19 @@ public class GuiPrintout extends GuiContainer
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void keyTyped(char c, int k) throws IOException
|
||||
protected void keyTyped( char c, int k ) throws IOException
|
||||
{
|
||||
super.keyTyped( c, k );
|
||||
|
||||
if( k == 205 )
|
||||
{
|
||||
// Right
|
||||
if( m_page < m_pages - 1 )
|
||||
{
|
||||
m_page = m_page + 1;
|
||||
}
|
||||
if( m_page < m_pages - 1 ) m_page++;
|
||||
}
|
||||
else if( k == 203 )
|
||||
{
|
||||
// Left
|
||||
if( m_page > 0 )
|
||||
{
|
||||
m_page = m_page - 1;
|
||||
}
|
||||
// Left
|
||||
if( m_page > 0 ) m_page--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,21 +89,15 @@ public class GuiPrintout extends GuiContainer
|
||||
super.handleMouseInput();
|
||||
|
||||
int mouseWheelChange = Mouse.getEventDWheel();
|
||||
if (mouseWheelChange < 0)
|
||||
if( mouseWheelChange < 0 )
|
||||
{
|
||||
// Up
|
||||
if( m_page < m_pages - 1 )
|
||||
{
|
||||
m_page = m_page + 1;
|
||||
}
|
||||
if( m_page < m_pages - 1 ) m_page++;
|
||||
}
|
||||
else if (mouseWheelChange > 0)
|
||||
else if( mouseWheelChange > 0 )
|
||||
{
|
||||
// Down
|
||||
if( m_page > 0 )
|
||||
{
|
||||
m_page = m_page - 1;
|
||||
}
|
||||
if( m_page > 0 ) m_page--;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,78 +112,20 @@ public class GuiPrintout extends GuiContainer
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float f)
|
||||
public void drawScreen( int mouseX, int mouseY, float f )
|
||||
{
|
||||
// Draw background
|
||||
zLevel = zLevel - 1;
|
||||
drawDefaultBackground();
|
||||
|
||||
zLevel = zLevel + 1;
|
||||
|
||||
// Draw the printout
|
||||
GlStateManager.color( 1.0f, 1.0f, 1.0f, 1.0f );
|
||||
this.mc.getTextureManager().bindTexture( background );
|
||||
|
||||
int startY = (height - ySize) / 2;
|
||||
//int startX = (width - xSize) / 2 - (m_page * 8);
|
||||
int startX = (width - (xSize + (m_pages - 1)*8)) / 2;
|
||||
|
||||
if( m_book )
|
||||
{
|
||||
// Border
|
||||
drawTexturedModalRect( startX - 8, startY - 8, xSize + 48, 0, 12, ySize + 24);
|
||||
drawTexturedModalRect( startX + xSize + (m_pages - 1)*8 - 4, startY - 8, xSize + 48 + 12, 0, 12, ySize + 24);
|
||||
|
||||
drawTexturedModalRect( startX, startY - 8, 0, ySize, xSize, 12);
|
||||
drawTexturedModalRect( startX, startY + ySize - 4, 0, ySize + 12, xSize, 12);
|
||||
for( int n=1; n<m_pages; ++n )
|
||||
{
|
||||
drawTexturedModalRect( startX + xSize + (n-1)*8, startY - 8, 0, ySize, 8, 12);
|
||||
drawTexturedModalRect( startX + xSize + (n-1)*8, startY + ySize - 4, 0, ySize + 12, 8, 12);
|
||||
}
|
||||
}
|
||||
|
||||
// Left half
|
||||
if( m_page == 0 )
|
||||
{
|
||||
drawTexturedModalRect( startX, startY, 24, 0, xSize / 2, ySize);
|
||||
drawTexturedModalRect( startX, startY, 0, 0, 12, ySize);
|
||||
}
|
||||
else
|
||||
{
|
||||
drawTexturedModalRect( startX, startY, 0, 0, 12, ySize);
|
||||
for( int n=1; n<m_page; ++n )
|
||||
{
|
||||
drawTexturedModalRect( startX + n*8, startY, 12, 0, 12, ySize);
|
||||
}
|
||||
drawTexturedModalRect( startX + m_page*8, startY, 24, 0, xSize / 2, ySize);
|
||||
}
|
||||
|
||||
// Right half
|
||||
if( m_page == (m_pages - 1) )
|
||||
{
|
||||
drawTexturedModalRect( startX + m_page*8 + xSize/2, startY, 24 + xSize / 2, 0, xSize / 2, ySize);
|
||||
drawTexturedModalRect( startX + m_page*8 + (xSize - 12), startY, 24 + xSize + 12, 0, 12, ySize);
|
||||
}
|
||||
else
|
||||
{
|
||||
drawTexturedModalRect( startX + (m_pages - 1)*8 + (xSize - 12), startY, 24 + xSize + 12, 0, 12, ySize);
|
||||
for( int n=m_pages-2; n>=m_page; --n )
|
||||
{
|
||||
drawTexturedModalRect( startX + n*8 + (xSize - 12), startY, 24 + xSize, 0, 12, ySize);
|
||||
}
|
||||
drawTexturedModalRect( startX + m_page*8 + xSize/2, startY, 24 + xSize / 2, 0, xSize / 2, ySize);
|
||||
}
|
||||
|
||||
// Draw the text
|
||||
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer)ComputerCraft.getFixedWidthFontRenderer();
|
||||
int x = startX + m_page * 8 + 13;
|
||||
int y = startY + 11;
|
||||
for( int line=0; line<ItemPrintout.LINES_PER_PAGE; ++line )
|
||||
{
|
||||
int lineIdx = ItemPrintout.LINES_PER_PAGE * m_page + line;
|
||||
if( lineIdx >= 0 && lineIdx < m_text.length )
|
||||
{
|
||||
fontRenderer.drawString( m_text[lineIdx], x, y, m_colours[lineIdx], null, 0, 0, false, Palette.DEFAULT );
|
||||
}
|
||||
y = y + FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
}
|
||||
int startY = (height - Y_SIZE) / 2;
|
||||
int startX = (width - X_SIZE) / 2;
|
||||
|
||||
drawBorder( startX, startY, zLevel, m_page, m_pages, m_book );
|
||||
drawText( startX + X_TEXT_MARGIN, startY + Y_TEXT_MARGIN, ItemPrintout.LINES_PER_PAGE * m_page, m_text, m_colours );
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import net.minecraft.client.renderer.block.model.IBakedModel;
|
||||
import net.minecraft.client.renderer.block.model.ModelBakery;
|
||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
|
||||
import net.minecraft.client.renderer.color.IItemColor;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.client.resources.SimpleReloadableResourceManager;
|
||||
@@ -115,8 +116,23 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
|
||||
MinecraftForge.EVENT_BUS.register( handlers );
|
||||
}
|
||||
|
||||
public class ForgeHandlers
|
||||
public static class ForgeHandlers
|
||||
{
|
||||
private static final String[] TURTLE_UPGRADES = {
|
||||
"turtle_modem_off_left",
|
||||
"turtle_modem_on_left",
|
||||
"turtle_modem_off_right",
|
||||
"turtle_modem_on_right",
|
||||
"turtle_crafting_table_left",
|
||||
"turtle_crafting_table_right",
|
||||
"advanced_turtle_modem_off_left",
|
||||
"advanced_turtle_modem_on_left",
|
||||
"advanced_turtle_modem_off_right",
|
||||
"advanced_turtle_modem_on_right",
|
||||
"turtle_speaker_upgrade_left",
|
||||
"turtle_speaker_upgrade_right",
|
||||
};
|
||||
|
||||
private TurtleSmartItemModel m_turtleSmartItemModel;
|
||||
|
||||
public ForgeHandlers()
|
||||
@@ -142,24 +158,27 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
|
||||
@SubscribeEvent
|
||||
public void onTextureStitchEvent( TextureStitchEvent.Pre event )
|
||||
{
|
||||
event.getMap().registerSprite( new ResourceLocation( "computercraft", "blocks/crafty_upgrade" ) );
|
||||
// Load all textures for upgrades
|
||||
TextureMap map = event.getMap();
|
||||
for( String upgrade : TURTLE_UPGRADES )
|
||||
{
|
||||
IModel model = ModelLoaderRegistry.getModelOrMissing( new ResourceLocation( "computercraft", "block/" + upgrade ) );
|
||||
for( ResourceLocation texture : model.getTextures() )
|
||||
{
|
||||
map.registerSprite( texture );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onModelBakeEvent( ModelBakeEvent event )
|
||||
{
|
||||
loadModel( event, "turtle_modem_off_left" );
|
||||
loadModel( event, "turtle_modem_on_left" );
|
||||
loadModel( event, "turtle_modem_off_right" );
|
||||
loadModel( event, "turtle_modem_on_right" );
|
||||
loadModel( event, "turtle_crafting_table_left" );
|
||||
loadModel( event, "turtle_crafting_table_right" );
|
||||
loadModel( event, "advanced_turtle_modem_off_left" );
|
||||
loadModel( event, "advanced_turtle_modem_on_left" );
|
||||
loadModel( event, "advanced_turtle_modem_off_right" );
|
||||
loadModel( event, "advanced_turtle_modem_on_right" );
|
||||
loadModel( event, "turtle_speaker_upgrade_left" );
|
||||
loadModel( event, "turtle_speaker_upgrade_right" );
|
||||
// Load all upgrade models
|
||||
for( String upgrade : TURTLE_UPGRADES )
|
||||
{
|
||||
loadModel( event, upgrade );
|
||||
}
|
||||
|
||||
loadSmartModel( event, "turtle_dynamic", m_turtleSmartItemModel );
|
||||
}
|
||||
|
||||
@@ -191,7 +210,7 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
|
||||
private static class TurtleItemColour implements IItemColor
|
||||
{
|
||||
@Override
|
||||
public int getColorFromItemstack( @Nonnull ItemStack stack, int tintIndex )
|
||||
public int colorMultiplier( @Nonnull ItemStack stack, int tintIndex )
|
||||
{
|
||||
if( tintIndex == 0 )
|
||||
{
|
||||
|
@@ -9,13 +9,16 @@ package dan200.computercraft.client.proxy;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.gui.*;
|
||||
import dan200.computercraft.client.render.ItemPocketRenderer;
|
||||
import dan200.computercraft.client.render.ItemPrintoutRenderer;
|
||||
import dan200.computercraft.client.render.RenderOverlayCable;
|
||||
import dan200.computercraft.client.render.TileEntityCableRenderer;
|
||||
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
|
||||
import dan200.computercraft.shared.command.ContainerViewComputer;
|
||||
import dan200.computercraft.shared.computer.blocks.ComputerState;
|
||||
import dan200.computercraft.shared.computer.blocks.TileComputer;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.IComputer;
|
||||
import dan200.computercraft.shared.computer.items.ItemComputer;
|
||||
import dan200.computercraft.shared.media.inventory.ContainerHeldItem;
|
||||
import dan200.computercraft.shared.media.items.ItemDiskLegacy;
|
||||
@@ -23,6 +26,7 @@ import dan200.computercraft.shared.media.items.ItemPrintout;
|
||||
import dan200.computercraft.shared.network.ComputerCraftPacket;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
|
||||
import dan200.computercraft.shared.peripheral.modem.TileCable;
|
||||
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
||||
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
|
||||
import dan200.computercraft.shared.peripheral.printer.TilePrinter;
|
||||
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
|
||||
@@ -31,9 +35,10 @@ import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
|
||||
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
|
||||
import dan200.computercraft.shared.turtle.entity.TurtleVisionCamera;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import gnu.trove.map.hash.TIntIntHashMap;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.gui.GuiNewChat;
|
||||
import net.minecraft.client.renderer.ItemMeshDefinition;
|
||||
import net.minecraft.client.renderer.block.model.ModelBakery;
|
||||
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
|
||||
@@ -48,6 +53,7 @@ import net.minecraft.util.IThreadListener;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.client.event.ModelRegistryEvent;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
@@ -55,6 +61,7 @@ import net.minecraftforge.client.event.RenderHandEvent;
|
||||
import net.minecraftforge.client.event.RenderPlayerEvent;
|
||||
import net.minecraftforge.client.model.ModelLoader;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.fml.client.registry.ClientRegistry;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
@@ -68,6 +75,8 @@ import java.util.List;
|
||||
|
||||
public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
{
|
||||
private static TIntIntHashMap lastCounts = new TIntIntHashMap();
|
||||
|
||||
private long m_tick;
|
||||
private long m_renderFrame;
|
||||
private FixedWidthFontRenderer m_fixedWidthFontRenderer;
|
||||
@@ -113,6 +122,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
registerItemModel( ComputerCraft.Blocks.commandComputer, "command_computer" );
|
||||
registerItemModel( ComputerCraft.Blocks.advancedModem, "advanced_modem" );
|
||||
registerItemModel( ComputerCraft.Blocks.peripheral, 5, "speaker" );
|
||||
registerItemModel( ComputerCraft.Blocks.wiredModemFull, "wired_modem_full" );
|
||||
|
||||
registerItemModel( ComputerCraft.Items.disk, "disk" );
|
||||
registerItemModel( ComputerCraft.Items.diskExpanded, "disk_expanded" );
|
||||
@@ -294,12 +304,6 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
return m_renderFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteDisplayLists( int list, int range )
|
||||
{
|
||||
GlStateManager.glDeleteLists( list, range );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getFixedWidthFontRenderer()
|
||||
{
|
||||
@@ -364,6 +368,13 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getComputerGUI( IComputer computer, int width, int height, ComputerFamily family )
|
||||
{
|
||||
ContainerViewComputer container = new ContainerViewComputer( computer );
|
||||
return new GuiComputer( container, family, computer, width, height );
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getWorldDir( World world )
|
||||
{
|
||||
@@ -379,6 +390,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
case ComputerCraftPacket.ComputerTerminalChanged:
|
||||
case ComputerCraftPacket.ComputerDeleted:
|
||||
case ComputerCraftPacket.PlayRecord:
|
||||
case ComputerCraftPacket.PostChat:
|
||||
{
|
||||
// Packet from Server to Client
|
||||
IThreadListener listener = Minecraft.getMinecraft();
|
||||
@@ -445,6 +457,36 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
{
|
||||
mc.world.playRecord( pos, null );
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ComputerCraftPacket.PostChat:
|
||||
{
|
||||
/*
|
||||
This allows us to send delete chat messages of the same "category" as the previous one.
|
||||
It's used by the various /computercraft commands to avoid filling the chat with repetitive
|
||||
messages.
|
||||
*/
|
||||
|
||||
int id = packet.m_dataInt[0];
|
||||
ITextComponent[] components = new ITextComponent[packet.m_dataString.length];
|
||||
for( int i = 0; i < packet.m_dataString.length; i++ )
|
||||
{
|
||||
components[i] = ITextComponent.Serializer.jsonToComponent( packet.m_dataString[i] );
|
||||
}
|
||||
|
||||
GuiNewChat chat = Minecraft.getMinecraft().ingameGUI.getChatGUI();
|
||||
|
||||
// Keep track of how many lines we wrote last time, deleting any extra ones.
|
||||
int lastCount = lastCounts.get( id );
|
||||
for( int i = components.length; i < lastCount; i++ ) chat.deleteChatLine( i + id );
|
||||
lastCounts.put( id, components.length );
|
||||
|
||||
// Add new lines
|
||||
for( int i = 0; i < components.length; i++ )
|
||||
{
|
||||
chat.printChatMessageWithOptionalDeletion( components[i], id + i );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -456,6 +498,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
MinecraftForge.EVENT_BUS.register( handlers );
|
||||
MinecraftForge.EVENT_BUS.register( new RenderOverlayCable() );
|
||||
MinecraftForge.EVENT_BUS.register( new ItemPocketRenderer() );
|
||||
MinecraftForge.EVENT_BUS.register( new ItemPrintoutRenderer() );
|
||||
}
|
||||
|
||||
public class ForgeHandlers
|
||||
@@ -542,6 +585,15 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
m_renderFrame++;
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onWorldUnload( WorldEvent.Unload event )
|
||||
{
|
||||
if( event.getWorld().isRemote )
|
||||
{
|
||||
ClientMonitor.destroyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
@@ -555,7 +607,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColorFromItemstack( @Nonnull ItemStack stack, int layer )
|
||||
public int colorMultiplier( @Nonnull ItemStack stack, int layer )
|
||||
{
|
||||
return layer == 0 ? 0xFFFFFF : disk.getColour( stack );
|
||||
}
|
||||
|
@@ -0,0 +1,197 @@
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.media.items.ItemPrintout;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.ItemRenderer;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.EnumHandSide;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraftforge.client.event.RenderItemInFrameEvent;
|
||||
import net.minecraftforge.client.event.RenderSpecificHandEvent;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
|
||||
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
||||
import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAGE;
|
||||
import static dan200.computercraft.shared.media.items.ItemPrintout.LINE_MAX_LENGTH;
|
||||
|
||||
public class ItemPrintoutRenderer
|
||||
{
|
||||
@SubscribeEvent
|
||||
public void onRenderInHand( RenderSpecificHandEvent event )
|
||||
{
|
||||
ItemStack stack = event.getItemStack();
|
||||
if( stack.getItem() != ComputerCraft.Items.printout ) return;
|
||||
|
||||
event.setCanceled( true );
|
||||
|
||||
EntityPlayer player = Minecraft.getMinecraft().player;
|
||||
|
||||
GlStateManager.pushMatrix();
|
||||
if( event.getHand() == EnumHand.MAIN_HAND && player.getHeldItemOffhand().isEmpty() )
|
||||
{
|
||||
renderPrintoutFirstPersonCentre(
|
||||
event.getInterpolatedPitch(),
|
||||
event.getEquipProgress(),
|
||||
event.getSwingProgress(),
|
||||
stack
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderPrintoutFirstPersonSide(
|
||||
event.getHand() == EnumHand.MAIN_HAND ? player.getPrimaryHand() : player.getPrimaryHand().opposite(),
|
||||
event.getEquipProgress(),
|
||||
event.getSwingProgress(),
|
||||
stack
|
||||
);
|
||||
}
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a pocket computer to one side of the player.
|
||||
*
|
||||
* @param side The side to render on
|
||||
* @param equipProgress The equip progress of this item
|
||||
* @param swingProgress The swing progress of this item
|
||||
* @param stack The stack to render
|
||||
* @see ItemRenderer#renderMapFirstPersonSide(float, EnumHandSide, float, ItemStack)
|
||||
*/
|
||||
private void renderPrintoutFirstPersonSide( EnumHandSide side, float equipProgress, float swingProgress, ItemStack stack )
|
||||
{
|
||||
Minecraft minecraft = Minecraft.getMinecraft();
|
||||
float offset = side == EnumHandSide.RIGHT ? 1f : -1f;
|
||||
GlStateManager.translate( offset * 0.125f, -0.125f, 0f );
|
||||
|
||||
// If the player is not invisible then render a single arm
|
||||
if( !minecraft.player.isInvisible() )
|
||||
{
|
||||
GlStateManager.pushMatrix();
|
||||
GlStateManager.rotate( offset * 10f, 0f, 0f, 1f );
|
||||
minecraft.getItemRenderer().renderArmFirstPerson( equipProgress, swingProgress, side );
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
|
||||
// Setup the appropriate transformations. This is just copied from the
|
||||
// corresponding method in ItemRenderer.
|
||||
GlStateManager.pushMatrix();
|
||||
GlStateManager.translate( offset * 0.51f, -0.08f + equipProgress * -1.2f, -0.75f );
|
||||
float f1 = MathHelper.sqrt( swingProgress );
|
||||
float f2 = MathHelper.sin( f1 * (float) Math.PI );
|
||||
float f3 = -0.5f * f2;
|
||||
float f4 = 0.4f * MathHelper.sin( f1 * ((float) Math.PI * 2f) );
|
||||
float f5 = -0.3f * MathHelper.sin( swingProgress * (float) Math.PI );
|
||||
GlStateManager.translate( offset * f3, f4 - 0.3f * f2, f5 );
|
||||
GlStateManager.rotate( f2 * -45f, 1f, 0f, 0f );
|
||||
GlStateManager.rotate( offset * f2 * -30f, 0f, 1f, 0f );
|
||||
|
||||
renderPrintoutFirstPerson( stack );
|
||||
|
||||
GlStateManager.popMatrix();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an item in the middle of the screen
|
||||
*
|
||||
* @param pitch The pitch of the player
|
||||
* @param equipProgress The equip progress of this item
|
||||
* @param swingProgress The swing progress of this item
|
||||
* @param stack The stack to render
|
||||
* @see ItemRenderer#renderMapFirstPerson(float, float, float)
|
||||
*/
|
||||
private void renderPrintoutFirstPersonCentre( float pitch, float equipProgress, float swingProgress, ItemStack stack )
|
||||
{
|
||||
ItemRenderer itemRenderer = Minecraft.getMinecraft().getItemRenderer();
|
||||
|
||||
// Setup the appropriate transformations. This is just copied from the
|
||||
// corresponding method in ItemRenderer.
|
||||
float swingRt = MathHelper.sqrt( swingProgress );
|
||||
float tX = -0.2f * MathHelper.sin( swingProgress * (float) Math.PI );
|
||||
float tZ = -0.4f * MathHelper.sin( swingRt * (float) Math.PI );
|
||||
GlStateManager.translate( 0f, -tX / 2f, tZ );
|
||||
float pitchAngle = itemRenderer.getMapAngleFromPitch( pitch );
|
||||
GlStateManager.translate( 0f, 0.04f + equipProgress * -1.2f + pitchAngle * -0.5f, -0.72f );
|
||||
GlStateManager.rotate( pitchAngle * -85f, 1f, 0f, 0f );
|
||||
itemRenderer.renderArms();
|
||||
float rX = MathHelper.sin( swingRt * (float) Math.PI );
|
||||
GlStateManager.rotate( rX * 20f, 1f, 0f, 0f );
|
||||
GlStateManager.scale( 2f, 2f, 2f );
|
||||
|
||||
renderPrintoutFirstPerson( stack );
|
||||
}
|
||||
|
||||
|
||||
private static void renderPrintoutFirstPerson( ItemStack stack )
|
||||
{
|
||||
// Setup various transformations. Note that these are partially adapated from the corresponding method
|
||||
// in ItemRenderer.renderMapFirstPerson
|
||||
GlStateManager.disableLighting();
|
||||
|
||||
GlStateManager.rotate( 180f, 0f, 1f, 0f );
|
||||
GlStateManager.rotate( 180f, 0f, 0f, 1f );
|
||||
GlStateManager.scale( 0.42f, 0.42f, -0.42f );
|
||||
GlStateManager.translate( -0.5f, -0.48f, 0.0f );
|
||||
|
||||
drawPrintout( stack );
|
||||
|
||||
GlStateManager.enableLighting();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onRenderInFrame( RenderItemInFrameEvent event )
|
||||
{
|
||||
ItemStack stack = event.getItem();
|
||||
if( stack.getItem() != ComputerCraft.Items.printout ) return;
|
||||
|
||||
event.setCanceled( true );
|
||||
|
||||
GlStateManager.disableLighting();
|
||||
|
||||
// Move a little bit forward to ensure we're not clipping with the frame
|
||||
GlStateManager.translate( 0.0f, 0.0f, -0.001f );
|
||||
GlStateManager.rotate( 180f, 0f, 0f, 1f );
|
||||
GlStateManager.scale( 0.95f, 0.95f, -0.95f );
|
||||
GlStateManager.translate( -0.5f, -0.5f, 0.0f );
|
||||
|
||||
drawPrintout( stack );
|
||||
|
||||
GlStateManager.enableLighting();
|
||||
}
|
||||
|
||||
private static void drawPrintout( ItemStack stack )
|
||||
{
|
||||
int pages = ItemPrintout.getPageCount( stack );
|
||||
boolean book = ItemPrintout.getType( stack ) == ItemPrintout.Type.Book;
|
||||
|
||||
double width = LINE_MAX_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2;
|
||||
double height = LINES_PER_PAGE * FONT_HEIGHT + Y_TEXT_MARGIN * 2;
|
||||
|
||||
// Non-books will be left aligned
|
||||
if( !book ) width += offsetAt( pages );
|
||||
|
||||
double visualWidth = width, visualHeight = height;
|
||||
|
||||
// Meanwhile books will be centred
|
||||
if( book )
|
||||
{
|
||||
visualWidth += 2 * COVER_SIZE + 2 * offsetAt( pages );
|
||||
visualHeight += 2 * COVER_SIZE;
|
||||
}
|
||||
|
||||
double max = Math.max( visualHeight, visualWidth );
|
||||
|
||||
// Scale the printout to fit correctly.
|
||||
double scale = 1.0 / max;
|
||||
GlStateManager.scale( scale, scale, scale );
|
||||
GlStateManager.translate( (max - width) / 2.0f, (max - height) / 2.0f, 0.0f );
|
||||
|
||||
drawBorder( 0, 0, -0.01, 0, pages, book );
|
||||
drawText( X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, ItemPrintout.getText( stack ), ItemPrintout.getColours( stack ) );
|
||||
}
|
||||
}
|
@@ -189,7 +189,7 @@ public final class ModelTransformer
|
||||
private BakedQuadBuilder( VertexFormat format )
|
||||
{
|
||||
this.format = format;
|
||||
this.vertexData = new int[ format.getNextOffset() ];
|
||||
this.vertexData = new int[ format.getSize() ];
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@@ -0,0 +1,169 @@
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAGE;
|
||||
|
||||
public class PrintoutRenderer
|
||||
{
|
||||
private static final ResourceLocation BG = new ResourceLocation( "computercraft", "textures/gui/printout.png" );
|
||||
private static final double BG_SIZE = 256.0;
|
||||
|
||||
/**
|
||||
* Width of a page
|
||||
*/
|
||||
public static final int X_SIZE = 172;
|
||||
|
||||
/**
|
||||
* Height of a page
|
||||
*/
|
||||
public static final int Y_SIZE = 209;
|
||||
|
||||
/**
|
||||
* Padding between the left and right of a page and the text
|
||||
*/
|
||||
public static final int X_TEXT_MARGIN = 13;
|
||||
|
||||
/**
|
||||
* Padding between the top and bottom of a page and the text
|
||||
*/
|
||||
public static final int Y_TEXT_MARGIN = 11;
|
||||
|
||||
/**
|
||||
* Width of the extra page texture
|
||||
*/
|
||||
private static final int X_FOLD_SIZE = 12;
|
||||
|
||||
/**
|
||||
* Size of the leather cover
|
||||
*/
|
||||
public static final int COVER_SIZE = 12;
|
||||
|
||||
private static final int COVER_Y = Y_SIZE;
|
||||
private static final int COVER_X = X_SIZE + 4 * X_FOLD_SIZE;
|
||||
|
||||
public static void drawText( int x, int y, int start, TextBuffer[] text, TextBuffer[] colours )
|
||||
{
|
||||
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer) ComputerCraft.getFixedWidthFontRenderer();
|
||||
|
||||
for( int line = 0; line < LINES_PER_PAGE && line < text.length; ++line )
|
||||
{
|
||||
fontRenderer.drawString( text[ start + line ], x, y + line * FONT_HEIGHT, colours[ start + line ], null, 0, 0, false, Palette.DEFAULT );
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawText( int x, int y, int start, String[] text, String[] colours )
|
||||
{
|
||||
GlStateManager.color( 1.0f, 1.0f, 1.0f, 1.0f );
|
||||
GlStateManager.enableBlend();
|
||||
GlStateManager.enableTexture2D();
|
||||
|
||||
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer) ComputerCraft.getFixedWidthFontRenderer();
|
||||
|
||||
for( int line = 0; line < LINES_PER_PAGE && line < text.length; ++line )
|
||||
{
|
||||
fontRenderer.drawString( new TextBuffer( text[ start + line ] ), x, y + line * FONT_HEIGHT, new TextBuffer( colours[ start + line ] ), null, 0, 0, false, Palette.DEFAULT );
|
||||
}
|
||||
}
|
||||
|
||||
public static void drawBorder( double x, double y, double z, int page, int pages, boolean isBook )
|
||||
{
|
||||
GlStateManager.color( 1.0f, 1.0f, 1.0f, 1.0f );
|
||||
GlStateManager.enableBlend();
|
||||
GlStateManager.enableTexture2D();
|
||||
|
||||
Minecraft.getMinecraft().getTextureManager().bindTexture( BG );
|
||||
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
BufferBuilder buffer = tessellator.getBuffer();
|
||||
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX );
|
||||
|
||||
int leftPages = page;
|
||||
int rightPages = pages - page - 1;
|
||||
|
||||
if( isBook )
|
||||
{
|
||||
// Border
|
||||
double offset = offsetAt( pages );
|
||||
final double left = x - 4 - offset;
|
||||
final double right = x + X_SIZE + offset - 4;
|
||||
|
||||
// Left and right border
|
||||
drawTexture( buffer, left - 4, y - 8, z - 0.02, COVER_X, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2 );
|
||||
drawTexture( buffer, right, y - 8, z - 0.02, COVER_X + COVER_SIZE, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2 );
|
||||
|
||||
// Draw centre panel (just stretched texture, sorry).
|
||||
drawTexture( buffer,
|
||||
x - offset, y, z - 0.02, X_SIZE + offset * 2, Y_SIZE,
|
||||
COVER_X + COVER_SIZE / 2, COVER_SIZE, COVER_SIZE, Y_SIZE
|
||||
);
|
||||
|
||||
double borderX = left;
|
||||
while( borderX < right )
|
||||
{
|
||||
double thisWidth = Math.min( right - borderX, X_SIZE );
|
||||
drawTexture( buffer, borderX, y - 8, z - 0.02, 0, COVER_Y, thisWidth, COVER_SIZE );
|
||||
drawTexture( buffer, borderX, y + Y_SIZE - 4, z - 0.02, 0, COVER_Y + COVER_SIZE, thisWidth, COVER_SIZE );
|
||||
borderX += thisWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Left half
|
||||
drawTexture( buffer, x, y, z, X_FOLD_SIZE * 2, 0, X_SIZE / 2, Y_SIZE );
|
||||
for( int n = 0; n <= leftPages; n++ )
|
||||
{
|
||||
drawTexture( buffer,
|
||||
x - offsetAt( n ), y, z - 1e-3 * n,
|
||||
// Use the left "bold" fold for the outermost page
|
||||
n == leftPages ? 0 : X_FOLD_SIZE, 0,
|
||||
X_FOLD_SIZE, Y_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
// Right half
|
||||
drawTexture( buffer, x + X_SIZE / 2, y, z, X_FOLD_SIZE * 2 + X_SIZE / 2, 0, X_SIZE / 2, Y_SIZE );
|
||||
for( int n = 0; n <= rightPages; n++ )
|
||||
{
|
||||
drawTexture( buffer,
|
||||
x + (X_SIZE - X_FOLD_SIZE) + offsetAt( n ), y, z - 1e-3 * n,
|
||||
// Two folds, then the main page. Use the right "bold" fold for the outermost page.
|
||||
X_FOLD_SIZE * 2 + X_SIZE + (n == rightPages ? X_FOLD_SIZE : 0), 0,
|
||||
X_FOLD_SIZE, Y_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
tessellator.draw();
|
||||
}
|
||||
|
||||
private static void drawTexture( BufferBuilder buffer, double x, double y, double z, double u, double v, double width, double height )
|
||||
{
|
||||
buffer.pos( x, y + height, z ).tex( u / BG_SIZE, (v + height) / BG_SIZE ).endVertex();
|
||||
buffer.pos( x + width, y + height, z ).tex( (u + width) / BG_SIZE, (v + height) / BG_SIZE ).endVertex();
|
||||
buffer.pos( x + width, y, z ).tex( (u + width) / BG_SIZE, v / BG_SIZE ).endVertex();
|
||||
buffer.pos( x, y, z ).tex( u / BG_SIZE, v / BG_SIZE ).endVertex();
|
||||
}
|
||||
|
||||
private static void drawTexture( BufferBuilder buffer, double x, double y, double z, double width, double height, double u, double v, double tWidth, double tHeight )
|
||||
{
|
||||
buffer.pos( x, y + height, z ).tex( u / BG_SIZE, (v + tHeight) / BG_SIZE ).endVertex();
|
||||
buffer.pos( x + width, y + height, z ).tex( (u + tWidth) / BG_SIZE, (v + tHeight) / BG_SIZE ).endVertex();
|
||||
buffer.pos( x + width, y, z ).tex( (u + tWidth) / BG_SIZE, v / BG_SIZE ).endVertex();
|
||||
buffer.pos( x, y, z ).tex( u / BG_SIZE, v / BG_SIZE ).endVertex();
|
||||
}
|
||||
|
||||
public static double offsetAt( int page )
|
||||
{
|
||||
return 32 * (1 - Math.pow( 1.2, -page ));
|
||||
}
|
||||
}
|
@@ -54,8 +54,6 @@ public class RenderOverlayCable
|
||||
GlStateManager.depthMask( false );
|
||||
GlStateManager.pushMatrix();
|
||||
|
||||
EnumFacing direction = type != PeripheralType.Cable ? cable.getDirection() : null;
|
||||
|
||||
{
|
||||
EntityPlayer player = event.getPlayer();
|
||||
double x = player.lastTickPosX + (player.posX - player.lastTickPosX) * event.getPartialTicks();
|
||||
@@ -78,7 +76,7 @@ public class RenderOverlayCable
|
||||
|
||||
for( EnumFacing facing : EnumFacing.VALUES )
|
||||
{
|
||||
if( direction == facing || BlockCable.isCable( world, pos.offset( facing ) ) )
|
||||
if( BlockCable.doesConnectVisually( state, world, pos, facing ) )
|
||||
{
|
||||
flags |= 1 << facing.ordinal();
|
||||
|
||||
|
@@ -71,7 +71,7 @@ public class TileEntityCableRenderer extends TileEntitySpecialRenderer<TileCable
|
||||
buffer.setTranslation( x - pos.getX(), y - pos.getY(), z - pos.getZ() );
|
||||
buffer.noColor();
|
||||
|
||||
ForgeHooksClient.setRenderLayer( block.getBlockLayer() );
|
||||
ForgeHooksClient.setRenderLayer( block.getRenderLayer() );
|
||||
|
||||
// See BlockRendererDispatcher#renderBlockDamage
|
||||
TextureAtlasSprite breakingTexture = mc.getTextureMapBlocks().getAtlasSprite( "minecraft:blocks/destroy_stage_" + destroyStage );
|
||||
|
@@ -10,7 +10,7 @@ import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.common.ClientTerminal;
|
||||
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
||||
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
@@ -18,6 +18,7 @@ import dan200.computercraft.shared.util.Palette;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.GlStateManager;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
@@ -43,25 +44,24 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
private void renderMonitorAt( TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
|
||||
{
|
||||
// Render from the origin monitor
|
||||
TileMonitor origin = monitor.getOrigin();
|
||||
if( origin == null )
|
||||
{
|
||||
return;
|
||||
}
|
||||
ClientMonitor originTerminal = monitor.getClientMonitor();
|
||||
|
||||
// Ensure each monitor is rendered only once
|
||||
long renderFrame = ComputerCraft.getRenderFrame();
|
||||
if( origin.m_lastRenderFrame == renderFrame )
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
origin.m_lastRenderFrame = renderFrame;
|
||||
}
|
||||
|
||||
boolean redraw = origin.pollChanged();
|
||||
if( originTerminal == null ) return;
|
||||
TileMonitor origin = originTerminal.getOrigin();
|
||||
BlockPos monitorPos = monitor.getPos();
|
||||
|
||||
// Ensure each monitor terminal is rendered only once. We allow rendering a specific tile
|
||||
// multiple times in a single frame to ensure compatibility with shaders which may run a
|
||||
// pass multiple times.
|
||||
long renderFrame = ComputerCraft.getRenderFrame();
|
||||
if( originTerminal.lastRenderFrame == renderFrame && !monitorPos.equals( originTerminal.lastRenderPos ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
originTerminal.lastRenderFrame = renderFrame;
|
||||
originTerminal.lastRenderPos = monitorPos;
|
||||
|
||||
BlockPos originPos = origin.getPos();
|
||||
posX += originPos.getX() - monitorPos.getX();
|
||||
posY += originPos.getY() - monitorPos.getY();
|
||||
@@ -94,34 +94,34 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
BufferBuilder renderer = tessellator.getBuffer();
|
||||
|
||||
// Get terminal
|
||||
ClientTerminal clientTerminal = (ClientTerminal)origin.getTerminal();
|
||||
Terminal terminal = (clientTerminal != null) ? clientTerminal.getTerminal() : null;
|
||||
redraw = redraw || (clientTerminal != null && clientTerminal.hasTerminalChanged());
|
||||
boolean redraw = originTerminal.pollTerminalChanged();
|
||||
|
||||
// Draw the contents
|
||||
GlStateManager.depthMask( false );
|
||||
OpenGlHelper.setLightmapTextureCoords( OpenGlHelper.lightmapTexUnit, 0xFF, 0xFF );
|
||||
GlStateManager.disableLighting();
|
||||
mc.entityRenderer.disableLightmap();
|
||||
try
|
||||
{
|
||||
Terminal terminal = originTerminal.getTerminal();
|
||||
if( terminal != null )
|
||||
{
|
||||
Palette palette = terminal.getPalette();
|
||||
|
||||
// Allocate display lists
|
||||
if( origin.m_renderDisplayList < 0 )
|
||||
if( originTerminal.renderDisplayLists == null )
|
||||
{
|
||||
origin.m_renderDisplayList = GlStateManager.glGenLists( 3 );
|
||||
originTerminal.createLists();
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
// Draw a terminal
|
||||
boolean greyscale = !clientTerminal.isColour();
|
||||
boolean greyscale = !originTerminal.isColour();
|
||||
int width = terminal.getWidth();
|
||||
int height = terminal.getHeight();
|
||||
int cursorX = terminal.getCursorX();
|
||||
int cursorY = terminal.getCursorY();
|
||||
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer)ComputerCraft.getFixedWidthFontRenderer();
|
||||
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer) ComputerCraft.getFixedWidthFontRenderer();
|
||||
|
||||
GlStateManager.pushMatrix();
|
||||
try
|
||||
@@ -135,7 +135,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
if( redraw )
|
||||
{
|
||||
// Build background display list
|
||||
GlStateManager.glNewList( origin.m_renderDisplayList, GL11.GL_COMPILE );
|
||||
GlStateManager.glNewList( originTerminal.renderDisplayLists[0], GL11.GL_COMPILE );
|
||||
try
|
||||
{
|
||||
double marginXSize = TileMonitor.RENDER_MARGIN / xScale;
|
||||
@@ -149,7 +149,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
GlStateManager.scale( 1.0, marginSquash, 1.0 );
|
||||
GlStateManager.translate( 0.0, -marginYSize / marginSquash, 0.0 );
|
||||
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( 0 ), marginXSize, marginXSize, greyscale, palette );
|
||||
GlStateManager.translate( 0.0, ( marginYSize + height * FixedWidthFontRenderer.FONT_HEIGHT ) / marginSquash, 0.0 );
|
||||
GlStateManager.translate( 0.0, (marginYSize + height * FixedWidthFontRenderer.FONT_HEIGHT) / marginSquash, 0.0 );
|
||||
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( height - 1 ), marginXSize, marginXSize, greyscale, palette );
|
||||
}
|
||||
finally
|
||||
@@ -174,7 +174,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
GlStateManager.glEndList();
|
||||
}
|
||||
}
|
||||
GlStateManager.callList( origin.m_renderDisplayList );
|
||||
GlStateManager.callList( originTerminal.renderDisplayLists[0] );
|
||||
GlStateManager.resetColor();
|
||||
|
||||
// Draw text
|
||||
@@ -182,7 +182,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
if( redraw )
|
||||
{
|
||||
// Build text display list
|
||||
GlStateManager.glNewList( origin.m_renderDisplayList + 1, GL11.GL_COMPILE );
|
||||
GlStateManager.glNewList( originTerminal.renderDisplayLists[1], GL11.GL_COMPILE );
|
||||
try
|
||||
{
|
||||
// Lines
|
||||
@@ -202,7 +202,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
GlStateManager.glEndList();
|
||||
}
|
||||
}
|
||||
GlStateManager.callList( origin.m_renderDisplayList + 1 );
|
||||
GlStateManager.callList( originTerminal.renderDisplayLists[1] );
|
||||
GlStateManager.resetColor();
|
||||
|
||||
// Draw cursor
|
||||
@@ -210,7 +210,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
if( redraw )
|
||||
{
|
||||
// Build cursor display list
|
||||
GlStateManager.glNewList( origin.m_renderDisplayList + 2, GL11.GL_COMPILE );
|
||||
GlStateManager.glNewList( originTerminal.renderDisplayLists[2], GL11.GL_COMPILE );
|
||||
try
|
||||
{
|
||||
// Cursor
|
||||
@@ -236,7 +236,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
|
||||
}
|
||||
if( ComputerCraft.getGlobalCursorBlink() )
|
||||
{
|
||||
GlStateManager.callList( origin.m_renderDisplayList + 2 );
|
||||
GlStateManager.callList( originTerminal.renderDisplayLists[2] );
|
||||
GlStateManager.resetColor();
|
||||
}
|
||||
}
|
||||
|
@@ -167,7 +167,7 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
|
||||
ModelResourceLocation baseModelLocation = TileEntityTurtleRenderer.getTurtleModel( combo.m_family, combo.m_colour );
|
||||
ModelResourceLocation overlayModelLocation = TileEntityTurtleRenderer.getTurtleOverlayModel( combo.m_family, combo.m_overlay, combo.m_christmas );
|
||||
IBakedModel baseModel = modelManager.getModel( baseModelLocation );
|
||||
IBakedModel overlayModel = (overlayModelLocation != null) ? modelManager.getModel( baseModelLocation ) : null;
|
||||
IBakedModel overlayModel = (overlayModelLocation != null) ? modelManager.getModel( overlayModelLocation ) : null;
|
||||
Matrix4f transform = combo.m_flip ? s_flip : s_identity;
|
||||
Pair<IBakedModel, Matrix4f> leftModel = (combo.m_leftUpgrade != null) ? combo.m_leftUpgrade.getModel( null, TurtleSide.Left ) : null;
|
||||
Pair<IBakedModel, Matrix4f> rightModel = (combo.m_rightUpgrade != null) ? combo.m_rightUpgrade.getModel( null, TurtleSide.Right ) : null;
|
||||
|
@@ -3,14 +3,17 @@ package dan200.computercraft.core.apis;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.IComputerOwned;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class ComputerAccess implements IComputerAccess
|
||||
public abstract class ComputerAccess implements IComputerAccess, IComputerOwned
|
||||
{
|
||||
private final IAPIEnvironment m_environment;
|
||||
private final Set<String> m_mounts = new HashSet<>();
|
||||
@@ -133,6 +136,13 @@ public abstract class ComputerAccess implements IComputerAccess
|
||||
m_environment.queueEvent( event, arguments );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Computer getComputer()
|
||||
{
|
||||
return m_environment.getComputer();
|
||||
}
|
||||
|
||||
private String findFreeLocation( String desiredLoc )
|
||||
{
|
||||
try
|
||||
|
@@ -9,18 +9,23 @@ package dan200.computercraft.core.apis;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
|
||||
import dan200.computercraft.core.apis.handles.BinaryOutputHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedInputHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedOutputHandle;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.BinaryWritableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedWritableHandle;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
import dan200.computercraft.core.filesystem.FileSystemWrapper;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
|
||||
|
||||
@@ -88,6 +93,7 @@ public class FSAPI implements ILuaAPI
|
||||
{
|
||||
// list
|
||||
String path = getString( args, 0 );
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
try {
|
||||
String[] results = m_fileSystem.list( path );
|
||||
Map<Object,Object> table = new HashMap<>();
|
||||
@@ -162,6 +168,7 @@ public class FSAPI implements ILuaAPI
|
||||
// makeDir
|
||||
String path = getString( args, 0 );
|
||||
try {
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.makeDir( path );
|
||||
return null;
|
||||
} catch( FileSystemException e ) {
|
||||
@@ -174,6 +181,7 @@ public class FSAPI implements ILuaAPI
|
||||
String path = getString( args, 0 );
|
||||
String dest = getString( args, 1 );
|
||||
try {
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.move( path, dest );
|
||||
return null;
|
||||
} catch( FileSystemException e ) {
|
||||
@@ -186,6 +194,7 @@ public class FSAPI implements ILuaAPI
|
||||
String path = getString( args, 0 );
|
||||
String dest = getString( args, 1 );
|
||||
try {
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.copy( path, dest );
|
||||
return null;
|
||||
} catch( FileSystemException e ) {
|
||||
@@ -197,6 +206,7 @@ public class FSAPI implements ILuaAPI
|
||||
// delete
|
||||
String path = getString( args, 0 );
|
||||
try {
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
m_fileSystem.delete( path );
|
||||
return null;
|
||||
} catch( FileSystemException e ) {
|
||||
@@ -208,44 +218,45 @@ public class FSAPI implements ILuaAPI
|
||||
// open
|
||||
String path = getString( args, 0 );
|
||||
String mode = getString( args, 1 );
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
try {
|
||||
switch( mode )
|
||||
{
|
||||
case "r":
|
||||
{
|
||||
// Open the file for reading, then create a wrapper around the reader
|
||||
InputStream reader = m_fileSystem.openForRead( path );
|
||||
return new Object[] { new EncodedInputHandle( reader ) };
|
||||
FileSystemWrapper<BufferedReader> reader = m_fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedReadableHandle( reader.get(), reader ) };
|
||||
}
|
||||
case "w":
|
||||
{
|
||||
// Open the file for writing, then create a wrapper around the writer
|
||||
OutputStream writer = m_fileSystem.openForWrite( path, false );
|
||||
return new Object[] { new EncodedOutputHandle( writer ) };
|
||||
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
|
||||
}
|
||||
case "a":
|
||||
{
|
||||
// Open the file for appending, then create a wrapper around the writer
|
||||
OutputStream writer = m_fileSystem.openForWrite( path, true );
|
||||
return new Object[] { new EncodedOutputHandle( writer ) };
|
||||
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 );
|
||||
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
|
||||
}
|
||||
case "rb":
|
||||
{
|
||||
// Open the file for binary reading, then create a wrapper around the reader
|
||||
InputStream reader = m_fileSystem.openForRead( path );
|
||||
return new Object[] { new BinaryInputHandle( reader ) };
|
||||
FileSystemWrapper<ReadableByteChannel> reader = m_fileSystem.openForRead( path, Function.identity() );
|
||||
return new Object[] { new BinaryReadableHandle( reader.get(), reader ) };
|
||||
}
|
||||
case "wb":
|
||||
{
|
||||
// Open the file for binary writing, then create a wrapper around the writer
|
||||
OutputStream writer = m_fileSystem.openForWrite( path, false );
|
||||
return new Object[] { new BinaryOutputHandle( writer ) };
|
||||
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, false, Function.identity() );
|
||||
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
|
||||
}
|
||||
case "ab":
|
||||
{
|
||||
// Open the file for binary appending, then create a wrapper around the reader
|
||||
OutputStream writer = m_fileSystem.openForWrite( path, true );
|
||||
return new Object[] { new BinaryOutputHandle( writer ) };
|
||||
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, true, Function.identity() );
|
||||
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
|
||||
}
|
||||
default:
|
||||
throw new LuaException( "Unsupported mode" );
|
||||
@@ -288,6 +299,7 @@ public class FSAPI implements ILuaAPI
|
||||
// find
|
||||
String path = getString( args, 0 );
|
||||
try {
|
||||
m_env.addTrackingChange( TrackingField.FS_OPS );
|
||||
String[] results = m_fileSystem.find( path );
|
||||
Map<Object,Object> table = new HashMap<>();
|
||||
for(int i=0; i<results.length; ++i ) {
|
||||
|
@@ -6,6 +6,7 @@
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
@@ -21,9 +22,14 @@ import java.util.*;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.*;
|
||||
import static dan200.computercraft.core.apis.TableHelper.*;
|
||||
|
||||
public class HTTPAPI implements ILuaAPI
|
||||
{
|
||||
private static final Set<String> HTTP_METHODS = ImmutableSet.of(
|
||||
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE"
|
||||
);
|
||||
|
||||
private final IAPIEnvironment m_apiEnvironment;
|
||||
private final List<Future<?>> m_httpTasks;
|
||||
private final Set<Closeable> m_closeables;
|
||||
@@ -38,7 +44,7 @@ public class HTTPAPI implements ILuaAPI
|
||||
@Override
|
||||
public String[] getNames()
|
||||
{
|
||||
return new String[] {
|
||||
return new String[]{
|
||||
"http"
|
||||
};
|
||||
}
|
||||
@@ -59,7 +65,7 @@ public class HTTPAPI implements ILuaAPI
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown( )
|
||||
public void shutdown()
|
||||
{
|
||||
synchronized( m_httpTasks )
|
||||
{
|
||||
@@ -89,7 +95,7 @@ public class HTTPAPI implements ILuaAPI
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[] {
|
||||
return new String[]{
|
||||
"request",
|
||||
"checkURL",
|
||||
"websocket",
|
||||
@@ -101,52 +107,68 @@ public class HTTPAPI implements ILuaAPI
|
||||
{
|
||||
switch( method )
|
||||
{
|
||||
case 0:
|
||||
case 0: // request
|
||||
{
|
||||
// request
|
||||
// Get URL
|
||||
String urlString = getString( args, 0 );
|
||||
String urlString, postString, requestMethod;
|
||||
Map<Object, Object> headerTable;
|
||||
boolean binary, redirect;
|
||||
|
||||
// Get POST
|
||||
String postString = optString( args, 1, null );
|
||||
|
||||
// Get Headers
|
||||
Map<String, String> headers = null;
|
||||
Map<Object, Object> table = optTable( args, 2, null );
|
||||
if( table != null )
|
||||
if( args.length >= 1 && args[0] instanceof Map )
|
||||
{
|
||||
headers = new HashMap<>( table.size() );
|
||||
for( Object key : table.keySet() )
|
||||
Map<?, ?> options = (Map) args[0];
|
||||
urlString = getStringField( options, "url" );
|
||||
postString = optStringField( options, "body", null );
|
||||
headerTable = optTableField( options, "headers", null );
|
||||
binary = optBooleanField( options, "binary", false );
|
||||
requestMethod = optStringField( options, "method", null );
|
||||
redirect = optBooleanField( options, "redirect", true );
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get URL and post information
|
||||
urlString = getString( args, 0 );
|
||||
postString = optString( args, 1, null );
|
||||
headerTable = optTable( args, 2, null );
|
||||
binary = optBoolean( args, 3, false );
|
||||
requestMethod = null;
|
||||
redirect = true;
|
||||
}
|
||||
|
||||
Map<String, String> headers = null;
|
||||
if( headerTable != null )
|
||||
{
|
||||
headers = new HashMap<>( headerTable.size() );
|
||||
for( Object key : headerTable.keySet() )
|
||||
{
|
||||
Object value = table.get( key );
|
||||
Object value = headerTable.get( key );
|
||||
if( key instanceof String && value instanceof String )
|
||||
{
|
||||
headers.put( (String)key, (String)value );
|
||||
headers.put( (String) key, (String) value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get binary
|
||||
boolean binary = false;
|
||||
if( args.length >= 4 )
|
||||
|
||||
if( requestMethod != null && !HTTP_METHODS.contains( requestMethod ) )
|
||||
{
|
||||
binary = args[ 3 ] != null && !args[ 3 ].equals( Boolean.FALSE );
|
||||
throw new LuaException( "Unsupported HTTP method" );
|
||||
}
|
||||
|
||||
// Make the request
|
||||
try
|
||||
{
|
||||
URL url = HTTPRequest.checkURL( urlString );
|
||||
HTTPRequest request = new HTTPRequest( m_apiEnvironment, urlString, url, postString, headers, binary );
|
||||
HTTPRequest request = new HTTPRequest( m_apiEnvironment, urlString, url, postString, headers, binary, requestMethod, redirect );
|
||||
synchronized( m_httpTasks )
|
||||
{
|
||||
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) );
|
||||
}
|
||||
return new Object[] { true };
|
||||
return new Object[]{ true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[] { false, e.getMessage() };
|
||||
return new Object[]{ false, e.getMessage() };
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
@@ -164,11 +186,11 @@ public class HTTPAPI implements ILuaAPI
|
||||
{
|
||||
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) );
|
||||
}
|
||||
return new Object[] { true };
|
||||
return new Object[]{ true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[] { false, e.getMessage() };
|
||||
return new Object[]{ false, e.getMessage() };
|
||||
}
|
||||
}
|
||||
case 2: // websocket
|
||||
@@ -201,11 +223,11 @@ public class HTTPAPI implements ILuaAPI
|
||||
{
|
||||
m_httpTasks.add( connector );
|
||||
}
|
||||
return new Object[] { true };
|
||||
return new Object[]{ true };
|
||||
}
|
||||
catch( HTTPRequestException e )
|
||||
{
|
||||
return new Object[] { false, e.getMessage() };
|
||||
return new Object[]{ false, e.getMessage() };
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
@@ -9,16 +9,19 @@ package dan200.computercraft.core.apis;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.IComputerEnvironment;
|
||||
import dan200.computercraft.core.computer.IComputerOwned;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
|
||||
public interface IAPIEnvironment
|
||||
public interface IAPIEnvironment extends IComputerOwned
|
||||
{
|
||||
interface IPeripheralChangeListener
|
||||
{
|
||||
void onPeripheralChanged( int side, IPeripheral newPeripheral );
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
Computer getComputer();
|
||||
int getComputerID();
|
||||
IComputerEnvironment getComputerEnvironment();
|
||||
@@ -42,4 +45,11 @@ public interface IAPIEnvironment
|
||||
|
||||
String getLabel();
|
||||
void setLabel( String label );
|
||||
|
||||
void addTrackingChange( TrackingField field, long change );
|
||||
|
||||
default void addTrackingChange( TrackingField field )
|
||||
{
|
||||
addTrackingChange( field, 1 );
|
||||
}
|
||||
}
|
||||
|
16
src/main/java/dan200/computercraft/core/apis/ILuaAPI.java
Normal file
16
src/main/java/dan200/computercraft/core/apis/ILuaAPI.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
/**
|
||||
* This exists purely to ensure binary compatibility.
|
||||
*
|
||||
* @see dan200.computercraft.api.lua.ILuaAPI
|
||||
*/
|
||||
public interface ILuaAPI extends dan200.computercraft.api.lua.ILuaAPI
|
||||
{
|
||||
void advance( double v );
|
||||
|
||||
default void update()
|
||||
{
|
||||
advance( 0.05 );
|
||||
}
|
||||
}
|
@@ -15,10 +15,12 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.ComputerThread;
|
||||
import dan200.computercraft.core.computer.ITask;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -108,6 +110,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
if( method >= 0 )
|
||||
{
|
||||
m_environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
|
||||
return m_peripheral.callMethod( this, context, method, arguments );
|
||||
}
|
||||
else
|
||||
@@ -180,10 +183,49 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
}
|
||||
return m_side;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Map<String, IPeripheral> getAvailablePeripherals()
|
||||
{
|
||||
if( !m_attached )
|
||||
{
|
||||
throw new RuntimeException( "You are not attached to this Computer" );
|
||||
}
|
||||
|
||||
Map<String, IPeripheral> peripherals = new HashMap<>();
|
||||
for( PeripheralWrapper wrapper : m_peripherals )
|
||||
{
|
||||
if( wrapper != null && wrapper.isAttached() )
|
||||
{
|
||||
peripherals.put( wrapper.getAttachmentName(), wrapper.getPeripheral() );
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap( peripherals );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IPeripheral getAvailablePeripheral( @Nonnull String name )
|
||||
{
|
||||
if( !m_attached )
|
||||
{
|
||||
throw new RuntimeException( "You are not attached to this Computer" );
|
||||
}
|
||||
|
||||
for( PeripheralWrapper wrapper : m_peripherals )
|
||||
{
|
||||
if( wrapper != null && wrapper.isAttached() && wrapper.getAttachmentName().equals( name ) )
|
||||
{
|
||||
return wrapper.getPeripheral();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private final IAPIEnvironment m_environment;
|
||||
private FileSystem m_fileSystem;
|
||||
private final PeripheralWrapper[] m_peripherals;
|
||||
private boolean m_running;
|
||||
|
||||
@@ -285,7 +327,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
{
|
||||
synchronized( m_peripherals )
|
||||
{
|
||||
m_fileSystem = m_environment.getFileSystem();
|
||||
m_running = true;
|
||||
for( int i=0; i<6; ++i )
|
||||
{
|
||||
@@ -312,7 +353,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
|
||||
wrapper.detach();
|
||||
}
|
||||
}
|
||||
m_fileSystem = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
214
src/main/java/dan200/computercraft/core/apis/TableHelper.java
Normal file
214
src/main/java/dan200/computercraft/core/apis/TableHelper.java
Normal file
@@ -0,0 +1,214 @@
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Various helpers for tables
|
||||
*/
|
||||
public final class TableHelper
|
||||
{
|
||||
private TableHelper()
|
||||
{
|
||||
throw new IllegalStateException( "Cannot instantiate singleton " + getClass().getName() );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nullable Object actual )
|
||||
{
|
||||
return badKey( key, expected, ArgumentHelper.getType( actual ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nonnull String actual )
|
||||
{
|
||||
return new LuaException( "bad field '" + key + "' (" + expected + " expected, got " + actual + ")" );
|
||||
}
|
||||
|
||||
public static double getNumberField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
|
||||
{
|
||||
Object value = table.get( key );
|
||||
if( value instanceof Number )
|
||||
{
|
||||
return ((Number) value).doubleValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw badKey( key, "number", value );
|
||||
}
|
||||
}
|
||||
|
||||
public static int getIntField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
|
||||
{
|
||||
Object value = table.get( key );
|
||||
if( value instanceof Number )
|
||||
{
|
||||
return (int) ((Number) value).longValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw badKey( key, "number", value );
|
||||
}
|
||||
}
|
||||
|
||||
public static double getRealField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
|
||||
{
|
||||
return checkReal( key, getNumberField( table, key ) );
|
||||
}
|
||||
|
||||
public static boolean getBooleanField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
|
||||
{
|
||||
Object value = table.get( key );
|
||||
if( value instanceof Boolean )
|
||||
{
|
||||
return (Boolean) value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw badKey( key, "boolean", value );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static String getStringField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
|
||||
{
|
||||
Object value = table.get( key );
|
||||
if( value instanceof String )
|
||||
{
|
||||
return (String) value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw badKey( key, "string", value );
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings( "unchecked" )
|
||||
@Nonnull
|
||||
public static Map<Object, Object> getTableField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
|
||||
{
|
||||
Object value = table.get( key );
|
||||
if( value instanceof Map )
|
||||
{
|
||||
return (Map<Object, Object>) value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw badKey( key, "table", value );
|
||||
}
|
||||
}
|
||||
|
||||
public static double optNumberField( @Nonnull Map<?, ?> table, @Nonnull String key, double def ) throws LuaException
|
||||
{
|
||||
Object value = table.get( key );
|
||||
if( value == null )
|
||||
{
|
||||
return def;
|
||||
}
|
||||
else if( value instanceof Number )
|
||||
{
|
||||
return ((Number) value).doubleValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw badKey( key, "number", value );
|
||||
}
|
||||
}
|
||||
|
||||
public static int optIntField( @Nonnull Map<?, ?> table, @Nonnull String key, int def ) throws LuaException
|
||||
{
|
||||
Object value = table.get( key );
|
||||
if( value == null )
|
||||
{
|
||||
return def;
|
||||
}
|
||||
else if( value instanceof Number )
|
||||
{
|
||||
return (int) ((Number) value).longValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw badKey( key, "number", value );
|
||||
}
|
||||
}
|
||||
|
||||
public static double optRealField( @Nonnull Map<?, ?> table, @Nonnull String key, double def ) throws LuaException
|
||||
{
|
||||
return checkReal( key, optNumberField( table, key, def ) );
|
||||
}
|
||||
|
||||
public static boolean optBooleanField( @Nonnull Map<?, ?> table, @Nonnull String key, boolean def ) throws LuaException
|
||||
{
|
||||
Object value = table.get( key );
|
||||
if( value == null )
|
||||
{
|
||||
return def;
|
||||
}
|
||||
else if( value instanceof Boolean )
|
||||
{
|
||||
return (Boolean) value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw badKey( key, "boolean", value );
|
||||
}
|
||||
}
|
||||
|
||||
public static String optStringField( @Nonnull Map<?, ?> table, @Nonnull String key, String def ) throws LuaException
|
||||
{
|
||||
Object value = table.get( key );
|
||||
if( value == null )
|
||||
{
|
||||
return def;
|
||||
}
|
||||
else if( value instanceof String )
|
||||
{
|
||||
return (String) value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw badKey( key, "string", value );
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings( "unchecked" )
|
||||
public static Map<Object, Object> optTableField( @Nonnull Map<?, ?> table, @Nonnull String key, Map<Object, Object> def ) throws LuaException
|
||||
{
|
||||
Object value = table.get( key );
|
||||
if( value == null )
|
||||
{
|
||||
return def;
|
||||
}
|
||||
else if( value instanceof Map )
|
||||
{
|
||||
return (Map<Object, Object>) value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw badKey( key, "table", value );
|
||||
}
|
||||
}
|
||||
|
||||
private static double checkReal( @Nonnull String key, double value ) throws LuaException
|
||||
{
|
||||
if( Double.isNaN( value ) )
|
||||
{
|
||||
throw badKey( key, "number", "nan" );
|
||||
}
|
||||
else if( value == Double.POSITIVE_INFINITY )
|
||||
{
|
||||
throw badKey( key, "number", "inf" );
|
||||
}
|
||||
else if( value == Double.NEGATIVE_INFINITY )
|
||||
{
|
||||
throw badKey( key, "number", "-inf" );
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
@@ -64,7 +64,8 @@ public class TermAPI implements ILuaAPI
|
||||
"setPaletteColour",
|
||||
"setPaletteColor",
|
||||
"getPaletteColour",
|
||||
"getPaletteColor"
|
||||
"getPaletteColor",
|
||||
"getCursorBlink",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -284,6 +285,9 @@ public class TermAPI implements ILuaAPI
|
||||
}
|
||||
return null;
|
||||
}
|
||||
case 23:
|
||||
// getCursorBlink
|
||||
return new Object[] { m_terminal.getCursorBlink() };
|
||||
default:
|
||||
{
|
||||
return null;
|
||||
|
@@ -0,0 +1,91 @@
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.NonWritableChannelException;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
|
||||
/**
|
||||
* A seekable, readable byte channel which is backed by a simple byte array.
|
||||
*/
|
||||
public class ArrayByteChannel implements SeekableByteChannel
|
||||
{
|
||||
private boolean closed = false;
|
||||
private int position = 0;
|
||||
|
||||
private final byte[] backing;
|
||||
|
||||
public ArrayByteChannel( byte[] backing )
|
||||
{
|
||||
this.backing = backing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read( ByteBuffer destination ) throws IOException
|
||||
{
|
||||
if( closed ) throw new ClosedChannelException();
|
||||
Preconditions.checkNotNull( destination, "destination" );
|
||||
|
||||
if( position >= backing.length ) return -1;
|
||||
|
||||
int remaining = Math.min( backing.length - position, destination.remaining() );
|
||||
destination.put( backing, position, remaining );
|
||||
position += remaining;
|
||||
return remaining;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write( ByteBuffer src ) throws IOException
|
||||
{
|
||||
if( closed ) throw new ClosedChannelException();
|
||||
throw new NonWritableChannelException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long position() throws IOException
|
||||
{
|
||||
if( closed ) throw new ClosedChannelException();
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel position( long newPosition ) throws IOException
|
||||
{
|
||||
if( closed ) throw new ClosedChannelException();
|
||||
if( newPosition < 0 || newPosition > Integer.MAX_VALUE )
|
||||
{
|
||||
throw new IllegalArgumentException( "Position out of bounds" );
|
||||
}
|
||||
position = (int) newPosition;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws IOException
|
||||
{
|
||||
if( closed ) throw new ClosedChannelException();
|
||||
return backing.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel truncate( long size ) throws IOException
|
||||
{
|
||||
if( closed ) throw new ClosedChannelException();
|
||||
throw new NonWritableChannelException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen()
|
||||
{
|
||||
return !closed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
closed = true;
|
||||
}
|
||||
}
|
@@ -1,89 +0,0 @@
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
|
||||
|
||||
public class BinaryInputHandle extends HandleGeneric
|
||||
{
|
||||
private final InputStream m_stream;
|
||||
|
||||
public BinaryInputHandle( InputStream reader )
|
||||
{
|
||||
super( reader );
|
||||
this.m_stream = reader;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[] {
|
||||
"read",
|
||||
"readAll",
|
||||
"close",
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
{
|
||||
switch( method )
|
||||
{
|
||||
case 0:
|
||||
// read
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
if( args.length > 0 && args[ 0 ] != null )
|
||||
{
|
||||
int count = getInt( args, 0 );
|
||||
if( count <= 0 || count >= 1024 * 16 )
|
||||
{
|
||||
throw new LuaException( "Count out of range" );
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[ count ];
|
||||
count = m_stream.read( bytes );
|
||||
if( count < 0 ) return null;
|
||||
if( count < bytes.length ) bytes = Arrays.copyOf( bytes, count );
|
||||
return new Object[] { bytes };
|
||||
}
|
||||
else
|
||||
{
|
||||
int b = m_stream.read();
|
||||
return b == -1 ? null : new Object[] { b };
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 1:
|
||||
// readAll
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
byte[] out = ByteStreams.toByteArray( m_stream );
|
||||
return out == null ? null : new Object[] { out };
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 2:
|
||||
//close
|
||||
close();
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,205 @@
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import com.google.common.collect.ObjectArrays;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
|
||||
|
||||
public class BinaryReadableHandle extends HandleGeneric
|
||||
{
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
private static final String[] METHOD_NAMES = new String[] { "read", "readAll", "readLine", "close" };
|
||||
private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
|
||||
|
||||
private final ReadableByteChannel m_reader;
|
||||
private final SeekableByteChannel m_seekable;
|
||||
private final ByteBuffer single = ByteBuffer.allocate( 1 );
|
||||
|
||||
public BinaryReadableHandle( ReadableByteChannel channel, Closeable closeable )
|
||||
{
|
||||
super( closeable );
|
||||
this.m_reader = channel;
|
||||
this.m_seekable = channel instanceof SeekableByteChannel ? (SeekableByteChannel) channel : null;
|
||||
}
|
||||
|
||||
public BinaryReadableHandle( ReadableByteChannel channel )
|
||||
{
|
||||
this( channel, channel );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
{
|
||||
switch( method )
|
||||
{
|
||||
case 0:
|
||||
// read
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
if( args.length > 0 && args[ 0 ] != null )
|
||||
{
|
||||
int count = getInt( args, 0 );
|
||||
if( count < 0 )
|
||||
{
|
||||
throw new LuaException( "Cannot read a negative number of bytes" );
|
||||
}
|
||||
else if( count == 0 && m_seekable != null )
|
||||
{
|
||||
return m_seekable.position() >= m_seekable.size() ? null : new Object[] { "" };
|
||||
}
|
||||
|
||||
if( count <= BUFFER_SIZE )
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocate( count );
|
||||
|
||||
int read = m_reader.read( buffer );
|
||||
if( read < 0 ) return null;
|
||||
return new Object[] { read < count ? Arrays.copyOf( buffer.array(), read ) : buffer.array() };
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
|
||||
|
||||
int read = m_reader.read( buffer );
|
||||
if( read < 0 ) return null;
|
||||
int totalRead = read;
|
||||
|
||||
// If we failed to read "enough" here, let's just abort
|
||||
if( totalRead >= count || read < BUFFER_SIZE )
|
||||
{
|
||||
return new Object[] { Arrays.copyOf( buffer.array(), read ) };
|
||||
}
|
||||
|
||||
// Build up an array of ByteBuffers. Hopefully this means we can perform less allocation
|
||||
// than doubling up the buffer each time.
|
||||
List<ByteBuffer> parts = new ArrayList<>( 4 );
|
||||
parts.add( buffer );
|
||||
while( totalRead < count && read >= BUFFER_SIZE )
|
||||
{
|
||||
buffer = ByteBuffer.allocate( BUFFER_SIZE );
|
||||
totalRead += read = m_reader.read( buffer );
|
||||
parts.add( buffer );
|
||||
}
|
||||
|
||||
// Now just copy all the bytes across!
|
||||
byte[] bytes = new byte[ totalRead ];
|
||||
int pos = 0;
|
||||
for( ByteBuffer part : parts )
|
||||
{
|
||||
System.arraycopy( part.array(), 0, bytes, pos, part.position() );
|
||||
pos += part.position();
|
||||
}
|
||||
return new Object[] { bytes };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
single.clear();
|
||||
int b = m_reader.read( single );
|
||||
return b == -1 ? null : new Object[] { single.get( 0 ) };
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 1:
|
||||
// readAll
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
int expected = 32;
|
||||
if( m_seekable != null )
|
||||
{
|
||||
expected = Math.max( expected, (int) (m_seekable.size() - m_seekable.position()) );
|
||||
}
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream( expected );
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate( 8192 );
|
||||
boolean readAnything = false;
|
||||
while( true )
|
||||
{
|
||||
buf.clear();
|
||||
int r = m_reader.read( buf );
|
||||
if( r == -1 ) break;
|
||||
|
||||
readAnything = true;
|
||||
stream.write( buf.array(), 0, r );
|
||||
}
|
||||
return readAnything ? new Object[] { stream.toByteArray() } : null;
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// readLine
|
||||
checkOpen();
|
||||
boolean withTrailing = optBoolean( args, 0, false );
|
||||
try
|
||||
{
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
boolean readAnything = false;
|
||||
while( true )
|
||||
{
|
||||
single.clear();
|
||||
int r = m_reader.read( single );
|
||||
if( r == -1 ) break;
|
||||
|
||||
readAnything = true;
|
||||
byte b = single.get( 0 );
|
||||
if( b == '\n' )
|
||||
{
|
||||
if( withTrailing ) stream.write( b );
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.write( b );
|
||||
}
|
||||
}
|
||||
|
||||
return readAnything ? new Object[] { stream.toByteArray() } : null;
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
//close
|
||||
close();
|
||||
return null;
|
||||
case 4:
|
||||
// seek
|
||||
checkOpen();
|
||||
return handleSeek( m_seekable, args );
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,33 +1,45 @@
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import com.google.common.collect.ObjectArrays;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.ArgumentHelper;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
public class BinaryOutputHandle extends HandleGeneric
|
||||
public class BinaryWritableHandle extends HandleGeneric
|
||||
{
|
||||
private final OutputStream m_writer;
|
||||
private static final String[] METHOD_NAMES = new String[] { "write", "flush", "close" };
|
||||
private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
|
||||
|
||||
public BinaryOutputHandle( OutputStream writer )
|
||||
private final WritableByteChannel m_writer;
|
||||
private final SeekableByteChannel m_seekable;
|
||||
private final ByteBuffer single = ByteBuffer.allocate( 1 );
|
||||
|
||||
public BinaryWritableHandle( WritableByteChannel channel, Closeable closeable )
|
||||
{
|
||||
super( writer );
|
||||
this.m_writer = writer;
|
||||
super( closeable );
|
||||
this.m_writer = channel;
|
||||
this.m_seekable = channel instanceof SeekableByteChannel ? (SeekableByteChannel) channel : null;
|
||||
}
|
||||
|
||||
public BinaryWritableHandle( WritableByteChannel channel )
|
||||
{
|
||||
this( channel, channel );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[] {
|
||||
"write",
|
||||
"flush",
|
||||
"close",
|
||||
};
|
||||
return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,12 +55,16 @@ public class BinaryOutputHandle extends HandleGeneric
|
||||
if( args.length > 0 && args[ 0 ] instanceof Number )
|
||||
{
|
||||
int number = ((Number) args[ 0 ]).intValue();
|
||||
m_writer.write( number );
|
||||
single.clear();
|
||||
single.put( (byte) number );
|
||||
single.flip();
|
||||
|
||||
m_writer.write( single );
|
||||
}
|
||||
else if( args.length > 0 && args[ 0 ] instanceof String )
|
||||
{
|
||||
String value = (String) args[ 0 ];
|
||||
m_writer.write( StringUtil.encodeString( value ) );
|
||||
m_writer.write( ByteBuffer.wrap( StringUtil.encodeString( value ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -65,7 +81,9 @@ public class BinaryOutputHandle extends HandleGeneric
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
m_writer.flush();
|
||||
// Technically this is not needed
|
||||
if( m_writer instanceof FileChannel ) ((FileChannel) m_writer).force( false );
|
||||
|
||||
return null;
|
||||
}
|
||||
catch( IOException e )
|
||||
@@ -76,6 +94,10 @@ public class BinaryOutputHandle extends HandleGeneric
|
||||
//close
|
||||
close();
|
||||
return null;
|
||||
case 3:
|
||||
// seek
|
||||
checkOpen();
|
||||
return handleSeek( m_seekable, args );
|
||||
default:
|
||||
return null;
|
||||
}
|
@@ -1,132 +0,0 @@
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.*;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.*;
|
||||
|
||||
public class EncodedInputHandle extends HandleGeneric
|
||||
{
|
||||
private final BufferedReader m_reader;
|
||||
|
||||
public EncodedInputHandle( BufferedReader reader )
|
||||
{
|
||||
super( reader );
|
||||
this.m_reader = reader;
|
||||
}
|
||||
|
||||
public EncodedInputHandle( InputStream stream )
|
||||
{
|
||||
this( stream, "UTF-8" );
|
||||
}
|
||||
|
||||
public EncodedInputHandle( InputStream stream, String encoding )
|
||||
{
|
||||
this( makeReader( stream, encoding ) );
|
||||
}
|
||||
|
||||
private static BufferedReader makeReader( InputStream stream, String encoding )
|
||||
{
|
||||
if( encoding == null ) encoding = "UTF-8";
|
||||
InputStreamReader streamReader;
|
||||
try
|
||||
{
|
||||
streamReader = new InputStreamReader( stream, encoding );
|
||||
}
|
||||
catch( UnsupportedEncodingException e )
|
||||
{
|
||||
streamReader = new InputStreamReader( stream );
|
||||
}
|
||||
return new BufferedReader( streamReader );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[] {
|
||||
"readLine",
|
||||
"readAll",
|
||||
"close",
|
||||
"read",
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
{
|
||||
switch( method )
|
||||
{
|
||||
case 0:
|
||||
// readLine
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
String line = m_reader.readLine();
|
||||
if( line != null )
|
||||
{
|
||||
return new Object[] { line };
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 1:
|
||||
// readAll
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
StringBuilder result = new StringBuilder( "" );
|
||||
String line = m_reader.readLine();
|
||||
while( line != null )
|
||||
{
|
||||
result.append( line );
|
||||
line = m_reader.readLine();
|
||||
if( line != null )
|
||||
{
|
||||
result.append( "\n" );
|
||||
}
|
||||
}
|
||||
return new Object[] { result.toString() };
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 2:
|
||||
// close
|
||||
close();
|
||||
return null;
|
||||
case 3:
|
||||
// read
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
int count = optInt( args, 0, 1 );
|
||||
if( count <= 0 || count >= 1024 * 16 )
|
||||
{
|
||||
throw new LuaException( "Count out of range" );
|
||||
}
|
||||
char[] bytes = new char[ count ];
|
||||
count = m_reader.read( bytes );
|
||||
if( count < 0 ) return null;
|
||||
String str = new String( bytes, 0, count );
|
||||
return new Object[] { str };
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,165 @@
|
||||
package dan200.computercraft.core.apis.handles;
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
|
||||
|
||||
public class EncodedReadableHandle extends HandleGeneric
|
||||
{
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
private BufferedReader m_reader;
|
||||
|
||||
public EncodedReadableHandle( @Nonnull BufferedReader reader, @Nonnull Closeable closable )
|
||||
{
|
||||
super( closable );
|
||||
this.m_reader = reader;
|
||||
}
|
||||
|
||||
public EncodedReadableHandle( @Nonnull BufferedReader reader )
|
||||
{
|
||||
this( reader, reader );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[] {
|
||||
"readLine",
|
||||
"readAll",
|
||||
"close",
|
||||
"read",
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
{
|
||||
switch( method )
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// readLine
|
||||
checkOpen();
|
||||
boolean withTrailing = optBoolean( args, 0, false );
|
||||
try
|
||||
{
|
||||
String line = m_reader.readLine();
|
||||
if( line != null )
|
||||
{
|
||||
// While this is technically inaccurate, it's better than nothing
|
||||
if( withTrailing ) line += "\n";
|
||||
return new Object[] { line };
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
// readAll
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
StringBuilder result = new StringBuilder( "" );
|
||||
String line = m_reader.readLine();
|
||||
while( line != null )
|
||||
{
|
||||
result.append( line );
|
||||
line = m_reader.readLine();
|
||||
if( line != null )
|
||||
{
|
||||
result.append( "\n" );
|
||||
}
|
||||
}
|
||||
return new Object[] { result.toString() };
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
case 2:
|
||||
// close
|
||||
close();
|
||||
return null;
|
||||
case 3:
|
||||
checkOpen();
|
||||
try
|
||||
{
|
||||
int count = optInt( args, 0, 1 );
|
||||
if( count < 0 )
|
||||
{
|
||||
// Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so
|
||||
// it seems best to remain somewhat consistent.
|
||||
throw new LuaException( "Cannot read a negative number of characters" );
|
||||
}
|
||||
else if( count <= BUFFER_SIZE )
|
||||
{
|
||||
// If we've got a small count, then allocate that and read it.
|
||||
char[] chars = new char[ count ];
|
||||
int read = m_reader.read( chars );
|
||||
|
||||
return read < 0 ? null : new Object[] { new String( chars, 0, read ) };
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we've got a large count, read in bunches of 8192.
|
||||
char[] buffer = new char[ BUFFER_SIZE ];
|
||||
|
||||
// Read the initial set of characters, failing if none are read.
|
||||
int read = m_reader.read( buffer, 0, Math.min( buffer.length, count ) );
|
||||
if( read == -1 ) return null;
|
||||
|
||||
StringBuilder out = new StringBuilder( read );
|
||||
count -= read;
|
||||
out.append( buffer, 0, read );
|
||||
|
||||
// Otherwise read until we either reach the limit or we no longer consume
|
||||
// the full buffer.
|
||||
while( read >= BUFFER_SIZE && count > 0 )
|
||||
{
|
||||
read = m_reader.read( buffer, 0, Math.min( BUFFER_SIZE, count ) );
|
||||
if( read == -1 ) break;
|
||||
count -= read;
|
||||
out.append( buffer, 0, read );
|
||||
}
|
||||
|
||||
return new Object[] { out.toString() };
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static BufferedReader openUtf8( ReadableByteChannel channel )
|
||||
{
|
||||
return open( channel, StandardCharsets.UTF_8 );
|
||||
}
|
||||
|
||||
public static BufferedReader open( ReadableByteChannel channel, Charset charset )
|
||||
{
|
||||
return new BufferedReader( Channels.newReader( channel, charset.newDecoder(), -1 ) );
|
||||
}
|
||||
}
|
@@ -4,41 +4,27 @@ import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.*;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class EncodedOutputHandle extends HandleGeneric
|
||||
public class EncodedWritableHandle extends HandleGeneric
|
||||
{
|
||||
private final BufferedWriter m_writer;
|
||||
private BufferedWriter m_writer;
|
||||
|
||||
public EncodedOutputHandle( BufferedWriter writer )
|
||||
public EncodedWritableHandle( @Nonnull BufferedWriter writer, @Nonnull Closeable closable )
|
||||
{
|
||||
super( writer );
|
||||
super( closable );
|
||||
this.m_writer = writer;
|
||||
}
|
||||
|
||||
public EncodedOutputHandle( OutputStream stream )
|
||||
public EncodedWritableHandle( @Nonnull BufferedWriter writer )
|
||||
{
|
||||
this( stream, "UTF-8" );
|
||||
}
|
||||
|
||||
public EncodedOutputHandle( OutputStream stream, String encoding )
|
||||
{
|
||||
this( makeWriter( stream, encoding ) );
|
||||
}
|
||||
|
||||
private static BufferedWriter makeWriter( OutputStream stream, String encoding )
|
||||
{
|
||||
if( encoding == null ) encoding = "UTF-8";
|
||||
OutputStreamWriter streamWriter;
|
||||
try
|
||||
{
|
||||
streamWriter = new OutputStreamWriter( stream, encoding );
|
||||
}
|
||||
catch( UnsupportedEncodingException e )
|
||||
{
|
||||
streamWriter = new OutputStreamWriter( stream );
|
||||
}
|
||||
return new BufferedWriter( streamWriter );
|
||||
this( writer, writer );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@@ -125,4 +111,15 @@ public class EncodedOutputHandle extends HandleGeneric
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static BufferedWriter openUtf8( WritableByteChannel channel )
|
||||
{
|
||||
return open( channel, StandardCharsets.UTF_8 );
|
||||
}
|
||||
|
||||
public static BufferedWriter open( WritableByteChannel channel, Charset charset )
|
||||
{
|
||||
return new BufferedWriter( Channels.newWriter( channel, charset.newEncoder(), -1 ) );
|
||||
}
|
||||
}
|
@@ -3,17 +3,22 @@ package dan200.computercraft.core.apis.handles;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optString;
|
||||
|
||||
public abstract class HandleGeneric implements ILuaObject
|
||||
{
|
||||
protected final Closeable m_closable;
|
||||
protected boolean m_open = true;
|
||||
private Closeable m_closable;
|
||||
private boolean m_open = true;
|
||||
|
||||
public HandleGeneric( Closeable m_closable )
|
||||
protected HandleGeneric( @Nonnull Closeable closable )
|
||||
{
|
||||
this.m_closable = m_closable;
|
||||
this.m_closable = closable;
|
||||
}
|
||||
|
||||
protected void checkOpen() throws LuaException
|
||||
@@ -21,7 +26,7 @@ public abstract class HandleGeneric implements ILuaObject
|
||||
if( !m_open ) throw new LuaException( "attempt to use a closed file" );
|
||||
}
|
||||
|
||||
protected void close()
|
||||
protected final void close()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -31,5 +36,48 @@ public abstract class HandleGeneric implements ILuaObject
|
||||
catch( IOException ignored )
|
||||
{
|
||||
}
|
||||
m_closable = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared implementation for various file handle types
|
||||
*
|
||||
* @param channel The channel to seek in
|
||||
* @param args The Lua arguments to process, like Lua's {@code file:seek}.
|
||||
* @return The new position of the file, or null if some error occured.
|
||||
* @throws LuaException If the arguments were invalid
|
||||
* @see <a href="https://www.lua.org/manual/5.1/manual.html#pdf-file:seek">{@code file:seek} in the Lua manual.</a>
|
||||
*/
|
||||
protected static Object[] handleSeek( SeekableByteChannel channel, Object[] args ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
String whence = optString( args, 0, "cur" );
|
||||
long offset = optInt( args, 1, 0 );
|
||||
switch( whence )
|
||||
{
|
||||
case "set":
|
||||
channel.position( offset );
|
||||
break;
|
||||
case "cur":
|
||||
channel.position( channel.position() + offset );
|
||||
break;
|
||||
case "end":
|
||||
channel.position( channel.size() + offset );
|
||||
break;
|
||||
default:
|
||||
throw new LuaException( "bad argument #1 to 'seek' (invalid option '" + whence + "'" );
|
||||
}
|
||||
|
||||
return new Object[] { channel.position() };
|
||||
}
|
||||
catch( IllegalArgumentException e )
|
||||
{
|
||||
return new Object[] { false, "Position is negative" };
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,12 +13,17 @@ import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedInputHandle;
|
||||
import dan200.computercraft.core.apis.handles.ArrayByteChannel;
|
||||
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
|
||||
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -78,8 +83,10 @@ public class HTTPRequest implements Runnable
|
||||
private final String m_postText;
|
||||
private final Map<String, String> m_headers;
|
||||
private boolean m_binary;
|
||||
private final String m_method;
|
||||
private final boolean m_followRedirects;
|
||||
|
||||
public HTTPRequest( IAPIEnvironment environment, String urlString, URL url, final String postText, final Map<String, String> headers, boolean binary ) throws HTTPRequestException
|
||||
public HTTPRequest( IAPIEnvironment environment, String urlString, URL url, final String postText, final Map<String, String> headers, boolean binary, final String method, final boolean followRedirects ) throws HTTPRequestException
|
||||
{
|
||||
m_environment = environment;
|
||||
m_urlString = urlString;
|
||||
@@ -87,6 +94,8 @@ public class HTTPRequest implements Runnable
|
||||
m_binary = binary;
|
||||
m_postText = postText;
|
||||
m_headers = headers;
|
||||
m_method = method;
|
||||
m_followRedirects = followRedirects;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,7 +110,7 @@ public class HTTPRequest implements Runnable
|
||||
{
|
||||
// Queue the failure event if not.
|
||||
String error = e.getMessage();
|
||||
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, error == null ? "Could not connect" : error, null } );
|
||||
m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, error == null ? "Could not connect" : error, null } );
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -119,6 +128,8 @@ public class HTTPRequest implements Runnable
|
||||
{
|
||||
connection.setRequestMethod( "GET" );
|
||||
}
|
||||
if( m_method != null ) connection.setRequestMethod( m_method );
|
||||
connection.setInstanceFollowRedirects( m_followRedirects );
|
||||
|
||||
// Set headers
|
||||
connection.setRequestProperty( "accept-charset", "UTF-8" );
|
||||
@@ -134,6 +145,11 @@ public class HTTPRequest implements Runnable
|
||||
}
|
||||
}
|
||||
|
||||
// Add request size and count to the tracker before opening the connection
|
||||
m_environment.addTrackingChange( TrackingField.HTTP_REQUESTS );
|
||||
m_environment.addTrackingChange( TrackingField.HTTP_UPLOAD,
|
||||
getHeaderSize( connection.getRequestProperties() ) + (m_postText == null ? 0 : m_postText.length()) );
|
||||
|
||||
// Send POST text
|
||||
if( m_postText != null )
|
||||
{
|
||||
@@ -170,6 +186,10 @@ public class HTTPRequest implements Runnable
|
||||
byte[] result = ByteStreams.toByteArray( is );
|
||||
is.close();
|
||||
|
||||
String encoding = connection.getContentEncoding();
|
||||
Charset charset = encoding != null && Charset.isSupported( encoding )
|
||||
? Charset.forName( encoding ) : StandardCharsets.UTF_8;
|
||||
|
||||
// We've got some sort of response, so let's build a resulting object.
|
||||
Joiner joiner = Joiner.on( ',' );
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
@@ -178,9 +198,14 @@ public class HTTPRequest implements Runnable
|
||||
headers.put( header.getKey(), joiner.join( header.getValue() ) );
|
||||
}
|
||||
|
||||
InputStream contents = new ByteArrayInputStream( result );
|
||||
m_environment.addTrackingChange( TrackingField.HTTP_DOWNLOAD,
|
||||
getHeaderSize( connection.getHeaderFields() ) + result.length );
|
||||
|
||||
SeekableByteChannel contents = new ArrayByteChannel( result );
|
||||
ILuaObject stream = wrapStream(
|
||||
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, connection.getContentEncoding() ),
|
||||
m_binary
|
||||
? new BinaryReadableHandle( contents )
|
||||
: new EncodedReadableHandle( EncodedReadableHandle.open( contents, charset ) ),
|
||||
connection.getResponseCode(), headers
|
||||
);
|
||||
|
||||
@@ -189,17 +214,17 @@ public class HTTPRequest implements Runnable
|
||||
// Queue the appropriate event.
|
||||
if( responseSuccess )
|
||||
{
|
||||
m_environment.queueEvent( "http_success", new Object[] { m_urlString, stream } );
|
||||
m_environment.queueEvent( "http_success", new Object[]{ m_urlString, stream } );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, "Could not connect", stream } );
|
||||
m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, "Could not connect", stream } );
|
||||
}
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
// There was an error
|
||||
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, "Could not connect", null } );
|
||||
m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, "Could not connect", null } );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,8 +234,8 @@ public class HTTPRequest implements Runnable
|
||||
final int methodOffset = oldMethods.length;
|
||||
|
||||
final String[] newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 );
|
||||
newMethods[ methodOffset + 0 ] = "getResponseCode";
|
||||
newMethods[ methodOffset + 1 ] = "getResponseHeaders";
|
||||
newMethods[methodOffset + 0] = "getResponseCode";
|
||||
newMethods[methodOffset + 1] = "getResponseHeaders";
|
||||
|
||||
return new ILuaObject()
|
||||
{
|
||||
@@ -233,12 +258,12 @@ public class HTTPRequest implements Runnable
|
||||
case 0:
|
||||
{
|
||||
// getResponseCode
|
||||
return new Object[] { responseCode };
|
||||
return new Object[]{ responseCode };
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
// getResponseHeaders
|
||||
return new Object[] { responseHeaders };
|
||||
return new Object[]{ responseHeaders };
|
||||
}
|
||||
default:
|
||||
{
|
||||
@@ -248,4 +273,15 @@ public class HTTPRequest implements Runnable
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static long getHeaderSize( Map<String, List<String>> headers )
|
||||
{
|
||||
long size = 0;
|
||||
for( Map.Entry<String, List<String>> header : headers.entrySet() )
|
||||
{
|
||||
size += header.getKey() == null ? 0 : header.getKey().length();
|
||||
for( String value : header.getValue() ) size += value == null ? 0 : value.length() + 1;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,10 @@ import dan200.computercraft.api.lua.ILuaObject;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.apis.HTTPAPI;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
@@ -25,6 +28,8 @@ import javax.annotation.Nullable;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean;
|
||||
|
||||
public class WebsocketConnection extends SimpleChannelInboundHandler<Object> implements ILuaObject, Closeable
|
||||
{
|
||||
public static final String SUCCESS_EVENT = "websocket_success";
|
||||
@@ -71,7 +76,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
|
||||
private void onClosed()
|
||||
{
|
||||
close( true );
|
||||
computer.queueEvent( CLOSE_EVENT, new Object[] { url } );
|
||||
computer.queueEvent( CLOSE_EVENT, new Object[]{ url } );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,7 +107,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
|
||||
if( !handshaker.isHandshakeComplete() )
|
||||
{
|
||||
handshaker.finishHandshake( ch, (FullHttpResponse) msg );
|
||||
computer.queueEvent( SUCCESS_EVENT, new Object[] { url, this } );
|
||||
computer.queueEvent( SUCCESS_EVENT, new Object[]{ url, this } );
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,14 +120,19 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
|
||||
WebSocketFrame frame = (WebSocketFrame) msg;
|
||||
if( frame instanceof TextWebSocketFrame )
|
||||
{
|
||||
computer.queueEvent( MESSAGE_EVENT, new Object[] { url, ((TextWebSocketFrame) frame).text() } );
|
||||
String data = ((TextWebSocketFrame) frame).text();
|
||||
|
||||
computer.addTrackingChange( TrackingField.WEBSOCKET_INCOMING, data.length() );
|
||||
computer.queueEvent( MESSAGE_EVENT, new Object[]{ url, data } );
|
||||
}
|
||||
else if( frame instanceof BinaryWebSocketFrame )
|
||||
{
|
||||
ByteBuf data = frame.content();
|
||||
byte[] converted = new byte[ data.readableBytes() ];
|
||||
byte[] converted = new byte[data.readableBytes()];
|
||||
data.readBytes( converted );
|
||||
computer.queueEvent( MESSAGE_EVENT, new Object[] { url, data } );
|
||||
|
||||
computer.addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length );
|
||||
computer.queueEvent( MESSAGE_EVENT, new Object[]{ url, converted } );
|
||||
}
|
||||
else if( frame instanceof CloseWebSocketFrame )
|
||||
{
|
||||
@@ -135,7 +145,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
|
||||
public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause )
|
||||
{
|
||||
ctx.close();
|
||||
computer.queueEvent( FAILURE_EVENT, new Object[] {
|
||||
computer.queueEvent( FAILURE_EVENT, new Object[]{
|
||||
url,
|
||||
cause instanceof WebSocketHandshakeException ? cause.getMessage() : "Could not connect"
|
||||
} );
|
||||
@@ -145,7 +155,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
return new String[] { "receive", "send", "close" };
|
||||
return new String[]{ "receive", "send", "close" };
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -159,16 +169,20 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
|
||||
{
|
||||
checkOpen();
|
||||
Object[] event = context.pullEvent( MESSAGE_EVENT );
|
||||
if( event.length >= 3 && Objects.equal( event[ 1 ], url ) )
|
||||
if( event.length >= 3 && Objects.equal( event[1], url ) )
|
||||
{
|
||||
return new Object[] { event[ 2 ] };
|
||||
return new Object[]{ event[2] };
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
checkOpen();
|
||||
String text = arguments.length > 0 && arguments[ 0 ] != null ? arguments[ 0 ].toString() : "";
|
||||
channel.writeAndFlush( new TextWebSocketFrame( text ) );
|
||||
String text = arguments.length > 0 && arguments[0] != null ? arguments[0].toString() : "";
|
||||
boolean binary = optBoolean(arguments, 1, false);
|
||||
computer.addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() );
|
||||
channel.writeAndFlush( binary
|
||||
? new BinaryWebSocketFrame( Unpooled.wrappedBuffer( StringUtil.encodeString( text ) ) )
|
||||
: new TextWebSocketFrame( text ) );
|
||||
return null;
|
||||
}
|
||||
case 2:
|
||||
|
@@ -21,6 +21,7 @@ import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
|
||||
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
|
||||
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
|
||||
@@ -34,7 +35,7 @@ import java.security.KeyStore;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/*
|
||||
/**
|
||||
* Provides functionality to verify and connect to a remote websocket.
|
||||
*/
|
||||
public final class WebsocketConnector
|
||||
@@ -177,7 +178,7 @@ public final class WebsocketConnector
|
||||
httpHeaders.add( header.getKey(), header.getValue() );
|
||||
}
|
||||
|
||||
WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, false, httpHeaders );
|
||||
WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, true, httpHeaders );
|
||||
final WebsocketConnection connection = new WebsocketConnection( environment, api, handshaker, address );
|
||||
|
||||
new Bootstrap()
|
||||
@@ -190,7 +191,12 @@ public final class WebsocketConnector
|
||||
{
|
||||
ChannelPipeline p = ch.pipeline();
|
||||
if( ssl != null ) p.addLast( ssl.newHandler( ch.alloc(), uri.getHost(), port ) );
|
||||
p.addLast( new HttpClientCodec(), new HttpObjectAggregator( 8192 ), connection );
|
||||
p.addLast(
|
||||
new HttpClientCodec(),
|
||||
new HttpObjectAggregator( 8192 ),
|
||||
WebSocketClientCompressionHandler.INSTANCE,
|
||||
connection
|
||||
);
|
||||
}
|
||||
} )
|
||||
.remoteAddress( socketAddress )
|
||||
|
@@ -12,6 +12,7 @@ import dan200.computercraft.api.filesystem.IFileSystem;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.apis.*;
|
||||
import dan200.computercraft.core.filesystem.FileSystem;
|
||||
@@ -19,6 +20,8 @@ import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
import dan200.computercraft.core.lua.CobaltLuaMachine;
|
||||
import dan200.computercraft.core.lua.ILuaMachine;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.tracking.Tracking;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -166,6 +169,12 @@ public class Computer
|
||||
m_computer.setLabel( label );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTrackingChange( TrackingField field, long change )
|
||||
{
|
||||
Tracking.addValue( m_computer, field, change );
|
||||
}
|
||||
|
||||
public void onPeripheralChanged( int side, IPeripheral peripheral )
|
||||
{
|
||||
synchronized( m_computer.m_peripherals )
|
||||
@@ -665,6 +674,11 @@ public class Computer
|
||||
{
|
||||
m_apis.add( api );
|
||||
}
|
||||
|
||||
public void addAPI( dan200.computercraft.core.apis.ILuaAPI api )
|
||||
{
|
||||
addAPI( (ILuaAPI) api );
|
||||
}
|
||||
|
||||
public void setPeripheral( int side, IPeripheral peripheral )
|
||||
{
|
||||
|
@@ -7,6 +7,7 @@
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.core.tracking.Tracking;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -68,17 +69,17 @@ public class ComputerThread
|
||||
s_stopped = false;
|
||||
if( s_threads == null || s_threads.length != ComputerCraft.computer_threads )
|
||||
{
|
||||
s_threads = new Thread[ ComputerCraft.computer_threads ];
|
||||
s_threads = new Thread[ComputerCraft.computer_threads];
|
||||
}
|
||||
|
||||
SecurityManager manager = System.getSecurityManager();
|
||||
final ThreadGroup group = manager == null ? Thread.currentThread().getThreadGroup() : manager.getThreadGroup();
|
||||
for( int i = 0; i < s_threads.length; i++ )
|
||||
{
|
||||
Thread thread = s_threads[ i ];
|
||||
Thread thread = s_threads[i];
|
||||
if( thread == null || !thread.isAlive() )
|
||||
{
|
||||
thread = s_threads[ i ] = new Thread( group, new TaskExecutor(), "ComputerCraft-Computer-Manager-" + s_ManagerCounter.getAndIncrement() );
|
||||
thread = s_threads[i] = new Thread( group, new TaskExecutor(), "ComputerCraft-Computer-Manager-" + s_ManagerCounter.getAndIncrement() );
|
||||
thread.setDaemon( true );
|
||||
thread.start();
|
||||
}
|
||||
@@ -195,6 +196,8 @@ public class ComputerThread
|
||||
thread.start();
|
||||
}
|
||||
|
||||
long start = System.nanoTime();
|
||||
|
||||
// Execute the task
|
||||
runner.submit( task );
|
||||
|
||||
@@ -221,6 +224,23 @@ public class ComputerThread
|
||||
// Interrupt the thread
|
||||
if( !done )
|
||||
{
|
||||
StringBuilder builder = new StringBuilder( "Terminating " );
|
||||
if( computer != null )
|
||||
{
|
||||
builder.append( "computer " ).append( computer.getID() );
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.append( "unknown computer" );
|
||||
}
|
||||
|
||||
builder.append( ". Thread is currently running" );
|
||||
for( StackTraceElement element : thread.getStackTrace() )
|
||||
{
|
||||
builder.append( "\n at " ).append( element );
|
||||
}
|
||||
ComputerCraft.log.error( builder.toString() );
|
||||
|
||||
thread.interrupt();
|
||||
thread = null;
|
||||
runner = null;
|
||||
@@ -229,6 +249,10 @@ public class ComputerThread
|
||||
}
|
||||
finally
|
||||
{
|
||||
long stop = System.nanoTime();
|
||||
Computer computer = task.getOwner();
|
||||
if( computer != null ) Tracking.addTaskTiming( computer, stop - start );
|
||||
|
||||
// Re-add it back onto the queue or remove it
|
||||
synchronized( s_taskLock )
|
||||
{
|
||||
|
@@ -0,0 +1,9 @@
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface IComputerOwned
|
||||
{
|
||||
@Nullable
|
||||
Computer getComputer();
|
||||
}
|
@@ -6,14 +6,17 @@
|
||||
|
||||
package dan200.computercraft.core.computer;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import dan200.computercraft.core.tracking.Tracking;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
|
||||
public class MainThread
|
||||
{
|
||||
private static final int MAX_TASKS_PER_TICK = 1000;
|
||||
private static final int MAX_TASKS_TOTAL = 50000;
|
||||
|
||||
private static final LinkedList<ITask> m_outstandingTasks = new LinkedList<>();
|
||||
private static final Queue<ITask> m_outstandingTasks = new ArrayDeque<>();
|
||||
private static final Object m_nextUnusedTaskIDLock = new Object();
|
||||
private static long m_nextUnusedTaskID = 0;
|
||||
|
||||
@@ -31,7 +34,7 @@ public class MainThread
|
||||
{
|
||||
if( m_outstandingTasks.size() < MAX_TASKS_TOTAL )
|
||||
{
|
||||
m_outstandingTasks.addLast( task );
|
||||
m_outstandingTasks.offer( task );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -46,14 +49,17 @@ public class MainThread
|
||||
ITask task = null;
|
||||
synchronized( m_outstandingTasks )
|
||||
{
|
||||
if( m_outstandingTasks.size() > 0 )
|
||||
{
|
||||
task = m_outstandingTasks.removeFirst();
|
||||
}
|
||||
task = m_outstandingTasks.poll();
|
||||
}
|
||||
if( task != null )
|
||||
{
|
||||
long start = System.nanoTime();
|
||||
task.execute();
|
||||
|
||||
long stop = System.nanoTime();
|
||||
Computer computer = task.getOwner();
|
||||
if( computer != null ) Tracking.addServerTiming( computer, stop - start );
|
||||
|
||||
++tasksThisTick;
|
||||
}
|
||||
else
|
||||
|
@@ -11,6 +11,7 @@ import dan200.computercraft.api.filesystem.IMount;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -114,6 +115,7 @@ public class ComboMount implements IMount
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
for( int i=m_parts.length-1; i>=0; --i )
|
||||
@@ -126,4 +128,19 @@ public class ComboMount implements IMount
|
||||
}
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
for( int i=m_parts.length-1; i>=0; --i )
|
||||
{
|
||||
IMount part = m_parts[i];
|
||||
if( part.exists( path ) && !part.isDirectory( path ) )
|
||||
{
|
||||
return part.openChannelForRead( path );
|
||||
}
|
||||
}
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
}
|
||||
|
@@ -9,34 +9,36 @@ package dan200.computercraft.core.filesystem;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.List;
|
||||
|
||||
public class EmptyMount implements IMount
|
||||
{
|
||||
{
|
||||
public EmptyMount()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// IMount implementation
|
||||
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path )
|
||||
{
|
||||
return path.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path )
|
||||
{
|
||||
return path.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void list( @Nonnull String path, @Nonnull List<String> contents )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getSize( @Nonnull String path )
|
||||
{
|
||||
@@ -45,8 +47,17 @@ public class EmptyMount implements IMount
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InputStream openForRead( @Nonnull String path )
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return null;
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
}
|
||||
|
@@ -10,64 +10,43 @@ import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.List;
|
||||
|
||||
public class FileMount implements IWritableMount
|
||||
{
|
||||
private static int MINIMUM_FILE_SIZE = 500;
|
||||
|
||||
private class CountingOutputStream extends OutputStream
|
||||
private static final int MINIMUM_FILE_SIZE = 500;
|
||||
|
||||
private class WritableCountingChannel implements WritableByteChannel
|
||||
{
|
||||
private OutputStream m_innerStream;
|
||||
private long m_ignoredBytesLeft;
|
||||
|
||||
public CountingOutputStream( OutputStream innerStream, long bytesToIgnore )
|
||||
|
||||
private final WritableByteChannel m_inner;
|
||||
long m_ignoredBytesLeft;
|
||||
|
||||
WritableCountingChannel( WritableByteChannel inner, long bytesToIgnore )
|
||||
{
|
||||
m_innerStream = innerStream;
|
||||
m_inner = inner;
|
||||
m_ignoredBytesLeft = bytesToIgnore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
m_innerStream.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException
|
||||
{
|
||||
m_innerStream.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write( @Nonnull byte[] b ) throws IOException
|
||||
{
|
||||
count( b.length );
|
||||
m_innerStream.write( b );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write( @Nonnull byte[] b, int off, int len ) throws IOException
|
||||
{
|
||||
count( len );
|
||||
m_innerStream.write( b, off, len );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write( int b ) throws IOException
|
||||
public int write( @Nonnull ByteBuffer b ) throws IOException
|
||||
{
|
||||
count( 1 );
|
||||
m_innerStream.write( b );
|
||||
count( b.remaining() );
|
||||
return m_inner.write( b );
|
||||
}
|
||||
|
||||
private void count( long n ) throws IOException
|
||||
void count( long n ) throws IOException
|
||||
{
|
||||
m_ignoredBytesLeft -= n;
|
||||
if( m_ignoredBytesLeft < 0 )
|
||||
{
|
||||
long newBytes = -m_ignoredBytesLeft;
|
||||
m_ignoredBytesLeft = 0;
|
||||
|
||||
|
||||
long bytesLeft = m_capacity - m_usedSpace;
|
||||
if( newBytes > bytesLeft )
|
||||
{
|
||||
@@ -79,12 +58,79 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen()
|
||||
{
|
||||
return m_inner.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
m_inner.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class SeekableCountingChannel extends WritableCountingChannel implements SeekableByteChannel
|
||||
{
|
||||
private final SeekableByteChannel m_inner;
|
||||
|
||||
SeekableCountingChannel( SeekableByteChannel inner, long bytesToIgnore )
|
||||
{
|
||||
super( inner, bytesToIgnore );
|
||||
this.m_inner = inner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel position( long newPosition ) throws IOException
|
||||
{
|
||||
if( !isOpen() ) throw new ClosedChannelException();
|
||||
if( newPosition < 0 ) throw new IllegalArgumentException();
|
||||
|
||||
long delta = newPosition - m_inner.position();
|
||||
if( delta < 0 )
|
||||
{
|
||||
m_ignoredBytesLeft -= delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
count( delta );
|
||||
}
|
||||
|
||||
return m_inner.position( newPosition );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel truncate( long size ) throws IOException
|
||||
{
|
||||
throw new IOException( "Not yet implemented" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read( ByteBuffer dst ) throws IOException
|
||||
{
|
||||
if( !m_inner.isOpen() ) throw new ClosedChannelException();
|
||||
throw new NonReadableChannelException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long position() throws IOException
|
||||
{
|
||||
return m_inner.position();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws IOException
|
||||
{
|
||||
return m_inner.size();
|
||||
}
|
||||
}
|
||||
|
||||
private File m_rootPath;
|
||||
private long m_capacity;
|
||||
private long m_usedSpace;
|
||||
|
||||
|
||||
public FileMount( File rootPath, long capacity )
|
||||
{
|
||||
m_rootPath = rootPath;
|
||||
@@ -93,7 +139,7 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
|
||||
// IMount implementation
|
||||
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path )
|
||||
{
|
||||
@@ -107,7 +153,7 @@ public class FileMount implements IWritableMount
|
||||
return file.exists();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path )
|
||||
{
|
||||
@@ -121,7 +167,7 @@ public class FileMount implements IWritableMount
|
||||
return file.exists() && file.isDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
|
||||
{
|
||||
@@ -150,7 +196,7 @@ public class FileMount implements IWritableMount
|
||||
{
|
||||
throw new IOException( "/" + path + ": Not a directory" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -180,9 +226,10 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
if( created() )
|
||||
@@ -193,11 +240,26 @@ public class FileMount implements IWritableMount
|
||||
return new FileInputStream( file );
|
||||
}
|
||||
}
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
if( created() )
|
||||
{
|
||||
File file = getRealPath( path );
|
||||
if( file.exists() && !file.isDirectory() )
|
||||
{
|
||||
return FileChannel.open( file.toPath(), StandardOpenOption.READ );
|
||||
}
|
||||
}
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
|
||||
// IWritableMount implementation
|
||||
|
||||
|
||||
@Override
|
||||
public void makeDirectory( @Nonnull String path ) throws IOException
|
||||
{
|
||||
@@ -224,7 +286,7 @@ public class FileMount implements IWritableMount
|
||||
{
|
||||
throw new IOException( "/" + path + ": Out of space" );
|
||||
}
|
||||
|
||||
|
||||
boolean success = file.mkdirs();
|
||||
if( success )
|
||||
{
|
||||
@@ -236,7 +298,7 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void delete( @Nonnull String path ) throws IOException
|
||||
{
|
||||
@@ -244,7 +306,7 @@ public class FileMount implements IWritableMount
|
||||
{
|
||||
throw new IOException( "/" + path + ": Access denied" );
|
||||
}
|
||||
|
||||
|
||||
if( created() )
|
||||
{
|
||||
File file = getRealPath( path );
|
||||
@@ -254,7 +316,7 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void deleteRecursively( File file ) throws IOException
|
||||
{
|
||||
// Empty directories first
|
||||
@@ -266,7 +328,7 @@ public class FileMount implements IWritableMount
|
||||
deleteRecursively( new File( file, aChildren ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Then delete
|
||||
long fileSize = file.isDirectory() ? 0 : file.length();
|
||||
boolean success = file.delete();
|
||||
@@ -279,10 +341,26 @@ public class FileMount implements IWritableMount
|
||||
throw new IOException( "Access denied" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public OutputStream openForWrite( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return Channels.newOutputStream( openChannelForWrite( path ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public OutputStream openForAppend( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return Channels.newOutputStream( openChannelForAppend( path ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException
|
||||
{
|
||||
create();
|
||||
File file = getRealPath( path );
|
||||
@@ -308,13 +386,14 @@ public class FileMount implements IWritableMount
|
||||
m_usedSpace -= Math.max( file.length(), MINIMUM_FILE_SIZE );
|
||||
m_usedSpace += MINIMUM_FILE_SIZE;
|
||||
}
|
||||
return new CountingOutputStream( new FileOutputStream( file, false ), MINIMUM_FILE_SIZE );
|
||||
return new SeekableCountingChannel( Files.newByteChannel( file.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE ),
|
||||
MINIMUM_FILE_SIZE );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public OutputStream openForAppend( @Nonnull String path ) throws IOException
|
||||
public WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException
|
||||
{
|
||||
if( created() )
|
||||
{
|
||||
@@ -329,7 +408,9 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
else
|
||||
{
|
||||
return new CountingOutputStream( new FileOutputStream( file, true ), Math.max( MINIMUM_FILE_SIZE - file.length(), 0 ) );
|
||||
// Allowing seeking when appending is not recommended, so we use a separate channel.
|
||||
return new WritableCountingChannel( Files.newByteChannel( file.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND ),
|
||||
Math.max( MINIMUM_FILE_SIZE - file.length(), 0 ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -337,23 +418,23 @@ public class FileMount implements IWritableMount
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getRemainingSpace()
|
||||
{
|
||||
return Math.max( m_capacity - m_usedSpace, 0 );
|
||||
}
|
||||
|
||||
|
||||
public File getRealPath( String path )
|
||||
{
|
||||
return new File( m_rootPath, path );
|
||||
}
|
||||
|
||||
|
||||
private boolean created()
|
||||
{
|
||||
return m_rootPath.exists();
|
||||
}
|
||||
|
||||
|
||||
private void create() throws IOException
|
||||
{
|
||||
if( !m_rootPath.exists() )
|
||||
@@ -365,7 +446,7 @@ public class FileMount implements IWritableMount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private long measureUsedSpace( File file )
|
||||
{
|
||||
if( !file.exists() )
|
||||
|
@@ -6,13 +6,23 @@
|
||||
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.filesystem.IFileSystem;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
import dan200.computercraft.api.filesystem.IWritableMount;
|
||||
|
||||
import java.io.*;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FileSystem
|
||||
@@ -146,14 +156,14 @@ public class FileSystem
|
||||
}
|
||||
}
|
||||
|
||||
public InputStream openForRead( String path ) throws FileSystemException
|
||||
public ReadableByteChannel openForRead( String path ) throws FileSystemException
|
||||
{
|
||||
path = toLocal( path );
|
||||
try
|
||||
{
|
||||
if( m_mount.exists( path ) && !m_mount.isDirectory( path ) )
|
||||
{
|
||||
return m_mount.openForRead( path );
|
||||
return m_mount.openChannelForRead( path );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -209,13 +219,17 @@ public class FileSystem
|
||||
m_writableMount.delete( path );
|
||||
}
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new FileSystemException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
public OutputStream openForWrite( String path ) throws FileSystemException
|
||||
public WritableByteChannel openForWrite( String path ) throws FileSystemException
|
||||
{
|
||||
if( m_writableMount == null )
|
||||
{
|
||||
@@ -238,16 +252,20 @@ public class FileSystem
|
||||
m_writableMount.makeDirectory( dir );
|
||||
}
|
||||
}
|
||||
return m_writableMount.openForWrite( path );
|
||||
return m_writableMount.openChannelForWrite( path );
|
||||
}
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new FileSystemException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
public OutputStream openForAppend( String path ) throws FileSystemException
|
||||
public WritableByteChannel openForAppend( String path ) throws FileSystemException
|
||||
{
|
||||
if( m_writableMount == null )
|
||||
{
|
||||
@@ -266,7 +284,7 @@ public class FileSystem
|
||||
m_writableMount.makeDirectory( dir );
|
||||
}
|
||||
}
|
||||
return m_writableMount.openForWrite( path );
|
||||
return m_writableMount.openChannelForWrite( path );
|
||||
}
|
||||
else if( m_mount.isDirectory( path ) )
|
||||
{
|
||||
@@ -274,9 +292,13 @@ public class FileSystem
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_writableMount.openForAppend( path );
|
||||
return m_writableMount.openChannelForAppend( path );
|
||||
}
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new FileSystemException( e.getMessage() );
|
||||
@@ -291,10 +313,12 @@ public class FileSystem
|
||||
}
|
||||
}
|
||||
|
||||
private final FileSystemMount m_wrapper = new FileSystemMount( this );
|
||||
private final FileSystemWrapperMount m_wrapper = new FileSystemWrapperMount( this );
|
||||
private final Map<String, MountWrapper> m_mounts = new HashMap<>();
|
||||
private final Set<Closeable> m_openFiles = Collections.newSetFromMap( new WeakHashMap<Closeable, Boolean>() );
|
||||
|
||||
|
||||
private final HashMap<WeakReference<FileSystemWrapper<?>>, Closeable> m_openFiles = new HashMap<>();
|
||||
private final ReferenceQueue<FileSystemWrapper<?>> m_openFileQueue = new ReferenceQueue<>();
|
||||
|
||||
public FileSystem( String rootLabel, IMount rootMount ) throws FileSystemException
|
||||
{
|
||||
mount( rootLabel, "", rootMount );
|
||||
@@ -310,24 +334,15 @@ public class FileSystem
|
||||
// Close all dangling open files
|
||||
synchronized( m_openFiles )
|
||||
{
|
||||
for( Closeable file : m_openFiles )
|
||||
{
|
||||
try {
|
||||
file.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
for( Closeable file : m_openFiles.values() ) closeQuietly( file );
|
||||
m_openFiles.clear();
|
||||
while( m_openFileQueue.poll() != null ) ;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void mount( String label, String location, IMount mount ) throws FileSystemException
|
||||
{
|
||||
if( mount == null )
|
||||
{
|
||||
throw new NullPointerException();
|
||||
}
|
||||
if( mount == null ) throw new NullPointerException();
|
||||
location = sanitizePath( location );
|
||||
if( location.contains( ".." ) ) {
|
||||
throw new FileSystemException( "Cannot mount below the root" );
|
||||
@@ -348,24 +363,18 @@ public class FileSystem
|
||||
}
|
||||
mount( new MountWrapper( label, location, mount ) );
|
||||
}
|
||||
|
||||
|
||||
private synchronized void mount( MountWrapper wrapper )
|
||||
{
|
||||
String location = wrapper.getLocation();
|
||||
if( m_mounts.containsKey( location ) )
|
||||
{
|
||||
m_mounts.remove( location );
|
||||
}
|
||||
m_mounts.remove( location );
|
||||
m_mounts.put( location, wrapper );
|
||||
}
|
||||
|
||||
public synchronized void unmount( String path )
|
||||
{
|
||||
path = sanitizePath( path );
|
||||
if( m_mounts.containsKey( path ) )
|
||||
{
|
||||
m_mounts.remove( path );
|
||||
}
|
||||
m_mounts.remove( path );
|
||||
}
|
||||
|
||||
public synchronized String combine( String path, String childPath )
|
||||
@@ -599,108 +608,85 @@ public class FileSystem
|
||||
else
|
||||
{
|
||||
// Copy a file:
|
||||
InputStream source = null;
|
||||
OutputStream destination = null;
|
||||
try
|
||||
try( ReadableByteChannel source = sourceMount.openForRead( sourcePath );
|
||||
WritableByteChannel destination = destinationMount.openForWrite( destinationPath ) )
|
||||
{
|
||||
// Open both files
|
||||
source = sourceMount.openForRead( sourcePath );
|
||||
destination = destinationMount.openForWrite( destinationPath );
|
||||
|
||||
// Copy bytes as fast as we can
|
||||
byte[] buffer = new byte[1024];
|
||||
while( true )
|
||||
{
|
||||
int bytesRead = source.read( buffer );
|
||||
if( bytesRead >= 0 )
|
||||
{
|
||||
destination.write( buffer, 0, bytesRead );
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
ByteStreams.copy( source, destination );
|
||||
}
|
||||
catch( AccessDeniedException e )
|
||||
{
|
||||
throw new FileSystemException( "Access denied" );
|
||||
}
|
||||
catch( IOException e )
|
||||
{
|
||||
throw new FileSystemException( e.getMessage() );
|
||||
}
|
||||
finally
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanup()
|
||||
{
|
||||
synchronized( m_openFiles )
|
||||
{
|
||||
Reference<?> ref;
|
||||
while( (ref = m_openFileQueue.poll()) != null )
|
||||
{
|
||||
// Close both files
|
||||
if( source != null )
|
||||
{
|
||||
try {
|
||||
source.close();
|
||||
} catch( IOException e ) {
|
||||
// nobody cares
|
||||
}
|
||||
}
|
||||
if( destination != null )
|
||||
{
|
||||
try {
|
||||
destination.close();
|
||||
} catch( IOException e ) {
|
||||
// nobody cares
|
||||
}
|
||||
}
|
||||
Closeable file = m_openFiles.remove( ref );
|
||||
if( file != null ) closeQuietly( file );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized <T> T openFile( T file, Closeable handle ) throws FileSystemException
|
||||
private synchronized <T extends Closeable> FileSystemWrapper<T> openFile( @Nonnull T file ) throws FileSystemException
|
||||
{
|
||||
synchronized( m_openFiles )
|
||||
{
|
||||
if( ComputerCraft.maximumFilesOpen > 0 &&
|
||||
m_openFiles.size() >= ComputerCraft.maximumFilesOpen )
|
||||
{
|
||||
if( handle != null )
|
||||
{
|
||||
try {
|
||||
handle.close();
|
||||
} catch ( IOException ignored ) {
|
||||
// shrug
|
||||
}
|
||||
}
|
||||
throw new FileSystemException("Too many files already open");
|
||||
closeQuietly( file );
|
||||
throw new FileSystemException( "Too many files already open" );
|
||||
}
|
||||
|
||||
m_openFiles.add( handle );
|
||||
return file;
|
||||
FileSystemWrapper<T> wrapper = new FileSystemWrapper<>( this, file, m_openFileQueue );
|
||||
m_openFiles.put( wrapper.self, file );
|
||||
return wrapper;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void closeFile( Closeable handle ) throws IOException
|
||||
synchronized void removeFile( FileSystemWrapper<?> handle )
|
||||
{
|
||||
synchronized( m_openFiles )
|
||||
{
|
||||
m_openFiles.remove( handle );
|
||||
handle.close();
|
||||
m_openFiles.remove( handle.self );
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized InputStream openForRead( String path ) throws FileSystemException
|
||||
|
||||
public synchronized <T extends Closeable> FileSystemWrapper<T> openForRead( String path, Function<ReadableByteChannel, T> open ) throws FileSystemException
|
||||
{
|
||||
path = sanitizePath ( path );
|
||||
cleanup();
|
||||
|
||||
path = sanitizePath( path );
|
||||
MountWrapper mount = getMount( path );
|
||||
InputStream stream = mount.openForRead( path );
|
||||
if( stream != null )
|
||||
ReadableByteChannel channel = mount.openForRead( path );
|
||||
if( channel != null )
|
||||
{
|
||||
return openFile( new ClosingInputStream( stream ), stream );
|
||||
return openFile( open.apply( channel ) );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public synchronized OutputStream openForWrite( String path, boolean append ) throws FileSystemException
|
||||
public synchronized <T extends Closeable> FileSystemWrapper<T> openForWrite( String path, boolean append, Function<WritableByteChannel, T> open ) throws FileSystemException
|
||||
{
|
||||
path = sanitizePath ( path );
|
||||
cleanup();
|
||||
|
||||
path = sanitizePath( path );
|
||||
MountWrapper mount = getMount( path );
|
||||
OutputStream stream = append ? mount.openForAppend( path ) : mount.openForWrite( path );
|
||||
if( stream != null )
|
||||
WritableByteChannel channel = append ? mount.openForAppend( path ) : mount.openForWrite( path );
|
||||
if( channel != null )
|
||||
{
|
||||
return openFile( new ClosingOutputStream( stream ), stream );
|
||||
return openFile( open.apply( channel ) );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -865,33 +851,14 @@ public class FileSystem
|
||||
}
|
||||
}
|
||||
|
||||
private class ClosingInputStream extends FilterInputStream
|
||||
private static void closeQuietly( Closeable c )
|
||||
{
|
||||
protected ClosingInputStream( InputStream in )
|
||||
try
|
||||
{
|
||||
super( in );
|
||||
c.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
catch( IOException ignored )
|
||||
{
|
||||
super.close();
|
||||
closeFile( in );
|
||||
}
|
||||
}
|
||||
|
||||
private class ClosingOutputStream extends FilterOutputStream
|
||||
{
|
||||
protected ClosingOutputStream( OutputStream out )
|
||||
{
|
||||
super( out );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
super.close();
|
||||
closeFile( out );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,185 +1,132 @@
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import dan200.computercraft.api.filesystem.IFileSystem;
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class FileSystemMount implements IFileSystem
|
||||
public class FileSystemMount implements IMount
|
||||
{
|
||||
private final FileSystem m_filesystem;
|
||||
private final Entry rootEntry;
|
||||
|
||||
public FileSystemMount( FileSystem m_filesystem )
|
||||
public FileSystemMount( FileSystem fileSystem, String root ) throws IOException
|
||||
{
|
||||
this.m_filesystem = m_filesystem;
|
||||
}
|
||||
Path rootPath = fileSystem.getPath( root );
|
||||
rootEntry = new Entry( "", rootPath );
|
||||
|
||||
@Override
|
||||
public void makeDirectory( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
Queue<Entry> entries = new ArrayDeque<>();
|
||||
entries.add( rootEntry );
|
||||
while( !entries.isEmpty() )
|
||||
{
|
||||
m_filesystem.makeDir( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
Entry entry = entries.remove();
|
||||
try( Stream<Path> childStream = Files.list( entry.path ) )
|
||||
{
|
||||
Iterator<Path> children = childStream.iterator();
|
||||
while( children.hasNext() )
|
||||
{
|
||||
Path childPath = children.next();
|
||||
Entry child = new Entry( childPath.getFileName().toString(), childPath );
|
||||
entry.children.put( child.name, child );
|
||||
if( child.directory ) entries.add( child );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete( @Nonnull String path ) throws IOException
|
||||
public boolean exists( @Nonnull String path )
|
||||
{
|
||||
try
|
||||
{
|
||||
m_filesystem.delete( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public OutputStream openForWrite( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.openForWrite( path, false );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public OutputStream openForAppend( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.openForWrite( path, true );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
return getFile( path ) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRemainingSpace() throws IOException
|
||||
public boolean isDirectory( @Nonnull String path )
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.getFreeSpace( "/" );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.exists( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.exists( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
Entry entry = getFile( path );
|
||||
return entry != null && entry.directory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
Collections.addAll( contents, m_filesystem.list( path ) );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
Entry entry = getFile( path );
|
||||
if( entry == null || !entry.directory ) throw new IOException( "/" + path + ": Not a directory" );
|
||||
|
||||
contents.addAll( entry.children.keySet() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.getSize( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
Entry file = getFile( path );
|
||||
if( file == null ) throw new IOException( "/" + path + ": No such file" );
|
||||
return file.size;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.openForRead( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
Entry file = getFile( path );
|
||||
if( file == null || file.directory ) throw new IOException( "/" + path + ": No such file" );
|
||||
|
||||
return Files.newInputStream( file.path, StandardOpenOption.READ );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String combine( String path, String child )
|
||||
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return m_filesystem.combine( path, child );
|
||||
Entry file = getFile( path );
|
||||
if( file == null || file.directory ) throw new IOException( "/" + path + ": No such file" );
|
||||
|
||||
return Files.newByteChannel( file.path, StandardOpenOption.READ );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy( String from, String to ) throws IOException
|
||||
private Entry getFile( String path )
|
||||
{
|
||||
try
|
||||
if( path.equals( "" ) ) return rootEntry;
|
||||
if( !path.contains( "/" ) ) return rootEntry.children.get( path );
|
||||
|
||||
String[] components = path.split( "/" );
|
||||
Entry entry = rootEntry;
|
||||
for( String component : components )
|
||||
{
|
||||
m_filesystem.copy( from, to );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
if( entry == null || entry.children == null ) return null;
|
||||
entry = entry.children.get( component );
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void move( String from, String to ) throws IOException
|
||||
private static class Entry
|
||||
{
|
||||
try
|
||||
final String name;
|
||||
final Path path;
|
||||
|
||||
final boolean directory;
|
||||
final long size;
|
||||
final Map<String, Entry> children;
|
||||
|
||||
private Entry( String name, Path path ) throws IOException
|
||||
{
|
||||
m_filesystem.move( from, to );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
if( name.endsWith( "/" ) || name.endsWith( "\\" ) ) name = name.substring( 0, name.length() - 1 );
|
||||
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
|
||||
BasicFileAttributes attributes = Files.readAttributes( path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS );
|
||||
this.directory = attributes.isDirectory();
|
||||
this.size = directory ? 0 : attributes.size();
|
||||
this.children = directory ? new HashMap<>() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,42 @@
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* An alternative closeable implementation that will free up resources in the filesystem.
|
||||
*
|
||||
* In an ideal world, we'd just wrap the closeable. However, as we do some {@code instanceof} checks
|
||||
* on the stream, it's not really possible as it'd require numerous instances.
|
||||
*
|
||||
* @param <T> The stream to wrap.
|
||||
*/
|
||||
public class FileSystemWrapper<T extends Closeable> implements Closeable
|
||||
{
|
||||
private final FileSystem fileSystem;
|
||||
private final T closeable;
|
||||
final WeakReference<FileSystemWrapper<?>> self;
|
||||
|
||||
FileSystemWrapper( FileSystem fileSystem, T closeable, ReferenceQueue<FileSystemWrapper<?>> queue )
|
||||
{
|
||||
this.fileSystem = fileSystem;
|
||||
this.closeable = closeable;
|
||||
this.self = new WeakReference<>( this, queue );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
fileSystem.removeFile( this );
|
||||
closeable.close();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public T get()
|
||||
{
|
||||
return closeable;
|
||||
}
|
||||
}
|
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2018. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import dan200.computercraft.api.filesystem.IFileSystem;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class FileSystemWrapperMount implements IFileSystem
|
||||
{
|
||||
private final FileSystem m_filesystem;
|
||||
|
||||
public FileSystemWrapperMount( FileSystem m_filesystem )
|
||||
{
|
||||
this.m_filesystem = m_filesystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeDirectory( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
m_filesystem.makeDir( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
m_filesystem.delete( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
// FIXME: Think of a better way of implementing this, so closing this will close on the computer.
|
||||
return m_filesystem.openForRead( path, Function.identity() ).get();
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.openForWrite( path, false, Function.identity() ).get();
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.openForWrite( path, true, Function.identity() ).get();
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return Channels.newInputStream( openChannelForRead( path ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public OutputStream openForWrite( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return Channels.newOutputStream( openChannelForWrite( path ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public OutputStream openForAppend( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return Channels.newOutputStream( openChannelForAppend( path ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRemainingSpace() throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.getFreeSpace( "/" );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.exists( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.exists( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
Collections.addAll( contents, m_filesystem.list( path ) );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize( @Nonnull String path ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
return m_filesystem.getSize( path );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String combine( String path, String child )
|
||||
{
|
||||
return m_filesystem.combine( path, child );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copy( String from, String to ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
m_filesystem.copy( from, to );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void move( String from, String to ) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
m_filesystem.move( from, to );
|
||||
}
|
||||
catch( FileSystemException e )
|
||||
{
|
||||
throw new IOException( e.getMessage() );
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,248 +0,0 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.filesystem;
|
||||
|
||||
import dan200.computercraft.api.filesystem.IMount;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public class JarMount implements IMount
|
||||
{
|
||||
private class FileInZip
|
||||
{
|
||||
private String m_path;
|
||||
private boolean m_directory;
|
||||
private long m_size;
|
||||
private Map<String, FileInZip> m_children;
|
||||
|
||||
public FileInZip( String path, boolean directory, long size )
|
||||
{
|
||||
m_path = path;
|
||||
m_directory = directory;
|
||||
m_size = m_directory ? 0 : size;
|
||||
m_children = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public String getPath()
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
public boolean isDirectory()
|
||||
{
|
||||
return m_directory;
|
||||
}
|
||||
|
||||
public long getSize()
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
public void list( List<String> contents )
|
||||
{
|
||||
contents.addAll( m_children.keySet() );
|
||||
}
|
||||
|
||||
public void insertChild( FileInZip child )
|
||||
{
|
||||
String localPath = FileSystem.toLocal( child.getPath(), m_path );
|
||||
m_children.put( localPath, child );
|
||||
}
|
||||
|
||||
public FileInZip getFile( String path )
|
||||
{
|
||||
// If we've reached the target, return this
|
||||
if( path.equals( m_path ) )
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
// Otherwise, get the next component of the path
|
||||
String localPath = FileSystem.toLocal( path, m_path );
|
||||
int slash = localPath.indexOf("/");
|
||||
if( slash >= 0 )
|
||||
{
|
||||
localPath = localPath.substring( 0, slash );
|
||||
}
|
||||
|
||||
// And recurse down using it
|
||||
FileInZip subFile = m_children.get( localPath );
|
||||
if( subFile != null )
|
||||
{
|
||||
return subFile.getFile( path );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public FileInZip getParent( String path )
|
||||
{
|
||||
if( path.length() == 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
FileInZip file = getFile( FileSystem.getDirectory( path ) );
|
||||
if( file.isDirectory() )
|
||||
{
|
||||
return file;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ZipFile m_zipFile;
|
||||
private FileInZip m_root;
|
||||
private String m_rootPath;
|
||||
|
||||
public JarMount( File jarFile, String subPath ) throws IOException
|
||||
{
|
||||
if( !jarFile.exists() || jarFile.isDirectory() )
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
// Open the zip file
|
||||
try
|
||||
{
|
||||
m_zipFile = new ZipFile( jarFile );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
throw new IOException( "Error loading zip file" );
|
||||
}
|
||||
|
||||
if( m_zipFile.getEntry( subPath ) == null )
|
||||
{
|
||||
m_zipFile.close();
|
||||
throw new IOException( "Zip does not contain path" );
|
||||
}
|
||||
|
||||
// Read in all the entries
|
||||
Enumeration<? extends ZipEntry> zipEntries = m_zipFile.entries();
|
||||
while( zipEntries.hasMoreElements() )
|
||||
{
|
||||
ZipEntry entry = zipEntries.nextElement();
|
||||
String entryName = entry.getName();
|
||||
if( entryName.startsWith( subPath ) )
|
||||
{
|
||||
entryName = FileSystem.toLocal( entryName, subPath );
|
||||
if( m_root == null )
|
||||
{
|
||||
if( entryName.equals( "" ) )
|
||||
{
|
||||
m_root = new FileInZip( entryName, entry.isDirectory(), entry.getSize() );
|
||||
m_rootPath = subPath;
|
||||
if( !m_root.isDirectory() )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: handle this case. The code currently assumes we find the root before anything else
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FileInZip parent = m_root.getParent( entryName );
|
||||
if( parent != null )
|
||||
{
|
||||
parent.insertChild( new FileInZip( entryName, entry.isDirectory(), entry.getSize() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: handle this case. The code currently assumes we find folders before their contents
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IMount implementation
|
||||
|
||||
@Override
|
||||
public boolean exists( @Nonnull String path )
|
||||
{
|
||||
FileInZip file = m_root.getFile( path );
|
||||
return file != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory( @Nonnull String path )
|
||||
{
|
||||
FileInZip file = m_root.getFile( path );
|
||||
if( file != null )
|
||||
{
|
||||
return file.isDirectory();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
|
||||
{
|
||||
FileInZip file = m_root.getFile( path );
|
||||
if( file != null && file.isDirectory() )
|
||||
{
|
||||
file.list( contents );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException( "/" + path + ": Not a directory" );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize( @Nonnull String path ) throws IOException
|
||||
{
|
||||
FileInZip file = m_root.getFile( path );
|
||||
if( file != null )
|
||||
{
|
||||
return file.getSize();
|
||||
}
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
FileInZip file = m_root.getFile( path );
|
||||
if( file != null && !file.isDirectory() )
|
||||
{
|
||||
try
|
||||
{
|
||||
String fullPath = m_rootPath;
|
||||
if( path.length() > 0 )
|
||||
{
|
||||
fullPath = fullPath + "/" + path;
|
||||
}
|
||||
ZipEntry entry = m_zipFile.getEntry( fullPath );
|
||||
if( entry != null )
|
||||
{
|
||||
return m_zipFile.getInputStream( entry );
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
// treat errors as non-existance of file
|
||||
}
|
||||
}
|
||||
throw new IOException( "/" + path + ": No such file" );
|
||||
}
|
||||
}
|
@@ -11,6 +11,7 @@ import dan200.computercraft.api.filesystem.IMount;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.List;
|
||||
|
||||
public class SubMount implements IMount
|
||||
@@ -52,11 +53,19 @@ public class SubMount implements IMount
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public InputStream openForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return m_parent.openForRead( getFullPath( path ) );
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
|
||||
{
|
||||
return m_parent.openChannelForRead( getFullPath( path ) );
|
||||
}
|
||||
|
||||
private String getFullPath( String path )
|
||||
{
|
||||
if( path.length() == 0 )
|
||||
|
@@ -128,7 +128,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
LibFunction.bind( state, m_globals, PrefixLoader.class, new String[]{ "load", "loadstring" } );
|
||||
|
||||
// Remove globals we don't want to expose
|
||||
m_globals.rawset( "collectgarbage", Constants.NIL );
|
||||
// m_globals.rawset( "collectgarbage", Constants.NIL );
|
||||
m_globals.rawset( "dofile", Constants.NIL );
|
||||
m_globals.rawset( "loadfile", Constants.NIL );
|
||||
m_globals.rawset( "print", Constants.NIL );
|
||||
|
@@ -56,7 +56,7 @@ public class Terminal
|
||||
m_palette = new Palette();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
public synchronized void reset()
|
||||
{
|
||||
m_cursorColour = 0;
|
||||
m_cursorBackgroundColour = 15;
|
||||
@@ -76,7 +76,7 @@ public class Terminal
|
||||
return m_height;
|
||||
}
|
||||
|
||||
public void resize( int width, int height )
|
||||
public synchronized void resize( int width, int height )
|
||||
{
|
||||
if( width == m_width && height == m_height )
|
||||
{
|
||||
@@ -189,7 +189,7 @@ public class Terminal
|
||||
return m_palette;
|
||||
}
|
||||
|
||||
public void blit( String text, String textColour, String backgroundColour )
|
||||
public synchronized void blit( String text, String textColour, String backgroundColour )
|
||||
{
|
||||
int x = m_cursorX;
|
||||
int y = m_cursorY;
|
||||
@@ -202,7 +202,7 @@ public class Terminal
|
||||
}
|
||||
}
|
||||
|
||||
public void write( String text )
|
||||
public synchronized void write( String text )
|
||||
{
|
||||
int x = m_cursorX;
|
||||
int y = m_cursorY;
|
||||
@@ -215,7 +215,7 @@ public class Terminal
|
||||
}
|
||||
}
|
||||
|
||||
public void scroll( int yDiff )
|
||||
public synchronized void scroll( int yDiff )
|
||||
{
|
||||
if( yDiff != 0 )
|
||||
{
|
||||
@@ -245,7 +245,7 @@ public class Terminal
|
||||
}
|
||||
}
|
||||
|
||||
public void clear()
|
||||
public synchronized void clear()
|
||||
{
|
||||
for( int y = 0; y < m_height; ++y )
|
||||
{
|
||||
@@ -256,7 +256,7 @@ public class Terminal
|
||||
m_changed = true;
|
||||
}
|
||||
|
||||
public void clearLine()
|
||||
public synchronized void clearLine()
|
||||
{
|
||||
int y = m_cursorY;
|
||||
if( y >= 0 && y < m_height )
|
||||
@@ -268,7 +268,7 @@ public class Terminal
|
||||
}
|
||||
}
|
||||
|
||||
public TextBuffer getLine( int y )
|
||||
public synchronized TextBuffer getLine( int y )
|
||||
{
|
||||
if( y >= 0 && y < m_height )
|
||||
{
|
||||
@@ -277,7 +277,7 @@ public class Terminal
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setLine( int y, String text, String textColour, String backgroundColour )
|
||||
public synchronized void setLine( int y, String text, String textColour, String backgroundColour )
|
||||
{
|
||||
m_text[y].write( text );
|
||||
m_textColour[y].write( textColour );
|
||||
@@ -285,7 +285,7 @@ public class Terminal
|
||||
m_changed = true;
|
||||
}
|
||||
|
||||
public TextBuffer getTextColourLine( int y )
|
||||
public synchronized TextBuffer getTextColourLine( int y )
|
||||
{
|
||||
if( y>=0 && y<m_height )
|
||||
{
|
||||
@@ -294,7 +294,7 @@ public class Terminal
|
||||
return null;
|
||||
}
|
||||
|
||||
public TextBuffer getBackgroundColourLine( int y )
|
||||
public synchronized TextBuffer getBackgroundColourLine( int y )
|
||||
{
|
||||
if( y>=0 && y<m_height )
|
||||
{
|
||||
@@ -318,7 +318,7 @@ public class Terminal
|
||||
m_changed = false;
|
||||
}
|
||||
|
||||
public NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound )
|
||||
public synchronized NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound )
|
||||
{
|
||||
nbttagcompound.setInteger( "term_cursorX", m_cursorX );
|
||||
nbttagcompound.setInteger( "term_cursorY", m_cursorY );
|
||||
@@ -338,7 +338,7 @@ public class Terminal
|
||||
return nbttagcompound;
|
||||
}
|
||||
|
||||
public void readFromNBT( NBTTagCompound nbttagcompound )
|
||||
public synchronized void readFromNBT( NBTTagCompound nbttagcompound )
|
||||
{
|
||||
m_cursorX = nbttagcompound.getInteger( "term_cursorX" );
|
||||
m_cursorY = nbttagcompound.getInteger( "term_cursorY" );
|
||||
|
@@ -0,0 +1,117 @@
|
||||
package dan200.computercraft.core.tracking;
|
||||
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import gnu.trove.map.hash.TObjectLongHashMap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class ComputerTracker
|
||||
{
|
||||
private final WeakReference<Computer> computer;
|
||||
private final int computerId;
|
||||
|
||||
private long tasks;
|
||||
private long totalTime;
|
||||
private long maxTime;
|
||||
|
||||
private long serverCount;
|
||||
private long serverTime;
|
||||
|
||||
private final TObjectLongHashMap<TrackingField> fields;
|
||||
|
||||
public ComputerTracker( Computer computer )
|
||||
{
|
||||
this.computer = new WeakReference<>( computer );
|
||||
this.computerId = computer.getID();
|
||||
this.fields = new TObjectLongHashMap<>();
|
||||
}
|
||||
|
||||
ComputerTracker( ComputerTracker timings )
|
||||
{
|
||||
this.computer = timings.computer;
|
||||
this.computerId = timings.computerId;
|
||||
|
||||
this.tasks = timings.tasks;
|
||||
this.totalTime = timings.totalTime;
|
||||
this.maxTime = timings.maxTime;
|
||||
|
||||
this.serverCount = timings.serverCount;
|
||||
this.serverTime = timings.serverTime;
|
||||
|
||||
this.fields = new TObjectLongHashMap<>( timings.fields );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Computer getComputer()
|
||||
{
|
||||
return computer.get();
|
||||
}
|
||||
|
||||
public int getComputerId()
|
||||
{
|
||||
return computerId;
|
||||
}
|
||||
|
||||
public long getTasks()
|
||||
{
|
||||
return tasks;
|
||||
}
|
||||
|
||||
public long getTotalTime()
|
||||
{
|
||||
return totalTime;
|
||||
}
|
||||
|
||||
public long getMaxTime()
|
||||
{
|
||||
return maxTime;
|
||||
}
|
||||
|
||||
public long getAverage()
|
||||
{
|
||||
return totalTime / tasks;
|
||||
}
|
||||
|
||||
void addTaskTiming( long time )
|
||||
{
|
||||
tasks++;
|
||||
totalTime += time;
|
||||
if( time > maxTime ) maxTime = time;
|
||||
}
|
||||
|
||||
void addMainTiming( long time )
|
||||
{
|
||||
serverCount++;
|
||||
serverTime += time;
|
||||
}
|
||||
|
||||
void addValue( TrackingField field, long change )
|
||||
{
|
||||
synchronized( fields )
|
||||
{
|
||||
fields.adjustOrPutValue( field, change, change );
|
||||
}
|
||||
}
|
||||
|
||||
public long get( TrackingField field )
|
||||
{
|
||||
if( field == TrackingField.TASKS ) return tasks;
|
||||
if( field == TrackingField.MAX_TIME ) return maxTime;
|
||||
if( field == TrackingField.TOTAL_TIME ) return totalTime;
|
||||
if( field == TrackingField.AVERAGE_TIME ) return tasks == 0 ? 0 : totalTime / tasks;
|
||||
|
||||
if( field == TrackingField.SERVER_COUNT ) return serverCount;
|
||||
if( field == TrackingField.SERVER_TIME ) return serverTime;
|
||||
|
||||
synchronized( fields )
|
||||
{
|
||||
return fields.get( field );
|
||||
}
|
||||
}
|
||||
|
||||
public String getFormatted( TrackingField field )
|
||||
{
|
||||
return field.format( get( field ) );
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package dan200.computercraft.core.tracking;
|
||||
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
|
||||
public interface Tracker
|
||||
{
|
||||
@Deprecated
|
||||
default void addTiming( Computer computer, long time )
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Report how long a task executed on the computer thread took.
|
||||
*
|
||||
* Computer thread tasks include events or a computer being turned on/off.
|
||||
*
|
||||
* @param computer The computer processing this task
|
||||
* @param time The time taken for this task.
|
||||
*/
|
||||
default void addTaskTiming( Computer computer, long time )
|
||||
{
|
||||
//noinspection deprecation
|
||||
addTiming( computer, time );
|
||||
}
|
||||
|
||||
/**
|
||||
* Report how long a task executed on the server thread took.
|
||||
*
|
||||
* Server tasks include actions performed by peripherals.
|
||||
*
|
||||
* @param computer The computer processing this task
|
||||
* @param time The time taken for this task.
|
||||
*/
|
||||
default void addServerTiming( Computer computer, long time )
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment an arbitrary field by some value. Implementations may track how often this is called
|
||||
* as well as the change, to compute some level of "average".
|
||||
*
|
||||
* @param computer The computer to increment
|
||||
* @param field The field to increment.
|
||||
* @param change The amount to increment said field by.
|
||||
*/
|
||||
default void addValue( Computer computer, TrackingField field, long change )
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
package dan200.computercraft.core.tracking;
|
||||
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class Tracking
|
||||
{
|
||||
static final AtomicInteger tracking = new AtomicInteger( 0 );
|
||||
|
||||
private static final Object lock = new Object();
|
||||
private static final HashMap<UUID, TrackingContext> contexts = new HashMap<>();
|
||||
private static final List<Tracker> trackers = new ArrayList<>();
|
||||
|
||||
public static TrackingContext getContext( UUID uuid )
|
||||
{
|
||||
synchronized( lock )
|
||||
{
|
||||
TrackingContext context = contexts.get( uuid );
|
||||
if( context == null ) contexts.put( uuid, context = new TrackingContext() );
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
public static void add( Tracker tracker )
|
||||
{
|
||||
synchronized( lock )
|
||||
{
|
||||
trackers.add( tracker );
|
||||
tracking.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
public static void addTaskTiming( Computer computer, long time )
|
||||
{
|
||||
if( tracking.get() == 0 ) return;
|
||||
|
||||
synchronized( contexts )
|
||||
{
|
||||
for( TrackingContext context : contexts.values() ) context.addTaskTiming( computer, time );
|
||||
for( Tracker tracker : trackers ) tracker.addTaskTiming( computer, time );
|
||||
}
|
||||
}
|
||||
|
||||
public static void addServerTiming( Computer computer, long time )
|
||||
{
|
||||
if( tracking.get() == 0 ) return;
|
||||
|
||||
synchronized( contexts )
|
||||
{
|
||||
for( TrackingContext context : contexts.values() ) context.addServerTiming( computer, time );
|
||||
for( Tracker tracker : trackers ) tracker.addServerTiming( computer, time );
|
||||
}
|
||||
}
|
||||
|
||||
public static void addValue( Computer computer, TrackingField field, long change )
|
||||
{
|
||||
if( tracking.get() == 0 ) return;
|
||||
|
||||
synchronized( lock )
|
||||
{
|
||||
for( TrackingContext context : contexts.values() ) context.addValue( computer, field, change );
|
||||
for( Tracker tracker : trackers ) tracker.addValue( computer, field, change );
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset()
|
||||
{
|
||||
synchronized( lock )
|
||||
{
|
||||
contexts.clear();
|
||||
trackers.clear();
|
||||
tracking.set( 0 );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
package dan200.computercraft.core.tracking;
|
||||
|
||||
import com.google.common.collect.MapMaker;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tracks timing information about computers, including how long they ran for
|
||||
* and the number of events they handled.
|
||||
*
|
||||
* Note that this <em>will</em> track computers which have been deleted (hence
|
||||
* the presence of {@link #timingLookup} and {@link #timings}
|
||||
*/
|
||||
public class TrackingContext implements Tracker
|
||||
{
|
||||
private boolean tracking = false;
|
||||
|
||||
private final List<ComputerTracker> timings = new ArrayList<>();
|
||||
private final Map<Computer, ComputerTracker> timingLookup = new MapMaker().weakKeys().makeMap();
|
||||
|
||||
public synchronized void start()
|
||||
{
|
||||
if( !tracking ) Tracking.tracking.incrementAndGet();
|
||||
tracking = true;
|
||||
|
||||
timings.clear();
|
||||
timingLookup.clear();
|
||||
}
|
||||
|
||||
public synchronized boolean stop()
|
||||
{
|
||||
if( !tracking ) return false;
|
||||
|
||||
Tracking.tracking.decrementAndGet();
|
||||
tracking = false;
|
||||
timingLookup.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized List<ComputerTracker> getImmutableTimings()
|
||||
{
|
||||
ArrayList<ComputerTracker> timings = new ArrayList<>( this.timings.size() );
|
||||
for( ComputerTracker timing : this.timings ) timings.add( new ComputerTracker( timing ) );
|
||||
return timings;
|
||||
}
|
||||
|
||||
public synchronized List<ComputerTracker> getTimings()
|
||||
{
|
||||
return new ArrayList<>( timings );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTaskTiming( Computer computer, long time )
|
||||
{
|
||||
if( !tracking ) return;
|
||||
|
||||
synchronized( this )
|
||||
{
|
||||
ComputerTracker computerTimings = timingLookup.get( computer );
|
||||
if( computerTimings == null )
|
||||
{
|
||||
computerTimings = new ComputerTracker( computer );
|
||||
timingLookup.put( computer, computerTimings );
|
||||
timings.add( computerTimings );
|
||||
}
|
||||
|
||||
computerTimings.addTaskTiming( time );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addServerTiming( Computer computer, long time )
|
||||
{
|
||||
if( !tracking ) return;
|
||||
|
||||
synchronized( this )
|
||||
{
|
||||
ComputerTracker computerTimings = timingLookup.get( computer );
|
||||
if( computerTimings == null )
|
||||
{
|
||||
computerTimings = new ComputerTracker( computer );
|
||||
timingLookup.put( computer, computerTimings );
|
||||
timings.add( computerTimings );
|
||||
}
|
||||
|
||||
computerTimings.addMainTiming( time );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addValue( Computer computer, TrackingField field, long change )
|
||||
{
|
||||
if( !tracking ) return;
|
||||
|
||||
synchronized( this )
|
||||
{
|
||||
ComputerTracker computerTimings = timingLookup.get( computer );
|
||||
if( computerTimings == null )
|
||||
{
|
||||
computerTimings = new ComputerTracker( computer );
|
||||
timingLookup.put( computer, computerTimings );
|
||||
timings.add( computerTimings );
|
||||
}
|
||||
|
||||
computerTimings.addValue( field, change );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
package dan200.computercraft.core.tracking;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
public class TrackingField
|
||||
{
|
||||
private static final Map<String, TrackingField> fields = new HashMap<>();
|
||||
|
||||
public static final TrackingField TASKS = TrackingField.of( "tasks", "Tasks", x -> String.format( "%4d", x ) );
|
||||
public static final TrackingField TOTAL_TIME = TrackingField.of( "total", "Total time", x -> String.format( "%7.1fms", x / 1e6 ) );
|
||||
public static final TrackingField AVERAGE_TIME = TrackingField.of( "average", "Average time", x -> String.format( "%4.1fms", x / 1e6 ) );
|
||||
public static final TrackingField MAX_TIME = TrackingField.of( "max", "Max time", x -> String.format( "%5.1fms", x / 1e6 ) );
|
||||
|
||||
public static final TrackingField SERVER_COUNT = TrackingField.of( "server_count", "Server task count", x -> String.format( "%4d", x ) );
|
||||
public static final TrackingField SERVER_TIME = TrackingField.of( "server_time", "Server task time", x -> String.format( "%7.1fms", x / 1e6 ) );
|
||||
|
||||
public static final TrackingField PERIPHERAL_OPS = TrackingField.of( "peripheral", "Peripheral calls", TrackingField::formatDefault );
|
||||
public static final TrackingField FS_OPS = TrackingField.of( "fs", "Filesystem operations", TrackingField::formatDefault );
|
||||
public static final TrackingField TURTLE_OPS = TrackingField.of( "turtle", "Turtle operations", TrackingField::formatDefault );
|
||||
|
||||
public static final TrackingField HTTP_REQUESTS = TrackingField.of( "http", "HTTP requests", TrackingField::formatDefault );
|
||||
public static final TrackingField HTTP_UPLOAD = TrackingField.of( "http_upload", "HTTP upload", TrackingField::formatBytes );
|
||||
public static final TrackingField HTTP_DOWNLOAD = TrackingField.of( "http_download", "HTTT download", TrackingField::formatBytes );
|
||||
|
||||
public static final TrackingField WEBSOCKET_INCOMING = TrackingField.of( "websocket_incoming", "Websocket incoming", TrackingField::formatBytes );
|
||||
public static final TrackingField WEBSOCKET_OUTGOING = TrackingField.of( "websocket_outgoing", "Websocket outgoing", TrackingField::formatBytes );
|
||||
|
||||
private final String id;
|
||||
private final String displayName;
|
||||
private final LongFunction<String> format;
|
||||
|
||||
public String id()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public String displayName()
|
||||
{
|
||||
return displayName;
|
||||
}
|
||||
|
||||
private TrackingField( String id, String displayName, LongFunction<String> format )
|
||||
{
|
||||
this.id = id;
|
||||
this.displayName = displayName;
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public String format( long value )
|
||||
{
|
||||
return format.apply( value );
|
||||
}
|
||||
|
||||
public static TrackingField of( String id, String displayName, LongFunction<String> format )
|
||||
{
|
||||
TrackingField field = new TrackingField( id, displayName, format );
|
||||
fields.put( id, field );
|
||||
return field;
|
||||
}
|
||||
|
||||
public static Map<String, TrackingField> fields()
|
||||
{
|
||||
return Collections.unmodifiableMap( fields );
|
||||
}
|
||||
|
||||
private static String formatDefault( long value )
|
||||
{
|
||||
return String.format( "%6d", value );
|
||||
}
|
||||
|
||||
/**
|
||||
* So technically a kibibyte, but let's not argue here.
|
||||
*/
|
||||
private static final int KILOBYTE_SIZE = 1024;
|
||||
|
||||
private static final String SI_PREFIXES = "KMGT";
|
||||
|
||||
private static String formatBytes( long bytes )
|
||||
{
|
||||
if( bytes < 1024 ) return String.format( "%10d B", bytes );
|
||||
int exp = (int) (Math.log( bytes ) / Math.log( KILOBYTE_SIZE ));
|
||||
if( exp > SI_PREFIXES.length() ) exp = SI_PREFIXES.length();
|
||||
return String.format( "%10.1f %siB", bytes / Math.pow( KILOBYTE_SIZE, exp ), SI_PREFIXES.charAt( exp - 1 ) );
|
||||
}
|
||||
}
|
@@ -8,6 +8,8 @@ package dan200.computercraft.server.proxy;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.computer.blocks.TileComputer;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.IComputer;
|
||||
import dan200.computercraft.shared.network.ComputerCraftPacket;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
|
||||
import dan200.computercraft.shared.peripheral.printer.TilePrinter;
|
||||
@@ -97,6 +99,12 @@ public class ComputerCraftProxyServer extends ComputerCraftProxyCommon
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getComputerGUI( IComputer computer, int width, int height, ComputerFamily family )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getWorldDir( World world )
|
||||
{
|
||||
|
@@ -0,0 +1,532 @@
|
||||
package dan200.computercraft.shared.command;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.tracking.ComputerTracker;
|
||||
import dan200.computercraft.core.tracking.Tracking;
|
||||
import dan200.computercraft.core.tracking.TrackingContext;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
import dan200.computercraft.shared.command.framework.*;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.CommandException;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static dan200.computercraft.shared.command.framework.ChatHelpers.*;
|
||||
|
||||
public final class CommandComputerCraft extends CommandDelegate
|
||||
{
|
||||
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;
|
||||
|
||||
public CommandComputerCraft()
|
||||
{
|
||||
super( create() );
|
||||
}
|
||||
|
||||
private static ISubCommand create()
|
||||
{
|
||||
CommandRoot root = new CommandRoot(
|
||||
"computercraft", "Various commands for controlling computers.",
|
||||
"The /computercraft command provides various debugging and administrator tools for controlling and " +
|
||||
"interacting with computers."
|
||||
);
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"dump", "[id]", "Display the status of computers.", UserLevel.OWNER_OP,
|
||||
"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\")."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
if( arguments.size() == 0 )
|
||||
{
|
||||
TextTable table = new TextTable( DUMP_LIST_ID, "Computer", "On", "Position" );
|
||||
|
||||
List<ServerComputer> computers = new ArrayList<>( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||
|
||||
// Unless we're on a server, limit the number of rows we can send.
|
||||
if( !(context.getSender() instanceof MinecraftServer) )
|
||||
{
|
||||
World world = context.getSender().getEntityWorld();
|
||||
BlockPos pos = context.getSender().getPosition();
|
||||
|
||||
computers.sort( ( a, b ) -> {
|
||||
if( a.getWorld() == b.getWorld() && a.getWorld() == world )
|
||||
{
|
||||
return Double.compare( a.getPosition().distanceSq( pos ), b.getPosition().distanceSq( 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.addRow(
|
||||
linkComputer( context, computer, computer.getID() ),
|
||||
bool( computer.isOn() ),
|
||||
linkPosition( context, computer )
|
||||
);
|
||||
}
|
||||
|
||||
table.displayTo( context.getSender() );
|
||||
}
|
||||
else if( arguments.size() == 1 )
|
||||
{
|
||||
ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) );
|
||||
|
||||
TextTable table = new TextTable( DUMP_SINGLE_ID );
|
||||
table.addRow( header( "Instance" ), text( Integer.toString( computer.getInstanceID() ) ) );
|
||||
table.addRow( header( "Id" ), text( Integer.toString( computer.getID() ) ) );
|
||||
table.addRow( header( "Label" ), text( computer.getLabel() ) );
|
||||
table.addRow( header( "On" ), bool( computer.isOn() ) );
|
||||
table.addRow( header( "Position" ), linkPosition( context, computer ) );
|
||||
table.addRow( header( "Family" ), text( computer.getFamily().toString() ) );
|
||||
|
||||
for( int i = 0; i < 6; i++ )
|
||||
{
|
||||
IPeripheral peripheral = computer.getPeripheral( i );
|
||||
if( peripheral != null )
|
||||
{
|
||||
table.addRow( header( "Peripheral " + Computer.s_sideNames[i] ), text( peripheral.getType() ) );
|
||||
}
|
||||
}
|
||||
|
||||
table.displayTo( context.getSender() );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new CommandException( context.getFullUsage() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return arguments.size() == 1
|
||||
? ComputerSelector.completeComputer( arguments.get( 0 ) )
|
||||
: Collections.emptyList();
|
||||
}
|
||||
} );
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"shutdown", "[ids...]", "Shutdown computers remotely.", UserLevel.OWNER_OP,
|
||||
"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\")."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
withComputers( arguments, computers -> {
|
||||
int shutdown = 0;
|
||||
for( ServerComputer computer : computers )
|
||||
{
|
||||
if( computer.isOn() ) shutdown++;
|
||||
computer.unload();
|
||||
}
|
||||
context.getSender().sendMessage( text( "Shutdown " + shutdown + " / " + computers.size() + " computers" ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return arguments.size() == 0
|
||||
? Collections.emptyList()
|
||||
: ComputerSelector.completeComputer( arguments.get( arguments.size() - 1 ) );
|
||||
}
|
||||
} );
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"turn-on", "ids...", "Turn computers on remotely.", UserLevel.OWNER_OP,
|
||||
"Turn on the listed computers. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) " +
|
||||
"or label (e.g. \"@My Computer\")."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
withComputers( arguments, computers -> {
|
||||
int on = 0;
|
||||
for( ServerComputer computer : computers )
|
||||
{
|
||||
if( !computer.isOn() ) on++;
|
||||
computer.turnOn();
|
||||
}
|
||||
context.getSender().sendMessage( text( "Turned on " + on + " / " + computers.size() + " computers" ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return arguments.size() == 0
|
||||
? Collections.emptyList()
|
||||
: ComputerSelector.completeComputer( arguments.get( arguments.size() - 1 ) );
|
||||
}
|
||||
} );
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"tp", "<id>", "Teleport to a specific computer.", UserLevel.OP,
|
||||
"Teleport to the location of a computer. You can either specify the computer's instance " +
|
||||
"id (e.g. 123) or computer id (e.g #123)."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
if( arguments.size() != 1 ) throw new CommandException( context.getFullUsage() );
|
||||
|
||||
ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) );
|
||||
World world = computer.getWorld();
|
||||
BlockPos pos = computer.getPosition();
|
||||
|
||||
if( world == null || pos == null ) throw new CommandException( "Cannot locate computer in world" );
|
||||
|
||||
ICommandSender sender = context.getSender();
|
||||
if( !(sender instanceof Entity) ) throw new CommandException( "Sender is not an entity" );
|
||||
|
||||
if( sender instanceof EntityPlayerMP )
|
||||
{
|
||||
EntityPlayerMP entity = (EntityPlayerMP) sender;
|
||||
if( entity.getEntityWorld() != world )
|
||||
{
|
||||
context.getServer().getPlayerList().changePlayerDimension( entity, world.provider.getDimension() );
|
||||
}
|
||||
|
||||
entity.setPositionAndUpdate( pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5 );
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity entity = (Entity) sender;
|
||||
if( entity.getEntityWorld() != world )
|
||||
{
|
||||
entity.changeDimension( world.provider.getDimension() );
|
||||
}
|
||||
|
||||
entity.setLocationAndAngles(
|
||||
pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5,
|
||||
entity.rotationYaw, entity.rotationPitch
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return arguments.size() == 1
|
||||
? ComputerSelector.completeComputer( arguments.get( 0 ) )
|
||||
: Collections.emptyList();
|
||||
}
|
||||
} );
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"view", "<id>", "View the terminal of a computer.", UserLevel.OP,
|
||||
"Open the terminal of a computer, allowing remote control of a computer. This does not provide access to " +
|
||||
"turtle's inventories. You can either specify the computer's instance id (e.g. 123) or computer id (e.g #123)."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
if( arguments.size() != 1 ) throw new CommandException( context.getFullUsage() );
|
||||
|
||||
ICommandSender sender = context.getSender();
|
||||
if( !(sender instanceof EntityPlayerMP) )
|
||||
{
|
||||
throw new CommandException( "Cannot open terminal for non-player" );
|
||||
}
|
||||
|
||||
ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) );
|
||||
ComputerCraft.openComputerGUI( (EntityPlayerMP) sender, computer );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return arguments.size() == 1
|
||||
? ComputerSelector.completeComputer( arguments.get( 0 ) )
|
||||
: Collections.emptyList();
|
||||
}
|
||||
} );
|
||||
|
||||
CommandRoot track = new CommandRoot( "track", "Track execution times for computers.",
|
||||
"Track how long computers execute for, as well as how many events they handle. This presents information in " +
|
||||
"a similar way to /forge track and can be useful for diagnosing lag." );
|
||||
root.register( track );
|
||||
|
||||
track.register( new SubCommandBase(
|
||||
"start", "Start tracking all computers", UserLevel.OWNER_OP,
|
||||
"Start tracking all computers' execution times and event counts. This will discard the results of previous runs."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
getTimingContext( context ).start();
|
||||
|
||||
String stopCommand = "/" + context.parent().getFullPath() + " stop";
|
||||
context.getSender().sendMessage( list(
|
||||
text( "Run " ),
|
||||
link( text( stopCommand ), stopCommand, "Click to stop tracking" ),
|
||||
text( " to stop tracking and view the results" )
|
||||
) );
|
||||
}
|
||||
} );
|
||||
|
||||
track.register( new SubCommandBase(
|
||||
"stop", "Stop tracking all computers", UserLevel.OWNER_OP,
|
||||
"Stop tracking all computers' events and execution times"
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
TrackingContext timings = getTimingContext( context );
|
||||
if( !timings.stop() ) throw new CommandException( "Tracking not enabled" );
|
||||
displayTimings( context, timings.getImmutableTimings(), TrackingField.AVERAGE_TIME );
|
||||
}
|
||||
} );
|
||||
|
||||
track.register( new SubCommandBase(
|
||||
"dump", "[kind]", "Dump the latest track results", UserLevel.OWNER_OP,
|
||||
"Dump the latest results of computer tracking."
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
TrackingField field = TrackingField.AVERAGE_TIME;
|
||||
if( arguments.size() >= 1 )
|
||||
{
|
||||
field = TrackingField.fields().get( arguments.get( 0 ) );
|
||||
if( field == null ) throw new CommandException( "Unknown field '" + arguments.get( 0 ) + "'" );
|
||||
}
|
||||
|
||||
displayTimings( context, getTimingContext( context ).getImmutableTimings(), field );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
if( arguments.size() == 1 )
|
||||
{
|
||||
String match = arguments.get( 0 );
|
||||
|
||||
List<String> out = new ArrayList<>();
|
||||
for( String key : TrackingField.fields().keySet() )
|
||||
{
|
||||
if( CommandBase.doesStringStartWith( match, key ) ) out.add( key );
|
||||
}
|
||||
|
||||
out.sort( Comparator.naturalOrder() );
|
||||
return out;
|
||||
}
|
||||
else
|
||||
{
|
||||
return super.getCompletion( context, arguments );
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
root.register( new SubCommandBase(
|
||||
"reload", "Reload the ComputerCraft config file", UserLevel.OWNER_OP,
|
||||
"Reload the ComputerCraft config file"
|
||||
)
|
||||
{
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
ComputerCraft.loadConfig();
|
||||
ComputerCraft.syncConfig();
|
||||
context.getSender().sendMessage( new TextComponentString( "Reloaded config" ) );
|
||||
}
|
||||
} );
|
||||
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static ITextComponent linkComputer( CommandContext context, ServerComputer serverComputer, int computerId )
|
||||
{
|
||||
ITextComponent out = new TextComponentString( "" );
|
||||
|
||||
// Append the computer instance
|
||||
if( serverComputer == null )
|
||||
{
|
||||
out.appendSibling( text( "?" ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
out.appendSibling( link(
|
||||
text( Integer.toString( serverComputer.getInstanceID() ) ),
|
||||
"/computercraft dump " + serverComputer.getInstanceID(),
|
||||
"View more info about this computer"
|
||||
) );
|
||||
}
|
||||
|
||||
// And ID
|
||||
out.appendText( " (id " + computerId + ")" );
|
||||
|
||||
// And, if we're a player, some useful links
|
||||
if( serverComputer != null && UserLevel.OP.canExecute( context ) && context.fromPlayer() )
|
||||
{
|
||||
out
|
||||
.appendText( " " )
|
||||
.appendSibling( link(
|
||||
text( "\u261b" ),
|
||||
"/computercraft tp " + serverComputer.getInstanceID(),
|
||||
"Teleport to this computer"
|
||||
) )
|
||||
.appendText( " " )
|
||||
.appendSibling( link(
|
||||
text( "\u20e2" ),
|
||||
"/computercraft view " + serverComputer.getInstanceID(),
|
||||
"View this computer"
|
||||
) );
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static ITextComponent linkPosition( CommandContext context, ServerComputer computer )
|
||||
{
|
||||
if( UserLevel.OP.canExecute( context ) )
|
||||
{
|
||||
return link(
|
||||
position( computer.getPosition() ),
|
||||
"/computercraft tp " + computer.getInstanceID(),
|
||||
"Teleport to this computer"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
return position( computer.getPosition() );
|
||||
}
|
||||
}
|
||||
|
||||
private static TrackingContext getTimingContext( CommandContext context )
|
||||
{
|
||||
Entity entity = context.getSender().getCommandSenderEntity();
|
||||
if( entity instanceof EntityPlayerMP )
|
||||
{
|
||||
return Tracking.getContext( entity.getUniqueID() );
|
||||
}
|
||||
else
|
||||
{
|
||||
return Tracking.getContext( SYSTEM_UUID );
|
||||
}
|
||||
}
|
||||
|
||||
private static void displayTimings( CommandContext context, List<ComputerTracker> timings, TrackingField field ) throws CommandException
|
||||
{
|
||||
if( timings.isEmpty() ) throw new CommandException( "No timings available" );
|
||||
|
||||
Map<Computer, ServerComputer> lookup = new HashMap<>();
|
||||
int maxId = 0, maxInstance = 0;
|
||||
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();
|
||||
}
|
||||
|
||||
timings.sort( Comparator.<ComputerTracker, Long>comparing( x -> x.get( field ) ).reversed() );
|
||||
|
||||
boolean defaultLayout = field == TrackingField.TASKS || field == TrackingField.TOTAL_TIME
|
||||
|| field == TrackingField.AVERAGE_TIME || field == TrackingField.MAX_TIME;
|
||||
|
||||
|
||||
TextTable table = defaultLayout
|
||||
? new TextTable( TRACK_ID, "Computer", "Tasks", "Total", "Average", "Maximum" )
|
||||
: new TextTable( TRACK_ID, "Computer", field.displayName() );
|
||||
|
||||
for( ComputerTracker entry : timings )
|
||||
{
|
||||
Computer computer = entry.getComputer();
|
||||
ServerComputer serverComputer = computer == null ? null : lookup.get( computer );
|
||||
|
||||
ITextComponent computerComponent = linkComputer( context, serverComputer, entry.getComputerId() );
|
||||
|
||||
if( defaultLayout )
|
||||
{
|
||||
table.addRow(
|
||||
computerComponent,
|
||||
text( entry.getFormatted( TrackingField.TASKS ) ),
|
||||
text( entry.getFormatted( TrackingField.TOTAL_TIME ) ),
|
||||
text( entry.getFormatted( TrackingField.AVERAGE_TIME ) ),
|
||||
text( entry.getFormatted( TrackingField.MAX_TIME ) )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
table.addRow( computerComponent, text( entry.getFormatted( field ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
table.displayTo( context.getSender() );
|
||||
}
|
||||
|
||||
private static void withComputers( List<String> selectors, Consumer<Collection<ServerComputer>> action ) throws CommandException
|
||||
{
|
||||
Set<ServerComputer> computers = Sets.newHashSet();
|
||||
List<String> failed = new ArrayList<>();
|
||||
if( selectors.isEmpty() )
|
||||
{
|
||||
computers.addAll( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||
}
|
||||
else
|
||||
{
|
||||
for( String selector : selectors )
|
||||
{
|
||||
List<ServerComputer> selected = ComputerSelector.getComputers( selector );
|
||||
computers.addAll( selected );
|
||||
if( selected.isEmpty() ) failed.add( selector );
|
||||
}
|
||||
}
|
||||
|
||||
action.accept( computers );
|
||||
|
||||
if( !failed.isEmpty() )
|
||||
{
|
||||
throw new CommandException( "Could not find computers matching " + String.join( ", ", failed ) );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,157 @@
|
||||
package dan200.computercraft.shared.command;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.command.CommandException;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class ComputerSelector
|
||||
{
|
||||
private static List<ServerComputer> getComputers( Predicate<ServerComputer> predicate ) throws CommandException
|
||||
{
|
||||
// We copy it to prevent concurrent modifications.
|
||||
ArrayList<ServerComputer> computers = new ArrayList<>( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||
computers.removeIf( predicate.negate() );
|
||||
return computers;
|
||||
}
|
||||
|
||||
public static ServerComputer getComputer( String selector ) throws CommandException
|
||||
{
|
||||
List<ServerComputer> computers = getComputers( selector );
|
||||
if( computers.size() == 0 )
|
||||
{
|
||||
throw new CommandException( "No computer matching " + selector );
|
||||
}
|
||||
else if( computers.size() == 1 )
|
||||
{
|
||||
return computers.get( 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
StringBuilder builder = new StringBuilder( "Multiple computers matching " )
|
||||
.append( selector ).append( " (instances " );
|
||||
|
||||
for( int i = 0; i < computers.size(); i++ )
|
||||
{
|
||||
if( i > 0 ) builder.append( ", " );
|
||||
builder.append( computers.get( i ).getInstanceID() );
|
||||
}
|
||||
builder.append( ")" );
|
||||
|
||||
throw new CommandException( builder.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ServerComputer> getComputers( String selector ) throws CommandException
|
||||
{
|
||||
if( selector.length() > 0 && selector.charAt( 0 ) == '#' )
|
||||
{
|
||||
selector = selector.substring( 1 );
|
||||
|
||||
int id;
|
||||
try
|
||||
{
|
||||
id = Integer.parseInt( selector );
|
||||
}
|
||||
catch( NumberFormatException e )
|
||||
{
|
||||
throw new CommandException( "'" + selector + "' is not a valid number" );
|
||||
}
|
||||
|
||||
return getComputers( x -> x.getID() == id );
|
||||
}
|
||||
else if( selector.length() > 0 && selector.charAt( 0 ) == '@' )
|
||||
{
|
||||
String label = selector.substring( 1 );
|
||||
return getComputers( x -> Objects.equals( label, x.getLabel() ) );
|
||||
}
|
||||
else if( selector.length() > 0 && selector.charAt( 0 ) == '~' )
|
||||
{
|
||||
String familyName = selector.substring( 1 );
|
||||
return getComputers( x -> x.getFamily().name().equalsIgnoreCase( familyName ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
int instance;
|
||||
try
|
||||
{
|
||||
instance = Integer.parseInt( selector );
|
||||
}
|
||||
catch( NumberFormatException e )
|
||||
{
|
||||
throw new CommandException( "'" + selector + "' is not a valid number" );
|
||||
}
|
||||
|
||||
ServerComputer computer = ComputerCraft.serverComputerRegistry.get( instance );
|
||||
return computer == null ? Collections.emptyList() : Collections.singletonList( computer );
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> completeComputer( String selector )
|
||||
{
|
||||
TreeSet<String> options = Sets.newTreeSet();
|
||||
|
||||
// We copy it to prevent concurrent modifications.
|
||||
List<ServerComputer> computers = Lists.newArrayList( ComputerCraft.serverComputerRegistry.getComputers() );
|
||||
|
||||
if( selector.length() > 0 && selector.charAt( 0 ) == '#' )
|
||||
{
|
||||
selector = selector.substring( 1 );
|
||||
|
||||
for( ServerComputer computer : computers )
|
||||
{
|
||||
String id = Integer.toString( computer.getID() );
|
||||
if( id.startsWith( selector ) ) options.add( "#" + id );
|
||||
}
|
||||
}
|
||||
else if( selector.length() > 0 && selector.charAt( 0 ) == '@' )
|
||||
{
|
||||
String label = selector.substring( 1 );
|
||||
for( ServerComputer computer : computers )
|
||||
{
|
||||
String thisLabel = computer.getLabel();
|
||||
if( thisLabel != null && thisLabel.startsWith( label ) ) options.add( "@" + thisLabel );
|
||||
}
|
||||
}
|
||||
else if( selector.length() > 0 && selector.charAt( 0 ) == '~' )
|
||||
{
|
||||
String familyName = selector.substring( 1 ).toLowerCase( Locale.ENGLISH );
|
||||
for( ComputerFamily family : ComputerFamily.values() )
|
||||
{
|
||||
if( family.name().toLowerCase( Locale.ENGLISH ).startsWith( familyName ) )
|
||||
{
|
||||
options.add( "~" + family.name() );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for( ServerComputer computer : computers )
|
||||
{
|
||||
String id = Integer.toString( computer.getInstanceID() );
|
||||
if( id.startsWith( selector ) ) options.add( id );
|
||||
}
|
||||
}
|
||||
|
||||
if( options.size() > 100 )
|
||||
{
|
||||
ArrayList<String> result = Lists.newArrayListWithCapacity( 100 );
|
||||
for( String element : options )
|
||||
{
|
||||
if( result.size() > 100 ) break;
|
||||
result.add( element );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Lists.newArrayList( options );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package dan200.computercraft.shared.command;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.IComputer;
|
||||
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.inventory.Container;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.text.TextComponentTranslation;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class ContainerViewComputer extends Container implements IContainerComputer
|
||||
{
|
||||
private final IComputer computer;
|
||||
|
||||
public ContainerViewComputer( IComputer computer )
|
||||
{
|
||||
this.computer = computer;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IComputer getComputer()
|
||||
{
|
||||
return computer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInteractWith( @Nonnull EntityPlayer player )
|
||||
{
|
||||
if( computer instanceof ServerComputer )
|
||||
{
|
||||
ServerComputer serverComputer = (ServerComputer) computer;
|
||||
|
||||
// If this computer no longer exists then discard it.
|
||||
if( ComputerCraft.serverComputerRegistry.get( serverComputer.getInstanceID() ) != serverComputer )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we're a command computer then ensure we're in creative
|
||||
if( serverComputer.getFamily() == ComputerFamily.Command )
|
||||
{
|
||||
MinecraftServer server = player.getServer();
|
||||
if( server == null || !server.isCommandBlockEnabled() )
|
||||
{
|
||||
player.sendMessage( new TextComponentTranslation( "advMode.notEnabled" ) );
|
||||
return false;
|
||||
}
|
||||
else if( !ComputerCraft.canPlayerUseCommands( player ) || !player.capabilities.isCreativeMode )
|
||||
{
|
||||
player.sendMessage( new TextComponentTranslation( "advMode.notAllowed" ) );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,117 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.Style;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
import net.minecraft.util.text.event.ClickEvent;
|
||||
import net.minecraft.util.text.event.HoverEvent;
|
||||
|
||||
/**
|
||||
* Various helpers for building chat messages
|
||||
*/
|
||||
public final class ChatHelpers
|
||||
{
|
||||
private static final TextFormatting HEADER = TextFormatting.LIGHT_PURPLE;
|
||||
private static final TextFormatting SYNOPSIS = TextFormatting.AQUA;
|
||||
private static final TextFormatting NAME = TextFormatting.GREEN;
|
||||
|
||||
public static ITextComponent coloured( String text, TextFormatting colour )
|
||||
{
|
||||
ITextComponent component = new TextComponentString( text == null ? "" : text );
|
||||
component.getStyle().setColor( colour );
|
||||
return component;
|
||||
}
|
||||
|
||||
public static ITextComponent text( String text )
|
||||
{
|
||||
return new TextComponentString( text == null ? "" : text );
|
||||
}
|
||||
|
||||
public static ITextComponent list( ITextComponent... children )
|
||||
{
|
||||
ITextComponent component = new TextComponentString( "" );
|
||||
for( ITextComponent child : children )
|
||||
{
|
||||
component.appendSibling( child );
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
public static ITextComponent getHelp( CommandContext context, ISubCommand command, String prefix )
|
||||
{
|
||||
ITextComponent output = new TextComponentString( "" )
|
||||
.appendSibling( coloured( "/" + prefix + " " + command.getUsage( context ), HEADER ) )
|
||||
.appendText( " " )
|
||||
.appendSibling( coloured( command.getSynopsis(), SYNOPSIS ) );
|
||||
|
||||
String desc = command.getDescription();
|
||||
if( !Strings.isNullOrEmpty( desc ) ) output.appendText( "\n" + desc );
|
||||
|
||||
if( command instanceof CommandRoot )
|
||||
{
|
||||
for( ISubCommand subCommand : ((CommandRoot) command).getSubCommands().values() )
|
||||
{
|
||||
if( !subCommand.checkPermission( context ) ) continue;
|
||||
|
||||
output.appendText( "\n" );
|
||||
|
||||
ITextComponent component = coloured( subCommand.getName(), NAME );
|
||||
component.getStyle().setClickEvent( new ClickEvent(
|
||||
ClickEvent.Action.SUGGEST_COMMAND,
|
||||
"/" + prefix + " " + subCommand.getName()
|
||||
) );
|
||||
output.appendSibling( component );
|
||||
|
||||
output.appendText( " - " + subCommand.getSynopsis() );
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static ITextComponent position( BlockPos pos )
|
||||
{
|
||||
if( pos == null ) return text( "<no pos>" );
|
||||
return formatted( "%d, %d, %d", pos.getX(), pos.getY(), pos.getZ() );
|
||||
}
|
||||
|
||||
public static ITextComponent bool( boolean value )
|
||||
{
|
||||
if( value )
|
||||
{
|
||||
ITextComponent component = new TextComponentString( "Y" );
|
||||
component.getStyle().setColor( TextFormatting.GREEN );
|
||||
return component;
|
||||
}
|
||||
else
|
||||
{
|
||||
ITextComponent component = new TextComponentString( "N" );
|
||||
component.getStyle().setColor( TextFormatting.RED );
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
public static ITextComponent formatted( String format, Object... args )
|
||||
{
|
||||
return new TextComponentString( String.format( format, args ) );
|
||||
}
|
||||
|
||||
public static ITextComponent link( ITextComponent component, String command, String toolTip )
|
||||
{
|
||||
Style style = component.getStyle();
|
||||
|
||||
if( style.getColor() == null ) style.setColor( TextFormatting.YELLOW );
|
||||
style.setClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ) );
|
||||
style.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new TextComponentString( toolTip ) ) );
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
public static ITextComponent header( String text )
|
||||
{
|
||||
return coloured( text, HEADER );
|
||||
}
|
||||
}
|
@@ -0,0 +1,100 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents the way a command was invoked, including the command sender, the current server and
|
||||
* the "path" to this command.
|
||||
*/
|
||||
public final class CommandContext
|
||||
{
|
||||
private final MinecraftServer server;
|
||||
private final ICommandSender sender;
|
||||
private final List<ISubCommand> path;
|
||||
|
||||
public CommandContext( MinecraftServer server, ICommandSender sender, ISubCommand initial )
|
||||
{
|
||||
this.server = server;
|
||||
this.sender = sender;
|
||||
this.path = Collections.singletonList( initial );
|
||||
}
|
||||
|
||||
private CommandContext( MinecraftServer server, ICommandSender sender, List<ISubCommand> path )
|
||||
{
|
||||
this.server = server;
|
||||
this.sender = sender;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public CommandContext enter( ISubCommand child )
|
||||
{
|
||||
List<ISubCommand> newPath = Lists.newArrayListWithExpectedSize( path.size() + 1 );
|
||||
newPath.addAll( path );
|
||||
newPath.add( child );
|
||||
return new CommandContext( server, sender, newPath );
|
||||
}
|
||||
|
||||
public CommandContext parent()
|
||||
{
|
||||
if( path.size() == 1 ) throw new IllegalStateException( "No parent command" );
|
||||
return new CommandContext( server, sender, path.subList( 0, path.size() - 1 ) );
|
||||
}
|
||||
|
||||
public String getFullPath()
|
||||
{
|
||||
StringBuilder out = new StringBuilder();
|
||||
boolean first = true;
|
||||
for( ISubCommand command : path )
|
||||
{
|
||||
if( first )
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append( ' ' );
|
||||
}
|
||||
|
||||
out.append( command.getName() );
|
||||
}
|
||||
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
public String getFullUsage()
|
||||
{
|
||||
return "/" + getFullPath() + " " + path.get( path.size() - 1 ).getUsage( this );
|
||||
}
|
||||
|
||||
public List<ISubCommand> getPath()
|
||||
{
|
||||
return Collections.unmodifiableList( path );
|
||||
}
|
||||
|
||||
public String getRootCommand()
|
||||
{
|
||||
return path.get( 0 ).getName();
|
||||
}
|
||||
|
||||
public MinecraftServer getServer()
|
||||
{
|
||||
return server;
|
||||
}
|
||||
|
||||
public ICommandSender getSender()
|
||||
{
|
||||
return sender;
|
||||
}
|
||||
|
||||
public boolean fromPlayer()
|
||||
{
|
||||
return sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import net.minecraft.command.CommandException;
|
||||
import net.minecraft.command.ICommand;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link net.minecraft.command.ICommand} which delegates to a {@link ISubCommand}.
|
||||
*/
|
||||
public class CommandDelegate implements ICommand
|
||||
{
|
||||
private final ISubCommand command;
|
||||
|
||||
public CommandDelegate( ISubCommand command )
|
||||
{
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return command.getName();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUsage( @Nonnull ICommandSender sender )
|
||||
{
|
||||
return new CommandContext( sender.getServer(), sender, command ).getFullUsage();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getAliases()
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute( @Nonnull MinecraftServer server, @Nonnull ICommandSender sender, @Nonnull String[] args ) throws CommandException
|
||||
{
|
||||
try
|
||||
{
|
||||
command.execute( new CommandContext( server, sender, command ), Arrays.asList( args ) );
|
||||
}
|
||||
catch( CommandException e )
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch( Throwable e )
|
||||
{
|
||||
ComputerCraft.log.error( "Unhandled exception in command", e );
|
||||
throw new CommandException( "Unhandled exception: " + e.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getTabCompletions( @Nonnull MinecraftServer server, @Nonnull ICommandSender sender, @Nonnull String[] args, @Nullable BlockPos pos )
|
||||
{
|
||||
return command.getCompletion( new CommandContext( server, sender, command ), Arrays.asList( args ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission( @Nonnull MinecraftServer server, @Nonnull ICommandSender sender )
|
||||
{
|
||||
return command.checkPermission( new CommandContext( server, sender, command ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUsernameIndex( @Nonnull String[] args, int index )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo( @Nonnull ICommand o )
|
||||
{
|
||||
return getName().compareTo( o.getName() );
|
||||
}
|
||||
}
|
@@ -0,0 +1,149 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.CommandException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A command which delegates to a series of sub commands
|
||||
*/
|
||||
public class CommandRoot implements ISubCommand
|
||||
{
|
||||
private final String name;
|
||||
private final String synopsis;
|
||||
private final String description;
|
||||
private final Map<String, ISubCommand> subCommands = Maps.newHashMap();
|
||||
|
||||
public CommandRoot( String name, String synopsis, String description )
|
||||
{
|
||||
this.name = name;
|
||||
this.synopsis = synopsis;
|
||||
this.description = description;
|
||||
|
||||
register( new SubCommandHelp( this ) );
|
||||
}
|
||||
|
||||
public void register( ISubCommand command )
|
||||
{
|
||||
subCommands.put( command.getName(), command );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUsage( CommandContext context )
|
||||
{
|
||||
StringBuilder out = new StringBuilder( "<" );
|
||||
boolean first = true;
|
||||
for( ISubCommand command : subCommands.values() )
|
||||
{
|
||||
if( command.checkPermission( context ) )
|
||||
{
|
||||
if( first )
|
||||
{
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
out.append( "|" );
|
||||
}
|
||||
|
||||
out.append( command.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
return out.append( ">" ).toString();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getSynopsis()
|
||||
{
|
||||
return synopsis;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getDescription()
|
||||
{
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission( @Nonnull CommandContext context )
|
||||
{
|
||||
for( ISubCommand command : subCommands.values() )
|
||||
{
|
||||
if( !(command instanceof SubCommandHelp) && command.checkPermission( context ) ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Map<String, ISubCommand> getSubCommands()
|
||||
{
|
||||
return Collections.unmodifiableMap( subCommands );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
if( arguments.size() == 0 )
|
||||
{
|
||||
context.getSender().sendMessage( ChatHelpers.getHelp( context, this, context.getFullPath() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
ISubCommand command = subCommands.get( arguments.get( 0 ) );
|
||||
if( command == null || !command.checkPermission( context ) )
|
||||
{
|
||||
throw new CommandException( context.getFullUsage() );
|
||||
}
|
||||
|
||||
command.execute( context.enter( command ), arguments.subList( 1, arguments.size() ) );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
if( arguments.size() == 0 )
|
||||
{
|
||||
return Lists.newArrayList( subCommands.keySet() );
|
||||
}
|
||||
else if( arguments.size() == 1 )
|
||||
{
|
||||
List<String> list = Lists.newArrayList();
|
||||
String match = arguments.get( 0 );
|
||||
|
||||
for( ISubCommand command : subCommands.values() )
|
||||
{
|
||||
if( CommandBase.doesStringStartWith( match, command.getName() ) && command.checkPermission( context ) )
|
||||
{
|
||||
list.add( command.getName() );
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
else
|
||||
{
|
||||
ISubCommand command = subCommands.get( arguments.get( 0 ) );
|
||||
if( command == null || !command.checkPermission( context ) ) return Collections.emptyList();
|
||||
|
||||
return command.getCompletion( context, arguments.subList( 1, arguments.size() ) );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import net.minecraft.command.CommandException;
|
||||
import net.minecraft.command.ICommand;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A slightly different implementation of {@link ICommand} which is delegated to.
|
||||
*/
|
||||
public interface ISubCommand
|
||||
{
|
||||
/**
|
||||
* Get the name of this command
|
||||
*
|
||||
* @return The name of this command
|
||||
* @see ICommand#getName()
|
||||
*/
|
||||
@Nonnull
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Get the usage of this command
|
||||
*
|
||||
* @param context The context this command is executed in
|
||||
* @return The usage of this command
|
||||
* @see ICommand#getUsage(ICommandSender)
|
||||
*/
|
||||
@Nonnull
|
||||
String getUsage( CommandContext context );
|
||||
|
||||
/**
|
||||
* Get a short description of this command, including its usage.
|
||||
*
|
||||
* @return The command's synopsis
|
||||
*/
|
||||
@Nonnull
|
||||
String getSynopsis();
|
||||
|
||||
/**
|
||||
* Get the lengthy description of this command. This synopsis is prepended to this.
|
||||
*
|
||||
* @return The command's description
|
||||
*/
|
||||
@Nonnull
|
||||
String getDescription();
|
||||
|
||||
/**
|
||||
* Determine whether a given command sender has permission to execute this command.
|
||||
*
|
||||
* @param context The current command context.
|
||||
* @return Whether this command can be executed.
|
||||
* @see ICommand#checkPermission(MinecraftServer, ICommandSender)
|
||||
*/
|
||||
boolean checkPermission( @Nonnull CommandContext context );
|
||||
|
||||
/**
|
||||
* Execute this command
|
||||
*
|
||||
* @param context The current command context.
|
||||
* @param arguments The arguments passed @throws CommandException When an error occurs
|
||||
* @see ICommand#execute(MinecraftServer, ICommandSender, String[])
|
||||
*/
|
||||
void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException;
|
||||
|
||||
/**
|
||||
* Get a list of possible completions
|
||||
*
|
||||
* @param context The current command context.
|
||||
* @param arguments The arguments passed. You should complete the last one.
|
||||
* @return List of possible completions
|
||||
* @see ICommand#getTabCompletions(MinecraftServer, ICommandSender, String[], BlockPos)
|
||||
*/
|
||||
@Nonnull
|
||||
List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments );
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SubCommandBase implements ISubCommand
|
||||
{
|
||||
private final String name;
|
||||
private final String usage;
|
||||
private final String synopsis;
|
||||
private final String description;
|
||||
private final UserLevel level;
|
||||
|
||||
public SubCommandBase( String name, String usage, String synopsis, UserLevel level, String description )
|
||||
{
|
||||
this.name = name;
|
||||
this.usage = usage;
|
||||
this.synopsis = synopsis;
|
||||
this.description = description;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public SubCommandBase( String name, String synopsis, UserLevel level, String description )
|
||||
{
|
||||
this( name, "", synopsis, level, description );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUsage( CommandContext context )
|
||||
{
|
||||
return usage;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getSynopsis()
|
||||
{
|
||||
return synopsis;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getDescription()
|
||||
{
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission( @Nonnull CommandContext context )
|
||||
{
|
||||
return level.canExecute( context );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import joptsimple.internal.Strings;
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.CommandException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class SubCommandHelp implements ISubCommand
|
||||
{
|
||||
private final CommandRoot branchCommand;
|
||||
|
||||
public SubCommandHelp( CommandRoot branchCommand )
|
||||
{
|
||||
this.branchCommand = branchCommand;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "help";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUsage( CommandContext context )
|
||||
{
|
||||
return "[command]";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getSynopsis()
|
||||
{
|
||||
return "Provide help for a specific command";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getDescription()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkPermission( @Nonnull CommandContext context )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
|
||||
{
|
||||
ISubCommand command = branchCommand;
|
||||
|
||||
for( int i = 0; i < arguments.size(); i++ )
|
||||
{
|
||||
String commandName = arguments.get( i );
|
||||
if( command instanceof CommandRoot )
|
||||
{
|
||||
command = ((CommandRoot) command).getSubCommands().get( commandName );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new CommandException( Strings.join( arguments.subList( 0, i ), " " ) + " has no sub-commands" );
|
||||
}
|
||||
|
||||
if( command == null )
|
||||
{
|
||||
throw new CommandException( "No such command " + Strings.join( arguments.subList( 0, i + 1 ), " " ) );
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder prefix = new StringBuilder( context.parent().getFullPath() );
|
||||
for( String argument : arguments )
|
||||
{
|
||||
prefix.append( ' ' ).append( argument );
|
||||
}
|
||||
context.getSender().sendMessage( ChatHelpers.getHelp( context, command, prefix.toString() ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
|
||||
{
|
||||
CommandRoot command = branchCommand;
|
||||
|
||||
for( int i = 0; i < arguments.size() - 1; i++ )
|
||||
{
|
||||
String commandName = arguments.get( i );
|
||||
ISubCommand subCommand = command.getSubCommands().get( commandName );
|
||||
|
||||
if( subCommand instanceof CommandRoot )
|
||||
{
|
||||
command = (CommandRoot) subCommand;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
if( arguments.size() == 0 )
|
||||
{
|
||||
return Lists.newArrayList( command.getSubCommands().keySet() );
|
||||
}
|
||||
else
|
||||
{
|
||||
List<String> list = Lists.newArrayList();
|
||||
String match = arguments.get( arguments.size() - 1 );
|
||||
|
||||
for( String entry : command.getSubCommands().keySet() )
|
||||
{
|
||||
if( CommandBase.doesStringStartWith( match, entry ) )
|
||||
{
|
||||
list.add( entry );
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,147 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured;
|
||||
|
||||
/**
|
||||
* Adapated from Sponge's PaginationCalculator
|
||||
*/
|
||||
public class TextFormatter
|
||||
{
|
||||
private static final char PADDING_CHAR = '\u02cc';
|
||||
|
||||
/**
|
||||
* Yoinked from FontRenderer
|
||||
*
|
||||
* @see net.minecraft.client.gui.FontRenderer#charWidth
|
||||
* @see net.minecraft.client.gui.FontRenderer#getCharWidth(char)
|
||||
*/
|
||||
private static final String CHARACTERS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
|
||||
private static final int[] CHAR_WIDTHS = new int[]{
|
||||
6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4,
|
||||
4, 6, 7, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 2, 5, 6, 6, 6, 6, 3, 5, 5, 5, 6, 2, 6, 2, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 5, 6, 5, 6,
|
||||
7, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 6, 6,
|
||||
3, 6, 6, 6, 6, 6, 5, 6, 6, 2, 6, 5, 3, 6, 6, 6,
|
||||
6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 5, 2, 5, 7, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 3, 6, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6,
|
||||
6, 3, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 2, 6, 6,
|
||||
8, 9, 9, 6, 6, 6, 8, 8, 6, 8, 8, 8, 8, 8, 6, 6,
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
|
||||
9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 5, 9, 9,
|
||||
8, 7, 7, 8, 7, 8, 8, 8, 7, 8, 8, 7, 9, 9, 6, 7,
|
||||
7, 7, 7, 7, 9, 6, 7, 8, 7, 6, 6, 9, 7, 6, 7, 1
|
||||
};
|
||||
|
||||
private static final int[] EXTRA_CHARS = new int[]{
|
||||
'\u20e2', '\u261b',
|
||||
};
|
||||
|
||||
private static final byte[] EXTRA_WIDTHS = new byte[]{
|
||||
8, 4,
|
||||
};
|
||||
|
||||
public static int getWidth( int codePoint )
|
||||
{
|
||||
// Escape codes
|
||||
if( codePoint == 167 ) return -1;
|
||||
|
||||
// Space and non-breaking space
|
||||
if( codePoint == 32 || codePoint == 160 ) return 4;
|
||||
|
||||
// Built-in characters
|
||||
int nonUnicodeIdx = CHARACTERS.indexOf( codePoint );
|
||||
if( codePoint > 0 && nonUnicodeIdx != -1 ) return CHAR_WIDTHS[nonUnicodeIdx];
|
||||
|
||||
// Other special characters we use.
|
||||
int extraIdx = Arrays.binarySearch( EXTRA_CHARS, codePoint );
|
||||
if( extraIdx >= 0 ) return EXTRA_WIDTHS[extraIdx];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int getWidth( ITextComponent component )
|
||||
{
|
||||
int total = 0;
|
||||
if( component instanceof TextComponentString )
|
||||
{
|
||||
String contents = component.getUnformattedComponentText();
|
||||
|
||||
int bold = component.getStyle().getBold() ? 1 : 0;
|
||||
for( int i = 0; i < contents.length(); i++ )
|
||||
{
|
||||
int cp = contents.charAt( i );
|
||||
assert cp != '\n';
|
||||
int width = getWidth( cp );
|
||||
if( width < 0 )
|
||||
{
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
total += width + bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for( ITextComponent child : component.getSiblings() )
|
||||
{
|
||||
total += getWidth( child );
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public static boolean isPlayer( ICommandSender sender )
|
||||
{
|
||||
return sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
|
||||
}
|
||||
|
||||
public static int getMaxWidth( ICommandSender sender )
|
||||
{
|
||||
return isPlayer( sender ) ? 320 : 80;
|
||||
}
|
||||
|
||||
public static int getWidthFor( ITextComponent component, ICommandSender sender )
|
||||
{
|
||||
return isPlayer( sender ) ? getWidth( component ) : component.getUnformattedText().length();
|
||||
}
|
||||
|
||||
public static int getWidthFor( int codepoint, ICommandSender sender )
|
||||
{
|
||||
return isPlayer( sender ) ? getWidth( codepoint ) : 1;
|
||||
}
|
||||
|
||||
public static void appendFixedWidth( ITextComponent out, ICommandSender sender, ITextComponent entry, int maxWidth )
|
||||
{
|
||||
out.appendSibling( entry );
|
||||
|
||||
int width = getWidthFor( entry, sender );
|
||||
int delta = maxWidth - width;
|
||||
|
||||
int spaceWidth = getWidthFor( ' ', sender );
|
||||
int spaces = delta / spaceWidth;
|
||||
int extra = delta % spaces;
|
||||
|
||||
// Append a fixed number of spaces
|
||||
if( spaces > 0 ) out.appendSibling( new TextComponentString( StringUtils.repeat( ' ', spaces ) ) );
|
||||
|
||||
// Append several minor characters to pad to a full string
|
||||
if( extra > 0 )
|
||||
{
|
||||
out.appendSibling( coloured( StringUtils.repeat( PADDING_CHAR, extra ), TextFormatting.GRAY ) );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,170 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.network.ComputerCraftPacket;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.TextComponentString;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured;
|
||||
import static dan200.computercraft.shared.command.framework.ChatHelpers.text;
|
||||
import static dan200.computercraft.shared.command.framework.TextFormatter.*;
|
||||
|
||||
public class TextTable
|
||||
{
|
||||
private static final ITextComponent SEPARATOR = coloured( "| ", TextFormatting.GRAY );
|
||||
private static final ITextComponent LINE = text( "\n" );
|
||||
|
||||
private final int id;
|
||||
private int columns = -1;
|
||||
private final ITextComponent[] header;
|
||||
private final List<ITextComponent[]> rows = Lists.newArrayList();
|
||||
|
||||
public TextTable( int id, @Nonnull ITextComponent... header )
|
||||
{
|
||||
this.id = id;
|
||||
this.header = header;
|
||||
this.columns = header.length;
|
||||
}
|
||||
|
||||
public TextTable( int id )
|
||||
{
|
||||
this.id = id;
|
||||
this.header = null;
|
||||
}
|
||||
|
||||
public TextTable( int id, @Nonnull String... header )
|
||||
{
|
||||
this.id = id;
|
||||
this.header = new ITextComponent[header.length];
|
||||
for( int i = 0; i < header.length; i++ )
|
||||
{
|
||||
this.header[i] = ChatHelpers.header( header[i] );
|
||||
}
|
||||
this.columns = header.length;
|
||||
}
|
||||
|
||||
public void addRow( @Nonnull ITextComponent... row )
|
||||
{
|
||||
if( columns == -1 )
|
||||
{
|
||||
columns = row.length;
|
||||
}
|
||||
else if( row.length != columns )
|
||||
{
|
||||
throw new IllegalArgumentException( "Row is the incorrect length" );
|
||||
}
|
||||
|
||||
rows.add( row );
|
||||
}
|
||||
|
||||
public void displayTo( ICommandSender sender )
|
||||
{
|
||||
if( columns <= 0 ) return;
|
||||
|
||||
int[] maxWidths = new int[columns];
|
||||
|
||||
if( header != null )
|
||||
{
|
||||
for( int i = 0; i < columns; i++ )
|
||||
{
|
||||
maxWidths[i] = getWidthFor( header[i], sender );
|
||||
}
|
||||
}
|
||||
|
||||
// Limit the number of rows to fit within a single chat window on default Minecraft
|
||||
// options.
|
||||
int height = isPlayer( sender ) ? 18 : 100;
|
||||
int limit = rows.size() <= height ? rows.size() : height - 1;
|
||||
|
||||
for( int y = 0; y < limit; y++ )
|
||||
{
|
||||
ITextComponent[] row = rows.get( y );
|
||||
for( int i = 0; i < row.length; i++ )
|
||||
{
|
||||
int width = getWidthFor( row[i], sender );
|
||||
if( width > maxWidths[i] ) maxWidths[i] = width;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a small amount of extra padding. This defaults to 3 spaces for players
|
||||
// and 1 for everyone else.
|
||||
int padding = isPlayer( sender ) ? getWidth( ' ' ) * 3 : 1;
|
||||
for( int i = 0; i < maxWidths.length; i++ ) maxWidths[i] += padding;
|
||||
|
||||
int totalWidth = (columns - 1) * getWidthFor( SEPARATOR, sender );
|
||||
for( int x : maxWidths ) totalWidth += x;
|
||||
|
||||
// TODO: Limit the widths of some entries if totalWidth > maxWidth
|
||||
|
||||
List<ITextComponent> out = new ArrayList<>();
|
||||
|
||||
if( header != null )
|
||||
{
|
||||
TextComponentString line = new TextComponentString( "" );
|
||||
for( int i = 0; i < columns - 1; i++ )
|
||||
{
|
||||
appendFixedWidth( line, sender, header[i], maxWidths[i] );
|
||||
line.appendSibling( SEPARATOR );
|
||||
}
|
||||
line.appendSibling( header[columns - 1] );
|
||||
out.add( line );
|
||||
|
||||
// Round the width up rather than down
|
||||
int rowCharWidth = getWidthFor( '=', sender );
|
||||
int rowWidth = totalWidth / rowCharWidth + (totalWidth % rowCharWidth == 0 ? 0 : 1);
|
||||
out.add( coloured( StringUtils.repeat( '=', rowWidth ), TextFormatting.GRAY ) );
|
||||
}
|
||||
|
||||
for( int i = 0; i < limit; i++ )
|
||||
{
|
||||
TextComponentString line = new TextComponentString( "" );
|
||||
ITextComponent[] row = rows.get( i );
|
||||
for( int j = 0; j < columns - 1; j++ )
|
||||
{
|
||||
appendFixedWidth( line, sender, row[j], maxWidths[j] );
|
||||
line.appendSibling( SEPARATOR );
|
||||
}
|
||||
line.appendSibling( row[columns - 1] );
|
||||
out.add( line );
|
||||
}
|
||||
|
||||
if( rows.size() > limit )
|
||||
{
|
||||
out.add( coloured( (rows.size() - limit) + " additional rows...", TextFormatting.AQUA ) );
|
||||
}
|
||||
|
||||
if( isPlayer( sender ) && id != 0 )
|
||||
{
|
||||
ComputerCraftPacket packet = new ComputerCraftPacket();
|
||||
packet.m_packetType = ComputerCraftPacket.PostChat;
|
||||
packet.m_dataInt = new int[]{ id };
|
||||
|
||||
String[] lines = packet.m_dataString = new String[out.size()];
|
||||
for( int i = 0; i < out.size(); i++ )
|
||||
{
|
||||
lines[i] = ITextComponent.Serializer.componentToJson( out.get( i ) );
|
||||
}
|
||||
|
||||
ComputerCraft.sendToPlayer( (EntityPlayerMP) sender, packet );
|
||||
}
|
||||
else
|
||||
{
|
||||
ITextComponent result = new TextComponentString( "" );
|
||||
for( int i = 0; i < out.size(); i++ )
|
||||
{
|
||||
if( i > 0 ) result.appendSibling( LINE );
|
||||
result.appendSibling( out.get( i ) );
|
||||
}
|
||||
sender.sendMessage( result );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
package dan200.computercraft.shared.command.framework;
|
||||
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
/**
|
||||
* The level a user must be at in order to execute a command.
|
||||
*/
|
||||
public enum UserLevel
|
||||
{
|
||||
/**
|
||||
* Only can be used by the owner of the server: namely the server console or the player in SSP.
|
||||
*/
|
||||
OWNER,
|
||||
|
||||
/**
|
||||
* Can only be used by ops.
|
||||
*/
|
||||
OP,
|
||||
|
||||
/**
|
||||
* Can be used by any op, or the player in SSP.
|
||||
*/
|
||||
OWNER_OP,
|
||||
|
||||
/**
|
||||
* Can be used by anyone.
|
||||
*/
|
||||
ANYONE;
|
||||
|
||||
public int toLevel()
|
||||
{
|
||||
switch( this )
|
||||
{
|
||||
case OWNER:
|
||||
return 4;
|
||||
case OP:
|
||||
case OWNER_OP:
|
||||
return 2;
|
||||
case ANYONE:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canExecute( CommandContext context )
|
||||
{
|
||||
if( this == ANYONE ) return true;
|
||||
|
||||
// We *always* allow level 0 stuff, even if the
|
||||
MinecraftServer server = context.getServer();
|
||||
ICommandSender sender = context.getSender();
|
||||
|
||||
if( server.isSinglePlayer() && sender instanceof EntityPlayerMP &&
|
||||
((EntityPlayerMP) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerOwner() ) )
|
||||
{
|
||||
if( this == OWNER || this == OWNER_OP ) return true;
|
||||
}
|
||||
|
||||
return sender.canUseCommand( toLevel(), context.getRootCommand() );
|
||||
}
|
||||
}
|
@@ -36,7 +36,7 @@ public abstract class BlockGeneric extends Block implements
|
||||
protected BlockGeneric( Material material )
|
||||
{
|
||||
super( material );
|
||||
this.isBlockContainer = true;
|
||||
this.hasTileEntity = true;
|
||||
}
|
||||
|
||||
protected abstract IBlockState getDefaultBlockState( int damage, EnumFacing placedSide );
|
||||
|
@@ -14,29 +14,28 @@ public class ClientTerminal implements ITerminal
|
||||
private boolean m_colour;
|
||||
private Terminal m_terminal;
|
||||
private boolean m_terminalChanged;
|
||||
private boolean m_terminalChangedLastFrame;
|
||||
|
||||
public ClientTerminal( boolean colour )
|
||||
{
|
||||
m_colour = colour;
|
||||
m_terminal = null;
|
||||
m_terminalChanged = false;
|
||||
m_terminalChangedLastFrame = false;
|
||||
}
|
||||
|
||||
public void update()
|
||||
{
|
||||
m_terminalChangedLastFrame = m_terminalChanged || (m_terminal != null && m_terminal.getChanged());
|
||||
if( m_terminal != null )
|
||||
{
|
||||
m_terminalChanged |= m_terminal.getChanged();
|
||||
m_terminal.clearChanged();
|
||||
}
|
||||
m_terminalChanged = false;
|
||||
}
|
||||
|
||||
public boolean hasTerminalChanged()
|
||||
public boolean pollTerminalChanged()
|
||||
{
|
||||
return m_terminalChangedLastFrame;
|
||||
boolean changed = m_terminalChanged;
|
||||
m_terminalChanged = false;
|
||||
return changed;
|
||||
}
|
||||
|
||||
// ITerminal implementation
|
||||
|
@@ -86,7 +86,7 @@ public class ColourableRecipe extends IForgeRegistryEntry.Impl<IRecipe> implemen
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidden()
|
||||
public boolean isDynamic()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@@ -54,6 +54,11 @@ public class ServerTerminal implements ITerminal
|
||||
}
|
||||
}
|
||||
|
||||
protected void markTerminalChanged()
|
||||
{
|
||||
m_terminalChanged = true;
|
||||
}
|
||||
|
||||
public void update()
|
||||
{
|
||||
m_terminalChangedLastFrame = m_terminalChanged || (m_terminal != null && m_terminal.getChanged());
|
||||
|
@@ -42,7 +42,7 @@ public class BlockCommandComputer extends BlockComputerBase
|
||||
super( Material.IRON );
|
||||
setBlockUnbreakable();
|
||||
setResistance( 6000000.0F );
|
||||
setUnlocalizedName( "computercraft:command_computer" );
|
||||
setTranslationKey( "computercraft:command_computer" );
|
||||
setCreativeTab( ComputerCraft.mainCreativeTab );
|
||||
setDefaultState( this.blockState.getBaseState()
|
||||
.withProperty( Properties.FACING, EnumFacing.NORTH )
|
||||
@@ -62,7 +62,7 @@ public class BlockCommandComputer extends BlockComputerBase
|
||||
@Deprecated
|
||||
public IBlockState getStateFromMeta( int meta )
|
||||
{
|
||||
EnumFacing dir = EnumFacing.getFront( meta & 0x7 );
|
||||
EnumFacing dir = EnumFacing.byIndex( meta & 0x7 );
|
||||
if( dir.getAxis() == EnumFacing.Axis.Y )
|
||||
{
|
||||
dir = EnumFacing.NORTH;
|
||||
|
@@ -44,7 +44,7 @@ public class BlockComputer extends BlockComputerBase
|
||||
{
|
||||
super( Material.ROCK );
|
||||
setHardness( 2.0f );
|
||||
setUnlocalizedName( "computercraft:computer" );
|
||||
setTranslationKey( "computercraft:computer" );
|
||||
setCreativeTab( ComputerCraft.mainCreativeTab );
|
||||
setDefaultState( this.blockState.getBaseState()
|
||||
.withProperty( Properties.FACING, EnumFacing.NORTH )
|
||||
@@ -65,7 +65,7 @@ public class BlockComputer extends BlockComputerBase
|
||||
@Deprecated
|
||||
public IBlockState getStateFromMeta( int meta )
|
||||
{
|
||||
EnumFacing dir = EnumFacing.getFront( meta & 0x7 );
|
||||
EnumFacing dir = EnumFacing.byIndex( meta & 0x7 );
|
||||
if( dir.getAxis() == EnumFacing.Axis.Y )
|
||||
{
|
||||
dir = EnumFacing.NORTH;
|
||||
|
@@ -97,4 +97,11 @@ public class ComputerPeripheral
|
||||
{
|
||||
return (other != null && other.getClass() == this.getClass());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Object getTarget()
|
||||
{
|
||||
return m_computer.getTile();
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user