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

Compare commits

...

149 Commits

Author SHA1 Message Date
SquidDev
8819f2559d Add some tests for the new executor system
And fix a couple of bugs picked up by said tests
2018-09-18 18:21:00 +01:00
SquidDev
4b741739e8 Update the executor to work with the new calling system 2018-09-13 10:32:45 +01:00
SquidDev
f23acef2dd Merge remote-tracking branch 'SquidDev-CC-ComputerCraft/feature/method-future' into feature/onethread-cobalt 2018-09-09 22:13:45 +01:00
SquidDev
ac8444b364 Migrate ComputerCraft's peripherals/APIs to use the non-blocking API
This is definitely a little ugly in places, mostly due to how we've
handled backwards compatibility.
2018-09-09 17:06:34 +01:00
SquidDev
3b4c1eac1c Proposal for replacing ILuaContext with a non-blocking alternative
This is an initial prototype for ways we could better integrate Lua's
asynchronous functionality (coroutines) with peripherals and other
Lua APIs.

The existing system assumes that coroutines each have a backing thread,
and so yields will suspect the current thread. This takes inspiration
from various "promise" libraries - asynchronous operations (such as
executing on the main thread or waiting for an event) return a promise -
the result of which can be consumed with `.then`.

While I am not aware of plans to change the Lua implementation, this
does give us greater flexibility in the future, leading the way for
Rembulan, single-threaded Cobalt, and even possibly OC support.
2018-09-09 17:04:07 +01:00
SquidDev
7cc77cb1ed Add a basic implementation of the single-thread Lua 2018-09-01 18:40:10 +01:00
SquidDev
0f70d68d0d A minor refactor to computer method calls
We move several rather complex classes into a separate file.
2018-09-01 17:34:26 +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
SquidDev
18429c50f9 Bump version 2018-05-15 11:59:21 +01:00
SquidDev
7ec8ddcf7d Delete previous chat messages of the same type
This uses a custom ComputerCraft packet to send chat messages to the
client. When received, we delete all messages of the same category
before sending the new ones.

This avoids cluttering the chat with near-identical messages, and helps
make working with the "individual dump" command easier, as the previous
computer's dump output is deleted.

Also change the max height of the TextTable to 18, so it fits within
Minecraft's default chat limit.
2018-05-15 11:44:23 +01:00
SquidDev
5bf9f9e3c5 Allow multiple HTTP request methods
This implements an argument format similar to LuaReqeust, as described
in dan200/ComputerCraft#515. The Lua argument checking code is a little
verbose and repetitive, but I'm not sure how to avoid that - we should
look into improving it in the future.

Closes #21
2018-05-15 10:11:08 +01:00
hugeblank
e4164ee9a1 amend additional typo in keys.lua
- fixed circumflex typo: you can now cîrcûmflêx on all your friends
- added comment making it clearer that the following lines are intended for backwards compatibility.
2018-05-14 15:54:06 +01:00
SquidDev
b522af3075 Add some basic tracking for HTTP requests and websockets
This should allow for easier identification of misbehaving computers,
which are consuming a large amount of bandwidth.
2018-05-14 15:52:16 +01:00
SquidDev
8775052dee Merge pull request #543 from hugeblank/ComputerCraft/patch-1
amend typo in keys.lua
2018-05-08 09:46:14 +01:00
hugeblank
c0c5d57e10 amend typo in keys.lua
The most important commit that CC has ever seen.
2018-05-08 01:10:45 -07:00
SquidDev
4c2e97b1af Enable compression extension for websockets 2018-05-05 08:34:35 +01:00
SquidDev
f17df15117 Always create extended disks
Whilst the legacy ones are important for backwards compatibility, they
cannot have an ID of 0, which introduces issues when they are the first
disk created in the world.
2018-04-27 09:51:28 +01:00
SquidDev
bfbb18bdfc Add support for tracking server-thread tasks too
This allows us to track how much work various peripherals are doing.
This will not work with all systems, such as Plethora, as that has its
own execution system.
2018-04-24 12:26:26 +01:00
SquidDev
cac65ef755 Print a useful debug log if we cannot safely terminate a task 2018-04-24 11:07:33 +01:00
SquidDev
a42793024b Merge pull request #541 from SquidDev-CC/ComputerCraft/feature/unlimited-reads
Remove upper bound on how many bytes/characters can be read
2018-04-23 16:42:28 +01:00
SquidDev
7dbc4e6455 Remove upper bound on how many bytes/characters can be read
The limit was added to prevent people creating arbitrarily large buffers
(for instance, handle.read(2^31) would create a 2GB char array). For
"large" counts, we now read in blocks of size 8192, adding to an
extendable buffer.
2018-04-23 16:41:00 +01:00
SquidDev
a0d71cb3ad A couple of fixes for maven and CCEmuX
- Add additional maven metadata and strip dependencies
 - Shift ICommand registration into the proxy, to avoid class loading
   issues. This is probably rather temperamental, but classloading
   always is.
2018-04-20 19:39:53 +01:00
SquidDev
83546d0acb Add support for Maven uploading 2018-04-19 22:36:00 +01:00
SquidDev
e2f9ddd534 Add support for arbitrary trackers
This is used by cc-prometheus to monitor statistics. It isn't currently
exposed as an API, as I'm making no guarantees on the stability of this.
2018-04-19 21:59:35 +01:00
SquidDev
911e404bfa Add support for tracking fs, turtle and peripheral operations
See #25
2018-04-17 09:22:26 +01:00
SquidDev
bfeafe163f Various improvements to the tracking system
- Trackers are created per-user, meaning multiple people can run
   /computercraft track at once.
 - Allow sorting the tracking information by arbitrary fields.
 - Add support for tracking arbitrary fields (though none are currently
   implemented).
2018-04-16 21:06:16 +01:00
SquidDev
6cf32f1f74 Various improvements to peripheral invalidation
- Abstract peripheral ID and type checking into separate class
 - Update peripherals directly rather than marking as invalid then
   fetching from the network.
 - Update peripherals when adjacent tiles change

This does result in a slightly more ugly interface, but reduces the
amount of work needed to perform partial updates of peripherals, such as
those done by neighbouring tile updates.
2018-04-16 18:22:28 +01:00
SquidDev
04f162ef25 Rename node → m_node
It's a bit of an ugly naming convention, but it's consistent.
2018-04-16 17:10:08 +01:00
SquidDev
b2aa390ae1 Split up modem and connection invalidation 2018-04-16 17:02:20 +01:00
SquidDev
6ca61f000f Minor performance improvements to WiredNetworkChange
This should improve the performance of the common case, where one
peripheral set is empty.
2018-04-16 15:31:37 +01:00
SquidDev
20a47a7f88 Merge pull request #540 from SquidDev-CC/ComputerCraft/hotfix/fakeplayer-stubs
Add additional method stubs to the TurtlePlayer
2018-04-16 09:39:15 +01:00
SquidDev
e2e6946c92 Add additional method stubs to the TurtlePlayer
Forge's default fake player implementation doesn't override all methods
which use the connection. As it is not set, we get an NPE and thus crash
the server. We simply stub those methods out ourselves to prevent such
an issue.
2018-04-16 09:34:44 +01:00
SquidDev
abe917cd54 Fix peripherals showing up when they shouldn't on world load
When initially attaching a modem, the adjacent computer would not show
up on its own peripheral list (like in vanilla CC). However, it would
show up when the chunk was reloaded as peripherals were added through a
different method.

This prevents such behaviour, always hiding the remote peripheral from
the object which provides it.

Closes #20
2018-04-07 10:23:11 +01:00
SquidDev
a1d77ab8e7 Merge pull request #536 from Luca0208/ComputerCraft/master
Make wget automatically determine the file name.
2018-04-06 21:08:54 +01:00
SquidDev
c8db671409 Override monitors' lightmap coordinates
Shaders appear to ignore all the other subtle (and not-so-subtle) hints
we drop that monitors shouldn't be rendered with shadows. This solution
isn't optimal, as monitors may still be tinted due to sunlight, but
there is nothing we can do about that.

Many thanks to ferreusveritas for their help in diagnosing, fixing and
testing this issue.
2018-03-30 12:57:27 +01:00
SquidDev
52641b7bea Allow rendering a monitor tile multiple times in a tick
Shader mods may perform multiple passes when rendering a tile, so
monitors will be drawn transparently on later passes. In order to
prevent this we allow drawing the a single tile multiple times in a
tick.
2018-03-29 12:31:20 +01:00
SquidDev
3e751ee94a Use Unicode escape code instead of literal
It doesn't compile under Gradle on my system. Goodness knows why not.
2018-03-29 11:47:45 +01:00
SquidDev
2b28cc3558 Merge remote-tracking branch 'SquidDev-CC-ComputerCraft/hotfix/disk-drive-stop' 2018-03-29 11:41:00 +01:00
SquidDev
f9761388b1 Fix .isDiskPresent() always reporting true
We were still determining if the stack was null, but post-1.11 this will
never be the case.
2018-03-29 11:36:59 +01:00
SquidDev
d28694eb57 Rewrite the command system's text padder
This uses narrow near-invisible unicode characters instead of attempting
to toggle between bold and non-bold spaces.
2018-03-25 21:31:31 +01:00
SquidDev
d758895578 Bump to Forge 1.12.2
Nobody is still using 1.12, so we might as well update to the latest -
it makes it easier to test mod compatibility at least.
2018-03-24 12:20:51 +00:00
SquidDev
043d5f00ca Convert wired elements to capabilities
See #18
2018-03-24 11:57:36 +00:00
SquidDev
36878e75b7 Merge remote-tracking branch 'origin/master' 2018-03-15 17:53:21 +00:00
SquidDev
ebb50cba48 Bump version 2018-03-15 17:43:23 +00:00
SquidDev
7c218361d9 Merge pull request #528 from SquidDev-CC/ComputerCraft/feature/computer-upgrade
Add recipes to upgrade computers
2018-03-15 17:39:17 +00:00
SquidDev
bb2eab0bed Use the ore dictionary for upgrade recipes
See #517 for motivation
2018-03-15 17:34:58 +00:00
SquidDev
e8c0cf3857 Convert TurtleRecipe to use ComputerConvertRecipe
The two recipes are pretty similar, so this allows us to substantially
simplify the code. This now introduces the additional requirement that
computers must be used to create turtles, rather than pocket computers
or another turtle.
2018-03-13 14:21:39 +00:00
SquidDev
db825a7aab Add recipes to convert computer items into their upgraded counterparts
This adds IComputerItem.withFamily(ItemStack, ComputerFamily) as well as
a ComputerFamilyRecipe class. Each type of computer (normal, turtle,
pocket) defines a recipe using this class, as they require a different
number of gold ingots to upgrade.
2018-03-13 14:14:36 +00:00
SquidDev
dbcae810f0 Fix monitor.getTextScale() being doubled
Closes #16. The original method divided by 2 in the getter, but that
was removed in our monitor rewrite.
2018-03-10 22:16:15 +00:00
Daniel Ratcliffe
914df8b0c7 Merge pull request #521 from SquidDev-CC/hotfix/modem-full-block
Fix wireless modems suffocating entities
2018-03-05 14:07:57 +00:00
SquidDev
f753513289 Wrap ComputerCraft fake player with brackets
Hopefully this'll make GriefPrevention play a little nicer
2018-03-01 15:58:09 +00:00
SquidDev
7bb8efed1d Dispose of monitor display lists when unloading worlds
This means all display lists are correctly deleted when changing
dimensions/exiting to main menu.
2018-02-24 20:37:14 +00:00
SquidDev
0cec4aee8c Merge pull request #15 from Lignum/maybe-fix-monitors
Maybe fix monitors
2018-02-24 16:59:21 +00:00
Lignum
e0c9dc24e7 Generate individual display lists instead of relying on their names being contiguous 2018-02-24 17:21:12 +01:00
SquidDev
244907a39a Various cherry picks from world thread safety
- Ensure monitor draw lists are not reused after deleting them.
 - Make terminal methods synchronized, avoding NPEs and IOOBEs when
   resizing.
2018-02-22 21:20:08 +00:00
SquidDev
9be61abd6b Merge pull request #5 from SquidDev-CC/feature/network-api
Well, how badly can this go?
2018-02-21 15:47:15 +00:00
SquidDev
922f424a78 Add full block wired modems
These act similarly to conventional wired modems, but with the advantage
that they are a full block. This means they can be attached to
peripherals which are not solid (such as chests). Further more, as they
do not have a direction, they allow wrapping peripherals on all 6 sides.

It's worth noting that wired modems do not require a cable - they will
automatically form connections to adjacent network elements when placed.
2018-02-21 15:40:08 +00:00
SquidDev
5c7828dd79 Convert TileCable to use the wired network API
There are several important things to note here:

 - The network element is associated with the cable, whilst the
   peripheral (and so packet sender/receiver) is associated with the
   modem. This allows us to have the main element be in the centre of
   the cable block, whilst the modem is in the centre of the adjacent
   computer.

 - Cables will connect to any adjacent network element, not just
   other cables.

 - Rednet messages are now sent on the computer thread, rather than the
   cable tick.
2018-02-21 15:35:38 +00:00
SquidDev
74f5093d2a Add the default implementation of wired networks 2018-02-21 15:29:34 +00:00
SquidDev
4651e362c9 Add an API for wired networks
The API is composed of three primary classes:

- IWiredElement: Represents some physical entity in the network. This
   will generally be a block (such as a cable or modem), but it is not
   required to be.

   Each element can provide a series of peripherals, which will be
   exposed to other elements on the network.

- IWiredNode: Every wired element has a unique wired node. This acts
   as a thread-safe proxy for communicating with the rest of the
   network (such as sending packets). Each node is also its own packet
   network.

- IWiredNetwork: This is responsible for keeping track of nodes and
   peripherals in the network. It provides methods for forming and
   breaking connections, correctly joining and splitting networks where
   needed.

Tiles which wish to be part of a wired network should implement
IWiredElementTile or register a custom IWiredProvider. When loaded into
the world, it should connect to adjacent nodes. Similarly, when removed
(either due to being broken or chunk unloads), it should break those
connections.

There is no method to query the layout of the network, as that offers
greater flexibility in changing or extending the implementation later
on.
2018-02-21 15:26:13 +00:00
SquidDev
a2e2a5cb37 Add the concept of "available peripherals" to IComputerAccess
This provides a mechanism for peripherals to see what else a computer is
connected to - and then interact with those peripherals.

We also add the ability to query what block or tile a peripheral
targets. This allows one to interact with the original block of adjacent
peripherals instead.
2018-02-21 15:25:20 +00:00
SquidDev
15a3882016 Fix monitor clear state being cleared without a redraw 2018-02-21 14:32:53 +00:00
SquidDev
d3ecd5214b Merge remote-tracking branch 'SquidDev-CC-ComputerCraft/feature/turtle-event' 2018-02-16 10:48:43 +00:00
SquidDev
0a8a8a742e Add config options to disable various turtle actions
Mostly intended for those people who don't like .inspect() or
.getItemDetail(), but could allow modpacks to block equipping upgrades,
placing blocks, etc...
2018-02-16 10:37:47 +00:00
SquidDev
ecff23d027 Add turtle events
The main aim of this is to allow for greater extensibility for other
mods. For instance, you can now prevent turtles placing dirt blocks, or
turning when on gravel.
2018-02-16 10:33:32 +00:00
SquidDev
20dcb32bae Add command to reload config from disk
Also bump version number, as we're relatively close to a release and
it's frustrating having to bump it when putting out previews.
2018-02-16 09:33:40 +00:00
SquidDev
678462d2db Minor fixes to the monitor rewrite 2018-02-15 20:49:34 +00:00
SquidDev
61fdfec09b Further overrides for wireless modem blockiness 2018-02-15 18:17:38 +00:00
SquidDev
5eadf5533d Fix wireless modems suffocating entities
As of #458, BlockPeripheral will act as a full/opaque block for some
peripherals and a transparent one for others. However, some Block
methods use the default state rather than the current one. This means
modems report being a full block when they are not, leading to
suffocating entities and lighting glitches.
2018-02-15 18:17:02 +00:00
SquidDev
2d3cd5dc80 Merge pull request #521 from SquidDev-CC/ComputerCraft/hotfix/modem-full-block
Fix wireless modems suffocating entities
2018-02-15 18:00:58 +00:00
SquidDev
5208ad0b98 Fix wireless modems suffocating entities
As of #458, BlockPeripheral will act as a full/opaque block for some
peripherals and a transparent one for others. However, some Block
methods use the default state rather than the current one. This means
modems report being a full block when they are not, leading to
suffocating entities and lighting glitches.
2018-02-15 17:56:08 +00:00
SquidDev
662fb96beb Overhaul monitor's terminal code
This restructures monitor in order to make it thread-safe: namely
removing any world interaction from the computer thread.

Instead of each monitor having their own terminal, resize flag, etc...
we use a monitor "multiblock" object. This is constructed on the origin
monitor and propagated to other monitors when required.

We attempt to construct the multiblock object (and so the corresponding
terminal) as lazily as posible. Consequently, we only create the
terminal when fetching the peripheral (not when attaching, as that is
done on the computer thread).

If a monitor is resized (say due to placing/breaking a monitor) then we
will invalidate all references to the multiblock object, construct a new
one if required, and propagate it to all component monitors.

This commit also fixes several instances of glLists not being deleted
after use. It is not a comprehensive fix, but that is outside the scope
of this commit.
2018-02-14 21:30:07 +00:00
SquidDev
4c14431a3d Various improvements to command system
- Ensure usage is consistent
 - Allow computer selectors to return multiple values
 - Fix commands being marked as usable when it isn't
 - Add /computercraft turn-on, a counter to /computercraft shutdown
2018-02-13 11:45:13 +00:00
SquidDev
5ae38a3f18 Merge pull request #520 from SquidDev-CC/ComputerCraft/hotfix/turtle-world-border
Prevent turtles moving beyond the world border
2018-02-13 11:21:55 +00:00
SquidDev
94e10d1f67 Prevent turtles moving beyond the world border
As tiles outside the world border are not ticked, turtles are rendered
entirely useless. Furthermore, the turtle animation will never progress
resulting in visual glitches.

In order to avoid this, we ensure the target position is within the
world border when moving to it.
2018-02-12 17:50:46 +00:00
SquidDev
0a50676884 Fix turtle owner not being persisted 2018-02-10 16:10:16 +00:00
SquidDev
41cce78fcb Merge pull request #518 from SquidDev-CC/ComputerCraft/feature/owner-tracking
Track which player "owns" a turtle
2018-02-05 11:07:06 +00:00
SquidDev
4c0fa1fabe Track which player "owns" a turtle
When a player places a turtle, they are marked as its owner. Any actions
they perform (such as breaking blocks, moving, etc...) are performed
using this player's game profile.

This allows turtles to work correctly with various permissions mods.
Previously you would have to whitelist all turtles in order for them to
function within a claim.
2018-02-04 21:35:21 +00:00
SquidDev
54e1dafa3f Merge pull request #517 from SquidDev-CC/ComputerCraft/feature/ore-dict
Add ore dictionary support to all recipes
2018-02-04 20:06:34 +00:00
SquidDev
3ac76bc05b Add ore dictionary support to all recipes 2018-02-04 20:02:12 +00:00
SquidDev
83030df3ee Add computer performance monitor 2018-02-02 13:34:27 +00:00
SquidDev
07d15caf6f Bump Cobalt version 2018-02-02 12:26:21 +00:00
SquidDev
3298efe652 Prevent computer dump command sending too much information 2018-01-20 11:07:09 +00:00
SquidDev
01d9919a3e Merge pull request #508 from SquidDev-CC/ComputerCraft/hotfix/turtle-speaker-model
Fix turtle speaker upgrade's missing texture
2018-01-19 13:10:20 +00:00
SquidDev
80b1170b63 Extract required textures from models instead
This ensures we will not get missing texture errors in the future, and
allows resource pack artists to use additional textures.
2018-01-19 13:04:50 +00:00
SquidDev
2e7302e654 Fix turtle speaker upgrade's missing texture
The sprite was not registered into the atlas, meaning it rendered the
missing texture instead.
2018-01-19 12:42:32 +00:00
SquidDev
ca7fb8a0b4 Cache turtle family within the tile
This means one can call .getFamily() in a thread-safe manner, ensuring
turtle.getFuelLimit() does not cause issues. As we use a specialist
TE class for each family this does not require any specialist caching.
2018-01-18 13:06:34 +00:00
SquidDev
c9b0894f26 Cache direction of modems within the tile
This ensures the world is not accessed from another thread.

Closes #410
2018-01-18 13:06:11 +00:00
SquidDev
c3454a195d Merge branch 'master' of https://github.com/dan200/ComputerCraft 2018-01-15 17:18:48 +00:00
Daniel Ratcliffe
3b3dd8071b Merge pull request #506 from Wojbie/Advanced-Monitor-Count-Fix
Fix advanced monitor recipe.
2018-01-15 12:59:35 +00:00
Wojbie
0d28c67534 Fix advanced monitor recipe.
Fix recipe to create 4 monitors.
2018-01-14 23:03:14 +01:00
SquidDev
d0af85754a Merge branch 'master' of https://github.com/dan200/ComputerCraft 2018-01-13 10:49:07 +00:00
Daniel Ratcliffe
3e265c27ff Merge pull request #455 from Wilma456/fileread
Add read() to Filehandle
2018-01-13 00:58:19 +00:00
Daniel Ratcliffe
8d356f50c4 Merge pull request #440 from Wilma456/iomulti
Make io.write() accept multiple args
2018-01-13 00:48:07 +00:00
Daniel Ratcliffe
f30c4f16c0 Merge pull request #411 from Wilma456/copyfixup
Fix Bug in copy.lua, mkdir.lua and rename.lua (updated)
2018-01-13 00:32:55 +00:00
Daniel Ratcliffe
8bb8caa315 Merge pull request #448 from Wilma456/writecheck
Fix check of write()
2018-01-13 00:28:08 +00:00
SquidDev
0f17a3d72e Merge branch 'master' of https://github.com/dan200/ComputerCraft 2018-01-12 14:15:22 +00:00
Daniel Ratcliffe
7647369e2d Merge pull request #446 from Wilma456/moduledir
Add folder /rom/modules
2018-01-12 14:10:50 +00:00
Daniel Ratcliffe
4b4208e724 Merge pull request #476 from SquidDev-CC/hotfix/printer-clear
Fix the printer overwriting the current page
2018-01-12 13:59:51 +00:00
Daniel Ratcliffe
2a16a1df85 Merge pull request #486 from Wilma456/extensionfix
Fix Bug in Paint and Edit
2018-01-12 13:59:15 +00:00
Daniel Ratcliffe
25f7c58400 Merge pull request #494 from SquidDev-CC/hotfix/collision-aabb
Fix getCollisionBoundingBox not using all AABBs
2018-01-12 13:57:50 +00:00
Daniel Ratcliffe
c3db91f11f Merge pull request #485 from Luca0208/patch-1
Removed the "the" that was too much(In /rom/help/cd.txt)
2018-01-12 13:56:48 +00:00
Daniel Ratcliffe
8c66ce03d4 Merge pull request #475 from Wilma456/ioline
Fix io.lines()
2018-01-12 13:56:21 +00:00
Daniel Ratcliffe
2be2a0625e Merge pull request #502 from SquidDev-CC/hotfix/missing-overlay
Fix turtle overlay not being rendered in items
2018-01-12 13:54:59 +00:00
Daniel Ratcliffe
c904d5041b Merge pull request #499 from SquidDev-CC/hotfix/null-network
Fix ComputerCraftAPI.getWirelessNetwork() failing
2018-01-12 13:54:33 +00:00
SquidDev
632762768e Add workaround for incorrect overload of getDrops being overridden
Closes #2
2017-12-30 19:03:32 +00:00
SquidDev
c69ba205f8 Merge pull request #502 from SquidDev-CC/ComputerCraft/hotfix/missing-overlay
Fix turtle overlay not being rendered in items
2017-12-24 21:49:01 +00:00
SquidDev
019f4dbea9 Fix turtle overlay not being rendered in items 2017-12-24 21:44:55 +00:00
SquidDev
259ea41ce3 Merge pull request #499 from SquidDev-CC/ComputerCraft/hotfix/null-network
Fix ComputerCraftAPI.getWirelessNetwork() failing
2017-12-10 15:39:59 +00:00
SquidDev
11290f7204 Fix ComputerCraftAPI.getWirelessNetwork() failing
I've got to admit, it is super embarrassing that a) I didn't notice this
when testing and b) no one else has noticed until now.
2017-12-10 15:37:40 +00:00
Wilma456
4fb0240a36 Changes suggested by SquidDev and update help file 2017-09-24 17:36:20 +02:00
Wilma456 (Jakob0815)
f20a7afa7f Better Code 2017-09-18 15:22:44 +02:00
Wilma456
5be2202b2e Add read() to Filehandle 2017-09-16 16:06:27 +02:00
Wilma456 (Jakob0815)
b8630f739a Add Check requested by dan200 2017-09-13 19:21:17 +02:00
SquidDev
1ef7c8e8db Only send the packet to people within 64 blocks
This is equivalent to what vanilla Minecraft does
2017-09-12 22:44:49 +01:00
Wilma456
1415dd0dae Changes requested by dan200 2017-09-12 20:43:07 +02:00
Wilma456
5989d021c7 Add folder /rom/modules 2017-09-12 16:44:22 +02:00
Wilma456
90c4ebd208 Fix Bug in copy.lua, mkdir.lua and rename.lua 2017-08-09 19:32:29 +02:00
241 changed files with 10699 additions and 2815 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,42 @@ 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).
## 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.
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

