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

Compare commits

..

252 Commits

Author SHA1 Message Date
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
SquidDev
abd06133fb More binary compatibility stubs for ILuaAPI 2017-12-07 09:24:44 +00:00
SquidDev
29a3a0c48f Bump version 2017-12-07 09:08:15 +00:00
SquidDev
2728c63512 Add back ILuaAPI
This ensures binary compatibility with all those evil mods (Plethora)
which CC internals.
2017-12-07 09:06:47 +00:00
SquidDev
f3b11bc1c2 Copy over CCTweaks's command system
This adds several commands which may be useful for server owners. It'd
be nice to integrate this into ComputerCraft itself, but the associated
command framework is quite large so we'd have to think about it.
2017-12-06 15:51:51 +00:00
SquidDev
04590befb3 Add support for viewing arbitrary computers in a GUI
Important terminal (such as terminal size) is packed into the the
coordinate fields.
2017-12-06 15:43:25 +00:00
SquidDev
4e9034f910 Make http.websocket call synchronous in bios.lua
I thought I'd done this already, but it's possible it got lost during a
rebase.
2017-12-06 09:28:38 +00:00
SquidDev
ba9cfa3764 Bump version
Also add CurseGradle support
2017-12-01 20:05:26 +00:00
SquidDev
341e3e2f89 Merge pull request #497 from SquidDev-CC/ComputerCraft/feature/pocket-map
Add map-like rendering for pocket computers
2017-12-01 19:41:07 +00:00
SquidDev
3f70ca5192 Merge pull request #492 from SquidDev-CC/ComputerCraft/feature/fun-turtle-rendering
Improve vertex transformation system
2017-12-01 19:39:33 +00:00
SquidDev
f11bfc53ee Use IComputer instead of ServerComputer on the client 2017-12-01 19:32:15 +00:00
SquidDev
61e3967b8e Merge pull request #494 from SquidDev-CC/ComputerCraft/hotfix/collision-aabb
Fix getCollisionBoundingBox not using all AABBs
2017-12-01 19:19:17 +00:00
SquidDev
add86ea100 Merge pull request #491 from SquidDev-CC/ComputerCraft/feature/api-api
Provide an API for registering custom APIs
2017-12-01 19:18:40 +00:00
SquidDev
dd51c89278 Add map-like rendering for pocket computers 2017-12-01 11:28:15 +00:00
SquidDev
788d783745 Fix getCollisionBoundingBox not using all AABBs
Closes #493
2017-11-22 10:52:28 +00:00
SquidDev
35da60543e Improve turtles by 200%
Every other mod has some fun feature, so should we. And yes, this was
worth the 400 lines it took to implement.
2017-11-21 00:34:35 +00:00
SquidDev
ce7923d248 Improve vertex transformation system
This migrates TurtleMultiModel's current vertex transformation system
into something more powerful and "correct". Namely, it has the following
improvements:

 - Handles all position formats (float, byte, etc...)
 - Correctly translates normals of quads
 - Reorders faces if the winding order is reversed
2017-11-21 00:18:03 +00:00
SquidDev
55847460c5 Provide an API for registering custom APIs
ILuaAPI has been moved to dan200.computercraft.api.lua. One creates
a new API by registering an instance of ILuaAPIFactory. This takes an
instance of IComputerSystem and returns such an API.

IComputerSystem is an extension of IComputerAccess, with methods to
access additional information about the the computer, such as its label
and filesystem.
2017-11-19 18:23:38 +00:00
SquidDev
893524b0a8 Mark computers as changed when changing on state
Previously they were not marked as such, meaning computer state was not
broadcast to the client until blinking state changed.
2017-11-19 15:23:12 +00:00
SquidDev
8fb3ae405f Ensure we don't strip any whitespace 2017-11-19 15:04:21 +00:00
SquidDev
aa447ec101 Fix term.getTextScale() not using the main monitor 2017-11-19 14:03:48 +00:00
SquidDev
56b1cb4521 Fixup README a little 2017-11-19 13:48:17 +00:00
SquidDev
90cc24614c Add a subjectively fancy logo 2017-11-15 18:20:22 +00:00
SquidDev
d7301ff15e Merge pull request #412 from Wilma456/ComputerCraft-1/textfix
Add Check to textutils.tabulate/pagedTabulate
2017-11-15 16:58:13 +00:00
SquidDev
1cf10c5c47 Merge pull request #490 from zardyh/ComputerCraft/master
Propagate errors arising from API loading
2017-11-15 16:51:26 +00:00
SquidDev
6691ec8e3a Merge pull request #390 from Wilma456/ComputerCraft-1/errormsg
Show fs error in paint and edit
2017-11-15 16:39:37 +00:00
SquidDev
a9f77221ff Merge pull request #469 from Wilma456/ComputerCraft-1/newrecipe
Add more Recipes to Recipebook
2017-11-15 16:35:27 +00:00
SquidDev
dd3b69a633 Rebranding!
I feel kinda guilty about this, but it's probably a good idea to make it
clear that this isn't "actual, proper, stable" ComputerCraft.
2017-11-15 16:25:10 +00:00
hydraz
d766f8b34e Propagate errors arising from API loading 2017-11-15 14:22:36 -02:00
SquidDev
2ae6fb47e7 Move CommandComputer into a child package
Means we can be a little more organised where we put the additional
commands.
2017-11-15 15:57:10 +00:00
SquidDev
dd5698241b Add support for running multiple computers at the same time
- ComputerThread constructs multiple threads instead of just one,
   depending on a config options.
 - The synchronized blocks of PeripheralAPI.PeripheralWrapper have been
   shifted a little to ensure no deadlocks occur.
2017-11-15 13:30:40 +00:00
SquidDev
ed8e9d7817 Add support for enabling Lua's debug library
Whilst I'm pretty sure this is safe for general use, I'm disabling this
by default for now. I may consider enabling it in the future if no
issues are found.
2017-11-15 12:18:10 +00:00
SquidDev
6c29b44c3c Merge pull request #440 from Wilma456/ComputerCraft-1/iomulti
Make io.write() accept multiple args
2017-11-15 11:47:33 +00:00
SquidDev
0caa133089 Merge pull request #454 from SquidDev-CC/ComputerCraft/hotfix/lazy-computer-peripheral
[WIP] Only instantiate ServerComputer on tile ticks
2017-11-15 11:42:54 +00:00
SquidDev
a8b08bd971 Remove apis.HTTPRequest
I evidently duplicated this during some rebase, more fool me.
2017-11-15 11:39:48 +00:00
SquidDev
c9181a121f Merge pull request #395 from SquidDev-CC/ComputerCraft/feature/websocket
Websocket support
2017-11-15 11:39:02 +00:00
SquidDev
30f4e0829f Add websocket support to HTTP API
This uses Netty's websocket functionality, meaning we do not have to
depend on another library.

As websockets do not fit neatly into the standard polling socket model,
the API is significantly more event based than CCTweaks's. One uses
http.websocket to connect, which will wait until a connection is
established and then returns the connection object (an async variant is
available).

Once you have a websocket object, you can use .send(msg) to transmit a
message. Incoming messages will fire a "websocket_message" event, with
the URL and content as arguments. A convenience method (.receive())
exists to aid waiting for valid messages.
2017-11-15 11:32:17 +00:00
SquidDev
2155fce036 Merge pull request #486 from Wilma456/ComputerCraft-1/extensionfix
Fix Bug in Paint and Edit
2017-11-14 23:55:14 +00:00
SquidDev
27602ec8fc Merge pull request #485 from Luca0208/ComputerCraft/patch-1
Removed the "the" that was too much(In /rom/help/cd.txt)
2017-11-14 23:54:54 +00:00
SquidDev
66f683d9c9 Merge pull request #475 from Wilma456/ComputerCraft-1/ioline
Fix io.lines()
2017-11-14 23:53:50 +00:00
SquidDev
ac08a52323 Merge pull request #480 from Wilma456/ComputerCraft-1/monitorscale
Add getTextScale() to Monitor
2017-11-14 23:53:04 +00:00
SquidDev
fe0f998c27 Merge pull request #448 from Wilma456/ComputerCraft-1/writecheck
Fix check of write()
2017-11-14 23:50:12 +00:00
Wilma456
bcf79165f9 Merge pull request #455 from Wilma456/ComputerCraft-1/fileread
Add read() to Filehandle
2017-11-14 23:48:38 +00:00
Steven Dirth
9b2a50cdfc Merge pull request #362 from KingofGamesYami/ComputerCraft/featurecommand-event
Command Event
2017-11-14 23:27:11 +00:00
Wilma456
c5d99db654 Merge pull request #411 from Wilma456/ComputerCraft-1/copyfixup
Fix Bug in copy.lua, mkdir.lua and rename.lua (updated)
2017-11-14 23:24:11 +00:00
SquidDev
5253ab3e58 Merge pull request #463 from josephcsible/ComputerCraft/notnull
Remove some unnecessary null checks
2017-11-14 22:58:36 +00:00
SquidDev
11d8253d9c Merge pull request #464 from josephcsible/ComputerCraft/unnecessary
Remove unnecessary code
2017-11-14 22:57:36 +00:00
SquidDev
bc2b481918 Merge pull request #289 from Wojbie/ComputerCraft/Speaker-pocket-computer-light
Add pocket computer light support to Speaker.
2017-11-14 22:46:01 +00:00
SquidDev
b26564ccb9 Update README to explain what this project is
Also add a .editorconfig file to ensure some level of consistent
formatting.
2017-11-14 22:42:07 +00:00
SquidDev
28e3ffe978 Update Gradle and build system
- Bundle Javadoc and APIs using gradle instead of deploy.sh
 - Bump Gradle to 4.3, significantly improving compile times.
2017-11-14 22:42:03 +00:00
SquidDev
540e2e25aa Merge pull request #163 from SquidDev-CC/ComputerCraft/feature/cobalt
Replace LuaJ with Cobalt
2017-11-14 21:48:47 +00:00
SquidDev
845118e9e2 Merge pull request #218 from SquidDev-CC/ComputerCraft/feature/new-computer-thread
Rewrite the computer thread system
2017-11-14 21:33:20 +00:00
SquidDev
b2b8753ee7 Merge pull request #227 from SquidDev-CC/ComputerCraft/feature/improved-cable
Improving cable/wired modem interactions
2017-11-14 21:32:53 +00:00
SquidDev
060fb21bdb Merge pull request #298 from SquidDev-CC/ComputerCraft/feature/luaj-bit32
Replace BitAPI with a LuaJ implementation of bit32
2017-11-14 21:32:19 +00:00
SquidDev
ef008709c7 Merge pull request #402 from SquidDev-CC/ComputerCraft/feature/shell-resolution
Tweak shell program resolution slightly
2017-11-14 21:31:10 +00:00
SquidDev
09da119f27 Merge pull request #451 from SquidDev-CC/ComputerCraft/hotfix/disk-drive-stop
Use custom packet to play records, instead of using block events
2017-11-14 21:30:52 +00:00
SquidDev
f8487d1e1c Merge pull request #453 from SquidDev-CC/ComputerCraft/hotfix/eager-remove-te
Remove tile before calling destroy
2017-11-14 21:15:47 +00:00
SquidDev
b6f773ffce Merge pull request #457 from SquidDev-CC/ComputerCraft/hotfix/computer-reload
Turn on ServerComputer instances if they have timed out
2017-11-14 21:14:49 +00:00
SquidDev
c8673473ef Merge pull request #476 from SquidDev-CC/ComputerCraft/hotfix/printer-clear
Fix the printer overwriting the current page
2017-11-14 21:14:10 +00:00
SquidDev
3829815756 Merge pull request #479 from SquidDev-CC/ComputerCraft/feature/network-optimisations
Only send terminal state to interacting players
2017-11-14 21:13:17 +00:00
SquidDev
7eac8faf0d Merge pull request #482 from SquidDev-CC/ComputerCraft/feature/jei-integration
Add simple JEI integration
2017-11-14 21:06:02 +00:00
SquidDev
0bd0f4d313 Prefix all loaded strings with "="
Whilst this is not consistent with normal Lua, this is required in order
to remain compatible with LuaJ.
2017-11-14 18:41:01 +00:00
SquidDev
73873eb8cb Change timeout system to occur on instructions instead of API calls
This means loops which do not touch CC specific methods will still
produce an error, rather than terminating the computer.
2017-11-14 18:41:00 +00:00
SquidDev
0420b6c831 Remove string metatable protection
The string metatable and environment are no longer shared, so this
sandboxing is no longer required.
2017-11-14 18:41:00 +00:00
SquidDev
bb741975b7 Migrate LuaJLuaMachine to use Cobalt 2017-11-14 18:41:00 +00:00
SquidDev
8bffec6964 Add dependency on Cobalt 2017-11-14 18:40:57 +00:00
Wilma456
9e19dd7070 Fix Bug in Paint and Edit 2017-11-02 20:14:34 +01:00
Luca S
aba0e3d2d4 Removed the "the" that was too much 2017-10-29 19:06:34 +01:00
SquidDev
d9d025e33b Add simple JEI integration
- Ensure pocket computers and turtles are distinguished by upgrades and
   computer family.
 - Ensure disks are distinguished by colour.
 - Hide treasure disks from the list
2017-10-25 13:40:35 +01:00
Wilma456 (Jakob0815)
1fe29ab098 Add getTextScale() to Monitor 2017-10-13 12:37:55 +02:00
SquidDev
53f16782ab Only send terminal state to interacting players
This splits the computer state (blinking, label, etc...) and terminal
state into two separate packets. When a computer changes, the computer
state is sent to all players and the terminal state is sent to players
who are curerntly using the computer.

This reduces network usage by a substantial amount.
2017-10-12 10:45:38 +01:00
SquidDev
bfb4f88304 Fix the printer clearing the previous page
When printing on top of an already printed page, the previous contents
should be preserved. However, this did not occur as the stack had been
shrunk and so the item was no longer considered a printout.

Closes SquidDev-CC/ComputerCraft#2
2017-10-06 12:04:49 +01:00
SquidDev
fb6d65ec23 Invalidate the network when the peripheral is removed
Fixes #83
2017-10-04 21:49:41 +01:00
SquidDev
6b364052c7 Only render breaking animation on the part being hit 2017-10-04 21:49:40 +01:00
SquidDev
7169abcd7b Ensure the modem's peripheral is incorrectly invalidated when changed 2017-10-04 21:49:40 +01:00
SquidDev
75ccfbdb3d Migrate cable core block state to an enum
This allows us to render the cable "core", as was done pre-1.8.
2017-10-04 21:49:40 +01:00
SquidDev
728644c104 Initial attempt at improving cable/wired modem interactions
- Cable and modem can be broken individually
 - Ray tracing will go through "holes" in the cable.
 - Pick block will determine which part you are looking at.
 - Selection box will only highlight the region you are looking at:
   modem or cable.
2017-10-04 21:49:37 +01:00
Wilma456 (Jakob0815)
999351e667 Fix io.lines() 2017-10-04 18:51:48 +02:00
Wilma456
11e879db41 Removce Bow Upgrade Recipe 2017-09-29 18:56:52 +02:00
Wilma456
a4a774fcdf Add more Recipes to Recipebook 2017-09-27 20:08:48 +02:00
Wilma456
4fb0240a36 Changes suggested by SquidDev and update help file 2017-09-24 17:36:20 +02:00
Joseph C. Sible
80ec54eaf6 Remove unnecessary code
- Remove unnecessary casts
- Use the diamond operator where possible
- Remove "throws" declarations that aren't actually thrown
- Remove unused local variables
- Remove unused imports
- Remove redundant superinterfaces
2017-09-24 01:23:29 -04:00
Joseph C. Sible
9e4ae3a494 Remove some unnecessary null checks
We know turtle can't be null in any of these places, since in preceding code,
we called methods on it, so we would have gotten a NullPointerException then
and never gotten here if it were null.
2017-09-24 01:00:55 -04:00
Daniel Ratcliffe
19e4c03d3a Merge pull request #456 from Wilma456/ioerror
Set errorlevel for "Unsupported format" to 2
2017-09-22 13:20:34 +01:00
Daniel Ratcliffe
c6b8cb1fab Merge pull request #458 from SquidDev-CC/hotfix/block-shapes
Fix BlockFaceShape not being overridden for turtles and peripherals
2017-09-22 13:20:08 +01:00
Wilma456 (Jakob0815)
f20a7afa7f Better Code 2017-09-18 15:22:44 +02:00
SquidDev
01f5d006fc Fix BlockFaceShape not being overridden for turtles and peripherals
This meant one could perform various illogical actions to
non-full-blocks, such as connecting fences and placing paitings.

We also modify the behaviour of isOpaqueCube and isFullCube for
peripherals, only returning false for the case of modems and cables.
2017-09-18 08:33:40 +01:00
SquidDev
cd6b076efe Turn on ServerComputer instances if they have timed out 2017-09-16 20:09:51 +01:00
Wilma456 (Jakob0815)
f8193a4d23 Set errorlevel for "Unsupported format" to 2 2017-09-16 16:11:36 +02:00
Wilma456
5be2202b2e Add read() to Filehandle 2017-09-16 16:06:27 +02:00
SquidDev
fbbfe33e21 Do not instantiate ServerComputer instances in the peripheral provider
Instead we create a ComputerProxy, which delegates methods to the
ServerComputer or TileComputerBase, depending on which one exists.
2017-09-15 18:58:13 +01:00
SquidDev
7a916ed8c2 Do not instantiate a ServerComputer for pocket computers's mount 2017-09-15 18:48:57 +01:00
SquidDev
60305cd106 Remove tile before calling destroy
This ensures that the tile will updating neighbouring blocks, and so
the destroyed tile will not be wrapped as a peripheral.
2017-09-15 17:40:53 +01:00
Wilma456 (Jakob0815)
b8630f739a Add Check requested by dan200 2017-09-13 19:21:17 +02:00
Daniel Ratcliffe
1c8480a329 Merge pull request #441 from SquidDev-CC/hotfix/paintutils-read
Fix a non-existent method being used in paintutils
2017-09-13 17:10:07 +01:00
Daniel Ratcliffe
5219648128 Merge pull request #445 from SquidDev-CC/hotfix/container-backgrounds
Fix background and tooltips not rendering within containers
2017-09-13 00:43:10 +01: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 (Jakob0815)
282aa804f8 Changes sugested by dan200 2017-09-12 19:42:08 +02:00
Daniel Ratcliffe
e959051239 Merge pull request #447 from BombBloke/patch-1
Correct minor typo in rednet.receive
2017-09-12 18:14:34 +01:00
Wilma456 (Jakob0815)
373b7ba293 Fix check of write()
if you call write(nil), you will get the error "bios.lua:229: bad argument: string expected, got nil", so nil is not a valid argument for write() and should be removed.
2017-09-12 17:17:58 +02:00
Bomb Bloke
70c6f3498b Correct minor typo in rednet.receive
Caused attempts to set a time-out value to throw "expected number, got number".
2017-09-13 01:06:59 +10:00
SquidDev
afec3743f3 Use custom packet to play records, instead of using block events
Breaking a disk drive was not stopping the record being played as the
block event never reached the client. Instead, we send a custom packet
which starts/stops music at a given location.

We also remove all the plumbing for eventReceived/sendBlockEvent from
the generic block/tile classes, as they are no longer used.

Closes #443
2017-09-12 15:46:46 +01:00
Wilma456
5989d021c7 Add folder /rom/modules 2017-09-12 16:44:22 +02:00
SquidDev
baa8993999 Fix background and tooltips not rendering within containers
The methods to draw these now have to be explicitly called, hence not
showing up.
2017-09-12 15:05:32 +01:00
Wilma456 (Jakob0815)
92f5860de6 Use select() 2017-09-12 15:27:09 +02:00
SquidDev
12abd4292e Fixes a non-existent method being used in paintutils 2017-09-12 09:52:21 +01:00
SquidDev
4bd5b0d236 Remove HTTPTask, queueing the event when it has finished executing
This means we don't have to have lots of shared state between the run
and whenFinished method, and allows for easier chaining of futures later
on.
2017-09-11 22:13:00 +01:00
Wilma456 (Jakob0815)
0115bc8dca Make io.write() accept multiple args
This is just to bring the io API from CC close the the io API from normal lua, which accept multiple args for io.write().
2017-09-11 15:47:30 +02:00
SquidDev
5f323a85a7 Rethrow/retrigger interrupted status where appropriate 2017-09-10 22:08:08 +01:00
SquidDev
85c556d324 Rewrite the computer thread system
This makes a couple of significant changes to the original system, to
reduce the number of threads created and allow for multiple threads in
the future. There are several notable changes from the original
implementation:

 - A blocking queue is used for the main task queue queue. This removes
   the need for the "monitor" variable and allows for multiple threads
   polling this queue in the future.
 - The thread used to execute tasks is "cached" between tasks,
   significantly reducing the number of threads which need to be
   created. If a task needs to be stopped then the thread is then
   terminated and a new one constructed, though this rarely happens.
2017-09-10 22:08:08 +01:00
Wilma456
0c1114edbc Update to new Style 2017-08-30 17:29:06 +02:00
Wilma456 (Jakob0815)
2c264728d9 Add Check to textutils.tabulate/pagedTabulate 2017-08-10 13:40:35 +02:00
Wilma456
90c4ebd208 Fix Bug in copy.lua, mkdir.lua and rename.lua 2017-08-09 19:32:29 +02:00
SquidDev
5df97e5133 Tweak shell program resolution slightly
- Path containing '/' or '\' are resolved relative to the current
   directory, rather than using the path. Paths starting with '/' still
   resolve relative to the root directory.
 - Shell completion will also include sub-directories of the current
   directory.

Closes #219
2017-08-03 07:24:21 +01:00
Wojbie
acb5f65e16 Functional change Lignum suggested 2017-07-29 01:14:36 +02:00
Wilma456
c9e7b45509 Show fs error in paint and edit 2017-07-28 15:36:33 +02:00
SquidDev
6fca136327 Move Bit32 library to LuaJ sources 2017-06-28 23:05:48 +01:00
SquidDev
084bbe8480 Replace BitAPI with a LuaJ implementation of bit32 2017-06-17 21:39:12 +01:00
Wojbie
225ec594e7 Adds pocket computer light support to Speaker
This makes use of new pocket computer light access peripherals have and adds said functionality to speaker. If noisy pocket has made sound the pocket computer light will turn dark blue for a second.
2017-06-12 20:54:38 +02:00
340 changed files with 14847 additions and 3788 deletions

17
.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
# Sadly too many files have whitespace errors, so we leave this as is for
# now and just make sure we don't introduce any more.
# trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.properties]
insert_final_newline = false

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

