1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-16 14:37:39 +00:00

Compare commits

...

46 Commits

Author SHA1 Message Date
SquidDev
51e787f631 Bump version and update feature list 2018-10-24 12:49:35 +01:00
SquidDev
8080699030 Couple of minor improvements to the alternative mount implementation
- Rename openStreamFor* methods to more accurate openChannelFor*
 - Fix ArrayByteChannel having an incorrect .position() implementation

Cherry-picked from the PR against dan200/ComputerCraft
2018-10-24 12:39:22 +01:00
SquidDev
e555f9f7f0 Merge pull request #575 from SquidDev-CC/ComputerCraft/feature/file-seeking
Rewrite file systems to use ByteChannels
2018-10-24 12:20:53 +01:00
SquidDev
822db6e9b5 Add support for binary websockets
- Add an argument to send which controls whether it's a binary message
   or not. This is a little ugly, but it's probably more effective than
   anything else.
 - Fix binary frames not correctly queueing the correct data in the
   message event.

Closes #69
2018-10-23 12:15:40 +01:00
SquidDev
ac1f30ef43 Clamp the volume for all sounds, rather than just notes
This restores the previous behaviour.
2018-10-12 12:05:31 +01:00
SquidDev
0fc1b8c46b Merge pull request #578 from SquidDev-CC/ComputerCraft/feature/speaker-sound
Change speakers to use the SPacketCustomSound packet instead
2018-10-10 16:57:34 +01:00
SquidDev
e7c19bcf55 Change speakers to use the SPacketCustomSound packet instead
The method to register new SoundEvents is private, which means that few
(if any) mods actually register them. Consequently, one could not use
the speaker to play any modded sound, as they weren't registered on the
server side.

Using SPacketCustomSound does mean we can no longer determine if a sound
exists or not, but I think a price I'm willing to pay in order to allow
playing modded sounds.
2018-10-10 08:46:30 +01:00
SquidDev
63ca8aca4c Merge pull request #68 from Vexatos/patch-1
Create de_de.lang
2018-10-02 16:47:42 +01:00
Vexatos
2caa9c57fc Create de_de.lang 2018-10-02 15:43:43 +02:00
SquidDev
33fad2da15 Merge pull request #577 from SquidDev-CC/ComputerCraft/feature/get-blink
Add .getCursorBlink to monitors and terminals
2018-09-28 16:23:00 +01:00
SquidDev
518eefbe10 Rewrite file systems to use ByteChannels
This replaces the existing IMount openFor* method with openChannelFor*
ones, which return an appropriate byte channel instead.

As channels are not correctly closed when GCed, we introduce a
FileSystemWrapper. We store a weak reference to this, and when it is
GCed or the file closed, we will remove it from our "open file" set and
ensure any underlying buffers are closed.

While this change may seem a little odd, it does introduce some
benefits:

 - We can replace JarMount with a more general FileSystemMount. This
   does assume a read-only file system, but could technically be used
   for other sources.

 - Add support for seekable (binary) handles. We can now look for
   instances of SeekableByteChannel and dynamically add it. This works
   for all binary filesystem and HTTP streams.

 - Rewrite the io library to more accurately emulate PUC Lua's
   implementation. We do not correctly implement some elements (most
   noticably "*n", but it's a definite improvement.
2018-09-26 10:00:17 +01:00
SquidDev
1ba73454c1 Add .getCursorBlink to monitors and terminals
Closes #576
2018-09-23 09:34:28 +01:00
SquidDev
ee4735c17c Merge pull request #573 from osmarks/ComputerCraft/patch-1
Fix a crash in rednet `repeat`
2018-09-09 17:49:24 +01:00
Oliver Marks
b008edae90 Fix a crash in rednet repeat
This crash can be triggered remotely by specially constructed rednet messages, making this a bit of a problem, as any repeaters can be remotely crashed.
2018-09-08 21:55:36 +01:00
SquidDev
c6bd88f3ad Attempt to cut out a lot of synchronized calls
A lot of these don't actually have any effect as they'll only be called
on the main thread or they are getters where the state is guaranteed to
be consistent whenever it is accessed.

Hopefully this'll reduce the chance of world updates being blocked by
waiting for peripheral locks to be released.
2018-08-25 21:17:48 +01:00
SquidDev
efa57521c7 Bump version to 1.8pr1.8
While there haven't been a lot of changes, there's been a couple of bug
fixes and nice improvements.
2018-08-25 10:58:54 +01:00
SquidDev
4700f8831b Add a pull request template
Again, not entirely a fan of these, but there's been several PRs
which would have been better off targeting the original repo.
2018-08-24 17:37:20 +01:00
SquidDev
9428bee316 Merge pull request #65 from apemanzilla/fix/drop-consumer-overflow
Prevent stack overflows when using turtle.place() with a full inventory
2018-08-23 08:04:07 +01:00
apemanzilla
89c7183a1d Prevent stack overflows when using turtle.place() with a full inventory 2018-08-23 07:56:50 +01:00
SquidDev
d2a9e7e458 Reset a few more flags when rendering printouts
Closes #63
2018-08-13 22:25:58 +01:00
SquidDev
1774f1a079 Merge pull request #566 from SquidDev-CC/ComputerCraft/feature/tiny-lua-wins
A couple of small improvements to CraftOS
2018-08-12 15:49:19 +01:00
SquidDev
de1307913b A couple of small improvements to CraftOS
- Make window.reposition's argument validation a little more strict.
   Previously it would accept `window.reposition(x, y, width)` (no
   height argument), just not act upon it.
 - Use select instead of table.unpack within `pastebin run`.
 - Use `parallel.waitForAny` instead of `waitForAll` within the dance
   program.
 - Pipe the entire help file into `textutils.pagedPrint`, rather than
   doing it line by line.
 - Remove bytecode loading disabling from bios.lua. This never worked
   correctly, and serves little purpose as LuaJ is not vulnerable to
   such exploits.
2018-08-12 08:23:17 +01:00
SquidDev
093132533d Add charset bundled cable integration
- Bump MinecraftForge version so we don't crash on load. Oh boy, all
   the deprecation warnings.
 - Inject IBundledEmitter and IBundledReceiver capabilities onto all
   TileGenerics.
 - Register a IBundledRedstoneProvider instance for IBundledEmitter.
2018-08-11 10:49:21 +01:00
SquidDev
0685be6bfa Add issue templates
I'm not entirely a fan of massive templates, but there's 
been a couple of lacklustre issues recently, so it's probably
good to formalise my guidelines.
2018-08-04 10:52:26 +01:00
SquidDev
f40733e9a6 Error when missing computers after executing the command
This allows you to automate running various commands and still have them
"work" if some computers are not loaded.

Closes #47
2018-08-04 10:39:44 +01:00
SquidDev
a3d1cff298 Bump Cobalt version
This should allow us to do SquidDev-CC/mbs#18 due to the debug
improvements.
2018-08-03 20:58:20 +01:00
SquidDev
b8957cab5c Update to the latest mappings
This is a preliminary for updating to 1.13, as many of the name changes
apply to both. This will make it harder to remain consistent with
actual CC, though that will be less of a consideration when 1.13 hits.
2018-07-24 09:27:05 +01:00
SquidDev
3ac8dde779 Bump version 2018-07-09 21:02:20 +01:00
SquidDev
17dace979a Merge pull request #562 from SquidDev-CC/ComputerCraft/hotfix/turtle-sign-place
Be a little smarter about our detection of the placed sign
2018-07-09 20:57:42 +01:00
SquidDev
d405316a4b Be a little smarter about our detection of the placed sign
When placing a sign against a tile entity (such as a turtle or chest),
we would consider that block the "placed" one instead, meaning the text
was never set. This solution isn't entirely ideal either, but short of
capturing block snapshots I'm not sure of a better solution.

Fixes #552
2018-07-09 20:53:49 +01:00
SquidDev
7e18f2cead Clear the turtle's inventory on load 2018-07-09 18:22:42 +01:00
SquidDev
000786a1a7 Merge pull request #560 from SquidDev-CC/ComputerCraft/hotfix/turtle-destroy
Rewrite turtle block destroying
2018-07-08 23:01:13 +01:00
SquidDev
0bf13562b9 Provide a more direct way to get the related computer
Effectively shift extracting the computer away from Plethora into CC:T.
Ideally we wouldn't need this at all, but Plethora does some funky
things with tick timings.

See SquidDev-CC/plethora#125
2018-07-08 22:48:50 +01:00
SquidDev
45a189e834 Cache a turtle's fake player
Player construction can get a little expensive, and this is exacerbated
by Sponge. We now cache a turtle's fake player (updating the position
every time it is requested) in order to reduce this overhead.
2018-07-08 15:21:42 +01:00
SquidDev
0ce6f34a09 Rewrite printout rendering
- The current page is always centred when rendering in a GUI, with
   the turned pages moving from the sides.
 - Pages are no longer evenly distributed from the centre - they follow
   an exponential decay curve, so ones further out are closer together
   (a bit like an open book).
 - Render pages and books in item frames/in-hand (rather than just
   single pages).