@@ -15,19 +15,20 @@ buildscript {
}
plugins {
id 'com.matthewprenger.cursegradle' version '1.0.9'
id 'com.matthewprenger.cursegradle' version '1.0.10'
}
apply plugin: 'net.minecraftforge.gradle.forge'
apply plugin: 'org.ajoberstar.grgit'
apply plugin: 'maven-publish'
apply plugin: 'maven'
version = "1.80pr1.3"
version = "1.80pr1.8"
group = "org.squiddev"
archivesBaseName = "cc-tweaked"
minecraft {
version = "1.12-14.21.1.2387"
version = "1.12.2-14.23.4.2749"
runDir = "run"
replace '${version}', project.version
@@ -36,11 +37,12 @@ minecraft {
// stable_# stables are built at the discretion of the MCP team.
// Use non-default mappings at your own risk. they may not allways work.
// simply re-run your setup task after changing the mappings to update your workspace.
mappings = "snapshot_20170629"
mappings = "snapshot_20180724"
// makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
}
repositories {
mavenLocal()
maven {
name = "JEI"
url = "http://dvs1.progwml6.com/files/maven"
@@ -49,17 +51,27 @@ repositories {
name = "squiddev"
url = "https://dl.bintray.com/squiddev/maven"
}
ivy { artifactPattern "https://asie.pl/files/mods/Charset/LibOnly/[module]-[revision](-[classifier]).[ext]" }
}
configurations {
shade
compile.extendsFrom shade
deployerJars
}
dependencies {
deobfProvided "mezz.jei:jei_1.12:4.7.5.86:api"
runtime "mezz.jei:jei_1.12:4.7.5.86"
shade 'org.squiddev:Cobalt:0.3.0'
deobfProvided "mezz.jei:jei_1.12.2:4.8.5.159:api"
deobfProvided "pl.asie:Charset-Lib:0.5.4.6"
runtime "mezz.jei:jei_1.12.2:4.8.5.159"
shade 'org.squiddev:Cobalt:0.3.2-nothread'
testCompile 'junit:junit:4.11'
deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0"
}
javadoc {
@@ -124,6 +136,59 @@ curseforge {
}
}
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifact sourceJar
}
}
}
uploadArchives {
repositories {
if(project.hasProperty('mavenUploadUrl')) {
mavenDeployer {
configuration = configurations.deployerJars
repository(url: project.property('mavenUploadUrl')) {
authentication(
userName: project.property('mavenUploadUser'),
privateKey: project.property('mavenUploadKey'))
}
pom.project {
name 'CC: Tweaked'
packaging 'jar'
description 'A fork of ComputerCraft which aims to provide earlier access to the more experimental and in-development features of the mod.'
url 'https://github.com/SquidDev-CC/CC-Tweaked'
scm {
url 'https://github.com/dan200/ComputerCraft.git'
}
issueManagement {
system 'github'
url 'https://github.com/dan200/ComputerCraft/issues'
}
licenses {
license {
name 'ComputerCraft Public License, Version 1.0'
url 'https://github.com/dan200/ComputerCraft/blob/master/LICENSE'
distribution 'repo'
}
}
}
pom.whenConfigured { pom ->
pom.dependencies.clear()
}
}
}
}
}
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint"
@@ -132,3 +197,9 @@ gradle.projectsEvaluated {
runClient.outputs.upToDateWhen { false }
runServer.outputs.upToDateWhen { false }
test {
testLogging {
events "failed", "standardOut", "standardError"
}
}

1
settings.gradle Normal file
View File

@@ -0,0 +1 @@
rootProject.name = 'cc-tweaked'

View File

@@ -7,23 +7,28 @@
package dan200.computercraft;
import com.google.common.base.CaseFormat;
import com.google.common.base.Converter;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.api.permissions.ITurtlePermissionProvider;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.core.apis.AddressPredicate;
import dan200.computercraft.core.filesystem.ComboMount;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.JarMount;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.command.CommandComputer;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
@@ -42,6 +47,7 @@ import dan200.computercraft.shared.network.ComputerCraftPacket;
import dan200.computercraft.shared.network.PacketHandler;
import dan200.computercraft.shared.peripheral.common.BlockCable;
import dan200.computercraft.shared.peripheral.common.BlockPeripheral;
import dan200.computercraft.shared.peripheral.common.BlockWiredModemFull;
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
import dan200.computercraft.shared.peripheral.modem.BlockAdvancedModem;
import dan200.computercraft.shared.peripheral.modem.WirelessNetwork;
@@ -55,6 +61,8 @@ import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.*;
import dan200.computercraft.shared.wired.CapabilityWiredElement;
import dan200.computercraft.shared.wired.WiredNode;
import io.netty.buffer.Unpooled;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
@@ -62,11 +70,13 @@ import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketBuffer;
import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand;
import net.minecraft.util.NonNullList;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.common.config.ConfigCategory;
import net.minecraftforge.common.config.Configuration;
@@ -88,6 +98,7 @@ import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -140,6 +151,7 @@ public class ComputerCraft
public static int advancedTurtleFuelLimit = 100000;
public static boolean turtlesObeyBlockProtection = true;
public static boolean turtlesCanPush = true;
public static EnumSet<TurtleAction> turtleDisabledActions = EnumSet.noneOf( TurtleAction.class );
public static final int terminalWidth_computer = 51;
public static final int terminalHeight_computer = 19;
@@ -172,6 +184,7 @@ public class ComputerCraft
public static BlockTurtle turtleAdvanced;
public static BlockCommandComputer commandComputer;
public static BlockAdvancedModem advancedModem;
public static BlockWiredModemFull wiredModemFull;
}
public static class Items
@@ -222,6 +235,7 @@ public class ComputerCraft
public static Property advancedTurtleFuelLimit;
public static Property turtlesObeyBlockProtection;
public static Property turtlesCanPush;
public static Property turtleDisabledActions;
public static Property modem_range;
public static Property modem_highAltitudeRange;
@@ -277,6 +291,18 @@ public class ComputerCraft
// Load config
Config.config = new Configuration( event.getSuggestedConfigurationFile() );
loadConfig();
// Setup network
networkEventChannel = NetworkRegistry.INSTANCE.newEventDrivenChannel( "CC" );
networkEventChannel.register( new PacketHandler() );
proxy.preInit();
turtleProxy.preInit();
}
public static void loadConfig()
{
Config.config.load();
Config.http_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "http_enable", http_enable );
@@ -365,6 +391,9 @@ public class ComputerCraft
Config.turtlesCanPush = Config.config.get( Configuration.CATEGORY_GENERAL, "turtlesCanPush", turtlesCanPush );
Config.turtlesCanPush.setComment( "If set to true, Turtles will push entities out of the way instead of stopping if there is space to do so" );
Config.turtleDisabledActions = Config.config.get( Configuration.CATEGORY_GENERAL, "turtle_disabled_actions", new String[ 0 ] );
Config.turtleDisabledActions.setComment( "A list of turtle actions which are disabled." );
Config.maxNotesPerTick = Config.config.get( Configuration.CATEGORY_GENERAL, "maxNotesPerTick", maxNotesPerTick );
Config.maxNotesPerTick.setComment( "Maximum amount of notes a speaker can play at once" );
@@ -374,13 +403,6 @@ public class ComputerCraft
}
syncConfig();
// Setup network
networkEventChannel = NetworkRegistry.INSTANCE.newEventDrivenChannel( "CC" );
networkEventChannel.register( new PacketHandler() );
proxy.preInit();
turtleProxy.preInit();
}
public static void syncConfig() {
@@ -412,6 +434,20 @@ public class ComputerCraft
turtlesObeyBlockProtection = Config.turtlesObeyBlockProtection.getBoolean();
turtlesCanPush = Config.turtlesCanPush.getBoolean();
turtleDisabledActions.clear();
Converter<String, String> converter = CaseFormat.LOWER_CAMEL.converterTo( CaseFormat.UPPER_UNDERSCORE );
for( String value : Config.turtleDisabledActions.getStringList() )
{
try
{
turtleDisabledActions.add( TurtleAction.valueOf( converter.convert( value ) ) );
}
catch( IllegalArgumentException e )
{
ComputerCraft.log.error( "Unknown turtle action " + value );
}
}
maxNotesPerTick = Math.max(1, Config.maxNotesPerTick.getInt());
Config.config.save();
@@ -427,8 +463,7 @@ public class ComputerCraft
@Mod.EventHandler
public void onServerStarting( FMLServerStartingEvent event )
{
event.registerServerCommand( new CommandComputer() );
event.registerServerCommand( new CommandComputerCraft() );
proxy.initServer( event.getServer() );
}
@Mod.EventHandler
@@ -438,6 +473,7 @@ public class ComputerCraft
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
}
}
@@ -448,6 +484,7 @@ public class ComputerCraft
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
}
}
@@ -471,11 +508,6 @@ public class ComputerCraft
return proxy.getRenderFrame();
}
public static void deleteDisplayLists( int list, int range )
{
proxy.deleteDisplayLists( list, range );
}
public static Object getFixedWidthFontRenderer()
{
return proxy.getFixedWidthFontRenderer();
@@ -704,6 +736,11 @@ public class ComputerCraft
}
}
public static IWiredNode createWiredNodeForElement( IWiredElement element )
{
return new WiredNode( element );
}
public static IPeripheral getPeripheralAt( World world, BlockPos pos, EnumFacing side )
{
// Try the handlers in order:
@@ -725,6 +762,14 @@ public class ComputerCraft
return null;
}
public static IWiredElement getWiredElementAt( IBlockAccess world, BlockPos pos, EnumFacing side )
{
TileEntity tile = world.getTileEntity( pos );
return tile != null && tile.hasCapability( CapabilityWiredElement.CAPABILITY, side )
? tile.getCapability( CapabilityWiredElement.CAPABILITY, side )
: null;
}
public static int getDefaultBundledRedstoneOutput( World world, BlockPos pos, EnumFacing side )
{
if( WorldUtil.isBlockInWorld( world, pos ) )
@@ -826,7 +871,7 @@ public class ComputerCraft
return upgrades;
}
public IPacketNetwork getWirelessNetwork()
public static IPacketNetwork getWirelessNetwork()
{
return WirelessNetwork.getUniversal();
}
@@ -1080,13 +1125,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

@@ -12,6 +12,8 @@ import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider;
@@ -21,6 +23,7 @@ import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
@@ -328,6 +331,61 @@ public final class ComputerCraftAPI
}
}
/**
* Construct a new wired node for a given wired element
*
* @param element The element to construct it for
* @return The element's node
* @see IWiredElement#getNode()
*/
@Nonnull
public static IWiredNode createWiredNodeForElement( @Nonnull IWiredElement element )
{
findCC();
if( computerCraft_createWiredNodeForElement != null )
{
try
{
return (IWiredNode) computerCraft_createWiredNodeForElement.invoke( null, element );
}
catch( ReflectiveOperationException e )
{
throw new IllegalStateException( "Error creating wired node", e );
}
}
else
{
throw new IllegalStateException( "ComputerCraft cannot be found" );
}
}
/**
* Get the wired network element for a block in world
*
* @param world The world the block exists in
* @param pos The position the block exists in
* @param side The side to extract the network element from
* @return The element's node
* @see IWiredElement#getNode()
*/
@Nullable
public static IWiredElement getWiredElementAt( @Nonnull IBlockAccess world, @Nonnull BlockPos pos, @Nonnull EnumFacing side )
{
findCC();
if( computerCraft_getWiredElementAt != null )
{
try
{
return (IWiredElement) computerCraft_getWiredElementAt.invoke( null, world, pos, side );
}
catch( ReflectiveOperationException ignored )
{
}
}
return null;
}
// The functions below here are private, and are used to interface with the non-API ComputerCraft classes.
// Reflection is used here so you can develop your mod without decompiling ComputerCraft and including
// it in your solution, and so your mod won't crash if ComputerCraft is installed.
@@ -374,6 +432,12 @@ public final class ComputerCraftAPI
computerCraft_registerAPIFactory = findCCMethod( "registerAPIFactory", new Class<?>[] {
ILuaAPIFactory.class
} );
computerCraft_createWiredNodeForElement = findCCMethod( "createWiredNodeForElement", new Class<?>[] {
IWiredElement.class
} );
computerCraft_getWiredElementAt = findCCMethod( "getWiredElementAt", new Class<?>[]{
IBlockAccess.class, BlockPos.class, EnumFacing.class
} );
} catch( Exception e ) {
System.out.println( "ComputerCraftAPI: ComputerCraft not found." );
} finally {
@@ -411,4 +475,6 @@ public final class ComputerCraftAPI
private static Method computerCraft_registerPocketUpgrade = null;
private static Method computerCraft_getWirelessNetwork = null;
private static Method computerCraft_registerAPIFactory = null;
private static Method computerCraft_createWiredNodeForElement = null;
private static Method computerCraft_getWiredElementAt = null;
}

View File

@@ -0,0 +1,31 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
/**
* An interface passed to peripherals and {@link ILuaObject}s by computers or turtles, providing methods that allow the
* method to interact with the invoking computer.
*/
public interface ICallContext
{
/**
* Queue a task to be executed on the main server thread at the beginning of next tick, but do not wait for it to
* complete. This should be used when you need to interact with the world in a thread-safe manner but do not care
* about the result or you wish to run asynchronously.
*
* When the task has finished, it will enqueue a {@code task_completed} event, which takes the task id, a success
* value and the return values, or an error message if it failed. If you need to wait on this event, it may be
* better to use {@link MethodResult#onMainThread(ILuaCallable)}.
*
* @param task The task to execute on the main thread.
* @return The "id" of the task. This will be the first argument to the {@code task_completed} event.
* @throws LuaException If the task could not be queued.
*/
long issueMainThreadTask( @Nonnull ILuaTask task ) throws LuaException;
}

View File

@@ -0,0 +1,31 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
/**
* A function which calls performs an action in a specific context (such as on the server thread) and returns a result.
*
* @see MethodResult#onMainThread(ILuaCallable)
* @see ILuaContext#executeMainThreadTask(ILuaTask)
*/
@FunctionalInterface
public interface ILuaCallable
{
/**
* Run the code within the specified context and return the result to continue with.
*
* @return The result of executing this function. Note that this may not be evaluated within the same context as
* this call is.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
*/
@Nonnull
MethodResult execute() throws LuaException;
}

View File

@@ -1,6 +1,6 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only.
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
@@ -13,8 +13,11 @@ import javax.annotation.Nullable;
* An interface passed to peripherals and {@link ILuaObject}s by computers or turtles, providing methods
* that allow the peripheral call to wait for events before returning, just like in lua. This is very useful if you need
* to signal work to be performed on the main thread, and don't want to return until the work has been completed.
*
* This interface mostly exists for integrating with older code. One should use {@link MethodResult} instead, as this
* encourages an asynchronous way of interacting with Lua coroutines.
*/
public interface ILuaContext
public interface ILuaContext extends ICallContext
{
/**
* Wait for an event to occur on the computer, suspending the thread until it arises. This method is exactly
@@ -30,8 +33,10 @@ public interface ILuaContext
* @throws InterruptedException If the user shuts down or reboots the computer while pullEvent() is waiting for an
* event, InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @deprecated Use {@link MethodResult#pullEvent(String, ILuaFunction)}
*/
@Nonnull
@Deprecated
Object[] pullEvent( @Nullable String filter ) throws LuaException, InterruptedException;
/**
@@ -45,8 +50,10 @@ public interface ILuaContext
* an event, InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @see #pullEvent(String)
* @deprecated Use {@link MethodResult#pullEventRaw(String, ILuaFunction)}
*/
@Nonnull
@Deprecated
Object[] pullEventRaw( @Nullable String filter ) throws InterruptedException;
/**
@@ -59,8 +66,10 @@ public interface ILuaContext
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @see #pullEvent(String)
* @deprecated Use {@link MethodResult#pullEventRaw(ILuaFunction)}
*/
@Nonnull
@Deprecated
Object[] yield( @Nullable Object[] arguments ) throws InterruptedException;
/**
@@ -76,22 +85,9 @@ public interface ILuaContext
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @deprecated Use {@link MethodResult#onMainThread(ILuaCallable)}
*/
@Nullable
@Deprecated
Object[] executeMainThreadTask( @Nonnull ILuaTask task ) throws LuaException, InterruptedException;
/**
* Queue a task to be executed on the main server thread at the beginning of next tick, but do not wait for it to
* complete. This should be used when you need to interact with the world in a thread-safe manner but do not care
* about the result or you wish to run asynchronously.
*
* When the task has finished, it will enqueue a {@code task_completed} event, which takes the task id, a success
* value and the return values, or an error message if it failed. If you need to wait on this event, it may be
* better to use {@link #executeMainThreadTask(ILuaTask)}.
*
* @param task The task to execute on the main thread.
* @return The "id" of the task. This will be the first argument to the {@code task_completed} event.
* @throws LuaException If the task could not be queued.
*/
long issueMainThreadTask( @Nonnull ILuaTask task ) throws LuaException;
}

View File

@@ -0,0 +1,24 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A function which executes using a {@link ILuaContext}.
*
* Like {@link ILuaContext}, this is not intended for use in the future - it purely exists as an argument for
* {@link MethodResult#withLuaContext(ILuaContextTask)}.
*/
@FunctionalInterface
public interface ILuaContextTask
{
@Nullable
@Deprecated
Object[] execute( @Nonnull ILuaContext context ) throws LuaException, InterruptedException;
}

View File

@@ -0,0 +1,33 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A Lua function which consumes some values and returns a result.
*
* @see MethodResult#then(ILuaFunction)
* @see MethodResult#pullEvent(ILuaFunction)
* @see MethodResult#pullEventRaw(String)
*/
@FunctionalInterface
public interface ILuaFunction
{
/**
* Accept the values and return another method result.
*
* @param values The inputs for this function.
* @return The result of executing this function.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
*/
@Nonnull
MethodResult call( @Nullable Object[] values ) throws LuaException;
}

View File

@@ -1,6 +1,6 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only.
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
@@ -41,16 +41,45 @@ public interface ILuaObject
* wishes to call. The integer indicates the index into the getMethodNames() table
* that corresponds to the string passed into peripheral.call()
* @param arguments The arguments for this method. See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}
* the possible values and conversion rules.
* @return An array of objects, representing the values you wish to return to the Lua program.
* See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])} for the valid values and
* conversion rules.
* @throws LuaException If the task could not be queued, or if the task threw an exception.
* for the possible values and conversion rules.
* @return An array of objects, representing the values you wish to return to the Lua program. See
* {@link MethodResult#of(Object...)} for the valid values and conversion rules.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.w
* intercepted, or the computer will leak memory and end up in a broken state.
* @see IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])
* @deprecated Use {@link #callMethod(ICallContext, int, Object[])} instead.
*/
@Nullable
@Deprecated
Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
/**
* Called when a user calls one of the methods that this object implements. This works the same as
* {@link IPeripheral#callMethod(IComputerAccess, ICallContext, int, Object[])}}. See that method for detailed
* documentation.
*
* @param context The context of the current call.
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
* wishes to call. The integer indicates the index into the getMethodNames() table
* that corresponds to the string passed into peripheral.call()
* @param arguments The arguments for this method. See {@link IPeripheral#callMethod(IComputerAccess, ICallContext, int, Object[])}
* for the possible values and conversion rules.
* @return The result of calling this method. Use {@link MethodResult#empty()} to return nothing or
* {@link MethodResult#of(Object...)} to return several values.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
* @see IPeripheral#callMethod(IComputerAccess, ICallContext, int, Object[])
* @see MethodResult
*/
@Nonnull
@SuppressWarnings( { "deprecation" } )
default MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return MethodResult.withLuaContext( lua -> callMethod( lua, method, arguments ) );
}
}

View File

@@ -0,0 +1,105 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* Evaluates {@link MethodResult}s within a {@link ILuaContext}.
*
* @see MethodResult#evaluate(ILuaContext)
* @see MethodResult#withLuaContext(ILuaContextTask)
* @deprecated This should not be used except to interface between the two call call systems.
*/
@Deprecated
class LuaContextResultEvaluator
{
@Deprecated
public static Object[] evaluate( @Nonnull ILuaContext context, @Nonnull MethodResult future ) throws LuaException, InterruptedException
{
Deque<ILuaFunction> callbacks = null;
while( true )
{
if( future instanceof MethodResult.AndThen )
{
MethodResult.AndThen then = ((MethodResult.AndThen) future);
// Thens are "unwrapped", being pushed onto a stack
if( callbacks == null ) callbacks = new ArrayDeque<>();
callbacks.addLast( then.getCallback() );
future = then.getPrevious();
if( future == null ) throw new NullPointerException( "Null result from " + then.getCallback() );
}
else if( future instanceof MethodResult.Immediate )
{
Object[] values = ((MethodResult.Immediate) future).getResult();
// Immediate values values will attempt to call the previous "then", or return if nothing
// else needs to be done.
ILuaFunction callback = callbacks == null ? null : callbacks.pollLast();
if( callback == null ) return values;
future = callback.call( values );
if( future == null ) throw new NullPointerException( "Null result from " + callback );
}
else if( future instanceof MethodResult.OnEvent )
{
MethodResult.OnEvent onEvent = (MethodResult.OnEvent) future;
// Poll for an event, and then call the previous "then" or return if nothing else needs
// to be done.
Object[] values = onEvent.isRaw() ? context.pullEventRaw( onEvent.getFilter() ) : context.pullEvent( onEvent.getFilter() );
ILuaFunction callback = callbacks == null ? null : callbacks.pollLast();
if( callback == null ) return values;
future = callback.call( values );
if( future == null ) throw new NullPointerException( "Null result from " + callback );
}
else if( future instanceof MethodResult.OnMainThread )
{
MethodResult.OnMainThread onMainThread = (MethodResult.OnMainThread) future;
// Evaluate our task on the main thread and mark it as the next future to evaluate.
Reference temporary = new Reference();
context.executeMainThreadTask( () -> {
temporary.value = onMainThread.getTask().execute();
return null;
} );
future = temporary.value;
if( future == null ) throw new NullPointerException( "Null result from " + onMainThread.getTask() );
}
else if( future instanceof MethodResult.WithLuaContext )
{
MethodResult.WithLuaContext withContext = (MethodResult.WithLuaContext) future;
// Run the task, and then call the previous "then" or return if nothing else
// needs to be done.
Object[] values = withContext.getConsumer().execute( context );
ILuaFunction callback = callbacks == null ? null : callbacks.pollLast();
if( callback == null ) return values;
future = callback.call( values );
if( future == null ) throw new NullPointerException( "Null result from " + callback );
}
else
{
throw new IllegalStateException( "Unknown MethodResult " + future );
}
}
}
private static class Reference
{
MethodResult value;
}
}

View File

@@ -0,0 +1,354 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ListenableFuture;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
/**
* The result of calling a method, such as {@link ILuaObject#callMethod(ICallContext, int, Object[])} or
* {@link IPeripheral#callMethod(IComputerAccess, ICallContext, int, Object[])}.
*
* This is non-dissimilar to a promise or {@link ListenableFuture}. One can either return an immediate value through
* {@link #of(Object...)}, wait for an external action with {@link #onMainThread(ILuaCallable)} or {@link #pullEvent()}
* and then act on the result of either of those by using {@link #then(ILuaFunction)}.
*/
public abstract class MethodResult
{
private static MethodResult empty;
MethodResult()
{
}
/**
* A result which returns immediately with no value.
*
* Use {@link #of(Object...)} if you need to return one or more values.
*
* @return The empty method result.
* @see #of(Object...)
*/
@Nonnull
public static MethodResult empty()
{
if( empty == null ) empty = new Immediate( null );
return empty;
}
/**
* A result which returns several values.
*
* @param result The values to return, this may be {@code null}. {@link Number}s, {@link String}s, {@link Boolean}s,
* {@link Map}s, {@link ILuaObject}s, and {@code null} be converted to their corresponding lua type.
* All other types will be converted to nil.
* @return A result which will return these values when evaluated.
* @see #empty()
*/
@Nonnull
public static MethodResult of( Object... result )
{
return result == null ? empty() : new Immediate( result );
}
/**
* Wait for an event to occur on the computer, suspending the coroutine until it arises. This method is equivalent
* to {@code os.pullEvent()} in Lua.
*
* Normally you'll wish to consume the event using {@link #then(ILuaFunction)}. This can be done slightly more
* easily with {@link #pullEvent(ILuaFunction)}.
*
* If you want to listen to a specific event, it's easier to use {@link #pullEvent(String)} rather than
* running until the desired event is found.
*
* @return The constructed method result. This evaluates to the name of the event that occurred, and any event
* parameters.
* @see #pullEvent(ILuaFunction)
* @see #pullEvent(String)
*/
@Nonnull
public static MethodResult pullEvent()
{
return new OnEvent( false, null );
}
/**
* Wait for the specified event to occur on the computer, suspending the coroutine until it arises. This method is
* equivalent to {@code os.pullEvent(event)} in Lua.
*
* Normally you'll wish to consume the event using {@link #then(ILuaFunction)}. This can be done slightly more
* easily with {@link #pullEvent(String, ILuaFunction)}.
*
* @param filter The event name to filter on.
* @return The constructed method result. This evaluates to the name of the event that occurred, and any event
* parameters.
* @see #pullEvent(String, ILuaFunction)
* @see #pullEvent()
*/
@Nonnull
public static MethodResult pullEvent( @Nonnull String filter )
{
Preconditions.checkNotNull( filter, "event cannot be null" );
return new OnEvent( false, filter );
}
/**
* Wait for an event to occur on the computer, suspending the coroutine until it arises. This method to
* {@link #pullEvent()} and {@link #then(ILuaFunction)}.
*
* If you want to listen to a specific event, it's easier to use {@link #pullEvent(String, ILuaFunction)} rather
* than running until the desired event is found.
*
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
* @see #pullEvent()
* @see #pullEvent(String, ILuaFunction)
*/
@Nonnull
public static MethodResult pullEvent( @Nonnull ILuaFunction callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new OnEvent( false, null ).then( callback );
}
/**
* Wait for the specified event to occur on the computer, suspending the coroutine until it arises. This method to
* {@link #pullEvent(String)} and {@link #then(ILuaFunction)}.
*
* @param filter The event name to filter on.
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
* @see #pullEvent(String)
* @see #pullEvent(ILuaFunction)
*/
@Nonnull
public static MethodResult pullEvent( @Nullable String filter, @Nonnull ILuaFunction callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new OnEvent( false, filter ).then( callback );
}
/**
* The same as {@link #pullEvent()}, except {@code terminated} events are also passed to the callback, instead of
* throwing an error. Only use this if you want to prevent program termination, which is not recommended.
*
* @return The constructed method result. This evaluates to the name of the event that occurred, and any event
* parameters.
*/
@Nonnull
public static MethodResult pullEventRaw()
{
return new OnEvent( true, null );
}
/**
* The same as {@link #pullEvent(String)}, except {@code terminated} events are also passed to the callback, instead
* of throwing an error. Only use this if you want to prevent program termination, which is not recommended.
*
* @param filter The event name to filter on.
* @return The constructed method result. This evaluates to the name of the event that occurred, and any event
* parameters.
*/
@Nonnull
public static MethodResult pullEventRaw( @Nonnull String filter )
{
return new OnEvent( true, filter );
}
/**
* The same as {@link #pullEvent(ILuaFunction)}, except {@code terminated} events are also passed to the callback,
* instead of throwing an error. Only use this if you want to prevent program termination, which is not recommended.
*
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
*/
@Nonnull
public static MethodResult pullEventRaw( @Nonnull ILuaFunction callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new OnEvent( true, null ).then( callback );
}
/**
* The same as {@link #pullEvent(String, ILuaFunction)}, except {@code terminated} events are also passed to the
* callback, instead of throwing an error. Only use this if you want to prevent program termination, which is not
* recommended.
*
* @param filter The event name to filter on.
* @param callback The function to call when the event is received.
* @return The constructed method result. This evaluates to the result of the {@code callback}.
*/
@Nonnull
public static MethodResult pullEventRaw( @Nullable String filter, @Nonnull ILuaFunction callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new OnEvent( true, filter ).then( callback );
}
/**
* Queue a task to be executed on the main server thread at the beginning of next tick, waiting for it to complete.
* This should be used when you need to interact with the world in a thread-safe manner.
*
* @param callback The task to execute on the server thread.
* @return The constructed method result, which evaluates to the result of the {@code callback}.
*/
@Nonnull
public static MethodResult onMainThread( @Nonnull ILuaCallable callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new OnMainThread( callback );
}
/**
* Consume the result of this {@link MethodResult} and return another result.
*
* Note this does NOT modify the current method result, rather returning a new (wrapped) one. You must return the
* result of this call if you wish to use it.
*
* @param callback The function which consumes the provided values.
* @return The constructed method result.
*/
@Nonnull
public final MethodResult then( @Nonnull ILuaFunction callback )
{
Preconditions.checkNotNull( callback, "callback cannot be null" );
return new AndThen( this, callback );
}
/**
* Execute a blocking task within a {@link ILuaContext} and return its result.
*
* @param consumer The task to execute with the provided Lua context.
* @return The constructed method result.
* @see #evaluate(ILuaContext)
* @deprecated This should not be used except to interface between the two call systems.
*/
@Deprecated
public static MethodResult withLuaContext( @Nonnull ILuaContextTask consumer )
{
Preconditions.checkNotNull( consumer, "consumer cannot be null" );
return new WithLuaContext( consumer );
}
/**
* Evaluate this result task using {@link ILuaContext} and return its result.
*
* @param context The context to execute with.
* @return The resulting values.
* @throws LuaException If an error was thrown while executing one of the methods within this future.
* @throws InterruptedException If the user shuts down or reboots the computer while the coroutine is suspended.
* @see #withLuaContext(ILuaContextTask)
* @deprecated This should not be used except to interface between the two call systems.
*/
@Deprecated
public final Object[] evaluate( @Nonnull ILuaContext context ) throws LuaException, InterruptedException
{
return LuaContextResultEvaluator.evaluate( context, this );
}
public static class Immediate extends MethodResult
{
@Nullable
private final Object[] values;
@Nullable
private Immediate( Object[] values )
{
this.values = values;
}
public Object[] getResult()
{
return values;
}
}
public static class OnEvent extends MethodResult
{
private final boolean raw;
private final String filter;
private OnEvent( boolean raw, String filter )
{
this.raw = raw;
this.filter = filter;
}
public boolean isRaw()
{
return raw;
}
@Nullable
public String getFilter()
{
return filter;
}
}
public static class OnMainThread extends MethodResult
{
private final ILuaCallable task;
public OnMainThread( ILuaCallable task )
{
this.task = task;
}
@Nonnull
public ILuaCallable getTask()
{
return task;
}
}
public static class AndThen extends MethodResult
{
private final MethodResult previous;
private final ILuaFunction callback;
private AndThen( MethodResult previous, ILuaFunction callback )
{
this.previous = previous;
this.callback = callback;
}
@Nonnull
public MethodResult getPrevious()
{
return previous;
}
@Nonnull
public ILuaFunction getCallback()
{
return callback;
}
}
public static class WithLuaContext extends MethodResult
{
private final ILuaContextTask consumer;
private WithLuaContext( ILuaContextTask consumer )
{
this.consumer = consumer;
}
@Nonnull
public ILuaContextTask getConsumer()
{
return consumer;
}
}
}