@@ -1,26 +1,47 @@
ComputerCraft # ![CC: Tweaked](logo.png)
============= [![Build Status](https://travis-ci.org/SquidDev-CC/CC-Tweaked.svg?branch=master)](https://travis-ci.org/SquidDev-CC/CC-Tweaked)
[![Build Status](https://travis-ci.org/dan200/ComputerCraft.svg?branch=master)](https://travis-ci.org/dan200/ComputerCraft)
ComputerCraft is a Minecraft modification which adds programmable Robots and Computers to the world of Minecraft. CC: Tweaked is a fork of ComputerCraft which aims to provide earlier access to the more experimental and in-development
If you're not familiar with ComputerCraft, visit the [Website](http://www.computercraft.info/download) or the [Wiki](http://www.computercraft.info/wiki) to find out more. features of the mod. For a more stable experience, I recommend checking out the
[original mod](https://github.com/dan200/ComputerCraft).
About this Repository ## What?
===================== 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.
ComputerCraft was originally released in late 2011 by [Daniel Ratcliffe](https://twitter.com/DanTwoHundred). In early 2017, after working on the mod solo for five years, it was decided to release the source code publicly to allow Dan to devote time to other projects. This repository marks the first public release of this source code. 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.
The code in this repository will always represent the "bleeding edge" of the ComputerCraft codebase, but stable builds back to 1.79 will be marked on the [Releases](https://github.com/dan200/ComputerCraft/releases) page. ## 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:
Contributing - 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).
While ComputerCraft will no longer be actively developed by Daniel Ratcliffe, you may still contribute pull requests which will be reviewed and incorporated into releases periodically. A pull request is more likely to be accepted if it meets the following criteria: ## Relation to CCTweaks?
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.
* It does not add any new dependencies for compiling, running or using the mod. ## Contributing
* It does not break compatibility with world saves or programs created with previous versions of the mod. Any contribution is welcome, be that using the mod, reporting bugs or contributing code. If you do wish to contribute
* It does not add unneccessary complexity for users of the mod, and maintains the accessibility for which the mod is known. code, do consider submitting it to the ComputerCraft repository instead.
* It does not add unneccessary complexity or stylistic changes to the code, especially where functionality is not being changed.
* It does not create bugs!
The pull requests most likely to be accepted are those which fix bugs, simplify code, or make the mod compatible with newer versions of Minecraft. 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:T in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from `build/libs`.

View File

@@ -13,22 +13,22 @@ buildscript {
classpath 'org.ajoberstar:gradle-git:1.6.0' classpath 'org.ajoberstar:gradle-git:1.6.0'
} }
} }
plugins {
id 'com.matthewprenger.cursegradle' version '1.0.10'
}
apply plugin: 'net.minecraftforge.gradle.forge' apply plugin: 'net.minecraftforge.gradle.forge'
apply plugin: 'org.ajoberstar.grgit' apply plugin: 'org.ajoberstar.grgit'
apply plugin: 'maven-publish'
apply plugin: 'maven'
/* version = "1.80pr1.8"
// for people who want stable - not yet functional for MC 1.8.8 - we require the forgegradle 2.1 snapshot group = "org.squiddev"
plugins { archivesBaseName = "cc-tweaked"
id "net.minecraftforge.gradle.forge" version "2.0.2"
}
*/
version = "1.80pr1"
group = "dan200.computercraft"
archivesBaseName = "ComputerCraft"
minecraft { minecraft {
version = "1.12-14.21.1.2387" version = "1.12.2-14.23.4.2749"
runDir = "run" runDir = "run"
replace '${version}', project.version replace '${version}', project.version
@@ -37,38 +37,60 @@ minecraft {
// stable_# stables are built at the discretion of the MCP team. // stable_# stables are built at the discretion of the MCP team.
// Use non-default mappings at your own risk. they may not allways work. // 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. // 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. // makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable.
} }
repositories {
maven {
name = "JEI"
url = "http://dvs1.progwml6.com/files/maven"
}
maven {
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 { dependencies {
// you may put jars on which you depend on in ./libs deobfProvided "mezz.jei:jei_1.12.2:4.8.5.159:api"
// or you may define them like so.. deobfProvided "pl.asie:Charset-Lib:0.5.4.6"
//compile "some.group:artifact:version:classifier"
//compile "some.group:artifact:version"
// real examples runtime "mezz.jei:jei_1.12.2:4.8.5.159"
//compile 'com.mod-buildcraft:buildcraft:6.0.8:dev' // adds buildcraft to the dev env
//compile 'com.googlecode.efficient-java-matrix-library:ejml:0.24' // adds ejml to the dev env
// the 'provided' configuration is for optional dependencies that exist at compile-time but might not at runtime. shade 'org.squiddev:Cobalt:0.3.2'
//provided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
// the deobf configurations: 'deobfCompile' and 'deobfProvided' are the same as the normal compile and provided, testCompile 'junit:junit:4.11'
// except that these dependencies get remapped to your current MCP mappings
//deobfCompile 'com.mod-buildcraft:buildcraft:6.0.8:dev'
//deobfProvided 'com.mod-buildcraft:buildcraft:6.0.8:dev'
// for more info... deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0"
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html }
// http://www.gradle.org/docs/current/userguide/dependency_management.html
javadoc {
include "dan200/computercraft/api/**/*.java"
} }
jar { jar {
dependsOn javadoc
manifest { manifest {
attributes('FMLAT': 'computercraft_at.cfg') attributes('FMLAT': 'computercraft_at.cfg')
} }
into("docs", { from (javadoc.destinationDir) })
into("api", { from (sourceSets.main.allSource) {
include "dan200/computercraft/api/**/*.java"
}})
from configurations.shade.collect { it.isDirectory() ? it : zipTree(it) }
} }
import org.ajoberstar.grgit.Grgit import org.ajoberstar.grgit.Grgit
@@ -103,9 +125,74 @@ processResources {
} }
} }
curseforge {
apiKey = project.hasProperty('curseForgeApiKey') ? project.curseForgeApiKey : ''
project {
id = '282001'
releaseType = 'beta'
changelog = ''
}
}
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 { gradle.projectsEvaluated {
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint" options.compilerArgs << "-Xlint"
} }
} }
runClient.outputs.upToDateWhen { false }
runServer.outputs.upToDateWhen { false }

View File

@@ -18,29 +18,4 @@ OUTPUTJAR=`ls -1 build/libs | grep -v sources | sed s/\-//g`
FRIENDLYNAME=`ls -1 build/libs | grep -v sources | sed s/\-/\ /g | sed s/\.jar//g` FRIENDLYNAME=`ls -1 build/libs | grep -v sources | sed s/\-/\ /g | sed s/\.jar//g`
cp build/libs/$INPUTJAR deploy/$OUTPUTJAR cp build/libs/$INPUTJAR deploy/$OUTPUTJAR
echo "Creating API..."
mkdir -p deploy/api/src/dan200/computercraft
cp -r build/sources/main/java/dan200/computercraft/api deploy/api/src/dan200/computercraft/api
echo "Creating API Javadocs..."
mkdir -p deploy/api/doc
cd src/main/java/dan200/computercraft/api
find . -type f -name "*.java" | xargs javadoc -d ../../../../../../deploy/api/doc -windowtitle "$FRIENDLYNAME"
cd ../../../../../..
echo "Adding API and Javadocs to deployment..."
cd deploy
zip -r $OUTPUTJAR api/doc > /dev/null
zip -r $OUTPUTJAR api/src/dan200/computercraft > /dev/null
cd ..
rm -rf deploy/api
echo "Adding LuaJ to deployment..."
mkdir deploy/luaj
cd deploy/luaj
jar xf ../../libs/luaj-jse-2.0.3.jar
zip -r ../$OUTPUTJAR org > /dev/null
cd ../..
rm -rf deploy/luaj
echo "Done." echo "Done."

Binary file not shown.

View File

@@ -1,6 +1,5 @@
#Mon Sep 14 12:28:28 PDT 2015
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip

110
gradlew vendored
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env sh
############################################################################## ##############################################################################
## ##
@@ -6,47 +6,6 @@
## ##
############################################################################## ##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS="-Xmx2048m"
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" PRG="$0"
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
fi fi
done done
SAVED="`pwd`" SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&- cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`" APP_HOME="`pwd -P`"
cd "$SAVED" >&- cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -90,7 +89,7 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n` MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -114,6 +113,7 @@ fi
if $cygwin ; then if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath # We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -154,11 +154,19 @@ if $cygwin ; then
esac esac
fi fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules # Escape application args
function splitJvmOpts() { save () {
JVM_OPTS=("$@") for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
} }
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS APP_ARGS=$(save "$@")
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" # Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

14
gradlew.bat vendored
View File

@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-d64 -Xmx2048m"
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail goto fail
:init :init
@rem Get command-line arguments, handling Windowz variants @rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args :win9xME_args
@rem Slurp the command line arguments. @rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%* set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute :execute
@rem Setup the command line @rem Setup the command line

Binary file not shown.

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,181 @@
/*******************************************************************************
* Copyright (c) 2012 Luaj.org. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
package org.luaj.vm2.lib;
import org.luaj.vm2.LuaInteger;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;
/**
* Subclass of LibFunction that implements the Lua standard {@code bit32} library.
*/
public class Bit32Lib extends ZeroArgFunction
{
public LuaValue call( )
{
LuaTable t = new LuaTable();
bind( t, Bit32LibV.class, new String[] {
"band", "bnot", "bor", "btest", "bxor", "extract", "replace"
} );
bind( t, Bit32Lib2.class, new String[] {
"arshift", "lrotate", "lshift", "rrotate", "rshift"
} );
env.set( "bit32", t );
return t;
}
public static final class Bit32LibV extends VarArgFunction
{
public Varargs invoke( Varargs args )
{
switch( opcode )
{
case 0: // band
{
int result = -1;
for( int i = 1; i <= args.narg(); i++ )
{
result &= args.checkint( i );
}
return bitsToValue( result );
}
case 1: // bnot
return bitsToValue( ~args.checkint( 1 ) );
case 2: // bot
{
int result = 0;
for( int i = 1; i <= args.narg(); i++ )
{
result |= args.checkint( i );
}
return bitsToValue( result );
}
case 3: // btest
{
int bits = -1;
for( int i = 1; i <= args.narg(); i++ )
{
bits &= args.checkint( i );
}
return valueOf( bits != 0 );
}
case 4: // bxor
{
int result = 0;
for( int i = 1; i <= args.narg(); i++ )
{
result ^= args.checkint( i );
}
return bitsToValue( result );
}
case 5: // extract
{
int field = args.checkint( 2 );
int width = args.optint( 3, 1 );
if( field < 0 ) argerror( 2, "field cannot be negative" );
if( width <= 0 ) argerror( 3, "width must be postive" );
if( field + width > 32 ) error( "trying to access non-existent bits" );
return bitsToValue( (args.checkint( 1 ) >>> field) & (-1 >>> (32 - width)) );
}
case 6: // replace
{
int n = args.checkint( 1 );
int v = args.checkint( 2 );
int field = args.checkint( 3 );
int width = args.optint( 4, 1 );
if( field < 0 ) argerror( 3, "field cannot be negative" );
if( width <= 0 ) argerror( 4, "width must be postive" );
if( field + width > 32 ) error( "trying to access non-existent bits" );
int mask = (-1 >>> (32 - width)) << field;
n = (n & ~mask) | ((v << field) & mask);
return bitsToValue( n );
}
}
return NIL;
}
}
public static final class Bit32Lib2 extends TwoArgFunction
{
public LuaValue call( LuaValue arg1, LuaValue arg2 )
{
switch( opcode )
{
case 0: // arshift
{
int x = arg1.checkint();
int disp = arg2.checkint();
return disp >= 0 ? bitsToValue( x >> disp ) : bitsToValue( x << -disp );
}
case 1: // lrotate
return rotate( arg1.checkint(), arg2.checkint() );
case 2: // lshift
return shift( arg1.checkint(), arg2.checkint() );
case 3: // rrotate
return rotate( arg1.checkint(), -arg2.checkint() );
case 4: // rshift
return shift( arg1.checkint(), -arg2.checkint() );
}
return NIL;
}
}
static LuaValue rotate( int x, int disp )
{
if( disp < 0 )
{
disp = -disp & 31;
return bitsToValue( (x >>> disp) | (x << (32 - disp)) );
}
else
{
disp = disp & 31;
return bitsToValue( (x << disp) | (x >>> (32 - disp)) );
}
}
static LuaValue shift( int x, int disp )
{
if( disp >= 32 || disp <= -32 )
{
return ZERO;
}
else if( disp >= 0 )
{
return bitsToValue( x << disp );
}
else
{
return bitsToValue( x >>> -disp );
}
}
private static LuaValue bitsToValue( int x )
{
return x < 0 ? LuaValue.valueOf( (long) x & 0xFFFFFFFFL ) : LuaInteger.valueOf( x );
}
}

1
settings.gradle Normal file
View File

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

View File

@@ -7,26 +7,37 @@
package dan200.computercraft; package dan200.computercraft;
import com.google.common.base.CaseFormat; import com.google.common.base.CaseFormat;
import com.google.common.base.Converter;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.IMediaProvider; import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork; 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.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider; import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.api.permissions.ITurtlePermissionProvider; import dan200.computercraft.api.permissions.ITurtlePermissionProvider;
import dan200.computercraft.api.pocket.IPocketUpgrade; import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.redstone.IBundledRedstoneProvider; import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.core.apis.AddressPredicate; import dan200.computercraft.core.apis.AddressPredicate;
import dan200.computercraft.core.filesystem.ComboMount; import dan200.computercraft.core.filesystem.ComboMount;
import dan200.computercraft.core.filesystem.FileMount; import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.core.filesystem.JarMount; 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; import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.computer.blocks.BlockCommandComputer; import dan200.computercraft.shared.computer.blocks.BlockCommandComputer;
import dan200.computercraft.shared.computer.blocks.BlockComputer; import dan200.computercraft.shared.computer.blocks.BlockComputer;
import dan200.computercraft.shared.computer.blocks.TileComputer; import dan200.computercraft.shared.computer.blocks.TileComputer;
import dan200.computercraft.shared.computer.core.ClientComputerRegistry; import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry; import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.media.items.ItemDiskExpanded; import dan200.computercraft.shared.media.items.ItemDiskExpanded;
import dan200.computercraft.shared.media.items.ItemDiskLegacy; import dan200.computercraft.shared.media.items.ItemDiskLegacy;
@@ -36,6 +47,7 @@ import dan200.computercraft.shared.network.ComputerCraftPacket;
import dan200.computercraft.shared.network.PacketHandler; import dan200.computercraft.shared.network.PacketHandler;
import dan200.computercraft.shared.peripheral.common.BlockCable; import dan200.computercraft.shared.peripheral.common.BlockCable;
import dan200.computercraft.shared.peripheral.common.BlockPeripheral; 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.diskdrive.TileDiskDrive;
import dan200.computercraft.shared.peripheral.modem.BlockAdvancedModem; import dan200.computercraft.shared.peripheral.modem.BlockAdvancedModem;
import dan200.computercraft.shared.peripheral.modem.WirelessNetwork; import dan200.computercraft.shared.peripheral.modem.WirelessNetwork;
@@ -49,6 +61,8 @@ import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.upgrades.*; import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.*; import dan200.computercraft.shared.util.*;
import dan200.computercraft.shared.wired.CapabilityWiredElement;
import dan200.computercraft.shared.wired.WiredNode;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayer;
@@ -56,11 +70,13 @@ import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.network.PacketBuffer; import net.minecraft.network.PacketBuffer;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumHand; import net.minecraft.util.EnumHand;
import net.minecraft.util.NonNullList; import net.minecraft.util.NonNullList;
import net.minecraft.util.SoundEvent; import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.common.config.ConfigCategory; import net.minecraftforge.common.config.ConfigCategory;
import net.minecraftforge.common.config.Configuration; import net.minecraftforge.common.config.Configuration;
@@ -81,10 +97,8 @@ import java.io.*;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap; import java.util.function.Function;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile; import java.util.zip.ZipFile;
@@ -93,7 +107,7 @@ import java.util.zip.ZipFile;
/////////////// ///////////////
@Mod( @Mod(
modid = ComputerCraft.MOD_ID, name = "ComputerCraft", version = "${version}", modid = ComputerCraft.MOD_ID, name = "CC: Tweaked", version = "${version}",
guiFactory = "dan200.computercraft.client.gui.GuiConfigCC$Factory" guiFactory = "dan200.computercraft.client.gui.GuiConfigCC$Factory"
) )
public class ComputerCraft public class ComputerCraft
@@ -109,6 +123,7 @@ public class ComputerCraft
// ComputerCraftEdu uses ID 104 // ComputerCraftEdu uses ID 104
public static final int printoutGUIID = 105; public static final int printoutGUIID = 105;
public static final int pocketComputerGUIID = 106; public static final int pocketComputerGUIID = 106;
public static final int viewComputerGUIID = 110;
// Configuration options // Configuration options
private static final String[] DEFAULT_HTTP_WHITELIST = new String[] { "*" }; private static final String[] DEFAULT_HTTP_WHITELIST = new String[] { "*" };
@@ -119,12 +134,15 @@ public class ComputerCraft
"192.168.0.0/16", "192.168.0.0/16",
"fd00::/8", "fd00::/8",
}; };
public static boolean http_enable = true; public static boolean http_enable = true;
public static boolean http_websocket_enable = true;
public static AddressPredicate http_whitelist = new AddressPredicate( DEFAULT_HTTP_WHITELIST ); public static AddressPredicate http_whitelist = new AddressPredicate( DEFAULT_HTTP_WHITELIST );
public static AddressPredicate http_blacklist = new AddressPredicate( DEFAULT_HTTP_BLACKLIST ); public static AddressPredicate http_blacklist = new AddressPredicate( DEFAULT_HTTP_BLACKLIST );
public static boolean disable_lua51_features = false; public static boolean disable_lua51_features = false;
public static String default_computer_settings = ""; public static String default_computer_settings = "";
public static boolean debug_enable = false;
public static int computer_threads = 1;
public static boolean logPeripheralErrors = false; public static boolean logPeripheralErrors = false;
public static boolean enableCommandBlock = false; public static boolean enableCommandBlock = false;
@@ -133,6 +151,7 @@ public class ComputerCraft
public static int advancedTurtleFuelLimit = 100000; public static int advancedTurtleFuelLimit = 100000;
public static boolean turtlesObeyBlockProtection = true; public static boolean turtlesObeyBlockProtection = true;
public static boolean turtlesCanPush = 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 terminalWidth_computer = 51;
public static final int terminalHeight_computer = 19; public static final int terminalHeight_computer = 19;
@@ -165,6 +184,7 @@ public class ComputerCraft
public static BlockTurtle turtleAdvanced; public static BlockTurtle turtleAdvanced;
public static BlockCommandComputer commandComputer; public static BlockCommandComputer commandComputer;
public static BlockAdvancedModem advancedModem; public static BlockAdvancedModem advancedModem;
public static BlockWiredModemFull wiredModemFull;
} }
public static class Items public static class Items
@@ -200,10 +220,13 @@ public class ComputerCraft
public static Configuration config; public static Configuration config;
public static Property http_enable; public static Property http_enable;
public static Property http_websocket_enable;
public static Property http_whitelist; public static Property http_whitelist;
public static Property http_blacklist; public static Property http_blacklist;
public static Property disable_lua51_features; public static Property disable_lua51_features;
public static Property default_computer_settings; public static Property default_computer_settings;
public static Property debug_enable;
public static Property computer_threads;
public static Property logPeripheralErrors; public static Property logPeripheralErrors;
public static Property enableCommandBlock; public static Property enableCommandBlock;
@@ -212,6 +235,7 @@ public class ComputerCraft
public static Property advancedTurtleFuelLimit; public static Property advancedTurtleFuelLimit;
public static Property turtlesObeyBlockProtection; public static Property turtlesObeyBlockProtection;
public static Property turtlesCanPush; public static Property turtlesCanPush;
public static Property turtleDisabledActions;
public static Property modem_range; public static Property modem_range;
public static Property modem_highAltitudeRange; public static Property modem_highAltitudeRange;
@@ -244,6 +268,7 @@ public class ComputerCraft
private static List<IMediaProvider> mediaProviders = new ArrayList<>(); private static List<IMediaProvider> mediaProviders = new ArrayList<>();
private static List<ITurtlePermissionProvider> permissionProviders = new ArrayList<>(); private static List<ITurtlePermissionProvider> permissionProviders = new ArrayList<>();
private static final Map<String, IPocketUpgrade> pocketUpgrades = new HashMap<>(); private static final Map<String, IPocketUpgrade> pocketUpgrades = new HashMap<>();
private static final Set<ILuaAPIFactory> apiFactories = new LinkedHashSet<>();
// Implementation // Implementation
@Mod.Instance( value = ComputerCraft.MOD_ID ) @Mod.Instance( value = ComputerCraft.MOD_ID )
@@ -266,11 +291,26 @@ public class ComputerCraft
// Load config // Load config
Config.config = new Configuration( event.getSuggestedConfigurationFile() ); 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.config.load();
Config.http_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "http_enable", http_enable ); Config.http_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "http_enable", http_enable );
Config.http_enable.setComment( "Enable the \"http\" API on Computers (see \"http_whitelist\" and \"http_blacklist\" for more fine grained control than this)" ); Config.http_enable.setComment( "Enable the \"http\" API on Computers (see \"http_whitelist\" and \"http_blacklist\" for more fine grained control than this)" );
Config.http_websocket_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "http_websocket_enable", http_websocket_enable );
Config.http_websocket_enable.setComment( "Enable use of http websockets. This requires the \"http_enable\" option to also be true." );
{ {
ConfigCategory category = Config.config.getCategory( Configuration.CATEGORY_GENERAL ); ConfigCategory category = Config.config.getCategory( Configuration.CATEGORY_GENERAL );
Property currentProperty = category.get( "http_whitelist" ); Property currentProperty = category.get( "http_whitelist" );
@@ -298,10 +338,20 @@ public class ComputerCraft
Config.default_computer_settings = Config.config.get( Configuration.CATEGORY_GENERAL, "default_computer_settings", default_computer_settings ); Config.default_computer_settings = Config.config.get( Configuration.CATEGORY_GENERAL, "default_computer_settings", default_computer_settings );
Config.default_computer_settings.setComment( "A comma seperated list of default system settings to set on new computers. Example: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\" will disable all autocompletion" ); Config.default_computer_settings.setComment( "A comma seperated list of default system settings to set on new computers. Example: \"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\" will disable all autocompletion" );
Config.debug_enable = Config.config.get( Configuration.CATEGORY_GENERAL, "debug_enable", debug_enable );
Config.debug_enable.setComment( "Enable Lua's debug library. Whilst this should be safe for general use, it may allow players to interact with other computers. Enable at your own risk." );
Config.computer_threads = Config.config.get( Configuration.CATEGORY_GENERAL, "computer_threads", computer_threads );
Config.computer_threads
.setMinValue( 1 )
.setRequiresWorldRestart( true )
.setComment( "Set the number of threads computers can run on. A higher number means more computers can run at once, but may induce lag.\n" +
"Please note that some mods may not work with a thread count higher than 1. Use with caution." );
Config.logPeripheralErrors = Config.config.get( Configuration.CATEGORY_GENERAL, "logPeripheralErrors", logPeripheralErrors ); Config.logPeripheralErrors = Config.config.get( Configuration.CATEGORY_GENERAL, "logPeripheralErrors", logPeripheralErrors );
Config.logPeripheralErrors.setComment( "Log exceptions thrown by peripherals and other Lua objects.\n" + Config.logPeripheralErrors.setComment( "Log exceptions thrown by peripherals and other Lua objects.\n" +
"This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." ); "This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." );
Config.enableCommandBlock = Config.config.get( Configuration.CATEGORY_GENERAL, "enableCommandBlock", enableCommandBlock ); Config.enableCommandBlock = Config.config.get( Configuration.CATEGORY_GENERAL, "enableCommandBlock", enableCommandBlock );
Config.enableCommandBlock.setComment( "Enable Command Block peripheral support" ); Config.enableCommandBlock.setComment( "Enable Command Block peripheral support" );
@@ -341,6 +391,9 @@ public class ComputerCraft
Config.turtlesCanPush = Config.config.get( Configuration.CATEGORY_GENERAL, "turtlesCanPush", turtlesCanPush ); 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.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 = Config.config.get( Configuration.CATEGORY_GENERAL, "maxNotesPerTick", maxNotesPerTick );
Config.maxNotesPerTick.setComment( "Maximum amount of notes a speaker can play at once" ); Config.maxNotesPerTick.setComment( "Maximum amount of notes a speaker can play at once" );
@@ -350,22 +403,18 @@ public class ComputerCraft
} }
syncConfig(); syncConfig();
// Setup network
networkEventChannel = NetworkRegistry.INSTANCE.newEventDrivenChannel( "CC" );
networkEventChannel.register( new PacketHandler() );
proxy.preInit();
turtleProxy.preInit();
} }
public static void syncConfig() { public static void syncConfig() {
http_enable = Config.http_enable.getBoolean(); http_enable = Config.http_enable.getBoolean();
http_websocket_enable = Config.http_websocket_enable.getBoolean();
http_whitelist = new AddressPredicate( Config.http_whitelist.getStringList() ); http_whitelist = new AddressPredicate( Config.http_whitelist.getStringList() );
http_blacklist = new AddressPredicate( Config.http_blacklist.getStringList() ); http_blacklist = new AddressPredicate( Config.http_blacklist.getStringList() );
disable_lua51_features = Config.disable_lua51_features.getBoolean(); disable_lua51_features = Config.disable_lua51_features.getBoolean();
default_computer_settings = Config.default_computer_settings.getString(); default_computer_settings = Config.default_computer_settings.getString();
debug_enable = Config.debug_enable.getBoolean();
computer_threads = Config.computer_threads.getInt();
logPeripheralErrors = Config.logPeripheralErrors.getBoolean(); logPeripheralErrors = Config.logPeripheralErrors.getBoolean();
enableCommandBlock = Config.enableCommandBlock.getBoolean(); enableCommandBlock = Config.enableCommandBlock.getBoolean();
@@ -385,6 +434,20 @@ public class ComputerCraft
turtlesObeyBlockProtection = Config.turtlesObeyBlockProtection.getBoolean(); turtlesObeyBlockProtection = Config.turtlesObeyBlockProtection.getBoolean();
turtlesCanPush = Config.turtlesCanPush.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()); maxNotesPerTick = Math.max(1, Config.maxNotesPerTick.getInt());
Config.config.save(); Config.config.save();
@@ -400,6 +463,7 @@ public class ComputerCraft
@Mod.EventHandler @Mod.EventHandler
public void onServerStarting( FMLServerStartingEvent event ) public void onServerStarting( FMLServerStartingEvent event )
{ {
proxy.initServer( event.getServer() );
} }
@Mod.EventHandler @Mod.EventHandler
@@ -409,6 +473,7 @@ public class ComputerCraft
{ {
ComputerCraft.serverComputerRegistry.reset(); ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks(); WirelessNetwork.resetNetworks();
Tracking.reset();
} }
} }
@@ -419,6 +484,7 @@ public class ComputerCraft
{ {
ComputerCraft.serverComputerRegistry.reset(); ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks(); WirelessNetwork.resetNetworks();
Tracking.reset();
} }
} }
@@ -442,11 +508,6 @@ public class ComputerCraft
return proxy.getRenderFrame(); return proxy.getRenderFrame();
} }
public static void deleteDisplayLists( int list, int range )
{
proxy.deleteDisplayLists( list, range );
}
public static Object getFixedWidthFontRenderer() public static Object getFixedWidthFontRenderer()
{ {
return proxy.getFixedWidthFontRenderer(); return proxy.getFixedWidthFontRenderer();
@@ -496,6 +557,24 @@ public class ComputerCraft
player.openGui( ComputerCraft.instance, ComputerCraft.pocketComputerGUIID, player.getEntityWorld(), hand.ordinal(), 0, 0 ); player.openGui( ComputerCraft.instance, ComputerCraft.pocketComputerGUIID, player.getEntityWorld(), hand.ordinal(), 0, 0 );
} }
public static void openComputerGUI( EntityPlayer player, ServerComputer computer )
{
ComputerFamily family = computer.getFamily();
int width = 0, height = 0;
Terminal terminal = computer.getTerminal();
if( terminal != null )
{
width = terminal.getWidth();
height = terminal.getHeight();
}
// Pack useful terminal information into the various coordinate bits.
// These are extracted in ComputerCraftProxyCommon.getClientGuiElement
player.openGui( ComputerCraft.instance, ComputerCraft.viewComputerGUIID, player.getEntityWorld(),
computer.getInstanceID(), family.ordinal(), (width & 0xFFFF) << 16 | (height & 0xFFFF)
);
}
public static File getBaseDir() public static File getBaseDir()
{ {
return FMLCommonHandler.instance().getMinecraftServerInstance().getFile("."); return FMLCommonHandler.instance().getMinecraftServerInstance().getFile(".");
@@ -533,6 +612,11 @@ public class ComputerCraft
networkEventChannel.sendToServer( encode( packet ) ); networkEventChannel.sendToServer( encode( packet ) );
} }
public static void sendToAllAround( ComputerCraftPacket packet, NetworkRegistry.TargetPoint point )
{
networkEventChannel.sendToAllAround( encode( packet ), point );
}
public static void handlePacket( ComputerCraftPacket packet, EntityPlayer player ) public static void handlePacket( ComputerCraftPacket packet, EntityPlayer player )
{ {
proxy.handlePacket( packet, player ); proxy.handlePacket( packet, player );
@@ -644,6 +728,19 @@ public class ComputerCraft
} }
} }
public static void registerAPIFactory( ILuaAPIFactory provider )
{
if( provider != null )
{
apiFactories.add( provider );
}
}
public static IWiredNode createWiredNodeForElement( IWiredElement element )
{
return new WiredNode( element );
}
public static IPeripheral getPeripheralAt( World world, BlockPos pos, EnumFacing side ) public static IPeripheral getPeripheralAt( World world, BlockPos pos, EnumFacing side )
{ {
// Try the handlers in order: // Try the handlers in order:
@@ -665,6 +762,14 @@ public class ComputerCraft
return null; 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 ) public static int getDefaultBundledRedstoneOutput( World world, BlockPos pos, EnumFacing side )
{ {
if( WorldUtil.isBlockInWorld( world, pos ) ) if( WorldUtil.isBlockInWorld( world, pos ) )
@@ -766,11 +871,16 @@ public class ComputerCraft
return upgrades; return upgrades;
} }
public IPacketNetwork getWirelessNetwork() public static IPacketNetwork getWirelessNetwork()
{ {
return WirelessNetwork.getUniversal(); return WirelessNetwork.getUniversal();
} }
public static Iterable<ILuaAPIFactory> getAPIFactories()
{
return apiFactories;
}
public static int createUniqueNumberedSaveDir( World world, String parentSubPath ) public static int createUniqueNumberedSaveDir( World world, String parentSubPath )
{ {
return IDAssigner.getNextIDFromDirectory(new File(getWorldDir(world), parentSubPath)); return IDAssigner.getNextIDFromDirectory(new File(getWorldDir(world), parentSubPath));
@@ -1015,13 +1125,18 @@ public class ComputerCraft
turtleProxy.addAllUpgradedTurtles( list ); 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

@@ -8,9 +8,12 @@ package dan200.computercraft.api;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPIFactory;
import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.media.IMediaProvider; import dan200.computercraft.api.media.IMediaProvider;
import dan200.computercraft.api.network.IPacketNetwork; 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.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider; import dan200.computercraft.api.peripheral.IPeripheralProvider;
@@ -20,6 +23,7 @@ import dan200.computercraft.api.redstone.IBundledRedstoneProvider;
import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World; import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@@ -311,6 +315,77 @@ public final class ComputerCraftAPI
return null; return null;
} }
public static void registerAPIFactory( @Nonnull ILuaAPIFactory upgrade )
{
findCC();
if( computerCraft_registerAPIFactory != null )
{
try
{
computerCraft_registerAPIFactory.invoke( null, upgrade );
}
catch( Exception e )
{
// It failed
}
}
}
/**
* 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. // 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 // 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. // it in your solution, and so your mod won't crash if ComputerCraft is installed.
@@ -354,6 +429,15 @@ public final class ComputerCraftAPI
} ); } );
computerCraft_getWirelessNetwork = findCCMethod( "getWirelessNetwork", new Class<?>[] { computerCraft_getWirelessNetwork = findCCMethod( "getWirelessNetwork", new Class<?>[] {
} ); } );
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 ) { } catch( Exception e ) {
System.out.println( "ComputerCraftAPI: ComputerCraft not found." ); System.out.println( "ComputerCraftAPI: ComputerCraft not found." );
} finally { } finally {
@@ -390,4 +474,7 @@ public final class ComputerCraftAPI
private static Method computerCraft_registerPermissionProvider = null; private static Method computerCraft_registerPermissionProvider = null;
private static Method computerCraft_registerPocketUpgrade = null; private static Method computerCraft_registerPocketUpgrade = null;
private static Method computerCraft_getWirelessNetwork = 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,38 @@
package dan200.computercraft.api.filesystem;
import java.io.IOException;
/**
* Provides a mount of the entire computer's file system.
*
* This exists for use by various APIs - one should not attempt to mount it.
*/
public interface IFileSystem extends IWritableMount
{
/**
* Combine two paths together, reducing them into a normalised form.
*
* @param path The main path.
* @param child The path to append.
* @return The combined, normalised path.
*/
String combine( String path, String child );
/**
* Copy files from one location to another.
*
* @param from The location to copy from.
* @param to The location to copy to. This should not exist.
* @throws IOException If the copy failed.
*/
void copy( String from, String to ) throws IOException;
/**
* Move files from one location to another.
*
* @param from The location to move from.
* @param to The location to move to. This should not exist.
* @throws IOException If the move failed.
*/
void move( String from, String to ) throws IOException;
}

View File

@@ -0,0 +1,29 @@
package dan200.computercraft.api.lua;
import dan200.computercraft.api.filesystem.IFileSystem;
import dan200.computercraft.api.peripheral.IComputerAccess;
import javax.annotation.Nullable;
/**
* An interface passed to {@link ILuaAPIFactory} in order to provide additional information
* about a computer.
*/
public interface IComputerSystem extends IComputerAccess
{
/**
* Get the file system for this computer.
*
* @return The computer's file system, or {@code null} if it is not initialised.
*/
@Nullable
IFileSystem getFileSystem();
/**
* Get the label for this computer
*
* @return This computer's label, or {@code null} if it is not set.
*/
@Nullable
String getLabel();
}

View File

@@ -0,0 +1,47 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2016. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.api.lua;
import dan200.computercraft.api.ComputerCraftAPI;
/**
* Represents a {@link ILuaObject} which is stored as a global variable on computer startup.
*
* Before implementing this interface, consider alternative methods of providing methods. It is generally preferred
* to use peripherals to provide functionality to users.
*
* @see ILuaAPIFactory
* @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
*/
public interface ILuaAPI extends ILuaObject
{
/**
* Get the globals this API will be assigned to. This will override any other global, so you should
*
* @return A list of globals this API will be assigned to.
*/
String[] getNames();
/**
* Called when the computer is turned on.
*
* One should only interact with the file system.
*/
default void startup() { }
/**
* Called every time the computer is ticked. This can be used to process various.
*/
default void update() { }
/**
* Called when the computer is turned off or unloaded.
*
* This should reset the state of the object, disposing any remaining file handles, or other resources.
*/
default void shutdown() { }
}

View File

@@ -0,0 +1,24 @@
package dan200.computercraft.api.lua;
import dan200.computercraft.api.ComputerCraftAPI;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Construct an {@link ILuaAPI} for a specific computer.
*
* @see ILuaAPI
* @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
*/
public interface ILuaAPIFactory
{
/**
* Create a new API instance for a given computer.
*
* @param computer The computer this API is for.
* @return The created API, or {@code null} if one should not be injected.
*/
@Nullable
ILuaAPI create( @Nonnull IComputerSystem computer );
}

View File

@@ -41,7 +41,8 @@ public interface IPacketNetwork
* to all receivers within range (or any interdimensional ones). * to all receivers within range (or any interdimensional ones).
* *
* @param packet The packet to send. * @param packet The packet to send.
* @see #transmitInterdimensional(Packet) * @param range The maximum distance this packet will be sent.
* @see #transmitInterdimensional(Packet)
* @see IPacketReceiver#receiveSameDimension(Packet, double) * @see IPacketReceiver#receiveSameDimension(Packet, double)
*/ */
void transmitSameDimension( @Nonnull Packet packet, double range ); void transmitSameDimension( @Nonnull Packet packet, double range );
@@ -51,7 +52,7 @@ public interface IPacketNetwork
* to all receivers across all dimensions. * to all receivers across all dimensions.
* *
* @param packet The packet to send. * @param packet The packet to send.
* @see #transmitSameDimension(Packet, double) * @see #transmitSameDimension(Packet, double)
* @see IPacketReceiver#receiveDifferentDimension(Packet) * @see IPacketReceiver#receiveDifferentDimension(Packet)
*/ */
void transmitInterdimensional( @Nonnull Packet packet ); void transmitInterdimensional( @Nonnull Packet packet );

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.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Map;
/** /**
* The interface passed to peripherals by computers or turtles, providing methods * The interface passed to peripherals by computers or turtles, providing methods
@@ -154,4 +156,33 @@ public interface IComputerAccess
*/ */
@Nonnull @Nonnull
String getAttachmentName(); 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

@@ -114,6 +114,18 @@ public interface IPeripheral
{ {
} }
/**
* Get the object that this peripheral provides methods for. This will generally be the tile entity
* or block, but may be an inventory, entity, etc...
*
* @return The object this peripheral targets
*/
@Nonnull
default Object getTarget()
{
return this;
}
/** /**
* Determine whether this peripheral is equivalent to another one. * Determine whether this peripheral is equivalent to another one.
* *

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.api.turtle; package dan200.computercraft.api.turtle;
import com.mojang.authlib.GameProfile;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
@@ -135,6 +136,14 @@ public interface ITurtleAccess
*/ */
int getColour(); 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 * Get the inventory of this turtle
* *
@@ -148,7 +157,7 @@ public interface ITurtleAccess
* Get the inventory of this turtle as an {@link IItemHandlerModifiable}. * Get the inventory of this turtle as an {@link IItemHandlerModifiable}.
* *
* @return This turtle's inventory * @return This turtle's inventory
* @see #getInventory() * @see #getInventory()
* @see IItemHandlerModifiable * @see IItemHandlerModifiable
* @see net.minecraftforge.items.CapabilityItemHandler#ITEM_HANDLER_CAPABILITY * @see net.minecraftforge.items.CapabilityItemHandler#ITEM_HANDLER_CAPABILITY
*/ */

View File

@@ -8,11 +8,15 @@ package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.peripheral.IPeripheral; 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.IBakedModel;
import net.minecraft.client.renderer.block.model.ModelResourceLocation; import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import net.minecraft.util.ResourceLocation; 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.Side;
import net.minecraftforge.fml.relauncher.SideOnly; import net.minecraftforge.fml.relauncher.SideOnly;
import org.apache.commons.lang3.tuple.Pair; 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 * 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. * 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 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 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. * @param verb Which action (dig or attack) the turtle is being called on to perform.

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

@@ -141,10 +141,10 @@ public class FixedWidthFontRenderer
} }
// Draw char // Draw char
int index = (int)s.charAt( i ); int index = s.charAt( i );
if( index < 0 || index > 255 ) if( index < 0 || index > 255 )
{ {
index = (int)'?'; index = '?';
} }
drawChar( renderer, x + i * FONT_WIDTH, y, index, colour, p, greyScale ); drawChar( renderer, x + i * FONT_WIDTH, y, index, colour, p, greyScale );
} }