This currently does some very dirty things with z values in order to
prevent z-fighting. It would be nice to avoid that, though turning off
writing to the z buffer causes issues with the bounding box.
2018-07-07 19:59:20 +01:00
SquidDev
4d984dc5ee Rewrite turtle block destroying
- Try to make drop capturing a little more generic. This now allows for
   capturing a block's drop at a given position, as well as any drop
   within a bounding box (for things which don't play nicely).

 - Use as much of Minecraft's block breaking logic as possible,
   hopefully simplifying things and making it more consistent with other
   mods.
2018-07-06 18:49:15 +01:00
SquidDev
984d358930 Provide .getNameLocal on modems
This provides the local modem's name on the remote network. Let's be
honest, I'll probably end up renaming this soon.
2018-06-29 20:29:04 +01:00
SquidDev
a95893b823 Send the terminal state to the pocket computer's player
As we only send the terminal to players using the GUI, the map interface
was never updated. We now will also send the terminal state to whoever
has the computer in their inventory.

This also marks the terminal as dirty when a new player picks the pocket
computer up, hopefully preventing any desync issues which might occur
then.

Fixes #42.
2018-06-24 19:18:30 +01:00
SquidDev
81aaead032 Improve using /computercraft as a non-player
- Fix text table only showing the first row (fixes #40)
 - Do not emit alignment characters in monospace environments
 - Reduce padding in monospace environments

Also make the output of dump consistent with that of the profiler: we
provide tp and view shortcuts for each computer.
2018-06-22 08:28:36 +01:00
SquidDev
a2083bcff1 Merge pull request #554 from SquidDev-CC/ComputerCraft/hotfix/gui-blemishes
Fix a couple of minor blemishes in the GUI textures
2018-06-19 22:24:32 +01:00
SquidDev
052f2a16dc Fix a couple of minor blemishes in the GUI textures
- Slight airbrush effect for normal turtles. While this is only really
   visible when upping the contrast, it's probably nice to fix.
 - A few off-colour pixels for advanced turtles
2018-06-19 22:18:48 +01:00
SquidDev
fd10ed6f62 Merge pull request #553 from SquidDev-CC/ComputerCraft/feature/fancy-printout
Fancy rendering of printouts
2018-06-19 19:44:51 +01:00
SquidDev
c0bdd4ff1d Add basic support for fancy rendering of printouts
- When held in first-person, single pages are displayed like a map.
 - When placed in an item frame, the page is drawn instead of the actual
   item.
2018-06-18 22:09:24 +01:00
SquidDev
5f0addbc3e Fix variable being declared too late 2018-06-18 19:01:36 +01:00
SquidDev
c9589ad0e7 Allow using any kind of skull for crafting heads
It's possible to acquire any mob head in vanilla, so it's probably best
if we allow any head to be used.
2018-06-02 11:12:29 +01:00
SquidDev
b21c495815 Update the README to be a bit more detailed
- Include a list of features, pretty similar to that on the forum
   thread or CurseForge page.
 - Replace CC: Tweaked with CC:T. After all, who wants to type all of
   that?
2018-05-15 12:27:04 +01:00
113 changed files with 2816 additions and 1719 deletions

16
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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.

View 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
View 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.

View File

@@ -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`.

View File

@@ -23,12 +23,12 @@ apply plugin: 'org.ajoberstar.grgit'
apply plugin: 'maven-publish'
apply plugin: 'maven'
version = "1.80pr1.6"
version = "1.80pr1.9"
group = "org.squiddev"
archivesBaseName = "cc-tweaked"
minecraft {
version = "1.12.2-14.23.2.2634"
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_20180324"
mappings = "snapshot_20180724"
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
}
@@ -50,6 +50,8 @@ repositories {
name = "squiddev"
url = "https://dl.bintray.com/squiddev/maven"
}
ivy { artifactPattern "https://asie.pl/files/mods/Charset/LibOnly/[module]-[revision](-[classifier]).[ext]" }
}
configurations {
@@ -60,8 +62,11 @@ configurations {
dependencies {
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.1'
shade 'org.squiddev:Cobalt:0.3.2'
testCompile 'junit:junit:4.11'
@@ -126,7 +131,7 @@ curseforge {
project {
id = '282001'
releaseType = 'beta'
changelog = ''
changelog = "Release notes can be found on the GitHub repository (https://github.com/SquidDev-CC/CC-Tweaked/releases/tag/v${project.version})."
}
}

View File

@@ -26,7 +26,7 @@ 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;
@@ -97,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;
@@ -921,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
}
}
@@ -943,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
@@ -959,7 +964,7 @@ public class ComputerCraft
}
catch( IOException e )
{
// Ignore
ComputerCraft.log.error( "Could not load resource pack '" + resourcePack1 + "'", e );
}
}
}
@@ -1108,7 +1113,7 @@ public class ComputerCraft
{
return turtleProxy.getTurtleUpgrade( id );
}
public static ITurtleUpgrade getTurtleUpgrade( int legacyID )
{
return turtleProxy.getTurtleUpgrade( legacyID );
@@ -1124,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();
}
}

View File

@@ -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 ) );
}
}

View File

@@ -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.

View File

@@ -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 );
}
}

View File

@@ -9,6 +9,7 @@ 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;
@@ -497,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

View File

@@ -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 ) );
}
}

View File

@@ -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 ));
}
}

View File

@@ -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 );

View File

@@ -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

View File

@@ -9,19 +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;
@@ -221,38 +225,38 @@ public class FSAPI implements ILuaAPI
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" );

View File

@@ -9,17 +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();

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -1,122 +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.ByteArrayOutputStream;
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 static final int BUFFER_SIZE = 8192;
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 )
{
// Whilst this may seem absurd to allow reading 0 bytes, PUC Lua it so
// it seems best to remain somewhat consistent.
throw new LuaException( "Cannot read a negative number of bytes" );
}
else if( count <= BUFFER_SIZE )
{
// If we've got a small count, then allocate that and read it.
byte[] bytes = new byte[ count ];
int read = m_stream.read( bytes );
if( read < 0 ) return null;
if( read < count ) bytes = Arrays.copyOf( bytes, read );
return new Object[] { bytes };
}
else
{
byte[] buffer = new byte[ BUFFER_SIZE ];
// Read the initial set of bytes, failing if none are read.
int read = m_stream.read( buffer, 0, Math.min( buffer.length, count ) );
if( read == -1 ) return null;
ByteArrayOutputStream out = new ByteArrayOutputStream( read );
count -= read;
out.write( buffer, 0, read );
// Otherwise read until we either reach the limit or we no longer consume
// the full buffer.
while( read >= buffer.length && count > 0 )
{
read = m_stream.read( buffer, 0, Math.min( BUFFER_SIZE, count ) );
if( read == -1 ) break;
count -= read;
out.write( buffer, 0, read );
}
return new Object[] { out.toByteArray() };
}
}
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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -4,45 +4,32 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import java.io.*;
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 EncodedInputHandle extends HandleGeneric
public class EncodedReadableHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
private final BufferedReader m_reader;
private BufferedReader m_reader;
public EncodedInputHandle( BufferedReader reader )
public EncodedReadableHandle( @Nonnull BufferedReader reader, @Nonnull Closeable closable )
{
super( reader );
super( closable );
this.m_reader = reader;
}
public EncodedInputHandle( InputStream stream )
public EncodedReadableHandle( @Nonnull BufferedReader reader )
{
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 );
this( reader, reader );
}
@Nonnull
@@ -63,13 +50,17 @@ public class EncodedInputHandle extends HandleGeneric
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
@@ -81,6 +72,7 @@ public class EncodedInputHandle extends HandleGeneric
{
return null;
}
}
case 1:
// readAll
checkOpen();
@@ -108,7 +100,6 @@ public class EncodedInputHandle extends HandleGeneric
close();
return null;
case 3:
// read
checkOpen();
try
{
@@ -161,4 +152,14 @@ public class EncodedInputHandle extends HandleGeneric
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 ) );
}
}

View File

@@ -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 ) );
}
}

View File

@@ -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;
}
}
}

View File

@@ -13,13 +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;
@@ -182,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<>();
@@ -193,9 +201,11 @@ public class HTTPRequest implements Runnable
m_environment.addTrackingChange( TrackingField.HTTP_DOWNLOAD,
getHeaderSize( connection.getHeaderFields() ) + result.length );
InputStream contents = new ByteArrayInputStream( result );
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
);

View File

@@ -13,7 +13,9 @@ 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;
@@ -26,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";
@@ -174,8 +178,11 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
{
checkOpen();
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( new TextWebSocketFrame( text ) );
channel.writeAndFlush( binary
? new BinaryWebSocketFrame( Unpooled.wrappedBuffer( StringUtil.encodeString( text ) ) )
: new TextWebSocketFrame( text ) );
return null;
}
case 2:

View File

@@ -0,0 +1,9 @@
package dan200.computercraft.core.computer;
import javax.annotation.Nullable;
public interface IComputerOwned
{
@Nullable
Computer getComputer();
}

View 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.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" );
}
}

View 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" );
}
}

View 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() )

View File

@@ -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 );
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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() );
}
}
}

View File

@@ -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" );
}
}

View 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 )

View File

@@ -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 );

View File

@@ -20,10 +20,10 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.world.World;
import net.minecraftforge.common.util.FakePlayer;
import javax.annotation.Nonnull;
import java.util.*;
import java.util.function.Consumer;
import static dan200.computercraft.shared.command.framework.ChatHelpers.*;
@@ -59,7 +59,7 @@ public final class CommandComputerCraft extends CommandDelegate
{
if( arguments.size() == 0 )
{
TextTable table = new TextTable( DUMP_LIST_ID, "Instance", "Id", "On", "Position" );
TextTable table = new TextTable( DUMP_LIST_ID, "Computer", "On", "Position" );
List<ServerComputer> computers = new ArrayList<>( ComputerCraft.serverComputerRegistry.getComputers() );
@@ -92,8 +92,7 @@ public final class CommandComputerCraft extends CommandDelegate
for( ServerComputer computer : computers )
{
table.addRow(
linkComputer( computer ),
text( Integer.toString( computer.getID() ) ),
linkComputer( context, computer, computer.getID() ),
bool( computer.isOn() ),
linkPosition( context, computer )
);
@@ -149,26 +148,15 @@ public final class CommandComputerCraft extends CommandDelegate
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
{
Set<ServerComputer> computers = Sets.newHashSet();
if( arguments.size() > 0 )
{
for( String arg : arguments )
withComputers( arguments, computers -> {
int shutdown = 0;
for( ServerComputer computer : computers )
{
computers.addAll( ComputerSelector.getComputers( arg ) );
if( computer.isOn() ) shutdown++;
computer.unload();
}
}
else
{
computers.addAll( ComputerCraft.serverComputerRegistry.getComputers() );
}
int shutdown = 0;
for( ServerComputer computer : computers )
{
if( computer.isOn() ) shutdown++;
computer.unload();
}
context.getSender().sendMessage( text( "Shutdown " + shutdown + " / " + computers.size() + " computers" ) );
context.getSender().sendMessage( text( "Shutdown " + shutdown + " / " + computers.size() + " computers" ) );
} );
}
@Nonnull
@@ -190,26 +178,15 @@ public final class CommandComputerCraft extends CommandDelegate
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
{
Set<ServerComputer> computers = Sets.newHashSet();
if( arguments.size() > 0 )
{
for( String arg : arguments )
withComputers( arguments, computers -> {
int on = 0;
for( ServerComputer computer : computers )
{
computers.addAll( ComputerSelector.getComputers( arg ) );
if( !computer.isOn() ) on++;
computer.turnOn();
}
}
else
{
computers.addAll( ComputerCraft.serverComputerRegistry.getComputers() );
}
int on = 0;
for( ServerComputer computer : computers )
{
if( !computer.isOn() ) on++;
computer.turnOn();
}
context.getSender().sendMessage( text( "Turned on " + on + " / " + computers.size() + " computers" ) );
context.getSender().sendMessage( text( "Turned on " + on + " / " + computers.size() + " computers" ) );
} );
}
@Nonnull
@@ -406,13 +383,46 @@ public final class CommandComputerCraft extends CommandDelegate
return root;
}
private static ITextComponent linkComputer( ServerComputer computer )
private static ITextComponent linkComputer( CommandContext context, ServerComputer serverComputer, int computerId )
{
return link(
text( Integer.toString( computer.getInstanceID() ) ),
"/computercraft dump " + computer.getInstanceID(),
"View more info about this computer"
);
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 )
@@ -458,9 +468,6 @@ public final class CommandComputerCraft extends CommandDelegate
if( server.getID() > maxId ) maxId = server.getID();
}
ICommandSender sender = context.getSender();
boolean isPlayer = sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
timings.sort( Comparator.<ComputerTracker, Long>comparing( x -> x.get( field ) ).reversed() );
boolean defaultLayout = field == TrackingField.TASKS || field == TrackingField.TOTAL_TIME
@@ -476,26 +483,7 @@ public final class CommandComputerCraft extends CommandDelegate
Computer computer = entry.getComputer();
ServerComputer serverComputer = computer == null ? null : lookup.get( computer );
ITextComponent computerComponent = new TextComponentString( "" )
.appendSibling( serverComputer == null ? text( "?" ) : linkComputer( serverComputer ) )
.appendText( " (id " + entry.getComputerId() + ")" );
if( serverComputer != null && UserLevel.OP.canExecute( context ) && isPlayer )
{
computerComponent
.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"
) );
}
ITextComponent computerComponent = linkComputer( context, serverComputer, entry.getComputerId() );
if( defaultLayout )
{
@@ -515,4 +503,30 @@ public final class CommandComputerCraft extends CommandDelegate
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 ) );
}
}
}

View File

@@ -12,30 +12,22 @@ import java.util.function.Predicate;
public final class ComputerSelector
{
private static List<ServerComputer> getComputers( Predicate<ServerComputer> predicate, String selector ) throws CommandException
private static List<ServerComputer> getComputers( Predicate<ServerComputer> predicate ) throws CommandException
{
// We copy it to prevent concurrent modifications.
List<ServerComputer> computers = Lists.newArrayList( ComputerCraft.serverComputerRegistry.getComputers() );
List<ServerComputer> candidates = Lists.newArrayList();
for( ServerComputer searchComputer : computers )
{
if( predicate.test( searchComputer ) ) candidates.add( searchComputer );
}
if( candidates.isEmpty() )
{
throw new CommandException( "No computer matching " + selector );
}
else
{
return candidates;
}
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() == 1 )
if( computers.size() == 0 )
{
throw new CommandException( "No computer matching " + selector );
}
else if( computers.size() == 1 )
{
return computers.get( 0 );
}
@@ -46,7 +38,7 @@ public final class ComputerSelector
for( int i = 0; i < computers.size(); i++ )
{
if( i > 1 ) builder.append( ", " );
if( i > 0 ) builder.append( ", " );
builder.append( computers.get( i ).getInstanceID() );
}
builder.append( ")" );
@@ -71,17 +63,17 @@ public final class ComputerSelector
throw new CommandException( "'" + selector + "' is not a valid number" );
}
return getComputers( x -> x.getID() == id, selector );
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() ), selector );
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 ), selector );
return getComputers( x -> x.getFamily().name().equalsIgnoreCase( familyName ) );
}
else
{
@@ -96,14 +88,7 @@ public final class ComputerSelector
}
ServerComputer computer = ComputerCraft.serverComputerRegistry.get( instance );
if( computer == null )
{
throw new CommandException( "No such computer for instance id " + instance );
}
else
{
return Collections.singletonList( computer );
}
return computer == null ? Collections.emptyList() : Collections.singletonList( computer );
}
}

View File

@@ -2,7 +2,9 @@ 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;
@@ -90,4 +92,9 @@ public final class CommandContext
{
return sender;
}
public boolean fromPlayer()
{
return sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
}
}

View File

@@ -17,7 +17,6 @@ import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured
*/
public class TextFormatter
{
private static final int SPACE_WIDTH = 4;
private static final char PADDING_CHAR = '\u02cc';
/**
@@ -54,7 +53,7 @@ public class TextFormatter
8, 4,
};
private static int getWidth( int codePoint )
public static int getWidth( int codePoint )
{
// Escape codes
if( codePoint == 167 ) return -1;
@@ -73,7 +72,7 @@ public class TextFormatter
return 0;
}
private static int getWidth( ITextComponent component )
public static int getWidth( ITextComponent component )
{
int total = 0;
if( component instanceof TextComponentString )
@@ -132,19 +131,17 @@ public class TextFormatter
int width = getWidthFor( entry, sender );
int delta = maxWidth - width;
if( delta > 0 )
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 )
{
int spaces = delta / SPACE_WIDTH;
int extra = delta % SPACE_WIDTH;
// 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 ) );
}
out.appendSibling( coloured( StringUtils.repeat( PADDING_CHAR, extra ), TextFormatting.GRAY ) );
}
}
}

View File

@@ -35,7 +35,7 @@ public class TextTable
this.columns = header.length;
}
public TextTable(int id)
public TextTable( int id )
{
this.id = id;
this.header = null;
@@ -90,14 +90,15 @@ public class TextTable
ITextComponent[] row = rows.get( y );
for( int i = 0; i < row.length; i++ )
{
int width = getWidthFor( row[i], sender ) + 3;
int width = getWidthFor( row[i], sender );
if( width > maxWidths[i] ) maxWidths[i] = width;
}
}
// Add a small amount of extra padding. We include this here instead of the separator to allow
// for "extra" characters
for( int i = 0; i < maxWidths.length; i++ ) maxWidths[i] += 4;
// 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;
@@ -161,7 +162,7 @@ public class TextTable
for( int i = 0; i < out.size(); i++ )
{
if( i > 0 ) result.appendSibling( LINE );
result.appendSibling( out.get( 0 ) );
result.appendSibling( out.get( i ) );
}
sender.sendMessage( result );
}

View File

@@ -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());

View File

@@ -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;

View File

@@ -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;

View File

@@ -154,7 +154,7 @@ public class ServerComputer extends ServerTerminal
return packet;
}
private ComputerCraftPacket createTerminalPacket() {
protected ComputerCraftPacket createTerminalPacket() {
ComputerCraftPacket packet = new ComputerCraftPacket();
packet.m_packetType = ComputerCraftPacket.ComputerTerminalChanged;
packet.m_dataInt = new int[] { getInstanceID() };
@@ -465,7 +465,7 @@ public class ServerComputer extends ServerTerminal
}
}
private boolean isInteracting( EntityPlayer player )
protected boolean isInteracting( EntityPlayer player )
{
if( player == null ) return false;

View File

@@ -24,7 +24,7 @@ public class ItemCommandComputer extends ItemComputer
super( block );
setMaxStackSize( 64 );
setHasSubtypes( true );
setUnlocalizedName( "computercraft:command_computer" );
setTranslationKey( "computercraft:command_computer" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}

View File

@@ -33,7 +33,7 @@ public class ItemComputer extends ItemComputerBase
super( block );
setMaxStackSize( 64 );
setHasSubtypes( true );
setUnlocalizedName( "computercraft:computer" );
setTranslationKey( "computercraft:computer" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}
@@ -114,7 +114,7 @@ public class ItemComputer extends ItemComputerBase
@Nonnull
@Override
public String getUnlocalizedName( @Nonnull ItemStack stack )
public String getTranslationKey( @Nonnull ItemStack stack )
{
switch( getFamily( stack ) )
{

View File

@@ -0,0 +1,88 @@
/*
* 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.shared.integration.charset;
import dan200.computercraft.shared.common.TileGeneric;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import pl.asie.charset.api.wires.IBundledEmitter;
import pl.asie.charset.api.wires.IBundledReceiver;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.shared.integration.charset.IntegrationCharset.CAPABILITY_EMITTER;
import static dan200.computercraft.shared.integration.charset.IntegrationCharset.CAPABILITY_RECEIVER;
final class BundledCapabilityProvider implements ICapabilityProvider
{
private final TileGeneric tile;
private IBundledReceiver receiver;
private IBundledEmitter[] emitters;
BundledCapabilityProvider( TileGeneric tile )
{
this.tile = tile;
}
@Override
public boolean hasCapability( @Nonnull Capability<?> capability, @Nullable EnumFacing side )
{
return capability == CAPABILITY_EMITTER || capability == CAPABILITY_RECEIVER;
}
@Nullable
@Override
public <T> T getCapability( @Nonnull Capability<T> capability, @Nullable EnumFacing side )
{
if( capability == CAPABILITY_RECEIVER )
{
IBundledReceiver receiver = this.receiver;
if( receiver == null ) receiver = this.receiver = tile::onNeighbourChange;
return CAPABILITY_RECEIVER.cast( receiver );
}
else if( capability == CAPABILITY_EMITTER )
{
IBundledEmitter[] emitters = this.emitters;
if( emitters == null ) emitters = this.emitters = new IBundledEmitter[7];
int index = side == null ? 6 : side.getIndex();
IBundledEmitter emitter = emitters[index];
if( emitter == null )
{
if( side == null )
{
emitter = emitters[index] = () -> {
int flags = 0;
for( EnumFacing facing : EnumFacing.VALUES ) flags |= tile.getBundledRedstoneOutput( facing );
return toBytes( flags );
};
}
else
{
emitter = emitters[index] = () -> toBytes( tile.getBundledRedstoneOutput( side ) );
}
}
;
return CAPABILITY_EMITTER.cast( emitter );
}
else
{
return null;
}
}
private static byte[] toBytes( int flag )
{
byte[] channels = new byte[16];
for( int i = 0; i < 16; i++ ) channels[i] = (flag & (1 << i)) == 0 ? (byte) 0 : 15;
return channels;
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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.shared.integration.charset;
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import static dan200.computercraft.shared.integration.charset.IntegrationCharset.CAPABILITY_EMITTER;
public class BundledRedstoneProvider implements IBundledRedstoneProvider
{
@Override
public int getBundledRedstoneOutput( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull EnumFacing side )
{
TileEntity tile = world.getTileEntity( pos );
if( tile == null || !tile.hasCapability( CAPABILITY_EMITTER, side ) ) return -1;
byte[] signal = tile.getCapability( CAPABILITY_EMITTER, side ).getBundledSignal();
if( signal == null ) return -1;
int flag = 0;
for( int i = 0; i < signal.length; i++ ) flag |= signal[i] > 0 ? (1 << i) : 0;
return flag;
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.shared.integration.charset;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.common.TileGeneric;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.capabilities.CapabilityInject;
import net.minecraftforge.event.AttachCapabilitiesEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import pl.asie.charset.api.wires.IBundledEmitter;
import pl.asie.charset.api.wires.IBundledReceiver;
public final class IntegrationCharset
{
private static final ResourceLocation CAPABILITY_KEY = new ResourceLocation( ComputerCraft.MOD_ID, "charset" );
@CapabilityInject( IBundledEmitter.class )
public static final Capability<IBundledEmitter> CAPABILITY_EMITTER = null;
@CapabilityInject( IBundledReceiver.class )
public static final Capability<IBundledReceiver> CAPABILITY_RECEIVER = null;
private IntegrationCharset()
{
}
public static void register()
{
if( CAPABILITY_EMITTER == null || CAPABILITY_RECEIVER == null ) return;
MinecraftForge.EVENT_BUS.register( new IntegrationCharset() );
ComputerCraftAPI.registerBundledRedstoneProvider( new BundledRedstoneProvider() );
}
@SubscribeEvent
public void attachGenericCapabilities( AttachCapabilitiesEvent<TileEntity> event)
{
TileEntity tile = event.getObject();
if(tile instanceof TileGeneric)
{
event.addCapability( CAPABILITY_KEY, new BundledCapabilityProvider( (TileGeneric) tile ) );
}
}
}

View File

@@ -33,7 +33,7 @@ public class ItemDiskLegacy extends Item
{
setMaxStackSize( 1 );
setHasSubtypes( true );
setUnlocalizedName( "computercraft:disk" );
setTranslationKey( "computercraft:disk" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}

View File

@@ -39,7 +39,7 @@ public class ItemPrintout extends Item
{
setMaxStackSize( 1 );
setHasSubtypes( true );
setUnlocalizedName( "computercraft:page" );
setTranslationKey( "computercraft:page" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}
@@ -64,7 +64,7 @@ public class ItemPrintout extends Item
@Nonnull
@Override
public String getUnlocalizedName( @Nonnull ItemStack stack )
public String getTranslationKey( @Nonnull ItemStack stack )
{
Type type = getType( stack );
switch( type )

View File

@@ -34,7 +34,7 @@ public class ItemTreasureDisk extends Item
{
setMaxStackSize( 1 );
setHasSubtypes( true );
setUnlocalizedName( "computercraft:treasure_disk" );
setTranslationKey( "computercraft:treasure_disk" );
}
@Override

View File

@@ -55,7 +55,7 @@ public class BlockCable extends BlockPeripheralBase
public BlockCable()
{
setHardness( 1.5f );
setUnlocalizedName( "computercraft:cable" );
setTranslationKey( "computercraft:cable" );
setCreativeTab( ComputerCraft.mainCreativeTab );
setDefaultState( this.blockState.getBaseState()
.withProperty( Properties.MODEM, BlockCableModemVariant.None )
@@ -94,12 +94,12 @@ public class BlockCable extends BlockPeripheralBase
if( meta < 6 )
{
state = state.withProperty( Properties.CABLE, BlockCableCableVariant.NONE );
state = state.withProperty( Properties.MODEM, BlockCableModemVariant.fromFacing( EnumFacing.getFront( meta ) ) );
state = state.withProperty( Properties.MODEM, BlockCableModemVariant.fromFacing( EnumFacing.byIndex( meta ) ) );
}
else if( meta < 12 )
{
state = state.withProperty( Properties.CABLE, BlockCableCableVariant.ANY );
state = state.withProperty( Properties.MODEM, BlockCableModemVariant.fromFacing( EnumFacing.getFront( meta - 6 ) ) );
state = state.withProperty( Properties.MODEM, BlockCableModemVariant.fromFacing( EnumFacing.byIndex( meta - 6 ) ) );
}
else if( meta == 13 )
{
@@ -296,7 +296,7 @@ public class BlockCable extends BlockPeripheralBase
}
}
return result == null ? null : new RayTraceResult( result.hitVec.addVector( pos.getX(), pos.getY(), pos.getZ() ), result.sideHit, pos );
return result == null ? null : new RayTraceResult( result.hitVec.add( pos.getX(), pos.getY(), pos.getZ() ), result.sideHit, pos );
}
else
{

View File

@@ -44,7 +44,7 @@ public class BlockPeripheral extends BlockPeripheralBase
public BlockPeripheral()
{
setHardness( 2.0f );
setUnlocalizedName( "computercraft:peripheral" );
setTranslationKey( "computercraft:peripheral" );
setCreativeTab( ComputerCraft.mainCreativeTab );
setDefaultState( this.blockState.getBaseState()
.withProperty( Properties.FACING, EnumFacing.NORTH )
@@ -55,7 +55,7 @@ public class BlockPeripheral extends BlockPeripheralBase
@Override
@Nonnull
@SideOnly( Side.CLIENT)
public BlockRenderLayer getBlockLayer()
public BlockRenderLayer getRenderLayer()
{
return BlockRenderLayer.CUTOUT;
}
@@ -76,7 +76,7 @@ public class BlockPeripheral extends BlockPeripheralBase
if( meta >= 2 && meta <= 5 )
{
state = state.withProperty( Properties.VARIANT, BlockPeripheralVariant.DiskDriveEmpty );
state = state.withProperty( Properties.FACING, EnumFacing.getFront( meta ) );
state = state.withProperty( Properties.FACING, EnumFacing.byIndex( meta ) );
}
else if( meta <= 9 )
{
@@ -93,7 +93,7 @@ public class BlockPeripheral extends BlockPeripheralBase
else
{
state = state.withProperty( Properties.VARIANT, BlockPeripheralVariant.WirelessModemOff );
state = state.withProperty( Properties.FACING, EnumFacing.getFront( meta - 4 ) );
state = state.withProperty( Properties.FACING, EnumFacing.byIndex( meta - 4 ) );
}
}
else if( meta == 10 )
@@ -651,7 +651,7 @@ public class BlockPeripheral extends BlockPeripheralBase
public boolean causesSuffocation(IBlockState state)
{
// This normally uses the default state
return blockMaterial.blocksMovement() && state.isOpaqueCube();
return material.blocksMovement() && state.isOpaqueCube();
}
@Override

View File

@@ -34,7 +34,7 @@ public class BlockWiredModemFull extends BlockPeripheralBase
public BlockWiredModemFull()
{
setHardness( 1.5f );
setUnlocalizedName( "computercraft:wired_modem_full" );
setTranslationKey( "computercraft:wired_modem_full" );
setCreativeTab( ComputerCraft.mainCreativeTab );
setDefaultState( blockState.getBaseState()
.withProperty( Properties.MODEM_ON, false )

View File

@@ -21,7 +21,7 @@ public class ItemAdvancedModem extends ItemPeripheralBase
public ItemAdvancedModem( Block block )
{
super( block );
setUnlocalizedName( "computercraft:advanced_modem" );
setTranslationKey( "computercraft:advanced_modem" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}

View File

@@ -28,7 +28,7 @@ public class ItemCable extends ItemPeripheralBase
public ItemCable( Block block )
{
super( block );
setUnlocalizedName( "computercraft:cable" );
setTranslationKey( "computercraft:cable" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}

View File

@@ -20,7 +20,7 @@ public class ItemPeripheral extends ItemPeripheralBase
public ItemPeripheral( Block block )
{
super( block );
setUnlocalizedName( "computercraft:peripheral" );
setTranslationKey( "computercraft:peripheral" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}

View File

@@ -59,7 +59,7 @@ public abstract class ItemPeripheralBase extends ItemBlock implements IPeriphera
@Nonnull
@Override
public String getUnlocalizedName( @Nonnull ItemStack stack )
public String getTranslationKey( @Nonnull ItemStack stack )
{
PeripheralType type = getPeripheralType( stack );
switch( type )

View File

@@ -112,12 +112,12 @@ public abstract class TilePeripheralBase extends TileGeneric
}
}
public synchronized int getAnim()
public int getAnim()
{
return m_anim;
}
public synchronized void setAnim( int anim )
public void setAnim( int anim )
{
if( anim != m_anim )
{
@@ -127,12 +127,12 @@ public abstract class TilePeripheralBase extends TileGeneric
}
@Override
public synchronized void update()
public void update()
{
if( m_changed )
{
updateBlock();
m_changed = false;
updateBlock();
}
}
@@ -143,7 +143,7 @@ public abstract class TilePeripheralBase extends TileGeneric
super.readFromNBT(nbttagcompound);
if( nbttagcompound.hasKey( "dir" ) )
{
m_dir = EnumFacing.getFront( nbttagcompound.getInteger( "dir" ) );
m_dir = EnumFacing.byIndex( nbttagcompound.getInteger( "dir" ) );
}
if( nbttagcompound.hasKey( "anim" ) )
{
@@ -174,7 +174,7 @@ public abstract class TilePeripheralBase extends TileGeneric
public void readDescription( @Nonnull NBTTagCompound nbttagcompound )
{
super.readDescription( nbttagcompound );
m_dir = EnumFacing.getFront( nbttagcompound.getInteger( "dir" ) );
m_dir = EnumFacing.byIndex( nbttagcompound.getInteger( "dir" ) );
m_anim = nbttagcompound.getInteger( "anim" );
if( nbttagcompound.hasKey( "label" ) )
{

View File

@@ -80,12 +80,9 @@ public class TileDiskDrive extends TilePeripheralBase
public void destroy()
{
ejectContents( true );
synchronized( this )
if( m_recordPlaying )
{
if( m_recordPlaying )
{
stopRecord();
}
stopRecord();
}
}
@@ -170,13 +167,10 @@ public class TileDiskDrive extends TilePeripheralBase
super.update();
// Ejection
synchronized( this )
if( m_ejectQueued )
{
if( m_ejectQueued )
{
ejectContents( false );
m_ejectQueued = false;
}
ejectContents( false );
m_ejectQueued = false;
}
// Music
@@ -391,10 +385,7 @@ public class TileDiskDrive extends TilePeripheralBase
@Override
public void clear()
{
synchronized( this )
{
setInventorySlotContents( 0, ItemStack.EMPTY );
}
setInventorySlotContents( 0, ItemStack.EMPTY );
}
@Override
@@ -425,18 +416,12 @@ public class TileDiskDrive extends TilePeripheralBase
@Nonnull
public ItemStack getDiskStack()
{
synchronized( this )
{
return getStackInSlot( 0 );
}
return getStackInSlot( 0 );
}
public void setDiskStack( @Nonnull ItemStack stack )
{
synchronized( this )
{
setInventorySlotContents( 0, stack );
}
setInventorySlotContents( 0, stack );
}
public IMedia getDiskMedia()
@@ -569,7 +554,7 @@ public class TileDiskDrive extends TilePeripheralBase
}
}
private synchronized void updateAnim()
private void updateAnim()
{
if( !m_diskStack.isEmpty() )
{
@@ -605,8 +590,8 @@ public class TileDiskDrive extends TilePeripheralBase
if( !destroyed )
{
EnumFacing dir = getDirection();
xOff = dir.getFrontOffsetX();
zOff = dir.getFrontOffsetZ();
xOff = dir.getXOffset();
zOff = dir.getZOffset();
}
BlockPos pos = getPos();

View File

@@ -33,7 +33,7 @@ public class BlockAdvancedModem extends BlockPeripheralBase
public BlockAdvancedModem()
{
setHardness( 2.0f );
setUnlocalizedName( "computercraft:advanced_modem" );
setTranslationKey( "computercraft:advanced_modem" );
setCreativeTab( ComputerCraft.mainCreativeTab );
setDefaultState( this.blockState.getBaseState()
.withProperty( Properties.FACING, EnumFacing.NORTH )
@@ -54,7 +54,7 @@ public class BlockAdvancedModem extends BlockPeripheralBase
public IBlockState getStateFromMeta( int meta )
{
IBlockState state = getDefaultState();
state = state.withProperty( Properties.FACING, EnumFacing.getFront( meta ) );
state = state.withProperty( Properties.FACING, EnumFacing.byIndex( meta ) );
state = state.withProperty( Properties.ON, false );
return state;
}

View File

@@ -75,7 +75,7 @@ public abstract class ModemPeripheral
m_open = false;
}
public synchronized boolean pollChanged()
public boolean pollChanged()
{
if( m_changed )
{
@@ -85,9 +85,9 @@ public abstract class ModemPeripheral
return false;
}
public synchronized boolean isActive()
public boolean isActive()
{
return (m_computer != null) && m_open;
return m_computer != null && m_open;
}
@Override

View File

@@ -108,10 +108,11 @@ public class TileCable extends TileModemBase
m_node = m_cable.getNode();
return new WiredModemPeripheral( m_cable )
{
@Nonnull
@Override
protected boolean canSeePeripheral( @Nonnull String peripheralName )
protected WiredModemLocalPeripheral getLocalPeripheral()
{
return !peripheralName.equals( m_peripheral.getConnectedName() );
return m_peripheral;
}
@Nonnull

View File

@@ -37,7 +37,7 @@ public abstract class TileModemBase extends TilePeripheralBase
protected abstract ModemPeripheral createPeripheral();
@Override
public synchronized void destroy()
public void destroy()
{
if( m_modem != null )
{

View File

@@ -401,12 +401,14 @@ public class TileWiredModemFull extends TilePeripheralBase
WiredModemPeripheral peripheral = m_modems[side.ordinal()];
if( peripheral == null )
{
WiredModemLocalPeripheral localPeripheral = m_peripherals[side.ordinal()];
peripheral = m_modems[side.ordinal()] = new WiredModemPeripheral( m_element )
{
@Nonnull
@Override
protected boolean canSeePeripheral( @Nonnull String peripheralName )
protected WiredModemLocalPeripheral getLocalPeripheral()
{
return !peripheralName.equals( m_peripherals[side.ordinal()].getConnectedName() );
return localPeripheral;
}
@Nonnull

View File

@@ -10,6 +10,8 @@ import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.network.wired.IWiredSender;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.IComputerOwned;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
@@ -56,7 +58,8 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
return modem.getWorld();
}
protected abstract boolean canSeePeripheral( @Nonnull String peripheralName );
@Nonnull
protected abstract WiredModemLocalPeripheral getLocalPeripheral();
//endregion
//region IPeripheral
@@ -65,13 +68,14 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
public String[] getMethodNames()
{
String[] methods = super.getMethodNames();
String[] newMethods = new String[methods.length + 5];
String[] newMethods = new String[methods.length + 6];
System.arraycopy( methods, 0, newMethods, 0, methods.length );
newMethods[methods.length] = "getNamesRemote";
newMethods[methods.length + 1] = "isPresentRemote";
newMethods[methods.length + 2] = "getTypeRemote";
newMethods[methods.length + 3] = "getMethodsRemote";
newMethods[methods.length + 4] = "callRemote";
newMethods[methods.length + 5] = "getNameLocal";
return newMethods;
}
@@ -135,6 +139,12 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
System.arraycopy( arguments, 2, methodArgs, 0, arguments.length - 2 );
return callMethodRemote( remoteName, context, methodName, methodArgs );
}
case 5:
{
// getNameLocal
String local = getLocalPeripheral().getConnectedName();
return local == null ? null : new Object[]{ local };
}
default:
{
// The regular modem methods
@@ -217,7 +227,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
private void attachPeripheralImpl( String periphName, IPeripheral peripheral )
{
if( !peripheralWrappers.containsKey( periphName ) && canSeePeripheral( periphName ) )
if( !peripheralWrappers.containsKey( periphName ) && !periphName.equals( getLocalPeripheral().getConnectedName() ) )
{
RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( modem, peripheral, getComputer(), periphName );
peripheralWrappers.put( periphName, wrapper );
@@ -265,7 +275,7 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
throw new LuaException( "No peripheral: " + remoteName );
}
private static class RemotePeripheralWrapper implements IComputerAccess
private static class RemotePeripheralWrapper implements IComputerAccess, IComputerOwned
{
private final WiredModemElement m_element;
private final IPeripheral m_peripheral;
@@ -400,5 +410,12 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
return m_element.getRemotePeripherals().get( name );
}
}
@Nullable
@Override
public Computer getComputer()
{
return m_computer instanceof IComputerOwned ? ((IComputerOwned) m_computer).getComputer() : null;
}
}
}

View File

@@ -66,7 +66,8 @@ public class MonitorPeripheral implements IPeripheral
"setPaletteColor",
"getPaletteColour",
"getPaletteColor",
"getTextScale"
"getTextScale",
"getCursorBlink",
};
}
@@ -245,8 +246,12 @@ public class MonitorPeripheral implements IPeripheral
// getTextScale
return new Object[] { monitor.getTextScale() / 2.0 };
}
case 25:
// getCursorBlink
return new Object[] { terminal.getCursorBlink() };
default:
return null;
}
return null;
}
@Override

View File

@@ -344,7 +344,7 @@ public class TileMonitor extends TilePeripheralBase
public EnumFacing getFront()
{
return m_dir <= 5 ? EnumFacing.getFront( m_dir ) : (m_dir <= 11 ? EnumFacing.DOWN : EnumFacing.UP);
return m_dir <= 5 ? EnumFacing.byIndex( m_dir ) : (m_dir <= 11 ? EnumFacing.DOWN : EnumFacing.UP);
}
public EnumFacing getRight()

View File

@@ -190,10 +190,7 @@ public class TilePrinter extends TilePeripheralBase
@Override
public ItemStack getStackInSlot(int i)
{
synchronized( m_inventory )
{
return m_inventory.get( i );
}
return m_inventory.get( i );
}
@Nonnull

View File

@@ -8,10 +8,11 @@ package dan200.computercraft.shared.peripheral.speaker;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.network.play.server.SPacketCustomSound;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
@@ -20,33 +21,35 @@ import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.atomic.AtomicInteger;
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
import static dan200.computercraft.core.apis.ArgumentHelper.optReal;
public class SpeakerPeripheral implements IPeripheral {
private TileSpeaker m_speaker;
public class SpeakerPeripheral implements IPeripheral
{
private final TileSpeaker m_speaker;
private long m_clock;
private long m_lastPlayTime;
private int m_notesThisTick;
private final AtomicInteger m_notesThisTick;
public SpeakerPeripheral()
{
m_clock = 0;
m_lastPlayTime = 0;
m_notesThisTick = 0;
this( null );
}
SpeakerPeripheral(TileSpeaker speaker)
SpeakerPeripheral( TileSpeaker speaker )
{
this();
m_clock = 0;
m_lastPlayTime = 0;
m_notesThisTick = new AtomicInteger( );
m_speaker = speaker;
}
public synchronized void update()
public void update()
{
m_clock++;
m_notesThisTick = 0;
m_notesThisTick.set( 0 );
}
public World getWorld()
@@ -59,9 +62,9 @@ public class SpeakerPeripheral implements IPeripheral {
return m_speaker.getPos();
}
public synchronized boolean madeSound(long ticks)
public boolean madeSound(long ticks)
{
return (m_clock - m_lastPlayTime <= ticks) ;
return m_clock - m_lastPlayTime <= ticks;
}
/* IPeripheral implementation */
@@ -69,15 +72,9 @@ public class SpeakerPeripheral implements IPeripheral {
@Override
public boolean equals( IPeripheral other )
{
if( other != null && other instanceof SpeakerPeripheral )
{
SpeakerPeripheral otherSpeaker = (SpeakerPeripheral) other;
return otherSpeaker.m_speaker == m_speaker;
}
else
{
return false;
}
if( other == this ) return true;
if( !(other instanceof SpeakerPeripheral) ) return false;
return m_speaker == ((SpeakerPeripheral) other).m_speaker;
}
@Nonnull
@@ -92,8 +89,8 @@ public class SpeakerPeripheral implements IPeripheral {
public String[] getMethodNames()
{
return new String[] {
"playSound", // Plays sound at resourceLocator
"playNote" // Plays note
"playSound", // Plays sound at resourceLocator
"playNote" // Plays note
};
}
@@ -105,18 +102,22 @@ public class SpeakerPeripheral implements IPeripheral {
// playSound
case 0:
{
return playSound(args, context, false);
String name = getString( args, 0 );
float volume = (float) optReal( args, 1, 1.0 );
float pitch = (float) optReal( args, 2, 1.0 );
return new Object[] { playSound( context, name, volume, pitch, false ) };
}
// playNote
case 1:
{
return playNote(args, context);
return playNote( args, context );
}
default:
{
throw new LuaException("Method index out of range!");
throw new LuaException( "Method index out of range!" );
}
}
@@ -125,75 +126,53 @@ public class SpeakerPeripheral implements IPeripheral {
@Nonnull
private synchronized Object[] playNote( Object[] arguments, ILuaContext context ) throws LuaException
{
String name = getString(arguments, 0);
String name = getString( arguments, 0 );
float volume = (float) optReal( arguments, 1, 1.0 );
float pitch = (float) optReal( arguments, 2, 1.0 );
String noteName = "block.note." + name;
// Check if sound exists
if ( !SoundEvent.REGISTRY.containsKey( new ResourceLocation( "block.note." + name ) ) )
// Check if the note exists
if( !SoundEvent.REGISTRY.containsKey( new ResourceLocation( noteName ) ) )
{
throw new LuaException("Invalid instrument, \"" + arguments[0] + "\"!");
throw new LuaException( "Invalid instrument, \"" + name + "\"!" );
}
// If the resource location for note block notes changes, this method call will need to be updated
Object[] returnValue = playSound(
new Object[] {
"block.note." + name,
(double)Math.min( volume, 3f ),
Math.pow( 2.0f, ( pitch - 12.0f ) / 12.0f)
}, context, true
);
boolean success = playSound( context, noteName, volume, (float) Math.pow( 2.0, (pitch - 12.0) / 12.0 ), true );
if( returnValue[0] instanceof Boolean && (Boolean) returnValue[0] )
{
m_notesThisTick++;
}
return returnValue;
if( success ) m_notesThisTick.incrementAndGet();
return new Object[] { success };
}
@Nonnull
private synchronized Object[] playSound( Object[] arguments, ILuaContext context, boolean isNote ) throws LuaException
private synchronized boolean playSound( ILuaContext context, String name, float volume, float pitch, boolean isNote ) throws LuaException
{
String name = getString(arguments, 0);
float volume = (float) optReal( arguments, 1, 1.0 );
float pitch = (float) optReal( arguments, 2, 1.0 );
ResourceLocation resourceName = new ResourceLocation( name );
if( m_clock - m_lastPlayTime >= TileSpeaker.MIN_TICKS_BETWEEN_SOUNDS || ( ( m_clock - m_lastPlayTime == 0 ) && ( m_notesThisTick < ComputerCraft.maxNotesPerTick ) && isNote ) )
if( m_clock - m_lastPlayTime < TileSpeaker.MIN_TICKS_BETWEEN_SOUNDS &&
(!isNote || m_clock - m_lastPlayTime != 0 || m_notesThisTick.get() >= ComputerCraft.maxNotesPerTick) )
{
if( SoundEvent.REGISTRY.containsKey(resourceName) )
{
final World world = getWorld();
final BlockPos pos = getPos();
final ResourceLocation resource = resourceName;
final float vol = volume;
final float soundPitch = pitch;
context.issueMainThreadTask(new ILuaTask()
{
@Nullable
@Override
public Object[] execute()
{
world.playSound( null, pos, SoundEvent.REGISTRY.getObject( resource ), SoundCategory.RECORDS, Math.min( vol, 3f ), soundPitch );
return null;
}
});
m_lastPlayTime = m_clock;
return new Object[]{true}; // Success, return true
}
else
{
return new Object[]{false}; // Failed - sound not existent, return false
}
}
else
{
return new Object[]{false}; // Failed - rate limited, return false
// Rate limiting occurs when we've already played a sound within the last tick, or we've
// played more notes than allowable within the current tick.
return false;
}
World world = getWorld();
BlockPos pos = getPos();
context.issueMainThreadTask( () -> {
MinecraftServer server = world.getMinecraftServer();
if( server == null ) return null;
double x = pos.getX() + 0.5, y = pos.getY() + 0.5, z = pos.getZ() + 0.5;
float adjVolume = Math.min( volume, 3.0f );
server.getPlayerList().sendToAllNearExcept(
null, x, y, z, adjVolume > 1.0f ? 16 * adjVolume : 16.0, world.provider.getDimension(),
new SPacketCustomSound( name, SoundCategory.RECORDS, x, y, z, adjVolume, pitch )
);
return null;
} );
m_lastPlayTime = m_clock;
return true;
}
}

View File

@@ -25,7 +25,7 @@ public class TileSpeaker extends TilePeripheralBase
}
@Override
public synchronized void update()
public void update()
{
m_peripheral.update();
}

View File

@@ -8,6 +8,7 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
@@ -138,7 +139,7 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
{
if( this.m_upgrade == upgrade ) return;
synchronized (this)
synchronized( this )
{
ComputerCraft.Items.pocketComputer.setUpgrade( m_stack, upgrade );
if( m_entity instanceof EntityPlayer ) ((EntityPlayer) m_entity).inventory.markDirty();
@@ -156,6 +157,9 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
setPosition( entity.getPosition() );
}
// If a new entity has picked it up then rebroadcast the terminal to them
if( entity != m_entity && entity instanceof EntityPlayerMP ) markTerminalChanged();
m_entity = entity;
m_stack = stack;
@@ -165,4 +169,19 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces
invalidatePeripheral();
}
}
public void broadcastState( boolean force )
{
super.broadcastState( force );
if( (hasTerminalChanged() || force) && m_entity instanceof EntityPlayerMP )
{
// Broadcast the state to the current entity if they're not already interacting with it.
EntityPlayerMP player = (EntityPlayerMP) m_entity;
if( player.connection != null && !isInteracting( player ) )
{
ComputerCraft.sendToPlayer( player, createTerminalPacket() );
}
}
}
}

View File

@@ -43,7 +43,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
{
setMaxStackSize( 1 );
setHasSubtypes( true );
setUnlocalizedName( "computercraft:pocket_computer" );
setTranslationKey( "computercraft:pocket_computer" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}
@@ -189,7 +189,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
@Nonnull
@Override
public String getUnlocalizedName( @Nonnull ItemStack stack )
public String getTranslationKey( @Nonnull ItemStack stack )
{
switch( getFamily( stack ) )
{
@@ -209,7 +209,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
@Override
public String getItemStackDisplayName( @Nonnull ItemStack stack )
{
String baseString = getUnlocalizedName( stack );
String baseString = getTranslationKey( stack );
IPocketUpgrade upgrade = getUpgrade( stack );
if( upgrade != null )
{

View File

@@ -21,7 +21,6 @@ import dan200.computercraft.shared.turtle.items.ItemTurtleNormal;
import dan200.computercraft.shared.turtle.items.TurtleItemFactory;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.IEntityDropConsumer;
import dan200.computercraft.shared.util.ImpostorRecipe;
import dan200.computercraft.shared.util.InventoryUtil;
import net.minecraft.block.Block;
@@ -33,33 +32,49 @@ import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.fml.common.eventhandler.EventPriority;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.registry.EntityRegistry;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.registries.IForgeRegistry;
import javax.annotation.Nonnull;
import java.util.*;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
{
{
private Map<Integer, ITurtleUpgrade> m_legacyTurtleUpgrades;
private Map<String, ITurtleUpgrade> m_turtleUpgrades;
private Map<Entity, IEntityDropConsumer> m_dropConsumers;
private Function<ItemStack, ItemStack> dropConsumer;
private List<ItemStack> remainingDrops;
private WeakReference<World> dropWorld;
private BlockPos dropPos;
private AxisAlignedBB dropBounds;
private WeakReference<Entity> dropEntity;
public CCTurtleProxyCommon()
{
m_legacyTurtleUpgrades = new HashMap<>();
m_turtleUpgrades = new HashMap<>();
m_dropConsumers = new WeakHashMap<>();
}
// ICCTurtleProxy implementation
@Override
@Override
public void preInit()
{
MinecraftForge.EVENT_BUS.register( this );
@@ -75,8 +90,8 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
// RecipeSorter.register( "computercraft:turtle", TurtleRecipe.class, RecipeSorter.Category.SHAPED, "after:minecraft:shapeless" );
// RecipeSorter.register( "computercraft:turtle_upgrade", TurtleUpgradeRecipe.class, RecipeSorter.Category.SHAPED, "after:minecraft:shapeless" );
}
@Override
@Override
public void init()
{
registerForgeHandlers();
@@ -94,7 +109,7 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
ComputerCraft.log.error( message );
throw new RuntimeException( message );
}
// Register
registerTurtleUpgradeInternal( upgrade );
}
@@ -110,7 +125,7 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
{
return m_legacyTurtleUpgrades.get( legacyId );
}
@Override
public ITurtleUpgrade getTurtleUpgrade( @Nonnull ItemStack stack )
{
@@ -126,7 +141,7 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
}
catch( Exception e )
{
ComputerCraft.log.error("Error getting computer upgrade item", e);
ComputerCraft.log.error( "Error getting computer upgrade item", e );
}
}
return null;
@@ -148,7 +163,7 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
return true;
}
}
private void addAllUpgradedTurtles( ComputerFamily family, NonNullList<ItemStack> list )
{
ItemStack basicStack = TurtleItemFactory.create( -1, null, -1, family, null, null, 0, null );
@@ -169,7 +184,7 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
private void addUpgradedTurtle( ComputerFamily family, ITurtleUpgrade upgrade, List<ItemStack> list )
{
if ( isUpgradeSuitableForFamily( family, upgrade ) )
if( isUpgradeSuitableForFamily( family, upgrade ) )
{
ItemStack stack = TurtleItemFactory.create( -1, null, -1, family, upgrade, null, 0, null );
if( !stack.isEmpty() )
@@ -178,54 +193,65 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
}
}
}
@Override
public void addAllUpgradedTurtles( NonNullList<ItemStack> list )
{
addAllUpgradedTurtles( ComputerFamily.Normal, list );
addAllUpgradedTurtles( ComputerFamily.Advanced, list );
}
@Override
public void setEntityDropConsumer( Entity entity, IEntityDropConsumer consumer )
public void setDropConsumer( Entity entity, Function<ItemStack, ItemStack> consumer )
{
if( !m_dropConsumers.containsKey( entity ) )
{
boolean captured = entity.captureDrops;
if( !captured )
{
entity.captureDrops = true;
ArrayList<EntityItem> items = entity.capturedDrops;
if( items == null || items.size() == 0 )
{
m_dropConsumers.put( entity, consumer );
}
}
}
}
dropConsumer = consumer;
remainingDrops = new ArrayList<>();
dropEntity = new WeakReference<>( entity );
dropWorld = new WeakReference<>( entity.world );
dropPos = null;
dropBounds = new AxisAlignedBB( entity.getPosition() ).grow( 2, 2, 2 );
entity.captureDrops = true;
}
@Override
public void clearEntityDropConsumer( Entity entity )
public void setDropConsumer( World world, BlockPos pos, Function<ItemStack, ItemStack> consumer )
{
if( m_dropConsumers.containsKey( entity ) )
dropConsumer = consumer;
remainingDrops = new ArrayList<>();
dropEntity = null;
dropWorld = new WeakReference<>( world );
dropPos = pos;
dropBounds = new AxisAlignedBB( pos ).grow( 2, 2, 2 );
}
@Override
public List<ItemStack> clearDropConsumer()
{
if( dropEntity != null )
{
boolean captured = entity.captureDrops;
if( captured )
Entity entity = dropEntity.get();
if( entity != null )
{
entity.captureDrops = false;
ArrayList<EntityItem> items = entity.capturedDrops;
if( items != null )
if( entity.capturedDrops != null )
{
dispatchEntityDrops( entity, items );
items.clear();
for( EntityItem entityItem : entity.capturedDrops ) handleDrops( entityItem.getItem() );
entity.capturedDrops.clear();
}
}
m_dropConsumers.remove( entity );
}
List<ItemStack> remainingStacks = remainingDrops;
dropConsumer = null;
remainingDrops = null;
dropEntity = null;
dropWorld = null;
dropPos = null;
dropBounds = null;
return remainingStacks;
}
private void registerTurtleUpgradeInternal( ITurtleUpgrade upgrade )
@@ -289,7 +315,7 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
{
IForgeRegistry<Item> registry = event.getRegistry();
registry.register( new ItemTurtleLegacy( ComputerCraft.Blocks.turtle).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "turtle" ) ) );
registry.register( new ItemTurtleLegacy( ComputerCraft.Blocks.turtle ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "turtle" ) ) );
registry.register( new ItemTurtleNormal( ComputerCraft.Blocks.turtleExpanded ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "turtle_expanded" ) ) );
registry.register( new ItemTurtleAdvanced( ComputerCraft.Blocks.turtleAdvanced ).setRegistryName( new ResourceLocation( ComputerCraft.MOD_ID, "turtle_advanced" ) ) );
}
@@ -362,7 +388,7 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
private void registerUpgrades()
{
// Upgrades
ComputerCraft.Upgrades.wirelessModem = new TurtleModem( false, new ResourceLocation( "computercraft", "wireless_modem" ), 1 );
ComputerCraft.Upgrades.wirelessModem = new TurtleModem( false, new ResourceLocation( "computercraft", "wireless_modem" ), 1 );
registerTurtleUpgradeInternal( ComputerCraft.Upgrades.wirelessModem );
ComputerCraft.Upgrades.craftingTable = new TurtleCraftingTable( 2 );
@@ -396,10 +422,10 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
// We have to use mappings.getAllMappings() as the mod ID is upper case but the domain lower.
for( RegistryEvent.MissingMappings.Mapping<Item> mapping : mappings.getAllMappings() )
{
String domain = mapping.key.getResourceDomain();
String domain = mapping.key.getNamespace();
if( !domain.equalsIgnoreCase( ComputerCraft.MOD_ID ) ) continue;
String key = mapping.key.getResourcePath();
String key = mapping.key.getPath();
if( key.equalsIgnoreCase( "CC-Turtle" ) )
{
mapping.remap( Item.getItemFromBlock( ComputerCraft.Blocks.turtle ) );
@@ -421,10 +447,10 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
// We have to use mappings.getAllMappings() as the mod ID is upper case but the domain lower.
for( RegistryEvent.MissingMappings.Mapping<Block> mapping : mappings.getAllMappings() )
{
String domain = mapping.key.getResourceDomain();
String domain = mapping.key.getNamespace();
if( !domain.equalsIgnoreCase( ComputerCraft.MOD_ID ) ) continue;
String key = mapping.key.getResourcePath();
String key = mapping.key.getPath();
if( key.equalsIgnoreCase( "CC-Turtle" ) )
{
mapping.remap( ComputerCraft.Blocks.turtle );
@@ -447,26 +473,60 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
GameRegistry.registerTileEntity( TileTurtleExpanded.class, ComputerCraft.LOWER_ID + " : " + "turtleex" );
GameRegistry.registerTileEntity( TileTurtleAdvanced.class, ComputerCraft.LOWER_ID + " : " + "turtleadv" );
}
private void registerForgeHandlers()
{
ForgeHandlers handlers = new ForgeHandlers();
MinecraftForge.EVENT_BUS.register( handlers );
}
public class ForgeHandlers
{
private ForgeHandlers()
{
}
// Forge event responses
@SubscribeEvent
private void handleDrops(ItemStack stack)
{
ItemStack remaining = dropConsumer.apply(stack);
if (!remaining.isEmpty()) remainingDrops.add(remaining);
}
private class ForgeHandlers
{
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onEntityLivingDrops( LivingDropsEvent event )
{
dispatchEntityDrops( event.getEntity(), event.getDrops() );
// Capture any mob drops for the current entity
if( dropEntity != null && event.getEntity() == dropEntity.get() )
{
List<EntityItem> drops = event.getDrops();
for( EntityItem entityItem : drops ) handleDrops( entityItem.getItem() );
drops.clear();
}
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onHarvestDrops( BlockEvent.HarvestDropsEvent event )
{
// Capture block drops for the current entity
if( dropWorld != null && dropWorld.get() == event.getWorld()
&& dropPos != null && dropPos.equals( event.getPos() ) )
{
for( ItemStack item : event.getDrops() )
{
if( event.getWorld().rand.nextFloat() < event.getDropChance() ) handleDrops( item );
}
event.getDrops().clear();
}
}
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onEntitySpawn( EntityJoinWorldEvent event )
{
// Capture any nearby item spawns
if( dropWorld != null && dropWorld.get() == event.getWorld() && event.getEntity() instanceof EntityItem
&& dropBounds.contains( event.getEntity().getPositionVector() ) )
{
handleDrops( ((EntityItem) event.getEntity()).getItem() );
event.setCanceled( true );
}
}
@SubscribeEvent
public void onTurtleAction( TurtleActionEvent event) {
if( ComputerCraft.turtleDisabledActions.contains( event.getAction() ) )
@@ -475,18 +535,5 @@ public abstract class CCTurtleProxyCommon implements ICCTurtleProxy
}
}
}
private void dispatchEntityDrops( Entity entity, java.util.List<EntityItem> drops )
{
IEntityDropConsumer consumer = m_dropConsumers.get( entity );
if( consumer != null )
{
// All checks have passed, lets dispatch the drops
for(EntityItem entityItem : drops)
{
consumer.consumeDrop( entity, entityItem.getItem() );
}
drops.clear();
}
}
}

View File

@@ -24,6 +24,7 @@ import dan200.computercraft.shared.computer.core.*;
import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.items.ItemCommandComputer;
import dan200.computercraft.shared.computer.items.ItemComputer;
import dan200.computercraft.shared.integration.charset.IntegrationCharset;
import dan200.computercraft.shared.media.common.DefaultMediaProvider;
import dan200.computercraft.shared.media.inventory.ContainerHeldItem;
import dan200.computercraft.shared.media.items.ItemDiskExpanded;
@@ -76,6 +77,7 @@ import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.client.event.ConfigChangedEvent;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.common.network.FMLNetworkEvent;
@@ -83,6 +85,7 @@ import net.minecraftforge.fml.common.network.IGuiHandler;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.registry.GameRegistry;
import net.minecraftforge.registries.IForgeRegistry;
import pl.asie.charset.ModCharset;
import javax.annotation.Nonnull;
import java.io.File;
@@ -117,6 +120,8 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
{
registerTileEntities();
registerForgeHandlers();
if( Loader.isModLoaded( ModCharset.MODID ) ) IntegrationCharset.register();
}
@Override
@@ -414,10 +419,10 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
// We have to use mappings.getAllMappings() as the mod ID is upper case but the domain lower.
for( RegistryEvent.MissingMappings.Mapping<Item> mapping : mappings.getAllMappings() )
{
String domain = mapping.key.getResourceDomain();
String domain = mapping.key.getNamespace();
if( !domain.equalsIgnoreCase( ComputerCraft.MOD_ID ) ) continue;
String key = mapping.key.getResourcePath();
String key = mapping.key.getPath();
if( key.equalsIgnoreCase( "CC-Computer" ) )
{
mapping.remap( Item.getItemFromBlock( ComputerCraft.Blocks.computer ) );
@@ -451,10 +456,10 @@ public abstract class ComputerCraftProxyCommon implements IComputerCraftProxy
// We have to use mappings.getAllMappings() as the mod ID is upper case but the domain lower.
for( RegistryEvent.MissingMappings.Mapping<Block> mapping : mappings.getAllMappings() )
{
String domain = mapping.key.getResourceDomain();
String domain = mapping.key.getNamespace();
if( !domain.equalsIgnoreCase( ComputerCraft.MOD_ID ) ) continue;
String key = mapping.key.getResourcePath();
String key = mapping.key.getPath();
if( key.equalsIgnoreCase( "CC-Computer" ) )
{
mapping.remap( ComputerCraft.Blocks.computer );

View File

@@ -7,12 +7,16 @@
package dan200.computercraft.shared.proxy;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.shared.util.IEntityDropConsumer;
import net.minecraft.entity.Entity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.NonNullList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
public interface ICCTurtleProxy
{
@@ -25,6 +29,7 @@ public interface ICCTurtleProxy
ITurtleUpgrade getTurtleUpgrade( @Nonnull ItemStack item );
void addAllUpgradedTurtles( NonNullList<ItemStack> list );
void setEntityDropConsumer( Entity entity, IEntityDropConsumer consumer );
void clearEntityDropConsumer( Entity entity );
void setDropConsumer( Entity entity, Function<ItemStack, ItemStack> consumer );
void setDropConsumer( World world, BlockPos pos, Function<ItemStack, ItemStack> consumer );
List<ItemStack> clearDropConsumer();
}

View File

@@ -48,7 +48,7 @@ public class BlockTurtle extends BlockComputerBase
{
super( Material.IRON );
setHardness( 2.5f );
setUnlocalizedName( "computercraft:turtle" );
setTranslationKey( "computercraft:turtle" );
setCreativeTab( ComputerCraft.mainCreativeTab );
setDefaultState( this.blockState.getBaseState()
.withProperty( Properties.FACING, EnumFacing.NORTH )

View File

@@ -127,6 +127,8 @@ public class TurtleBrain implements ITurtleAccess
private TurtleAnimation m_animation;
private int m_animationProgress;
private int m_lastAnimationProgress;
TurtlePlayer m_cachedPlayer;
public TurtleBrain( TileTurtle turtle )
{
@@ -224,7 +226,7 @@ public class TurtleBrain implements ITurtleAccess
public void readFromNBT( NBTTagCompound nbttagcompound )
{
// Read state
m_direction = EnumFacing.getFront( nbttagcompound.getInteger( "dir" ) );
m_direction = EnumFacing.byIndex( nbttagcompound.getInteger( "dir" ) );
m_selectedSlot = nbttagcompound.getInteger( "selectedSlot" );
if( nbttagcompound.hasKey( "fuelLevel" ) )
{
@@ -368,8 +370,8 @@ public class TurtleBrain implements ITurtleAccess
// Write overlay
if( m_overlay != null )
{
nbttagcompound.setString( "overlay_mod", m_overlay.getResourceDomain() );
nbttagcompound.setString( "overlay_path", m_overlay.getResourcePath() );
nbttagcompound.setString( "overlay_mod", m_overlay.getNamespace() );
nbttagcompound.setString( "overlay_path", m_overlay.getPath() );
}
// Write NBT
@@ -427,8 +429,8 @@ public class TurtleBrain implements ITurtleAccess
// Overlay
if( m_overlay != null )
{
nbttagcompound.setString( "overlay_mod", m_overlay.getResourceDomain() );
nbttagcompound.setString( "overlay_path", m_overlay.getResourcePath() );
nbttagcompound.setString( "overlay_mod", m_overlay.getNamespace() );
nbttagcompound.setString( "overlay_path", m_overlay.getPath() );
}
// Animation
@@ -503,7 +505,7 @@ public class TurtleBrain implements ITurtleAccess
m_lastAnimationProgress = 0;
}
m_direction = EnumFacing.getFront( nbttagcompound.getInteger( "direction" ) );
m_direction = EnumFacing.byIndex( nbttagcompound.getInteger( "direction" ) );
m_fuelLevel = nbttagcompound.getInteger( "fuelLevel" );
}
@@ -996,9 +998,9 @@ public class TurtleBrain implements ITurtleAccess
double distance = -1.0 + getAnimationFraction( f );
return new Vec3d(
distance * dir.getFrontOffsetX(),
distance * dir.getFrontOffsetY(),
distance * dir.getFrontOffsetZ()
distance * dir.getXOffset(),
distance * dir.getYOffset(),
distance * dir.getZOffset()
);
}
default:
@@ -1182,31 +1184,31 @@ public class TurtleBrain implements ITurtleAccess
float pushFrac = 1.0f - ((float)(m_animationProgress + 1) / (float)ANIM_DURATION);
float push = Math.max( pushFrac + 0.0125f, 0.0f );
if (moveDir.getFrontOffsetX() < 0)
if (moveDir.getXOffset() < 0)
{
minX += moveDir.getFrontOffsetX() * push;
minX += moveDir.getXOffset() * push;
}
else
{
maxX -= moveDir.getFrontOffsetX() * push;
maxX -= moveDir.getXOffset() * push;
}
if (moveDir.getFrontOffsetY() < 0)
if (moveDir.getYOffset() < 0)
{
minY += moveDir.getFrontOffsetY() * push;
minY += moveDir.getYOffset() * push;
}
else
{
maxY -= moveDir.getFrontOffsetY() * push;
maxY -= moveDir.getYOffset() * push;
}
if (moveDir.getFrontOffsetZ() < 0)
if (moveDir.getZOffset() < 0)
{
minZ += moveDir.getFrontOffsetZ() * push;
minZ += moveDir.getZOffset() * push;
}
else
{
maxZ -= moveDir.getFrontOffsetZ() * push;
maxZ -= moveDir.getZOffset() * push;
}
AxisAlignedBB aabb = new AxisAlignedBB( minX, minY, minZ, maxX, maxY, maxZ );
@@ -1214,9 +1216,9 @@ public class TurtleBrain implements ITurtleAccess
if( !list.isEmpty() )
{
double pushStep = 1.0f / ANIM_DURATION;
double pushStepX = moveDir.getFrontOffsetX() * pushStep;
double pushStepY = moveDir.getFrontOffsetY() * pushStep;
double pushStepZ = moveDir.getFrontOffsetZ() * pushStep;
double pushStepX = moveDir.getXOffset() * pushStep;
double pushStepY = moveDir.getYOffset() * pushStep;
double pushStepZ = moveDir.getZOffset() * pushStep;
for (Entity entity : list)
{
entity.move( MoverType.PISTON, pushStepX, pushStepY, pushStepZ );

View File

@@ -126,7 +126,7 @@ public class TurtleCompareCommand implements ITurtleCommand
{
return TurtleCommandResult.success();
}
else if( selectedStack.getUnlocalizedName().equals( lookAtStack.getUnlocalizedName() ) )
else if( selectedStack.getTranslationKey().equals( lookAtStack.getTranslationKey() ) )
{
return TurtleCommandResult.success();
}

View File

@@ -87,9 +87,9 @@ public class TurtleMoveCommand implements ITurtleCommand
if( entityBB != null )
{
AxisAlignedBB pushedBB = entityBB.offset(
direction.getFrontOffsetX(),
direction.getFrontOffsetY(),
direction.getFrontOffsetZ()
direction.getXOffset(),
direction.getYOffset(),
direction.getZOffset()
);
if( !oldWorld.getCollisionBoxes( null, pushedBB ).isEmpty() )
{

View File

@@ -30,7 +30,6 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
@@ -38,6 +37,7 @@ import net.minecraftforge.fml.common.eventhandler.Event;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull;
import java.util.List;
public class TurtlePlaceCommand implements ITurtleCommand
{
@@ -180,12 +180,12 @@ public class TurtlePlaceCommand implements ITurtleCommand
public static TurtlePlayer createPlayer( ITurtleAccess turtle, BlockPos position, EnumFacing direction )
{
TurtlePlayer turtlePlayer = new TurtlePlayer( turtle );
TurtlePlayer turtlePlayer = TurtlePlayer.get( turtle );
orientPlayer( turtle, turtlePlayer, position, direction );
return turtlePlayer;
}
public static void orientPlayer( ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, EnumFacing direction )
private static void orientPlayer( ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, EnumFacing direction )
{
turtlePlayer.posX = position.getX() + 0.5;
turtlePlayer.posY = position.getY() + 0.5;
@@ -194,9 +194,9 @@ public class TurtlePlaceCommand implements ITurtleCommand
// Stop intersection with the turtle itself
if( turtle.getPosition().equals( position ) )
{
turtlePlayer.posX += 0.48 * direction.getFrontOffsetX();
turtlePlayer.posY += 0.48 * direction.getFrontOffsetY();
turtlePlayer.posZ += 0.48 * direction.getFrontOffsetZ();
turtlePlayer.posX += 0.48 * direction.getXOffset();
turtlePlayer.posY += 0.48 * direction.getYOffset();
turtlePlayer.posZ += 0.48 * direction.getZOffset();
}
if( direction.getAxis() != EnumFacing.Axis.Y )
@@ -241,14 +241,10 @@ public class TurtlePlaceCommand implements ITurtleCommand
// Start claiming entity drops
Entity hitEntity = hit.getKey();
Vec3d hitPos = hit.getValue();
ComputerCraft.setEntityDropConsumer( hitEntity, ( entity, drop ) ->
{
ItemStack remainder = InventoryUtil.storeItems( drop, turtle.getItemHandler(), turtle.getSelectedSlot() );
if( !remainder.isEmpty() )
{
WorldUtil.dropItemStack( remainder, world, position, turtle.getDirection().getOpposite() );
}
} );
ComputerCraft.setDropConsumer(
hitEntity,
drop -> InventoryUtil.storeItems( drop, turtle.getItemHandler(), turtle.getSelectedSlot() )
);
// Place on the entity
boolean placed = false;
@@ -285,7 +281,11 @@ public class TurtlePlaceCommand implements ITurtleCommand
}
// Stop claiming drops
ComputerCraft.clearEntityDropConsumer( hitEntity );
List<ItemStack> remainingDrops = ComputerCraft.clearDropConsumer();
for( ItemStack remaining : remainingDrops )
{
WorldUtil.dropItemStack( remaining, world, position, turtle.getDirection().getOpposite() );
}
// Put everything we collected into the turtles inventory, then return
ItemStack remainder = turtlePlayer.unloadInventory( turtle );
@@ -367,9 +367,9 @@ public class TurtlePlaceCommand implements ITurtleCommand
orientPlayer( turtle, turtlePlayer, playerPosition, playerDir );
// Calculate where the turtle would hit the block
float hitX = 0.5f + side.getFrontOffsetX() * 0.5f;
float hitY = 0.5f + side.getFrontOffsetY() * 0.5f;
float hitZ = 0.5f + side.getFrontOffsetZ() * 0.5f;
float hitX = 0.5f + side.getXOffset() * 0.5f;
float hitY = 0.5f + side.getYOffset() * 0.5f;
float hitZ = 0.5f + side.getZOffset() * 0.5f;
if( Math.abs( hitY - 0.5f ) < 0.01f )
{
hitY = 0.45f;
@@ -382,7 +382,7 @@ public class TurtlePlaceCommand implements ITurtleCommand
// Do the deploying (put everything in the players inventory)
boolean placed = false;
TileEntity existingTile = turtle.getWorld().getTileEntity( position );
// See PlayerInteractionManager.processRightClickBlock
PlayerInteractEvent.RightClickBlock event = ForgeHooks.onRightClickBlock( turtlePlayer, EnumHand.MAIN_HAND, position, side, new Vec3d( hitX, hitY, hitZ ) );
@@ -426,12 +426,11 @@ public class TurtlePlaceCommand implements ITurtleCommand
{
World world = turtle.getWorld();
TileEntity tile = world.getTileEntity( position );
if( tile == null )
if( tile == null || tile == existingTile )
{
BlockPos newPosition = WorldUtil.moveCoords( position, side );
tile = world.getTileEntity( newPosition );
tile = world.getTileEntity( WorldUtil.moveCoords( position, side ) );
}
if( tile != null && tile instanceof TileEntitySign )
if( tile instanceof TileEntitySign )
{
TileEntitySign signTile = (TileEntitySign) tile;
String s = (String)extraArguments[0];

View File

@@ -36,16 +36,31 @@ public class TurtlePlayer extends FakePlayer
"[ComputerCraft]"
);
/**
* Construct a TurtlePlayer which exists in the world
*
* @param world The world the player exists in
* @deprecated This is required by {@link Entity}.
*/
@Deprecated
public TurtlePlayer( World world )
{
super( (WorldServer) world, DEFAULT_PROFILE );
}
public TurtlePlayer( ITurtleAccess turtle )
{
super( (WorldServer) turtle.getWorld(), getProfile( turtle.getOwningPlayer() ));
private TurtlePlayer( ITurtleAccess turtle )
{
super( (WorldServer) turtle.getWorld(), getProfile( turtle.getOwningPlayer() ) );
setState( turtle );
}
private static GameProfile getProfile( @Nullable GameProfile profile )
{
return profile != null && profile.isComplete() ? profile : DEFAULT_PROFILE;
}
private void setState( ITurtleAccess turtle )
{
BlockPos position = turtle.getPosition();
posX = position.getX() + 0.5;
posY = position.getY() + 0.5;
@@ -53,11 +68,27 @@ public class TurtlePlayer extends FakePlayer
rotationYaw = turtle.getDirection().getHorizontalAngle();
rotationPitch = 0.0f;
inventory.clear();
}
private static GameProfile getProfile( @Nullable GameProfile profile )
public static TurtlePlayer get( ITurtleAccess access )
{
return profile != null && profile.isComplete() ? profile : DEFAULT_PROFILE;
if( !(access instanceof TurtleBrain) ) return new TurtlePlayer( access );
TurtleBrain brain = (TurtleBrain) access;
TurtlePlayer player = brain.m_cachedPlayer;
if( player == null || player.getGameProfile() != getProfile( access.getOwningPlayer() )
|| player.getEntityWorld() != access.getWorld() )
{
player = brain.m_cachedPlayer = new TurtlePlayer( brain );
}
else
{
player.setState( access );
}
return player;
}
public void loadInventory( @Nonnull ItemStack currentStack )
@@ -76,7 +107,7 @@ public class TurtlePlayer extends FakePlayer
// Store (or drop) anything else we found
BlockPos dropPosition = turtle.getPosition();
EnumFacing dropDirection = turtle.getDirection().getOpposite();
for( int i=0; i<inventory.getSizeInventory(); ++i )
for( int i = 0; i < inventory.getSizeInventory(); ++i )
{
ItemStack stack = inventory.getStackInSlot( i );
if( !stack.isEmpty() )

View File

@@ -15,7 +15,7 @@ public class ItemTurtleAdvanced extends ItemTurtleNormal
public ItemTurtleAdvanced( Block block )
{
super( block );
setUnlocalizedName( "computercraft:advanced_turtle" );
setTranslationKey( "computercraft:advanced_turtle" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}

View File

@@ -114,7 +114,7 @@ public abstract class ItemTurtleBase extends ItemComputerBase implements ITurtle
@Nonnull
@Override
public String getUnlocalizedName( @Nonnull ItemStack stack )
public String getTranslationKey( @Nonnull ItemStack stack )
{
ComputerFamily family = getFamily( stack );
switch( family )
@@ -139,7 +139,7 @@ public abstract class ItemTurtleBase extends ItemComputerBase implements ITurtle
@Override
public String getItemStackDisplayName( @Nonnull ItemStack stack )
{
String baseString = getUnlocalizedName( stack );
String baseString = getTranslationKey( stack );
ITurtleUpgrade left = getUpgrade( stack, TurtleSide.Left );
ITurtleUpgrade right = getUpgrade( stack, TurtleSide.Right );
if( left != null && right != null )

View File

@@ -23,7 +23,7 @@ public class ItemTurtleLegacy extends ItemTurtleBase
public ItemTurtleLegacy( Block block )
{
super( block );
setUnlocalizedName( "computercraft:turtle" );
setTranslationKey( "computercraft:turtle" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}

View File

@@ -24,7 +24,7 @@ public class ItemTurtleNormal extends ItemTurtleBase
public ItemTurtleNormal( Block block )
{
super( block );
setUnlocalizedName( "computercraft:turtle" );
setTranslationKey( "computercraft:turtle" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}
@@ -72,8 +72,8 @@ public class ItemTurtleNormal extends ItemTurtleBase
}
if( overlay != null )
{
nbt.setString( "overlay_mod", overlay.getResourceDomain() );
nbt.setString( "overlay_path", overlay.getResourcePath() );
nbt.setString( "overlay_mod", overlay.getNamespace() );
nbt.setString( "overlay_path", overlay.getPath() );
}
stack.setTagCompound( nbt );

View File

@@ -11,13 +11,14 @@ import dan200.computercraft.api.turtle.TurtleCommandResult;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleVerb;
import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand;
import dan200.computercraft.shared.turtle.core.TurtlePlayer;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
@@ -30,20 +31,16 @@ public class TurtleHoe extends TurtleTool
}
@Override
protected boolean canBreakBlock( World world, BlockPos pos )
protected boolean canBreakBlock( IBlockState state, World world, BlockPos pos, TurtlePlayer player )
{
if( super.canBreakBlock( world, pos ) )
{
IBlockState state = world.getBlockState( pos );
Material material = state.getMaterial( );
return
material == Material.PLANTS ||
material == Material.CACTUS ||
material == Material.GOURD ||
material == Material.LEAVES ||
material == Material.VINE;
}
return false;
if( !super.canBreakBlock( state, world, pos, player ) ) return false;
Material material = state.getMaterial();
return material == Material.PLANTS ||
material == Material.CACTUS ||
material == Material.GOURD ||
material == Material.LEAVES ||
material == Material.VINE;
}
@Nonnull

View File

@@ -118,7 +118,7 @@ public class TurtleInventoryCrafting extends InventoryCrafting
}
// Do post-pickup stuff
TurtlePlayer turtlePlayer = new TurtlePlayer( m_turtle );
TurtlePlayer turtlePlayer = TurtlePlayer.get( m_turtle );
result.onCrafting( world, turtlePlayer, numToCraft );
results.add( result );

View File

@@ -11,6 +11,7 @@ import dan200.computercraft.api.turtle.TurtleCommandResult;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleVerb;
import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand;
import dan200.computercraft.shared.turtle.core.TurtlePlayer;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.item.Item;
@@ -30,26 +31,22 @@ public class TurtleShovel extends TurtleTool
}
@Override
protected boolean canBreakBlock( World world, BlockPos pos )
protected boolean canBreakBlock( IBlockState state, World world, BlockPos pos, TurtlePlayer player )
{
if( super.canBreakBlock( world, pos ) )
{
IBlockState state = world.getBlockState( pos );
Material material = state.getMaterial( );
return
material == Material.GROUND ||
material == Material.SAND ||
material == Material.SNOW ||
material == Material.CLAY ||
material == Material.CRAFTED_SNOW ||
material == Material.GRASS ||
material == Material.PLANTS ||
material == Material.CACTUS ||
material == Material.GOURD ||
material == Material.LEAVES ||
material == Material.VINE;
}
return false;
if( !super.canBreakBlock( state, world, pos, player ) ) return false;
Material material = state.getMaterial();
return material == Material.GROUND ||
material == Material.SAND ||
material == Material.SNOW ||
material == Material.CLAY ||
material == Material.CRAFTED_SNOW ||
material == Material.GRASS ||
material == Material.PLANTS ||
material == Material.CACTUS ||
material == Material.GOURD ||
material == Material.LEAVES ||
material == Material.VINE;
}
@Nonnull

View File

@@ -5,11 +5,13 @@
*/
package dan200.computercraft.shared.turtle.upgrades;
import dan200.computercraft.shared.turtle.core.TurtlePlayer;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.item.Item;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public class TurtleSword extends TurtleTool
@@ -20,20 +22,16 @@ public class TurtleSword extends TurtleTool
}
@Override
protected boolean canBreakBlock( World world, BlockPos pos )
protected boolean canBreakBlock( IBlockState state, World world, BlockPos pos, TurtlePlayer player )
{
if( super.canBreakBlock( world, pos ) )
{
IBlockState state = world.getBlockState( pos );
Material material = state.getMaterial( );
return
material == Material.PLANTS ||
material == Material.LEAVES ||
material == Material.VINE ||
material == Material.CLOTH ||
material == Material.WEB;
}
return false;
if( !super.canBreakBlock( state, world, pos, player ) ) return false;
Material material = state.getMaterial();
return material == Material.PLANTS ||
material == Material.LEAVES ||
material == Material.VINE ||
material == Material.CLOTH ||
material == Material.WEB;
}
@Override

View File

@@ -23,20 +23,17 @@ import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.entity.Entity;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.item.EntityArmorStand;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.ForgeEventFactory;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.fml.relauncher.Side;
@@ -46,6 +43,7 @@ import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull;
import javax.vecmath.Matrix4f;
import java.util.List;
import java.util.function.Function;
public class TurtleTool implements ITurtleUpgrade
{
@@ -142,27 +140,20 @@ public class TurtleTool implements ITurtleUpgrade
}
}
protected boolean canBreakBlock( World world, BlockPos pos )
protected boolean canBreakBlock( IBlockState state, World world, BlockPos pos, TurtlePlayer player )
{
IBlockState state = world.getBlockState( pos );
Block block = state.getBlock();
return !block.isAir( state, world, pos ) && block != Blocks.BEDROCK && state.getBlockHardness( world, pos ) > -1.0F;
return !block.isAir( state, world, pos )
&& block != Blocks.BEDROCK
&& state.getPlayerRelativeBlockHardness( player, world, pos ) > 0
&& block.canEntityDestroy( state, world, pos, player );
}
protected boolean canHarvestBlock( ITurtleAccess turtleAccess, BlockPos pos )
{
World world = turtleAccess.getWorld();
Block block = world.getBlockState( pos ).getBlock();
TurtlePlayer turtlePlayer = new TurtlePlayer( turtleAccess );
turtlePlayer.loadInventory( m_item.copy() );
return ForgeHooks.canHarvestBlock( block, turtlePlayer, world, pos );
}
protected float getDamageMultiplier()
{
return 3.0f;
}
private TurtleCommandResult attack( final ITurtleAccess turtle, EnumFacing direction, TurtleSide side )
{
// Create a fake player, and orient it appropriately
@@ -193,22 +184,15 @@ public class TurtleTool implements ITurtleUpgrade
{
return TurtleCommandResult.failure( attackEvent.getFailureMessage() );
}
// Start claiming entity drops
ComputerCraft.setEntityDropConsumer( hitEntity, ( entity, drop ) ->
{
ItemStack remainder = InventoryUtil.storeItems( drop, turtle.getItemHandler(), turtle.getSelectedSlot() );
if( !remainder.isEmpty() )
{
WorldUtil.dropItemStack( remainder, world, position, turtle.getDirection().getOpposite() );
}
} );
ComputerCraft.setDropConsumer( hitEntity, turtleDropConsumer( turtle ) );
// Attack the entity
boolean attacked = false;
if( !hitEntity.hitByEntity( turtlePlayer ) )
{
float damage = (float)turtlePlayer.getEntityAttribute( SharedMonsterAttributes.ATTACK_DAMAGE ).getAttributeValue();
float damage = (float) turtlePlayer.getEntityAttribute( SharedMonsterAttributes.ATTACK_DAMAGE ).getAttributeValue();
damage *= getDamageMultiplier();
if( damage > 0.0f )
{
@@ -234,7 +218,7 @@ public class TurtleTool implements ITurtleUpgrade
}
// Stop claiming drops
ComputerCraft.clearEntityDropConsumer( hitEntity );
stopConsuming( turtle );
// Put everything we collected into the turtles inventory, then return
if( attacked )
@@ -246,75 +230,77 @@ public class TurtleTool implements ITurtleUpgrade
return TurtleCommandResult.failure( "Nothing to attack here" );
}
private TurtleCommandResult dig( ITurtleAccess turtle, EnumFacing direction, TurtleSide side )
{
// Get ready to dig
World world = turtle.getWorld();
BlockPos position = turtle.getPosition();
BlockPos newPosition = WorldUtil.moveCoords( position, direction );
BlockPos turtlePosition = turtle.getPosition();
BlockPos blockPosition = WorldUtil.moveCoords( turtlePosition, direction );
if( WorldUtil.isBlockInWorld( world, newPosition ) &&
!world.isAirBlock( newPosition ) &&
!WorldUtil.isLiquidBlock( world, newPosition ) )
if( WorldUtil.isBlockInWorld( world, blockPosition ) &&
!world.isAirBlock( blockPosition ) &&
!WorldUtil.isLiquidBlock( world, blockPosition ) )
{
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, position, direction );
IBlockState state = world.getBlockState( blockPosition );
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, turtlePosition, direction );
turtlePlayer.loadInventory( m_item.copy() );
if( ComputerCraft.turtlesObeyBlockProtection )
{
// Check spawn protection
if( MinecraftForge.EVENT_BUS.post( new BlockEvent.BreakEvent( world, newPosition, world.getBlockState( newPosition ), turtlePlayer ) ) )
if( MinecraftForge.EVENT_BUS.post( new BlockEvent.BreakEvent( world, blockPosition, state, turtlePlayer ) ) )
{
return TurtleCommandResult.failure( "Cannot break protected block" );
}
if( !ComputerCraft.isBlockEditable( world, newPosition, turtlePlayer ) )
if( !ComputerCraft.isBlockEditable( world, blockPosition, turtlePlayer ) )
{
return TurtleCommandResult.failure( "Cannot break protected block" );
}
}
// Check if we can break the block
if( !canBreakBlock( world, newPosition ) )
if( !canBreakBlock( state, world, blockPosition, turtlePlayer ) )
{
return TurtleCommandResult.failure( "Unbreakable block detected" );
}
// Fire the dig event, checking whether it was cancelled.
TurtleBlockEvent.Dig digEvent = new TurtleBlockEvent.Dig( turtle, turtlePlayer, world, newPosition, world.getBlockState( newPosition ), this, side );
TurtleBlockEvent.Dig digEvent = new TurtleBlockEvent.Dig( turtle, turtlePlayer, world, blockPosition, state, this, side );
if( MinecraftForge.EVENT_BUS.post( digEvent ) )
{
return TurtleCommandResult.failure( digEvent.getFailureMessage() );
}
// Consume the items the block drops
if( canHarvestBlock( turtle, newPosition ) )
{
List<ItemStack> items = getBlockDropped( world, newPosition, turtlePlayer );
if( items != null && items.size() > 0 )
{
for( ItemStack stack : items )
{
ItemStack remainder = InventoryUtil.storeItems( stack, turtle.getItemHandler(), turtle.getSelectedSlot() );
if( !remainder.isEmpty() )
{
// If there's no room for the items, drop them
WorldUtil.dropItemStack( remainder, world, position, direction );
}
}
}
}
ComputerCraft.setDropConsumer( world, blockPosition, turtleDropConsumer( turtle ) );
TileEntity tile = world.getTileEntity( blockPosition );
// Much of this logic comes from PlayerInteractionManager#tryHarvestBlock, so it's a good idea
// to consult there before making any changes.
// Play the destruction sound
world.playEvent( 2001, blockPosition, Block.getStateId( state ) );
// Destroy the block
IBlockState previousState = world.getBlockState( newPosition );
world.playEvent(2001, newPosition, Block.getStateId(previousState));
world.setBlockToAir( newPosition );
boolean canHarvest = state.getBlock().canHarvestBlock( world, blockPosition, turtlePlayer );
boolean canBreak = state.getBlock().removedByPlayer( state, world, blockPosition, turtlePlayer, canHarvest );
if( canBreak ) state.getBlock().onPlayerDestroy( world, blockPosition, state );
if( canHarvest )
{
state.getBlock().harvestBlock( world, turtlePlayer, blockPosition, state, tile, turtlePlayer.getHeldItemMainhand() );
}
stopConsuming( turtle );
// Remember the previous block
if( turtle instanceof TurtleBrain )
{
TurtleBrain brain = (TurtleBrain)turtle;
brain.saveBlockChange( newPosition, previousState );
TurtleBrain brain = (TurtleBrain) turtle;
brain.saveBlockChange( blockPosition, state );
}
return TurtleCommandResult.success();
@@ -323,22 +309,17 @@ public class TurtleTool implements ITurtleUpgrade
return TurtleCommandResult.failure( "Nothing to dig here" );
}
@SuppressWarnings("deprecation")
private List<ItemStack> getBlockDropped( World world, BlockPos pos, EntityPlayer player )
private Function<ItemStack, ItemStack> turtleDropConsumer( ITurtleAccess turtle )
{
IBlockState state = world.getBlockState( pos );
Block block = state.getBlock();
// Note, we use the deprecated version as some mods override that instead. Those mods are wrong (TM).
List<ItemStack> drops = block.getDrops( world, pos, world.getBlockState( pos ), 0 );
double chance = ForgeEventFactory.fireBlockHarvesting( drops, world, pos, state, 0, 1, false, player );
return drop -> InventoryUtil.storeItems( drop, turtle.getItemHandler(), turtle.getSelectedSlot() );
}
for( int i = drops.size() - 1; i >= 0; i-- )
private void stopConsuming( ITurtleAccess turtle )
{
List<ItemStack> extra = ComputerCraft.clearDropConsumer();
for( ItemStack remainder : extra )
{
if( world.rand.nextFloat() > chance )
{
drops.remove( i );
}
WorldUtil.dropItemStack( remainder, turtle.getWorld(), turtle.getPosition(), turtle.getDirection().getOpposite() );
}
return drops;
}
}

View File

@@ -24,7 +24,7 @@ public class CreativeTabMain extends CreativeTabs
@Nonnull
@Override
@SideOnly(Side.CLIENT)
public ItemStack getTabIconItem()
public ItemStack createIcon()
{
return new ItemStack( ComputerCraft.Blocks.computer );
}
@@ -32,7 +32,7 @@ public class CreativeTabMain extends CreativeTabs
@Nonnull
@Override
@SideOnly(Side.CLIENT)
public String getTranslatedTabLabel()
public String getTranslationKey()
{
return getTabLabel();
}

View File

@@ -21,14 +21,14 @@ public class CreativeTabTreasure extends CreativeTabs
@Nonnull
@Override
public ItemStack getTabIconItem()
public ItemStack createIcon()
{
return new ItemStack( ComputerCraft.Items.treasureDisk );
}
@Nonnull
@Override
public String getTranslatedTabLabel()
public String getTranslationKey()
{
return getTabLabel();
}

View File

@@ -1,16 +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.shared.util;
import net.minecraft.entity.Entity;
import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull;
public interface IEntityDropConsumer
{
void consumeDrop( Entity dropper, @Nonnull ItemStack drop );
}

View File

@@ -73,13 +73,13 @@ public class InventoryUtil
// Look for entity with inventory
Vec3d vecStart = new Vec3d(
pos.getX() + 0.5 + 0.6 * side.getFrontOffsetX(),
pos.getY() + 0.5 + 0.6 * side.getFrontOffsetY(),
pos.getZ() + 0.5 + 0.6 * side.getFrontOffsetZ()
pos.getX() + 0.5 + 0.6 * side.getXOffset(),
pos.getY() + 0.5 + 0.6 * side.getYOffset(),
pos.getZ() + 0.5 + 0.6 * side.getZOffset()
);
EnumFacing dir = side.getOpposite();
Vec3d vecDir = new Vec3d(
dir.getFrontOffsetX(), dir.getFrontOffsetY(), dir.getFrontOffsetZ()
dir.getXOffset(), dir.getYOffset(), dir.getZOffset()
);
Pair<Entity, Vec3d> hit = WorldUtil.rayTraceEntities( world, vecStart, vecDir, 1.1 );
if( hit != null )

View File

@@ -43,14 +43,14 @@ public class WorldUtil
public static Pair<Entity, Vec3d> rayTraceEntities( World world, Vec3d vecStart, Vec3d vecDir, double distance )
{
Vec3d vecEnd = vecStart.addVector( vecDir.x * distance, vecDir.y * distance, vecDir.z * distance );
Vec3d vecEnd = vecStart.add( vecDir.x * distance, vecDir.y * distance, vecDir.z * distance );
// Raycast for blocks
RayTraceResult result = world.rayTraceBlocks( vecStart.addVector(0.0,0.0,0.0), vecEnd.addVector(0.0,0.0,0.0) );
RayTraceResult result = world.rayTraceBlocks( vecStart, vecEnd );
if( result != null && result.typeOfHit == RayTraceResult.Type.BLOCK )
{
distance = vecStart.distanceTo( result.hitVec );
vecEnd = vecStart.addVector( vecDir.x * distance, vecDir.y * distance, vecDir.z * distance );
vecEnd = vecStart.add( vecDir.x * distance, vecDir.y * distance, vecDir.z * distance );
}
// Check for entities
@@ -114,7 +114,7 @@ public class WorldUtil
}
if( closest != null && closestDist <= distance )
{
Vec3d closestPos = vecStart.addVector( vecDir.x * closestDist, vecDir.y * closestDist, vecDir.z * closestDist );
Vec3d closestPos = vecStart.add( vecDir.x * closestDist, vecDir.y * closestDist, vecDir.z * closestDist );
return Pair.of( closest, closestPos );
}
return null;
@@ -128,7 +128,7 @@ public class WorldUtil
public static Vec3d getRayEnd( EntityPlayer player) {
double reach = player.getEntityAttribute(EntityPlayer.REACH_DISTANCE).getAttributeValue();
Vec3d look = player.getLookVec();
return getRayStart( player ).addVector( look.x * reach, look.y * reach, look.z * reach );
return getRayStart( player ).add( look.x * reach, look.y * reach, look.z * reach );
}
public static boolean isVecInsideInclusive(AxisAlignedBB bb , Vec3d vec) {
@@ -147,9 +147,9 @@ public class WorldUtil
double zDir;
if( direction != null )
{
xDir = direction.getFrontOffsetX();
yDir = direction.getFrontOffsetY();
zDir = direction.getFrontOffsetZ();
xDir = direction.getXOffset();
yDir = direction.getYOffset();
zDir = direction.getZOffset();
}
else
{

View File

@@ -1,7 +1,7 @@
# RecordMedia (and related methods)
public net.minecraft.item.ItemRecord field_185076_b # sound
public net.minecraft.item.ItemRecord field_185077_c # displayName
# ItemPocketRenderer
# ItemPocketRenderer/ItemPrintoutRenderer
public net.minecraft.client.renderer.ItemRenderer func_187466_c()V # renderArms
public net.minecraft.client.renderer.ItemRenderer func_178100_c(F)F # getMapAngleFromPitch
public net.minecraft.client.renderer.ItemRenderer func_187456_a(FFLnet/minecraft/util/EnumHandSide;)V # renderArmFirstPerson

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