View File

@@ -0,0 +1,29 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.ComputerCraftAPI;
import javax.annotation.Nonnull;
/**
* An object which may be part of a wired network.
*
* Elements should construct a node using {@link ComputerCraftAPI#createWiredNodeForElement(IWiredElement)}. This acts
* as a proxy for all network objects. Whilst the node may change networks, an element's node should remain constant
* for its lifespan.
*
* Elements are generally tied to a block or tile entity in world. In such as case, one should provide the
* {@link IWiredElement} capability for the appropriate sides.
*/
public interface IWiredElement extends IWiredSender
{
/**
* Called when objects on the network change. This may occur when network nodes are added or removed, or when
* peripherals change.
*
* @param change The change which occurred.
* @see IWiredNetworkChange
*/
default void networkChanged( @Nonnull IWiredNetworkChange change )
{
}
}

View File

@@ -0,0 +1,80 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* A wired network is composed of one of more {@link IWiredNode}s, a set of connections between them, and a series
* of peripherals.
*
* Networks from a connected graph. This means there is some path between all nodes on the network. Further more, if
* there is some path between two nodes then they must be on the same network. {@link IWiredNetwork} will automatically
* handle the merging and splitting of networks (and thus changing of available nodes and peripherals) as connections
* change.
*
* This does mean one can not rely on the network remaining consistent between subsequent operations. Consequently,
* it is generally preferred to use the methods provided by {@link IWiredNode}.
*
* @see IWiredNode#getNetwork()
*/
public interface IWiredNetwork
{
/**
* Create a connection between two nodes.
*
* This should only be used on the server thread.
*
* @param left The first node to connect
* @param right The second node to connect
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @throws IllegalStateException If neither node is on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see IWiredNode#connectTo(IWiredNode)
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
*/
boolean connect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
/**
* Destroy a connection between this node and another.
*
* This should only be used on the server thread.
*
* @param left The first node in the connection.
* @param right The second node in the connection.
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If either node is not on the network.
* @throws IllegalArgumentException If {@code left} and {@code right} are equal.
* @see IWiredNode#disconnectFrom(IWiredNode)
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
*/
boolean disconnect( @Nonnull IWiredNode left, @Nonnull IWiredNode right );
/**
* Sever all connections this node has, removing it from this network.
*
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param node The node to remove
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredNode#remove()
*/
boolean remove( @Nonnull IWiredNode node );
/**
* Update the peripherals a node provides.
*
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param node The node to attach peripherals for.
* @param peripherals The new peripherals for this node.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredNode#updatePeripherals(Map)
*/
void updatePeripherals( @Nonnull IWiredNode node, @Nonnull Map<String, IPeripheral> peripherals );
}

View File

@@ -0,0 +1,32 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* Represents a change to the objects on a wired network.
*
* @see IWiredElement#networkChanged(IWiredNetworkChange)
*/
public interface IWiredNetworkChange
{
/**
* A set of peripherals which have been removed. Note that there may be entries with the same name
* in the added and removed set, but with a different peripheral.
*
* @return The set of removed peripherals.
*/
@Nonnull
Map<String, IPeripheral> peripheralsRemoved();
/**
* A set of peripherals which have been added. Note that there may be entries with the same name
* in the added and removed set, but with a different peripheral.
*
* @return The set of added peripherals.
*/
@Nonnull
Map<String, IPeripheral> peripheralsAdded();
}

View File

@@ -0,0 +1,103 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.IPacketNetwork;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* Wired nodes act as a layer between {@link IWiredElement}s and {@link IWiredNetwork}s.
*
* Firstly, a node acts as a packet network, capable of sending and receiving modem messages to connected nodes. These
* methods may be safely used on any thread.
*
* When sending a packet, the system will attempt to find the shortest path between the two nodes based on their
* element's position. Note that packet senders and receivers can have different locations from their associated
* element: the distance between the two will be added to the total packet's distance.
*
* Wired nodes also provide several convenience methods for interacting with a wired network. These should only ever
* be used on the main server thread.
*/
public interface IWiredNode extends IPacketNetwork
{
/**
* The associated element for this network node.
*
* @return This node's element.
*/
@Nonnull
IWiredElement getElement();
/**
* The network this node is currently connected to. Note that this may change
* after any network operation, so it should not be cached.
*
* This should only be used on the server thread.
*
* @return This node's network.
*/
@Nonnull
IWiredNetwork getNetwork();
/**
* Create a connection from this node to another.
*
* This should only be used on the server thread.
*
* @param node The other node to connect to.
* @return {@code true} if a connection was created or {@code false} if the connection already exists.
* @see IWiredNetwork#connect(IWiredNode, IWiredNode)
* @see IWiredNode#disconnectFrom(IWiredNode)
*/
default boolean connectTo( @Nonnull IWiredNode node )
{
return getNetwork().connect( this, node );
}
/**
* Destroy a connection between this node and another.
*
* This should only be used on the server thread.
*
* @param node The other node to disconnect from.
* @return {@code true} if a connection was destroyed or {@code false} if no connection exists.
* @throws IllegalArgumentException If {@code node} is not on the same network.
* @see IWiredNetwork#disconnect(IWiredNode, IWiredNode)
* @see IWiredNode#connectTo(IWiredNode)
*/
default boolean disconnectFrom( @Nonnull IWiredNode node )
{
return getNetwork().disconnect( this, node );
}
/**
* Sever all connections this node has, removing it from this network.
*
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @return Whether this node was removed from the network. One cannot remove a node from a network where it is the
* only element.
* @throws IllegalArgumentException If the node is not in the network.
* @see IWiredNetwork#remove(IWiredNode)
*/
default boolean remove()
{
return getNetwork().remove( this );
}
/**
* Mark this node's peripherals as having changed.
*
* This should only be used on the server thread. You should only call this on nodes
* that your network element owns.
*
* @param peripherals The new peripherals for this node.
* @see IWiredNetwork#updatePeripherals(IWiredNode, Map)
*/
default void updatePeripherals( @Nonnull Map<String, IPeripheral> peripherals )
{
getNetwork().updatePeripherals( this, peripherals );
}
}

View File

@@ -0,0 +1,25 @@
package dan200.computercraft.api.network.wired;
import dan200.computercraft.api.network.IPacketSender;
import javax.annotation.Nonnull;
/**
* An object on a {@link IWiredNetwork} capable of sending packets.
*
* Unlike a regular {@link IPacketSender}, this must be associated with the node you are attempting to
* to send the packet from.
*/
public interface IWiredSender extends IPacketSender
{
/**
* The node in the network representing this object.
*
* This should be used as a proxy for the main network. One should send packets
* and register receivers through this object.
*
* @return The node for this element.
*/
@Nonnull
IWiredNode getNode();
}

View File

@@ -0,0 +1,10 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
@API( owner="ComputerCraft", provides="ComputerCraft|API|Network|Wired", apiVersion="${version}" )
package dan200.computercraft.api.network.wired;
import net.minecraftforge.fml.common.API;

View File

@@ -13,6 +13,8 @@ import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Map;
/**
* The interface passed to peripherals by computers or turtles, providing methods
@@ -154,4 +156,33 @@ public interface IComputerAccess
*/
@Nonnull
String getAttachmentName();
/**
* Get a set of peripherals that this computer access can "see", along with their attachment name.
*
* This may include other peripherals on the wired network or peripherals on other sides of the computer.
*
* @return All reachable peripherals
* @see #getAttachmentName()
* @see #getAvailablePeripheral(String)
*/
@Nonnull
default Map<String, IPeripheral> getAvailablePeripherals()
{
return Collections.emptyMap();
}
/**
* Get a reachable peripheral with the given attachement name. This is a equivalent to
* {@link #getAvailablePeripherals()}{@code .get(name)}, though may be more performant.
*
* @param name The peripheral's attached name
* @return The reachable peripheral, or {@code null} if none can be found.
* @see #getAvailablePeripherals()
*/
@Nullable
default IPeripheral getAvailablePeripheral( @Nonnull String name )
{
return null;
}
}

View File

@@ -1,13 +1,15 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only.
* Copyright Daniel Ratcliffe, 2011-2018. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.peripheral;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -70,10 +72,46 @@ public interface IPeripheral
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @see #getMethodNames
* @deprecated Use {@link #callMethod(IComputerAccess, ICallContext, int, Object[])} instead.
*/
@Nullable
@Deprecated
Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
/**
* This is called when a lua program on an attached computer calls {@code peripheral.call()} with
* one of the methods exposed by {@link #getMethodNames()}.
*
* Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe
* when interacting with Minecraft objects.
*
* @param computer The interface to the computer that is making the call. Remember that multiple
* computers can be attached to a peripheral at once.
* @param context The context of the current call.
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
* wishes to call. The integer indicates the index into the getMethodNames() table
* that corresponds to the string passed into peripheral.call()
* @param arguments An array of objects, representing the arguments passed into {@code peripheral.call()}.<br>
* Lua values of type "string" will be represented by Object type String.<br>
* Lua values of type "number" will be represented by Object type Double.<br>
* Lua values of type "boolean" will be represented by Object type Boolean.<br>
* Lua values of type "table" will be represented by Object type Map.<br>
* Lua values of any other type will be represented by a null object.<br>
* This array will be empty if no arguments are passed.
* @return The result of calling this method. Use {@link MethodResult#empty()} to return nothing or
* {@link MethodResult#of(Object...)} to return several values.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
* @see #getMethodNames
*/
@Nonnull
@SuppressWarnings({ "deprecation" })
default MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return MethodResult.withLuaContext( lua -> callMethod( computer, lua, method, arguments ) );
}
/**
* Is called when canAttachToSide has returned true, and a computer is attaching to the peripheral.
*
@@ -114,6 +152,18 @@ public interface IPeripheral
{
}
/**
* Get the object that this peripheral provides methods for. This will generally be the tile entity
* or block, but may be an inventory, entity, etc...
*
* @return The object this peripheral targets
*/
@Nonnull
default Object getTarget()
{
return this;
}
/**
* Determine whether this peripheral is equivalent to another one.
*

View File

@@ -6,8 +6,10 @@
package dan200.computercraft.api.turtle;
import com.mojang.authlib.GameProfile;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.inventory.IInventory;
import net.minecraft.nbt.NBTTagCompound;
@@ -135,6 +137,14 @@ public interface ITurtleAccess
*/
int getColour();
/**
* Get the player who owns this turtle, namely whoever placed it.
*
* @return This turtle's owner.
*/
@Nonnull
GameProfile getOwningPlayer();
/**
* Get the inventory of this turtle
*
@@ -148,7 +158,7 @@ public interface ITurtleAccess
* Get the inventory of this turtle as an {@link IItemHandlerModifiable}.
*
* @return This turtle's inventory
* @see #getInventory()
* @see #getInventory()
* @see IItemHandlerModifiable
* @see net.minecraftforge.items.CapabilityItemHandler#ITEM_HANDLER_CAPABILITY
*/
@@ -229,10 +239,30 @@ public interface ITurtleAccess
* intercepted, or the computer will leak memory and end up in a broken state.
* @see ITurtleCommand
* @see ILuaContext#pullEvent(String)
* @deprecated Use {@link #executeCommand(ITurtleCommand)} instead.
*/
@Nonnull
@Deprecated
Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException;
/**
* Adds a custom command to the turtles command queue. Unlike peripheral methods, these custom commands will be
* executed on the main thread, so are guaranteed to be able to access Minecraft objects safely, and will be queued
* up with the turtles standard movement and tool commands.
*
* An issued command will return an unique integer, which will be supplied as a parameter to a "turtle_response"
* event issued to the turtle after the command has completed. Look at the Lua source code for "rom/apis/turtle" for
* how to build a Lua wrapper around this functionality.
*
* @param command An object which will execute the custom command when its point in the queue is reached
* @return The constructed method result. This evaluates to the result of the provided {@code command}.
* @throws UnsupportedOperationException When attempting to execute a command on the client side.
* @see ITurtleCommand
* @see MethodResult#pullEvent(String)
*/
@Nonnull
MethodResult executeCommand( @Nonnull ITurtleCommand command );
/**
* Start playing a specific animation. This will prevent other turtle commands from executing until
* it is finished.

View File

@@ -8,11 +8,15 @@ package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.turtle.event.TurtleAttackEvent;
import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.apache.commons.lang3.tuple.Pair;
@@ -100,6 +104,9 @@ public interface ITurtleUpgrade
* Will only be called for Tool turtle. Called when turtle.dig() or turtle.attack() is called
* by the turtle, and the tool is required to do some work.
*
* Conforming implementations should fire {@link BlockEvent.BreakEvent} and {@link TurtleBlockEvent.Dig}for digging,
* {@link AttackEntityEvent} and {@link TurtleAttackEvent} for attacking.
*
* @param turtle Access to the turtle that the tool resides on.
* @param side Which side of the turtle (left or right) the tool resides on.
* @param verb Which action (dig or attack) the turtle is being called on to perform.
@@ -126,7 +133,7 @@ public interface ITurtleUpgrade
* @return The model that you wish to be used to render your upgrade, and a transformation to apply to it. Returning
* a transformation of {@code null} has the same effect as the identify matrix.
*/
@SideOnly(Side.CLIENT)
@SideOnly( Side.CLIENT )
@Nonnull
Pair<IBakedModel, Matrix4f> getModel( @Nullable ITurtleAccess turtle, @Nonnull TurtleSide side );

View File

@@ -0,0 +1,84 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
/**
* A basic action that a turtle may perform, as accessed by the {@code turtle} API.
*
* @see TurtleActionEvent
*/
public enum TurtleAction
{
/**
* A turtle moves to a new position.
*
* @see TurtleBlockEvent.Move
*/
MOVE,
/**
* A turtle turns in a specific direction.
*/
TURN,
/**
* A turtle attempts to dig a block.
*
* @see TurtleBlockEvent.Dig
*/
DIG,
/**
* A turtle attempts to place a block or item in the world.
*
* @see TurtleBlockEvent.Place
*/
PLACE,
/**
* A turtle attempts to attack an entity.
*
* @see TurtleActionEvent
*/
ATTACK,
/**
* Drop an item into an inventory/the world.
*
* @see TurtleInventoryEvent.Drop
*/
DROP,
/**
* Suck an item from an inventory or the world.
*
* @see TurtleInventoryEvent.Suck
*/
SUCK,
/**
* Refuel the turtle's fuel levels.
*/
REFUEL,
/**
* Equip or unequip an item.
*/
EQUIP,
/**
* Inspect a block in world
*
* @see TurtleBlockEvent.Inspect
*/
INSPECT,
/**
* Gather metdata about an item in the turtle's inventory.
*/
INSPECT_ITEM,
}

View File

@@ -0,0 +1,76 @@
package dan200.computercraft.api.turtle.event;
import com.google.common.base.Preconditions;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.TurtleCommandResult;
import net.minecraftforge.fml.common.eventhandler.Cancelable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An event fired when a turtle is performing a known action.
*/
@Cancelable
public class TurtleActionEvent extends TurtleEvent
{
private final TurtleAction action;
private String failureMessage;
public TurtleActionEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action )
{
super( turtle );
Preconditions.checkNotNull( action, "action cannot be null" );
this.action = action;
}
public TurtleAction getAction()
{
return action;
}
/**
* Sets the cancellation state of this action.
*
* If {@code cancel} is {@code true}, this action will not be carried out.
*
* @param cancel The new canceled value.
* @see TurtleCommandResult#failure()
* @deprecated Use {@link #setCanceled(boolean, String)} instead.
*/
@Override
@Deprecated
public void setCanceled( boolean cancel )
{
setCanceled( cancel, null );
}
/**
* Set the cancellation state of this action, setting a failure message if required.
*
* If {@code cancel} is {@code true}, this action will not be carried out.
*
* @param cancel The new canceled value.
* @param failureMessage The message to return to the user explaining the failure.
* @see TurtleCommandResult#failure(String)
*/
public void setCanceled( boolean cancel, @Nullable String failureMessage )
{
super.setCanceled( cancel );
this.failureMessage = cancel ? failureMessage : null;
}
/**
* Get the message with which this will fail.
*
* @return The failure message.
* @see TurtleCommandResult#failure()
* @see #setCanceled(boolean, String)
*/
@Nullable
public String getFailureMessage()
{
return failureMessage;
}
}

View File

@@ -0,0 +1,80 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
import com.google.common.base.Preconditions;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleVerb;
import net.minecraft.entity.Entity;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import javax.annotation.Nonnull;
/**
* Fired when a turtle attempts to attack an entity.
*
* This must be fired by {@link ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, EnumFacing)},
* as the base {@code turtle.attack()} command does not fire it.
*
* Note that such commands should also fire {@link AttackEntityEvent}, so you do not need to listen to both.
*
* @see TurtleAction#ATTACK
*/
public class TurtleAttackEvent extends TurtlePlayerEvent
{
private final Entity target;
private final ITurtleUpgrade upgrade;
private final TurtleSide side;
public TurtleAttackEvent( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull Entity target, @Nonnull ITurtleUpgrade upgrade, @Nonnull TurtleSide side )
{
super( turtle, TurtleAction.ATTACK, player );
Preconditions.checkNotNull( target, "target cannot be null" );
Preconditions.checkNotNull( upgrade, "upgrade cannot be null" );
Preconditions.checkNotNull( side, "side cannot be null" );
this.target = target;
this.upgrade = upgrade;
this.side = side;
}
/**
* Get the entity being attacked by this turtle.
*
* @return The entity being attacked.
*/
@Nonnull
public Entity getTarget()
{
return target;
}
/**
* Get the upgrade responsible for attacking.
*
* @return The upgrade responsible for attacking.
*/
@Nonnull
public ITurtleUpgrade getUpgrade()
{
return upgrade;
}
/**
* Get the side the attacking upgrade is on.
*
* @return The upgrade's side.
*/
@Nonnull
public TurtleSide getSide()
{
return side;
}
}

View File