View File

@@ -33,7 +33,7 @@ public class GuiComputer extends GuiContainer
private final int m_termHeight; private final int m_termHeight;
private WidgetTerminal m_terminal; private WidgetTerminal m_terminal;
protected GuiComputer( Container container, ComputerFamily family, IComputer computer, int termWidth, int termHeight ) public GuiComputer( Container container, ComputerFamily family, IComputer computer, int termWidth, int termHeight )
{ {
super( container ); super( container );
m_family = family; m_family = family;

View File

@@ -18,7 +18,7 @@ public class GuiConfigCC extends GuiConfig
{ {
public GuiConfigCC( GuiScreen parentScreen ) public GuiConfigCC( GuiScreen parentScreen )
{ {
super( parentScreen, getConfigElements(), ComputerCraft.MOD_ID, false, false, "ComputerCraft" ); super( parentScreen, getConfigElements(), ComputerCraft.MOD_ID, false, false, "CC: Tweaked" );
} }
private static List<IConfigElement> getConfigElements() private static List<IConfigElement> getConfigElements()

View File

@@ -43,4 +43,12 @@ public class GuiDiskDrive extends GuiContainer
int i1 = (height - ySize) / 2; int i1 = (height - ySize) / 2;
drawTexturedModalRect(l, i1, 0, 0, xSize, ySize); drawTexturedModalRect(l, i1, 0, 0, xSize, ySize);
} }
@Override
public void drawScreen( int mouseX, int mouseY, float partialTicks)
{
drawDefaultBackground();
super.drawScreen(mouseX, mouseY, partialTicks);
renderHoveredToolTip(mouseX, mouseY);
}
} }

View File

@@ -51,4 +51,12 @@ public class GuiPrinter extends GuiContainer
drawTexturedModalRect(startX + 34, startY + 21, 176, 0, 25, 45); drawTexturedModalRect(startX + 34, startY + 21, 176, 0, 25, 45);
} }
} }
@Override
public void drawScreen( int mouseX, int mouseY, float partialTicks)
{
drawDefaultBackground();
super.drawScreen(mouseX, mouseY, partialTicks);
renderHoveredToolTip(mouseX, mouseY);
}
} }

View File

@@ -6,25 +6,19 @@
package dan200.computercraft.client.gui; package dan200.computercraft.client.gui;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.terminal.TextBuffer; import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.media.inventory.ContainerHeldItem; import dan200.computercraft.shared.media.inventory.ContainerHeldItem;
import dan200.computercraft.shared.media.items.ItemPrintout; import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.gui.inventory.GuiContainer; import net.minecraft.client.gui.inventory.GuiContainer;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.input.Mouse; import org.lwjgl.input.Mouse;
import java.io.IOException; import java.io.IOException;
import static dan200.computercraft.client.render.PrintoutRenderer.*;
public class GuiPrintout extends GuiContainer 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 boolean m_book;
private final int m_pages; private final int m_pages;
private final TextBuffer[] m_text; private final TextBuffer[] m_text;
@@ -34,23 +28,18 @@ public class GuiPrintout extends GuiContainer
public GuiPrintout( ContainerHeldItem container ) public GuiPrintout( ContainerHeldItem container )
{ {
super( container ); super( container );
m_book = (ItemPrintout.getType( container.getStack() ) == ItemPrintout.Type.Book);
String[] text = ItemPrintout.getText( container.getStack() ); String[] text = ItemPrintout.getText( container.getStack() );
m_text = new TextBuffer[ text.length ]; m_text = new TextBuffer[ text.length ];
for( int i=0; i<m_text.length; ++i ) for( int i = 0; i < m_text.length; ++i ) m_text[ i ] = new TextBuffer( text[ i ] );
{
m_text[i] = new TextBuffer( text[i] );
}
String[] colours = ItemPrintout.getColours( container.getStack() ); String[] colours = ItemPrintout.getColours( container.getStack() );
m_colours = new TextBuffer[ colours.length ]; m_colours = new TextBuffer[ colours.length ];
for( int i=0; i<m_colours.length; ++i ) for( int i = 0; i < m_colours.length; ++i ) m_colours[ i ] = new TextBuffer( colours[ i ] );
{
m_colours[i] = new TextBuffer( colours[i] );
}
m_pages = Math.max( m_text.length / ItemPrintout.LINES_PER_PAGE, 1 );
m_page = 0; 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 @Override
@@ -78,25 +67,19 @@ public class GuiPrintout extends GuiContainer
} }
@Override @Override
protected void keyTyped(char c, int k) throws IOException protected void keyTyped( char c, int k ) throws IOException
{ {
super.keyTyped( c, k ); super.keyTyped( c, k );
if( k == 205 ) if( k == 205 )
{ {
// Right // Right
if( m_page < m_pages - 1 ) if( m_page < m_pages - 1 ) m_page++;
{
m_page = m_page + 1;
}
} }
else if( k == 203 ) else if( k == 203 )
{ {
// Left // Left
if( m_page > 0 ) if( m_page > 0 ) m_page--;
{
m_page = m_page - 1;
}
} }
} }
@@ -106,21 +89,15 @@ public class GuiPrintout extends GuiContainer
super.handleMouseInput(); super.handleMouseInput();
int mouseWheelChange = Mouse.getEventDWheel(); int mouseWheelChange = Mouse.getEventDWheel();
if (mouseWheelChange < 0) if( mouseWheelChange < 0 )
{ {
// Up // Up
if( m_page < m_pages - 1 ) if( m_page < m_pages - 1 ) m_page++;
{
m_page = m_page + 1;
}
} }
else if (mouseWheelChange > 0) else if( mouseWheelChange > 0 )
{ {
// Down // Down
if( m_page > 0 ) if( m_page > 0 ) m_page--;
{
m_page = m_page - 1;
}
} }
} }
@@ -135,78 +112,20 @@ public class GuiPrintout extends GuiContainer
} }
@Override @Override
public void drawScreen(int mouseX, int mouseY, float f) public void drawScreen( int mouseX, int mouseY, float f )
{ {
// Draw background // Draw background
zLevel = zLevel - 1;
drawDefaultBackground(); drawDefaultBackground();
zLevel = zLevel + 1;
// Draw the printout // Draw the printout
GlStateManager.color( 1.0f, 1.0f, 1.0f, 1.0f ); 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 int startY = (height - Y_SIZE) / 2;
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer)ComputerCraft.getFixedWidthFontRenderer(); int startX = (width - X_SIZE) / 2;
int x = startX + m_page * 8 + 13;
int y = startY + 11; drawBorder( startX, startY, zLevel, m_page, m_pages, m_book );
for( int line=0; line<ItemPrintout.LINES_PER_PAGE; ++line ) drawText( startX + X_TEXT_MARGIN, startY + Y_TEXT_MARGIN, ItemPrintout.LINES_PER_PAGE * m_page, m_text, m_colours );
{
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;
}
} }
} }

View File

@@ -155,4 +155,12 @@ public class GuiTurtle extends GuiContainer
drawSelectionSlot( advanced ); drawSelectionSlot( advanced );
} }
@Override
public void drawScreen( int mouseX, int mouseY, float partialTicks)
{
drawDefaultBackground();
super.drawScreen(mouseX, mouseY, partialTicks);
renderHoveredToolTip(mouseX, mouseY);
}
} }

View File

@@ -175,10 +175,10 @@ public abstract class Widget extends Gui
{ {
Tessellator tessellator = Tessellator.getInstance(); Tessellator tessellator = Tessellator.getInstance();
tessellator.getBuffer().begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX ); tessellator.getBuffer().begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX );
tessellator.getBuffer().pos( (double) ( x + 0 ), (double) ( y + h ), (double) this.zLevel ).tex( 0.0, 1.0 ).endVertex(); tessellator.getBuffer().pos( x + 0, y + h, this.zLevel ).tex( 0.0, 1.0 ).endVertex();
tessellator.getBuffer().pos( (double) ( x + w ), (double) ( y + h ), (double) this.zLevel ).tex( 1.0, 1.0 ).endVertex(); tessellator.getBuffer().pos( x + w, y + h, this.zLevel ).tex( 1.0, 1.0 ).endVertex();
tessellator.getBuffer().pos( (double) ( x + w ), (double) ( y + 0 ), (double) this.zLevel ).tex( 1.0, 0.0 ).endVertex(); tessellator.getBuffer().pos( x + w, y + 0, this.zLevel ).tex( 1.0, 0.0 ).endVertex();
tessellator.getBuffer().pos( (double) ( x + 0 ), (double) ( y + 0 ), (double) this.zLevel ).tex( 0.0, 0.0 ).endVertex(); tessellator.getBuffer().pos( x + 0, y + 0, this.zLevel ).tex( 0.0, 0.0 ).endVertex();
tessellator.draw(); tessellator.draw();
} }

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.ModelBakery;
import net.minecraft.client.renderer.block.model.ModelResourceLocation; import net.minecraft.client.renderer.block.model.ModelResourceLocation;
import net.minecraft.client.renderer.color.IItemColor; 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.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.SimpleReloadableResourceManager; import net.minecraft.client.resources.SimpleReloadableResourceManager;
@@ -115,8 +116,23 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
MinecraftForge.EVENT_BUS.register( handlers ); 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; private TurtleSmartItemModel m_turtleSmartItemModel;
public ForgeHandlers() public ForgeHandlers()
@@ -142,24 +158,27 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
@SubscribeEvent @SubscribeEvent
public void onTextureStitchEvent( TextureStitchEvent.Pre event ) 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 @SubscribeEvent
public void onModelBakeEvent( ModelBakeEvent event ) public void onModelBakeEvent( ModelBakeEvent event )
{ {
loadModel( event, "turtle_modem_off_left" ); // Load all upgrade models
loadModel( event, "turtle_modem_on_left" ); for( String upgrade : TURTLE_UPGRADES )
loadModel( event, "turtle_modem_off_right" ); {
loadModel( event, "turtle_modem_on_right" ); loadModel( event, upgrade );
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" );
loadSmartModel( event, "turtle_dynamic", m_turtleSmartItemModel ); loadSmartModel( event, "turtle_dynamic", m_turtleSmartItemModel );
} }
@@ -191,7 +210,7 @@ public class CCTurtleProxyClient extends CCTurtleProxyCommon
private static class TurtleItemColour implements IItemColor private static class TurtleItemColour implements IItemColor
{ {
@Override @Override
public int getColorFromItemstack( @Nonnull ItemStack stack, int tintIndex ) public int colorMultiplier( @Nonnull ItemStack stack, int tintIndex )
{ {
if( tintIndex == 0 ) if( tintIndex == 0 )
{ {

View File

@@ -8,17 +8,25 @@ package dan200.computercraft.client.proxy;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.*; import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.render.ItemPocketRenderer;
import dan200.computercraft.client.render.ItemPrintoutRenderer;
import dan200.computercraft.client.render.RenderOverlayCable;
import dan200.computercraft.client.render.TileEntityCableRenderer;
import dan200.computercraft.client.render.TileEntityMonitorRenderer; import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.shared.command.ContainerViewComputer;
import dan200.computercraft.shared.computer.blocks.ComputerState; import dan200.computercraft.shared.computer.blocks.ComputerState;
import dan200.computercraft.shared.computer.blocks.TileComputer; import dan200.computercraft.shared.computer.blocks.TileComputer;
import dan200.computercraft.shared.computer.core.ClientComputer; import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.IComputer;
import dan200.computercraft.shared.computer.items.ItemComputer; import dan200.computercraft.shared.computer.items.ItemComputer;
import dan200.computercraft.shared.media.inventory.ContainerHeldItem; import dan200.computercraft.shared.media.inventory.ContainerHeldItem;
import dan200.computercraft.shared.media.items.ItemDiskLegacy; import dan200.computercraft.shared.media.items.ItemDiskLegacy;
import dan200.computercraft.shared.media.items.ItemPrintout; import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.network.ComputerCraftPacket; import dan200.computercraft.shared.network.ComputerCraftPacket;
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive; 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.monitor.TileMonitor;
import dan200.computercraft.shared.peripheral.printer.TilePrinter; import dan200.computercraft.shared.peripheral.printer.TilePrinter;
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer; import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
@@ -27,9 +35,10 @@ import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.entity.TurtleVisionCamera; import dan200.computercraft.shared.turtle.entity.TurtleVisionCamera;
import dan200.computercraft.shared.util.Colour; import dan200.computercraft.shared.util.Colour;
import gnu.trove.map.hash.TIntIntHashMap;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.client.Minecraft; 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.ItemMeshDefinition;
import net.minecraft.client.renderer.block.model.ModelBakery; import net.minecraft.client.renderer.block.model.ModelBakery;
import net.minecraft.client.renderer.block.model.ModelResourceLocation; import net.minecraft.client.renderer.block.model.ModelResourceLocation;
@@ -44,6 +53,7 @@ import net.minecraft.util.IThreadListener;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundEvent; import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraftforge.client.event.ModelRegistryEvent; import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.client.event.RenderGameOverlayEvent;
@@ -51,7 +61,7 @@ import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.client.event.RenderPlayerEvent; import net.minecraftforge.client.event.RenderPlayerEvent;
import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.fml.client.FMLClientHandler; import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fml.client.registry.ClientRegistry; import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent; import net.minecraftforge.fml.common.gameevent.TickEvent;
@@ -65,6 +75,8 @@ import java.util.List;
public class ComputerCraftProxyClient extends ComputerCraftProxyCommon public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
{ {
private static TIntIntHashMap lastCounts = new TIntIntHashMap();
private long m_tick; private long m_tick;
private long m_renderFrame; private long m_renderFrame;
private FixedWidthFontRenderer m_fixedWidthFontRenderer; private FixedWidthFontRenderer m_fixedWidthFontRenderer;
@@ -110,6 +122,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
registerItemModel( ComputerCraft.Blocks.commandComputer, "command_computer" ); registerItemModel( ComputerCraft.Blocks.commandComputer, "command_computer" );
registerItemModel( ComputerCraft.Blocks.advancedModem, "advanced_modem" ); registerItemModel( ComputerCraft.Blocks.advancedModem, "advanced_modem" );
registerItemModel( ComputerCraft.Blocks.peripheral, 5, "speaker" ); registerItemModel( ComputerCraft.Blocks.peripheral, 5, "speaker" );
registerItemModel( ComputerCraft.Blocks.wiredModemFull, "wired_modem_full" );
registerItemModel( ComputerCraft.Items.disk, "disk" ); registerItemModel( ComputerCraft.Items.disk, "disk" );
registerItemModel( ComputerCraft.Items.diskExpanded, "disk_expanded" ); registerItemModel( ComputerCraft.Items.diskExpanded, "disk_expanded" );
@@ -222,6 +235,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
// Setup renderers // Setup renderers
ClientRegistry.bindTileEntitySpecialRenderer( TileMonitor.class, new TileEntityMonitorRenderer() ); ClientRegistry.bindTileEntitySpecialRenderer( TileMonitor.class, new TileEntityMonitorRenderer() );
ClientRegistry.bindTileEntitySpecialRenderer( TileCable.class, new TileEntityCableRenderer() );
} }
private void registerItemModel( Block block, int damage, String name ) private void registerItemModel( Block block, int damage, String name )
@@ -290,12 +304,6 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
return m_renderFrame; return m_renderFrame;
} }
@Override
public void deleteDisplayLists( int list, int range )
{
GlStateManager.glDeleteLists( list, range );
}
@Override @Override
public Object getFixedWidthFontRenderer() public Object getFixedWidthFontRenderer()
{ {
@@ -314,17 +322,6 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
} }
} }
@Override
public void playRecord( SoundEvent record, String recordInfo, World world, BlockPos pos )
{
Minecraft mc = FMLClientHandler.instance().getClient();
world.playRecord( pos, record );
if( record != null )
{
mc.ingameGUI.setRecordPlayingMessage( recordInfo );
}
}
@Override @Override
public Object getDiskDriveGUI( InventoryPlayer inventory, TileDiskDrive drive ) public Object getDiskDriveGUI( InventoryPlayer inventory, TileDiskDrive drive )
{ {
@@ -371,6 +368,13 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
return null; return null;
} }
@Override
public Object getComputerGUI( IComputer computer, int width, int height, ComputerFamily family )
{
ContainerViewComputer container = new ContainerViewComputer( computer );
return new GuiComputer( container, family, computer, width, height );
}
@Override @Override
public File getWorldDir( World world ) public File getWorldDir( World world )
{ {
@@ -383,7 +387,10 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
switch( packet.m_packetType ) switch( packet.m_packetType )
{ {
case ComputerCraftPacket.ComputerChanged: case ComputerCraftPacket.ComputerChanged:
case ComputerCraftPacket.ComputerTerminalChanged:
case ComputerCraftPacket.ComputerDeleted: case ComputerCraftPacket.ComputerDeleted:
case ComputerCraftPacket.PlayRecord:
case ComputerCraftPacket.PostChat:
{ {
// Packet from Server to Client // Packet from Server to Client
IThreadListener listener = Minecraft.getMinecraft(); IThreadListener listener = Minecraft.getMinecraft();
@@ -417,6 +424,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
// Packets from Server to Client // // Packets from Server to Client //
/////////////////////////////////// ///////////////////////////////////
case ComputerCraftPacket.ComputerChanged: case ComputerCraftPacket.ComputerChanged:
case ComputerCraftPacket.ComputerTerminalChanged:
{ {
int instanceID = packet.m_dataInt[ 0 ]; int instanceID = packet.m_dataInt[ 0 ];
if( !ComputerCraft.clientComputerRegistry.contains( instanceID ) ) if( !ComputerCraft.clientComputerRegistry.contains( instanceID ) )
@@ -435,6 +443,52 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
} }
break; break;
} }
case ComputerCraftPacket.PlayRecord:
{
BlockPos pos = new BlockPos( packet.m_dataInt[ 0 ], packet.m_dataInt[ 1 ], packet.m_dataInt[ 2 ] );
Minecraft mc = Minecraft.getMinecraft();
if( packet.m_dataInt.length > 3 )
{
SoundEvent sound = SoundEvent.REGISTRY.getObjectById( packet.m_dataInt[ 3 ] );
mc.world.playRecord( pos, sound );
mc.ingameGUI.setRecordPlayingMessage( packet.m_dataString[ 0 ] );
}
else
{
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;
}
} }
} }
@@ -442,6 +496,9 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
{ {
ForgeHandlers handlers = new ForgeHandlers(); ForgeHandlers handlers = new ForgeHandlers();
MinecraftForge.EVENT_BUS.register( handlers ); 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 public class ForgeHandlers
@@ -528,6 +585,15 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
m_renderFrame++; m_renderFrame++;
} }
} }
@SubscribeEvent
public void onWorldUnload( WorldEvent.Unload event )
{
if( event.getWorld().isRemote )
{
ClientMonitor.destroyAll();
}
}
} }
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)
@@ -541,7 +607,7 @@ public class ComputerCraftProxyClient extends ComputerCraftProxyCommon
} }
@Override @Override
public int getColorFromItemstack( @Nonnull ItemStack stack, int layer ) public int colorMultiplier( @Nonnull ItemStack stack, int layer )
{ {
return layer == 0 ? 0xFFFFFF : disk.getColour( stack ); return layer == 0 ? 0xFFFFFF : disk.getColour( stack );
} }

View File

@@ -0,0 +1,264 @@
package dan200.computercraft.client.render;
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.computer.core.ClientComputer;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.ItemRenderer;
import net.minecraft.client.renderer.RenderItem;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.texture.TextureMap;
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.ForgeHooksClient;
import net.minecraftforge.client.event.RenderSpecificHandEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.lwjgl.opengl.GL11;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
/**
* Emulates map rendering for pocket computers
*/
@SideOnly(Side.CLIENT)
public class ItemPocketRenderer
{
@SubscribeEvent
public void renderItem( RenderSpecificHandEvent event )
{
ItemStack stack = event.getItemStack();
if( !(stack.getItem() instanceof ItemPocketComputer) ) return;
event.setCanceled( true );
EntityPlayer player = Minecraft.getMinecraft().player;
GlStateManager.pushMatrix();
if( event.getHand() == EnumHand.MAIN_HAND && player.getHeldItemOffhand().isEmpty() )
{
renderItemFirstCentre(
event.getInterpolatedPitch(),
event.getEquipProgress(),
event.getSwingProgress(),
stack
);
}
else
{
renderItemFirstPersonSide(
event.getHand() == EnumHand.MAIN_HAND ? player.getPrimaryHand() : player.getPrimaryHand().opposite(),
event.getEquipProgress(),
event.getSwingProgress(),
stack
);
}
GlStateManager.popMatrix();
}
/**
* The main rendering method for pocket computers and their associated terminal
*
* @param stack The stack to render
* @see ItemRenderer#renderMapFirstPerson(ItemStack)
*/
private void renderPocketComputerItem( ItemStack stack )
{
// Setup various transformations. Note that these are partially adapated from the corresponding method
// in ItemRenderer
GlStateManager.disableLighting();
GlStateManager.rotate( 180f, 0f, 1f, 0f );
GlStateManager.rotate( 180f, 0f, 0f, 1f );
GlStateManager.scale( 0.5, 0.5, 0.5 );
ItemPocketComputer pocketComputer = ComputerCraft.Items.pocketComputer;
ClientComputer computer = pocketComputer.createClientComputer( stack );
{
// First render the background item. We use the item's model rather than a direct texture as this ensures
// we display the pocket light and other such decorations.
GlStateManager.pushMatrix();
GlStateManager.scale( 1.0f, -1.0f, 1.0f );
Minecraft minecraft = Minecraft.getMinecraft();
TextureManager textureManager = minecraft.getTextureManager();
RenderItem renderItem = minecraft.getRenderItem();
// Copy of RenderItem#renderItemModelIntoGUI but without the translation or scaling
textureManager.bindTexture( TextureMap.LOCATION_BLOCKS_TEXTURE );
textureManager.getTexture( TextureMap.LOCATION_BLOCKS_TEXTURE ).setBlurMipmap( false, false );
GlStateManager.enableRescaleNormal();
GlStateManager.enableAlpha();
GlStateManager.alphaFunc( GL11.GL_GREATER, 0.1F );
GlStateManager.enableBlend();
GlStateManager.blendFunc( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA );
GlStateManager.color( 1.0F, 1.0F, 1.0F, 1.0F );
IBakedModel bakedmodel = renderItem.getItemModelWithOverrides( stack, null, null );
bakedmodel = ForgeHooksClient.handleCameraTransforms( bakedmodel, ItemCameraTransforms.TransformType.GUI, false );
renderItem.renderItem( stack, bakedmodel );
GlStateManager.disableAlpha();
GlStateManager.disableRescaleNormal();
GlStateManager.popMatrix();
}
// If we've a computer and terminal then attempt to render it.
if( computer != null )
{
Terminal terminal = computer.getTerminal();
if( terminal != null )
{
synchronized( terminal )
{
GlStateManager.pushMatrix();
GlStateManager.disableDepth();
// Reset the position to be at the top left corner of the pocket computer
// Note we translate towards the screen slightly too.
GlStateManager.translate( -8 / 16.0, -8 / 16.0, 0.5 / 16.0 );
// Translate to the top left of the screen.
GlStateManager.translate( 4 / 16.0, 3 / 16.0, 0 );
// Work out the scaling required to resize the terminal in order to fit on the computer
final int margin = 2;
int tw = terminal.getWidth();
int th = terminal.getHeight();
int width = tw * FONT_WIDTH + margin * 2;
int height = th * FONT_HEIGHT + margin * 2;
int max = Math.max( height, width );
// The grid is 8 * 8 wide, so we start with a base of 1/2 (8 / 16).
double scale = 1.0 / 2.0 / max;
GlStateManager.scale( scale, scale, scale );
// The margin/start positions are determined in order for the terminal to be centred.
int startX = (max - width) / 2 + margin;
int startY = (max - height) / 2 + margin;
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer) ComputerCraft.getFixedWidthFontRenderer();
boolean greyscale = !computer.isColour();
Palette palette = terminal.getPalette();
// Render the actual text
for( int line = 0; line < th; ++line )
{
TextBuffer text = terminal.getLine( line );
TextBuffer colour = terminal.getTextColourLine( line );
TextBuffer backgroundColour = terminal.getBackgroundColourLine( line );
fontRenderer.drawString(
text, startX, startY + line * FONT_HEIGHT,
colour, backgroundColour, margin, margin, greyscale, palette
);
}
// And render the cursor;
int tx = terminal.getCursorX(), ty = terminal.getCursorY();
if( terminal.getCursorBlink() && ComputerCraft.getGlobalCursorBlink() &&
tx >= 0 && ty >= 0 && tx < tw && ty < th )
{
TextBuffer cursorColour = new TextBuffer( "0123456789abcdef".charAt( terminal.getTextColour() ), 1 );
fontRenderer.drawString(
new TextBuffer( '_', 1 ), startX + FONT_WIDTH * tx, startY + FONT_HEIGHT * ty,
cursorColour, null, 0, 0, greyscale, palette
);
}
GlStateManager.enableDepth();
GlStateManager.popMatrix();
}
}
}
GlStateManager.enableLighting();
}
/**
* 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 renderItemFirstPersonSide( 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 );
renderPocketComputerItem( 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 renderItemFirstCentre( 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 );
renderPocketComputerItem( 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

@@ -0,0 +1,264 @@
package dan200.computercraft.client.render;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.EnumFacing;
import net.minecraftforge.client.model.pipeline.IVertexConsumer;
import net.minecraftforge.client.model.pipeline.LightUtil;
import net.minecraftforge.client.model.pipeline.VertexTransformer;
import net.minecraftforge.common.model.TRSRTransformation;
import javax.annotation.Nonnull;
import javax.vecmath.Matrix4f;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;
import java.util.List;
/**
* Transforms vertices of a model, remaining aware of winding order, and rearranging
* vertices if needed.
*/
public final class ModelTransformer
{
private static final Matrix4f identity;
static
{
identity = new Matrix4f();
identity.setIdentity();
}
private ModelTransformer()
{
}
public static void transformQuadsTo( List<BakedQuad> output, List<BakedQuad> input, Matrix4f transform )
{
if( transform == null || transform.equals( identity ) )
{
output.addAll( input );
}
else
{
Matrix4f normalMatrix = new Matrix4f( transform );
normalMatrix.invert();
normalMatrix.transpose();
for( BakedQuad quad : input ) output.add( doTransformQuad( quad, transform, normalMatrix ) );
}
}
public static BakedQuad transformQuad( BakedQuad input, Matrix4f transform )
{
if( transform == null || transform.equals( identity ) ) return input;
Matrix4f normalMatrix = new Matrix4f( transform );
normalMatrix.invert();
normalMatrix.transpose();
return doTransformQuad( input, transform, normalMatrix );
}
private static BakedQuad doTransformQuad( BakedQuad input, Matrix4f positionMatrix, Matrix4f normalMatrix )
{
BakedQuadBuilder builder = new BakedQuadBuilder( input.getFormat() );
NormalAwareTransformer transformer = new NormalAwareTransformer( builder, positionMatrix, normalMatrix );
input.pipe( transformer );
if( transformer.areNormalsInverted() )
{
builder.swap( 1, 3 );
transformer.areNormalsInverted();
}
return builder.build();
}
/**
* A vertex transformer that tracks whether the normals have been inverted and so the vertices
* should be reordered so backface culling works as expected.
*/
private static class NormalAwareTransformer extends VertexTransformer
{
private final Matrix4f positionMatrix;
private final Matrix4f normalMatrix;
private int vertexIndex = 0, elementIndex = 0;
private final Point3f[] before = new Point3f[ 4 ];
private final Point3f[] after = new Point3f[ 4 ];
public NormalAwareTransformer( IVertexConsumer parent, Matrix4f positionMatrix, Matrix4f normalMatrix )
{
super( parent );
this.positionMatrix = positionMatrix;
this.normalMatrix = normalMatrix;
}
@Override
public void setQuadOrientation( EnumFacing orientation )
{
super.setQuadOrientation( orientation == null ? orientation : TRSRTransformation.rotate( positionMatrix, orientation ) );
}
@Override
public void put( int element, @Nonnull float... data )
{
switch( getVertexFormat().getElement( element ).getUsage() )
{
case POSITION:
{
Point3f vec = new Point3f( data );
Point3f newVec = new Point3f();
positionMatrix.transform( vec, newVec );
float[] newData = new float[ 4 ];
newVec.get( newData );
super.put( element, newData );
before[ vertexIndex ] = vec;
after[ vertexIndex ] = newVec;
break;
}
case NORMAL:
{
Vector3f vec = new Vector3f( data );
normalMatrix.transform( vec );
float[] newData = new float[ 4 ];
vec.get( newData );
super.put( element, newData );
break;
}
default:
super.put( element, data );
break;
}
elementIndex++;
if( elementIndex == getVertexFormat().getElementCount() )
{
vertexIndex++;
elementIndex = 0;
}
}
public boolean areNormalsInverted()
{
Vector3f temp1 = new Vector3f(), temp2 = new Vector3f();
Vector3f crossBefore = new Vector3f(), crossAfter = new Vector3f();
// Determine what cross product we expect to have
temp1.sub( before[ 1 ], before[ 0 ] );
temp2.sub( before[ 1 ], before[ 2 ] );
crossBefore.cross( temp1, temp2 );
normalMatrix.transform( crossBefore );
// And determine what cross product we actually have
temp1.sub( after[ 1 ], after[ 0 ] );
temp2.sub( after[ 1 ], after[ 2 ] );
crossAfter.cross( temp1, temp2 );
// If the angle between expected and actual cross product is greater than
// pi/2 radians then we will need to reorder our quads.
return Math.abs( crossBefore.angle( crossAfter ) ) >= Math.PI / 2;
}
}
/**
* A vertex consumer which is capable of building {@link BakedQuad}s.
*
* Equivalent to {@link net.minecraftforge.client.model.pipeline.UnpackedBakedQuad.Builder} but more memory
* efficient.
*
* This also provides the ability to swap vertices through {@link #swap(int, int)} to allow reordering.
*/
private static class BakedQuadBuilder implements IVertexConsumer
{
private final VertexFormat format;
private final int[] vertexData;
private int vertexIndex = 0, elementIndex = 0;
private EnumFacing orientation;
private int quadTint;
private boolean diffuse;
private TextureAtlasSprite texture;
private BakedQuadBuilder( VertexFormat format )
{
this.format = format;
this.vertexData = new int[ format.getSize() ];
}
@Nonnull
@Override
public VertexFormat getVertexFormat()
{
return format;
}
@Override
public void setQuadTint( int tint )
{
this.quadTint = tint;
}
@Override
public void setQuadOrientation( @Nonnull EnumFacing orientation )
{
this.orientation = orientation;
}
@Override
public void setApplyDiffuseLighting( boolean diffuse )
{
this.diffuse = diffuse;
}
@Override
public void setTexture( @Nonnull TextureAtlasSprite texture )
{
this.texture = texture;
}
@Override
public void put( int element, @Nonnull float... data )
{
LightUtil.pack( data, vertexData, format, vertexIndex, element );
elementIndex++;
if( elementIndex == getVertexFormat().getElementCount() )
{
vertexIndex++;
elementIndex = 0;
}
}
public void swap( int a, int b )
{
int length = vertexData.length / 4;
for( int i = 0; i < length; i++ )
{
int temp = vertexData[ a * length + i ];
vertexData[ a * length + i ] = vertexData[ b * length + i ];
vertexData[ b * length + i ] = temp;
}
}
public BakedQuad build()
{
if( elementIndex != 0 || vertexIndex != 4 )
{
throw new IllegalStateException( "Got an unexpected number of elements/vertices" );
}
if( texture == null )
{
throw new IllegalStateException( "Texture has not been set" );
}
return new BakedQuad( vertexData, quadTint, orientation, texture, diffuse, format );
}
}
}

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