@@ -0,0 +1,241 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
import com.google.common.base.Preconditions;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleVerb;
import net.minecraft.block.state.IBlockState;
import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.fml.common.eventhandler.Cancelable;
import javax.annotation.Nonnull;
import java.util.Map;
/**
* A general event for when a turtle interacts with a block or region.
*
* You should generally listen to one of the sub-events instead, cancelling them where
* appropriate.
*
* Note that you are not guaranteed to receive this event, if it has been cancelled by other
* mechanisms, such as block protection systems.
*
* Be aware that some events (such as {@link TurtleInventoryEvent}) do not necessarily interact
* with a block, simply objects within that block space.
*/
@Cancelable
public abstract class TurtleBlockEvent extends TurtlePlayerEvent
{
private final World world;
private final BlockPos pos;
protected TurtleBlockEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos )
{
super( turtle, action, player );
Preconditions.checkNotNull( world, "world cannot be null" );
Preconditions.checkNotNull( pos, "pos cannot be null" );
this.world = world;
this.pos = pos;
}
/**
* Get the world the turtle is interacting in.
*
* @return The world the turtle is interacting in.
*/
public World getWorld()
{
return world;
}
/**
* Get the position the turtle is interacting with. Note that this is different
* to {@link ITurtleAccess#getPosition()}.
*
* @return The position the turtle is interacting with.
*/
public BlockPos getPos()
{
return pos;
}
/**
* Fired when a turtle attempts to dig a block.
*
* This must be fired by {@link ITurtleUpgrade#useTool(ITurtleAccess, TurtleSide, TurtleVerb, EnumFacing)},
* as the base {@code turtle.dig()} command does not fire it.
*
* Note that such commands should also fire {@link BlockEvent.BreakEvent}, so you do not need to listen to both.
*
* @see TurtleAction#DIG
*/
@Cancelable
public static class Dig extends TurtleBlockEvent
{
private final IBlockState block;
private final ITurtleUpgrade upgrade;
private final TurtleSide side;
public Dig( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState block, @Nonnull ITurtleUpgrade upgrade, @Nonnull TurtleSide side )
{
super( turtle, TurtleAction.DIG, player, world, pos );
Preconditions.checkNotNull( block, "block cannot be null" );
Preconditions.checkNotNull( upgrade, "upgrade cannot be null" );
Preconditions.checkNotNull( side, "side cannot be null" );
this.block = block;
this.upgrade = upgrade;
this.side = side;
}
/**
* Get the block which is about to be broken.
*
* @return The block which is going to be broken.
*/
@Nonnull
public IBlockState getBlock()
{
return block;
}
/**
* Get the upgrade doing the digging
*
* @return The upgrade doing the digging.
*/
@Nonnull
public ITurtleUpgrade getUpgrade()
{
return upgrade;
}
/**
* Get the side the upgrade doing the digging is on.
*
* @return The upgrade's side.
*/
@Nonnull
public TurtleSide getSide()
{
return side;
}
}
/**
* Fired when a turtle attempts to move into a block.
*
* @see TurtleAction#MOVE
*/
@Cancelable
public static class Move extends TurtleBlockEvent
{
public Move( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos )
{
super( turtle, TurtleAction.MOVE, player, world, pos );
}
}
/**
* Fired when a turtle attempts to place a block in the world.
*
* @see TurtleAction#PLACE
*/
@Cancelable
public static class Place extends TurtleBlockEvent
{
private final ItemStack stack;
public Place( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull ItemStack stack )
{
super( turtle, TurtleAction.PLACE, player, world, pos );
Preconditions.checkNotNull( stack, "stack cannot be null" );
this.stack = stack;
}
/**
* Get the item stack that will be placed. This should not be modified.
*
* @return The item stack to be placed.
*/
@Nonnull
public ItemStack getStack()
{
return stack;
}
}
/**
* Fired when a turtle gathers data on a block in world.
*
* You may prevent blocks being inspected, or add additional information to the result.
*
* @see TurtleAction#INSPECT
*/
@Cancelable
public static class Inspect extends TurtleBlockEvent
{
private final IBlockState state;
private final Map<String, Object> data;
public Inspect( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull IBlockState state, @Nonnull Map<String, Object> data )
{
super( turtle, TurtleAction.INSPECT, player, world, pos );
Preconditions.checkNotNull( state, "state cannot be null" );
Preconditions.checkNotNull( data, "data cannot be null" );
this.data = data;
this.state = state;
}
/**
* Get the block state which is being inspected.
*
* @return The inspected block state.
*/
@Nonnull
public IBlockState getState()
{
return state;
}
/**
* Get the "inspection data" from this block, which will be returned to the user.
*
* @return This block's inspection data.
*/
@Nonnull
public Map<String, Object> getData()
{
return data;
}
/**
* Add new information to the inspection result. Note this will override fields with the same name.
*
* @param newData The data to add. Note all values should be convertable to Lua (see
* {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
*/
public void addData( @Nonnull Map<String, ?> newData )
{
Preconditions.checkNotNull( newData, "newData cannot be null" );
data.putAll( newData );
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
import com.google.common.base.Preconditions;
import dan200.computercraft.api.turtle.ITurtleAccess;
import net.minecraftforge.fml.common.eventhandler.Event;
import javax.annotation.Nonnull;
/**
* A base class for all events concerning a turtle. This will only ever constructed and fired on the server side,
* so sever specific methods on {@link ITurtleAccess} are safe to use.
*
* You should generally not need to subscribe to this event, preferring one of the more specific classes.
*
* @see TurtleActionEvent
*/
public abstract class TurtleEvent extends Event
{
private final ITurtleAccess turtle;
protected TurtleEvent( @Nonnull ITurtleAccess turtle )
{
Preconditions.checkNotNull( turtle, "turtle cannot be null" );
this.turtle = turtle;
}
/**
* Get the turtle which is performing this action.
*
* @return The access for this turtle.
*/
@Nonnull
public ITurtleAccess getTurtle()
{
return turtle;
}
}

View File

@@ -0,0 +1,84 @@
package dan200.computercraft.api.turtle.event;
import com.google.common.base.Preconditions;
import dan200.computercraft.api.turtle.ITurtleAccess;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.util.FakePlayer;
import net.minecraftforge.fml.common.eventhandler.Cancelable;
import net.minecraftforge.items.IItemHandler;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Fired when a turtle attempts to interact with an inventory.
*/
@Cancelable
public abstract class TurtleInventoryEvent extends TurtleBlockEvent
{
private final IItemHandler handler;
protected TurtleInventoryEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler )
{
super( turtle, action, player, world, pos );
this.handler = handler;
}
/**
* Get the inventory being interacted with
*
* @return The inventory being interacted with, {@code null} if the item will be dropped to/sucked from the world.
*/
@Nullable
public IItemHandler getItemHandler()
{
return handler;
}
/**
* Fired when a turtle attempts to suck from an inventory.
*
* @see TurtleAction#SUCK
*/
@Cancelable
public static class Suck extends TurtleInventoryEvent
{
public Suck( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler )
{
super( turtle, TurtleAction.SUCK, player, world, pos, handler );
}
}
/**
* Fired when a turtle attempts to drop an item into an inventory.
*
* @see TurtleAction#DROP
*/
@Cancelable
public static class Drop extends TurtleInventoryEvent
{
private final ItemStack stack;
public Drop( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nullable IItemHandler handler, @Nonnull ItemStack stack )
{
super( turtle, TurtleAction.DROP, player, world, pos, handler );
Preconditions.checkNotNull( stack, "stack cannot be null" );
this.stack = stack;
}
/**
* The item which will be inserted into the inventory/dropped on the ground.
*
* Note that this is a copy of the original stack, and so should not be modified, as that will have no effect.
*
* @return The item stack which will be dropped.
*/
public ItemStack getStack()
{
return stack.copy();
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.turtle.event;
import com.google.common.base.Preconditions;
import dan200.computercraft.api.turtle.ITurtleAccess;
import net.minecraftforge.common.util.FakePlayer;
import javax.annotation.Nonnull;
/**
* An action done by a turtle which is normally done by a player.
*
* {@link #getPlayer()} may be used to modify the player's attributes or perform permission checks.
*/
public abstract class TurtlePlayerEvent extends TurtleActionEvent
{
private final FakePlayer player;
protected TurtlePlayerEvent( @Nonnull ITurtleAccess turtle, @Nonnull TurtleAction action, @Nonnull FakePlayer player )
{
super( turtle, action );
Preconditions.checkNotNull( player, "player cannot be null" );
this.player = player;
}
/**
* A fake player, representing this turtle.
*
* This may be used for triggering permission checks.
*
* @return A {@link FakePlayer} representing this turtle.
*/
@Nonnull
public FakePlayer getPlayer()
{
return player;
}
}

View File

@@ -0,0 +1,10 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
@API(owner = "ComputerCraft", provides = "ComputerCraft|API|Turtle|Event", apiVersion = "${version}")
package dan200.computercraft.api.turtle.event;
import net.minecraftforge.fml.common.API;

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

@@ -20,6 +20,7 @@ import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ModelBakery;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.client.renderer.color.IItemColor;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.SimpleReloadableResourceManager;
@@ -115,8 +116,23 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
MinecraftForge.EVENT_BUS.register( handlers );
}
public class ForgeHandlers
public static class ForgeHandlers
{
private static final String[] TURTLE_UPGRADES = {
"turtle_modem_off_left",
"turtle_modem_on_left",
"turtle_modem_off_right",
"turtle_modem_on_right",
"turtle_crafting_table_left",
"turtle_crafting_table_right",
"advanced_turtle_modem_off_left",
"advanced_turtle_modem_on_left",
"advanced_turtle_modem_off_right",
"advanced_turtle_modem_on_right",
"turtle_speaker_upgrade_left",
"turtle_speaker_upgrade_right",
};
private TurtleSmartItemModel m_turtleSmartItemModel;
public ForgeHandlers()
@@ -142,24 +158,27 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
@SubscribeEvent
public void onTextureStitchEvent( TextureStitchEvent.Pre event )
{
event.getMap().registerSprite( new ResourceLocation( "computercraft", "blocks/crafty_upgrade" ) );
// Load all textures for upgrades
TextureMap map = event.getMap();
for( String upgrade : TURTLE_UPGRADES )
{
IModel model = ModelLoaderRegistry.getModelOrMissing( new ResourceLocation( "computercraft", "block/" + upgrade ) );
for( ResourceLocation texture : model.getTextures() )
{
map.registerSprite( texture );
}
}
}
@SubscribeEvent
public void onModelBakeEvent( ModelBakeEvent event )
{
loadModel( event, "turtle_modem_off_left" );
loadModel( event, "turtle_modem_on_left" );
loadModel( event, "turtle_modem_off_right" );
loadModel( event, "turtle_modem_on_right" );
loadModel( event, "turtle_crafting_table_left" );
loadModel( event, "turtle_crafting_table_right" );
loadModel( event, "advanced_turtle_modem_off_left" );
loadModel( event, "advanced_turtle_modem_on_left" );
loadModel( event, "advanced_turtle_modem_off_right" );
loadModel( event, "advanced_turtle_modem_on_right" );
loadModel( event, "turtle_speaker_upgrade_left" );
loadModel( event, "turtle_speaker_upgrade_right" );
// Load all upgrade models
for( String upgrade : TURTLE_UPGRADES )
{
loadModel( event, upgrade );
}
loadSmartModel( event, "turtle_dynamic", m_turtleSmartItemModel );
}
@@ -191,7 +210,7 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
private static class TurtleItemColour implements IItemColor
{
@Override
public int getColorFromItemstack( @Nonnull ItemStack stack, int tintIndex )
public int colorMultiplier( @Nonnull ItemStack stack, int tintIndex )
{
if( tintIndex == 0 )
{

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;
@@ -25,6 +26,7 @@ import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.network.ComputerCraftPacket;
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
import dan200.computercraft.shared.peripheral.modem.TileCable;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.peripheral.printer.TilePrinter;
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
@@ -33,9 +35,10 @@ import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.entity.TurtleVisionCamera;
import dan200.computercraft.shared.util.Colour;
import gnu.trove.map.hash.TIntIntHashMap;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.gui.GuiNewChat;
import net.minecraft.client.renderer.ItemMeshDefinition;
import net.minecraft.client.renderer.block.model.ModelBakery;
import net.minecraft.client.renderer.block.model.ModelResourceLocation;
@@ -50,6 +53,7 @@ import net.minecraft.util.IThreadListener;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.World;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
@@ -57,6 +61,7 @@ import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.client.event.RenderPlayerEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
@@ -70,6 +75,8 @@ import java.util.List;
public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
{
private static TIntIntHashMap lastCounts = new TIntIntHashMap();
private long m_tick;
private long m_renderFrame;
private FixedWidthFontRenderer m_fixedWidthFontRenderer;
@@ -115,6 +122,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
registerItemModel( ComputerCraft.Blocks.commandComputer, "command_computer" );
registerItemModel( ComputerCraft.Blocks.advancedModem, "advanced_modem" );
registerItemModel( ComputerCraft.Blocks.peripheral, 5, "speaker" );
registerItemModel( ComputerCraft.Blocks.wiredModemFull, "wired_modem_full" );
registerItemModel( ComputerCraft.Items.disk, "disk" );
registerItemModel( ComputerCraft.Items.diskExpanded, "disk_expanded" );
@@ -296,12 +304,6 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
return m_renderFrame;
}
@Override
public void deleteDisplayLists( int list, int range )
{
GlStateManager.glDeleteLists( list, range );
}
@Override
public Object getFixedWidthFontRenderer()
{
@@ -388,6 +390,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
case ComputerCraftPacket.ComputerTerminalChanged:
case ComputerCraftPacket.ComputerDeleted:
case ComputerCraftPacket.PlayRecord:
case ComputerCraftPacket.PostChat:
{
// Packet from Server to Client
IThreadListener listener = Minecraft.getMinecraft();
@@ -454,6 +457,36 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
{
mc.world.playRecord( pos, null );
}
break;
}
case ComputerCraftPacket.PostChat:
{
/*
This allows us to send delete chat messages of the same "category" as the previous one.
It's used by the various /computercraft commands to avoid filling the chat with repetitive
messages.
*/
int id = packet.m_dataInt[0];
ITextComponent[] components = new ITextComponent[packet.m_dataString.length];
for( int i = 0; i < packet.m_dataString.length; i++ )
{
components[i] = ITextComponent.Serializer.jsonToComponent( packet.m_dataString[i] );
}
GuiNewChat chat = Minecraft.getMinecraft().ingameGUI.getChatGUI();
// Keep track of how many lines we wrote last time, deleting any extra ones.
int lastCount = lastCounts.get( id );
for( int i = components.length; i < lastCount; i++ ) chat.deleteChatLine( i + id );
lastCounts.put( id, components.length );
// Add new lines
for( int i = 0; i < components.length; i++ )
{
chat.printChatMessageWithOptionalDeletion( components[i], id + i );
}
break;
}
}
@@ -465,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
@@ -551,6 +585,15 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
m_renderFrame++;
}
}
@SubscribeEvent
public void onWorldUnload( WorldEvent.Unload event )
{
if( event.getWorld().isRemote )
{
ClientMonitor.destroyAll();
}
}
}
@SideOnly(Side.CLIENT)
@@ -564,7 +607,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
}
@Override
public int getColorFromItemstack( @Nonnull ItemStack stack, int layer )
public int colorMultiplier( @Nonnull ItemStack stack, int layer )
{
return layer == 0 ? 0xFFFFFF : disk.getColour( stack );
}

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

@@ -189,7 +189,7 @@ public final class ModelTransformer
private BakedQuadBuilder( VertexFormat format )
{
this.format = format;
this.vertexData = new int[ format.getNextOffset() ];
this.vertexData = new int[ format.getSize() ];
}
@Nonnull

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

@@ -54,8 +54,6 @@ public class RenderOverlayCable
GlStateManager.depthMask( false );
GlStateManager.pushMatrix();
EnumFacing direction = type != PeripheralType.Cable ? cable.getDirection() : null;
{
EntityPlayer player = event.getPlayer();
double x = player.lastTickPosX + (player.posX - player.lastTickPosX) * event.getPartialTicks();
@@ -78,7 +76,7 @@ public class RenderOverlayCable
for( EnumFacing facing : EnumFacing.VALUES )
{
if( direction == facing || BlockCable.isCable( world, pos.offset( facing ) ) )
if( BlockCable.doesConnectVisually( state, world, pos, facing ) )
{
flags |= 1 << facing.ordinal();

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

@@ -10,7 +10,7 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.common.ClientTerminal;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.DirectionUtil;
@@ -18,6 +18,7 @@ import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
@@ -43,25 +44,24 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
private void renderMonitorAt( TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
{
// Render from the origin monitor
TileMonitor origin = monitor.getOrigin();
if( origin == null )
{
return;
}
ClientMonitor originTerminal = monitor.getClientMonitor();
// Ensure each monitor is rendered only once
long renderFrame = ComputerCraft.getRenderFrame();
if( origin.m_lastRenderFrame == renderFrame )
{
return;
}
else
{
origin.m_lastRenderFrame = renderFrame;
}
boolean redraw = origin.pollChanged();
if( originTerminal == null ) return;
TileMonitor origin = originTerminal.getOrigin();
BlockPos monitorPos = monitor.getPos();
// Ensure each monitor terminal is rendered only once. We allow rendering a specific tile
// multiple times in a single frame to ensure compatibility with shaders which may run a
// pass multiple times.
long renderFrame = ComputerCraft.getRenderFrame();
if( originTerminal.lastRenderFrame == renderFrame && !monitorPos.equals( originTerminal.lastRenderPos ) )
{
return;
}
originTerminal.lastRenderFrame = renderFrame;
originTerminal.lastRenderPos = monitorPos;
BlockPos originPos = origin.getPos();
posX += originPos.getX() - monitorPos.getX();
posY += originPos.getY() - monitorPos.getY();
@@ -94,34 +94,34 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
BufferBuilder renderer = tessellator.getBuffer();
// Get terminal
ClientTerminal clientTerminal = (ClientTerminal)origin.getTerminal();
Terminal terminal = (clientTerminal != null) ? clientTerminal.getTerminal() : null;
redraw = redraw || (clientTerminal != null && clientTerminal.hasTerminalChanged());
boolean redraw = originTerminal.pollTerminalChanged();
// Draw the contents
GlStateManager.depthMask( false );
OpenGlHelper.setLightmapTextureCoords( OpenGlHelper.lightmapTexUnit, 0xFF, 0xFF );
GlStateManager.disableLighting();
mc.entityRenderer.disableLightmap();
try
{
Terminal terminal = originTerminal.getTerminal();
if( terminal != null )
{
Palette palette = terminal.getPalette();
// Allocate display lists
if( origin.m_renderDisplayList < 0 )
if( originTerminal.renderDisplayLists == null )
{
origin.m_renderDisplayList = GlStateManager.glGenLists( 3 );
originTerminal.createLists();
redraw = true;
}
// Draw a terminal
boolean greyscale = !clientTerminal.isColour();
boolean greyscale = !originTerminal.isColour();
int width = terminal.getWidth();
int height = terminal.getHeight();
int cursorX = terminal.getCursorX();
int cursorY = terminal.getCursorY();
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer)ComputerCraft.getFixedWidthFontRenderer();
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer) ComputerCraft.getFixedWidthFontRenderer();
GlStateManager.pushMatrix();
try
@@ -135,7 +135,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( redraw )
{
// Build background display list
GlStateManager.glNewList( origin.m_renderDisplayList, GL11.GL_COMPILE );
GlStateManager.glNewList( originTerminal.renderDisplayLists[0], GL11.GL_COMPILE );
try
{
double marginXSize = TileMonitor.RENDER_MARGIN / xScale;
@@ -149,7 +149,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
GlStateManager.scale( 1.0, marginSquash, 1.0 );
GlStateManager.translate( 0.0, -marginYSize / marginSquash, 0.0 );
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( 0 ), marginXSize, marginXSize, greyscale, palette );
GlStateManager.translate( 0.0, ( marginYSize + height * FixedWidthFontRenderer.FONT_HEIGHT ) / marginSquash, 0.0 );
GlStateManager.translate( 0.0, (marginYSize + height * FixedWidthFontRenderer.FONT_HEIGHT) / marginSquash, 0.0 );
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( height - 1 ), marginXSize, marginXSize, greyscale, palette );
}
finally
@@ -174,7 +174,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
GlStateManager.glEndList();
}
}
GlStateManager.callList( origin.m_renderDisplayList );
GlStateManager.callList( originTerminal.renderDisplayLists[0] );
GlStateManager.resetColor();
// Draw text
@@ -182,7 +182,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( redraw )
{
// Build text display list
GlStateManager.glNewList( origin.m_renderDisplayList + 1, GL11.GL_COMPILE );
GlStateManager.glNewList( originTerminal.renderDisplayLists[1], GL11.GL_COMPILE );
try
{
// Lines
@@ -202,7 +202,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
GlStateManager.glEndList();
}
}
GlStateManager.callList( origin.m_renderDisplayList + 1 );
GlStateManager.callList( originTerminal.renderDisplayLists[1] );
GlStateManager.resetColor();
// Draw cursor
@@ -210,7 +210,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( redraw )
{
// Build cursor display list
GlStateManager.glNewList( origin.m_renderDisplayList + 2, GL11.GL_COMPILE );
GlStateManager.glNewList( originTerminal.renderDisplayLists[2], GL11.GL_COMPILE );
try
{
// Cursor
@@ -236,7 +236,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
}
if( ComputerCraft.getGlobalCursorBlink() )
{
GlStateManager.callList( origin.m_renderDisplayList + 2 );
GlStateManager.callList( originTerminal.renderDisplayLists[2] );
GlStateManager.resetColor();
}
}

View File

@@ -167,7 +167,7 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
ModelResourceLocation baseModelLocation = TileEntityTurtleRenderer.getTurtleModel( combo.m_family, combo.m_colour );
ModelResourceLocation overlayModelLocation = TileEntityTurtleRenderer.getTurtleOverlayModel( combo.m_family, combo.m_overlay, combo.m_christmas );
IBakedModel baseModel = modelManager.getModel( baseModelLocation );
IBakedModel overlayModel = (overlayModelLocation != null) ? modelManager.getModel( baseModelLocation ) : null;
IBakedModel overlayModel = (overlayModelLocation != null) ? modelManager.getModel( overlayModelLocation ) : null;
Matrix4f transform = combo.m_flip ? s_flip : s_identity;
Pair<IBakedModel, Matrix4f> leftModel = (combo.m_leftUpgrade != null) ? combo.m_leftUpgrade.getModel( null, TurtleSide.Left ) : null;
Pair<IBakedModel, Matrix4f> rightModel = (combo.m_rightUpgrade != null) ? combo.m_rightUpgrade.getModel( null, TurtleSide.Right ) : null;

View File

@@ -6,13 +6,12 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.terminal.TextBuffer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.core.apis.ArgumentHelper.getString;
import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
@@ -41,27 +40,28 @@ public class BufferAPI implements ILuaAPI
};
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
switch( method )
{
case 0:
{
// len
return new Object[] { m_buffer.length() };
return MethodResult.of( m_buffer.length() );
}
case 1:
{
// tostring
return new Object[] { m_buffer.toString() };
return MethodResult.of( m_buffer.toString() );
}
case 2:
{
// read
int start = optInt( arguments, 0, 0 );
int end = optInt( arguments, 1, m_buffer.length() );
return new Object[] { m_buffer.read( start, end ) };
return MethodResult.of( m_buffer.read( start, end ) );
}
case 3:
{
@@ -70,7 +70,7 @@ public class BufferAPI implements ILuaAPI
int start = optInt( arguments, 1, 0 );
int end = optInt( arguments, 2, start + text.length() );
m_buffer.write( text, start, end );
return null;
return MethodResult.empty();
}
case 4:
{
@@ -79,14 +79,22 @@ public class BufferAPI implements ILuaAPI
int start = optInt( arguments, 1, 0 );
int end = optInt( arguments, 2, m_buffer.length() );
m_buffer.fill( text, start, end );
return null;
return MethodResult.empty();
}
default:
{
return null;
return MethodResult.empty();
}
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
}
public BufferAPI( IAPIEnvironment _env )
@@ -110,8 +118,9 @@ public class BufferAPI implements ILuaAPI
};
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
switch( method )
{
@@ -124,12 +133,20 @@ public class BufferAPI implements ILuaAPI
throw ArgumentHelper.badArgument( 1, "positive number", Integer.toString( repetitions ) );
}
TextBuffer buffer = new TextBuffer( text, repetitions );
return new Object[] { new BufferLuaObject( buffer ) };
return MethodResult.of( new BufferLuaObject( buffer ) );
}
default:
{
return null;
return MethodResult.empty();
}
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
}

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

@@ -6,17 +6,18 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*;
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.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
@@ -79,8 +80,9 @@ public class FSAPI implements ILuaAPI
};
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
@@ -88,13 +90,14 @@ public class FSAPI implements ILuaAPI
{
// list
String path = getString( args, 0 );
m_env.addTrackingChange( TrackingField.FS_OPS );
try {
String[] results = m_fileSystem.list( path );
Map<Object,Object> table = new HashMap<>();
for(int i=0; i<results.length; ++i ) {
table.put( i+1, results[i] );
}
return new Object[] { table };
return MethodResult.of( table );
}
catch( FileSystemException e )
{
@@ -106,13 +109,13 @@ public class FSAPI implements ILuaAPI
// combine
String pathA = getString( args, 0 );
String pathB = getString( args, 1 );
return new Object[] { m_fileSystem.combine( pathA, pathB ) };
return MethodResult.of( m_fileSystem.combine( pathA, pathB ) );
}
case 2:
{
// getName
String path = getString( args, 0 );
return new Object[]{ FileSystem.getName( path ) };
return MethodResult.of( FileSystem.getName( path ) );
}
case 3:
{
@@ -120,7 +123,7 @@ public class FSAPI implements ILuaAPI
String path = getString( args, 0 );
try
{
return new Object[]{ m_fileSystem.getSize( path ) };
return MethodResult.of( m_fileSystem.getSize( path ) );
}
catch( FileSystemException e )
{
@@ -132,9 +135,9 @@ public class FSAPI implements ILuaAPI
// exists
String path = getString( args, 0 );
try {
return new Object[]{ m_fileSystem.exists( path ) };
return MethodResult.of( m_fileSystem.exists( path ) );
} catch( FileSystemException e ) {
return new Object[]{ false };
return MethodResult.of( false );
}
}
case 5:
@@ -142,9 +145,9 @@ public class FSAPI implements ILuaAPI
// isDir
String path = getString( args, 0 );
try {
return new Object[]{ m_fileSystem.isDir( path ) };
return MethodResult.of( m_fileSystem.isDir( path ) );
} catch( FileSystemException e ) {
return new Object[]{ false };
return MethodResult.of( false );
}
}
case 6:
@@ -152,9 +155,9 @@ public class FSAPI implements ILuaAPI
// isReadOnly
String path = getString( args, 0 );
try {
return new Object[]{ m_fileSystem.isReadOnly( path ) };
return MethodResult.of( m_fileSystem.isReadOnly( path ) );
} catch( FileSystemException e ) {
return new Object[]{ false };
return MethodResult.of( false );
}
}
case 7:
@@ -162,8 +165,9 @@ public class FSAPI implements ILuaAPI
// makeDir
String path = getString( args, 0 );
try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.makeDir( path );
return null;
return MethodResult.empty();
} catch( FileSystemException e ) {
throw new LuaException( e.getMessage() );
}
@@ -174,8 +178,9 @@ public class FSAPI implements ILuaAPI
String path = getString( args, 0 );
String dest = getString( args, 1 );
try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.move( path, dest );
return null;
return MethodResult.empty();
} catch( FileSystemException e ) {
throw new LuaException( e.getMessage() );
}
@@ -186,8 +191,9 @@ public class FSAPI implements ILuaAPI
String path = getString( args, 0 );
String dest = getString( args, 1 );
try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.copy( path, dest );
return null;
return MethodResult.empty();
} catch( FileSystemException e ) {
throw new LuaException( e.getMessage() );
}
@@ -197,8 +203,9 @@ public class FSAPI implements ILuaAPI
// delete
String path = getString( args, 0 );
try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.delete( path );
return null;
return MethodResult.empty();
} catch( FileSystemException e ) {
throw new LuaException( e.getMessage() );
}
@@ -208,6 +215,7 @@ public class FSAPI implements ILuaAPI
// open
String path = getString( args, 0 );
String mode = getString( args, 1 );
m_env.addTrackingChange( TrackingField.FS_OPS );
try {
switch( mode )
{
@@ -215,43 +223,43 @@ public class FSAPI implements ILuaAPI
{
// Open the file for reading, then create a wrapper around the reader
InputStream reader = m_fileSystem.openForRead( path );
return new Object[] { new EncodedInputHandle( reader ) };
return MethodResult.of( new EncodedInputHandle( 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 ) };
return MethodResult.of( new EncodedOutputHandle( 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 ) };
return MethodResult.of( new EncodedOutputHandle( 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 ) };
return MethodResult.of( new BinaryInputHandle( 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 ) };
return MethodResult.of( new BinaryOutputHandle( 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 ) };
return MethodResult.of( new BinaryOutputHandle( writer ) );
}
default:
throw new LuaException( "Unsupported mode" );
}
} catch( FileSystemException e ) {
return new Object[] { null, e.getMessage() };
return MethodResult.of( null, e.getMessage() );
}
}
case 12:
@@ -261,9 +269,9 @@ public class FSAPI implements ILuaAPI
try {
if( !m_fileSystem.exists( path ) )
{
return null;
return MethodResult.empty();
}
return new Object[]{ m_fileSystem.getMountLabel( path ) };
return MethodResult.of( m_fileSystem.getMountLabel( path ) );
} catch( FileSystemException e ) {
throw new LuaException( e.getMessage() );
}
@@ -276,9 +284,9 @@ public class FSAPI implements ILuaAPI
long freeSpace = m_fileSystem.getFreeSpace( path );
if( freeSpace >= 0 )
{
return new Object[]{ freeSpace };
return MethodResult.of( freeSpace );
}
return new Object[]{ "unlimited" };
return MethodResult.of( "unlimited" );
} catch( FileSystemException e ) {
throw new LuaException( e.getMessage() );
}
@@ -288,12 +296,13 @@ public class FSAPI implements ILuaAPI
// find
String path = getString( args, 0 );
try {
m_env.addTrackingChange( TrackingField.FS_OPS );
String[] results = m_fileSystem.find( path );
Map<Object,Object> table = new HashMap<>();
for(int i=0; i<results.length; ++i ) {
table.put( i+1, results[i] );
}
return new Object[] { table };
return MethodResult.of( table );
} catch( FileSystemException e ) {
throw new LuaException( e.getMessage() );
}
@@ -302,13 +311,21 @@ public class FSAPI implements ILuaAPI
{
// getDir
String path = getString( args, 0 );
return new Object[]{ FileSystem.getDirectory( path ) };
return MethodResult.of( FileSystem.getDirectory( path ) );
}
default:
{
assert( false );
return null;
return MethodResult.empty();
}
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
}

View File

@@ -6,13 +6,14 @@
package dan200.computercraft.core.apis;
import com.google.common.collect.ImmutableSet;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.http.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
@@ -21,9 +22,14 @@ import java.util.*;
import java.util.concurrent.Future;
import static dan200.computercraft.core.apis.ArgumentHelper.*;
import static dan200.computercraft.core.apis.TableHelper.*;
public class HTTPAPI implements ILuaAPI
{
private static final Set<String> HTTP_METHODS = ImmutableSet.of(
"GET", "POST", "HEAD", "OPTIONS", "PUT", "DELETE"
);
private final IAPIEnvironment m_apiEnvironment;
private final List<Future<?>> m_httpTasks;
private final Set<Closeable> m_closeables;
@@ -38,7 +44,7 @@ public class HTTPAPI implements ILuaAPI
@Override
public String[] getNames()
{
return new String[] {
return new String[]{
"http"
};
}
@@ -59,7 +65,7 @@ public class HTTPAPI implements ILuaAPI
}
@Override
public void shutdown( )
public void shutdown()
{
synchronized( m_httpTasks )
{
@@ -89,64 +95,81 @@ public class HTTPAPI implements ILuaAPI
@Override
public String[] getMethodNames()
{
return new String[] {
return new String[]{
"request",
"checkURL",
"websocket",
};
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
case 0:
case 0: // request
{
// request
// Get URL
String urlString = getString( args, 0 );
String urlString, postString, requestMethod;
Map<Object, Object> headerTable;
boolean binary, redirect;
// Get POST
String postString = optString( args, 1, null );
// Get Headers
Map<String, String> headers = null;
Map<Object, Object> table = optTable( args, 2, null );
if( table != null )
if( args.length >= 1 && args[0] instanceof Map )
{
headers = new HashMap<>( table.size() );
for( Object key : table.keySet() )
Map<?, ?> options = (Map) args[0];
urlString = getStringField( options, "url" );
postString = optStringField( options, "body", null );
headerTable = optTableField( options, "headers", null );
binary = optBooleanField( options, "binary", false );
requestMethod = optStringField( options, "method", null );
redirect = optBooleanField( options, "redirect", true );
}
else
{
// Get URL and post information
urlString = getString( args, 0 );
postString = optString( args, 1, null );
headerTable = optTable( args, 2, null );
binary = optBoolean( args, 3, false );
requestMethod = null;
redirect = true;
}
Map<String, String> headers = null;
if( headerTable != null )
{
headers = new HashMap<>( headerTable.size() );
for( Object key : headerTable.keySet() )
{
Object value = table.get( key );
Object value = headerTable.get( key );
if( key instanceof String && value instanceof String )
{
headers.put( (String)key, (String)value );
headers.put( (String) key, (String) value );
}
}
}
// Get binary
boolean binary = false;
if( args.length >= 4 )
if( requestMethod != null && !HTTP_METHODS.contains( requestMethod ) )
{
binary = args[ 3 ] != null && !args[ 3 ].equals( Boolean.FALSE );
throw new LuaException( "Unsupported HTTP method" );
}
// Make the request
try
{
URL url = HTTPRequest.checkURL( urlString );
HTTPRequest request = new HTTPRequest( m_apiEnvironment, urlString, url, postString, headers, binary );
HTTPRequest request = new HTTPRequest( m_apiEnvironment, urlString, url, postString, headers, binary, requestMethod, redirect );
synchronized( m_httpTasks )
{
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) );
}
return new Object[] { true };
return MethodResult.of(true);
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
return MethodResult.of( false, e.getMessage() );
}
}
case 1:
@@ -164,11 +187,11 @@ public class HTTPAPI implements ILuaAPI
{
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) );
}
return new Object[] { true };
return MethodResult.of( true );
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
return MethodResult.of( false, e.getMessage() );
}
}
case 2: // websocket
@@ -201,20 +224,28 @@ public class HTTPAPI implements ILuaAPI
{
m_httpTasks.add( connector );
}
return new Object[] { true };
return MethodResult.of(true);
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
return MethodResult.of( false, e.getMessage() );
}
}
default:
{
return null;
return MethodResult.empty();
}
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
public void addCloseable( Closeable closeable )
{
synchronized( m_closeables )

View File

@@ -9,16 +9,19 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.computer.IComputerOwned;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.TrackingField;
public interface IAPIEnvironment
public interface IAPIEnvironment extends IComputerOwned
{
interface IPeripheralChangeListener
{
void onPeripheralChanged( int side, IPeripheral newPeripheral );
}
@Override
Computer getComputer();
int getComputerID();
IComputerEnvironment getComputerEnvironment();
@@ -42,4 +45,11 @@ public interface IAPIEnvironment
String getLabel();
void setLabel( String label );
void addTrackingChange( TrackingField field, long change );
default void addTrackingChange( TrackingField field )
{
addTrackingChange( field, 1 );
}
}

View File

@@ -6,12 +6,12 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.shared.util.StringUtil;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import static dan200.computercraft.core.apis.ArgumentHelper.*;
@@ -221,7 +221,8 @@ public class OSAPI implements ILuaAPI
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
@Nonnull
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
@@ -229,7 +230,7 @@ public class OSAPI implements ILuaAPI
{
// queueEvent
queueLuaEvent( getString( args, 0 ), trimArray( args, 1 ) );
return null;
return MethodResult.empty();
}
case 1:
{
@@ -238,7 +239,7 @@ public class OSAPI implements ILuaAPI
synchronized( m_timers )
{
m_timers.put( m_nextTimerToken, new Timer( (int)Math.round( timer / 0.05 ) ) );
return new Object[] { m_nextTimerToken++ };
return MethodResult.of( m_nextTimerToken++ );
}
}
case 2:
@@ -253,33 +254,33 @@ public class OSAPI implements ILuaAPI
{
int day = (time > m_time) ? m_day : (m_day + 1);
m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
return new Object[] { m_nextAlarmToken++ };
return MethodResult.of( m_nextAlarmToken++ );
}
}
case 3:
{
// shutdown
m_apiEnvironment.shutdown();
return null;
return MethodResult.empty();
}
case 4:
{
// reboot
m_apiEnvironment.reboot();
return null;
return MethodResult.empty();
}
case 5:
case 6:
{
// computerID/getComputerID
return new Object[] { getComputerID() };
return MethodResult.of( getComputerID() );
}
case 7:
{
// setComputerLabel
String label = optString( args, 0, null );
m_apiEnvironment.setLabel( StringUtil.normaliseLabel( label ) );
return null;
return MethodResult.empty();
}
case 8:
case 9:
@@ -288,16 +289,16 @@ public class OSAPI implements ILuaAPI
String label = m_apiEnvironment.getLabel();
if( label != null )
{
return new Object[] { label };
return MethodResult.of( label );
}
return null;
return MethodResult.empty();
}
case 10:
{
// clock
synchronized( m_timers )
{
return new Object[] { m_clock * 0.05 };
return MethodResult.of( m_clock * 0.05 );
}
}
case 11:
@@ -310,19 +311,19 @@ public class OSAPI implements ILuaAPI
{
// Get Hour of day (UTC)
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getTimeForCalendar( c ) };
return MethodResult.of( getTimeForCalendar( c ) );
}
case "local":
{
// Get Hour of day (local time)
Calendar c = Calendar.getInstance();
return new Object[] { getTimeForCalendar( c ) };
return MethodResult.of( getTimeForCalendar( c ) );
}
case "ingame":
// Get ingame hour
synchronized( m_alarms )
{
return new Object[] { m_time };
return MethodResult.of( m_time );
}
default:
throw new LuaException( "Unsupported operation" );
@@ -338,19 +339,19 @@ public class OSAPI implements ILuaAPI
{
// Get numbers of days since 1970-01-01 (utc)
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getDayForCalendar( c ) };
return MethodResult.of( getDayForCalendar( c ) );
}
case "local":
{
// Get numbers of days since 1970-01-01 (local time)
Calendar c = Calendar.getInstance();
return new Object[] { getDayForCalendar( c ) };
return MethodResult.of( getDayForCalendar( c ) );
}
case "ingame":
// Get game day
synchronized( m_alarms )
{
return new Object[] { m_day };
return MethodResult.of( m_day );
}
default:
throw new LuaException( "Unsupported operation" );
@@ -367,7 +368,7 @@ public class OSAPI implements ILuaAPI
m_timers.remove( token );
}
}
return null;
return MethodResult.empty();
}
case 14:
{
@@ -380,7 +381,7 @@ public class OSAPI implements ILuaAPI
m_alarms.remove( token );
}
}
return null;
return MethodResult.empty();
}
case 15:
{
@@ -392,21 +393,21 @@ public class OSAPI implements ILuaAPI
{
// Get utc epoch
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getEpochForCalendar( c ) };
return MethodResult.of( getEpochForCalendar( c ) );
}
case "local":
{
// Get local epoch
Calendar c = Calendar.getInstance();
return new Object[] { getEpochForCalendar( c ) };
return MethodResult.of( getEpochForCalendar( c ) );
}
case "ingame":
// Get in-game epoch
synchronized( m_alarms )
{
return new Object[] {
return MethodResult.of(
m_day * 86400000 + (int) (m_time * 3600000.0f)
};
);
}
default:
throw new LuaException( "Unsupported operation" );
@@ -414,11 +415,18 @@ public class OSAPI implements ILuaAPI
}
default:
{
return null;
return MethodResult.empty();
}
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
// Private methods
private void queueLuaEvent( String event, Object[] args )