@@ -0,0 +1,213 @@
package dan200.computercraft.client.render;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.PeripheralType;
import dan200.computercraft.shared.peripheral.common.BlockCable;
import dan200.computercraft.shared.peripheral.modem.TileCable;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.client.event.DrawBlockHighlightEvent;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import org.lwjgl.opengl.GL11;
public class RenderOverlayCable
{
private static final float EXPAND = 0.002f;
private static final double MIN = TileCable.MIN - EXPAND;
private static final double MAX = TileCable.MAX + EXPAND;
@SubscribeEvent
public void drawHighlight( DrawBlockHighlightEvent event )
{
if( event.getTarget().typeOfHit != RayTraceResult.Type.BLOCK ) return;
BlockPos pos = event.getTarget().getBlockPos();
World world = event.getPlayer().getEntityWorld();
IBlockState state = world.getBlockState( pos );
if( state.getBlock() != ComputerCraft.Blocks.cable ) return;
TileEntity tile = world.getTileEntity( pos );
if( tile == null || !(tile instanceof TileCable) ) return;
event.setCanceled( true );
TileCable cable = (TileCable) tile;
PeripheralType type = cable.getPeripheralType();
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate( GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0 );
GlStateManager.color( 0.0f, 0.0f, 0.0f, 0.4f );
GL11.glLineWidth( 2.0F );
GlStateManager.disableTexture2D();
GlStateManager.depthMask( false );
GlStateManager.pushMatrix();
{
EntityPlayer player = event.getPlayer();
double x = player.lastTickPosX + (player.posX - player.lastTickPosX) * event.getPartialTicks();
double y = player.lastTickPosY + (player.posY - player.lastTickPosY) * event.getPartialTicks();
double z = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * event.getPartialTicks();
GlStateManager.translate( -x + pos.getX(), -y + pos.getY(), -z + pos.getZ() );
}
if( type != PeripheralType.Cable && WorldUtil.isVecInsideInclusive( cable.getModemBounds(), event.getTarget().hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) )
{
RenderGlobal.drawSelectionBoundingBox( cable.getModemBounds(), 0, 0, 0, 0.4f );
}
else
{
int flags = 0;
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
for( EnumFacing facing : EnumFacing.VALUES )
{
if( BlockCable.doesConnectVisually( state, world, pos, facing ) )
{
flags |= 1 << facing.ordinal();
switch( facing.getAxis() )
{
case X:
{
double offset = facing == EnumFacing.WEST ? -EXPAND : 1 + EXPAND;
double centre = facing == EnumFacing.WEST ? MIN : MAX;
buffer.begin( GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION );
buffer.pos( offset, MIN, MIN ).endVertex();
buffer.pos( offset, MAX, MIN ).endVertex();
buffer.pos( offset, MAX, MAX ).endVertex();
buffer.pos( offset, MIN, MAX ).endVertex();
buffer.pos( offset, MIN, MIN ).endVertex();
tessellator.draw();
buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION );
buffer.pos( offset, MIN, MIN ).endVertex();
buffer.pos( centre, MIN, MIN ).endVertex();
buffer.pos( offset, MAX, MIN ).endVertex();
buffer.pos( centre, MAX, MIN ).endVertex();
buffer.pos( offset, MAX, MAX ).endVertex();
buffer.pos( centre, MAX, MAX ).endVertex();
buffer.pos( offset, MIN, MAX ).endVertex();
buffer.pos( centre, MIN, MAX ).endVertex();
tessellator.draw();
break;
}
case Y:
{
double offset = facing == EnumFacing.DOWN ? -EXPAND : 1 + EXPAND;
double centre = facing == EnumFacing.DOWN ? MIN : MAX;
buffer.begin( GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION );
buffer.pos( MIN, offset, MIN ).endVertex();
buffer.pos( MAX, offset, MIN ).endVertex();
buffer.pos( MAX, offset, MAX ).endVertex();
buffer.pos( MIN, offset, MAX ).endVertex();
buffer.pos( MIN, offset, MIN ).endVertex();
tessellator.draw();
buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION );
buffer.pos( MIN, offset, MIN ).endVertex();
buffer.pos( MIN, centre, MIN ).endVertex();
buffer.pos( MAX, offset, MIN ).endVertex();
buffer.pos( MAX, centre, MIN ).endVertex();
buffer.pos( MAX, offset, MAX ).endVertex();
buffer.pos( MAX, centre, MAX ).endVertex();
buffer.pos( MIN, offset, MAX ).endVertex();
buffer.pos( MIN, centre, MAX ).endVertex();
tessellator.draw();
break;
}
case Z:
{
double offset = facing == EnumFacing.NORTH ? -EXPAND : 1 + EXPAND;
double centre = facing == EnumFacing.NORTH ? MIN : MAX;
buffer.begin( GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION );
buffer.pos( MIN, MIN, offset ).endVertex();
buffer.pos( MAX, MIN, offset ).endVertex();
buffer.pos( MAX, MAX, offset ).endVertex();
buffer.pos( MIN, MAX, offset ).endVertex();
buffer.pos( MIN, MIN, offset ).endVertex();
tessellator.draw();
buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION );
buffer.pos( MIN, MIN, offset ).endVertex();
buffer.pos( MIN, MIN, centre ).endVertex();
buffer.pos( MAX, MIN, offset ).endVertex();
buffer.pos( MAX, MIN, centre ).endVertex();
buffer.pos( MAX, MAX, offset ).endVertex();
buffer.pos( MAX, MAX, centre ).endVertex();
buffer.pos( MIN, MAX, offset ).endVertex();
buffer.pos( MIN, MAX, centre ).endVertex();
tessellator.draw();
break;
}
}
}
}
buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION );
draw( buffer, flags, EnumFacing.WEST, EnumFacing.DOWN, EnumFacing.Axis.Z );
draw( buffer, flags, EnumFacing.WEST, EnumFacing.UP, EnumFacing.Axis.Z );
draw( buffer, flags, EnumFacing.EAST, EnumFacing.DOWN, EnumFacing.Axis.Z );
draw( buffer, flags, EnumFacing.EAST, EnumFacing.UP, EnumFacing.Axis.Z );
draw( buffer, flags, EnumFacing.WEST, EnumFacing.NORTH, EnumFacing.Axis.Y );
draw( buffer, flags, EnumFacing.WEST, EnumFacing.SOUTH, EnumFacing.Axis.Y );
draw( buffer, flags, EnumFacing.EAST, EnumFacing.NORTH, EnumFacing.Axis.Y );
draw( buffer, flags, EnumFacing.EAST, EnumFacing.SOUTH, EnumFacing.Axis.Y );
draw( buffer, flags, EnumFacing.DOWN, EnumFacing.NORTH, EnumFacing.Axis.X );
draw( buffer, flags, EnumFacing.DOWN, EnumFacing.SOUTH, EnumFacing.Axis.X );
draw( buffer, flags, EnumFacing.UP, EnumFacing.NORTH, EnumFacing.Axis.X );
draw( buffer, flags, EnumFacing.UP, EnumFacing.SOUTH, EnumFacing.Axis.X );
tessellator.draw();
}
GlStateManager.popMatrix();
GlStateManager.depthMask( true );
GlStateManager.enableTexture2D();
GlStateManager.disableBlend();
}
private static void draw( BufferBuilder buffer, int flags, EnumFacing a, EnumFacing b, EnumFacing.Axis other )
{
if( ((flags >> a.ordinal()) & 1) != ((flags >> b.ordinal()) & 1) ) return;
double offA = a.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE ? MIN : MAX;
double offB = b.getAxisDirection() == EnumFacing.AxisDirection.NEGATIVE ? MIN : MAX;
switch( other )
{
case X:
buffer.pos( MIN, offA, offB ).endVertex();
buffer.pos( MAX, offA, offB ).endVertex();
break;
case Y:
buffer.pos( offA, MIN, offB ).endVertex();
buffer.pos( offA, MAX, offB ).endVertex();
break;
case Z:
buffer.pos( offA, offB, MIN ).endVertex();
buffer.pos( offA, offB, MAX ).endVertex();
break;
}
}
}

View File

@@ -0,0 +1,122 @@
package dan200.computercraft.client.render;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.PeripheralType;
import dan200.computercraft.shared.peripheral.common.BlockCable;
import dan200.computercraft.shared.peripheral.common.BlockCableModemVariant;
import dan200.computercraft.shared.peripheral.modem.TileCable;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.MinecraftForgeClient;
import org.lwjgl.opengl.GL11;
import javax.annotation.Nonnull;
/**
* Render breaking animation only over part of a {@link TileCable}.
*/
public class TileEntityCableRenderer extends TileEntitySpecialRenderer<TileCable>
{
@Override
public void render( @Nonnull TileCable te, double x, double y, double z, float partialTicks, int destroyStage, float alpha )
{
if( destroyStage < 0 ) return;
BlockPos pos = te.getPos();
Minecraft mc = Minecraft.getMinecraft();
RayTraceResult hit = mc.objectMouseOver;
if( hit == null || !hit.getBlockPos().equals( pos ) ) return;
if( MinecraftForgeClient.getRenderPass() != 0 ) return;
World world = te.getWorld();
IBlockState state = world.getBlockState( pos );
Block block = state.getBlock();
if( block != ComputerCraft.Blocks.cable ) return;
state = state.getActualState( world, pos );
if( te.getPeripheralType() != PeripheralType.Cable && WorldUtil.isVecInsideInclusive( te.getModemBounds(), hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) )
{
state = block.getDefaultState().withProperty( BlockCable.Properties.MODEM, state.getValue( BlockCable.Properties.MODEM ) );
}
else
{
state = state.withProperty( BlockCable.Properties.MODEM, BlockCableModemVariant.None );
}
IBakedModel model = mc.getBlockRendererDispatcher().getModelForState( state );
if( model == null ) return;
preRenderDamagedBlocks();
BufferBuilder buffer = Tessellator.getInstance().getBuffer();
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.BLOCK );
buffer.setTranslation( x - pos.getX(), y - pos.getY(), z - pos.getZ() );
buffer.noColor();
ForgeHooksClient.setRenderLayer( block.getRenderLayer() );
// See BlockRendererDispatcher#renderBlockDamage
TextureAtlasSprite breakingTexture = mc.getTextureMapBlocks().getAtlasSprite( "minecraft:blocks/destroy_stage_" + destroyStage );
Minecraft.getMinecraft().getBlockRendererDispatcher().getBlockModelRenderer().renderModel(
world,
ForgeHooksClient.getDamageModel( model, breakingTexture, state, world, pos ),
state, pos, buffer, true
);
ForgeHooksClient.setRenderLayer( BlockRenderLayer.SOLID );
buffer.setTranslation( 0, 0, 0 );
Tessellator.getInstance().draw();
postRenderDamagedBlocks();
}
/**
* @see RenderGlobal#preRenderDamagedBlocks()
*/
private void preRenderDamagedBlocks()
{
GlStateManager.disableLighting();
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate( GlStateManager.SourceFactor.DST_COLOR, GlStateManager.DestFactor.SRC_COLOR, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO );
GlStateManager.enableBlend();
GlStateManager.color( 1.0F, 1.0F, 1.0F, 0.5F );
GlStateManager.doPolygonOffset( -3.0F, -3.0F );
GlStateManager.enablePolygonOffset();
GlStateManager.alphaFunc( 516, 0.1F );
GlStateManager.enableAlpha();
GlStateManager.pushMatrix();
}
/**
* @see RenderGlobal#postRenderDamagedBlocks()
*/
private void postRenderDamagedBlocks()
{
GlStateManager.disableAlpha();
GlStateManager.doPolygonOffset( 0.0F, 0.0F );
GlStateManager.disablePolygonOffset();
GlStateManager.disablePolygonOffset();
GlStateManager.depthMask( true );
GlStateManager.popMatrix();
}
}

View File

@@ -10,7 +10,7 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.FixedWidthFontRenderer; import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer; 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.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.util.Colour; import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.DirectionUtil; 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.Minecraft;
import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; 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 ) private void renderMonitorAt( TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
{ {
// Render from the origin monitor // Render from the origin monitor
TileMonitor origin = monitor.getOrigin(); ClientMonitor originTerminal = monitor.getClientMonitor();
if( origin == null )
{
return;
}
// Ensure each monitor is rendered only once if( originTerminal == null ) return;
long renderFrame = ComputerCraft.getRenderFrame(); TileMonitor origin = originTerminal.getOrigin();
if( origin.m_lastRenderFrame == renderFrame )
{
return;
}
else
{
origin.m_lastRenderFrame = renderFrame;
}
boolean redraw = origin.pollChanged();
BlockPos monitorPos = monitor.getPos(); 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(); BlockPos originPos = origin.getPos();
posX += originPos.getX() - monitorPos.getX(); posX += originPos.getX() - monitorPos.getX();
posY += originPos.getY() - monitorPos.getY(); posY += originPos.getY() - monitorPos.getY();
@@ -82,11 +82,11 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
GlStateManager.rotate( pitch, 1.0f, 0.0f, 0.0f ); GlStateManager.rotate( pitch, 1.0f, 0.0f, 0.0f );
GlStateManager.translate( GlStateManager.translate(
-0.5 + TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN, -0.5 + TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN,
((double)origin.getHeight() - 0.5) - (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN), (origin.getHeight() - 0.5) - (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN),
0.5 0.5
); );
double xSize = (double)origin.getWidth() - 2.0 * ( TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER ); double xSize = origin.getWidth() - 2.0 * ( TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER );
double ySize = (double)origin.getHeight() - 2.0 * ( TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER ); double ySize = origin.getHeight() - 2.0 * ( TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER );
// Get renderers // Get renderers
Minecraft mc = Minecraft.getMinecraft(); Minecraft mc = Minecraft.getMinecraft();
@@ -94,40 +94,40 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
BufferBuilder renderer = tessellator.getBuffer(); BufferBuilder renderer = tessellator.getBuffer();
// Get terminal // Get terminal
ClientTerminal clientTerminal = (ClientTerminal)origin.getTerminal(); boolean redraw = originTerminal.pollTerminalChanged();
Terminal terminal = (clientTerminal != null) ? clientTerminal.getTerminal() : null;
redraw = redraw || (clientTerminal != null && clientTerminal.hasTerminalChanged());
// Draw the contents // Draw the contents
GlStateManager.depthMask( false ); GlStateManager.depthMask( false );
OpenGlHelper.setLightmapTextureCoords( OpenGlHelper.lightmapTexUnit, 0xFF, 0xFF );
GlStateManager.disableLighting(); GlStateManager.disableLighting();
mc.entityRenderer.disableLightmap(); mc.entityRenderer.disableLightmap();
try try
{ {
Terminal terminal = originTerminal.getTerminal();
if( terminal != null ) if( terminal != null )
{ {
Palette palette = terminal.getPalette(); Palette palette = terminal.getPalette();
// Allocate display lists // Allocate display lists
if( origin.m_renderDisplayList < 0 ) if( originTerminal.renderDisplayLists == null )
{ {
origin.m_renderDisplayList = GlStateManager.glGenLists( 3 ); originTerminal.createLists();
redraw = true; redraw = true;
} }
// Draw a terminal // Draw a terminal
boolean greyscale = !clientTerminal.isColour(); boolean greyscale = !originTerminal.isColour();
int width = terminal.getWidth(); int width = terminal.getWidth();
int height = terminal.getHeight(); int height = terminal.getHeight();
int cursorX = terminal.getCursorX(); int cursorX = terminal.getCursorX();
int cursorY = terminal.getCursorY(); int cursorY = terminal.getCursorY();
FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer)ComputerCraft.getFixedWidthFontRenderer(); FixedWidthFontRenderer fontRenderer = (FixedWidthFontRenderer) ComputerCraft.getFixedWidthFontRenderer();
GlStateManager.pushMatrix(); GlStateManager.pushMatrix();
try try
{ {
double xScale = xSize / (double) ( width * FixedWidthFontRenderer.FONT_WIDTH ); double xScale = xSize / ( width * FixedWidthFontRenderer.FONT_WIDTH );
double yScale = ySize / (double) ( height * FixedWidthFontRenderer.FONT_HEIGHT ); double yScale = ySize / ( height * FixedWidthFontRenderer.FONT_HEIGHT );
GlStateManager.scale( xScale, -yScale, 1.0 ); GlStateManager.scale( xScale, -yScale, 1.0 );
// Draw background // Draw background
@@ -135,12 +135,12 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( redraw ) if( redraw )
{ {
// Build background display list // Build background display list
GlStateManager.glNewList( origin.m_renderDisplayList, GL11.GL_COMPILE ); GlStateManager.glNewList( originTerminal.renderDisplayLists[0], GL11.GL_COMPILE );
try try
{ {
double marginXSize = TileMonitor.RENDER_MARGIN / xScale; double marginXSize = TileMonitor.RENDER_MARGIN / xScale;
double marginYSize = TileMonitor.RENDER_MARGIN / yScale; double marginYSize = TileMonitor.RENDER_MARGIN / yScale;
double marginSquash = marginYSize / (double) FixedWidthFontRenderer.FONT_HEIGHT; double marginSquash = marginYSize / FixedWidthFontRenderer.FONT_HEIGHT;
// Top and bottom margins // Top and bottom margins
GlStateManager.pushMatrix(); GlStateManager.pushMatrix();
@@ -149,7 +149,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
GlStateManager.scale( 1.0, marginSquash, 1.0 ); GlStateManager.scale( 1.0, marginSquash, 1.0 );
GlStateManager.translate( 0.0, -marginYSize / marginSquash, 0.0 ); GlStateManager.translate( 0.0, -marginYSize / marginSquash, 0.0 );
fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( 0 ), marginXSize, marginXSize, greyscale, palette ); 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 ); fontRenderer.drawStringBackgroundPart( 0, 0, terminal.getBackgroundColourLine( height - 1 ), marginXSize, marginXSize, greyscale, palette );
} }
finally finally
@@ -174,7 +174,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
GlStateManager.glEndList(); GlStateManager.glEndList();
} }
} }
GlStateManager.callList( origin.m_renderDisplayList ); GlStateManager.callList( originTerminal.renderDisplayLists[0] );
GlStateManager.resetColor(); GlStateManager.resetColor();
// Draw text // Draw text
@@ -182,7 +182,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( redraw ) if( redraw )
{ {
// Build text display list // Build text display list
GlStateManager.glNewList( origin.m_renderDisplayList + 1, GL11.GL_COMPILE ); GlStateManager.glNewList( originTerminal.renderDisplayLists[1], GL11.GL_COMPILE );
try try
{ {
// Lines // Lines
@@ -202,7 +202,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
GlStateManager.glEndList(); GlStateManager.glEndList();
} }
} }
GlStateManager.callList( origin.m_renderDisplayList + 1 ); GlStateManager.callList( originTerminal.renderDisplayLists[1] );
GlStateManager.resetColor(); GlStateManager.resetColor();
// Draw cursor // Draw cursor
@@ -210,7 +210,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
if( redraw ) if( redraw )
{ {
// Build cursor display list // Build cursor display list
GlStateManager.glNewList( origin.m_renderDisplayList + 2, GL11.GL_COMPILE ); GlStateManager.glNewList( originTerminal.renderDisplayLists[2], GL11.GL_COMPILE );
try try
{ {
// Cursor // Cursor
@@ -236,7 +236,7 @@ public class TileEntityMonitorRenderer extends TileEntitySpecialRenderer<TileMon
} }
if( ComputerCraft.getGlobalCursorBlink() ) if( ComputerCraft.getGlobalCursorBlink() )
{ {
GlStateManager.callList( origin.m_renderDisplayList + 2 ); GlStateManager.callList( originTerminal.renderDisplayLists[2] );
GlStateManager.resetColor(); GlStateManager.resetColor();
} }
} }

View File

@@ -9,7 +9,6 @@ package dan200.computercraft.client.render;
import dan200.computercraft.api.turtle.ITurtleUpgrade; import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.IComputer;
import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.entity.TurtleVisionCamera; import dan200.computercraft.shared.turtle.entity.TurtleVisionCamera;
import dan200.computercraft.shared.util.Holiday; import dan200.computercraft.shared.util.Holiday;
@@ -116,47 +115,34 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
// Setup the transform // Setup the transform
Vec3d offset; Vec3d offset;
float yaw; float yaw;
if( turtle != null ) offset = turtle.getRenderOffset( f );
{ yaw = turtle.getRenderYaw( f );
offset = turtle.getRenderOffset( f );
yaw = turtle.getRenderYaw( f );
}
else
{
offset = new Vec3d( 0.0, 0.0, 0.0 );
yaw = 0.0f;
}
GlStateManager.translate( posX + offset.x, posY + offset.y, posZ + offset.z ); GlStateManager.translate( posX + offset.x, posY + offset.y, posZ + offset.z );
// Render the label // Render the label
IComputer computer = (turtle != null) ? turtle.getComputer() : null; String label = turtle.createProxy().getLabel();
String label = (computer != null) ? computer.getLabel() : null;
if( label != null ) if( label != null )
{ {
renderLabel( turtle.getAccess().getPosition(), label ); renderLabel( turtle.getAccess().getPosition(), label );
} }
// Render the turtle // Render the turtle
GlStateManager.translate( 0.5f, 0.0f, 0.5f ); GlStateManager.translate( 0.5f, 0.5f, 0.5f );
GlStateManager.rotate( 180.0f - yaw, 0.0f, 1.0f, 0.0f ); GlStateManager.rotate( 180.0f - yaw, 0.0f, 1.0f, 0.0f );
GlStateManager.translate( -0.5f, 0.0f, -0.5f ); if( label != null && (label.equals( "Dinnerbone" ) || label.equals( "Grumm" )) )
{
// Flip the model and swap the cull face as winding order will have changed.
GlStateManager.scale( 1.0f, -1.0f, 1.0f );
GlStateManager.cullFace( GlStateManager.CullFace.FRONT );
}
GlStateManager.translate( -0.5f, -0.5f, -0.5f );
// Render the turtle // Render the turtle
int colour; int colour;
ComputerFamily family; ComputerFamily family;
ResourceLocation overlay; ResourceLocation overlay;
if( turtle != null ) colour = turtle.getColour();
{ family = turtle.getFamily();
colour = turtle.getColour(); overlay = turtle.getOverlay();
family = turtle.getFamily();
overlay = turtle.getOverlay();
}
else
{
colour = -1;
family = ComputerFamily.Normal;
overlay = null;
}
renderModel( state, getTurtleModel( family, colour != -1 ), colour == -1 ? null : new int[] { colour } ); renderModel( state, getTurtleModel( family, colour != -1 ), colour == -1 ? null : new int[] { colour } );
@@ -183,15 +169,13 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
} }
// Render the upgrades // Render the upgrades
if( turtle != null ) renderUpgrade( state, turtle, TurtleSide.Left, f );
{ renderUpgrade( state, turtle, TurtleSide.Right, f );
renderUpgrade( state, turtle, TurtleSide.Left, f );
renderUpgrade( state, turtle, TurtleSide.Right, f );
}
} }
finally finally
{ {
GlStateManager.popMatrix(); GlStateManager.popMatrix();
GlStateManager.cullFace( GlStateManager.CullFace.BACK );
} }
} }
@@ -310,10 +294,10 @@ public class TileEntityTurtleRenderer extends TileEntitySpecialRenderer<TileTurt
Tessellator tessellator = Tessellator.getInstance(); Tessellator tessellator = Tessellator.getInstance();
BufferBuilder renderer = tessellator.getBuffer(); BufferBuilder renderer = tessellator.getBuffer();
renderer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR ); renderer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR );
renderer.pos( (double) ( -xOffset - 1 ), (double) ( -1 + yOffset ), 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex(); renderer.pos( -xOffset - 1, -1 + yOffset, 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
renderer.pos( (double) ( -xOffset - 1 ), (double) ( 8 + yOffset ), 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex(); renderer.pos( -xOffset - 1, 8 + yOffset, 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
renderer.pos( (double) ( xOffset + 1 ), (double) ( 8 + yOffset ), 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex(); renderer.pos( xOffset + 1, 8 + yOffset, 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
renderer.pos( (double) ( xOffset + 1 ), (double) ( -1 + yOffset ), 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex(); renderer.pos( xOffset + 1, -1 + yOffset, 0.0D ).color( 0.0F, 0.0F, 0.0F, 0.25F ).endVertex();
tessellator.draw(); tessellator.draw();
} }
finally finally

View File

@@ -6,13 +6,10 @@ import net.minecraft.client.renderer.block.model.IBakedModel;
import net.minecraft.client.renderer.block.model.ItemCameraTransforms; import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
import net.minecraft.client.renderer.block.model.ItemOverrideList; import net.minecraft.client.renderer.block.model.ItemOverrideList;
import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.renderer.vertex.VertexFormatElement;
import net.minecraft.util.EnumFacing; import net.minecraft.util.EnumFacing;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.vecmath.Matrix4f; import javax.vecmath.Matrix4f;
import javax.vecmath.Point3f;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -20,16 +17,17 @@ import java.util.Map;
public class TurtleMultiModel implements IBakedModel public class TurtleMultiModel implements IBakedModel
{ {
private IBakedModel m_baseModel; private final IBakedModel m_baseModel;
private IBakedModel m_overlayModel; private final IBakedModel m_overlayModel;
private IBakedModel m_leftUpgradeModel; private final Matrix4f m_generalTransform;
private Matrix4f m_leftUpgradeTransform; private final IBakedModel m_leftUpgradeModel;
private IBakedModel m_rightUpgradeModel; private final Matrix4f m_leftUpgradeTransform;
private Matrix4f m_rightUpgradeTransform; private final IBakedModel m_rightUpgradeModel;
private final Matrix4f m_rightUpgradeTransform;
private List<BakedQuad> m_generalQuads; private List<BakedQuad> m_generalQuads;
private Map<EnumFacing, List<BakedQuad>> m_faceQuads; private Map<EnumFacing, List<BakedQuad>> m_faceQuads;
public TurtleMultiModel( IBakedModel baseModel, IBakedModel overlayModel, IBakedModel leftUpgradeModel, Matrix4f leftUpgradeTransform, IBakedModel rightUpgradeModel, Matrix4f rightUpgradeTransform ) public TurtleMultiModel( IBakedModel baseModel, IBakedModel overlayModel, Matrix4f generalTransform, IBakedModel leftUpgradeModel, Matrix4f leftUpgradeTransform, IBakedModel rightUpgradeModel, Matrix4f rightUpgradeTransform )
{ {
// Get the models // Get the models
m_baseModel = baseModel; m_baseModel = baseModel;
@@ -38,6 +36,7 @@ public class TurtleMultiModel implements IBakedModel
m_leftUpgradeTransform = leftUpgradeTransform; m_leftUpgradeTransform = leftUpgradeTransform;
m_rightUpgradeModel = rightUpgradeModel; m_rightUpgradeModel = rightUpgradeModel;
m_rightUpgradeTransform = rightUpgradeTransform; m_rightUpgradeTransform = rightUpgradeTransform;
m_generalTransform = generalTransform;
m_generalQuads = null; m_generalQuads = null;
m_faceQuads = new HashMap<>(); m_faceQuads = new HashMap<>();
} }
@@ -48,51 +47,52 @@ public class TurtleMultiModel implements IBakedModel
{ {
if( side != null ) if( side != null )
{ {
if( !m_faceQuads.containsKey( side ) ) if( !m_faceQuads.containsKey( side ) ) m_faceQuads.put( side, buildQuads( state, side, rand ) );
{
ArrayList<BakedQuad> quads = new ArrayList<>();
if( m_overlayModel != null )
{
quads.addAll( m_overlayModel.getQuads( state, side, rand ) );
}
if( m_leftUpgradeModel != null )
{
quads.addAll( transformQuads( m_leftUpgradeModel.getQuads( state, side, rand ), m_leftUpgradeTransform ) );
}
if( m_rightUpgradeModel != null )
{
quads.addAll( transformQuads( m_rightUpgradeModel.getQuads( state, side, rand ), m_rightUpgradeTransform ) );
}
quads.trimToSize();
m_faceQuads.put( side, quads );
}
return m_faceQuads.get( side ); return m_faceQuads.get( side );
} }
else else
{ {
if( m_generalQuads == null ) if( m_generalQuads == null ) m_generalQuads = buildQuads( state, side, rand );
{
ArrayList<BakedQuad> quads = new ArrayList<>();
quads.addAll( m_baseModel.getQuads( state, side, rand ) );
if( m_overlayModel != null )
{
quads.addAll( m_overlayModel.getQuads( state, side, rand ) );
}
if( m_leftUpgradeModel != null )
{
quads.addAll( transformQuads( m_leftUpgradeModel.getQuads( state, side, rand ), m_leftUpgradeTransform ) );
}
if( m_rightUpgradeModel != null )
{
quads.addAll( transformQuads( m_rightUpgradeModel.getQuads( state, side, rand ), m_rightUpgradeTransform ) );
}
quads.trimToSize();
m_generalQuads = quads;
}
return m_generalQuads; return m_generalQuads;
} }
} }
private List<BakedQuad> buildQuads( IBlockState state, EnumFacing side, long rand )
{
ArrayList<BakedQuad> quads = new ArrayList<>();
ModelTransformer.transformQuadsTo( quads, m_baseModel.getQuads( state, side, rand ), m_generalTransform );
if( m_overlayModel != null )
{
ModelTransformer.transformQuadsTo( quads, m_overlayModel.getQuads( state, side, rand ), m_generalTransform );
}
if( m_overlayModel != null )
{
ModelTransformer.transformQuadsTo( quads, m_overlayModel.getQuads( state, side, rand ), m_generalTransform );
}
if( m_leftUpgradeModel != null )
{
Matrix4f upgradeTransform = m_generalTransform;
if( m_leftUpgradeTransform != null )
{
upgradeTransform = new Matrix4f( m_generalTransform );
upgradeTransform.mul( m_leftUpgradeTransform );
}
ModelTransformer.transformQuadsTo( quads, m_leftUpgradeModel.getQuads( state, side, rand ), upgradeTransform );
}
if( m_rightUpgradeModel != null )
{
Matrix4f upgradeTransform = m_generalTransform;
if( m_rightUpgradeTransform != null )
{
upgradeTransform = new Matrix4f( m_generalTransform );
upgradeTransform.mul( m_rightUpgradeTransform );
}
ModelTransformer.transformQuadsTo( quads, m_rightUpgradeModel.getQuads( state, side, rand ), upgradeTransform );
}
quads.trimToSize();
return quads;
}
@Override @Override
public boolean isAmbientOcclusion() public boolean isAmbientOcclusion()
{ {
@@ -132,63 +132,4 @@ public class TurtleMultiModel implements IBakedModel
{ {
return ItemOverrideList.NONE; return ItemOverrideList.NONE;
} }
private List<BakedQuad> transformQuads( List<BakedQuad> input, Matrix4f transform )
{
if( transform == null || input.size() == 0 )
{
return input;
}
else
{
List<BakedQuad> output = new ArrayList<>( input.size() );
for( BakedQuad quad : input )
{
output.add( transformQuad( quad, transform ) );
}
return output;
}
}
private BakedQuad transformQuad( BakedQuad quad, Matrix4f transform )
{
int[] vertexData = quad.getVertexData().clone();
int offset = 0;
BakedQuad copy = new BakedQuad( vertexData, -1, quad.getFace(), quad.getSprite(), quad.shouldApplyDiffuseLighting(), quad.getFormat() );
VertexFormat format = copy.getFormat();
for( int i=0; i<format.getElementCount(); ++i ) // For each vertex element
{
VertexFormatElement element = format.getElement( i );
if( element.isPositionElement() &&
element.getType() == VertexFormatElement.EnumType.FLOAT &&
element.getElementCount() == 3 ) // When we find a position element
{
for( int j=0; j<4; ++j ) // For each corner of the quad
{
int start = offset + j * format.getNextOffset();
if( (start % 4) == 0 )
{
start = start / 4;
// Extract the position
Point3f pos = new Point3f(
Float.intBitsToFloat( vertexData[ start ] ),
Float.intBitsToFloat( vertexData[ start + 1 ] ),
Float.intBitsToFloat( vertexData[ start + 2 ] )
);
// Transform the position
transform.transform( pos );
// Insert the position
vertexData[ start ] = Float.floatToRawIntBits( pos.x );
vertexData[ start + 1 ] = Float.floatToRawIntBits( pos.y );
vertexData[ start + 2 ] = Float.floatToRawIntBits( pos.z );
}
}
}
offset += element.getSize();
}
return copy;
}
} }

View File

@@ -35,6 +35,19 @@ import java.util.List;
public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReloadListener public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReloadListener
{ {
private static final Matrix4f s_identity, s_flip;
static
{
s_identity = new Matrix4f();
s_identity.setIdentity();
s_flip = new Matrix4f();
s_flip.setIdentity();
s_flip.m11 = -1; // Flip on the y axis
s_flip.m13 = 1; // Models go from (0,0,0) to (1,1,1), so push back up.
}
private static class TurtleModelCombination private static class TurtleModelCombination
{ {
public final ComputerFamily m_family; public final ComputerFamily m_family;
@@ -43,8 +56,9 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
public final ITurtleUpgrade m_rightUpgrade; public final ITurtleUpgrade m_rightUpgrade;
public final ResourceLocation m_overlay; public final ResourceLocation m_overlay;
public final boolean m_christmas; public final boolean m_christmas;
public final boolean m_flip;
public TurtleModelCombination( ComputerFamily family, boolean colour, ITurtleUpgrade leftUpgrade, ITurtleUpgrade rightUpgrade, ResourceLocation overlay, boolean christmas ) public TurtleModelCombination( ComputerFamily family, boolean colour, ITurtleUpgrade leftUpgrade, ITurtleUpgrade rightUpgrade, ResourceLocation overlay, boolean christmas, boolean flip )
{ {
m_family = family; m_family = family;
m_colour = colour; m_colour = colour;
@@ -52,22 +66,26 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
m_rightUpgrade = rightUpgrade; m_rightUpgrade = rightUpgrade;
m_overlay = overlay; m_overlay = overlay;
m_christmas = christmas; m_christmas = christmas;
m_flip = flip;
} }
@Override @Override
public boolean equals( Object other ) public boolean equals( Object other )
{ {
if( other == this ) { if( other == this )
{
return true; return true;
} }
if( other instanceof TurtleModelCombination ) { if( other instanceof TurtleModelCombination )
TurtleModelCombination otherCombo = (TurtleModelCombination)other; {
TurtleModelCombination otherCombo = (TurtleModelCombination) other;
if( otherCombo.m_family == m_family && if( otherCombo.m_family == m_family &&
otherCombo.m_colour == m_colour && otherCombo.m_colour == m_colour &&
otherCombo.m_leftUpgrade == m_leftUpgrade && otherCombo.m_leftUpgrade == m_leftUpgrade &&
otherCombo.m_rightUpgrade == m_rightUpgrade && otherCombo.m_rightUpgrade == m_rightUpgrade &&
Objects.equal( otherCombo.m_overlay, m_overlay ) && Objects.equal( otherCombo.m_overlay, m_overlay ) &&
otherCombo.m_christmas == m_christmas ) otherCombo.m_christmas == m_christmas &&
otherCombo.m_flip == m_flip )
{ {
return true; return true;
} }
@@ -86,10 +104,11 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
result = prime * result + (m_rightUpgrade != null ? m_rightUpgrade.hashCode() : 0); result = prime * result + (m_rightUpgrade != null ? m_rightUpgrade.hashCode() : 0);
result = prime * result + (m_overlay != null ? m_overlay.hashCode() : 0); result = prime * result + (m_overlay != null ? m_overlay.hashCode() : 0);
result = prime * result + (m_christmas ? 1 : 0); result = prime * result + (m_christmas ? 1 : 0);
result = prime * result + (m_flip ? 1 : 0);
return result; return result;
} }
} }
private HashMap<TurtleModelCombination, IBakedModel> m_cachedModels; private HashMap<TurtleModelCombination, IBakedModel> m_cachedModels;
private ItemOverrideList m_overrides; private ItemOverrideList m_overrides;
private final TurtleModelCombination m_defaultCombination; private final TurtleModelCombination m_defaultCombination;
@@ -97,12 +116,12 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
public TurtleSmartItemModel() public TurtleSmartItemModel()
{ {
m_cachedModels = new HashMap<>(); m_cachedModels = new HashMap<>();
m_defaultCombination = new TurtleModelCombination( ComputerFamily.Normal, false, null, null, null, false ); m_defaultCombination = new TurtleModelCombination( ComputerFamily.Normal, false, null, null, null, false, false );
m_overrides = new ItemOverrideList( new ArrayList<>() ) m_overrides = new ItemOverrideList( new ArrayList<>() )
{ {
@Nonnull @Nonnull
@Override @Override
public IBakedModel handleItemState( @Nonnull IBakedModel originalModel, @Nonnull ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity) public IBakedModel handleItemState( @Nonnull IBakedModel originalModel, @Nonnull ItemStack stack, @Nullable World world, @Nullable EntityLivingBase entity )
{ {
ItemTurtleBase turtle = (ItemTurtleBase) stack.getItem(); ItemTurtleBase turtle = (ItemTurtleBase) stack.getItem();
ComputerFamily family = turtle.getFamily( stack ); ComputerFamily family = turtle.getFamily( stack );
@@ -111,7 +130,9 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
ITurtleUpgrade rightUpgrade = turtle.getUpgrade( stack, TurtleSide.Right ); ITurtleUpgrade rightUpgrade = turtle.getUpgrade( stack, TurtleSide.Right );
ResourceLocation overlay = turtle.getOverlay( stack ); ResourceLocation overlay = turtle.getOverlay( stack );
boolean christmas = HolidayUtil.getCurrentHoliday() == Holiday.Christmas; boolean christmas = HolidayUtil.getCurrentHoliday() == Holiday.Christmas;
TurtleModelCombination combo = new TurtleModelCombination( family, colour != -1, leftUpgrade, rightUpgrade, overlay, christmas ); String label = turtle.getLabel( stack );
boolean flip = label != null && (label.equals( "Dinnerbone" ) || label.equals( "Grumm" ));
TurtleModelCombination combo = new TurtleModelCombination( family, colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip );
if( m_cachedModels.containsKey( combo ) ) if( m_cachedModels.containsKey( combo ) )
{ {
return m_cachedModels.get( combo ); return m_cachedModels.get( combo );
@@ -146,28 +167,25 @@ public class TurtleSmartItemModel implements IBakedModel, IResourceManagerReload
ModelResourceLocation baseModelLocation = TileEntityTurtleRenderer.getTurtleModel( combo.m_family, combo.m_colour ); ModelResourceLocation baseModelLocation = TileEntityTurtleRenderer.getTurtleModel( combo.m_family, combo.m_colour );
ModelResourceLocation overlayModelLocation = TileEntityTurtleRenderer.getTurtleOverlayModel( combo.m_family, combo.m_overlay, combo.m_christmas ); ModelResourceLocation overlayModelLocation = TileEntityTurtleRenderer.getTurtleOverlayModel( combo.m_family, combo.m_overlay, combo.m_christmas );
IBakedModel baseModel = modelManager.getModel( baseModelLocation ); 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> 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; Pair<IBakedModel, Matrix4f> rightModel = (combo.m_rightUpgrade != null) ? combo.m_rightUpgrade.getModel( null, TurtleSide.Right ) : null;
if( leftModel != null && rightModel != null ) if( leftModel != null && rightModel != null )
{ {
return new TurtleMultiModel( baseModel, overlayModel, leftModel.getLeft(), leftModel.getRight(), rightModel.getLeft(), rightModel.getRight() ); return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel.getLeft(), leftModel.getRight(), rightModel.getLeft(), rightModel.getRight() );
} }
else if( leftModel != null ) else if( leftModel != null )
{ {
return new TurtleMultiModel( baseModel, overlayModel, leftModel.getLeft(), leftModel.getRight(), null, null ); return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel.getLeft(), leftModel.getRight(), null, null );
} }
else if( rightModel != null ) else if( rightModel != null )
{ {
return new TurtleMultiModel( baseModel, overlayModel, null, null, rightModel.getLeft(), rightModel.getRight() ); return new TurtleMultiModel( baseModel, overlayModel, transform, null, null, rightModel.getLeft(), rightModel.getRight() );
}
else if( overlayModel != null )
{
return new TurtleMultiModel( baseModel, overlayModel, null, null, null, null );
} }
else else
{ {
return baseModel; return new TurtleMultiModel( baseModel, overlayModel, transform, null, null, null, null );
} }
} }

View File

@@ -45,8 +45,8 @@ public class AddressPredicate
public AddressPredicate( String... filters ) public AddressPredicate( String... filters )
{ {
List<Pattern> wildcards = this.wildcards = new ArrayList<Pattern>(); List<Pattern> wildcards = this.wildcards = new ArrayList<>();
List<HostRange> ranges = this.ranges = new ArrayList<HostRange>(); List<HostRange> ranges = this.ranges = new ArrayList<>();
for( String filter : filters ) for( String filter : filters )
{ {

View File

@@ -1,95 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
// Contributed by Nia
// Based on LuaBit (http://luaforge.net/projects/bit)
public class BitAPI implements ILuaAPI
{
private static final int BNOT = 0;
private static final int BAND = 1;
private static final int BOR = 2;
private static final int BXOR = 3;
private static final int BRSHIFT = 4;
private static final int BLSHIFT = 5;
private static final int BLOGIC_RSHIFT = 6;
public BitAPI( IAPIEnvironment _environment )
{
}
@Override
public String[] getNames()
{
return new String[] {
"bit"
};
}
@Override
public void startup( )
{
}
@Override
public void advance( double _dt )
{
}
@Override
public void shutdown( )
{
}
@Nonnull
@Override
public String[] getMethodNames() {
return new String[] {
"bnot", "band", "bor", "bxor",
"brshift", "blshift", "blogic_rshift"
};
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
int ret = 0;
switch(method) {
case BNOT:
ret = ~getInt( args, 0 );
break;
case BAND:
ret = getInt( args, 0 ) & getInt( args, 1 );
break;
case BOR:
ret = getInt( args, 0 ) | getInt( args, 1 );
break;
case BXOR:
ret = getInt( args, 0 ) ^ getInt( args, 1 );
break;
case BRSHIFT:
ret = getInt( args, 0 ) >> getInt( args, 1 );
break;
case BLSHIFT:
ret = getInt( args, 0 ) << getInt( args, 1 );
break;
case BLOGIC_RSHIFT:
ret = getInt( args, 0 ) >>> getInt( args, 1 );
break;
}
return new Object[]{ ret&0xFFFFFFFFL };
}
}

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject; import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
@@ -100,21 +101,6 @@ public class BufferAPI implements ILuaAPI
}; };
} }
@Override
public void startup()
{
}
@Override
public void advance( double _dt )
{
}
@Override
public void shutdown()
{
}
@Nonnull @Nonnull
@Override @Override
public String[] getMethodNames() public String[] getMethodNames()

View File

@@ -0,0 +1,165 @@
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, IComputerOwned
{
private final IAPIEnvironment m_environment;
private final Set<String> m_mounts = new HashSet<>();
protected ComputerAccess( IAPIEnvironment m_environment )
{
this.m_environment = m_environment;
}
public void unmountAll()
{
FileSystem fileSystem = m_environment.getFileSystem();
for( String m_mount : m_mounts )
{
fileSystem.unmount( m_mount );
}
m_mounts.clear();
}
@Override
public String mount( @Nonnull String desiredLoc, @Nonnull IMount mount )
{
return mount( desiredLoc, mount, getAttachmentName() );
}
@Override
public synchronized String mount( @Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName )
{
// Mount the location
String location;
FileSystem fileSystem = m_environment.getFileSystem();
if( fileSystem == null )
{
throw new IllegalStateException( "File system has not been created" );
}
synchronized( fileSystem )
{
location = findFreeLocation( desiredLoc );
if( location != null )
{
try
{
fileSystem.mount( driveName, location, mount );
}
catch( FileSystemException ignored )
{
}
}
}
if( location != null )
{
m_mounts.add( location );
}
return location;
}
@Override
public String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount )
{
return mountWritable( desiredLoc, mount, getAttachmentName() );
}
@Override
public synchronized String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount, @Nonnull String driveName )
{
// Mount the location
String location;
FileSystem fileSystem = m_environment.getFileSystem();
if( fileSystem == null )
{
throw new IllegalStateException( "File system has not been created" );
}
synchronized( fileSystem )
{
location = findFreeLocation( desiredLoc );
if( location != null )
{
try
{
fileSystem.mountWritable( driveName, location, mount );
}
catch( FileSystemException ignored )
{
}
}
}
if( location != null )
{
m_mounts.add( location );
}
return location;
}
@Override
public void unmount( String location )
{
if( location != null )
{
if( !m_mounts.contains( location ) )
{
throw new RuntimeException( "You didn't mount this location" );
}
m_environment.getFileSystem().unmount( location );
m_mounts.remove( location );
}
}
@Override
public int getID()
{
return m_environment.getComputerID();
}
@Override
public void queueEvent( @Nonnull final String event, final Object[] arguments )
{
m_environment.queueEvent( event, arguments );
}
@Nullable
@Override
public Computer getComputer()
{
return m_environment.getComputer();
}
private String findFreeLocation( String desiredLoc )
{
try
{
FileSystem fileSystem = m_environment.getFileSystem();
if( !fileSystem.exists( desiredLoc ) )
{
return desiredLoc;
}
// We used to check foo2,foo3,foo4,etc here
// but the disk drive does this itself now
return null;
}
catch( FileSystemException e )
{
return null;
}
}
}

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.handles.BinaryInputHandle; import dan200.computercraft.core.apis.handles.BinaryInputHandle;
@@ -14,6 +15,7 @@ import dan200.computercraft.core.apis.handles.EncodedInputHandle;
import dan200.computercraft.core.apis.handles.EncodedOutputHandle; import dan200.computercraft.core.apis.handles.EncodedOutputHandle;
import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException; import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.InputStream; import java.io.InputStream;
@@ -48,11 +50,6 @@ public class FSAPI implements ILuaAPI
m_fileSystem = m_env.getFileSystem(); m_fileSystem = m_env.getFileSystem();
} }
@Override
public void advance( double _dt )
{
}
@Override @Override
public void shutdown( ) public void shutdown( )
{ {
@@ -92,6 +89,7 @@ public class FSAPI implements ILuaAPI
{ {
// list // list
String path = getString( args, 0 ); String path = getString( args, 0 );
m_env.addTrackingChange( TrackingField.FS_OPS );
try { try {
String[] results = m_fileSystem.list( path ); String[] results = m_fileSystem.list( path );
Map<Object,Object> table = new HashMap<>(); Map<Object,Object> table = new HashMap<>();
@@ -166,6 +164,7 @@ public class FSAPI implements ILuaAPI
// makeDir // makeDir
String path = getString( args, 0 ); String path = getString( args, 0 );
try { try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.makeDir( path ); m_fileSystem.makeDir( path );
return null; return null;
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
@@ -178,6 +177,7 @@ public class FSAPI implements ILuaAPI
String path = getString( args, 0 ); String path = getString( args, 0 );
String dest = getString( args, 1 ); String dest = getString( args, 1 );
try { try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.move( path, dest ); m_fileSystem.move( path, dest );
return null; return null;
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
@@ -190,6 +190,7 @@ public class FSAPI implements ILuaAPI
String path = getString( args, 0 ); String path = getString( args, 0 );
String dest = getString( args, 1 ); String dest = getString( args, 1 );
try { try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.copy( path, dest ); m_fileSystem.copy( path, dest );
return null; return null;
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
@@ -201,6 +202,7 @@ public class FSAPI implements ILuaAPI
// delete // delete
String path = getString( args, 0 ); String path = getString( args, 0 );
try { try {
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.delete( path ); m_fileSystem.delete( path );
return null; return null;
} catch( FileSystemException e ) { } catch( FileSystemException e ) {
@@ -212,6 +214,7 @@ public class FSAPI implements ILuaAPI
// open // open
String path = getString( args, 0 ); String path = getString( args, 0 );
String mode = getString( args, 1 ); String mode = getString( args, 1 );
m_env.addTrackingChange( TrackingField.FS_OPS );
try { try {
switch( mode ) switch( mode )
{ {
@@ -292,6 +295,7 @@ public class FSAPI implements ILuaAPI
// find // find
String path = getString( args, 0 ); String path = getString( args, 0 );
try { try {
m_env.addTrackingChange( TrackingField.FS_OPS );
String[] results = m_fileSystem.find( path ); String[] results = m_fileSystem.find( path );
Map<Object,Object> table = new HashMap<>(); Map<Object,Object> table = new HashMap<>();
for(int i=0; i<results.length; ++i ) { for(int i=0; i<results.length; ++i ) {

View File

@@ -6,81 +6,99 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import com.google.common.collect.ImmutableSet;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.http.HTTPCheck; import dan200.computercraft.core.apis.http.*;
import dan200.computercraft.core.apis.http.HTTPRequest;
import dan200.computercraft.core.apis.http.HTTPTask;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.*;
import java.util.concurrent.Future;
import static dan200.computercraft.core.apis.ArgumentHelper.*; import static dan200.computercraft.core.apis.ArgumentHelper.*;
import static dan200.computercraft.core.apis.TableHelper.*;
public class HTTPAPI implements ILuaAPI 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 IAPIEnvironment m_apiEnvironment;
private final List<HTTPTask> m_httpTasks; private final List<Future<?>> m_httpTasks;
private final Set<Closeable> m_closeables;
public HTTPAPI( IAPIEnvironment environment ) public HTTPAPI( IAPIEnvironment environment )
{ {
m_apiEnvironment = environment; m_apiEnvironment = environment;
m_httpTasks = new ArrayList<>(); m_httpTasks = new ArrayList<>();
m_closeables = new HashSet<>();
} }
@Override @Override
public String[] getNames() public String[] getNames()
{ {
return new String[] { return new String[]{
"http" "http"
}; };
} }
@Override @Override
public void startup( ) public void update()
{ {
} // Wait for all of our http requests
@Override
public void advance( double _dt )
{
// Wait for all of our http requests
synchronized( m_httpTasks ) synchronized( m_httpTasks )
{ {
Iterator<HTTPTask> it = m_httpTasks.iterator(); Iterator<Future<?>> it = m_httpTasks.iterator();
while( it.hasNext() ) while( it.hasNext() )
{ {
final HTTPTask h = it.next(); final Future<?> h = it.next();
if( h.isFinished() ) if( h.isDone() ) it.remove();
{
h.whenFinished( m_apiEnvironment );
it.remove();
}
} }
} }
} }
@Override @Override
public void shutdown( ) public void shutdown()
{ {
synchronized( m_httpTasks ) synchronized( m_httpTasks )
{ {
for( HTTPTask r : m_httpTasks ) for( Future<?> r : m_httpTasks )
{ {
r.cancel(); r.cancel( false );
} }
m_httpTasks.clear(); m_httpTasks.clear();
} }
synchronized( m_closeables )
{
for( Closeable x : m_closeables )
{
try
{
x.close();
}
catch( IOException ignored )
{
}
}
m_closeables.clear();
}
} }
@Nonnull @Nonnull
@Override @Override
public String[] getMethodNames() public String[] getMethodNames()
{ {
return new String[] { return new String[]{
"request", "request",
"checkURL" "checkURL",
"websocket",
}; };
} }
@@ -89,52 +107,68 @@ public class HTTPAPI implements ILuaAPI
{ {
switch( method ) switch( method )
{ {
case 0: case 0: // request
{ {
// request String urlString, postString, requestMethod;
// Get URL Map<Object, Object> headerTable;
String urlString = getString( args, 0 ); boolean binary, redirect;
// Get POST if( args.length >= 1 && args[0] instanceof Map )
String postString = optString( args, 1, null );
// Get Headers
Map<String, String> headers = null;
Map<Object, Object> table = optTable( args, 2, null );
if( table != null )
{ {
headers = new HashMap<>( table.size() ); Map<?, ?> options = (Map) args[0];
for( Object key : table.keySet() ) 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 ) 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( requestMethod != null && !HTTP_METHODS.contains( requestMethod ) )
if( args.length >= 4 )
{ {
binary = args[ 3 ] != null && !args[ 3 ].equals( Boolean.FALSE ); throw new LuaException( "Unsupported HTTP method" );
} }
// Make the request // Make the request
try try
{ {
URL url = HTTPRequest.checkURL( urlString ); URL url = HTTPRequest.checkURL( urlString );
HTTPRequest request = new HTTPRequest( urlString, url, postString, headers, binary ); HTTPRequest request = new HTTPRequest( m_apiEnvironment, urlString, url, postString, headers, binary, requestMethod, redirect );
synchronized( m_httpTasks ) synchronized( m_httpTasks )
{ {
m_httpTasks.add( HTTPTask.submit( request ) ); m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( request ) );
} }
return new Object[] { true }; return new Object[]{ true };
} }
catch( HTTPRequestException e ) catch( HTTPRequestException e )
{ {
return new Object[] { false, e.getMessage() }; return new Object[]{ false, e.getMessage() };
} }
} }
case 1: case 1:
@@ -147,15 +181,53 @@ public class HTTPAPI implements ILuaAPI
try try
{ {
URL url = HTTPRequest.checkURL( urlString ); URL url = HTTPRequest.checkURL( urlString );
HTTPCheck check = new HTTPCheck( urlString, url ); HTTPCheck check = new HTTPCheck( m_apiEnvironment, urlString, url );
synchronized( m_httpTasks ) { synchronized( m_httpTasks )
m_httpTasks.add( HTTPTask.submit( check ) ); {
m_httpTasks.add( HTTPExecutor.EXECUTOR.submit( check ) );
} }
return new Object[] { true }; return new Object[]{ true };
} }
catch( HTTPRequestException e ) catch( HTTPRequestException e )
{ {
return new Object[] { false, e.getMessage() }; return new Object[]{ false, e.getMessage() };
}
}
case 2: // websocket
{
String address = getString( args, 0 );
Map<Object, Object> headerTbl = optTable( args, 1, Collections.emptyMap() );
HashMap<String, String> headers = new HashMap<String, String>( headerTbl.size() );
for( Object key : headerTbl.keySet() )
{
Object value = headerTbl.get( key );
if( key instanceof String && value instanceof String )
{
headers.put( (String) key, (String) value );
}
}
if( !ComputerCraft.http_websocket_enable )
{
throw new LuaException( "Websocket connections are disabled" );
}
try
{
URI uri = WebsocketConnector.checkURI( address );
int port = WebsocketConnector.getPort( uri );
Future<?> connector = WebsocketConnector.createConnector( m_apiEnvironment, this, uri, address, port, headers );
synchronized( m_httpTasks )
{
m_httpTasks.add( connector );
}
return new Object[]{ true };
}
catch( HTTPRequestException e )
{
return new Object[]{ false, e.getMessage() };
} }
} }
default: default:
@@ -164,4 +236,20 @@ public class HTTPAPI implements ILuaAPI
} }
} }
} }
public void addCloseable( Closeable closeable )
{
synchronized( m_closeables )
{
m_closeables.add( closeable );
}
}
public void removeCloseable( Closeable closeable )
{
synchronized( m_closeables )
{
m_closeables.remove( closeable );
}
}
} }

View File

@@ -1,245 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import com.google.common.base.Joiner;
import com.google.common.io.ByteStreams;
import dan200.computercraft.ComputerCraft;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class HTTPRequest
{
public static URL checkURL( String urlString ) throws HTTPRequestException
{
URL url;
try
{
url = new URL( urlString );
}
catch( MalformedURLException e )
{
throw new HTTPRequestException( "URL malformed" );
}
// Validate the URL
String protocol = url.getProtocol().toLowerCase();
if( !protocol.equals("http") && !protocol.equals("https") )
{
throw new HTTPRequestException( "URL not http" );
}
// Compare the URL to the whitelist
if( !ComputerCraft.http_whitelist.matches( url.getHost() ) || ComputerCraft.http_blacklist.matches( url.getHost() ) )
{
throw new HTTPRequestException( "Domain not permitted" );
}
return url;
}
public HTTPRequest( String url, final String postText, final Map<String, String> headers, boolean binary ) throws HTTPRequestException
{
// Parse the URL
m_urlString = url;
m_url = checkURL( m_urlString );
m_binary = binary;
// Start the thread
m_cancelled = false;
m_complete = false;
m_success = false;
m_result = null;
m_responseCode = -1;
Thread thread = new Thread( () ->
{
try
{
// Connect to the URL
HttpURLConnection connection = (HttpURLConnection)m_url.openConnection();
if( postText != null )
{
connection.setRequestMethod( "POST" );
connection.setDoOutput( true );
}
else
{
connection.setRequestMethod( "GET" );
}
// Set headers
connection.setRequestProperty( "accept-charset", "UTF-8" );
if( postText != null )
{
connection.setRequestProperty( "content-type", "application/x-www-form-urlencoded; charset=utf-8" );
connection.setRequestProperty( "content-encoding", "UTF-8" );
}
if( headers != null )
{
for( Map.Entry<String, String> header : headers.entrySet() )
{
connection.setRequestProperty( header.getKey(), header.getValue() );
}
}
// Send POST text
if( postText != null )
{
OutputStream os = connection.getOutputStream();
OutputStreamWriter osw;
try
{
osw = new OutputStreamWriter( os, "UTF-8" );
}
catch( UnsupportedEncodingException e )
{
osw = new OutputStreamWriter( os );
}
BufferedWriter writer = new BufferedWriter( osw );
writer.write( postText, 0, postText.length() );
writer.close();
}
// Read response
InputStream is;
int code = connection.getResponseCode();
boolean responseSuccess;
if (code >= 200 && code < 400) {
is = connection.getInputStream();
responseSuccess = true;
} else {
is = connection.getErrorStream();
responseSuccess = false;
}
byte[] result = ByteStreams.toByteArray( is );
is.close();
synchronized( m_lock )
{
if( m_cancelled )
{
// We cancelled
m_complete = true;
m_success = false;
m_result = null;
}
else
{
// We completed
m_complete = true;
m_success = responseSuccess;
m_result = result;
m_responseCode = connection.getResponseCode();
m_encoding = connection.getContentEncoding();
Joiner joiner = Joiner.on( ',' );
Map<String, String> headers1 = m_responseHeaders = new HashMap<>();
for (Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
headers1.put(header.getKey(), joiner.join( header.getValue() ));
}
}
}
connection.disconnect(); // disconnect
}
catch( IOException e )
{
synchronized( m_lock )
{
// There was an error
m_complete = true;
m_success = false;
m_result = null;
}
}
} );
thread.setDaemon(true);
thread.start();
}
public String getURL() {
return m_urlString;
}
public void cancel()
{
synchronized(m_lock) {
m_cancelled = true;
}
}
public boolean isComplete()
{
synchronized(m_lock) {
return m_complete;
}
}
public int getResponseCode() {
synchronized(m_lock) {
return m_responseCode;
}
}
public Map<String, String> getResponseHeaders() {
synchronized (m_lock) {
return m_responseHeaders;
}
}
public boolean wasSuccessful()
{
synchronized(m_lock) {
return m_success;
}
}
public boolean isBinary()
{
return m_binary;
}
public InputStream getContents()
{
byte[] result;
synchronized(m_lock) {
result = m_result;
}
if( result != null ) {
return new ByteArrayInputStream( result );
}
return null;
}
public String getEncoding() {
return m_encoding;
}
private final Object m_lock = new Object();
private final URL m_url;
private final String m_urlString;
private boolean m_complete;
private boolean m_cancelled;
private boolean m_success;
private String m_encoding;
private byte[] m_result;
private boolean m_binary;
private int m_responseCode;
private Map<String, String> m_responseHeaders;
}

View File

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

View File

@@ -1,17 +1,16 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaObject;
public interface ILuaAPI extends ILuaObject /**
* This exists purely to ensure binary compatibility.
*
* @see dan200.computercraft.api.lua.ILuaAPI
*/
public interface ILuaAPI extends dan200.computercraft.api.lua.ILuaAPI
{ {
String[] getNames(); void advance( double v );
void startup(); // LT default void update()
void advance( double _dt ); // MT {
void shutdown(); // LT advance( 0.05 );
}
} }

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.shared.util.StringUtil; import dan200.computercraft.shared.util.StringUtil;
@@ -52,8 +53,8 @@ public class OSAPI implements ILuaAPI
@Override @Override
public int compareTo( @Nonnull Alarm o ) public int compareTo( @Nonnull Alarm o )
{ {
double t = (double)m_day * 24.0 + m_time; double t = m_day * 24.0 + m_time;
double ot = (double)m_day * 24.0 + m_time; double ot = m_day * 24.0 + m_time;
if( t < ot ) { if( t < ot ) {
return -1; return -1;
} else if( t > ot ) { } else if( t > ot ) {
@@ -102,7 +103,7 @@ public class OSAPI implements ILuaAPI
} }
@Override @Override
public void advance( double dt ) public void update()
{ {
synchronized( m_timers ) synchronized( m_timers )
{ {
@@ -135,13 +136,13 @@ public class OSAPI implements ILuaAPI
if( time > previousTime || day > previousDay ) if( time > previousTime || day > previousDay )
{ {
double now = (double)m_day * 24.0 + m_time; double now = m_day * 24.0 + m_time;
Iterator<Map.Entry<Integer, Alarm>> it = m_alarms.entrySet().iterator(); Iterator<Map.Entry<Integer, Alarm>> it = m_alarms.entrySet().iterator();
while( it.hasNext() ) while( it.hasNext() )
{ {
Map.Entry<Integer, Alarm> entry = it.next(); Map.Entry<Integer, Alarm> entry = it.next();
Alarm alarm = entry.getValue(); Alarm alarm = entry.getValue();
double t = (double)alarm.m_day * 24.0 + alarm.m_time; double t = alarm.m_day * 24.0 + alarm.m_time;
if( now >= t ) if( now >= t )
{ {
queueLuaEvent( "alarm", new Object[]{ entry.getKey() } ); queueLuaEvent( "alarm", new Object[]{ entry.getKey() } );
@@ -196,8 +197,8 @@ public class OSAPI implements ILuaAPI
private float getTimeForCalendar(Calendar c) private float getTimeForCalendar(Calendar c)
{ {
float time = c.get(Calendar.HOUR_OF_DAY); float time = c.get(Calendar.HOUR_OF_DAY);
time += (float)c.get(Calendar.MINUTE) / 60.0f; time += c.get(Calendar.MINUTE) / 60.0f;
time += (float)c.get(Calendar.SECOND) / (60.0f * 60.0f); time += c.get(Calendar.SECOND) / (60.0f * 60.0f);
return time; return time;
} }
@@ -296,7 +297,7 @@ public class OSAPI implements ILuaAPI
// clock // clock
synchronized( m_timers ) synchronized( m_timers )
{ {
return new Object[] { (double)m_clock * 0.05 }; return new Object[] { m_clock * 0.05 };
} }
} }
case 11: case 11:

View File

@@ -8,24 +8,27 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ComputerThread; import dan200.computercraft.core.computer.ComputerThread;
import dan200.computercraft.core.computer.ITask; import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.tracking.TrackingField;
import dan200.computercraft.core.filesystem.FileSystemException;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.*; import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static dan200.computercraft.core.apis.ArgumentHelper.getString; import static dan200.computercraft.core.apis.ArgumentHelper.getString;
public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChangeListener public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChangeListener
{ {
private class PeripheralWrapper implements IComputerAccess private class PeripheralWrapper extends ComputerAccess
{ {
private final String m_side; private final String m_side;
private final IPeripheral m_peripheral; private final IPeripheral m_peripheral;
@@ -34,74 +37,71 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
private String[] m_methods; private String[] m_methods;
private Map<String, Integer> m_methodMap; private Map<String, Integer> m_methodMap;
private boolean m_attached; private boolean m_attached;
private Set<String> m_mounts;
public PeripheralWrapper( IPeripheral peripheral, String side ) public PeripheralWrapper( IPeripheral peripheral, String side )
{ {
super(m_environment);
m_side = side; m_side = side;
m_peripheral = peripheral; m_peripheral = peripheral;
m_attached = false; m_attached = false;
m_type = peripheral.getType(); m_type = peripheral.getType();
m_methods = peripheral.getMethodNames(); m_methods = peripheral.getMethodNames();
assert( m_type != null ); assert( m_type != null );
assert( m_methods != null ); assert( m_methods != null );
m_methodMap = new HashMap<>(); m_methodMap = new HashMap<>();
for(int i=0; i<m_methods.length; ++i ) { for(int i=0; i<m_methods.length; ++i ) {
if( m_methods[i] != null ) { if( m_methods[i] != null ) {
m_methodMap.put( m_methods[i], i ); m_methodMap.put( m_methods[i], i );
} }
} }
m_mounts = new HashSet<>();
} }
public IPeripheral getPeripheral() public IPeripheral getPeripheral()
{ {
return m_peripheral; return m_peripheral;
} }
public String getType() public String getType()
{ {
return m_type; return m_type;
} }
public String[] getMethods() public String[] getMethods()
{ {
return m_methods; return m_methods;
} }
public synchronized boolean isAttached() public synchronized boolean isAttached()
{ {
return m_attached; return m_attached;
} }
public synchronized void attach() public synchronized void attach()
{ {
m_attached = true; m_attached = true;
m_peripheral.attach( this ); m_peripheral.attach( this );
} }
public synchronized void detach() public void detach()
{ {
// Call detach // Call detach
m_peripheral.detach( this ); m_peripheral.detach( this );
m_attached = false;
synchronized( this )
// Unmount everything the detach function forgot to do
for( String m_mount : m_mounts )
{ {
m_fileSystem.unmount( m_mount ); // Unmount everything the detach function forgot to do
unmountAll();
} }
m_mounts.clear();
m_attached = false;
} }
public Object[] call( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException public Object[] call( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException
{ {
int method = -1; int method = -1;
synchronized( this ) synchronized( this )
{ {
if( m_methodMap.containsKey( methodName ) ) if( m_methodMap.containsKey( methodName ) )
{ {
@@ -110,6 +110,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
if( method >= 0 ) if( method >= 0 )
{ {
m_environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
return m_peripheral.callMethod( this, context, method, arguments ); return m_peripheral.callMethod( this, context, method, arguments );
} }
else else
@@ -119,13 +120,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
// IComputerAccess implementation // IComputerAccess implementation
@Override
public String mount( @Nonnull String desiredLoc, @Nonnull IMount mount )
{
return mount( desiredLoc, mount, m_side );
}
@Override @Override
public synchronized String mount( @Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName ) public synchronized String mount( @Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName )
{ {
@@ -133,32 +127,8 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
{ {
throw new RuntimeException( "You are not attached to this Computer" ); throw new RuntimeException( "You are not attached to this Computer" );
} }
// Mount the location
String location;
synchronized( m_fileSystem )
{
location = findFreeLocation( desiredLoc );
if( location != null )
{
try {
m_fileSystem.mount( driveName, location, mount );
} catch( FileSystemException e ) {
// fail and return null
}
}
}
if( location != null )
{
m_mounts.add( location );
}
return location;
}
@Override return super.mount( desiredLoc, mount, driveName );
public String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount )
{
return mountWritable( desiredLoc, mount, m_side );
} }
@Override @Override
@@ -168,77 +138,94 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
{ {
throw new RuntimeException( "You are not attached to this Computer" ); throw new RuntimeException( "You are not attached to this Computer" );
} }
// Mount the location return super.mountWritable( desiredLoc, mount, driveName );
String location;
synchronized( m_fileSystem )
{
location = findFreeLocation( desiredLoc );
if( location != null )
{
try {
m_fileSystem.mountWritable( driveName, location, mount );
} catch( FileSystemException e ) {
// fail and return null
}
}
}
if( location != null )
{
m_mounts.add( location );
}
return location;
} }
@Override @Override
public synchronized void unmount( String location ) public synchronized void unmount( String location )
{ {
if( !m_attached ) { if( !m_attached )
throw new RuntimeException( "You are not attached to this Computer" );
}
if( location != null )
{ {
if( !m_mounts.contains( location ) ) {
throw new RuntimeException( "You didn't mount this location" );
}
m_fileSystem.unmount( location );
m_mounts.remove( location );
}
}
@Override
public synchronized int getID()
{
if( !m_attached ) {
throw new RuntimeException( "You are not attached to this Computer" ); throw new RuntimeException( "You are not attached to this Computer" );
} }
return m_environment.getComputerID();
super.unmount( location );
} }
@Override @Override
public synchronized void queueEvent( @Nonnull final String event, final Object[] arguments ) public int getID()
{ {
if( !m_attached ) { if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" ); throw new RuntimeException( "You are not attached to this Computer" );
} }
m_environment.queueEvent( event, arguments ); return super.getID();
} }
@Override
public void queueEvent( @Nonnull final String event, final Object[] arguments )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
super.queueEvent( event, arguments );
}
@Nonnull @Nonnull
@Override @Override
public synchronized String getAttachmentName() public String getAttachmentName()
{ {
if( !m_attached ) { if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" ); throw new RuntimeException( "You are not attached to this Computer" );
} }
return m_side; 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 final IAPIEnvironment m_environment;
private FileSystem m_fileSystem;
private final PeripheralWrapper[] m_peripherals; private final PeripheralWrapper[] m_peripherals;
private boolean m_running; private boolean m_running;
@@ -246,16 +233,16 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
{ {
m_environment = _environment; m_environment = _environment;
m_environment.setPeripheralChangeListener( this ); m_environment.setPeripheralChangeListener( this );
m_peripherals = new PeripheralWrapper[6]; m_peripherals = new PeripheralWrapper[6];
for(int i=0; i<6; ++i) for(int i=0; i<6; ++i)
{ {
m_peripherals[i] = null; m_peripherals[i] = null;
} }
m_running = false; m_running = false;
} }
// IPeripheralChangeListener // IPeripheralChangeListener
@Override @Override
@@ -282,11 +269,11 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
} }
}, null); }, null);
// Queue a detachment event // Queue a detachment event
m_environment.queueEvent( "peripheral_detach", new Object[] { Computer.s_sideNames[side] } ); m_environment.queueEvent( "peripheral_detach", new Object[] { Computer.s_sideNames[side] } );
} }
// Assign the new peripheral // Assign the new peripheral
if( newPeripheral != null ) if( newPeripheral != null )
{ {
@@ -296,7 +283,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
{ {
m_peripherals[side] = null; m_peripherals[side] = null;
} }
if( m_peripherals[side] != null ) if( m_peripherals[side] != null )
{ {
// Queue an attachment // Queue an attachment
@@ -318,7 +305,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
} }
}, null ); }, null );
// Queue an attachment event // Queue an attachment event
m_environment.queueEvent( "peripheral", new Object[] { Computer.s_sideNames[side] } ); m_environment.queueEvent( "peripheral", new Object[] { Computer.s_sideNames[side] } );
} }
@@ -326,7 +313,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
// ILuaAPI implementation // ILuaAPI implementation
@Override @Override
public String[] getNames() public String[] getNames()
{ {
@@ -340,7 +327,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
{ {
synchronized( m_peripherals ) synchronized( m_peripherals )
{ {
m_fileSystem = m_environment.getFileSystem();
m_running = true; m_running = true;
for( int i=0; i<6; ++i ) for( int i=0; i<6; ++i )
{ {
@@ -352,12 +338,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
} }
} }
@Override
public void advance( double _dt )
{
}
@Override @Override
public void shutdown( ) public void shutdown( )
{ {
@@ -372,7 +353,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
wrapper.detach(); wrapper.detach();
} }
} }
m_fileSystem = null;
} }
} }
@@ -464,8 +444,8 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
// call // call
int side = parseSide( args ); int side = parseSide( args );
String methodName = getString( args, 1 ); String methodName = getString( args, 1 );
Object[] methodArgs = trimArray( args, 2 ); Object[] methodArgs = Arrays.copyOfRange( args, 2, args.length );
if( side >= 0 ) if( side >= 0 )
{ {
PeripheralWrapper p; PeripheralWrapper p;
@@ -486,13 +466,8 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
} }
} }
// Privates
private Object[] trimArray( Object[] array, int skip ) // Privates
{
return Arrays.copyOfRange( array, skip, array.length );
}
private int parseSide( Object[] args ) throws LuaException private int parseSide( Object[] args ) throws LuaException
{ {
@@ -506,25 +481,4 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
return -1; return -1;
} }
private String findFreeLocation( String desiredLoc )
{
try
{
synchronized( m_fileSystem )
{
if( !m_fileSystem.exists( desiredLoc ) )
{
return desiredLoc;
}
// We used to check foo2,foo3,foo4,etc here
// but the disk drive does this itself now
return null;
}
}
catch( FileSystemException e )
{
return null;
}
}
} }

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.computer.Computer;
@@ -33,21 +34,6 @@ public class RedstoneAPI implements ILuaAPI
}; };
} }
@Override
public void startup( )
{
}
@Override
public void advance( double _dt )
{
}
@Override
public void shutdown( )
{
}
@Nonnull @Nonnull
@Override @Override
public String[] getMethodNames() public String[] getMethodNames()

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,6 +6,7 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.IComputerEnvironment; import dan200.computercraft.core.computer.IComputerEnvironment;
@@ -36,21 +37,6 @@ public class TermAPI implements ILuaAPI
}; };
} }
@Override
public void startup( )
{
}
@Override
public void advance( double _dt )
{
}
@Override
public void shutdown( )
{
}
@Nonnull @Nonnull
@Override @Override
public String[] getMethodNames() public String[] getMethodNames()

View File

@@ -5,6 +5,7 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
@@ -13,6 +14,8 @@ import static dan200.computercraft.core.apis.ArgumentHelper.getInt;
public class BinaryInputHandle extends HandleGeneric public class BinaryInputHandle extends HandleGeneric
{ {
private static final int BUFFER_SIZE = 8192;
private final InputStream m_stream; private final InputStream m_stream;
public BinaryInputHandle( InputStream reader ) public BinaryInputHandle( InputStream reader )
@@ -45,16 +48,46 @@ public class BinaryInputHandle extends HandleGeneric
if( args.length > 0 && args[ 0 ] != null ) if( args.length > 0 && args[ 0 ] != null )
{ {
int count = getInt( args, 0 ); 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 ]; if( read < 0 ) return null;
count = m_stream.read( bytes ); if( read < count ) bytes = Arrays.copyOf( bytes, read );
if( count < 0 ) return null; return new Object[] { bytes };
if( count < bytes.length ) bytes = Arrays.copyOf( bytes, count ); }
return new Object[] { bytes }; else
{
byte[] buffer = new byte[ BUFFER_SIZE ];
// Read the initial set of bytes, failing if none are read.
int read = m_stream.read( buffer, 0, Math.min( buffer.length, count ) );
if( read == -1 ) return null;
ByteArrayOutputStream out = new ByteArrayOutputStream( read );
count -= read;
out.write( buffer, 0, read );
// Otherwise read until we either reach the limit or we no longer consume
// the full buffer.
while( read >= buffer.length && count > 0 )
{
read = m_stream.read( buffer, 0, Math.min( BUFFER_SIZE, count ) );
if( read == -1 ) break;
count -= read;
out.write( buffer, 0, read );
}
return new Object[] { out.toByteArray() };
}
} }
else else
{ {

View File

@@ -6,8 +6,12 @@ import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.*; import java.io.*;
import static dan200.computercraft.core.apis.ArgumentHelper.optInt;
public class EncodedInputHandle extends HandleGeneric public class EncodedInputHandle extends HandleGeneric
{ {
private static final int BUFFER_SIZE = 8192;
private final BufferedReader m_reader; private final BufferedReader m_reader;
public EncodedInputHandle( BufferedReader reader ) public EncodedInputHandle( BufferedReader reader )
@@ -49,6 +53,7 @@ public class EncodedInputHandle extends HandleGeneric
"readLine", "readLine",
"readAll", "readAll",
"close", "close",
"read",
}; };
} }
@@ -102,6 +107,56 @@ public class EncodedInputHandle extends HandleGeneric
// close // close
close(); close();
return null; return null;
case 3:
// read
checkOpen();
try
{
int count = optInt( args, 0, 1 );
if( count < 0 )
{
// Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so
// it seems best to remain somewhat consistent.
throw new LuaException( "Cannot read a negative number of characters" );
}
else if( count <= BUFFER_SIZE )
{
// If we've got a small count, then allocate that and read it.
char[] chars = new char[ count ];
int read = m_reader.read( chars );
return read < 0 ? null : new Object[] { new String( chars, 0, read ) };
}
else
{
// If we've got a large count, read in bunches of 8192.
char[] buffer = new char[ BUFFER_SIZE ];
// Read the initial set of characters, failing if none are read.
int read = m_reader.read( buffer, 0, Math.min( buffer.length, count ) );
if( read == -1 ) return null;
StringBuilder out = new StringBuilder( read );
count -= read;
out.append( buffer, 0, read );
// Otherwise read until we either reach the limit or we no longer consume
// the full buffer.
while( read >= BUFFER_SIZE && count > 0 )
{
read = m_reader.read( buffer, 0, Math.min( BUFFER_SIZE, count ) );
if( read == -1 ) break;
count -= read;
out.append( buffer, 0, read );
}
return new Object[] { out.toString() };
}
}
catch( IOException e )
{
return null;
}
default: default:
return null; return null;
} }

View File

@@ -1,18 +1,18 @@
package dan200.computercraft.core.apis.http; package dan200.computercraft.core.apis.http;
import dan200.computercraft.core.apis.HTTPRequestException;
import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.apis.IAPIEnvironment;
import java.net.URL; import java.net.URL;
public class HTTPCheck implements HTTPTask.IHTTPTask public class HTTPCheck implements Runnable
{ {
private final IAPIEnvironment environment;
private final String urlString; private final String urlString;
private final URL url; private final URL url;
private String error;
public HTTPCheck( String urlString, URL url ) public HTTPCheck( IAPIEnvironment environment, String urlString, URL url )
{ {
this.environment = environment;
this.urlString = urlString; this.urlString = urlString;
this.url = url; this.url = url;
} }
@@ -22,24 +22,12 @@ public class HTTPCheck implements HTTPTask.IHTTPTask
{ {
try try
{ {
HTTPRequest.checkHost( url ); HTTPRequest.checkHost( url.getHost() );
environment.queueEvent( "http_check", new Object[] { urlString, true } );
} }
catch( HTTPRequestException e ) catch( HTTPRequestException e )
{ {
error = e.getMessage(); environment.queueEvent( "http_check", new Object[] { urlString, false, e.getMessage() } );
}
}
@Override
public void whenFinished( IAPIEnvironment environment )
{
if( error == null )
{
environment.queueEvent( "http_check", new Object[] { urlString, true } );
}
else
{
environment.queueEvent( "http_check", new Object[] { urlString, false, error } );
} }
} }
} }

View File

@@ -0,0 +1,45 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.http;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Just a shared object for executing simple HTTP related tasks.
*/
public final class HTTPExecutor
{
public static final ListeningExecutorService EXECUTOR = MoreExecutors.listeningDecorator( new ThreadPoolExecutor(
4, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setDaemon( true )
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
.setNameFormat( "ComputerCraft-HTTP-%d" )
.build()
) );
public static final EventLoopGroup LOOP_GROUP = new NioEventLoopGroup( 4, new ThreadFactoryBuilder()
.setDaemon( true )
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
.setNameFormat( "ComputerCraft-Netty-%d" )
.build()
);
private HTTPExecutor()
{
}
}

View File

@@ -12,10 +12,10 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext; import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject; import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.HTTPRequestException;
import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.apis.handles.BinaryInputHandle; import dan200.computercraft.core.apis.handles.BinaryInputHandle;
import dan200.computercraft.core.apis.handles.EncodedInputHandle; import dan200.computercraft.core.apis.handles.EncodedInputHandle;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.*; import java.io.*;
@@ -25,7 +25,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class HTTPRequest implements HTTPTask.IHTTPTask public class HTTPRequest implements Runnable
{ {
public static URL checkURL( String urlString ) throws HTTPRequestException public static URL checkURL( String urlString ) throws HTTPRequestException
{ {
@@ -55,11 +55,11 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
return url; return url;
} }
public static InetAddress checkHost( URL url ) throws HTTPRequestException public static InetAddress checkHost( String host ) throws HTTPRequestException
{ {
try try
{ {
InetAddress resolved = InetAddress.getByName( url.getHost() ); InetAddress resolved = InetAddress.getByName( host );
if( !ComputerCraft.http_whitelist.matches( resolved ) || ComputerCraft.http_blacklist.matches( resolved ) ) if( !ComputerCraft.http_whitelist.matches( resolved ) || ComputerCraft.http_blacklist.matches( resolved ) )
{ {
throw new HTTPRequestException( "Domain not permitted" ); throw new HTTPRequestException( "Domain not permitted" );
@@ -73,37 +73,25 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
} }
} }
private final IAPIEnvironment m_environment;
private final URL m_url; private final URL m_url;
private final String m_urlString; private final String m_urlString;
private final String m_postText; private final String m_postText;
private final Map<String, String> m_headers; private final Map<String, String> m_headers;
private boolean m_success = false;
private String m_encoding;
private byte[] m_result;
private boolean m_binary; private boolean m_binary;
private int m_responseCode = -1; private final String m_method;
private Map<String, String> m_responseHeaders; private final boolean m_followRedirects;
private String m_errorMessage;
public HTTPRequest( 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
{ {
// Parse the URL m_environment = environment;
m_urlString = urlString; m_urlString = urlString;
m_url = url; m_url = url;
m_binary = binary; m_binary = binary;
m_postText = postText; m_postText = postText;
m_headers = headers; m_headers = headers;
} m_method = method;
m_followRedirects = followRedirects;
public InputStream getContents()
{
byte[] result = m_result;
if( result != null )
{
return new ByteArrayInputStream( result );
}
return null;
} }
@Override @Override
@@ -112,12 +100,13 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
// First verify the address is allowed. // First verify the address is allowed.
try try
{ {
checkHost( m_url ); checkHost( m_url.getHost() );
} }
catch( HTTPRequestException e ) catch( HTTPRequestException e )
{ {
m_success = false; // Queue the failure event if not.
m_errorMessage = e.getMessage(); String error = e.getMessage();
m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, error == null ? "Could not connect" : error, null } );
return; return;
} }
@@ -135,6 +124,8 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
{ {
connection.setRequestMethod( "GET" ); connection.setRequestMethod( "GET" );
} }
if( m_method != null ) connection.setRequestMethod( m_method );
connection.setInstanceFollowRedirects( m_followRedirects );
// Set headers // Set headers
connection.setRequestProperty( "accept-charset", "UTF-8" ); connection.setRequestProperty( "accept-charset", "UTF-8" );
@@ -150,6 +141,11 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
} }
} }
// 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 // Send POST text
if( m_postText != null ) if( m_postText != null )
{ {
@@ -186,58 +182,39 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
byte[] result = ByteStreams.toByteArray( is ); byte[] result = ByteStreams.toByteArray( is );
is.close(); is.close();
// We completed // We've got some sort of response, so let's build a resulting object.
m_success = responseSuccess;
m_result = result;
m_responseCode = connection.getResponseCode();
m_encoding = connection.getContentEncoding();
Joiner joiner = Joiner.on( ',' ); Joiner joiner = Joiner.on( ',' );
Map<String, String> headers = m_responseHeaders = new HashMap<String, String>(); Map<String, String> headers = new HashMap<>();
for( Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet() ) for( Map.Entry<String, List<String>> header : connection.getHeaderFields().entrySet() )
{ {
headers.put( header.getKey(), joiner.join( header.getValue() ) ); 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() ),
connection.getResponseCode(), headers
);
connection.disconnect(); // disconnect connection.disconnect(); // disconnect
// Queue the appropriate event.
if( responseSuccess )
{
m_environment.queueEvent( "http_success", new Object[]{ m_urlString, stream } );
}
else
{
m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, "Could not connect", stream } );
}
} }
catch( IOException e ) catch( IOException e )
{ {
// There was an error // There was an error
m_success = false; m_environment.queueEvent( "http_failure", new Object[]{ m_urlString, "Could not connect", null } );
}
}
@Override
public void whenFinished( IAPIEnvironment environment )
{
final String url = m_urlString;
if( m_success )
{
// Queue the "http_success" event
InputStream contents = getContents();
Object result = wrapStream(
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, m_encoding ),
m_responseCode, m_responseHeaders
);
environment.queueEvent( "http_success", new Object[] { url, result } );
}
else
{
// Queue the "http_failure" event
String error = "Could not connect";
if( m_errorMessage != null ) error = m_errorMessage;
InputStream contents = getContents();
Object result = null;
if( contents != null )
{
result = wrapStream(
m_binary ? new BinaryInputHandle( contents ) : new EncodedInputHandle( contents, m_encoding ),
m_responseCode, m_responseHeaders
);
}
environment.queueEvent( "http_failure", new Object[] { url, error, result } );
} }
} }
@@ -247,8 +224,8 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
final int methodOffset = oldMethods.length; final int methodOffset = oldMethods.length;
final String[] newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 ); final String[] newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 );
newMethods[ methodOffset + 0 ] = "getResponseCode"; newMethods[methodOffset + 0] = "getResponseCode";
newMethods[ methodOffset + 1 ] = "getResponseHeaders"; newMethods[methodOffset + 1] = "getResponseHeaders";
return new ILuaObject() return new ILuaObject()
{ {
@@ -271,12 +248,12 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
case 0: case 0:
{ {
// getResponseCode // getResponseCode
return new Object[] { responseCode }; return new Object[]{ responseCode };
} }
case 1: case 1:
{ {
// getResponseHeaders // getResponseHeaders
return new Object[] { responseHeaders }; return new Object[]{ responseHeaders };
} }
default: default:
{ {
@@ -286,4 +263,15 @@ public class HTTPRequest implements HTTPTask.IHTTPTask
} }
}; };
} }
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

@@ -1,4 +1,4 @@
package dan200.computercraft.core.apis; package dan200.computercraft.core.apis.http;
public class HTTPRequestException extends Exception public class HTTPRequestException extends Exception
{ {

View File

@@ -1,61 +0,0 @@
package dan200.computercraft.core.apis.http;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import dan200.computercraft.core.apis.IAPIEnvironment;
import java.util.concurrent.*;
/**
* A task which executes asynchronously on a new thread.
*
* This functions very similarly to a {@link Future}, but with an additional
* method which is called on the main thread when the task is completed.
*/
public class HTTPTask
{
public interface IHTTPTask extends Runnable
{
void whenFinished( IAPIEnvironment environment );
}
private static final ExecutorService httpThreads = new ThreadPoolExecutor(
4, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setDaemon( true )
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
.setNameFormat( "ComputerCraft-HTTP-%d" )
.build()
);
private final Future<?> future;
private final IHTTPTask task;
private HTTPTask( Future<?> future, IHTTPTask task )
{
this.future = future;
this.task = task;
}
public static HTTPTask submit( IHTTPTask task )
{
Future<?> future = httpThreads.submit( task );
return new HTTPTask( future, task );
}
public void cancel()
{
future.cancel( false );
}
public boolean isFinished()
{
return future.isDone();
}
public void whenFinished( IAPIEnvironment environment )
{
task.whenFinished( environment );
}
}

View File

@@ -0,0 +1,193 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.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.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;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Closeable;
import java.io.IOException;
public class WebsocketConnection extends SimpleChannelInboundHandler<Object> implements ILuaObject, Closeable
{
public static final String SUCCESS_EVENT = "websocket_success";
public static final String FAILURE_EVENT = "websocket_failure";
public static final String CLOSE_EVENT = "websocket_closed";
public static final String MESSAGE_EVENT = "websocket_message";
private final String url;
private final IAPIEnvironment computer;
private final HTTPAPI api;
private boolean open = true;
private Channel channel;
private final WebSocketClientHandshaker handshaker;
public WebsocketConnection( IAPIEnvironment computer, HTTPAPI api, WebSocketClientHandshaker handshaker, String url )
{
this.computer = computer;
this.api = api;
this.handshaker = handshaker;
this.url = url;
api.addCloseable( this );
}
private void close( boolean remove )
{
open = false;
if( remove ) api.removeCloseable( this );
if( channel != null )
{
channel.close();
channel = null;
}
}
@Override
public void close() throws IOException
{
close( false );
}
private void onClosed()
{
close( true );
computer.queueEvent( CLOSE_EVENT, new Object[]{ url } );
}
@Override
public void handlerAdded( ChannelHandlerContext ctx ) throws Exception
{
channel = ctx.channel();
super.handlerAdded( ctx );
}
@Override
public void channelActive( ChannelHandlerContext ctx ) throws Exception
{
handshaker.handshake( ctx.channel() );
super.channelActive( ctx );
}
@Override
public void channelInactive( ChannelHandlerContext ctx ) throws Exception
{
onClosed();
super.channelInactive( ctx );
}
@Override
public void channelRead0( ChannelHandlerContext ctx, Object msg ) throws Exception
{
Channel ch = ctx.channel();
if( !handshaker.isHandshakeComplete() )
{
handshaker.finishHandshake( ch, (FullHttpResponse) msg );
computer.queueEvent( SUCCESS_EVENT, new Object[]{ url, this } );
return;
}
if( msg instanceof FullHttpResponse )
{
FullHttpResponse response = (FullHttpResponse) msg;
throw new IllegalStateException( "Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString( CharsetUtil.UTF_8 ) + ')' );
}
WebSocketFrame frame = (WebSocketFrame) msg;
if( frame instanceof TextWebSocketFrame )
{
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()];
data.readBytes( converted );
computer.addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length );
computer.queueEvent( MESSAGE_EVENT, new Object[]{ url, converted } );
}
else if( frame instanceof CloseWebSocketFrame )
{
ch.close();
onClosed();
}
}
@Override
public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause )
{
ctx.close();
computer.queueEvent( FAILURE_EVENT, new Object[]{
url,
cause instanceof WebSocketHandshakeException ? cause.getMessage() : "Could not connect"
} );
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[]{ "receive", "send", "close" };
}
@Nullable
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
switch( method )
{
case 0:
while( true )
{
checkOpen();
Object[] event = context.pullEvent( MESSAGE_EVENT );
if( event.length >= 3 && Objects.equal( event[1], url ) )
{
return new Object[]{ event[2] };
}
}
case 1:
{
checkOpen();
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;
}
case 2:
close( true );
return null;
default:
return null;
}
}
private void checkOpen() throws LuaException
{
if( !open ) throw new LuaException( "attempt to use a closed file" );
}
}

View File

@@ -0,0 +1,206 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2017. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.http;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.apis.HTTPAPI;
import dan200.computercraft.core.apis.IAPIEnvironment;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpHeaders;
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;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManagerFactory;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
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
{
private static final Object lock = new Object();
private static TrustManagerFactory trustManager;
private WebsocketConnector()
{
}
private static TrustManagerFactory getTrustManager()
{
if( trustManager != null ) return trustManager;
synchronized( lock )
{
if( trustManager != null ) return trustManager;
TrustManagerFactory tmf = null;
try
{
tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
tmf.init( (KeyStore) null );
}
catch( Exception e )
{
ComputerCraft.log.error( "Cannot setup trust manager", e );
}
return trustManager = tmf;
}
}
public static URI checkURI( String address ) throws HTTPRequestException
{
URI uri = null;
try
{
uri = new URI( address );
}
catch( URISyntaxException ignored )
{
}
if( uri == null || uri.getHost() == null )
{
try
{
uri = new URI( "ws://" + address );
}
catch( URISyntaxException ignored )
{
}
}
if( uri == null || uri.getHost() == null ) throw new HTTPRequestException( "URL malformed" );
String scheme = uri.getScheme();
if( scheme == null )
{
try
{
uri = new URI( "ws://" + uri.toString() );
}
catch( URISyntaxException e )
{
throw new HTTPRequestException( "URL malformed" );
}
}
else if( !scheme.equalsIgnoreCase( "wss" ) && !scheme.equalsIgnoreCase( "ws" ) )
{
throw new HTTPRequestException( "Invalid scheme '" + scheme + "'" );
}
if( !ComputerCraft.http_whitelist.matches( uri.getHost() ) || ComputerCraft.http_blacklist.matches( uri.getHost() ) )
{
throw new HTTPRequestException( "Domain not permitted" );
}
return uri;
}
public static int getPort( URI uri ) throws HTTPRequestException
{
int port = uri.getPort();
if( port >= 0 ) return port;
String scheme = uri.getScheme();
if( scheme.equalsIgnoreCase( "ws" ) )
{
return 80;
}
else if( scheme.equalsIgnoreCase( "wss" ) )
{
return 443;
}
else
{
throw new HTTPRequestException( "Invalid scheme '" + scheme + "'" );
}
}
public static Future<?> createConnector( final IAPIEnvironment environment, final HTTPAPI api, final URI uri, final String address, final int port, final Map<String, String> headers )
{
return HTTPExecutor.EXECUTOR.submit( () -> {
InetAddress resolved;
try
{
resolved = HTTPRequest.checkHost( uri.getHost() );
}
catch( HTTPRequestException e )
{
environment.queueEvent( WebsocketConnection.FAILURE_EVENT, new Object[] { address, e.getMessage() } );
return;
}
InetSocketAddress socketAddress = new InetSocketAddress( resolved, uri.getPort() == -1 ? port : uri.getPort() );
final SslContext ssl;
if( uri.getScheme().equalsIgnoreCase( "wss" ) )
{
try
{
ssl = SslContextBuilder.forClient().trustManager( getTrustManager() ).build();
}
catch( SSLException e )
{
environment.queueEvent( WebsocketConnection.FAILURE_EVENT, new Object[] { address, "Cannot create secure socket" } );
return;
}
}
else
{
ssl = null;
}
HttpHeaders httpHeaders = new DefaultHttpHeaders();
for( Map.Entry<String, String> header : headers.entrySet() )
{
httpHeaders.add( header.getKey(), header.getValue() );
}
WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, true, httpHeaders );
final WebsocketConnection connection = new WebsocketConnection( environment, api, handshaker, address );
new Bootstrap()
.group( HTTPExecutor.LOOP_GROUP )
.channel( NioSocketChannel.class )
.handler( new ChannelInitializer<SocketChannel>()
{
@Override
protected void initChannel( SocketChannel ch ) throws Exception
{
ChannelPipeline p = ch.pipeline();
if( ssl != null ) p.addLast( ssl.newHandler( ch.alloc(), uri.getHost(), port ) );
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator( 8192 ),
WebSocketClientCompressionHandler.INSTANCE,
connection
);
}
} )
.remoteAddress( socketAddress )
.connect();
} );
}
}