View File

@@ -8,17 +8,18 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerThread;
import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -96,7 +97,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
m_attached = false;
}
public Object[] call( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException
public MethodResult call( ICallContext context, String methodName, Object[] arguments ) throws LuaException
{
int method = -1;
synchronized( this )
@@ -108,6 +109,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
}
if( method >= 0 )
{
m_environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
return m_peripheral.callMethod( this, context, method, arguments );
}
else
@@ -180,10 +182,49 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
}
return m_side;
}
@Nonnull
@Override
public Map<String, IPeripheral> getAvailablePeripherals()
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
Map<String, IPeripheral> peripherals = new HashMap<>();
for( PeripheralWrapper wrapper : m_peripherals )
{
if( wrapper != null && wrapper.isAttached() )
{
peripherals.put( wrapper.getAttachmentName(), wrapper.getPeripheral() );
}
}
return Collections.unmodifiableMap( peripherals );
}
@Nullable
@Override
public IPeripheral getAvailablePeripheral( @Nonnull String name )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
for( PeripheralWrapper wrapper : m_peripherals )
{
if( wrapper != null && wrapper.isAttached() && wrapper.getAttachmentName().equals( name ) )
{
return wrapper.getPeripheral();
}
}
return null;
}
}
private final IAPIEnvironment m_environment;
private FileSystem m_fileSystem;
private final PeripheralWrapper[] m_peripherals;
private boolean m_running;
@@ -285,7 +326,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
{
synchronized( m_peripherals )
{
m_fileSystem = m_environment.getFileSystem();
m_running = true;
for( int i=0; i<6; ++i )
{
@@ -312,7 +352,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
wrapper.detach();
}
}
m_fileSystem = null;
}
}
@@ -328,8 +367,9 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
};
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
@@ -349,7 +389,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
}
}
}
return new Object[] { present };
return MethodResult.of( present );
}
case 1:
{
@@ -368,10 +408,10 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
}
if( type != null )
{
return new Object[] { type };
return MethodResult.of( type );
}
}
return null;
return MethodResult.empty();
}
case 2:
{
@@ -395,9 +435,9 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
for(int i=0; i<methods.length; ++i ) {
table.put( i+1, methods[i] );
}
return new Object[] { table };
return MethodResult.of( table );
}
return null;
return MethodResult.empty();
}
case 3:
{
@@ -422,11 +462,19 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
}
default:
{
return null;
return MethodResult.empty();
}
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
// Privates
private int parseSide( Object[] args ) throws LuaException

View File

@@ -6,12 +6,12 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.Computer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
@@ -56,8 +56,9 @@ public class RedstoneAPI implements ILuaAPI
};
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
@@ -69,7 +70,7 @@ public class RedstoneAPI implements ILuaAPI
{
table.put( i+1, Computer.s_sideNames[i] );
}
return new Object[] { table };
return MethodResult.of( table );
}
case 1:
{
@@ -77,19 +78,19 @@ public class RedstoneAPI implements ILuaAPI
int side = parseSide( args );
boolean output = getBoolean( args, 1 );
m_environment.setOutput( side, output ? 15 : 0 );
return null;
return MethodResult.empty();
}
case 2:
{
// getOutput
int side = parseSide( args );
return new Object[] { m_environment.getOutput( side ) > 0 };
return MethodResult.of( m_environment.getOutput( side ) > 0 );
}
case 3:
{
// getInput
int side = parseSide( args );
return new Object[] { m_environment.getInput( side ) > 0 };
return MethodResult.of( m_environment.getInput( side ) > 0 );
}
case 4:
{
@@ -97,19 +98,19 @@ public class RedstoneAPI implements ILuaAPI
int side = parseSide( args );
int output = getInt( args, 1 );
m_environment.setBundledOutput( side, output );
return null;
return MethodResult.empty();
}
case 5:
{
// getBundledOutput
int side = parseSide( args );
return new Object[] { m_environment.getBundledOutput( side ) };
return MethodResult.of( m_environment.getBundledOutput( side ) );
}
case 6:
{
// getBundledInput
int side = parseSide( args );
return new Object[] { m_environment.getBundledInput( side ) };
return MethodResult.of( m_environment.getBundledInput( side ) );
}
case 7:
{
@@ -117,7 +118,7 @@ public class RedstoneAPI implements ILuaAPI
int side = parseSide( args );
int mask = getInt( args, 1 );
int input = m_environment.getBundledInput( side );
return new Object[] { ((input & mask) == mask) };
return MethodResult.of( ((input & mask) == mask) );
}
case 8:
case 9:
@@ -130,28 +131,36 @@ public class RedstoneAPI implements ILuaAPI
throw new LuaException( "Expected number in range 0-15" );
}
m_environment.setOutput( side, output );
return null;
return MethodResult.empty();
}
case 10:
case 11:
{
// getAnalogOutput/getAnalogueOutput
int side = parseSide( args );
return new Object[] { m_environment.getOutput( side ) };
return MethodResult.of( m_environment.getOutput( side ) );
}
case 12:
case 13:
{
// getAnalogInput/getAnalogueInput
int side = parseSide( args );
return new Object[] { m_environment.getInput( side ) };
return MethodResult.of( m_environment.getInput( side ) );
}
default:
{
return null;
return MethodResult.empty();
}
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
private int parseSide( Object[] args ) throws LuaException
{

View File

@@ -0,0 +1,214 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
/**
* Various helpers for tables
*/
public final class TableHelper
{
private TableHelper()
{
throw new IllegalStateException( "Cannot instantiate singleton " + getClass().getName() );
}
@Nonnull
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nullable Object actual )
{
return badKey( key, expected, ArgumentHelper.getType( actual ) );
}
@Nonnull
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nonnull String actual )
{
return new LuaException( "bad field '" + key + "' (" + expected + " expected, got " + actual + ")" );
}
public static double getNumberField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Number )
{
return ((Number) value).doubleValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static int getIntField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Number )
{
return (int) ((Number) value).longValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static double getRealField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
return checkReal( key, getNumberField( table, key ) );
}
public static boolean getBooleanField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Boolean )
{
return (Boolean) value;
}
else
{
throw badKey( key, "boolean", value );
}
}
@Nonnull
public static String getStringField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof String )
{
return (String) value;
}
else
{
throw badKey( key, "string", value );
}
}
@SuppressWarnings( "unchecked" )
@Nonnull
public static Map<Object, Object> getTableField( @Nonnull Map<?, ?> table, @Nonnull String key ) throws LuaException
{
Object value = table.get( key );
if( value instanceof Map )
{
return (Map<Object, Object>) value;
}
else
{
throw badKey( key, "table", value );
}
}
public static double optNumberField( @Nonnull Map<?, ?> table, @Nonnull String key, double def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Number )
{
return ((Number) value).doubleValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static int optIntField( @Nonnull Map<?, ?> table, @Nonnull String key, int def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Number )
{
return (int) ((Number) value).longValue();
}
else
{
throw badKey( key, "number", value );
}
}
public static double optRealField( @Nonnull Map<?, ?> table, @Nonnull String key, double def ) throws LuaException
{
return checkReal( key, optNumberField( table, key, def ) );
}
public static boolean optBooleanField( @Nonnull Map<?, ?> table, @Nonnull String key, boolean def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Boolean )
{
return (Boolean) value;
}
else
{
throw badKey( key, "boolean", value );
}
}
public static String optStringField( @Nonnull Map<?, ?> table, @Nonnull String key, String def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof String )
{
return (String) value;
}
else
{
throw badKey( key, "string", value );
}
}
@SuppressWarnings( "unchecked" )
public static Map<Object, Object> optTableField( @Nonnull Map<?, ?> table, @Nonnull String key, Map<Object, Object> def ) throws LuaException
{
Object value = table.get( key );
if( value == null )
{
return def;
}
else if( value instanceof Map )
{
return (Map<Object, Object>) value;
}
else
{
throw badKey( key, "table", value );
}
}
private static double checkReal( @Nonnull String key, double value ) throws LuaException
{
if( Double.isNaN( value ) )
{
throw badKey( key, "number", "nan" );
}
else if( value == Double.POSITIVE_INFINITY )
{
throw badKey( key, "number", "inf" );
}
else if( value == Double.NEGATIVE_INFINITY )
{
throw badKey( key, "number", "-inf" );
}
else
{
return value;
}
}
}

View File

@@ -6,15 +6,15 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.util.Palette;
import org.apache.commons.lang3.ArrayUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static dan200.computercraft.core.apis.ArgumentHelper.*;
@@ -83,11 +83,9 @@ public class TermAPI implements ILuaAPI
return colour;
}
public static Object[] encodeColour( int colour ) throws LuaException
public static MethodResult encodeColour( int colour )
{
return new Object[] {
1 << colour
};
return MethodResult.of( 1 << colour );
}
public static void setColour( Terminal terminal, int colour, double r, double g, double b )
@@ -99,8 +97,9 @@ public class TermAPI implements ILuaAPI
}
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
@@ -119,7 +118,7 @@ public class TermAPI implements ILuaAPI
m_terminal.write( text );
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
}
return null;
return MethodResult.empty();
}
case 1:
{
@@ -129,7 +128,7 @@ public class TermAPI implements ILuaAPI
{
m_terminal.scroll(y);
}
return null;
return MethodResult.empty();
}
case 2:
{
@@ -140,7 +139,7 @@ public class TermAPI implements ILuaAPI
{
m_terminal.setCursorPos( x, y );
}
return null;
return MethodResult.empty();
}
case 3:
{
@@ -150,7 +149,7 @@ public class TermAPI implements ILuaAPI
{
m_terminal.setCursorBlink( b );
}
return null;
return MethodResult.empty();
}
case 4:
{
@@ -161,7 +160,7 @@ public class TermAPI implements ILuaAPI
x = m_terminal.getCursorX();
y = m_terminal.getCursorY();
}
return new Object[] { x + 1, y + 1 };
return MethodResult.of( x + 1, y + 1 );
}
case 5:
{
@@ -172,7 +171,7 @@ public class TermAPI implements ILuaAPI
width = m_terminal.getWidth();
height = m_terminal.getHeight();
}
return new Object[] { width, height };
return MethodResult.of( width, height );
}
case 6:
{
@@ -181,7 +180,7 @@ public class TermAPI implements ILuaAPI
{
m_terminal.clear();
}
return null;
return MethodResult.empty();
}
case 7:
{
@@ -190,7 +189,7 @@ public class TermAPI implements ILuaAPI
{
m_terminal.clearLine();
}
return null;
return MethodResult.empty();
}
case 8:
case 9:
@@ -201,7 +200,7 @@ public class TermAPI implements ILuaAPI
{
m_terminal.setTextColour( colour );
}
return null;
return MethodResult.empty();
}
case 10:
case 11:
@@ -212,13 +211,13 @@ public class TermAPI implements ILuaAPI
{
m_terminal.setBackgroundColour( colour );
}
return null;
return MethodResult.empty();
}
case 12:
case 13:
{
// isColour/isColor
return new Object[] { m_environment.isColour() };
return MethodResult.of( m_environment.isColour() );
}
case 14:
case 15:
@@ -248,7 +247,7 @@ public class TermAPI implements ILuaAPI
m_terminal.blit( text, textColour, backgroundColour );
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
}
return null;
return MethodResult.empty();
}
case 19:
case 20:
@@ -268,7 +267,7 @@ public class TermAPI implements ILuaAPI
double b = getReal( args, 3 );
setColour( m_terminal, colour, r, g, b );
}
return null;
return MethodResult.empty();
}
case 21:
case 22:
@@ -279,18 +278,26 @@ public class TermAPI implements ILuaAPI
{
if ( m_terminal.getPalette() != null )
{
return ArrayUtils.toObject( m_terminal.getPalette().getColour( colour ) );
return MethodResult.of( (Object[]) ArrayUtils.toObject( m_terminal.getPalette().getColour( colour ) ) );
}
}
return null;
return MethodResult.empty();
}
default:
{
return null;
return MethodResult.empty();
}
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
private static int getHighestBit( int group )
{
int bit = 0;

View File

@@ -1,10 +1,12 @@
package dan200.computercraft.core.apis.handles;
import com.google.common.io.ByteStreams;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
@@ -13,6 +15,8 @@ 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 )
@@ -32,8 +36,9 @@ public class BinaryInputHandle extends HandleGeneric
};
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
@@ -45,26 +50,56 @@ public class BinaryInputHandle extends HandleGeneric
if( args.length > 0 && args[ 0 ] != null )
{
int count = getInt( args, 0 );
if( count <= 0 || count >= 1024 * 16 )
if( count < 0 )
{
throw new LuaException( "Count out of range" );
// 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 );
byte[] bytes = new byte[ count ];
count = m_stream.read( bytes );
if( count < 0 ) return null;
if( count < bytes.length ) bytes = Arrays.copyOf( bytes, count );
return new Object[] { bytes };
if( read < 0 ) return MethodResult.empty();
if( read < count ) bytes = Arrays.copyOf( bytes, read );
return MethodResult.of( 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 MethodResult.of( new Object[]{ out.toByteArray() } );
}
}
else
{
int b = m_stream.read();
return b == -1 ? null : new Object[] { b };
return b == -1 ? MethodResult.empty() : MethodResult.of( b );
}
}
catch( IOException e )
{
return null;
return MethodResult.empty();
}
case 1:
// readAll
@@ -72,18 +107,18 @@ public class BinaryInputHandle extends HandleGeneric
try
{
byte[] out = ByteStreams.toByteArray( m_stream );
return out == null ? null : new Object[] { out };
return out == null ? MethodResult.empty() : MethodResult.of( out );
}
catch( IOException e )
{
return null;
return MethodResult.empty();
}
case 2:
//close
close();
return null;
return MethodResult.empty();
default:
return null;
return MethodResult.empty();
}
}
}

View File

@@ -1,7 +1,8 @@
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.core.apis.ArgumentHelper;
import dan200.computercraft.shared.util.StringUtil;
@@ -30,8 +31,9 @@ public class BinaryOutputHandle extends HandleGeneric
};
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
@@ -54,7 +56,7 @@ public class BinaryOutputHandle extends HandleGeneric
{
throw ArgumentHelper.badArgument( 0, "string or number", args.length > 0 ? args[ 0 ] : null );
}
return null;
return MethodResult.empty();
}
catch( IOException e )
{
@@ -66,18 +68,18 @@ public class BinaryOutputHandle extends HandleGeneric
try
{
m_writer.flush();
return null;
return MethodResult.empty();
}
catch( IOException e )
{
return null;
return MethodResult.empty();
}
case 2:
//close
close();
return null;
return MethodResult.empty();
default:
return null;
return MethodResult.empty();
}
}
}

View File

@@ -1,15 +1,18 @@
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import javax.annotation.Nonnull;
import java.io.*;
import static dan200.computercraft.core.apis.ArgumentHelper.*;
import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
public class EncodedInputHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
private final BufferedReader m_reader;
public EncodedInputHandle( BufferedReader reader )
@@ -55,8 +58,9 @@ public class EncodedInputHandle extends HandleGeneric
};
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
@@ -68,16 +72,16 @@ public class EncodedInputHandle extends HandleGeneric
String line = m_reader.readLine();
if( line != null )
{
return new Object[] { line };
return MethodResult.of( line );
}
else
{
return null;
return MethodResult.empty();
}
}
catch( IOException e )
{
return null;
return MethodResult.empty();
}
case 1:
// readAll
@@ -95,38 +99,68 @@ public class EncodedInputHandle extends HandleGeneric
result.append( "\n" );
}
}
return new Object[] { result.toString() };
return MethodResult.of( result.toString() );
}
catch( IOException e )
{
return null;
return MethodResult.empty();
}
case 2:
// close
close();
return null;
return MethodResult.empty();
case 3:
// read
checkOpen();
try
{
int count = optInt( args, 0, 1 );
if( count <= 0 || count >= 1024 * 16 )
if( count < 0 )
{
throw new LuaException( "Count out of range" );
// Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so
// it seems best to remain somewhat consistent.
throw new LuaException( "Cannot read a negative number of characters" );
}
else if( count <= BUFFER_SIZE )
{
// If we've got a small count, then allocate that and read it.
char[] chars = new char[ count ];
int read = m_reader.read( chars );
return read < 0 ? MethodResult.empty() : MethodResult.of( new String( chars, 0, read ) );
}
else
{
// If we've got a large count, read in bunches of 8192.
char[] buffer = new char[ BUFFER_SIZE ];
// Read the initial set of characters, failing if none are read.
int read = m_reader.read( buffer, 0, Math.min( buffer.length, count ) );
if( read == -1 ) return null;
StringBuilder out = new StringBuilder( read );
count -= read;
out.append( buffer, 0, read );
// Otherwise read until we either reach the limit or we no longer consume
// the full buffer.
while( read >= BUFFER_SIZE && count > 0 )
{
read = m_reader.read( buffer, 0, Math.min( BUFFER_SIZE, count ) );
if( read == -1 ) break;
count -= read;
out.append( buffer, 0, read );
}
return MethodResult.of( out.toString() );
}
char[] bytes = new char[ count ];
count = m_reader.read( bytes );
if( count < 0 ) return null;
String str = new String( bytes, 0, count );
return new Object[] { str };
}
catch( IOException e )
{
return null;
return MethodResult.empty();
}
default:
return null;
return MethodResult.empty();
}
}
}

View File

@@ -1,7 +1,8 @@
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import javax.annotation.Nonnull;
import java.io.*;
@@ -53,8 +54,9 @@ public class EncodedOutputHandle extends HandleGeneric
};
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
{
@@ -74,7 +76,7 @@ public class EncodedOutputHandle extends HandleGeneric
try
{
m_writer.write( text, 0, text.length() );
return null;
return MethodResult.empty();
}
catch( IOException e )
{
@@ -98,7 +100,7 @@ public class EncodedOutputHandle extends HandleGeneric
{
m_writer.write( text, 0, text.length() );
m_writer.newLine();
return null;
return MethodResult.empty();
}
catch( IOException e )
{
@@ -111,18 +113,18 @@ public class EncodedOutputHandle extends HandleGeneric
try
{
m_writer.flush();
return null;
return MethodResult.empty();
}
catch( IOException e )
{
return null;
return MethodResult.empty();
}
case 3:
// close
close();
return null;
return MethodResult.empty();
default:
return null;
return MethodResult.empty();
}
}
}

View File

@@ -1,8 +1,12 @@
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Closeable;
import java.io.IOException;
@@ -32,4 +36,12 @@ public abstract class HandleGeneric implements ILuaObject
{
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
}

View File

@@ -9,14 +9,14 @@ package dan200.computercraft.core.apis.http;
import com.google.common.base.Joiner;
import com.google.common.io.ByteStreams;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.apis.handles.BinaryInputHandle;
import dan200.computercraft.core.apis.handles.EncodedInputHandle;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.*;
import java.net.*;
import java.util.Arrays;
@@ -78,8 +78,10 @@ public class HTTPRequest implements Runnable
private final String m_postText;
private final Map<String, String> m_headers;
private boolean m_binary;
private final String m_method;
private final boolean m_followRedirects;
public HTTPRequest( IAPIEnvironment environment, String urlString, URL url, final String postText, final Map<String, String> headers, boolean binary ) throws HTTPRequestException
public HTTPRequest( IAPIEnvironment environment, String urlString, URL url, final String postText, final Map<String, String> headers, boolean binary, final String method, final boolean followRedirects ) throws HTTPRequestException
{
m_environment = environment;
m_urlString = urlString;
@@ -87,6 +89,8 @@ public class HTTPRequest implements Runnable
m_binary = binary;
m_postText = postText;
m_headers = headers;
m_method = method;
m_followRedirects = followRedirects;
}
@Override
@@ -101,7 +105,7 @@ public class HTTPRequest implements Runnable
{
// Queue the failure event if not.
String error = e.getMessage();
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, error == null ? "Could not connect" : error, null } );
m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, error == null ? "Could not connect" : error, null } );
return;
}
@@ -119,6 +123,8 @@ public class HTTPRequest implements Runnable
{
connection.setRequestMethod( "GET" );
}
if( m_method != null ) connection.setRequestMethod( m_method );
connection.setInstanceFollowRedirects( m_followRedirects );
// Set headers
connection.setRequestProperty( "accept-charset", "UTF-8" );
@@ -134,6 +140,11 @@ public class HTTPRequest implements Runnable
}
}
// Add request size and count to the tracker before opening the connection
m_environment.addTrackingChange( TrackingField.HTTP_REQUESTS );
m_environment.addTrackingChange( TrackingField.HTTP_UPLOAD,
getHeaderSize( connection.getRequestProperties() ) + (m_postText == null ? 0 : m_postText.length()) );
// Send POST text
if( m_postText != null )
{
@@ -178,6 +189,9 @@ public class HTTPRequest implements Runnable
headers.put( header.getKey(), joiner.join( header.getValue() ) );
}
m_environment.addTrackingChange( TrackingField.HTTP_DOWNLOAD,
getHeaderSize( connection.getHeaderFields() ) + result.length );
InputStream contents = new ByteArrayInputStream( result );
ILuaObject stream = wrapStream(
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, connection.getContentEncoding() ),
@@ -189,17 +203,17 @@ public class HTTPRequest implements Runnable
// Queue the appropriate event.
if( responseSuccess )
{
m_environment.queueEvent( "http_success", new Object[] { m_urlString, stream } );
m_environment.queueEvent( "http_success", new Object[]{ m_urlString, stream } );
}
else
{
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, "Could not connect", stream } );
m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, "Could not connect", stream } );
}
}
catch( IOException e )
{
// There was an error
m_environment.queueEvent( "http_failure", new Object[] { m_urlString, "Could not connect", null } );
m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, "Could not connect", null } );
}
}
@@ -209,8 +223,8 @@ public class HTTPRequest implements Runnable
final int methodOffset = oldMethods.length;
final String[] newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 );
newMethods[ methodOffset + 0 ] = "getResponseCode";
newMethods[ methodOffset + 1 ] = "getResponseHeaders";
newMethods[methodOffset + 0] = "getResponseCode";
newMethods[methodOffset + 1] = "getResponseHeaders";
return new ILuaObject()
{
@@ -221,8 +235,9 @@ public class HTTPRequest implements Runnable
return newMethods;
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] args ) throws LuaException
{
if( method < methodOffset )
{
@@ -233,19 +248,38 @@ public class HTTPRequest implements Runnable
case 0:
{
// getResponseCode
return new Object[] { responseCode };
return MethodResult.of( responseCode );
}
case 1:
{
// getResponseHeaders
return new Object[] { responseHeaders };
return MethodResult.of( responseHeaders );
}
default:
{
return null;
return MethodResult.empty();
}
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
};
}
private static long getHeaderSize( Map<String, List<String>> headers )
{
long size = 0;
for( Map.Entry<String, List<String>> header : headers.entrySet() )
{
size += header.getKey() == null ? 0 : header.getKey().length();
for( String value : header.getValue() ) size += value == null ? 0 : value.length() + 1;
}
return size;
}
}

View File

@@ -7,11 +7,10 @@
package dan200.computercraft.core.apis.http;
import com.google.common.base.Objects;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.apis.HTTPAPI;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.tracking.TrackingField;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
@@ -71,7 +70,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
private void onClosed()
{
close( true );
computer.queueEvent( CLOSE_EVENT, new Object[] { url } );
computer.queueEvent( CLOSE_EVENT, new Object[]{ url } );
}
@Override
@@ -102,7 +101,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
if( !handshaker.isHandshakeComplete() )
{
handshaker.finishHandshake( ch, (FullHttpResponse) msg );
computer.queueEvent( SUCCESS_EVENT, new Object[] { url, this } );
computer.queueEvent( SUCCESS_EVENT, new Object[]{ url, this } );
return;
}
@@ -115,14 +114,19 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
WebSocketFrame frame = (WebSocketFrame) msg;
if( frame instanceof TextWebSocketFrame )
{
computer.queueEvent( MESSAGE_EVENT, new Object[] { url, ((TextWebSocketFrame) frame).text() } );
String data = ((TextWebSocketFrame) frame).text();
computer.addTrackingChange( TrackingField.WEBSOCKET_INCOMING, data.length() );
computer.queueEvent( MESSAGE_EVENT, new Object[]{ url, data } );
}
else if( frame instanceof BinaryWebSocketFrame )
{
ByteBuf data = frame.content();
byte[] converted = new byte[ data.readableBytes() ];
byte[] converted = new byte[data.readableBytes()];
data.readBytes( converted );
computer.queueEvent( MESSAGE_EVENT, new Object[] { url, data } );
computer.addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length );
computer.queueEvent( MESSAGE_EVENT, new Object[]{ url, converted } );
}
else if( frame instanceof CloseWebSocketFrame )
{
@@ -135,7 +139,7 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause )
{
ctx.close();
computer.queueEvent( FAILURE_EVENT, new Object[] {
computer.queueEvent( FAILURE_EVENT, new Object[]{
url,
cause instanceof WebSocketHandshakeException ? cause.getMessage() : "Could not connect"
} );
@@ -145,37 +149,53 @@ public class WebsocketConnection extends SimpleChannelInboundHandler<Object> imp
@Override
public String[] getMethodNames()
{
return new String[] { "receive", "send", "close" };
return new String[]{ "receive", "send", "close" };
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
switch( method )
{
case 0:
while( true )
checkOpen();
return MethodResult.pullEvent( MESSAGE_EVENT, new ILuaFunction()
{
checkOpen();
Object[] event = context.pullEvent( MESSAGE_EVENT );
if( event.length >= 3 && Objects.equal( event[ 1 ], url ) )
@Nonnull
@Override
public MethodResult call( @Nullable Object[] event ) throws LuaException
{
return new Object[] { event[ 2 ] };
if( event != null && event.length >= 3 && Objects.equal( event[1], url ) )
{
return MethodResult.of( event[2] );
}
checkOpen();
return MethodResult.pullEvent( MESSAGE_EVENT, this );
}
}
} );
case 1:
{
checkOpen();
String text = arguments.length > 0 && arguments[ 0 ] != null ? arguments[ 0 ].toString() : "";
String text = arguments.length > 0 && arguments[0] != null ? arguments[0].toString() : "";
computer.addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() );
channel.writeAndFlush( new TextWebSocketFrame( text ) );
return null;
return MethodResult.empty();
}
case 2:
close( true );
return null;
return MethodResult.empty();
default:
return null;
return MethodResult.empty();
}
}

View File

@@ -21,6 +21,7 @@ import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
@@ -34,7 +35,7 @@ import java.security.KeyStore;
import java.util.Map;
import java.util.concurrent.Future;
/*
/**
* Provides functionality to verify and connect to a remote websocket.
*/
public final class WebsocketConnector
@@ -177,7 +178,7 @@ public final class WebsocketConnector
httpHeaders.add( header.getKey(), header.getValue() );
}
WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, false, httpHeaders );
WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, true, httpHeaders );
final WebsocketConnection connection = new WebsocketConnection( environment, api, handshaker, address );
new Bootstrap()
@@ -190,7 +191,12 @@ public final class WebsocketConnector
{
ChannelPipeline p = ch.pipeline();
if( ssl != null ) p.addLast( ssl.newHandler( ch.alloc(), uri.getHost(), port ) );
p.addLast( new HttpClientCodec(), new HttpObjectAggregator( 8192 ), connection );
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator( 8192 ),
WebSocketClientCompressionHandler.INSTANCE,
connection
);
}
} )
.remoteAddress( socketAddress )

View File

@@ -20,6 +20,8 @@ import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -167,6 +169,12 @@ public class Computer
m_computer.setLabel( label );
}
@Override
public void addTrackingChange( TrackingField field, long change )
{
Tracking.addValue( m_computer, field, change );
}
public void onPeripheralChanged( int side, IPeripheral peripheral )
{
synchronized( m_computer.m_peripherals )
@@ -257,10 +265,18 @@ public class Computer
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return delegate.callMethod( context, method, arguments );
}
@Nonnull
@Override
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
return delegate.callMethod( context, method, arguments );
}
}
private static IMount s_romMount = null;
@@ -952,7 +968,7 @@ public class Computer
return;
}
}
final Computer computer = this;
ITask task = new ITask() {
@Override

View File

@@ -7,6 +7,7 @@
package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.tracking.Tracking;
import java.util.HashSet;
import java.util.Set;
@@ -68,17 +69,17 @@ public class ComputerThread
s_stopped = false;
if( s_threads == null || s_threads.length != ComputerCraft.computer_threads )
{
s_threads = new Thread[ ComputerCraft.computer_threads ];
s_threads = new Thread[ComputerCraft.computer_threads];
}
SecurityManager manager = System.getSecurityManager();
final ThreadGroup group = manager == null ? Thread.currentThread().getThreadGroup() : manager.getThreadGroup();
for( int i = 0; i < s_threads.length; i++ )
{
Thread thread = s_threads[ i ];
Thread thread = s_threads[i];
if( thread == null || !thread.isAlive() )
{
thread = s_threads[ i ] = new Thread( group, new TaskExecutor(), "ComputerCraft-Computer-Manager-" + s_ManagerCounter.getAndIncrement() );
thread = s_threads[i] = new Thread( group, new TaskExecutor(), "ComputerCraft-Computer-Manager-" + s_ManagerCounter.getAndIncrement() );
thread.setDaemon( true );
thread.start();
}
@@ -195,6 +196,8 @@ public class ComputerThread
thread.start();
}
long start = System.nanoTime();
// Execute the task
runner.submit( task );
@@ -221,6 +224,23 @@ public class ComputerThread
// Interrupt the thread
if( !done )
{
StringBuilder builder = new StringBuilder( "Terminating " );
if( computer != null )
{
builder.append( "computer " ).append( computer.getID() );
}
else
{
builder.append( "unknown computer" );
}
builder.append( ". Thread is currently running" );
for( StackTraceElement element : thread.getStackTrace() )
{
builder.append( "\n at " ).append( element );
}
ComputerCraft.log.error( builder.toString() );
thread.interrupt();
thread = null;
runner = null;
@@ -229,6 +249,10 @@ public class ComputerThread
}
finally
{
long stop = System.nanoTime();
Computer computer = task.getOwner();
if( computer != null ) Tracking.addTaskTiming( computer, stop - start );
// Re-add it back onto the queue or remove it
synchronized( s_taskLock )
{

View File

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

View File

@@ -6,14 +6,17 @@
package dan200.computercraft.core.computer;
import java.util.LinkedList;
import dan200.computercraft.core.tracking.Tracking;
import java.util.ArrayDeque;
import java.util.Queue;
public class MainThread
{
private static final int MAX_TASKS_PER_TICK = 1000;
private static final int MAX_TASKS_TOTAL = 50000;
private static final LinkedList<ITask> m_outstandingTasks = new LinkedList<>();
private static final Queue<ITask> m_outstandingTasks = new ArrayDeque<>();
private static final Object m_nextUnusedTaskIDLock = new Object();
private static long m_nextUnusedTaskID = 0;
@@ -31,7 +34,7 @@ public class MainThread
{
if( m_outstandingTasks.size() < MAX_TASKS_TOTAL )
{
m_outstandingTasks.addLast( task );
m_outstandingTasks.offer( task );
return true;
}
}
@@ -46,14 +49,17 @@ public class MainThread
ITask task = null;
synchronized( m_outstandingTasks )
{
if( m_outstandingTasks.size() > 0 )
{
task = m_outstandingTasks.removeFirst();
}
task = m_outstandingTasks.poll();
}
if( task != null )
{
long start = System.nanoTime();
task.execute();
long stop = System.nanoTime();
Computer computer = task.getOwner();
if( computer != null ) Tracking.addServerTiming( computer, stop - start );
++tasksThisTick;
}
else

View File

@@ -0,0 +1,87 @@
/*
* 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.lua;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.computer.MainThread;
import javax.annotation.Nonnull;
class CobaltCallContext implements ICallContext
{
private final Computer computer;
CobaltCallContext( Computer computer )
{
this.computer = computer;
}
@Override
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
final ITask iTask = new ITask()
{
@Override
public Computer getOwner()
{
return computer;
}
@Override
public void execute()
{
try
{
Object[] results = task.execute();
if( results != null )
{
Object[] eventArguments = new Object[results.length + 2];
eventArguments[0] = taskID;
eventArguments[1] = true;
System.arraycopy( results, 0, eventArguments, 2, results.length );
computer.queueEvent( "task_complete", eventArguments );
}
else
{
computer.queueEvent( "task_complete", new Object[]{ taskID, true } );
}
}
catch( LuaException e )
{
computer.queueEvent( "task_complete", new Object[]{
taskID, false, e.getMessage()
} );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error running task", t );
}
computer.queueEvent( "task_complete", new Object[]{
taskID, false, "Java Exception Thrown: " + t.toString()
} );
}
}
};
if( MainThread.queueTask( iTask ) )
{
return taskID;
}
else
{
throw new LuaException( "Task limit exceeded" );
}
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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.lua;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaContextTask;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.Computer;
import org.squiddev.cobalt.LuaError;
import org.squiddev.cobalt.LuaState;
import org.squiddev.cobalt.LuaThread;
import org.squiddev.cobalt.UnwindThrowable;
import javax.annotation.Nonnull;
import java.lang.ref.WeakReference;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* An ugly wrapper for {@link ILuaContext} style calls, which executes them on a separate thread.
*/
class CobaltLuaContext extends CobaltCallContext implements ILuaContext
{
private static final ThreadGroup group = new ThreadGroup( "ComputerCraft-Lua" );
private static final AtomicInteger threadCounter = new AtomicInteger();
private static final ExecutorService threads = new ThreadPoolExecutor(
4, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(),
task -> {
Thread thread = new Thread( group, task, group.getName() + "-" + threadCounter.incrementAndGet() );
if( !thread.isDaemon() ) thread.setDaemon( true );
if( thread.getPriority() != Thread.NORM_PRIORITY ) thread.setPriority( Thread.NORM_PRIORITY );
return thread;
}
);
private boolean done = false;
private Object[] values;
private LuaError exception;
private final Semaphore yield = new Semaphore();
private final Semaphore resume = new Semaphore();
private WeakReference<LuaThread> thread;
CobaltLuaContext( Computer computer, LuaState state )
{
super( computer );
this.thread = state.getCurrentThread().getReference();
}
@Nonnull
@Override
@Deprecated
public Object[] pullEvent( String filter ) throws LuaException, InterruptedException
{
Object[] results = pullEventRaw( filter );
if( results.length >= 1 && results[0].equals( "terminate" ) )
{
throw new LuaException( "Terminated", 0 );
}
return results;
}
@Nonnull
@Override
@Deprecated
public Object[] pullEventRaw( String filter ) throws InterruptedException
{
return yield( new Object[]{ filter } );
}
@Nonnull
@Override
@Deprecated
public Object[] yield( Object[] yieldArgs ) throws InterruptedException
{
if( done ) throw new IllegalStateException( "Cannot yield when complete" );
values = yieldArgs;
yield.signal();
// Every 30 seconds check to see if the coroutine has been GCed
// if so then abort this task.
while( !resume.await( 30000 ) )
{
if( thread.get() == null ) throw new InterruptedException( "Orphaned async task" );
}
return values;
}
@Override
@Deprecated
public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException, InterruptedException
{
// Issue task
final long taskID = issueMainThreadTask( task );
// Wait for response
while( true )
{
Object[] response = pullEvent( "task_complete" );
if( response.length >= 3 && response[1] instanceof Number && response[2] instanceof Boolean )
{
if( ((Number) response[1]).intValue() == taskID )
{
Object[] returnValues = new Object[response.length - 3];
if( (Boolean) response[2] )
{
// Extract the return values from the event and return them
System.arraycopy( response, 3, returnValues, 0, returnValues.length );
return returnValues;
}
else
{
// Extract the error message from the event and raise it
if( response.length >= 4 && response[3] instanceof String )
{
throw new LuaException( (String) response[3] );
}
else
{
throw new LuaException();
}
}
}
}
}
}
void execute( ILuaContextTask task )
{
threads.submit( () -> {
try
{
values = task.execute( this );
}
catch( LuaException e )
{
exception = new LuaError( e.getMessage(), e.getLevel() );
}
catch( InterruptedException e )
{
exception = new LuaError( "Java Exception Thrown: " + e.toString(), 0 );
}
finally
{
done = true;
yield.signal();
}
} );
}
void resume( Object[] args )
{
values = args;
resume.signal();
}
Object[] await( LuaState state, CobaltLuaMachine machine ) throws LuaError, UnwindThrowable
{
if( !done )
{
try
{
yield.await();
}
catch( InterruptedException e )
{
throw new LuaError( "Java Exception Thrown: " + e.toString(), 0 );
}
}
if( done )
{
if( exception != null ) throw exception;
return values;
}
else
{
LuaThread.yield( state, machine.toValues( values ) );
throw new IllegalStateException( "Unreachable" );
}
}
}

View File

@@ -7,14 +7,9 @@
package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.computer.MainThread;
import org.squiddev.cobalt.*;
import org.squiddev.cobalt.compiler.CompileException;
import org.squiddev.cobalt.compiler.LoadState;
@@ -27,7 +22,6 @@ import org.squiddev.cobalt.function.VarArgFunction;
import org.squiddev.cobalt.lib.*;
import org.squiddev.cobalt.lib.platform.AbstractResourceManipulator;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -76,7 +70,7 @@ public class CobaltLuaMachine implements ILuaMachine
int count = ++this.count;
if( count > 100000 )
{
if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE );
if( m_hardAbortMessage != null ) throw new LuaError( m_hardAbortMessage );
this.count = 0;
}
else
@@ -90,7 +84,7 @@ public class CobaltLuaMachine implements ILuaMachine
@Override
public void poll() throws LuaError
{
if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE );
if( m_hardAbortMessage != null ) throw new LuaError( m_hardAbortMessage );
handleSoftAbort();
}
@@ -125,7 +119,7 @@ public class CobaltLuaMachine implements ILuaMachine
if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() );
// Register custom load/loadstring provider which automatically adds prefixes.
LibFunction.bind( state, m_globals, PrefixLoader.class, new String[]{ "load", "loadstring" } );
LibFunction.bind( m_globals, PrefixLoader.class, new String[]{ "load", "loadstring" } );
// Remove globals we don't want to expose
m_globals.rawset( "collectgarbage", Constants.NIL );
@@ -216,18 +210,13 @@ public class CobaltLuaMachine implements ILuaMachine
resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) );
}
Varargs results = m_mainRoutine.resume( resumeArgs );
LuaValue filter = LuaThread.run( m_mainRoutine, resumeArgs ).first();
if( m_hardAbortMessage != null )
{
throw new LuaError( m_hardAbortMessage );
}
else if( !results.first().checkBoolean() )
{
throw new LuaError( results.arg( 2 ).checkString() );
}
else
{
LuaValue filter = results.arg( 2 );
if( filter.isString() )
{
m_eventFilter = filter.toString();
@@ -246,6 +235,7 @@ public class CobaltLuaMachine implements ILuaMachine
}
catch( LuaError e )
{
if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Main thread crashed", e );
m_mainRoutine.abandon();
m_mainRoutine = null;
}
@@ -302,181 +292,12 @@ public class CobaltLuaMachine implements ILuaMachine
{
LuaTable table = new LuaTable();
String[] methods = object.getMethodNames();
for( int i = 0; i < methods.length; ++i )
for( int method = 0; method < methods.length; method++ )
{
if( methods[ i ] != null )
if( methods[method] != null )
{
final int method = i;
final ILuaObject apiObject = object;
final String methodName = methods[ i ];
table.rawset( methodName, new VarArgFunction()
{
@Override
public Varargs invoke( final LuaState state, Varargs _args ) throws LuaError
{
Object[] arguments = toObjects( _args, 1 );
Object[] results;
try
{
results = apiObject.callMethod( new ILuaContext()
{
@Nonnull
@Override
public Object[] pullEvent( String filter ) throws LuaException, InterruptedException
{
Object[] results = pullEventRaw( filter );
if( results.length >= 1 && results[ 0 ].equals( "terminate" ) )
{
throw new LuaException( "Terminated", 0 );
}
return results;
}
@Nonnull
@Override
public Object[] pullEventRaw( String filter ) throws InterruptedException
{
return yield( new Object[] { filter } );
}
@Nonnull
@Override
public Object[] yield( Object[] yieldArgs ) throws InterruptedException
{
try
{
Varargs results = LuaThread.yield( state, toValues( yieldArgs ) );
return toObjects( results, 1 );
}
catch( OrphanedThread e )
{
throw new InterruptedException();
}
catch( Throwable e )
{
throw new RuntimeException( e );
}
}
@Override
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
final ITask iTask = new ITask()
{
@Override
public Computer getOwner()
{
return m_computer;
}
@Override
public void execute()
{
try
{
Object[] results = task.execute();
if( results != null )
{
Object[] eventArguments = new Object[ results.length + 2 ];
eventArguments[ 0 ] = taskID;
eventArguments[ 1 ] = true;
System.arraycopy( results, 0, eventArguments, 2, results.length );
m_computer.queueEvent( "task_complete", eventArguments );
}
else
{
m_computer.queueEvent( "task_complete", new Object[] { taskID, true } );
}
}
catch( LuaException e )
{
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, e.getMessage()
} );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error running task", t );
}
m_computer.queueEvent( "task_complete", new Object[] {
taskID, false, "Java Exception Thrown: " + t.toString()
} );
}
}
};
if( MainThread.queueTask( iTask ) )
{
return taskID;
}
else
{
throw new LuaException( "Task limit exceeded" );
}
}
@Override
public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException, InterruptedException
{
// Issue task
final long taskID = issueMainThreadTask( task );
// Wait for response
while( true )
{
Object[] response = pullEvent( "task_complete" );
if( response.length >= 3 && response[ 1 ] instanceof Number && response[ 2 ] instanceof Boolean )
{
if( ((Number) response[ 1 ]).intValue() == taskID )
{
Object[] returnValues = new Object[ response.length - 3 ];
if( (Boolean) response[ 2 ] )
{
// Extract the return values from the event and return them
System.arraycopy( response, 3, returnValues, 0, returnValues.length );
return returnValues;
}
else
{
// Extract the error message from the event and raise it
if( response.length >= 4 && response[ 3 ] instanceof String )
{
throw new LuaException( (String) response[ 3 ] );
}
else
{
throw new LuaException();
}
}
}
}
}
}
}, method, arguments );
}
catch( InterruptedException e )
{
throw new OrphanedThread();
}
catch( LuaException e )
{
throw new LuaError( e.getMessage(), e.getLevel() );
}
catch( Throwable t )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error calling " + methodName + " on " + apiObject, t );
}
throw new LuaError( "Java Exception Thrown: " + t.toString(), 0 );
}
return toValues( results );
}
} );
final String methodName = methods[method];
table.rawset( methodName, new CobaltWrapperFunction( this, m_computer, object, method, methodName ) );
}
}
return table;
@@ -544,7 +365,7 @@ public class CobaltLuaMachine implements ILuaMachine
}
}
private Varargs toValues( Object[] objects )
Varargs toValues( Object[] objects )
{
if( objects == null || objects.length == 0 )
{
@@ -635,7 +456,7 @@ public class CobaltLuaMachine implements ILuaMachine
}
}
private static Object[] toObjects( Varargs values, int startIdx )
static Object[] toObjects( Varargs values, int startIdx )
{
int count = values.count();
Object[] objects = new Object[ count - startIdx + 1 ];
@@ -703,13 +524,23 @@ public class CobaltLuaMachine implements ILuaMachine
if( remaining <= 0 )
{
LuaValue s;
state.getCurrentThread().disableYield();
try
{
s = OperationHelper.call( state, func );
} catch (LuaError e)
}
catch( UnwindThrowable e )
{
throw new IOException( "Impossible yield within load" );
}
catch( LuaError e )
{
throw new IOException( e );
}
finally
{
state.getCurrentThread().enableYield();
}
if( s.isNil() )
{
@@ -735,4 +566,5 @@ public class CobaltLuaMachine implements ILuaMachine
return bytes[offset++];
}
}
}

View File

@@ -0,0 +1,292 @@
/*
* 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.lua;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaFunction;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.core.computer.Computer;
import org.squiddev.cobalt.*;
import org.squiddev.cobalt.debug.DebugState;
import org.squiddev.cobalt.function.VarArgFunction;
import java.util.ArrayDeque;
import java.util.Deque;
class CobaltWrapperFunction extends VarArgFunction implements Resumable<CobaltWrapperFunction.State>
{
private final CobaltLuaMachine machine;
private final Computer computer;
private final CobaltCallContext callContext;
private final ILuaObject delegate;
private final int method;
private final String methodName;
CobaltWrapperFunction( CobaltLuaMachine machine, Computer computer, ILuaObject delegate, int method, String methodName )
{
this.machine = machine;
this.computer = computer;
this.callContext = new CobaltCallContext( computer );
this.delegate = delegate;
this.method = method;
this.methodName = methodName;
}
@Override
public Varargs invoke( final LuaState state, Varargs args ) throws LuaError, UnwindThrowable
{
MethodResult future;
try
{
future = delegate.callMethod( callContext, method, CobaltLuaMachine.toObjects( args, 1 ) );
}
catch( LuaException e )
{
throw new LuaError( e.getMessage(), e.getLevel() );
}
catch( Exception e )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error calling " + methodName + " on " + delegate, e );
}
throw new LuaError( "Java Exception Thrown: " + e.toString(), 0 );
}
// Verify we've a "well formed" future
if( future == null )
{
ComputerCraft.log.error( "Null result from " + delegate );
throw new LuaError( "Java Exception Thrown: Null result" );
}
// Fast path for immediate results
if( future instanceof MethodResult.Immediate )
{
return machine.toValues( ((MethodResult.Immediate) future).getResult() );
}
State context = new State();
try
{
return runFuture( state, context, future );
}
catch( UnwindThrowable e )
{
// Push our state onto the stack if need-be. Normally this wouldn't be safe and we
// should do this at the very beginning, but we know that we won't be calling anything
// else which will push to the resume stack.
DebugState ds = state.debug.getDebugState();
state.debug.onCall( ds, this, context );
throw e;
}
}
@Override
public Varargs resume( LuaState state, State context, Varargs args ) throws LuaError, UnwindThrowable
{
Varargs result;
try
{
result = doResume( state, context, args );
}
catch( LuaError e )
{
state.debug.onReturn();
throw e;
}
catch( Exception e )
{
state.debug.onReturn();
throw new LuaError( e );
}
state.debug.onReturn();
return result;
}
private Varargs doResume( LuaState state, State context, Varargs args ) throws LuaError, UnwindThrowable
{
MethodResult future = context.pending;
if( future instanceof MethodResult.OnEvent )
{
MethodResult.OnEvent onEvent = (MethodResult.OnEvent) future;
if( !onEvent.isRaw() && args.first().toString().equals( "terminate" ) )
{
throw new LuaError( "Terminated", 0 );
}
return runCallback( state, context, CobaltLuaMachine.toObjects( args, 1 ) );
}
else if( future instanceof MethodResult.OnMainThread )
{
if( args.arg( 2 ).isNumber() && args.arg( 3 ).isBoolean() && args.arg( 2 ).toLong() == context.taskId )
{
if( args.arg( 3 ).toBoolean() )
{
// Extract the return values from the event and return them
return runFuture( state, context, context.taskResult );
}
else
{
// Extract the error message from the event and raise it
throw new LuaError( args.arg( 4 ) );
}
}
else
{
LuaThread.yield( state, ValueFactory.valueOf( "task_complete" ) );
throw new IllegalStateException( "Unreachable" );
}
}
else if( future instanceof MethodResult.WithLuaContext )
{
context.luaContext.resume( CobaltLuaMachine.toObjects( args, 1 ) );
return runCallback( state, context, context.luaContext.await( state, machine ) );
}
else
{
ComputerCraft.log.error( "Unknown method result " + future );
throw new LuaError( "Java Exception Thrown: Unknown method result" );
}
}
@Override
public Varargs resumeError( LuaState state, State context, LuaError error ) throws LuaError
{
state.debug.onReturn();
throw error;
}
private Varargs runFuture( LuaState state, State context, MethodResult future ) throws LuaError, UnwindThrowable
{
Deque<ILuaFunction> callbacks = context.callbacks;
while( true )
{
if( future instanceof MethodResult.AndThen )
{
MethodResult.AndThen then = ((MethodResult.AndThen) future);
// Thens are "unwrapped", being pushed onto a stack
if( callbacks == null ) callbacks = context.callbacks = new ArrayDeque<>();
callbacks.addLast( then.getCallback() );
future = then.getPrevious();
}
else if( future instanceof MethodResult.Immediate )
{
Object[] values = ((MethodResult.Immediate) future).getResult();
// Immediate values values will attempt to call the previous "then", or return if nothing
// else needs to be done.
ILuaFunction callback = callbacks == null ? null : callbacks.pollLast();
if( callback == null ) return machine.toValues( values );
future = runFunction( callback, values );
}
else if( future instanceof MethodResult.OnEvent )
{
MethodResult.OnEvent onEvent = (MethodResult.OnEvent) future;
// Mark this future as pending and yield
context.pending = future;
String filter = onEvent.getFilter();
LuaThread.yield( state, filter == null ? Constants.NIL : ValueFactory.valueOf( filter ) );
throw new IllegalStateException( "Unreachable" );
}
else if( future instanceof MethodResult.OnMainThread )
{
MethodResult.OnMainThread onMainThread = (MethodResult.OnMainThread) future;
// Mark this future as pending and yield
context.pending = future;
try
{
context.taskId = callContext.issueMainThreadTask( () -> {
context.taskResult = onMainThread.getTask().execute();
return null;
} );
}
catch( LuaException e )
{
throw new LuaError( e.getMessage(), e.getLevel() );
}
LuaThread.yield( state, ValueFactory.valueOf( "task_complete" ) );
throw new IllegalStateException( "Unreachable" );
}
else if( future instanceof MethodResult.WithLuaContext )
{
MethodResult.WithLuaContext withContext = (MethodResult.WithLuaContext) future;
// Mark this future as pending and execute on a separate thread.
context.pending = future;
CobaltLuaContext luaContext = context.luaContext = new CobaltLuaContext( computer, state );
luaContext.execute( withContext.getConsumer() );
return runCallback( state, context, luaContext.await( state, machine ) );
}
else
{
ComputerCraft.log.error( "Unknown method result " + future );
throw new LuaError( "Java Exception Thrown: Unknown method result" );
}
}
}
private Varargs runCallback( LuaState state, State context, Object[] args ) throws LuaError, UnwindThrowable
{
Deque<ILuaFunction> callbacks = context.callbacks;
ILuaFunction callback = callbacks == null ? null : callbacks.pollLast();
if( callback == null ) return machine.toValues( args );
return runFuture( state, context, runFunction( callback, args ) );
}
private MethodResult runFunction( ILuaFunction func, Object[] args ) throws LuaError
{
MethodResult result;
try
{
result = func.call( args );
}
catch( LuaException e )
{
throw new LuaError( e.getMessage(), e.getLevel() );
}
catch( Exception e )
{
if( ComputerCraft.logPeripheralErrors )
{
ComputerCraft.log.error( "Error calling " + methodName + " on " + delegate, e );
}
throw new LuaError( "Java Exception Thrown: " + e.toString(), 0 );
}
if( result == null )
{
ComputerCraft.log.error( "Null result from " + func );
throw new LuaError( "Java Exception Thrown: Null result" );
}
return result;
}
static class State
{
Deque<ILuaFunction> callbacks;
MethodResult pending;
CobaltLuaContext luaContext;
long taskId;
MethodResult taskResult;
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.lua;
/**
* A trivial way of signalling
*/
public final class Semaphore
{
private volatile boolean state = false;
public synchronized void signal()
{
state = true;
notify();
}
public synchronized void await() throws InterruptedException
{
while( !state ) wait();
state = false;
}
public synchronized boolean await( long timeout ) throws InterruptedException
{
if( !state )
{
wait( timeout );
if( !state ) return false;
}
state = false;
return true;
}
}

View File