View File

@@ -8,16 +8,23 @@ package dan200.computercraft.core.computer;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IFileSystem;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.apis.*; import dan200.computercraft.core.apis.*;
import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException; import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.lua.CobaltLuaMachine;
import dan200.computercraft.core.lua.ILuaMachine; import dan200.computercraft.core.lua.ILuaMachine;
import dan200.computercraft.core.lua.LuaJLuaMachine;
import dan200.computercraft.core.terminal.Terminal; 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;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
@@ -162,6 +169,12 @@ public class Computer
m_computer.setLabel( label ); 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 ) public void onPeripheralChanged( int side, IPeripheral peripheral )
{ {
synchronized( m_computer.m_peripherals ) synchronized( m_computer.m_peripherals )
@@ -173,7 +186,91 @@ public class Computer
} }
} }
} }
private static class ComputerSystem extends ComputerAccess implements IComputerSystem
{
private final IAPIEnvironment m_environment;
private ComputerSystem( IAPIEnvironment m_environment )
{
super( m_environment );
this.m_environment = m_environment;
}
@Nonnull
@Override
public String getAttachmentName()
{
return "computer";
}
@Nullable
@Override
public IFileSystem getFileSystem()
{
FileSystem fs = m_environment.getFileSystem();
return fs == null ? null : fs.getMountWrapper();
}
@Nullable
@Override
public String getLabel()
{
return m_environment.getLabel();
}
}
private static class APIWrapper implements ILuaAPI
{
private final ILuaAPI delegate;
private final ComputerSystem system;
private APIWrapper( ILuaAPI delegate, ComputerSystem system )
{
this.delegate = delegate;
this.system = system;
}
@Override
public String[] getNames()
{
return delegate.getNames();
}
@Override
public void startup()
{
delegate.startup();
}
@Override
public void update()
{
delegate.update();
}
@Override
public void shutdown()
{
delegate.shutdown();
system.unmountAll();
}
@Nonnull
@Override
public String[] getMethodNames()
{
return delegate.getMethodNames();
}
@Nullable
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
{
return delegate.callMethod( context, method, arguments );
}
}
private static IMount s_romMount = null; private static IMount s_romMount = null;
private int m_id; private int m_id;
@@ -371,7 +468,7 @@ public class Computer
{ {
for(ILuaAPI api : m_apis) for(ILuaAPI api : m_apis)
{ {
api.advance( _dt ); api.update();
} }
} }
} }
@@ -420,14 +517,13 @@ public class Computer
} }
} }
public boolean pollChanged() public boolean pollAndResetChanged()
{ {
return m_externalOutputChanged; synchronized(this) {
} boolean changed = m_externalOutputChanged;
m_externalOutputChanged = false;
public void clearChanged() return changed;
{ }
m_externalOutputChanged = false;
} }
public boolean isBlinking() public boolean isBlinking()
@@ -452,7 +548,7 @@ public class Computer
private boolean initFileSystem() private boolean initFileSystem()
{ {
// Create the file system // Create the file system
int id = assignID(); assignID();
try try
{ {
m_fileSystem = new FileSystem( "hdd", getRootMount() ); m_fileSystem = new FileSystem( "hdd", getRootMount() );
@@ -578,6 +674,11 @@ public class Computer
{ {
m_apis.add( api ); m_apis.add( api );
} }
public void addAPI( dan200.computercraft.core.apis.ILuaAPI api )
{
addAPI( (ILuaAPI) api );
}
public void setPeripheral( int side, IPeripheral peripheral ) public void setPeripheral( int side, IPeripheral peripheral )
{ {
@@ -611,18 +712,27 @@ public class Computer
m_apis.add( new FSAPI( m_apiEnvironment ) ); m_apis.add( new FSAPI( m_apiEnvironment ) );
m_apis.add( new PeripheralAPI( m_apiEnvironment ) ); m_apis.add( new PeripheralAPI( m_apiEnvironment ) );
m_apis.add( new OSAPI( m_apiEnvironment ) ); m_apis.add( new OSAPI( m_apiEnvironment ) );
m_apis.add( new BitAPI( m_apiEnvironment ) );
//m_apis.add( new BufferAPI( m_apiEnvironment ) ); //m_apis.add( new BufferAPI( m_apiEnvironment ) );
if( ComputerCraft.http_enable ) if( ComputerCraft.http_enable )
{ {
m_apis.add( new HTTPAPI( m_apiEnvironment ) ); m_apis.add( new HTTPAPI( m_apiEnvironment ) );
} }
for( ILuaAPIFactory factory : ComputerCraft.getAPIFactories() )
{
ComputerSystem system = new ComputerSystem( m_apiEnvironment );
ILuaAPI api = factory.create( system );
if( api != null )
{
m_apis.add( api );
}
}
} }
private void initLua() private void initLua()
{ {
// Create the lua machine // Create the lua machine
ILuaMachine machine = new LuaJLuaMachine( this ); ILuaMachine machine = new CobaltLuaMachine( this );
// Add the APIs // Add the APIs
for(ILuaAPI api : m_apis) for(ILuaAPI api : m_apis)
@@ -688,6 +798,7 @@ public class Computer
return; return;
} }
m_state = State.Starting; m_state = State.Starting;
m_externalOutputChanged = true;
m_ticksSinceStart = 0; m_ticksSinceStart = 0;
} }
@@ -747,6 +858,7 @@ public class Computer
// Start a new state // Start a new state
m_state = State.Running; m_state = State.Running;
m_externalOutputChanged = true;
synchronized( m_machine ) synchronized( m_machine )
{ {
m_machine.handleEvent( null, null ); m_machine.handleEvent( null, null );
@@ -765,6 +877,7 @@ public class Computer
return; return;
} }
m_state = State.Stopping; m_state = State.Stopping;
m_externalOutputChanged = true;
} }
// Turn the computercraft off // Turn the computercraft off
@@ -789,7 +902,7 @@ public class Computer
// Shutdown our APIs // Shutdown our APIs
synchronized( m_apis ) synchronized( m_apis )
{ {
for(ILuaAPI api : m_apis) for( ILuaAPI api : m_apis )
{ {
api.shutdown(); api.shutdown();
} }
@@ -828,6 +941,7 @@ public class Computer
} }
m_state = State.Off; m_state = State.Off;
m_externalOutputChanged = true;
if( reboot ) if( reboot )
{ {
m_startRequested = true; m_startRequested = true;

View File

@@ -7,224 +7,347 @@
package dan200.computercraft.core.computer; package dan200.computercraft.core.computer;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.tracking.Tracking;
import java.util.ArrayList; import java.util.HashSet;
import java.util.Iterator; import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class ComputerThread public class ComputerThread
{ {
private static final Object m_lock; private static final int QUEUE_LIMIT = 256;
private static Thread m_thread;
private static final WeakHashMap <Object, LinkedBlockingQueue<ITask>> m_computerTasks;
private static final ArrayList <LinkedBlockingQueue<ITask>> m_computerTasksActive;
private static final ArrayList <LinkedBlockingQueue<ITask>> m_computerTasksPending;
private static final Object m_defaultQueue;
private static final Object m_monitor;
private static boolean m_running; /**
private static boolean m_stopped; * Lock used for modifications to the object
*/
static private static final Object s_stateLock = new Object();
{
m_lock = new Object(); /**
m_thread = null; * Lock for various task operations
m_computerTasks = new WeakHashMap<>(); */
m_computerTasksPending = new ArrayList<>(); private static final Object s_taskLock = new Object();
m_computerTasksActive = new ArrayList<>();
m_defaultQueue = new Object(); /**
m_monitor = new Object(); * Map of objects to task list
m_running = false; */
m_stopped = false; private static final WeakHashMap<Object, BlockingQueue<ITask>> s_computerTaskQueues = new WeakHashMap<>();
}
/**
* Active queues to execute
*/
private static final BlockingQueue<BlockingQueue<ITask>> s_computerTasksActive = new LinkedBlockingQueue<>();
private static final Set<BlockingQueue<ITask>> s_computerTasksActiveSet = new HashSet<>();
/**
* The default object for items which don't have an owner
*/
private static final Object s_defaultOwner = new Object();
/**
* Whether the thread is stopped or should be stopped
*/
private static boolean s_stopped = false;
/**
* The thread tasks execute on
*/
private static Thread[] s_threads = null;
private static final AtomicInteger s_ManagerCounter = new AtomicInteger( 1 );
private static final AtomicInteger s_DelegateCounter = new AtomicInteger( 1 );
/**
* Start the computer thread
*/
public static void start() public static void start()
{ {
synchronized( m_lock ) synchronized( s_stateLock )
{ {
if( m_running ) s_stopped = false;
if( s_threads == null || s_threads.length != ComputerCraft.computer_threads )
{ {
m_stopped = false; s_threads = new Thread[ComputerCraft.computer_threads];
return;
} }
m_thread = new Thread( () -> 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];
if( thread == null || !thread.isAlive() )
{
thread = s_threads[i] = new Thread( group, new TaskExecutor(), "ComputerCraft-Computer-Manager-" + s_ManagerCounter.getAndIncrement() );
thread.setDaemon( true );
thread.start();
}
}
}
}
/**
* Attempt to stop the computer thread
*/
public static void stop()
{
synchronized( s_stateLock )
{
if( s_threads != null )
{
s_stopped = true;
for( Thread thread : s_threads )
{
if( thread != null && thread.isAlive() )
{
thread.interrupt();
}
}
}
}
synchronized( s_taskLock )
{
s_computerTaskQueues.clear();
s_computerTasksActive.clear();
s_computerTasksActiveSet.clear();
}
}
/**
* Queue a task to execute on the thread
*
* @param task The task to execute
* @param computer The computer to execute it on, use {@code null} to execute on the default object.
*/
public static void queueTask( ITask task, Computer computer )
{
Object queueObject = computer == null ? s_defaultOwner : computer;
BlockingQueue<ITask> queue;
synchronized( s_computerTaskQueues )
{
queue = s_computerTaskQueues.get( queueObject );
if( queue == null )
{
s_computerTaskQueues.put( queueObject, queue = new LinkedBlockingQueue<>( QUEUE_LIMIT ) );
}
}
synchronized( s_taskLock )
{
if( queue.offer( task ) && !s_computerTasksActiveSet.contains( queue ) )
{
s_computerTasksActive.add( queue );
s_computerTasksActiveSet.add( queue );
}
}
}
/**
* Responsible for pulling and managing computer tasks. This pulls a task from {@link #s_computerTasksActive},
* creates a new thread using {@link TaskRunner} or reuses a previous one and uses that to execute the task.
*
* If the task times out, then it will attempt to interrupt the {@link TaskRunner} instance.
*/
private static final class TaskExecutor implements Runnable
{
private TaskRunner runner;
private Thread thread;
@Override
public void run()
{
try
{ {
while( true ) while( true )
{ {
synchronized( m_computerTasksPending ) // Wait for an active queue to execute
{ BlockingQueue<ITask> queue = s_computerTasksActive.take();
if (!m_computerTasksPending.isEmpty())
{
Iterator<LinkedBlockingQueue<ITask>> it = m_computerTasksPending.iterator();
while(it.hasNext())
{
LinkedBlockingQueue<ITask> queue = it.next();
if (!m_computerTasksActive.contains(queue))
{
m_computerTasksActive.add(queue);
}
it.remove();
}
/*
m_computerTasksActive.addAll(m_computerTasksPending); // put any that have been added since
m_computerTasksPending.clear();
*/
}
}
Iterator<LinkedBlockingQueue<ITask>> it = m_computerTasksActive.iterator();
while (it.hasNext())
{
LinkedBlockingQueue<ITask> queue = it.next();
if (queue == null || queue.isEmpty()) // we don't need the blocking part of the queue. Null check to ensure it exists due to a weird NPE I got
{
continue;
}
synchronized( m_lock )
{
if( m_stopped )
{
m_running = false;
m_thread = null;
return;
}
}
try
{
final ITask task = queue.take();
// Create the task // If threads should be stopped then return
Thread worker = new Thread( () -> synchronized( s_stateLock )
{
try {
task.execute();
} catch( Throwable e ) {
ComputerCraft.log.error( "Error running task", e );
}
} );
// Run the task
worker.setDaemon(true);
worker.start();
worker.join( 7000 );
if( worker.isAlive() )
{
// Task ran for too long
// Initiate escape plan
Computer computer = task.getOwner();
if( computer != null )
{
// Step 1: Soft abort
computer.abort( false );
worker.join( 1500 );
if( worker.isAlive() )
{
// Step 2: Hard abort
computer.abort( true );
worker.join( 1500 );
}
}
// Step 3: abandon
if( worker.isAlive() )
{
// ComputerCraft.log.warn( "Failed to abort Computer " + computer.getID() + ". Dangling lua thread could cause errors." );
worker.interrupt();
}
}
}
catch( InterruptedException e )
{
continue;
}
synchronized (queue)
{
if (queue.isEmpty())
{
it.remove();
}
}
}
while (m_computerTasksActive.isEmpty() && m_computerTasksPending.isEmpty())
{ {
synchronized (m_monitor) if( s_stopped ) return;
{
try
{
m_monitor.wait();
}
catch( InterruptedException e )
{
}
}
} }
execute( queue );
} }
}, "Computer Dispatch Thread" ); }
catch( InterruptedException ignored )
m_thread.setDaemon(true);
m_thread.start();
m_running = true;
}
}
public static void stop()
{
synchronized( m_lock )
{
if( m_running )
{ {
m_stopped = true; Thread.currentThread().interrupt();
m_thread.interrupt();
} }
} }
}
public static void queueTask( ITask _task, Computer computer )
{
Object queueObject = computer;
if (queueObject == null)
{
queueObject = m_defaultQueue;
}
LinkedBlockingQueue<ITask> queue = m_computerTasks.get(queueObject);
if (queue == null) private void execute( BlockingQueue<ITask> queue ) throws InterruptedException
{ {
m_computerTasks.put(queueObject, queue = new LinkedBlockingQueue<>( 256 )); ITask task = queue.remove();
}
if( thread == null || !thread.isAlive() )
synchronized ( m_computerTasksPending )
{
if( queue.offer( _task ) )
{ {
if( !m_computerTasksPending.contains( queue ) ) runner = new TaskRunner();
SecurityManager manager = System.getSecurityManager();
final ThreadGroup group = manager == null ? Thread.currentThread().getThreadGroup() : manager.getThreadGroup();
Thread thread = this.thread = new Thread( group, runner, "ComputerCraft-Computer-Runner" + s_DelegateCounter.getAndIncrement() );
thread.setDaemon( true );
thread.start();
}
long start = System.nanoTime();
// Execute the task
runner.submit( task );
try
{
// If we timed out rather than exiting:
boolean done = runner.await( 7000 );
if( !done )
{ {
m_computerTasksPending.add( queue ); // Attempt to soft then hard abort
Computer computer = task.getOwner();
if( computer != null )
{
computer.abort( false );
done = runner.await( 1500 );
if( !done )
{
computer.abort( true );
done = runner.await( 1500 );
}
}
// 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;
}
} }
} }
else finally
{ {
//System.out.println( "Event queue overflow" ); 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 )
{
if( queue.isEmpty() )
{
s_computerTasksActiveSet.remove( queue );
}
else
{
s_computerTasksActive.add( queue );
}
}
} }
} }
}
synchronized (m_monitor)
/**
* Responsible for the actual running of tasks. It waitin for the {@link TaskRunner#input} semaphore to be
* triggered, consumes a task and then triggers {@link TaskRunner#finished}.
*/
private static final class TaskRunner implements Runnable
{
private final Semaphore input = new Semaphore();
private final Semaphore finished = new Semaphore();
private ITask task;
@Override
public void run()
{ {
m_monitor.notify(); try
{
while( true )
{
input.await();
try
{
task.execute();
}
catch( RuntimeException e )
{
ComputerCraft.log.error( "Error running task.", e );
}
task = null;
finished.signal();
}
}
catch( InterruptedException e )
{
ComputerCraft.log.error( "Error running task.", e );
Thread.currentThread().interrupt();
}
}
void submit( ITask task )
{
this.task = task;
input.signal();
}
boolean await( long timeout ) throws InterruptedException
{
return finished.await( timeout );
}
}
/**
* A simple method to allow awaiting/providing a signal.
*
* Java does provide similar classes, but I only needed something simple.
*/
private static final class Semaphore
{
private volatile boolean state = false;
synchronized void signal()
{
state = true;
notify();
}
synchronized void await() throws InterruptedException
{
while( !state ) wait();
state = false;
}
synchronized boolean await( long timeout ) throws InterruptedException
{
if( !state )
{
wait( timeout );
if( !state ) return false;
}
state = false;
return true;
} }
} }
} }

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; 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 public class MainThread
{ {
private static final int MAX_TASKS_PER_TICK = 1000; private static final int MAX_TASKS_PER_TICK = 1000;
private static final int MAX_TASKS_TOTAL = 50000; 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 final Object m_nextUnusedTaskIDLock = new Object();
private static long m_nextUnusedTaskID = 0; private static long m_nextUnusedTaskID = 0;
@@ -31,7 +34,7 @@ public class MainThread
{ {
if( m_outstandingTasks.size() < MAX_TASKS_TOTAL ) if( m_outstandingTasks.size() < MAX_TASKS_TOTAL )
{ {
m_outstandingTasks.addLast( task ); m_outstandingTasks.offer( task );
return true; return true;
} }
} }
@@ -46,14 +49,17 @@ public class MainThread
ITask task = null; ITask task = null;
synchronized( m_outstandingTasks ) synchronized( m_outstandingTasks )
{ {
if( m_outstandingTasks.size() > 0 ) task = m_outstandingTasks.poll();
{
task = m_outstandingTasks.removeFirst();
}
} }
if( task != null ) if( task != null )
{ {
long start = System.nanoTime();
task.execute(); task.execute();
long stop = System.nanoTime();
Computer computer = task.getOwner();
if( computer != null ) Tracking.addServerTiming( computer, stop - start );
++tasksThisTick; ++tasksThisTick;
} }
else else

View File

@@ -9,7 +9,6 @@ package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
@@ -22,31 +21,31 @@ public class EmptyMount implements IMount
// IMount implementation // IMount implementation
@Override @Override
public boolean exists( @Nonnull String path ) throws IOException public boolean exists( @Nonnull String path )
{ {
return path.isEmpty(); return path.isEmpty();
} }
@Override @Override
public boolean isDirectory( @Nonnull String path ) throws IOException public boolean isDirectory( @Nonnull String path )
{ {
return path.isEmpty(); return path.isEmpty();
} }
@Override @Override
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException public void list( @Nonnull String path, @Nonnull List<String> contents )
{ {
} }
@Override @Override
public long getSize( @Nonnull String path ) throws IOException public long getSize( @Nonnull String path )
{ {
return 0; return 0;
} }
@Nonnull @Nonnull
@Override @Override
public InputStream openForRead( @Nonnull String path ) throws IOException public InputStream openForRead( @Nonnull String path )
{ {
return null; return null;
} }

View File

@@ -95,7 +95,7 @@ public class FileMount implements IWritableMount
// IMount implementation // IMount implementation
@Override @Override
public boolean exists( @Nonnull String path ) throws IOException public boolean exists( @Nonnull String path )
{ {
if( !created() ) if( !created() )
{ {
@@ -109,7 +109,7 @@ public class FileMount implements IWritableMount
} }
@Override @Override
public boolean isDirectory( @Nonnull String path ) throws IOException public boolean isDirectory( @Nonnull String path )
{ {
if( !created() ) if( !created() )
{ {
@@ -339,7 +339,7 @@ public class FileMount implements IWritableMount
} }
@Override @Override
public long getRemainingSpace() throws IOException public long getRemainingSpace()
{ {
return Math.max( m_capacity - m_usedSpace, 0 ); return Math.max( m_capacity - m_usedSpace, 0 );
} }

View File

@@ -7,6 +7,7 @@
package dan200.computercraft.core.filesystem; package dan200.computercraft.core.filesystem;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.filesystem.IFileSystem;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
@@ -65,7 +66,7 @@ public class FileSystem
} }
} }
public boolean isReadOnly( String path ) throws FileSystemException public boolean isReadOnly( String path )
{ {
return (m_writableMount == null); return (m_writableMount == null);
} }
@@ -290,6 +291,7 @@ public class FileSystem
} }
} }
private final FileSystemMount m_wrapper = new FileSystemMount( this );
private final Map<String, MountWrapper> m_mounts = new HashMap<>(); private final Map<String, MountWrapper> m_mounts = new HashMap<>();
private final Set<Closeable> m_openFiles = Collections.newSetFromMap( new WeakHashMap<Closeable, Boolean>() ); private final Set<Closeable> m_openFiles = Collections.newSetFromMap( new WeakHashMap<Closeable, Boolean>() );
@@ -347,7 +349,7 @@ public class FileSystem
mount( new MountWrapper( label, location, mount ) ); mount( new MountWrapper( label, location, mount ) );
} }
private synchronized void mount( MountWrapper wrapper ) throws FileSystemException private synchronized void mount( MountWrapper wrapper )
{ {
String location = wrapper.getLocation(); String location = wrapper.getLocation();
if( m_mounts.containsKey( location ) ) if( m_mounts.containsKey( location ) )
@@ -734,6 +736,11 @@ public class FileSystem
return match; return match;
} }
public IFileSystem getMountWrapper()
{
return m_wrapper;
}
private static String sanitizePath( String path ) private static String sanitizePath( String path )
{ {
return sanitizePath( path, false ); return sanitizePath( path, false );

View File

@@ -0,0 +1,185 @@
package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.IFileSystem;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.List;
public class FileSystemMount implements IFileSystem
{
private final FileSystem m_filesystem;
public FileSystemMount( FileSystem m_filesystem )
{
this.m_filesystem = m_filesystem;
}
@Override
public void makeDirectory( @Nonnull String path ) throws IOException
{
try
{
m_filesystem.makeDir( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public void delete( @Nonnull String path ) throws IOException
{
try
{
m_filesystem.delete( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Nonnull
@Override
public OutputStream openForWrite( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.openForWrite( path, false );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Nonnull
@Override
public OutputStream openForAppend( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.openForWrite( path, true );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public long getRemainingSpace() throws IOException
{
try
{
return m_filesystem.getFreeSpace( "/" );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public boolean exists( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.exists( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public boolean isDirectory( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.exists( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public void list( @Nonnull String path, @Nonnull List<String> contents ) throws IOException
{
try
{
Collections.addAll( contents, m_filesystem.list( path ) );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public long getSize( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.getSize( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Nonnull
@Override
public InputStream openForRead( @Nonnull String path ) throws IOException
{
try
{
return m_filesystem.openForRead( path );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public String combine( String path, String child )
{
return m_filesystem.combine( path, child );
}
@Override
public void copy( String from, String to ) throws IOException
{
try
{
m_filesystem.copy( from, to );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
@Override
public void move( String from, String to ) throws IOException
{
try
{
m_filesystem.move( from, to );
}
catch( FileSystemException e )
{
throw new IOException( e.getMessage() );
}
}
}

View File

@@ -176,14 +176,14 @@ public class JarMount implements IMount
// IMount implementation // IMount implementation
@Override @Override
public boolean exists( @Nonnull String path ) throws IOException public boolean exists( @Nonnull String path )
{ {
FileInZip file = m_root.getFile( path ); FileInZip file = m_root.getFile( path );
return file != null; return file != null;
} }
@Override @Override
public boolean isDirectory( @Nonnull String path ) throws IOException public boolean isDirectory( @Nonnull String path )
{ {
FileInZip file = m_root.getFile( path ); FileInZip file = m_root.getFile( path );
if( file != null ) if( file != null )

View File

@@ -11,109 +11,135 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaObject; import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.ILuaTask; import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.core.computer.Computer; import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.ITask; import dan200.computercraft.core.computer.ITask;
import dan200.computercraft.core.computer.MainThread; import dan200.computercraft.core.computer.MainThread;
import org.squiddev.cobalt.*;
import org.luaj.vm2.*; import org.squiddev.cobalt.compiler.CompileException;
import org.luaj.vm2.lib.OneArgFunction; import org.squiddev.cobalt.compiler.LoadState;
import org.luaj.vm2.lib.VarArgFunction; import org.squiddev.cobalt.debug.DebugFrame;
import org.luaj.vm2.lib.ZeroArgFunction; import org.squiddev.cobalt.debug.DebugHandler;
import org.luaj.vm2.lib.jse.JsePlatform; import org.squiddev.cobalt.debug.DebugState;
import org.squiddev.cobalt.function.LibFunction;
import org.squiddev.cobalt.function.LuaFunction;
import org.squiddev.cobalt.function.VarArgFunction;
import org.squiddev.cobalt.lib.*;
import org.squiddev.cobalt.lib.platform.AbstractResourceManipulator;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.*; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.Map; import java.util.Map;
public class LuaJLuaMachine implements ILuaMachine import static org.squiddev.cobalt.Constants.NONE;
{ import static org.squiddev.cobalt.ValueFactory.valueOf;
private Computer m_computer; import static org.squiddev.cobalt.ValueFactory.varargsOf;
private LuaValue m_globals; public class CobaltLuaMachine implements ILuaMachine
private LuaValue m_loadString; {
private LuaValue m_assert; private final Computer m_computer;
private LuaValue m_coroutine_create;
private LuaValue m_coroutine_resume; private final LuaState m_state;
private LuaValue m_coroutine_yield; private final LuaTable m_globals;
private LuaValue m_mainRoutine; private LuaThread m_mainRoutine;
private String m_eventFilter; private String m_eventFilter;
private String m_softAbortMessage; private String m_softAbortMessage;
private String m_hardAbortMessage; private String m_hardAbortMessage;
private Map<Object, LuaValue> m_valuesInProgress; public CobaltLuaMachine( Computer computer )
private Map<LuaValue, Object> m_objectsInProgress;
public LuaJLuaMachine( Computer computer )
{ {
m_computer = computer; m_computer = computer;
// Create an environment to run in // Create an environment to run in
m_globals = JsePlatform.debugGlobals(); final LuaState state = this.m_state = new LuaState( new AbstractResourceManipulator()
m_loadString = m_globals.get("loadstring"); {
m_assert = m_globals.get("assert");
LuaValue coroutine = m_globals.get("coroutine");
final LuaValue native_coroutine_create = coroutine.get("create");
LuaValue debug = m_globals.get("debug");
final LuaValue debug_sethook = debug.get("sethook");
coroutine.set("create", new OneArgFunction() {
@Override @Override
public LuaValue call( LuaValue value ) public InputStream findResource( String filename )
{ {
final LuaThread thread = native_coroutine_create.call( value ).checkthread(); return null;
debug_sethook.invoke( new LuaValue[] {
thread,
new ZeroArgFunction() {
@Override
public LuaValue call() {
String hardAbortMessage = m_hardAbortMessage;
if( hardAbortMessage != null )
{
LuaThread.yield(LuaValue.NIL);
}
return LuaValue.NIL;
}
},
LuaValue.NIL,
LuaValue.valueOf(100000)
} );
return thread;
} }
}); } );
state.debug = new DebugHandler( state )
m_coroutine_create = coroutine.get("create"); {
m_coroutine_resume = coroutine.get("resume"); private int count = 0;
m_coroutine_yield = coroutine.get("yield"); private boolean hasSoftAbort;
@Override
public void onInstruction( DebugState ds, DebugFrame di, int pc, Varargs extras, int top ) throws LuaError
{
int count = ++this.count;
if( count > 100000 )
{
if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE );
this.count = 0;
}
else
{
handleSoftAbort();
}
super.onInstruction( ds, di, pc, extras, top );
}
@Override
public void poll() throws LuaError
{
if( m_hardAbortMessage != null ) LuaThread.yield( state, NONE );
handleSoftAbort();
}
private void handleSoftAbort() throws LuaError {
// If the soft abort has been cleared then we can reset our flags and continue.
String message = m_softAbortMessage;
if (message == null) {
hasSoftAbort = false;
return;
}
if (hasSoftAbort && m_hardAbortMessage == null) {
// If we have fired our soft abort, but we haven't been hard aborted then everything is OK.
return;
}
hasSoftAbort = true;
throw new LuaError(message);
}
};
m_globals = new LuaTable();
state.setupThread( m_globals );
// Add basic libraries
m_globals.load( state, new BaseLib() );
m_globals.load( state, new TableLib() );
m_globals.load( state, new StringLib() );
m_globals.load( state, new MathLib() );
m_globals.load( state, new CoroutineLib() );
m_globals.load( state, new Bit32Lib() );
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" } );
// Remove globals we don't want to expose // Remove globals we don't want to expose
m_globals.set( "collectgarbage", LuaValue.NIL ); m_globals.rawset( "collectgarbage", Constants.NIL );
m_globals.set( "dofile", LuaValue.NIL ); m_globals.rawset( "dofile", Constants.NIL );
m_globals.set( "loadfile", LuaValue.NIL ); m_globals.rawset( "loadfile", Constants.NIL );
m_globals.set( "module", LuaValue.NIL ); m_globals.rawset( "print", Constants.NIL );
m_globals.set( "require", LuaValue.NIL );
m_globals.set( "package", LuaValue.NIL );
m_globals.set( "io", LuaValue.NIL );
m_globals.set( "os", LuaValue.NIL );
m_globals.set( "print", LuaValue.NIL );
m_globals.set( "luajava", LuaValue.NIL );
m_globals.set( "debug", LuaValue.NIL );
m_globals.set( "newproxy", LuaValue.NIL );
m_globals.set( "__inext", LuaValue.NIL );
// Add version globals // Add version globals
m_globals.set( "_VERSION", "Lua 5.1" ); m_globals.rawset( "_VERSION", valueOf( "Lua 5.1" ) );
m_globals.set( "_HOST", computer.getAPIEnvironment().getComputerEnvironment().getHostString() ); m_globals.rawset( "_HOST", valueOf( computer.getAPIEnvironment().getComputerEnvironment().getHostString() ) );
m_globals.set( "_CC_DEFAULT_SETTINGS", toValue( ComputerCraft.default_computer_settings ) ); m_globals.rawset( "_CC_DEFAULT_SETTINGS", valueOf( ComputerCraft.default_computer_settings ) );
if( ComputerCraft.disable_lua51_features ) if( ComputerCraft.disable_lua51_features )
{ {
m_globals.set( "_CC_DISABLE_LUA51_FEATURES", toValue( true ) ); m_globals.rawset( "_CC_DISABLE_LUA51_FEATURES", Constants.TRUE );
} }
// Our main function will go here // Our main function will go here
@@ -123,7 +149,7 @@ public class LuaJLuaMachine implements ILuaMachine
m_softAbortMessage = null; m_softAbortMessage = null;
m_hardAbortMessage = null; m_hardAbortMessage = null;
} }
@Override @Override
public void addAPI( ILuaAPI api ) public void addAPI( ILuaAPI api )
{ {
@@ -132,10 +158,10 @@ public class LuaJLuaMachine implements ILuaMachine
String[] names = api.getNames(); String[] names = api.getNames();
for( String name : names ) for( String name : names )
{ {
m_globals.set( name, table ); m_globals.rawset( name, table );
} }
} }
@Override @Override
public void loadBios( InputStream bios ) public void loadBios( InputStream bios )
{ {
@@ -144,56 +170,31 @@ public class LuaJLuaMachine implements ILuaMachine
{ {
return; return;
} }
try try
{ {
// Read the whole bios into a string LuaFunction value = LoadState.load( m_state, bios, "@bios.lua", m_globals );
String biosText; m_mainRoutine = new LuaThread( m_state, value, m_globals );
try
{
InputStreamReader isr;
try
{
isr = new InputStreamReader( bios, "UTF-8" );
}
catch( UnsupportedEncodingException e )
{
isr = new InputStreamReader( bios );
}
BufferedReader reader = new BufferedReader( isr );
StringBuilder fileText = new StringBuilder( "" );
String line = reader.readLine();
while( line != null ) {
fileText.append( line );
line = reader.readLine();
if( line != null ) {
fileText.append( "\n" );
}
}
biosText = fileText.toString();
}
catch( IOException e )
{
throw new LuaError( "Could not read file" );
}
// Load it
LuaValue program = m_assert.call( m_loadString.call(
toValue( biosText ), toValue( "bios.lua" )
));
m_mainRoutine = m_coroutine_create.call( program );
} }
catch( LuaError e ) catch( CompileException e )
{
if( m_mainRoutine != null )
{
m_mainRoutine.abandon();
m_mainRoutine = null;
}
}
catch( IOException e )
{ {
ComputerCraft.log.warn( "Could not load bios.lua ", e ); ComputerCraft.log.warn( "Could not load bios.lua ", e );
if( m_mainRoutine != null ) if( m_mainRoutine != null )
{ {
((LuaThread)m_mainRoutine).abandon(); m_mainRoutine.abandon();
m_mainRoutine = null; m_mainRoutine = null;
} }
} }
} }
@Override @Override
public void handleEvent( String eventName, Object[] arguments ) public void handleEvent( String eventName, Object[] arguments )
{ {
@@ -206,35 +207,28 @@ public class LuaJLuaMachine implements ILuaMachine
{ {
return; return;
} }
try try
{ {
LuaValue[] resumeArgs; Varargs resumeArgs = Constants.NONE;
if( eventName != null ) if( eventName != null )
{ {
resumeArgs = toValues( arguments, 2 ); resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) );
resumeArgs[0] = m_mainRoutine;
resumeArgs[1] = toValue( eventName );
} }
else
{ Varargs results = m_mainRoutine.resume( resumeArgs );
resumeArgs = new LuaValue[1]; if( m_hardAbortMessage != null )
resumeArgs[0] = m_mainRoutine;
}
Varargs results = m_coroutine_resume.invoke( LuaValue.varargsOf( resumeArgs ) );
if( m_hardAbortMessage != null )
{ {
throw new LuaError( m_hardAbortMessage ); throw new LuaError( m_hardAbortMessage );
} }
else if( results.arg1().checkboolean() == false ) else if( !results.first().checkBoolean() )
{ {
throw new LuaError( results.arg(2).checkstring().toString() ); throw new LuaError( results.arg( 2 ).checkString() );
} }
else else
{ {
LuaValue filter = results.arg(2); LuaValue filter = results.arg( 2 );
if( filter.isstring() ) if( filter.isString() )
{ {
m_eventFilter = filter.toString(); m_eventFilter = filter.toString();
} }
@@ -243,16 +237,16 @@ public class LuaJLuaMachine implements ILuaMachine
m_eventFilter = null; m_eventFilter = null;
} }
} }
LuaThread mainThread = (LuaThread)m_mainRoutine; LuaThread mainThread = m_mainRoutine;
if( mainThread.getStatus().equals("dead") ) if( mainThread.getStatus().equals( "dead" ) )
{ {
m_mainRoutine = null; m_mainRoutine = null;
} }
} }
catch( LuaError e ) catch( LuaError e )
{ {
((LuaThread)m_mainRoutine).abandon(); m_mainRoutine.abandon();
m_mainRoutine = null; m_mainRoutine = null;
} }
finally finally
@@ -262,13 +256,13 @@ public class LuaJLuaMachine implements ILuaMachine
} }
} }
@Override @Override
public void softAbort( String abortMessage ) public void softAbort( String abortMessage )
{ {
m_softAbortMessage = abortMessage; m_softAbortMessage = abortMessage;
} }
@Override @Override
public void hardAbort( String abortMessage ) public void hardAbort( String abortMessage )
{ {
m_softAbortMessage = abortMessage; m_softAbortMessage = abortMessage;
@@ -280,100 +274,88 @@ public class LuaJLuaMachine implements ILuaMachine
{ {
return false; return false;
} }
@Override @Override
public boolean restoreState( InputStream input ) public boolean restoreState( InputStream input )
{ {
return false; return false;
} }
@Override @Override
public boolean isFinished() public boolean isFinished()
{ {
return (m_mainRoutine == null); return (m_mainRoutine == null);
} }
@Override @Override
public void unload() public void unload()
{ {
if( m_mainRoutine != null ) if( m_mainRoutine != null )
{ {
LuaThread mainThread = (LuaThread)m_mainRoutine; LuaThread mainThread = m_mainRoutine;
mainThread.abandon(); mainThread.abandon();
m_mainRoutine = null; m_mainRoutine = null;
} }
} }
private void tryAbort() throws LuaError
{
// while( m_stopped )
// {
// m_coroutine_yield.call();
// }
String abortMessage = m_softAbortMessage;
if( abortMessage != null )
{
m_softAbortMessage = null;
m_hardAbortMessage = null;
throw new LuaError( abortMessage );
}
}
private LuaTable wrapLuaObject( ILuaObject object ) private LuaTable wrapLuaObject( ILuaObject object )
{ {
LuaTable table = new LuaTable(); LuaTable table = new LuaTable();
String[] methods = object.getMethodNames(); String[] methods = object.getMethodNames();
for(int i=0; i<methods.length; ++i ) for( int i = 0; i < methods.length; ++i )
{ {
if( methods[i] != null ) if( methods[ i ] != null )
{ {
final int method = i; final int method = i;
final ILuaObject apiObject = object; final ILuaObject apiObject = object;
final String methodName = methods[i]; final String methodName = methods[ i ];
table.set( methodName, new VarArgFunction() { table.rawset( methodName, new VarArgFunction()
{
@Override @Override
public Varargs invoke( Varargs _args ) public Varargs invoke( final LuaState state, Varargs _args ) throws LuaError
{ {
tryAbort();
Object[] arguments = toObjects( _args, 1 ); Object[] arguments = toObjects( _args, 1 );
Object[] results; Object[] results;
try try
{ {
results = apiObject.callMethod( new ILuaContext() { results = apiObject.callMethod( new ILuaContext()
{
@Nonnull @Nonnull
@Override @Override
public Object[] pullEvent( String filter ) throws LuaException, InterruptedException public Object[] pullEvent( String filter ) throws LuaException, InterruptedException
{ {
Object[] results = pullEventRaw( filter ); Object[] results = pullEventRaw( filter );
if( results.length >= 1 && results[0].equals( "terminate" ) ) if( results.length >= 1 && results[ 0 ].equals( "terminate" ) )
{ {
throw new LuaException( "Terminated", 0 ); throw new LuaException( "Terminated", 0 );
} }
return results; return results;
} }
@Nonnull @Nonnull
@Override @Override
public Object[] pullEventRaw( String filter ) throws InterruptedException public Object[] pullEventRaw( String filter ) throws InterruptedException
{ {
return yield( new Object[] { filter } ); return yield( new Object[] { filter } );
} }
@Nonnull @Nonnull
@Override @Override
public Object[] yield( Object[] yieldArgs ) throws InterruptedException public Object[] yield( Object[] yieldArgs ) throws InterruptedException
{ {
try try
{ {
LuaValue[] yieldValues = toValues( yieldArgs, 0 ); Varargs results = LuaThread.yield( state, toValues( yieldArgs ) );
Varargs results = m_coroutine_yield.invoke( LuaValue.varargsOf( yieldValues ) );
return toObjects( results, 1 ); return toObjects( results, 1 );
} }
catch( OrphanedThread e ) catch( OrphanedThread e )
{ {
throw new InterruptedException(); throw new InterruptedException();
} }
catch( Throwable e )
{
throw new RuntimeException( e );
}
} }
@Override @Override
@@ -448,10 +430,10 @@ public class LuaJLuaMachine implements ILuaMachine
Object[] response = pullEvent( "task_complete" ); Object[] response = pullEvent( "task_complete" );
if( response.length >= 3 && response[ 1 ] instanceof Number && response[ 2 ] instanceof Boolean ) if( response.length >= 3 && response[ 1 ] instanceof Number && response[ 2 ] instanceof Boolean )
{ {
if( ( (Number)response[ 1 ] ).intValue() == taskID ) if( ((Number) response[ 1 ]).intValue() == taskID )
{ {
Object[] returnValues = new Object[ response.length - 3 ]; Object[] returnValues = new Object[ response.length - 3 ];
if( (Boolean)response[ 2 ] ) if( (Boolean) response[ 2 ] )
{ {
// Extract the return values from the event and return them // Extract the return values from the event and return them
System.arraycopy( response, 3, returnValues, 0, returnValues.length ); System.arraycopy( response, 3, returnValues, 0, returnValues.length );
@@ -460,9 +442,9 @@ public class LuaJLuaMachine implements ILuaMachine
else else
{ {
// Extract the error message from the event and raise it // Extract the error message from the event and raise it
if( response.length >= 4 && response[3] instanceof String ) if( response.length >= 4 && response[ 3 ] instanceof String )
{ {
throw new LuaException( (String)response[ 3 ] ); throw new LuaException( (String) response[ 3 ] );
} }
else else
{ {
@@ -479,7 +461,7 @@ public class LuaJLuaMachine implements ILuaMachine
catch( InterruptedException e ) catch( InterruptedException e )
{ {
throw new OrphanedThread(); throw new OrphanedThread();
} }
catch( LuaException e ) catch( LuaException e )
{ {
throw new LuaError( e.getMessage(), e.getLevel() ); throw new LuaError( e.getMessage(), e.getLevel() );
@@ -492,7 +474,7 @@ public class LuaJLuaMachine implements ILuaMachine
} }
throw new LuaError( "Java Exception Thrown: " + t.toString(), 0 ); throw new LuaError( "Java Exception Thrown: " + t.toString(), 0 );
} }
return LuaValue.varargsOf( toValues( results, 0 ) ); return toValues( results );
} }
} ); } );
} }
@@ -500,192 +482,257 @@ public class LuaJLuaMachine implements ILuaMachine
return table; return table;
} }
private LuaValue toValue( Object object ) private LuaValue toValue( Object object, Map<Object, LuaValue> values )
{ {
if( object == null ) if( object == null )
{ {
return LuaValue.NIL; return Constants.NIL;
} }
else if( object instanceof Number ) else if( object instanceof Number )
{ {
double d = ((Number)object).doubleValue(); double d = ((Number) object).doubleValue();
return LuaValue.valueOf( d ); return valueOf( d );
} }
else if( object instanceof Boolean ) else if( object instanceof Boolean )
{ {
boolean b = (Boolean) object; return valueOf( (Boolean) object );
return LuaValue.valueOf( b );
} }
else if( object instanceof String ) else if( object instanceof String )
{ {
String s = object.toString(); String s = object.toString();
return LuaValue.valueOf( s ); return valueOf( s );
} }
else if( object instanceof byte[] ) else if( object instanceof byte[] )
{ {
byte[] b = (byte[]) object; byte[] b = (byte[]) object;
return LuaValue.valueOf( Arrays.copyOf( b, b.length ) ); return valueOf( Arrays.copyOf( b, b.length ) );
} }
else if( object instanceof Map ) else if( object instanceof Map )
{ {
// Table: // Table:
// Start remembering stuff // Start remembering stuff
boolean clearWhenDone = false; if( values == null )
try
{ {
if( m_valuesInProgress == null ) values = new IdentityHashMap<>();
{ }
m_valuesInProgress = new IdentityHashMap<>(); else if( values.containsKey( object ) )
clearWhenDone = true; {
} return values.get( object );
else if( m_valuesInProgress.containsKey( object ) ) }
{ LuaTable table = new LuaTable();
return m_valuesInProgress.get( object ); values.put( object, table );
}
LuaValue table = new LuaTable();
m_valuesInProgress.put( object, table );
// Convert all keys // Convert all keys
for( Map.Entry<?, ?> pair : ((Map<?, ?>) object).entrySet() ) for( Map.Entry<?, ?> pair : ((Map<?, ?>) object).entrySet() )
{
LuaValue key = toValue( pair.getKey() );
LuaValue value = toValue( pair.getValue() );
if( !key.isnil() && !value.isnil() )
{
table.set( key, value );
}
}
return table;
}
finally
{ {
// Clear (if exiting top level) LuaValue key = toValue( pair.getKey(), values );
if( clearWhenDone ) LuaValue value = toValue( pair.getValue(), values );
if( !key.isNil() && !value.isNil() )
{ {
m_valuesInProgress = null; table.rawset( key, value );
} }
} }
return table;
} }
else if( object instanceof ILuaObject ) else if( object instanceof ILuaObject )
{ {
return wrapLuaObject( (ILuaObject)object ); return wrapLuaObject( (ILuaObject) object );
} }
else else
{ {
return LuaValue.NIL; return Constants.NIL;
} }
} }
private LuaValue[] toValues( Object[] objects, int leaveEmpty ) private Varargs toValues( Object[] objects )
{ {
if( objects == null || objects.length == 0 ) if( objects == null || objects.length == 0 )
{ {
return new LuaValue[ leaveEmpty ]; return Constants.NONE;
} }
LuaValue[] values = new LuaValue[objects.length + leaveEmpty]; LuaValue[] values = new LuaValue[ objects.length ];
for( int i=0; i<values.length; ++i ) for( int i = 0; i < values.length; ++i )
{ {
if( i < leaveEmpty ) Object object = objects[ i ];
{ values[ i ] = toValue( object, null );
values[i] = null;
continue;
}
Object object = objects[i - leaveEmpty];
values[i] = toValue( object );
} }
return values; return varargsOf( values );
} }
private Object toObject( LuaValue value ) private static Object toObject( LuaValue value, Map<LuaValue, Object> objects )
{ {
switch( value.type() ) switch( value.type() )
{ {
case LuaValue.TNIL: case Constants.TNIL:
case LuaValue.TNONE: case Constants.TNONE:
{ {
return null; return null;
} }
case LuaValue.TINT: case Constants.TINT:
case LuaValue.TNUMBER: case Constants.TNUMBER:
{ {
return value.todouble(); return value.toDouble();
} }
case LuaValue.TBOOLEAN: case Constants.TBOOLEAN:
{ {
return value.toboolean(); return value.toBoolean();
} }
case LuaValue.TSTRING: case Constants.TSTRING:
{ {
LuaString str = value.checkstring(); return value.toString();
return str.tojstring();
} }
case LuaValue.TTABLE: case Constants.TTABLE:
{ {
// Table: // Table:
boolean clearWhenDone = false; // Start remembering stuff
try if( objects == null )
{ {
// Start remembering stuff objects = new IdentityHashMap<>();
if( m_objectsInProgress == null )
{
m_objectsInProgress = new IdentityHashMap<>();
clearWhenDone = true;
}
else if( m_objectsInProgress.containsKey( value ) )
{
return m_objectsInProgress.get( value );
}
Map<Object, Object> table = new HashMap<>();
m_objectsInProgress.put( value, table );
// Convert all keys
LuaValue k = LuaValue.NIL;
while( true )
{
Varargs keyValue = value.next( k );
k = keyValue.arg1();
if( k.isnil() )
{
break;
}
LuaValue v = keyValue.arg(2);
Object keyObject = toObject(k);
Object valueObject = toObject(v);
if( keyObject != null && valueObject != null )
{
table.put( keyObject, valueObject );
}
}
return table;
} }
finally else if( objects.containsKey( value ) )
{ {
// Clear (if exiting top level) return objects.get( value );
if( clearWhenDone ) }
Map<Object, Object> table = new HashMap<>();
objects.put( value, table );
LuaTable luaTable = (LuaTable) value;
// Convert all keys
LuaValue k = Constants.NIL;
while( true )
{
Varargs keyValue;
try
{ {
m_objectsInProgress = null; keyValue = luaTable.next( k );
}
catch( LuaError luaError )
{
break;
}
k = keyValue.first();
if( k.isNil() )
{
break;
}
LuaValue v = keyValue.arg( 2 );
Object keyObject = toObject( k, objects );
Object valueObject = toObject( v, objects );
if( keyObject != null && valueObject != null )
{
table.put( keyObject, valueObject );
} }
} }
return table;
} }
default: default:
{ {
return null; return null;
} }
} }
} }
private Object[] toObjects( Varargs values, int startIdx ) private static Object[] toObjects( Varargs values, int startIdx )
{ {
int count = values.narg(); int count = values.count();
Object[] objects = new Object[ count - startIdx + 1 ]; Object[] objects = new Object[ count - startIdx + 1 ];
for( int n=startIdx; n<=count; ++n ) for( int n = startIdx; n <= count; ++n )
{ {
int i = n - startIdx; int i = n - startIdx;
LuaValue value = values.arg(n); LuaValue value = values.arg( n );
objects[i] = toObject( value ); objects[ i ] = toObject( value, null );
} }
return objects; return objects;
} }
private static class PrefixLoader extends VarArgFunction
{
private static final LuaString FUNCTION_STR = valueOf( "function" );
private static final LuaString EQ_STR = valueOf( "=" );
@Override
public Varargs invoke( LuaState state, Varargs args ) throws LuaError
{
switch (opcode)
{
case 0: // "load", // ( func [,chunkname] ) -> chunk | nil, msg
{
LuaValue func = args.arg( 1 ).checkFunction();
LuaString chunkname = args.arg( 2 ).optLuaString( FUNCTION_STR );
if( !chunkname.startsWith( '@' ) && !chunkname.startsWith( '=' ) )
{
chunkname = OperationHelper.concat( EQ_STR, chunkname );
}
return BaseLib.loadStream( state, new StringInputStream( state, func ), chunkname );
}
case 1: // "loadstring", // ( string [,chunkname] ) -> chunk | nil, msg
{
LuaString script = args.arg( 1 ).checkLuaString();
LuaString chunkname = args.arg( 2 ).optLuaString( script );
if( !chunkname.startsWith( '@' ) && !chunkname.startsWith( '=' ) )
{
chunkname = OperationHelper.concat( EQ_STR, chunkname );
}
return BaseLib.loadStream( state, script.toInputStream(), chunkname );
}
}
return NONE;
}
}
private static class StringInputStream extends InputStream
{
private final LuaState state;
private final LuaValue func;
private byte[] bytes;
private int offset, remaining = 0;
public StringInputStream( LuaState state, LuaValue func )
{
this.state = state;
this.func = func;
}
@Override
public int read() throws IOException
{
if( remaining <= 0 )
{
LuaValue s;
try
{
s = OperationHelper.call( state, func );
} catch (LuaError e)
{
throw new IOException( e );
}
if( s.isNil() )
{
return -1;
}
LuaString ls;
try
{
ls = s.strvalue();
} catch (LuaError e)
{
throw new IOException( e );
}
bytes = ls.bytes;
offset = ls.offset;
remaining = ls.length;
if( remaining <= 0 )
{
return -1;
}
}
--remaining;
return bytes[offset++];
}
}
} }

View File

@@ -5,7 +5,7 @@
*/ */
package dan200.computercraft.core.lua; package dan200.computercraft.core.lua;
import dan200.computercraft.core.apis.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;

View File

@@ -56,7 +56,7 @@ public class Terminal
m_palette = new Palette(); m_palette = new Palette();
} }
public void reset() public synchronized void reset()
{ {
m_cursorColour = 0; m_cursorColour = 0;
m_cursorBackgroundColour = 15; m_cursorBackgroundColour = 15;
@@ -76,7 +76,7 @@ public class Terminal
return m_height; 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 ) if( width == m_width && height == m_height )
{ {
@@ -189,7 +189,7 @@ public class Terminal
return m_palette; 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 x = m_cursorX;
int y = m_cursorY; 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 x = m_cursorX;
int y = m_cursorY; 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 ) 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 ) for( int y = 0; y < m_height; ++y )
{ {
@@ -256,7 +256,7 @@ public class Terminal
m_changed = true; m_changed = true;
} }
public void clearLine() public synchronized void clearLine()
{ {
int y = m_cursorY; int y = m_cursorY;
if( y >= 0 && y < m_height ) 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 ) if( y >= 0 && y < m_height )
{ {
@@ -277,7 +277,7 @@ public class Terminal
return null; 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_text[y].write( text );
m_textColour[y].write( textColour ); m_textColour[y].write( textColour );
@@ -285,7 +285,7 @@ public class Terminal
m_changed = true; m_changed = true;
} }
public TextBuffer getTextColourLine( int y ) public synchronized TextBuffer getTextColourLine( int y )
{ {
if( y>=0 && y<m_height ) if( y>=0 && y<m_height )
{ {
@@ -294,7 +294,7 @@ public class Terminal
return null; return null;
} }
public TextBuffer getBackgroundColourLine( int y ) public synchronized TextBuffer getBackgroundColourLine( int y )
{ {
if( y>=0 && y<m_height ) if( y>=0 && y<m_height )
{ {
@@ -318,7 +318,7 @@ public class Terminal
m_changed = false; m_changed = false;
} }
public NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound ) public synchronized NBTTagCompound writeToNBT( NBTTagCompound nbttagcompound )
{ {
nbttagcompound.setInteger( "term_cursorX", m_cursorX ); nbttagcompound.setInteger( "term_cursorX", m_cursorX );
nbttagcompound.setInteger( "term_cursorY", m_cursorY ); nbttagcompound.setInteger( "term_cursorY", m_cursorY );
@@ -338,7 +338,7 @@ public class Terminal
return nbttagcompound; return nbttagcompound;
} }
public void readFromNBT( NBTTagCompound nbttagcompound ) public synchronized void readFromNBT( NBTTagCompound nbttagcompound )
{ {
m_cursorX = nbttagcompound.getInteger( "term_cursorX" ); m_cursorX = nbttagcompound.getInteger( "term_cursorX" );
m_cursorY = nbttagcompound.getInteger( "term_cursorY" ); 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 ) );
}
}

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