@@ -56,7 +56,7 @@ public class Terminal
m_palette = new Palette();
}
public void reset()
public synchronized void reset()
{
m_cursorColour = 0;
m_cursorBackgroundColour = 15;
@@ -76,7 +76,7 @@ public class Terminal
return m_height;
}
public void resize( int width, int height )
public synchronized void resize( int width, int height )
{
if( width == m_width && height == m_height )
{
@@ -189,7 +189,7 @@ public class Terminal
return m_palette;
}
public void blit( String text, String textColour, String backgroundColour )
public synchronized void blit( String text, String textColour, String backgroundColour )
{
int x = m_cursorX;
int y = m_cursorY;
@@ -202,7 +202,7 @@ public class Terminal
}
}
public void write( String text )
public synchronized void write( String text )
{
int x = m_cursorX;
int y = m_cursorY;
@@ -215,7 +215,7 @@ public class Terminal
}
}
public void scroll( int yDiff )
public synchronized void scroll( int yDiff )
{
if( yDiff != 0 )
{
@@ -245,7 +245,7 @@ public class Terminal
}
}
public void clear()
public synchronized void clear()
{
for( int y = 0; y < m_height; ++y )
{
@@ -256,7 +256,7 @@ public class Terminal
m_changed = true;
}
public void clearLine()
public synchronized void clearLine()
{
int y = m_cursorY;
if( y >= 0 && y < m_height )
@@ -268,7 +268,7 @@ public class Terminal
}
}
public TextBuffer getLine( int y )
public synchronized TextBuffer getLine( int y )
{
if( y >= 0 && y < m_height )
{
@@ -277,7 +277,7 @@ public class Terminal
return null;
}
public void setLine( int y, String text, String textColour, String backgroundColour )
public synchronized void setLine( int y, String text, String textColour, String backgroundColour )
{
m_text[y].write( text );
m_textColour[y].write( textColour );
@@ -285,7 +285,7 @@ public class Terminal
m_changed = true;
}
public TextBuffer getTextColourLine( int y )
public synchronized TextBuffer getTextColourLine( int y )
{
if( y>=0 && y<m_height )
{
@@ -294,7 +294,7 @@ public class Terminal
return null;
}
public TextBuffer getBackgroundColourLine( int y )
public synchronized TextBuffer getBackgroundColourLine( int y )
{
if( y>=0 && y<m_height )
{
@@ -318,7 +318,7 @@ public class Terminal
m_changed = false;
}
public NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound )
public synchronized NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound )
{
nbttagcompound.setInteger( "term_cursorX", m_cursorX );
nbttagcompound.setInteger( "term_cursorY", m_cursorY );
@@ -338,7 +338,7 @@ public class Terminal
return nbttagcompound;
}
public void readFromNBT( NBTTagCompound nbttagcompound )
public synchronized void readFromNBT( NBTTagCompound nbttagcompound )
{
m_cursorX = nbttagcompound.getInteger( "term_cursorX" );
m_cursorY = nbttagcompound.getInteger( "term_cursorY" );

View File

@@ -0,0 +1,117 @@
package dan200.computercraft.core.tracking;
import dan200.computercraft.core.computer.Computer;
import gnu.trove.map.hash.TObjectLongHashMap;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
public class ComputerTracker
{
private final WeakReference<Computer> computer;
private final int computerId;
private long tasks;
private long totalTime;
private long maxTime;
private long serverCount;
private long serverTime;
private final TObjectLongHashMap<TrackingField> fields;
public ComputerTracker( Computer computer )
{
this.computer = new WeakReference<>( computer );
this.computerId = computer.getID();
this.fields = new TObjectLongHashMap<>();
}
ComputerTracker( ComputerTracker timings )
{
this.computer = timings.computer;
this.computerId = timings.computerId;
this.tasks = timings.tasks;
this.totalTime = timings.totalTime;
this.maxTime = timings.maxTime;
this.serverCount = timings.serverCount;
this.serverTime = timings.serverTime;
this.fields = new TObjectLongHashMap<>( timings.fields );
}
@Nullable
public Computer getComputer()
{
return computer.get();
}
public int getComputerId()
{
return computerId;
}
public long getTasks()
{
return tasks;
}
public long getTotalTime()
{
return totalTime;
}
public long getMaxTime()
{
return maxTime;
}
public long getAverage()
{
return totalTime / tasks;
}
void addTaskTiming( long time )
{
tasks++;
totalTime += time;
if( time > maxTime ) maxTime = time;
}
void addMainTiming( long time )
{
serverCount++;
serverTime += time;
}
void addValue( TrackingField field, long change )
{
synchronized( fields )
{
fields.adjustOrPutValue( field, change, change );
}
}
public long get( TrackingField field )
{
if( field == TrackingField.TASKS ) return tasks;
if( field == TrackingField.MAX_TIME ) return maxTime;
if( field == TrackingField.TOTAL_TIME ) return totalTime;
if( field == TrackingField.AVERAGE_TIME ) return tasks == 0 ? 0 : totalTime / tasks;
if( field == TrackingField.SERVER_COUNT ) return serverCount;
if( field == TrackingField.SERVER_TIME ) return serverTime;
synchronized( fields )
{
return fields.get( field );
}
}
public String getFormatted( TrackingField field )
{
return field.format( get( field ) );
}
}

View File

@@ -0,0 +1,49 @@
package dan200.computercraft.core.tracking;
import dan200.computercraft.core.computer.Computer;
public interface Tracker
{
@Deprecated
default void addTiming( Computer computer, long time )
{
}
/**
* Report how long a task executed on the computer thread took.
*
* Computer thread tasks include events or a computer being turned on/off.
*
* @param computer The computer processing this task
* @param time The time taken for this task.
*/
default void addTaskTiming( Computer computer, long time )
{
//noinspection deprecation
addTiming( computer, time );
}
/**
* Report how long a task executed on the server thread took.
*
* Server tasks include actions performed by peripherals.
*
* @param computer The computer processing this task
* @param time The time taken for this task.
*/
default void addServerTiming( Computer computer, long time )
{
}
/**
* Increment an arbitrary field by some value. Implementations may track how often this is called
* as well as the change, to compute some level of "average".
*
* @param computer The computer to increment
* @param field The field to increment.
* @param change The amount to increment said field by.
*/
default void addValue( Computer computer, TrackingField field, long change )
{
}
}

View File

@@ -0,0 +1,80 @@
package dan200.computercraft.core.tracking;
import dan200.computercraft.core.computer.Computer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
public class Tracking
{
static final AtomicInteger tracking = new AtomicInteger( 0 );
private static final Object lock = new Object();
private static final HashMap<UUID, TrackingContext> contexts = new HashMap<>();
private static final List<Tracker> trackers = new ArrayList<>();
public static TrackingContext getContext( UUID uuid )
{
synchronized( lock )
{
TrackingContext context = contexts.get( uuid );
if( context == null ) contexts.put( uuid, context = new TrackingContext() );
return context;
}
}
public static void add( Tracker tracker )
{
synchronized( lock )
{
trackers.add( tracker );
tracking.incrementAndGet();
}
}
public static void addTaskTiming( Computer computer, long time )
{
if( tracking.get() == 0 ) return;
synchronized( contexts )
{
for( TrackingContext context : contexts.values() ) context.addTaskTiming( computer, time );
for( Tracker tracker : trackers ) tracker.addTaskTiming( computer, time );
}
}
public static void addServerTiming( Computer computer, long time )
{
if( tracking.get() == 0 ) return;
synchronized( contexts )
{
for( TrackingContext context : contexts.values() ) context.addServerTiming( computer, time );
for( Tracker tracker : trackers ) tracker.addServerTiming( computer, time );
}
}
public static void addValue( Computer computer, TrackingField field, long change )
{
if( tracking.get() == 0 ) return;
synchronized( lock )
{
for( TrackingContext context : contexts.values() ) context.addValue( computer, field, change );
for( Tracker tracker : trackers ) tracker.addValue( computer, field, change );
}
}
public static void reset()
{
synchronized( lock )
{
contexts.clear();
trackers.clear();
tracking.set( 0 );
}
}
}

View File

@@ -0,0 +1,111 @@
package dan200.computercraft.core.tracking;
import com.google.common.collect.MapMaker;
import dan200.computercraft.core.computer.Computer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Tracks timing information about computers, including how long they ran for
* and the number of events they handled.
*
* Note that this <em>will</em> track computers which have been deleted (hence
* the presence of {@link #timingLookup} and {@link #timings}
*/
public class TrackingContext implements Tracker
{
private boolean tracking = false;
private final List<ComputerTracker> timings = new ArrayList<>();
private final Map<Computer, ComputerTracker> timingLookup = new MapMaker().weakKeys().makeMap();
public synchronized void start()
{
if( !tracking ) Tracking.tracking.incrementAndGet();
tracking = true;
timings.clear();
timingLookup.clear();
}
public synchronized boolean stop()
{
if( !tracking ) return false;
Tracking.tracking.decrementAndGet();
tracking = false;
timingLookup.clear();
return true;
}
public synchronized List<ComputerTracker> getImmutableTimings()
{
ArrayList<ComputerTracker> timings = new ArrayList<>( this.timings.size() );
for( ComputerTracker timing : this.timings ) timings.add( new ComputerTracker( timing ) );
return timings;
}
public synchronized List<ComputerTracker> getTimings()
{
return new ArrayList<>( timings );
}
@Override
public void addTaskTiming( Computer computer, long time )
{
if( !tracking ) return;
synchronized( this )
{
ComputerTracker computerTimings = timingLookup.get( computer );
if( computerTimings == null )
{
computerTimings = new ComputerTracker( computer );
timingLookup.put( computer, computerTimings );
timings.add( computerTimings );
}
computerTimings.addTaskTiming( time );
}
}
@Override
public void addServerTiming( Computer computer, long time )
{
if( !tracking ) return;
synchronized( this )
{
ComputerTracker computerTimings = timingLookup.get( computer );
if( computerTimings == null )
{
computerTimings = new ComputerTracker( computer );
timingLookup.put( computer, computerTimings );
timings.add( computerTimings );
}
computerTimings.addMainTiming( time );
}
}
@Override
public void addValue( Computer computer, TrackingField field, long change )
{
if( !tracking ) return;
synchronized( this )
{
ComputerTracker computerTimings = timingLookup.get( computer );
if( computerTimings == null )
{
computerTimings = new ComputerTracker( computer );
timingLookup.put( computer, computerTimings );
timings.add( computerTimings );
}
computerTimings.addValue( field, change );
}
}
}

View File

@@ -0,0 +1,88 @@
package dan200.computercraft.core.tracking;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.LongFunction;
public class TrackingField
{
private static final Map<String, TrackingField> fields = new HashMap<>();
public static final TrackingField TASKS = TrackingField.of( "tasks", "Tasks", x -> String.format( "%4d", x ) );
public static final TrackingField TOTAL_TIME = TrackingField.of( "total", "Total time", x -> String.format( "%7.1fms", x / 1e6 ) );
public static final TrackingField AVERAGE_TIME = TrackingField.of( "average", "Average time", x -> String.format( "%4.1fms", x / 1e6 ) );
public static final TrackingField MAX_TIME = TrackingField.of( "max", "Max time", x -> String.format( "%5.1fms", x / 1e6 ) );
public static final TrackingField SERVER_COUNT = TrackingField.of( "server_count", "Server task count", x -> String.format( "%4d", x ) );
public static final TrackingField SERVER_TIME = TrackingField.of( "server_time", "Server task time", x -> String.format( "%7.1fms", x / 1e6 ) );
public static final TrackingField PERIPHERAL_OPS = TrackingField.of( "peripheral", "Peripheral calls", TrackingField::formatDefault );
public static final TrackingField FS_OPS = TrackingField.of( "fs", "Filesystem operations", TrackingField::formatDefault );
public static final TrackingField TURTLE_OPS = TrackingField.of( "turtle", "Turtle operations", TrackingField::formatDefault );
public static final TrackingField HTTP_REQUESTS = TrackingField.of( "http", "HTTP requests", TrackingField::formatDefault );
public static final TrackingField HTTP_UPLOAD = TrackingField.of( "http_upload", "HTTP upload", TrackingField::formatBytes );
public static final TrackingField HTTP_DOWNLOAD = TrackingField.of( "http_download", "HTTT download", TrackingField::formatBytes );
public static final TrackingField WEBSOCKET_INCOMING = TrackingField.of( "websocket_incoming", "Websocket incoming", TrackingField::formatBytes );
public static final TrackingField WEBSOCKET_OUTGOING = TrackingField.of( "websocket_outgoing", "Websocket outgoing", TrackingField::formatBytes );
private final String id;
private final String displayName;
private final LongFunction<String> format;
public String id()
{
return id;
}
public String displayName()
{
return displayName;
}
private TrackingField( String id, String displayName, LongFunction<String> format )
{
this.id = id;
this.displayName = displayName;
this.format = format;
}
public String format( long value )
{
return format.apply( value );
}
public static TrackingField of( String id, String displayName, LongFunction<String> format )
{
TrackingField field = new TrackingField( id, displayName, format );
fields.put( id, field );
return field;
}
public static Map<String, TrackingField> fields()
{
return Collections.unmodifiableMap( fields );
}
private static String formatDefault( long value )
{
return String.format( "%6d", value );
}
/**
* So technically a kibibyte, but let's not argue here.
*/
private static final int KILOBYTE_SIZE = 1024;
private static final String SI_PREFIXES = "KMGT";
private static String formatBytes( long bytes )
{
if( bytes < 1024 ) return String.format( "%10d B", bytes );
int exp = (int) (Math.log( bytes ) / Math.log( KILOBYTE_SIZE ));
if( exp > SI_PREFIXES.length() ) exp = SI_PREFIXES.length();
return String.format( "%10.1f %siB", bytes / Math.pow( KILOBYTE_SIZE, exp ), SI_PREFIXES.charAt( exp - 1 ) );
}
}

View File

@@ -1,27 +1,40 @@
package dan200.computercraft.shared.command;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.tracking.ComputerTracker;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingContext;
import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.shared.command.framework.*;
import dan200.computercraft.shared.computer.core.ServerComputer;
import net.minecraft.command.CommandBase;
import net.minecraft.command.CommandException;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.function.Consumer;
import static dan200.computercraft.shared.command.framework.ChatHelpers.*;
public final class CommandComputerCraft extends CommandDelegate
{
public static final UUID SYSTEM_UUID = new UUID( 0, 0 );
private static final int DUMP_LIST_ID = 5373952;
private static final int DUMP_SINGLE_ID = 1844510720;
private static final int TRACK_ID = 373882880;
public CommandComputerCraft()
{
super( create() );
@@ -37,8 +50,8 @@ public final class CommandComputerCraft extends CommandDelegate
root.register( new SubCommandBase(
"dump", "[id]", "Display the status of computers.", UserLevel.OWNER_OP,
"Display the status of all computers or specific information about one computer. You can either specify the computer's instance " +
"id (e.g. 123) or computer id (e.g #123)."
"Display the status of all computers or specific information about one computer. You can specify the " +
"computer's instance id (e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\")."
)
{
@Override
@@ -46,19 +59,43 @@ public final class CommandComputerCraft extends CommandDelegate
{
if( arguments.size() == 0 )
{
TextTable table = new TextTable( "Instance", "Id", "On", "Position" );
TextTable table = new TextTable( DUMP_LIST_ID, "Computer", "On", "Position" );
int max = 50;
for( ServerComputer computer : ComputerCraft.serverComputerRegistry.getComputers() )
List<ServerComputer> computers = new ArrayList<>( ComputerCraft.serverComputerRegistry.getComputers() );
// Unless we're on a server, limit the number of rows we can send.
if( !(context.getSender() instanceof MinecraftServer) )
{
World world = context.getSender().getEntityWorld();
BlockPos pos = context.getSender().getPosition();
computers.sort( ( a, b ) -> {
if( a.getWorld() == b.getWorld() && a.getWorld() == world )
{
return Double.compare( a.getPosition().distanceSq( pos ), b.getPosition().distanceSq( pos ) );
}
else if( a.getWorld() == world )
{
return -1;
}
else if( b.getWorld() == world )
{
return 1;
}
else
{
return Integer.compare( a.getInstanceID(), b.getInstanceID() );
}
} );
}
for( ServerComputer computer : computers )
{
table.addRow(
linkComputer( computer ),
text( Integer.toString( computer.getID() ) ),
linkComputer( context, computer, computer.getID() ),
bool( computer.isOn() ),
linkPosition( context, computer )
);
if( max-- < 0 ) break;
}
table.displayTo( context.getSender() );
@@ -67,7 +104,7 @@ public final class CommandComputerCraft extends CommandDelegate
{
ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) );
TextTable table = new TextTable();
TextTable table = new TextTable( DUMP_SINGLE_ID );
table.addRow( header( "Instance" ), text( Integer.toString( computer.getInstanceID() ) ) );
table.addRow( header( "Id" ), text( Integer.toString( computer.getID() ) ) );
table.addRow( header( "Label" ), text( computer.getLabel() ) );
@@ -80,7 +117,7 @@ public final class CommandComputerCraft extends CommandDelegate
IPeripheral peripheral = computer.getPeripheral( i );
if( peripheral != null )
{
table.addRow( header( "Peripheral " + Computer.s_sideNames[ i ] ), text( peripheral.getType() ) );
table.addRow( header( "Peripheral " + Computer.s_sideNames[i] ), text( peripheral.getType() ) );
}
}
@@ -98,39 +135,28 @@ public final class CommandComputerCraft extends CommandDelegate
{
return arguments.size() == 1
? ComputerSelector.completeComputer( arguments.get( 0 ) )
: Collections.<String>emptyList();
: Collections.emptyList();
}
} );
root.register( new SubCommandBase(
"shutdown", "[ids...]", "Shutdown computers remotely.", UserLevel.OWNER_OP,
"Shutdown the listed computers or all if none are specified. You can either specify the computer's instance " +
"id (e.g. 123) or computer id (e.g #123)."
"Shutdown the listed computers or all if none are specified. You can specify the computer's instance id " +
"(e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\")."
)
{
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
{
List<ServerComputer> computers = Lists.newArrayList();
if( arguments.size() > 0 )
{
for( String arg : arguments )
withComputers( arguments, computers -> {
int shutdown = 0;
for( ServerComputer computer : computers )
{
computers.add( ComputerSelector.getComputer( 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
@@ -138,7 +164,37 @@ public final class CommandComputerCraft extends CommandDelegate
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
{
return arguments.size() == 0
? Collections.<String>emptyList()
? Collections.emptyList()
: ComputerSelector.completeComputer( arguments.get( arguments.size() - 1 ) );
}
} );
root.register( new SubCommandBase(
"turn-on", "ids...", "Turn computers on remotely.", UserLevel.OWNER_OP,
"Turn on the listed computers. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) " +
"or label (e.g. \"@My Computer\")."
)
{
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
{
withComputers( arguments, computers -> {
int on = 0;
for( ServerComputer computer : computers )
{
if( !computer.isOn() ) on++;
computer.turnOn();
}
context.getSender().sendMessage( text( "Turned on " + on + " / " + computers.size() + " computers" ) );
} );
}
@Nonnull
@Override
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
{
return arguments.size() == 0
? Collections.emptyList()
: ComputerSelector.completeComputer( arguments.get( arguments.size() - 1 ) );
}
} );
@@ -194,48 +250,179 @@ public final class CommandComputerCraft extends CommandDelegate
{
return arguments.size() == 1
? ComputerSelector.completeComputer( arguments.get( 0 ) )
: Collections.<String>emptyList();
: Collections.emptyList();
}
} );
root.register(new SubCommandBase(
root.register( new SubCommandBase(
"view", "<id>", "View the terminal of a computer.", UserLevel.OP,
"Open the terminal of a computer, allowing remote control of a computer. This does not provide access to " +
"turtle's inventories. You can either specify the computer's instance id (e.g. 123) or computer id (e.g #123)."
) {
)
{
@Override
public void execute(@Nonnull CommandContext context, @Nonnull List<String> arguments) throws CommandException {
if (arguments.size() != 1) throw new CommandException(context.getFullUsage());
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
{
if( arguments.size() != 1 ) throw new CommandException( context.getFullUsage() );
ICommandSender sender = context.getSender();
if (!(sender instanceof EntityPlayerMP)) {
throw new CommandException("Cannot open terminal for non-player");
if( !(sender instanceof EntityPlayerMP) )
{
throw new CommandException( "Cannot open terminal for non-player" );
}
ServerComputer computer = ComputerSelector.getComputer(arguments.get(0));
ServerComputer computer = ComputerSelector.getComputer( arguments.get( 0 ) );
ComputerCraft.openComputerGUI( (EntityPlayerMP) sender, computer );
}
@Nonnull
@Override
public List<String> getCompletion(@Nonnull CommandContext context, @Nonnull List<String> arguments) {
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
{
return arguments.size() == 1
? ComputerSelector.completeComputer( arguments.get( 0 ) )
: Collections.emptyList();
}
});
} );
CommandRoot track = new CommandRoot( "track", "Track execution times for computers.",
"Track how long computers execute for, as well as how many events they handle. This presents information in " +
"a similar way to /forge track and can be useful for diagnosing lag." );
root.register( track );
track.register( new SubCommandBase(
"start", "Start tracking all computers", UserLevel.OWNER_OP,
"Start tracking all computers' execution times and event counts. This will discard the results of previous runs."
)
{
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments )
{
getTimingContext( context ).start();
String stopCommand = "/" + context.parent().getFullPath() + " stop";
context.getSender().sendMessage( list(
text( "Run " ),
link( text( stopCommand ), stopCommand, "Click to stop tracking" ),
text( " to stop tracking and view the results" )
) );
}
} );
track.register( new SubCommandBase(
"stop", "Stop tracking all computers", UserLevel.OWNER_OP,
"Stop tracking all computers' events and execution times"
)
{
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
{
TrackingContext timings = getTimingContext( context );
if( !timings.stop() ) throw new CommandException( "Tracking not enabled" );
displayTimings( context, timings.getImmutableTimings(), TrackingField.AVERAGE_TIME );
}
} );
track.register( new SubCommandBase(
"dump", "[kind]", "Dump the latest track results", UserLevel.OWNER_OP,
"Dump the latest results of computer tracking."
)
{
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments ) throws CommandException
{
TrackingField field = TrackingField.AVERAGE_TIME;
if( arguments.size() >= 1 )
{
field = TrackingField.fields().get( arguments.get( 0 ) );
if( field == null ) throw new CommandException( "Unknown field '" + arguments.get( 0 ) + "'" );
}
displayTimings( context, getTimingContext( context ).getImmutableTimings(), field );
}
@Nonnull
@Override
public List<String> getCompletion( @Nonnull CommandContext context, @Nonnull List<String> arguments )
{
if( arguments.size() == 1 )
{
String match = arguments.get( 0 );
List<String> out = new ArrayList<>();
for( String key : TrackingField.fields().keySet() )
{
if( CommandBase.doesStringStartWith( match, key ) ) out.add( key );
}
out.sort( Comparator.naturalOrder() );
return out;
}
else
{
return super.getCompletion( context, arguments );
}
}
} );
root.register( new SubCommandBase(
"reload", "Reload the ComputerCraft config file", UserLevel.OWNER_OP,
"Reload the ComputerCraft config file"
)
{
@Override
public void execute( @Nonnull CommandContext context, @Nonnull List<String> arguments )
{
ComputerCraft.loadConfig();
ComputerCraft.syncConfig();
context.getSender().sendMessage( new TextComponentString( "Reloaded config" ) );
}
} );
return root;
}
private static ITextComponent linkComputer( 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 )
@@ -253,4 +440,93 @@ public final class CommandComputerCraft extends CommandDelegate
return position( computer.getPosition() );
}
}
private static TrackingContext getTimingContext( CommandContext context )
{
Entity entity = context.getSender().getCommandSenderEntity();
if( entity instanceof EntityPlayerMP )
{
return Tracking.getContext( entity.getUniqueID() );
}
else
{
return Tracking.getContext( SYSTEM_UUID );
}
}
private static void displayTimings( CommandContext context, List<ComputerTracker> timings, TrackingField field ) throws CommandException
{
if( timings.isEmpty() ) throw new CommandException( "No timings available" );
Map<Computer, ServerComputer> lookup = new HashMap<>();
int maxId = 0, maxInstance = 0;
for( ServerComputer server : ComputerCraft.serverComputerRegistry.getComputers() )
{
lookup.put( server.getComputer(), server );
if( server.getInstanceID() > maxInstance ) maxInstance = server.getInstanceID();
if( server.getID() > maxId ) maxId = server.getID();
}
timings.sort( Comparator.<ComputerTracker, Long>comparing( x -> x.get( field ) ).reversed() );
boolean defaultLayout = field == TrackingField.TASKS || field == TrackingField.TOTAL_TIME
|| field == TrackingField.AVERAGE_TIME || field == TrackingField.MAX_TIME;
TextTable table = defaultLayout
? new TextTable( TRACK_ID, "Computer", "Tasks", "Total", "Average", "Maximum" )
: new TextTable( TRACK_ID, "Computer", field.displayName() );
for( ComputerTracker entry : timings )
{
Computer computer = entry.getComputer();
ServerComputer serverComputer = computer == null ? null : lookup.get( computer );
ITextComponent computerComponent = linkComputer( context, serverComputer, entry.getComputerId() );
if( defaultLayout )
{
table.addRow(
computerComponent,
text( entry.getFormatted( TrackingField.TASKS ) ),
text( entry.getFormatted( TrackingField.TOTAL_TIME ) ),
text( entry.getFormatted( TrackingField.AVERAGE_TIME ) ),
text( entry.getFormatted( TrackingField.MAX_TIME ) )
);
}
else
{
table.addRow( computerComponent, text( entry.getFormatted( field ) ) );
}
}
table.displayTo( context.getSender() );
}
private static void withComputers( List<String> selectors, Consumer<Collection<ServerComputer>> action ) throws CommandException
{
Set<ServerComputer> computers = Sets.newHashSet();
List<String> failed = new ArrayList<>();
if( selectors.isEmpty() )
{
computers.addAll( ComputerCraft.serverComputerRegistry.getComputers() );
}
else
{
for( String selector : selectors )
{
List<ServerComputer> selected = ComputerSelector.getComputers( selector );
computers.addAll( selected );
if( selected.isEmpty() ) failed.add( selector );
}
}
action.accept( computers );
if( !failed.isEmpty() )
{
throw new CommandException( "Could not find computers matching " + String.join( ", ", failed ) );
}
}
}

View File

@@ -3,15 +3,51 @@ package dan200.computercraft.shared.command;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import net.minecraft.command.CommandException;
import java.util.List;
import java.util.Set;
import java.util.*;
import java.util.function.Predicate;
public final class ComputerSelector
{
private static List<ServerComputer> getComputers( Predicate<ServerComputer> predicate ) throws CommandException
{
// We copy it to prevent concurrent modifications.
ArrayList<ServerComputer> computers = new ArrayList<>( ComputerCraft.serverComputerRegistry.getComputers() );
computers.removeIf( predicate.negate() );
return computers;
}
public static ServerComputer getComputer( String selector ) throws CommandException
{
List<ServerComputer> computers = getComputers( selector );
if( computers.size() == 0 )
{
throw new CommandException( "No computer matching " + selector );
}
else if( computers.size() == 1 )
{
return computers.get( 0 );
}
else
{
StringBuilder builder = new StringBuilder( "Multiple computers matching " )
.append( selector ).append( " (instances " );
for( int i = 0; i < computers.size(); i++ )
{
if( i > 0 ) builder.append( ", " );
builder.append( computers.get( i ).getInstanceID() );
}
builder.append( ")" );
throw new CommandException( builder.toString() );
}
}
public static List<ServerComputer> getComputers( String selector ) throws CommandException
{
if( selector.length() > 0 && selector.charAt( 0 ) == '#' )
{
@@ -27,49 +63,17 @@ public final class ComputerSelector
throw new CommandException( "'" + selector + "' is not a valid number" );
}
// 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( searchComputer.getID() == id )
{
candidates.add( searchComputer );
}
}
if( candidates.size() == 0 )
{
throw new CommandException( "No such computer for id " + id );
}
else if( candidates.size() == 1 )
{
return candidates.get( 0 );
}
else
{
StringBuilder builder = new StringBuilder( "Multiple computers with id " )
.append( id ).append( " (instances " );
boolean first = true;
for( ServerComputer computer : candidates )
{
if( first )
{
first = false;
}
else
{
builder.append( ", " );
}
builder.append( computer.getInstanceID() );
}
builder.append( ")" );
throw new CommandException( builder.toString() );
}
return getComputers( x -> x.getID() == id );
}
else if( selector.length() > 0 && selector.charAt( 0 ) == '@' )
{
String label = selector.substring( 1 );
return getComputers( x -> Objects.equals( label, x.getLabel() ) );
}
else if( selector.length() > 0 && selector.charAt( 0 ) == '~' )
{
String familyName = selector.substring( 1 );
return getComputers( x -> x.getFamily().name().equalsIgnoreCase( familyName ) );
}
else
{
@@ -84,20 +88,13 @@ 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 computer;
}
return computer == null ? Collections.emptyList() : Collections.singletonList( computer );
}
}
public static List<String> completeComputer( String selector )
{
Set<String> options = Sets.newHashSet();
TreeSet<String> options = Sets.newTreeSet();
// We copy it to prevent concurrent modifications.
List<ServerComputer> computers = Lists.newArrayList( ComputerCraft.serverComputerRegistry.getComputers() );
@@ -112,6 +109,26 @@ public final class ComputerSelector
if( id.startsWith( selector ) ) options.add( "#" + id );
}
}
else if( selector.length() > 0 && selector.charAt( 0 ) == '@' )
{
String label = selector.substring( 1 );
for( ServerComputer computer : computers )
{
String thisLabel = computer.getLabel();
if( thisLabel != null && thisLabel.startsWith( label ) ) options.add( "@" + thisLabel );
}
}
else if( selector.length() > 0 && selector.charAt( 0 ) == '~' )
{
String familyName = selector.substring( 1 ).toLowerCase( Locale.ENGLISH );
for( ComputerFamily family : ComputerFamily.values() )
{
if( family.name().toLowerCase( Locale.ENGLISH ).startsWith( familyName ) )
{
options.add( "~" + family.name() );
}
}
}
else
{
for( ServerComputer computer : computers )
@@ -121,6 +138,20 @@ public final class ComputerSelector
}
}
return Lists.newArrayList( options );
if( options.size() > 100 )
{
ArrayList<String> result = Lists.newArrayListWithCapacity( 100 );
for( String element : options )
{
if( result.size() > 100 ) break;
result.add( element );
}
return result;
}
else
{
return Lists.newArrayList( options );
}
}
}

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

@@ -36,7 +36,7 @@ public class CommandDelegate implements ICommand
@Override
public String getUsage( @Nonnull ICommandSender sender )
{
return "/" + command.getName() + " " + command.getUsage( new CommandContext( sender.getServer(), sender, command ) );
return new CommandContext( sender.getServer(), sender, command ).getFullUsage();
}
@Nonnull

View File

@@ -86,7 +86,7 @@ public class CommandRoot implements ISubCommand
{
for( ISubCommand command : subCommands.values() )
{
if( command.checkPermission( context ) ) return true;
if( !(command instanceof SubCommandHelp) && command.checkPermission( context ) ) return true;
}
return false;
}
@@ -108,7 +108,7 @@ public class CommandRoot implements ISubCommand
ISubCommand command = subCommands.get( arguments.get( 0 ) );
if( command == null || !command.checkPermission( context ) )
{
throw new CommandException( getName() + " " + getUsage( context ) );
throw new CommandException( context.getFullUsage() );
}
command.execute( context.enter( command ), arguments.subList( 1, arguments.size() ) );

View File

@@ -0,0 +1,147 @@
package dan200.computercraft.shared.command.framework;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.common.util.FakePlayer;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured;
/**
* Adapated from Sponge's PaginationCalculator
*/
public class TextFormatter
{
private static final char PADDING_CHAR = '\u02cc';
/**
* Yoinked from FontRenderer
*
* @see net.minecraft.client.gui.FontRenderer#charWidth
* @see net.minecraft.client.gui.FontRenderer#getCharWidth(char)
*/
private static final String CHARACTERS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
private static final int[] CHAR_WIDTHS = new int[]{
6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4,
4, 6, 7, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1,
1, 2, 5, 6, 6, 6, 6, 3, 5, 5, 5, 6, 2, 6, 2, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 5, 6, 5, 6,
7, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 6, 6,
3, 6, 6, 6, 6, 6, 5, 6, 6, 2, 6, 5, 3, 6, 6, 6,
6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 5, 2, 5, 7, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 3, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6,
6, 3, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 2, 6, 6,
8, 9, 9, 6, 6, 6, 8, 8, 6, 8, 8, 8, 8, 8, 6, 6,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 5, 9, 9,
8, 7, 7, 8, 7, 8, 8, 8, 7, 8, 8, 7, 9, 9, 6, 7,
7, 7, 7, 7, 9, 6, 7, 8, 7, 6, 6, 9, 7, 6, 7, 1
};
private static final int[] EXTRA_CHARS = new int[]{
'\u20e2', '\u261b',
};
private static final byte[] EXTRA_WIDTHS = new byte[]{
8, 4,
};
public static int getWidth( int codePoint )
{
// Escape codes
if( codePoint == 167 ) return -1;
// Space and non-breaking space
if( codePoint == 32 || codePoint == 160 ) return 4;
// Built-in characters
int nonUnicodeIdx = CHARACTERS.indexOf( codePoint );
if( codePoint > 0 && nonUnicodeIdx != -1 ) return CHAR_WIDTHS[nonUnicodeIdx];
// Other special characters we use.
int extraIdx = Arrays.binarySearch( EXTRA_CHARS, codePoint );
if( extraIdx >= 0 ) return EXTRA_WIDTHS[extraIdx];
return 0;
}
public static int getWidth( ITextComponent component )
{
int total = 0;
if( component instanceof TextComponentString )
{
String contents = component.getUnformattedComponentText();
int bold = component.getStyle().getBold() ? 1 : 0;
for( int i = 0; i < contents.length(); i++ )
{
int cp = contents.charAt( i );
assert cp != '\n';
int width = getWidth( cp );
if( width < 0 )
{
i++;
}
else
{
total += width + bold;
}
}
}
for( ITextComponent child : component.getSiblings() )
{
total += getWidth( child );
}
return total;
}
public static boolean isPlayer( ICommandSender sender )
{
return sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
}
public static int getMaxWidth( ICommandSender sender )
{
return isPlayer( sender ) ? 320 : 80;
}
public static int getWidthFor( ITextComponent component, ICommandSender sender )
{
return isPlayer( sender ) ? getWidth( component ) : component.getUnformattedText().length();
}
public static int getWidthFor( int codepoint, ICommandSender sender )
{
return isPlayer( sender ) ? getWidth( codepoint ) : 1;
}
public static void appendFixedWidth( ITextComponent out, ICommandSender sender, ITextComponent entry, int maxWidth )
{
out.appendSibling( entry );
int width = getWidthFor( entry, sender );
int delta = maxWidth - width;
int spaceWidth = getWidthFor( ' ', sender );
int spaces = delta / spaceWidth;
int extra = delta % spaces;
// Append a fixed number of spaces
if( spaces > 0 ) out.appendSibling( new TextComponentString( StringUtils.repeat( ' ', spaces ) ) );
// Append several minor characters to pad to a full string
if( extra > 0 )
{
out.appendSibling( coloured( StringUtils.repeat( PADDING_CHAR, extra ), TextFormatting.GRAY ) );
}
}
}

View File

@@ -1,117 +1,53 @@
package dan200.computercraft.shared.command.framework;
import com.google.common.collect.Lists;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.network.ComputerCraftPacket;
import net.minecraft.command.ICommandSender;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.TextComponentString;
import net.minecraft.util.text.TextFormatting;
import net.minecraftforge.common.util.FakePlayer;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import static dan200.computercraft.shared.command.framework.ChatHelpers.coloured;
import static dan200.computercraft.shared.command.framework.ChatHelpers.text;
import static dan200.computercraft.shared.command.framework.TextFormatter.*;
public class TextTable
{
private static final String CHARACTERS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
private static final int[] CHAR_WIDTHS = new int[] {
6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 4,
4, 6, 7, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1, 1, 1,
1, 2, 5, 6, 6, 6, 6, 3, 5, 5, 5, 6, 2, 6, 2, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 5, 6, 5, 6,
7, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 4, 6, 6,
3, 6, 6, 6, 6, 6, 5, 6, 6, 2, 6, 5, 3, 6, 6, 6,
6, 6, 6, 6, 4, 6, 6, 6, 6, 6, 6, 5, 2, 5, 7, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6, 3, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 6,
6, 3, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 2, 6, 6,
8, 9, 9, 6, 6, 6, 8, 8, 6, 8, 8, 8, 8, 8, 6, 6,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 6, 9, 9, 9, 5, 9, 9,
8, 7, 7, 8, 7, 8, 8, 8, 7, 8, 8, 7, 9, 9, 6, 7,
7, 7, 7, 7, 9, 6, 7, 8, 7, 6, 6, 9, 7, 6, 7, 1
};
private static final ITextComponent SEPARATOR = coloured( " | ", TextFormatting.GRAY );
private static final ITextComponent SEPARATOR = coloured( "| ", TextFormatting.GRAY );
private static final ITextComponent LINE = text( "\n" );
private static int getWidth( char character, ICommandSender sender )
{
if( sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer) )
{
// Use font widths here.
if( character == 167 )
{
return -1;
}
else if( character == 32 )
{
return 4;
}
else if( CHARACTERS.indexOf( character ) != -1 )
{
return CHAR_WIDTHS[ character ];
}
else
{
// Eh, close enough.
return 6;
}
}
else
{
return 1;
}
}
private static int getWidth( ITextComponent text, ICommandSender sender )
{
int sum = 0;
String chars = text.getUnformattedText();
for( int i = 0; i < chars.length(); i++ )
{
sum += getWidth( chars.charAt( i ), sender );
}
return sum;
}
private static boolean isPlayer( ICommandSender sender )
{
return sender instanceof EntityPlayerMP && !(sender instanceof FakePlayer);
}
private static int getMaxWidth( ICommandSender sender )
{
return isPlayer( sender ) ? 320 : 80;
}
private final int id;
private int columns = -1;
private final ITextComponent[] header;
private final List<ITextComponent[]> rows = Lists.newArrayList();
public TextTable( @Nonnull ITextComponent... header )
public TextTable( int id, @Nonnull ITextComponent... header )
{
this.id = id;
this.header = header;
this.columns = header.length;
}
public TextTable()
public TextTable( int id )
{
header = null;
this.id = id;
this.header = null;
}
public TextTable( @Nonnull String... header )
public TextTable( int id, @Nonnull String... header )
{
this.header = new ITextComponent[ header.length ];
this.id = id;
this.header = new ITextComponent[header.length];
for( int i = 0; i < header.length; i++ )
{
this.header[ i ] = ChatHelpers.header( header[ i ] );
this.header[i] = ChatHelpers.header( header[i] );
}
this.columns = header.length;
}
@@ -134,117 +70,101 @@ public class TextTable
{
if( columns <= 0 ) return;
final int maxWidth = getMaxWidth( sender );
int[] minWidths = new int[ columns ];
int[] maxWidths = new int[ columns ];
int[] rowWidths = new int[ columns ];
int[] maxWidths = new int[columns];
if( header != null )
{
for( int i = 0; i < columns; i++ )
{
maxWidths[ i ] = minWidths[ i ] = getWidth( header[ i ], sender );
maxWidths[i] = getWidthFor( header[i], sender );
}
}
for( ITextComponent[] row : rows )
// Limit the number of rows to fit within a single chat window on default Minecraft
// options.
int height = isPlayer( sender ) ? 18 : 100;
int limit = rows.size() <= height ? rows.size() : height - 1;
for( int y = 0; y < limit; y++ )
{
ITextComponent[] row = rows.get( y );
for( int i = 0; i < row.length; i++ )
{
int width = getWidth( row[ i ], sender );
rowWidths[ i ] += width;
if( width > maxWidths[ i ] )
{
maxWidths[ i ] = width;
}
int width = getWidthFor( row[i], sender );
if( width > maxWidths[i] ) maxWidths[i] = width;
}
}
// Calculate the average width
for( int i = 0; i < columns; i++ )
{
rowWidths[ i ] = Math.max( rowWidths[ i ], rows.size() );
}
// 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) * getWidth( SEPARATOR, sender );
int totalWidth = (columns - 1) * getWidthFor( SEPARATOR, sender );
for( int x : maxWidths ) totalWidth += x;
// TODO: Limit the widths of some entries if totalWidth > maxWidth
ITextComponent out = new TextComponentString( "" );
List<ITextComponent> out = new ArrayList<>();
if( header != null )
{
for( int i = 0; i < columns; i++ )
TextComponentString line = new TextComponentString( "" );
for( int i = 0; i < columns - 1; i++ )
{
if( i != 0 ) out.appendSibling( SEPARATOR );
appendFixed( out, sender, header[ i ], maxWidths[ i ] );
appendFixedWidth( line, sender, header[i], maxWidths[i] );
line.appendSibling( SEPARATOR );
}
out.appendSibling( LINE );
line.appendSibling( header[columns - 1] );
out.add( line );
// Round the width up rather than down
int rowCharWidth = getWidth( '=', sender );
int rowCharWidth = getWidthFor( '=', sender );
int rowWidth = totalWidth / rowCharWidth + (totalWidth % rowCharWidth == 0 ? 0 : 1);
out.appendSibling( coloured( StringUtils.repeat( '=', rowWidth ), TextFormatting.GRAY ) );
out.appendSibling( LINE );
out.add( coloured( StringUtils.repeat( '=', rowWidth ), TextFormatting.GRAY ) );
}
for( int i = 0; i < rows.size(); i++ )
for( int i = 0; i < limit; i++ )
{
TextComponentString line = new TextComponentString( "" );
ITextComponent[] row = rows.get( i );
if( i != 0 ) out.appendSibling( LINE );
for( int j = 0; j < columns; j++ )
for( int j = 0; j < columns - 1; j++ )
{
if( j != 0 ) out.appendSibling( SEPARATOR );
appendFixed( out, sender, row[ j ], maxWidths[ j ] );
appendFixedWidth( line, sender, row[j], maxWidths[j] );
line.appendSibling( SEPARATOR );
}
line.appendSibling( row[columns - 1] );
out.add( line );
}
sender.sendMessage( out );
}
private static void appendFixed( ITextComponent out, ICommandSender sender, ITextComponent entry, int maxWidth )
{
int length = getWidth( entry, sender );
int delta = length - maxWidth;
if( delta < 0 )
if( rows.size() > limit )
{
// Convert to overflow;
delta = -delta;
out.add( coloured( (rows.size() - limit) + " additional rows...", TextFormatting.AQUA ) );
}
// We have to remove some padding as there is a padding added between formatted and unformatted text
if( !entry.getStyle().isEmpty() && isPlayer( sender ) ) delta -= 1;
if( isPlayer( sender ) && id != 0 )
{
ComputerCraftPacket packet = new ComputerCraftPacket();
packet.m_packetType = ComputerCraftPacket.PostChat;
packet.m_dataInt = new int[]{ id };
out.appendSibling( entry );
int spaceWidth = getWidth( ' ', sender );
int spaces = delta / spaceWidth;
int missing = delta % spaceWidth;
spaces -= missing;
ITextComponent component = new TextComponentString( StringUtils.repeat( ' ', spaces < 0 ? 0 : spaces ) );
if( missing > 0 )
String[] lines = packet.m_dataString = new String[out.size()];
for( int i = 0; i < out.size(); i++ )
{
ITextComponent bold = new TextComponentString( StringUtils.repeat( ' ', missing ) );
bold.getStyle().setBold( true );
component.appendSibling( bold );
lines[i] = ITextComponent.Serializer.componentToJson( out.get( i ) );
}
out.appendSibling( component );
}
else if( delta > 0 )
{
out.appendSibling( entry );
ComputerCraft.sendToPlayer( (EntityPlayerMP) sender, packet );
}
else
{
out.appendSibling( entry );
// We have to add some padding as we expect a padding between formatted and unformatted text
// and there won't be.
if( entry.getStyle().isEmpty() && isPlayer( sender ) ) out.appendText( " " );
ITextComponent result = new TextComponentString( "" );
for( int i = 0; i < out.size(); i++ )
{
if( i > 0 ) result.appendSibling( LINE );
result.appendSibling( out.get( i ) );
}
sender.sendMessage( result );
}
}
}

View File

@@ -36,7 +36,7 @@ public abstract class BlockGeneric extends Block implements
protected BlockGeneric( Material material )
{
super( material );
this.isBlockContainer = true;
this.hasTileEntity = true;
}
protected abstract IBlockState getDefaultBlockState( int damage, EnumFacing placedSide );

View File

@@ -14,29 +14,28 @@ public class ClientTerminal implements ITerminal
private boolean m_colour;
private Terminal m_terminal;
private boolean m_terminalChanged;
private boolean m_terminalChangedLastFrame;
public ClientTerminal( boolean colour )
{
m_colour = colour;
m_terminal = null;
m_terminalChanged = false;
m_terminalChangedLastFrame = false;
}
public void update()
{
m_terminalChangedLastFrame = m_terminalChanged || (m_terminal != null && m_terminal.getChanged());
if( m_terminal != null )
{
m_terminalChanged |= m_terminal.getChanged();
m_terminal.clearChanged();
}
m_terminalChanged = false;
}
public boolean hasTerminalChanged()
public boolean pollTerminalChanged()
{
return m_terminalChangedLastFrame;
boolean changed = m_terminalChanged;
m_terminalChanged = false;
return changed;
}
// ITerminal implementation

View File

@@ -86,7 +86,7 @@ public class ColourableRecipe extends IForgeRegistryEntry.Impl<IRecipe> implemen
}
@Override
public boolean isHidden()
public boolean isDynamic()
{
return true;
}

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

@@ -8,9 +8,7 @@ package dan200.computercraft.shared.computer.apis;
import com.google.common.collect.ImmutableMap;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.block.Block;
@@ -24,6 +22,7 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
@@ -82,7 +81,7 @@ public class CommandAPI implements ILuaAPI
sender.clearOutput();
int result = commandManager.executeCommand( sender, command );
return new Object[]{ (result > 0), sender.copyOutput() };
return new Object[] { (result > 0), sender.copyOutput() };
}
catch( Throwable t )
{
@@ -90,7 +89,7 @@ public class CommandAPI implements ILuaAPI
{
ComputerCraft.log.error( "Error running command.", t );
}
return new Object[]{ false, createOutput( "Java Exception Thrown: " + t.toString() ) };
return new Object[] { false, createOutput( "Java Exception Thrown: " + t.toString() ) };
}
}
else
@@ -131,7 +130,8 @@ public class CommandAPI implements ILuaAPI
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
@Nonnull
public MethodResult callMethod( @Nonnull ICallContext context, int method, @Nonnull Object[] arguments ) throws LuaException
{
switch( method )
{
@@ -139,19 +139,19 @@ public class CommandAPI implements ILuaAPI
{
// exec
final String command = getString( arguments, 0 );
return context.executeMainThreadTask( () -> doCommand( command ) );
return MethodResult.onMainThread( () -> MethodResult.of( doCommand( command ) ) );
}
case 1:
{
// execAsync
final String command = getString( arguments, 0 );
long taskID = context.issueMainThreadTask( () -> doCommand( command ) );
return new Object[] { taskID };
return MethodResult.of( taskID );
}
case 2:
{
// list
return context.executeMainThreadTask( () ->
return MethodResult.onMainThread( () ->
{
int i = 1;
Map<Object, Object> result = new HashMap<>();
@@ -182,7 +182,7 @@ public class CommandAPI implements ILuaAPI
}
}
}
return new Object[]{ result };
return MethodResult.of( result );
} );
}
case 3:
@@ -190,7 +190,7 @@ public class CommandAPI implements ILuaAPI
// getBlockPosition
// This is probably safe to do on the Lua thread. Probably.
BlockPos pos = m_computer.getPos();
return new Object[] { pos.getX(), pos.getY(), pos.getZ() };
return MethodResult.of( pos.getX(), pos.getY(), pos.getZ() );
}
case 4:
{
@@ -201,7 +201,7 @@ public class CommandAPI implements ILuaAPI
final int maxx = getInt( arguments, 3 );
final int maxy = getInt( arguments, 4 );
final int maxz = getInt( arguments, 5 );
return context.executeMainThreadTask( () ->
return MethodResult.onMainThread( () ->
{
// Get the details of the block
World world = m_computer.getWorld();
@@ -236,7 +236,7 @@ public class CommandAPI implements ILuaAPI
}
}
}
return new Object[]{ results };
return MethodResult.of( results );
} );
}
case 5:
@@ -245,14 +245,14 @@ public class CommandAPI implements ILuaAPI
final int x = getInt( arguments, 0 );
final int y = getInt( arguments, 1 );
final int z = getInt( arguments, 2 );
return context.executeMainThreadTask( () ->
return MethodResult.onMainThread( () ->
{
// Get the details of the block
World world = m_computer.getWorld();
BlockPos position = new BlockPos( x, y, z );
if( WorldUtil.isBlockInWorld( world, position ) )
{
return new Object[]{ getBlockInfo( world, position ) };
return MethodResult.of( getBlockInfo( world, position ) );
}
else
{
@@ -262,8 +262,16 @@ public class CommandAPI implements ILuaAPI
}
default:
{
return null;
return MethodResult.empty();
}
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( (ICallContext) context, method, arguments ).evaluate( context );
}
}

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

@@ -6,11 +6,15 @@
package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.api.lua.ICallContext;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class ComputerPeripheral
implements IPeripheral
@@ -23,7 +27,7 @@ public class ComputerPeripheral
m_type = type;
m_computer = computer;
}
// IPeripheral implementation
@Nonnull
@@ -47,8 +51,9 @@ public class ComputerPeripheral
};
}
@Nonnull
@Override
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments )
public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ICallContext context, int method, @Nonnull Object[] arguments )
{
switch( method )
{
@@ -56,45 +61,58 @@ public class ComputerPeripheral
{
// turnOn
m_computer.turnOn();
return null;
return MethodResult.empty();
}
case 1:
{
// shutdown
m_computer.shutdown();
return null;
return MethodResult.empty();
}
case 2:
{
// reboot
m_computer.reboot();
return null;
return MethodResult.empty();
}
case 3:
{
// getID
return new Object[] {
m_computer.assignID()
};
return MethodResult.of( m_computer.assignID() );
}
case 4:
{
// isOn
return new Object[] { m_computer.isOn() };
return MethodResult.of( m_computer.isOn() );
}
case 5:
// getLabel
return new Object[] { m_computer.getLabel() };
return MethodResult.of( m_computer.getLabel() );
default:
{
return null;
return MethodResult.empty();
}
}
}
@Nullable
@Override
@Deprecated
public Object[] callMethod( @Nonnull IComputerAccess access, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return callMethod( access, (ICallContext) context, method, arguments ).evaluate( context );
}
@Override
public boolean equals( IPeripheral other )
{
return (other != null && other.getClass() == this.getClass());
}
@Nonnull
@Override
public Object getTarget()
{
return m_computer.getTile();
}
}

View File

@@ -94,6 +94,11 @@ public class ServerComputer extends ServerTerminal
return m_computer.getAPIEnvironment();
}
public Computer getComputer()
{
return m_computer;
}
@Override
public void update()
{
@@ -149,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() };
@@ -460,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

@@ -16,4 +16,5 @@ public interface IComputerItem
int getComputerID( @Nonnull ItemStack stack );
String getLabel( @Nonnull ItemStack stack );
ComputerFamily getFamily( @Nonnull ItemStack stack );
ItemStack withFamily(@Nonnull ItemStack stack, @Nonnull ComputerFamily family);
}

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

@@ -27,13 +27,13 @@ import javax.annotation.Nullable;
public class ItemComputer extends ItemComputerBase
{
public static int HIGHEST_DAMAGE_VALUE_ID = 16382;
public ItemComputer( Block block )
{
super( block );
setMaxStackSize( 64 );
setHasSubtypes( true );
setUnlocalizedName( "computercraft:computer" );
setTranslationKey( "computercraft:computer" );
setCreativeTab( ComputerCraft.mainCreativeTab );
}
@@ -87,7 +87,7 @@ public class ItemComputer extends ItemComputerBase
TileEntity tile = world.getTileEntity( pos );
if( tile != null && tile instanceof IComputerTile )
{
IComputerTile computer = (IComputerTile)tile;
IComputerTile computer = (IComputerTile) tile;
setupComputerAfterPlacement( stack, computer );
}
return true;
@@ -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 ) )
{
@@ -146,10 +146,16 @@ public class ItemComputer extends ItemComputerBase
else
{
int damage = stack.getItemDamage() & 0x3fff;
return ( damage - 1 );
return (damage - 1);
}
}
@Override
public ItemStack withFamily( @Nonnull ItemStack stack, @Nonnull ComputerFamily family )
{
return ComputerItemFactory.create( getComputerID( stack ), getLabel( stack ), family );
}
@Override
public ComputerFamily getFamily( int damage )
{

View File

@@ -0,0 +1,66 @@
package dan200.computercraft.shared.computer.recipe;
import dan200.computercraft.shared.computer.items.IComputerItem;
import net.minecraft.inventory.InventoryCrafting;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.item.crafting.ShapedRecipes;
import net.minecraft.world.World;
import net.minecraftforge.common.crafting.CraftingHelper;
import javax.annotation.Nonnull;
/**
* Represents a recipe which converts a computer from one form into another.
*/
public abstract class ComputerConvertRecipe extends ShapedRecipes
{
public ComputerConvertRecipe( String group, @Nonnull CraftingHelper.ShapedPrimer primer, @Nonnull ItemStack result )
{
super( group, primer.width, primer.height, primer.input, result );
}
@Nonnull
protected abstract ItemStack convert( @Nonnull ItemStack stack );
@Override
public boolean matches( @Nonnull InventoryCrafting inventory, @Nonnull World world )
{
// See if we match the recipe, and extract the input computercraft ID
ItemStack computerStack = null;
for( int y = 0; y < 3; ++y )
{
for( int x = 0; x < 3; ++x )
{
ItemStack stack = inventory.getStackInRowAndColumn( x, y );
Ingredient target = getIngredients().get( x + y * 3 );
// First verify we match the ingredient
if( !target.apply( stack ) ) return false;
// We want to ensure we have a computer item somewhere in the recipe
if( stack.getItem() instanceof IComputerItem ) computerStack = stack;
}
}
return computerStack != null;
}
@Nonnull
@Override
public ItemStack getCraftingResult( @Nonnull InventoryCrafting inventory )
{
for( int y = 0; y < 3; ++y )
{
for( int x = 0; x < 3; ++x )
{
ItemStack item = inventory.getStackInRowAndColumn( x, y );
// If we're a computer, convert!
if( item.getItem() instanceof IComputerItem ) return convert( item );
}
}
return ItemStack.EMPTY;
}
}

View File

@@ -0,0 +1,47 @@
package dan200.computercraft.shared.computer.recipe;
import com.google.gson.JsonObject;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.util.RecipeUtil;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.util.JsonUtils;
import net.minecraftforge.common.crafting.CraftingHelper;
import net.minecraftforge.common.crafting.IRecipeFactory;
import net.minecraftforge.common.crafting.JsonContext;
import javax.annotation.Nonnull;
public class ComputerFamilyRecipe extends ComputerConvertRecipe
{
private final ComputerFamily family;
public ComputerFamilyRecipe( String group, @Nonnull CraftingHelper.ShapedPrimer primer, @Nonnull ItemStack result, ComputerFamily family )
{
super( group, primer, result );
this.family = family;
}
@Nonnull
@Override
protected ItemStack convert( @Nonnull ItemStack stack )
{
return ((IComputerItem) stack.getItem()).withFamily( stack, family );
}
public static class Factory implements IRecipeFactory
{
@Override
public IRecipe parse( JsonContext context, JsonObject json )
{
String group = JsonUtils.getString( json, "group", "" );
ComputerFamily family = RecipeUtil.getFamily( json, "family" );
CraftingHelper.ShapedPrimer primer = RecipeUtil.getPrimer( context, json );
ItemStack result = deserializeItem( JsonUtils.getJsonObject( json, "result" ), false );
return new ComputerFamilyRecipe( group, primer, result, family );
}
}
}

View File

@@ -0,0 +1,66 @@
package dan200.computercraft.shared.computer.recipe;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.util.RecipeUtil;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.util.JsonUtils;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.crafting.IIngredientFactory;
import net.minecraftforge.common.crafting.JsonContext;
import net.minecraftforge.fml.common.registry.ForgeRegistries;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Represents an ingredient which requires an item to have a specific
* computer family. This allows us to have operations which only work
* on normal or
*/
public class ComputerIngredient extends Ingredient
{
private final IComputerItem item;
private final ComputerFamily family;
public <T extends Item & IComputerItem> ComputerIngredient( T item, int data, ComputerFamily family )
{
super( new ItemStack( item, 1, data ) );
this.item = item;
this.family = family;
}
@Override
public boolean apply( @Nullable ItemStack stack )
{
return stack != null && stack.getItem() == item && item.getFamily( stack ) == family;
}
public static class Factory implements IIngredientFactory
{
@Nonnull
@Override
public Ingredient parse( JsonContext context, JsonObject json )
{
String itemName = context.appendModId( JsonUtils.getString( json, "item" ) );
int data = JsonUtils.getInt( json, "data", 0 );
ComputerFamily family = RecipeUtil.getFamily( json, "family" );
Item item = ForgeRegistries.ITEMS.getValue( new ResourceLocation( itemName ) );
if( item == null ) throw new JsonSyntaxException( "Unknown item '" + itemName + "'" );
if( !(item instanceof IComputerItem) )
{
throw new JsonSyntaxException( "Item '" + itemName + "' is not a computer item" );
}
return new ComputerIngredient( (Item & IComputerItem) item, data, family );
}
}
}

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

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