1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-02 14:43:00 +00:00

Compare commits

..

89 Commits

Author SHA1 Message Date
SquidDev
1efabccd14 Merge branch 'mc-1.14.x' into mc-1.15.x 2020-06-21 12:09:28 +01:00
SquidDev
029374e9aa Merge branch 'master' into mc-1.14.x 2020-06-21 12:08:30 +01:00
SquidDev
2a8efb3fd5 Fix crashes when rendering monitors of varying sizes
When calling .flip(), we limit the size of the buffer. However, this
limit is not reset when writing the next time, which means we get
out-of-bounds errors, even if the buffer is /technically/ big enough.

Clearing the buffer before drawing (rather than just resetting the
position) is enough to fix this.

Fixes #476 (and closes #477, which is a duplicate)
2020-06-21 12:03:24 +01:00
SquidDev
48edcde4ef Fix handling of CC: T's version
We never added back replacing of ${version} strings, which means that CC
was reporting incorrect version numbers in _HOST, the user agent and
network versions. This meant we would allow connections even on
mismatched versions (#464).

We shift all version handling into ComputerCraftAPI(Impl) - this now
relies on Forge code, so we don't want to run it in emulators.
2020-06-19 18:49:27 +01:00
Weblate
58a2995bbc Translations for Dutch
Translations for Chinese (Simplified)

Translations for Korean

Translations for German

Co-authored-by: SquidDev <bonzoweb@hotmail.co.uk>
2020-06-18 14:10:23 +00:00
SquidDev
a35dcb28ef Import translations and clean up
- Strip any gui._.config options. These haven't been used since 1.12
   and while they may return, it doesn't seem worth it right now.
 - Fix a couple of typos in the English translations.
 - Import from https://i18n.tweaked.cc. There's definitely some problems
   with the import - empty translations are still included, so we write
   a script to strip them.
2020-06-18 13:10:51 +01:00
SquidDev
7b2d482387 Make the CF release stable 2020-06-16 09:45:42 +01:00
SquidDev
2b077554f7 Merge branch 'mc-1.14.x' into mc-1.15.x
# Conflicts:
#	gradle.properties
#	src/main/java/dan200/computercraft/client/render/MonitorTextureBufferShader.java
#	src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java
#	src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java
#	src/main/java/dan200/computercraft/shared/Config.java
#	src/main/java/dan200/computercraft/shared/network/NetworkHandler.java
#	src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java
#	src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java
#	src/main/resources/data/computercraft/lua/rom/help/changelog.txt
#	src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt
2020-06-16 09:24:14 +01:00
SquidDev
9134f243c1 Merge branch 'master' into mc-1.14.x 2020-06-15 22:05:02 +01:00
SquidDev
c0f3ca81fb Bump to 1.89.0 2020-06-15 21:37:08 +01:00
SquidDev
190ed4fd20 Fix incorrect disk drive method name
The previous comment was wrong, and I never double checked. Closes #472.
2020-06-11 21:19:14 +01:00
SquidDev
b9ff9b7f90 Allow returning lua functions
Not sure how this will play with persistence when it happens (badly,
most likely), but it's not a bad idea to support it.

Closes #466
2020-06-03 21:44:08 +01:00
SquidDev
b9b8121be9 Expose tags for turtle.{inspect,getItemDetail}
This is simply exposed as a table from tag -> true. While this is less
natural than an array, it allows for easy esting of whether a tag is
present.

Closes #461
2020-06-01 11:17:05 +01:00
Lignum
014bf55cd4 Cherry pick several improvements from #455
- Use texture over texture2D - the latter was deprecated in GLSL 1.30.
 - Cache the tbo buffer - this saves an allocation when monitors update.

Closes #455. While the rest of the PR has some nice changes, it
performs signlificantly worse on my system.
2020-05-31 17:23:49 +01:00
Lignum
085ae2e74a Use an older version of GLSL (#459)
This ensures that the MVP matrix is available within the monitor
fragment shader, without requiring the ARB_compatibility extension.
2020-05-28 11:06:14 +01:00
Lignum
4ff33f165d Fetch MVP matrix in monitor shader instead (#454) 2020-05-25 11:19:03 +01:00
SquidDev
d929c02d2a Fix settings loading failing for defined settings
Yes, this was the only piece of code which wasn't tested :/. Fixes #457.
2020-05-24 12:16:51 +01:00
Jonathan Coates
d50a08a549 Rewrite monitor networking (#453)
This moves monitor networking into its own packet, rather than serialising
using NBT. This allows us to be more flexible with how monitors are
serialised.

We now compress terminal data using gzip. This reduces the packet size
of a max-sized-monitor from ~25kb to as little as 100b.

On my test set of images (what I would consider to be the extreme end of
the "reasonable" case), we have packets from 1.4kb bytes up to 12kb,
with a mean of 6kb. Even in the worst case, this is a 2x reduction in
packet size.

While this is a fantastic win for the common case, it is not abuse-proof.
One can create a terminal with high entropy (and so uncompressible). This
will still be close to the original packet size.

In order to prevent any other abuse, we also limit the amount of monitor
data a client can possibly receive to 1MB (configurable).
2020-05-20 08:44:44 +01:00
SquidDev
c493d668c8 Bump version 2020-05-17 17:02:17 +01:00
SquidDev
53477fd3a1 Fix several AIOB exceptions
Closes #450
2020-05-17 16:58:19 +01:00
SquidDev
87aa839b60 Upgrade the release to beta
It should be release quality in all honesty[^1], but let's leave it a
few days to see if any issues trickle in.

[^1]: Well, aside from upsidedown turtles!
2020-05-16 10:20:16 +01:00
SquidDev
e02ccdcb1a Fix incorrect shadowing 2020-05-16 10:19:25 +01:00
SquidDev
f36f532c63 Migrate config to the server (rather than common) 2020-05-15 23:05:14 +01:00
SquidDev
5a816917d5 Normalise config names 2020-05-15 23:04:04 +01:00
SquidDev
7af63d052d Make many more http options domain-specific
timetout, max_upload, max_download and max_websocket_message may now be
configured on a domain-by-domain basis. This uses the same system that
we use for the block/allow-list from before:

Example:

    [[http.rules]]
        host = "*"
        action = "allow"
	max_upload = 4194304
	max_download = 16777216
	timeout = 30000
2020-05-15 23:04:04 +01:00
SquidDev
4f8217d1ab Implement IPeripheral.getTarget on a few additional TEs
Also make it nullable. Hopefully this will allow us to distinguish
between non-default implementations more easily.
2020-05-15 17:34:00 +01:00
SquidDev
5409d441b5 Expose peripherals as a capability
This registers IPeripheral as a capability. As a result, all (Minecraft
facing) functionality operates using LazyOptional<_>s instead.

Peripheral providers should now return a LazyOptional<IPeripheral> too.
Hopefully this will allow custom peripherals to mark themselves as
invalid (say, because a dependency has changed).

While peripheral providers are somewhat redundant, they still have their
usages. If a peripheral is applied to a large number of blocks (for
instance, all inventories) then using capabilities does incur some
memory overhead.

We also make the following changes based on the above:
 - Remove the default implementation for IWiredElement, migrating the
   definition to a common "Capabilities" class.

 - Remove IPeripheralTile - we'll exclusively use capabilities now.
   Absurdly this is the most complex change, as all TEs needed to be
   migrated too.

   I'm not 100% sure of the correctness of this changes so far - I've
   tested it pretty well, but blocks with more complex peripheral logic
   (wired/wireless modems and turtles) are still a little messy.

 - Remove the "command block" peripheral provider, attaching a
   capability instead.
2020-05-15 17:09:12 +01:00
Jonathan Coates
d5f82fa458 Replace getMethodNames/callMethod with annotations (#447)
When creating a peripheral or custom Lua object, one must implement two
methods:

 - getMethodNames(): String[] - Returns the name of the methods
 - callMethod(int, ...): Object[] - Invokes the method using an index in
   the above array.

This has a couple of problems:
 - It's somewhat unwieldy to use - you need to keep track of array
   indices, which leads to ugly code.
 - Functions which yield (for instance, those which run on the main
   thread) are blocking. This means we need to spawn new threads for
   each CC-side yield.

We replace this system with a few changes:

 - @LuaFunction annotation: One may annotate a public instance method
   with this annotation. This then exposes a peripheral/lua object
   method.

   Furthermore, this method can accept and return a variety of types,
   which often makes functions cleaner (e.g. can return an int rather
   than an Object[], and specify and int argument rather than
   Object[]).

 - MethodResult: Instead of returning an Object[] and having blocking
   yields, functions return a MethodResult. This either contains an
   immediate return, or an instruction to yield with some continuation
   to resume with.

   MethodResult is then interpreted by the Lua runtime (i.e. Cobalt),
   rather than our weird bodgey hacks before. This means we no longer
   spawn new threads when yielding within CC.

 - Methods accept IArguments instead of a raw Object array. This has a
   few benefits:
   - Consistent argument handling - people no longer need to use
     ArgumentHelper (as it doesn't exist!), or even be aware of its
     existence - you're rather forced into using it.
   - More efficient code in some cases. We provide a Cobalt-specific
     implementation of IArguments, which avoids the boxing/unboxing when
     handling numbers and binary strings.
2020-05-15 13:21:16 +01:00
SquidDev
d0deab3519 Update changelog to include some missing things 2020-05-15 10:48:31 +01:00
SquidDev
d5a8df753a Merge branch 'mc-1.14.x' into mc-1.15.x 2020-05-15 10:36:23 +01:00
SquidDev
13de2c4dd0 Fix location of cc.require
Yay for 1.12->1.13 changes!
2020-05-15 10:28:35 +01:00
SquidDev
906280225e Merge branch 'master' into mc-1.14.x 2020-05-15 10:19:57 +01:00
SquidDev
161a5b4707 Document and test the redstone library
The tests may be a little agressive, but I wanted some sanity checks for
the 1.15 API rewrite.
2020-05-15 10:03:47 +01:00
SquidDev
c6b6b4479c Update changelog and fix doc typo 2020-05-14 19:23:57 +01:00
SquidDev
96e7b60285 Display function arguments and positions in the REPL
- cc.pretty.pretty now accepts two additional options:
   - function_args: Show function arguments
   - function_source: Show where functions are defined.
 - Expose the two options as lua.* settings (defaulting function_args to
   true, and function_source to false).
   These are then used in the Lua REPL.

Closes #361
2020-05-14 19:11:57 +01:00
SquidDev
086fccd997 Move the package library into a separate module
Hopefully this makes it a little easier for people to use in custom
shells (and anything else where it might be useful).
2020-05-14 17:27:50 +01:00
Jonathan Coates
5dfaf6eee9 Merge branch 'mc-1.14.x' into mc-1.15.x 2020-05-13 15:28:05 +01:00
Jonathan Coates
e251dd066c Fix test failures 2020-05-13 15:27:50 +01:00
SquidDev
9abcfe56ea Create the coverage directory before writing
Odd that this failed - we should make the directory within the test
suite - but let's see if this helps.
2020-05-13 14:41:50 +01:00
SquidDev
abbc641fd4 Merge branch 'mc-1.14.x' into mc-1.15.x 2020-05-13 14:36:39 +01:00
SquidDev
c60dcb4f5a Fix deprecated usage 2020-05-13 14:04:32 +01:00
SquidDev
4be0b15afa Merge branch 'master' into mc-1.14.x 2020-05-13 14:04:28 +01:00
SquidDev
a4ae36b6b3 Bump version to 1.88.0
There's probably some other stuff I'll get in before release, but let's
do this just in case.
2020-05-13 13:43:40 +01:00
SquidDev
ac075d9f53 Allow using command computers in survival mode
I'm really not a fan of this change, but it's gated behind a config
option and there's apparently sufficient demand that it's worthwhile.
Closes #442.
2020-05-13 10:26:59 +01:00
Lupus590
05d7be0362 Improvements to the various file system programs (rm, mv, rename) (#440)
This enforces several sanity checks before actually attempting
the move, allowing us to produce friendlier error messages.
2020-05-12 11:32:48 +01:00
SquidDev
9a71dc1a26 Copy across a bunch of 5.1/5.3 io tests
I've been meaning to do this for ages. Woops.
2020-05-11 18:05:40 +01:00
SquidDev
156023b154 Create more work for myself
This ensures no lines start with an empty line, and all files finish
with exactly one "\n".
2020-05-11 16:08:25 +01:00
SquidDev
6b3773a862 Run tests with coverage
- Use jacoco for Java-side coverage. Our Java coverage is /terrible
   (~10%), as we only really test the core libraries. Still a good thing
   to track for regressions though.

 - mcfly now tracks Lua side coverage. This works in several stages:
   - Replace loadfile to include the whole path
   - Add a debug hook which just tracks filename->(lines->count). This
     is then submitted to the Java test runner.
   - On test completion, we emit a luacov.report.out file.

   As the debug hook is inserted by mcfly, this does not include any
   computer startup (such as loading apis, or the root of bios.lua),
   despite they're executed.

   This would be possible to do (for instance, inject a custom header
   into bios.lua). However, we're not actually testing any of the
   behaviour of startup (aside from "does it not crash"), so I'm not
   sure whether to include it or not. Something I'll most likely
   re-evaluate.
2020-05-11 15:47:30 +01:00
Jonathan Coates
376d628cf0 Make the local Lua message a little shorter
Co-authored-by: exerro <benedict.allen2514@gmail.com>
2020-05-09 08:30:55 +01:00
Lupus590
44062ebd52 Allow lua REPL to warn about using local variables (#367)
`local varname = value` results in `varname` being inaccessible in
the next REPL input. This is often unintended and can lead to confusing
behaviour. We produce a warning when this occurs.
2020-05-08 16:07:33 +01:00
Jonathan Coates
5739285fc2 Finish off documentation for the commands API 2020-05-05 21:17:52 +01:00
Jonathan Coates
70b457ed18 Add a monitor renderer using TBOs (#443)
This uses the system described in #409, to render monitors in a more
efficient manner.

Each monitor is backed by a texture buffer object (TBO) which contains
a relatively compact encoding of the terminal state. This is then
rendered using a shader, which consumes the TBO and uses it to index
into main font texture.

As we're transmitting significantly less data to the GPU (only 3 bytes
per character), this effectively reduces any update lag to 0. FPS appears
to be up by a small fraction (10-15fps on my machine, to ~110), possibly
as we're now only drawing a single quad (though doing much more work in
the shader).

On my laptop, with its Intel integrated graphics card, I'm able to draw
120 full-sized monitors (with an effective resolution of 3972 x 2330) at
a consistent 60fps. Updates still cause a slight spike, but we always
remain above 30fps - a significant improvement over VBOs, where updates
would go off the chart.

Many thanks to @Lignum and @Lemmmy for devising this scheme, and helping
test and review it! ♥
2020-05-05 13:05:23 +01:00
SquidDev
ca2995ed38 Merge branch 'mc-1.14.x' into mc-1.15.x 2020-05-04 10:28:55 +01:00
SquidDev
6816931659 Merge branch 'master' into mc-1.14.x 2020-05-04 10:28:15 +01:00
SquidDev
1547ecbeb3 Fix incorrect palette serialisation 2020-05-04 10:26:33 +01:00
SquidDev
e918f55b58 Merge branch 'mc-1.14.x' into mc-1.15.x 2020-05-04 10:05:32 +01:00
SquidDev
c28b468844 Update languages and language script 2020-05-04 09:57:38 +01:00
SquidDev
052cf8ee7d Merge branch 'master' into mc-1.14.x 2020-05-04 09:39:54 +01:00
SquidDev
550ada2f9e Restore previous behaviour for unknown colours
Unknown blit colours, such as " " will be translated to black for the
background or white for the foreground. This restores the behaviour from
before #412.
2020-05-04 09:15:23 +01:00
SquidDev
17b7727262 Improve serialisation of terminals
- Write to a PacketBuffer instead of generating an NBT tag. This is
   then converted to an NBT byte array when we send across the network.
 - Pack background/foreground colours into a single byte.

This derives from some work I did back in 2017, and some of the changes
made/planned in #409. However, this patch does not change how terminals
are represented, it simply makes the transfer more compact.

This makes the patch incredibly small (100 lines!), but also limited in
what improvements it can make compared with #409. We send 26626 bytes
for a full-sized monitor. While a 2x improvement over the previous 58558
bytes, there's a lot of room for improvement.
2020-05-03 10:38:31 +01:00
Jonathan Coates
4553e404b2 Merge pull request #437 from SquidDev-CC/feature/keep-ids
Preserve computer ids on unlabelled computers
2020-05-03 06:54:04 +01:00
Drew Lemmy
a565a571f9 Return the peripheral name when wrapping (#436) 2020-05-03 06:53:42 +01:00
SquidDev
fb64b6017b Add shell.execute
This functions the same as shell.run, but does not tokenise the
arguments. This allows us to pass command line arguments through to
another program without having to re-quote them.

Closes #417
2020-05-02 11:05:09 +01:00
SquidDev
ed4229ab70 Keep ids of unlabelled computers and turtles 2020-05-02 10:38:18 +01:00
SquidDev
3fb906ef6c Show computer/disk ids in the tool tip more often
- Remove the parenthesis around the text (so it's now
   "Computer ID: 12"), rather than "(Computer ID: 12").
 - Show the tooltip if the computer has an ID and no label (as well as
   when in advanced mode).
2020-05-02 10:38:17 +01:00
Jonathan Coates
e1663f3df0 Fix malformed doc comments 2020-05-01 08:50:44 +01:00
SquidDev
52c6584c81 Fix treasure disk colour handler 2020-04-30 11:33:31 +01:00
SquidDev
9f87eda5de Load treasure disks from various loot tables 2020-04-30 11:19:46 +01:00
SquidDev
697e9449cf Delete existing treasure disks 2020-04-30 10:59:55 +01:00
SquidDev
76c3e4c155 Merge branch 'mc-1.14.x' into mc-1.15.x 2020-04-29 17:37:53 +01:00
SquidDev
358289b5f9 Merge branch 'master' into mc-1.14.x 2020-04-29 17:37:36 +01:00
SquidDev
5eec24676f Prevent computers scanning peripherals twice 2020-04-29 17:37:02 +01:00
SquidDev
f52b8fa2de Bump mappings version 2020-04-29 16:23:18 +01:00
Jonathan Coates
447c3ab125 Clean up dance.lua
Not sure what keys.escape was doing there. That's very old.
2020-04-28 09:51:06 +01:00
Jonathan Coates
8fac68386e Fix usages of global variables
- Lint references to unknown fields of modules, excluding the keys and
   colours modules. This caught several silly errors in our stub files,
   but nothing else.
 - Lint on using unknown globals. This highlighted a couple of really
   silly mistakes. Fixes #427.
 - Add documentation for fs.attributes, fs.getCapacity and pocket, as
   they were not defined before.

Co-authored-by: JackMacWindows <jackmacwindowslinux@gmail.com>
2020-04-28 09:42:34 +01:00
SquidDev
a3021c4697 Merge branch 'mc-1.14.x' into mc-1.15.x 2020-04-24 18:44:29 +01:00
SquidDev
3d7a81696d Merge branch 'mc-1.14.x' into mc-1.15.x 2020-04-23 10:58:30 +01:00
SquidDev
33260a7747 Merge branch 'mc-1.14.x' into mc-1.15.x 2020-04-23 10:04:37 +01:00
SquidDev
af40f5ae5c Add back CraftTweaker integration 2020-04-22 11:11:02 +01:00
SquidDev
759d02a249 Some post-merge cleanup 2020-04-22 11:04:29 +01:00
SquidDev
d7729337ac Merge branch 'mc-1.14.x' into mc-1.15.x 2020-04-22 10:39:00 +01:00
Jonathan Coates
f106733d71 Redo how http block/allow lists are stored. (#396)
This replaces the allow/block lists with a series of rules. Each rule
takes the form

    [[http.rules]]
    host = "127.0.0.0/8"
    action = "block"

This is pretty much the same as the previous config style, in that hosts
may be domains, wildcards or in CIDR notation. However, they may also be
mixed, so you could allow a specific IP, and then block all others.
2020-04-22 08:58:21 +01:00
SquidDev
2360a6e951 Remove several deprecated methods 2020-04-10 21:26:11 +01:00
SquidDev
f4f71185ae Add back map rendering
Closes #357. Also bump Forge and mappings versions - it includes a
couple of bug fixes we need.
2020-04-10 21:17:31 +01:00
SquidDev
649acbae1c Add back item frame rendering for printouts
Also fix a recipe loading issue, due to capitalisation of enums
2020-01-30 10:07:47 +00:00
SquidDev
05eada427b Add back custom block highlights
Also bump Forge version to enable this
2020-01-30 09:00:37 +00:00
SquidDev
f3a330e330 Normalise enums to use SHOUTY_CASE
PascalCase is more .NET than Java
2020-01-28 22:28:48 +00:00
SquidDev
044d2b2b06 Some API cleanup
- Remove *Stream methods on IMount/IWritableMount, and make the channel
   ones the primary.
 - Fix location of AbstractTurtleUpgrade
 - Make IComputerAccess.getAvailablePeripheral and .getMainThreadMonitor
   mandatory.
 - IComputerAccess throws a specialised NotAttachedException
2020-01-28 22:23:43 +00:00
SquidDev
fb440b0d2e Update to 1.15
Most of the port is pretty simple. The main problems are regarding
changes to Minecraft's rendering system.

 - Remove several rendering tweaks until Forge's compatibility it
   brought up-to-date
    - Map rendering for pocket computers and printouts
    - Item frame rendering for printouts
    - Custom block outlines for monitors and cables/wired modems
    - Custom breaking progress for cables/wired modems

 - Turtle "Dinnerbone" rendering is currently broken, as normals are not
   correctly transformed.

 - Rewrite FixedWidthFontRenderer to to the buffer in a single sweep.
   In order to do this, the term_font now also bundles a "background"
   section, which is just a blank region of the screen.

 - Render monitors using a VBO instead of a call list. I haven't
   compared performance yet, but it manages to render a 6x5 array of
   _static_ monitors at almost 60fps, which seems pretty reasonable.
2020-01-24 09:12:29 +00:00
380 changed files with 11003 additions and 20001 deletions

View File

@@ -32,6 +32,9 @@ jobs:
name: CC-Tweaked
path: build/libs
- name: Upload Coverage
run: bash <(curl -s https://codecov.io/bash)
lint-lua:
name: Lint Lua
runs-on: ubuntu-latest

View File

@@ -9,7 +9,7 @@ buildscript {
}
dependencies {
classpath 'com.google.code.gson:gson:2.8.1'
classpath 'net.minecraftforge.gradle:ForgeGradle:3.0.154'
classpath 'net.minecraftforge.gradle:ForgeGradle:3.0.169'
classpath 'net.sf.proguard:proguard-gradle:6.1.0beta2'
classpath 'org.ajoberstar.grgit:grgit-gradle:3.0.0'
}
@@ -17,6 +17,7 @@ buildscript {
plugins {
id "checkstyle"
id "jacoco"
id "com.github.hierynomus.license" version "0.15.0"
id "com.matthewprenger.cursegradle" version "1.3.0"
id "com.github.breadmoirai.github-release" version "2.2.4"
@@ -47,7 +48,7 @@ minecraft {
}
server {
workingDirectory project.file('run')
workingDirectory project.file("run/server-${mc_version}")
property 'forge.logging.markers', 'REGISTRIES,REGISTRYDUMP'
property 'forge.logging.console.level', 'debug'
@@ -99,15 +100,16 @@ dependencies {
minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}"
compileOnly fg.deobf("mezz.jei:jei-1.14.4:6.0.0.27:api")
compileOnly fg.deobf("com.blamejared.crafttweaker:CraftTweaker-1.14.4:5.0.1.162")
compileOnly fg.deobf("mezz.jei:jei-1.15.2:6.0.0.3:api")
compileOnly fg.deobf("com.blamejared.crafttweaker:CraftTweaker-1.15.2:6.0.0.9")
runtimeOnly fg.deobf("mezz.jei:jei-1.14.4:6.0.0.27")
runtimeOnly fg.deobf("mezz.jei:jei-1.15.2:6.0.0.3")
shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
testImplementation 'org.hamcrest:hamcrest:2.2'
deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0"
}
@@ -200,8 +202,6 @@ task proguardMove(dependsOn: proguard) {
}
}
processResources {
inputs.property "version", mod_version
inputs.property "mcversion", mc_version
@@ -288,6 +288,15 @@ test {
}
}
jacocoTestReport {
reports {
xml.enabled true
html.enabled true
}
}
check.dependsOn jacocoTestReport
license {
mapping("java", "SLASHSTAR_STYLE")
strictCheck true

View File

@@ -10,6 +10,10 @@
<property name="file" value="config/checkstyle/suppressions.xml" />
</module>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="render_old"/>
</module>
<module name="TreeWalker">
<!-- Annotations -->
<module name="AnnotationLocation" />
@@ -110,11 +114,11 @@
</module>
<module name="ParameterName" />
<module name="StaticVariableName">
<property name="format" value="^[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z]+)?$" />
<property name="format" value="^[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z_]+)?$" />
<property name="applyToPrivate" value="false" />
</module>
<module name="StaticVariableName">
<property name="format" value="^(s_)?[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z]+)?$" />
<property name="format" value="^(s_)?[a-z][a-zA-Z0-9]*|CAPABILITY(_[A-Z_]+)?$" />
<property name="applyToPrivate" value="true" />
</module>
<module name="TypeName" />

View File

@@ -1,6 +1,77 @@
--- Execute a specific command.
--
-- @tparam string command The command to execute.
-- @treturn boolean Whether the command executed successfully.
-- @treturn { string... } The output of this command, as a list of lines.
-- @treturn number|nil The number of "affected" objects, or `nil` if the command
-- failed. The definition of this varies from command to command.
-- @usage Set the block above the command computer to stone.
--
-- commands.exec("setblock ~ ~1 ~ minecraft:stone")
function exec(command) end
--- Asynchronously execute a command.
--
-- Unlike @{exec}, this will immediately return, instead of waiting for the
-- command to execute. This allows you to run multiple commands at the same
-- time.
--
-- When this command has finished executing, it will queue a `task_complete`
-- event containing the result of executing this command (what @{exec} would
-- return).
--
-- @tparam string command The command to execute.
-- @treturn number The "task id". When this command has been executed, it will
-- queue a `task_complete` event with a matching id.
-- @usage Asynchronously sets the block above the computer to stone.
--
-- commands.execAsync("~ ~1 ~ minecraft:stone")
-- @see parallel One may also use the parallel API to run multiple commands at
-- once.
function execAsync(commad) end
--- List all available commands which the computer has permission to execute.
--
-- @treturn { string... } A list of all available commands
function list() end
--- Get the position of the current command computer.
--
-- @treturn number This computer's x position.
-- @treturn number This computer's y position.
-- @treturn number This computer's z position.
-- @see gps.locate To get the position of a non-command computer.
function getBlockPosition() end
function getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z) end
--- Get some basic information about a block.
--
-- The returned table contains the current name, metadata and block state (as
-- with @{turtle.inspect}). If there is a tile entity for that block, its NBT
-- will also be returned.
--
-- @tparam number x The x position of the block to query.
-- @tparam number y The y position of the block to query.
-- @tparam number z The z position of the block to query.
-- @treturn table The given block's information.
-- @throws If the coordinates are not within the world, or are not currently
-- loaded.
function getBlockInfo(x, y, z) end
--- Get information about a range of blocks.
--
-- This returns the same information as @{getBlockInfo}, just for multiple
-- blocks at once.
--
-- Blocks are traversed by ascending y level, followed by z and x - the returned
-- table may be indexed using `x + z*width + y*depth*depth`.
--
-- @tparam number min_x The start x coordinate of the range to query.
-- @tparam number min_y The start y coordinate of the range to query.
-- @tparam number min_z The start z coordinate of the range to query.
-- @tparam number max_x The end x coordinate of the range to query.
-- @tparam number max_y The end y coordinate of the range to query.
-- @tparam number max_z The end z coordinate of the range to query.
-- @treturn { table... } A list of information about each block.
-- @throws If the coordinates are not within the world.
-- @throws If trying to get information about more than 4096 blocks.
function getBlockInfos(min_x, min_y, min_z, max_x, max_y, max_z) end

View File

@@ -19,6 +19,48 @@ function getFreeSpace(path) end
function find(pattern) end
function getDir(path) end
--- Returns true if a path is mounted to the parent filesystem.
--
-- The root filesystem "/" is considered a mount, along with disk folders and
-- the rom folder. Other programs (such as network shares) can exstend this to
-- make other mount types by correctly assigning their return value for getDrive.
--
-- @tparam string path The path to check.
-- @treturn boolean If the path is mounted, rather than a normal file/folder.
-- @throws If the path does not exist.
-- @see getDrive
function isDriveRoot(path) end
--- Get the capacity of the drive at the given path.
--
-- This may be used in conjunction with @{getFreeSpace} to determine what
-- percentage of this drive has been used.
--
-- @tparam string path The path of the drive to get.
-- @treturn number This drive's capacity. This will be 0 for "read-only" drives,
-- such as the ROM or treasure disks.
function getCapacity(path) end
--- Get attributes about a specific file or folder.
--
-- The returned attributes table contains information about the size of the
-- file, whether it is a directory, and when it was created and last modified.
--
-- The creation and modification times are given as the number of milliseconds
-- since the UNIX epoch. This may be given to @{os.date} in order to convert it
-- to more usable form.
--
-- @tparam string path The path to get attributes for.
-- @treturn { size = number, isDir = boolean, created = number, modified = number }
-- The resulting attributes.
-- @throws If the path does not exist.
-- @see getSize If you only care about the file's size.
-- @see isDir If you only care whether a path is a directory or not.
function attributes(path) end
-- Defined in bios.lua
function complete(sPath, sLocation, bIncludeFiles, bIncludeDirs) end
--- A file handle which can be read from.
--
-- @type ReadHandle

View File

@@ -15,3 +15,10 @@ function cancelTimer(id) end
function cancelAlarm(id) end
function epoch(timezone) end
function date(format, time) end
-- Defined in bios.lua
function loadAPI(path) end
function pullEvent(filter) end
function pullEventRaw(filter) end
function version() end
function run(env, path, ...) end

28
doc/stub/pocket.lua Normal file
View File

@@ -0,0 +1,28 @@
--[[-
Control the current pocket computer, adding or removing upgrades.
This API is only available on pocket computers. As such, you may use its
presence to determine what kind of computer you are using:
```lua
if pocket then
print("On a pocket computer")
else
print("On something else")
end
```
]]
--- Search the player's inventory for another upgrade, replacing the existing
-- one with that item if found.
--
-- This inventory search starts from the player's currently selected slot,
-- allowing you to prioritise upgrades.
--
-- @throws If an upgrade cannot be found.
function equipBack() end
--- Remove the pocket computer's current upgrade.
--
-- @throws If this pocket computer does not currently have an upgrade.
function unequipBack() end

View File

@@ -1,14 +1,120 @@
--[[- Interact with redstone attached to this computer.
The @{redstone} library exposes three "types" of redstone control:
- Binary input/output (@{setOutput}/@{getInput}): These simply check if a
redstone wire has any input or output. A signal strength of 1 and 15 are
treated the same.
- Analogue input/output (@{setAnalogueOutput}/@{getAnalogueInput}): These
work with the actual signal strength of the redstone wired, from 0 to 15.
- Bundled cables (@{setBundledOutput}/@{getBundledInput}): These interact with
"bundled" cables, such as those from Project:Red. These allow you to send
16 separate on/off signals. Each channel corresponds to a colour, with the
first being @{colors.white} and the last @{colors.black}.
Whenever a redstone input changes, a `redstone` event will be fired. This may
be used in or
This module may also be referred to as `rs`. For example, one may call
`rs.getSides()` instead of @{redstone.getSides}.
@module redstone
@usage Toggle the redstone signal above the computer every 0.5 seconds.
while true do
redstone.setOutput("top", not redstone.getOutput("top"))
sleep(0.5)
end
@usage Mimic a redstone comparator in [subtraction mode][comparator].
while true do
local rear = rs.getAnalogueInput("back")
local sides = math.max(rs.getAnalogueInput("left"), rs.getAnalogueInput("right"))
rs.setAnalogueOutput("front", math.max(rear - sides, 0))
os.pullEvent("redstone") -- Wait for a change to inputs.
end
[comparator]: https://minecraft.gamepedia.com/Redstone_Comparator#Subtract_signal_strength "Redstone Comparator on the Minecraft wiki."
]]
--- Returns a table containing the six sides of the computer. Namely, "top",
-- "bottom", "left", "right", "front" and "back".
--
-- @treturn { string... } A table of valid sides.
function getSides() end
--- Turn the redstone signal of a specific side on or off.
--
-- @tparam string side The side to set.
-- @tparam boolean on Whether the redstone signal should be on or off. When on,
-- a signal strength of 15 is emitted.
function setOutput(side, on) end
--- Get the current redstone output of a specific side.
--
-- @tparam string side The side to get.
-- @treturn boolean Whether the redstone output is on or off.
-- @see setOutput
function getOutput(side) end
--- Get the current redstone input of a specific side.
--
-- @tparam string side The side to get.
-- @treturn boolean Whether the redstone input is on or off.
function getInput(side) end
function setBundledOutput(side, output) end
function getBundledOutput(side) end
function getBundledInput(side) end
function testBundledInput(side, mask) end
--- Set the redstone signal strength for a specific side.
--
-- @tparam string side The side to set.
-- @tparam number value The signal strength, between 0 and 15.
-- @throws If `value` is not between 0 and 15.
function setAnalogOutput(side, value) end
setAnalogueOutput = setAnalogOutput
--- Get the redstone output signal strength for a specific side.
--
-- @tparam string side The side to get.
-- @treturn number The output signal strength, between 0 and 15.
-- @see setAnalogueOutput
function getAnalogOutput(sid) end
getAnalogueOutput = getAnalogOutput
--- Get the redstone input signal strength for a specific side.
--
-- @tparam string side The side to get.
-- @treturn number The input signal strength, between 0 and 15.
function getAnalogInput(side) end
getAnalogueInput = getAnaloguInput
getAnalogueInput = getAnalogInput
--- Set the bundled cable output for a specific side.
--
-- @tparam string side The side to set.
-- @tparam number The colour bitmask to set.
-- @see colors.subtract For removing a colour from the bitmask.
-- @see colors.combine For adding a colour to the bitmask.
function setBundledOutput(side, output) end
--- Get the bundled cable output for a specific side.
--
-- @tparam string side The side to get.
-- @treturn number The bundled cable's output.
function getBundledOutput(side) end
--- Get the bundled cable input for a specific side.
--
-- @tparam string side The side to get.
-- @treturn number The bundled cable's input.
-- @see testBundledInput To determine if a specific colour is set.
function getBundledInput(side) end
--- Determine if a specific combination of colours are on for the given side.
--
-- @tparam string side The side to test.
-- @tparam number mask The mask to test.
-- @see getBundledInput
-- @see colors.combine For adding a colour to the bitmask.
-- @usage Check if @{colors.white} and @{colors.black} are on for above the
-- computer.
--
-- print(redstone.testBundledInput("top", colors.combine(colors.white, colors.black)))
function testBundledInput(side, mask) end

View File

@@ -15,14 +15,14 @@ isColor = isColour
function getTextColour() end
getTextColor = getTextColor
function getBackgroundColour() end
getBackgroundColour = getBackgroundColour
getBackgroundColor = getBackgroundColour
function blit(text, text_colours, background_colours) end
function setPaletteColour(colour, ...) end
setPaletteColour = setPaletteColour
setPaletteColor = setPaletteColour
function getPaletteColour(colour, ...) end
getPaletteColour = getPaletteColour
getPaletteColor = getPaletteColour
function nativePaletteColour(colour) end
nativePaletteColour = nativePaletteColour
nativePaletteColor = nativePaletteColour
--- @type Redirect
local Redirect = {}

View File

@@ -1,7 +1,7 @@
# Mod properties
mod_version=1.87.1
mod_version=1.89.1
# Minecraft properties (update mods.toml when changing)
mc_version=1.14.4
forge_version=28.1.71
mappings_version=20191123-1.14.3
mc_version=1.15.2
forge_version=31.1.41
mappings_version=20200429-1.15.1

View File

@@ -33,17 +33,27 @@
;; It's useful to name arguments for documentation, so we allow this. It'd
;; be good to find a compromise in the future, but this works for now.
-var:unused-arg
-var:unused-arg)
;; Some APIS (keys, colour and os mainly) are incomplete right now.
-var:unresolved-member)
(lint
(bracket-spaces
(call no-space)
(function-args no-space)
(parens no-space)
(table space)
(index no-space))))
(index no-space))
;; colours imports from colors, and we don't handle that right now.
;; keys is entirely dynamic, so we skip it.
(dynamic-modules colours keys)
(globals
:max
_CC_DEFAULT_SETTINGS
_CC_DISABLE_LUA51_FEATURES
;; Ideally we'd pick these up from bios.lua, but illuaminate currently
;; isn't smart enough.
sleep write printError read rs)))
;; We disable the unused global linter in bios.lua and the APIs. In the future
;; hopefully we'll get illuaminate to handle this.
@@ -60,17 +70,13 @@
;; Suppress warnings for currently undocumented modules.
(at
(/doc/stub/commands.lua
/doc/stub/fs.lua
(/doc/stub/fs.lua
/doc/stub/http.lua
/doc/stub/os.lua
/doc/stub/redstone.lua
/doc/stub/term.lua
/doc/stub/turtle.lua
/src/main/resources/*/computercraft/lua/rom/apis/command/commands.lua
/src/main/resources/*/computercraft/lua/rom/apis/io.lua
/src/main/resources/*/computercraft/lua/rom/apis/window.lua
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua)
/src/main/resources/*/computercraft/lua/rom/apis/window.lua)
(linters -doc:undocumented -doc:undocumented-arg))
@@ -79,6 +85,11 @@
(/src/main/resources/*/computercraft/lua/rom/apis/textutils.lua
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/completion.lua
/src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua
/src/main/resources/*/computercraft/lua/rom/programs/advanced/multishell.lua
/src/main/resources/*/computercraft/lua/rom/programs/shell.lua)
(linters -doc:unresolved-reference))
(at /src/test/resources/test-rom
(lint
(globals
:max sleep write
cct_test describe expect howlci fail it pending stub)))

View File

@@ -17,6 +17,9 @@
{
"condition": "computercraft:block_named"
},
{
"condition": "computercraft:has_id"
},
{
"condition": "minecraft:inverted",
"term": {

View File

@@ -17,6 +17,9 @@
{
"condition": "computercraft:block_named"
},
{
"condition": "computercraft:has_id"
},
{
"condition": "minecraft:inverted",
"term": {

View File

@@ -17,6 +17,9 @@
{
"condition": "computercraft:block_named"
},
{
"condition": "computercraft:has_id"
},
{
"condition": "minecraft:inverted",
"term": {

View File

@@ -17,6 +17,9 @@
{
"condition": "computercraft:block_named"
},
{
"condition": "computercraft:has_id"
},
{
"condition": "minecraft:inverted",
"term": {

View File

@@ -0,0 +1 @@
{}

View File

@@ -6,7 +6,8 @@
package dan200.computercraft;
import dan200.computercraft.api.turtle.event.TurtleAction;
import dan200.computercraft.core.apis.AddressPredicate;
import dan200.computercraft.core.apis.http.options.Action;
import dan200.computercraft.core.apis.http.options.AddressRule;
import dan200.computercraft.shared.Config;
import dan200.computercraft.shared.computer.blocks.BlockComputer;
import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
@@ -39,8 +40,13 @@ import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Mod( ComputerCraft.MOD_ID )
public final class ComputerCraft
@@ -50,8 +56,8 @@ public final class ComputerCraft
public static final int DATAFIXER_VERSION = 0;
// Configuration options
public static final String[] DEFAULT_HTTP_WHITELIST = new String[] { "*" };
public static final String[] DEFAULT_HTTP_BLACKLIST = new String[] {
public static final String[] DEFAULT_HTTP_ALLOW = new String[] { "*" };
public static final String[] DEFAULT_HTTP_DENY = new String[] {
"127.0.0.0/8",
"10.0.0.0/8",
"172.16.0.0/12",
@@ -62,34 +68,38 @@ public final class ComputerCraft
public static int computerSpaceLimit = 1000 * 1000;
public static int floppySpaceLimit = 125 * 1000;
public static int maximumFilesOpen = 128;
public static boolean disable_lua51_features = false;
public static String default_computer_settings = "";
public static boolean debug_enable = true;
public static boolean logPeripheralErrors = false;
public static boolean disableLua51Features = false;
public static String defaultComputerSettings = "";
public static boolean debugEnable = true;
public static boolean logComputerErrors = true;
public static boolean commandRequireCreative = true;
public static int computer_threads = 1;
public static int computerThreads = 1;
public static long maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( 10 );
public static long maxMainComputerTime = TimeUnit.MILLISECONDS.toNanos( 5 );
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_blacklist = new AddressPredicate( DEFAULT_HTTP_BLACKLIST );
public static boolean httpEnabled = true;
public static boolean httpWebsocketEnabled = true;
public static List<AddressRule> httpRules = Collections.unmodifiableList( Stream.concat(
Stream.of( DEFAULT_HTTP_DENY )
.map( x -> AddressRule.parse( x, Action.DENY.toPartial() ) )
.filter( Objects::nonNull ),
Stream.of( DEFAULT_HTTP_ALLOW )
.map( x -> AddressRule.parse( x, Action.ALLOW.toPartial() ) )
.filter( Objects::nonNull )
).collect( Collectors.toList() ) );
public static int httpTimeout = 30000;
public static int httpMaxRequests = 16;
public static long httpMaxDownload = 16 * 1024 * 1024;
public static long httpMaxUpload = 4 * 1024 * 1024;
public static int httpMaxWebsockets = 4;
public static int httpMaxWebsocketMessage = 128 * 1024;
public static boolean enableCommandBlock = false;
public static int modem_range = 64;
public static int modem_highAltitudeRange = 384;
public static int modem_rangeDuringStorm = 64;
public static int modem_highAltitudeRangeDuringStorm = 384;
public static int modemRange = 64;
public static int modemHighAltitudeRange = 384;
public static int modemRangeDuringStorm = 64;
public static int modemHighAltitudeRangeDuringStorm = 384;
public static int maxNotesPerTick = 8;
public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST;
public static long monitorBandwidth = 1_000_000;
public static boolean turtlesNeedFuel = true;
public static int turtleFuelLimit = 20000;
@@ -187,11 +197,6 @@ public final class ComputerCraft
Config.load();
}
public static String getVersion()
{
return "${version}";
}
public static InputStream getResourceFile( String domain, String subPath )
{
IReloadableResourceManager manager = ServerLifecycleHooks.getCurrentServer().getResourceManager();

View File

@@ -5,7 +5,6 @@
*/
package dan200.computercraft;
import com.google.common.collect.MapMaker;
import dan200.computercraft.api.ComputerCraftAPI.IComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
@@ -24,39 +23,40 @@ import dan200.computercraft.core.filesystem.ResourceMount;
import dan200.computercraft.shared.*;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import dan200.computercraft.shared.util.IDAssigner;
import dan200.computercraft.shared.wired.CapabilityWiredElement;
import dan200.computercraft.shared.wired.WiredNode;
import net.minecraft.resources.IReloadableResourceManager;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockReader;
import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.server.ServerLifecycleHooks;
import javax.annotation.Nonnull;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Map;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_WIRED_ELEMENT;
public final class ComputerCraftAPIImpl implements IComputerCraftAPI
{
public static final ComputerCraftAPIImpl INSTANCE = new ComputerCraftAPIImpl();
private String version;
private ComputerCraftAPIImpl()
{
}
private WeakReference<IReloadableResourceManager> currentResources;
private final Map<ResourceLocation, ResourceMount> mountCache = new MapMaker().weakValues().concurrencyLevel( 1 ).makeMap();
@Nonnull
@Override
public String getInstalledVersion()
{
return "${version}";
if( version != null ) return version;
return version = ModList.get().getModContainerById( ComputerCraft.MOD_ID )
.map( x -> x.getModInfo().getVersion().toString() )
.orElse( "unknown" );
}
@Override
@@ -147,6 +147,6 @@ public final class ComputerCraftAPIImpl implements IComputerCraftAPI
public LazyOptional<IWiredElement> getWiredElementAt( @Nonnull IBlockReader world, @Nonnull BlockPos pos, @Nonnull Direction side )
{
TileEntity tile = world.getTileEntity( pos );
return tile == null ? LazyOptional.empty() : tile.getCapability( CapabilityWiredElement.CAPABILITY, side );
return tile == null ? LazyOptional.empty() : tile.getCapability( CAPABILITY_WIRED_ELEMENT, side );
}
}

View File

@@ -43,9 +43,10 @@ public final class ComputerCraftAPI
}
@Nonnull
@Deprecated
public static String getAPIVersion()
{
return "${version}";
return getInstalledVersion();
}
/**
@@ -112,31 +113,6 @@ public final class ComputerCraftAPI
return getInstance().createResourceMount( domain, subPath );
}
/**
* Creates a file system mount to a resource folder, and returns it.
*
* Use in conjunction with {@link IComputerAccess#mount} or {@link IComputerAccess#mountWritable} to mount a
* resource folder onto a computer's file system.
*
* The files in this mount will be a combination of files in all mod jar, and data packs that contain
* resources with the same domain and path.
*
* @param klass The mod class to which the files belong.
* @param domain The domain under which to look for resources. eg: "mymod".
* @param subPath The subPath under which to look for resources. eg: "lua/myfiles".
* @return The mount, or {@code null} if it could be created for some reason.
* @see IComputerAccess#mount(String, IMount)
* @see IComputerAccess#mountWritable(String, IWritableMount)
* @see IMount
* @deprecated Use {@link #createResourceMount(String, String)} instead.
*/
@Nullable
@Deprecated
public static IMount createResourceMount( Class<?> klass, @Nonnull String domain, @Nonnull String subPath )
{
return getInstance().createResourceMount( domain, subPath );
}
/**
* Registers a peripheral provider to convert blocks into {@link IPeripheral} implementations.
*

View File

@@ -0,0 +1,61 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. 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.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.TransformationMatrix;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.ModelManager;
import net.minecraft.client.renderer.model.ModelResourceLocation;
import net.minecraft.item.ItemStack;
import javax.annotation.Nonnull;
import java.util.Objects;
/**
* A model to render, combined with a transformation matrix to apply.
*/
public final class TransformedModel
{
private final IBakedModel model;
private final TransformationMatrix matrix;
public TransformedModel( @Nonnull IBakedModel model, @Nonnull TransformationMatrix matrix )
{
this.model = Objects.requireNonNull( model );
this.matrix = Objects.requireNonNull( matrix );
}
public TransformedModel( @Nonnull IBakedModel model )
{
this.model = Objects.requireNonNull( model );
this.matrix = TransformationMatrix.identity();
}
public static TransformedModel of( @Nonnull ModelResourceLocation location )
{
ModelManager modelManager = Minecraft.getInstance().getModelManager();
return new TransformedModel( modelManager.getModel( location ) );
}
public static TransformedModel of( @Nonnull ItemStack item, @Nonnull TransformationMatrix transform )
{
IBakedModel model = Minecraft.getInstance().getItemRenderer().getItemModelMesher().getItemModel( item );
return new TransformedModel( model, transform );
}
@Nonnull
public IBakedModel getModel()
{
return model;
}
@Nonnull
public TransformationMatrix getMatrix()
{
return matrix;
}
}

View File

@@ -11,8 +11,6 @@ import net.minecraft.world.World;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
@@ -23,10 +21,10 @@ import java.util.List;
*
* Ready made implementations of this interface can be created using
* {@link ComputerCraftAPI#createSaveDirMount(World, String, long)} or
* {@link ComputerCraftAPI#createResourceMount(Class, String, String)}, or you're free to implement it yourselves!
* {@link ComputerCraftAPI#createResourceMount(String, String)}, or you're free to implement it yourselves!
*
* @see ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see ComputerCraftAPI#createResourceMount(Class, String, String)
* @see ComputerCraftAPI#createResourceMount(String, String)
* @see IComputerAccess#mount(String, IMount)
* @see IWritableMount
*/
@@ -68,18 +66,6 @@ public interface IMount
*/
long getSize( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link InputStream} representing its contents.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream representing the contents of the file.
* @throws IOException If the file does not exist, or could not be opened.
* @deprecated Use {@link #openChannelForRead(String)} instead
*/
@Nonnull
@Deprecated
InputStream openForRead( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link ReadableByteChannel} representing its contents.
*
@@ -90,10 +76,7 @@ public interface IMount
* @throws IOException If the file does not exist, or could not be opened.
*/
@Nonnull
default ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException
{
return Channels.newChannel( openForRead( path ) );
}
ReadableByteChannel openForRead( @Nonnull String path ) throws IOException;
/**
* Get attributes about the given file.

View File

@@ -12,7 +12,6 @@ import net.minecraft.world.World;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.util.OptionalLong;
@@ -46,18 +45,6 @@ public interface IWritableMount extends IMount
*/
void delete( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link OutputStream} for writing to it.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream for writing to
* @throws IOException If the file could not be opened for writing.
* @deprecated Use {@link #openChannelForWrite(String)} instead.
*/
@Nonnull
@Deprecated
OutputStream openForWrite( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link OutputStream} for writing to it.
*
@@ -67,22 +54,7 @@ public interface IWritableMount extends IMount
* @throws IOException If the file could not be opened for writing.
*/
@Nonnull
default WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException
{
return Channels.newChannel( openForWrite( path ) );
}
/**
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
*
* @param path A file path in normalised format, relative to the mount location. ie: "programs/myprogram".
* @return A stream for writing to.
* @throws IOException If the file could not be opened for writing.
* @deprecated Use {@link #openChannelForAppend(String)} instead.
*/
@Nonnull
@Deprecated
OutputStream openForAppend( @Nonnull String path ) throws IOException;
WritableByteChannel openForWrite( @Nonnull String path ) throws IOException;
/**
* Opens a file with a given path, and returns an {@link OutputStream} for appending to it.
@@ -93,10 +65,7 @@ public interface IWritableMount extends IMount
* @throws IOException If the file could not be opened for writing.
*/
@Nonnull
default WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException
{
return Channels.newChannel( openForAppend( path ) );
}
WritableByteChannel openForAppend( @Nonnull String path ) throws IOException;
/**
* Get the amount of free space on the mount, in bytes. You should decrease this value as the user writes to the

View File

@@ -1,334 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
/**
* Provides methods for extracting values and validating Lua arguments, such as those provided to
* {@link ILuaObject#callMethod(ILuaContext, int, Object[])} or
* {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}.
*
* This provides two sets of functions: the {@code get*} methods, which require an argument to be valid, and
* {@code opt*}, which accept a default value and return that if the argument was not present or was {@code null}.
* If the argument is of the wrong type, a suitable error message will be thrown, with a similar format to Lua's own
* error messages.
*
* <h2>Example usage:</h2>
* <pre>
* {@code
* int slot = getInt( args, 0 );
* int amount = optInt( args, 1, 64 );
* }
* </pre>
*/
public final class ArgumentHelper
{
private ArgumentHelper()
{
}
/**
* Get a string representation of the given value's type.
*
* @param value The value whose type we are trying to compute.
* @return A string representation of the given value's type, in a similar format to that provided by Lua's
* {@code type} function.
*/
@Nonnull
public static String getType( @Nullable Object value )
{
if( value == null ) return "nil";
if( value instanceof String ) return "string";
if( value instanceof Boolean ) return "boolean";
if( value instanceof Number ) return "number";
if( value instanceof Map ) return "table";
return "userdata";
}
/**
* Construct a "bad argument" exception, from an expected type and the actual value provided.
*
* @param index The argument number, starting from 0.
* @param expected The expected type for this argument.
* @param actual The actual value provided for this argument.
* @return The constructed exception, which should be thrown immediately.
*/
@Nonnull
public static LuaException badArgumentOf( int index, @Nonnull String expected, @Nullable Object actual )
{
return badArgument( index, expected, getType( actual ) );
}
/**
* Construct a "bad argument" exception, from an expected and actual type.
*
* @param index The argument number, starting from 0.
* @param expected The expected type for this argument.
* @param actual The provided type for this argument.
* @return The constructed exception, which should be thrown immediately.
*/
@Nonnull
public static LuaException badArgument( int index, @Nonnull String expected, @Nonnull String actual )
{
return new LuaException( "bad argument #" + (index + 1) + " (" + expected + " expected, got " + actual + ")" );
}
/**
* Get an argument as a double.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @return The argument's value.
* @throws LuaException If the value is not a number.
* @see #getFiniteDouble(Object[], int) if you require this to be finite (i.e. not infinite or NaN).
*/
public static double getDouble( @Nonnull Object[] args, int index ) throws LuaException
{
if( index >= args.length ) throw badArgument( index, "number", "nil" );
Object value = args[index];
if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
return ((Number) value).doubleValue();
}
/**
* Get an argument as an integer.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @return The argument's value.
* @throws LuaException If the value is not an integer.
*/
public static int getInt( @Nonnull Object[] args, int index ) throws LuaException
{
return (int) getLong( args, index );
}
/**
* Get an argument as a long.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @return The argument's value.
* @throws LuaException If the value is not a long.
*/
public static long getLong( @Nonnull Object[] args, int index ) throws LuaException
{
if( index >= args.length ) throw badArgument( index, "number", "nil" );
Object value = args[index];
if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
return checkFinite( index, (Number) value ).longValue();
}
/**
* Get an argument as a finite number (not infinite or NaN).
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @return The argument's value.
* @throws LuaException If the value is not finite.
*/
public static double getFiniteDouble( @Nonnull Object[] args, int index ) throws LuaException
{
return checkFinite( index, getDouble( args, index ) );
}
/**
* Get an argument as a boolean.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @return The argument's value.
* @throws LuaException If the value is not a boolean.
*/
public static boolean getBoolean( @Nonnull Object[] args, int index ) throws LuaException
{
if( index >= args.length ) throw badArgument( index, "boolean", "nil" );
Object value = args[index];
if( !(value instanceof Boolean) ) throw badArgumentOf( index, "boolean", value );
return (Boolean) value;
}
/**
* Get an argument as a string.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @return The argument's value.
* @throws LuaException If the value is not a string.
*/
@Nonnull
public static String getString( @Nonnull Object[] args, int index ) throws LuaException
{
if( index >= args.length ) throw badArgument( index, "string", "nil" );
Object value = args[index];
if( !(value instanceof String) ) throw badArgumentOf( index, "string", value );
return (String) value;
}
/**
* Get an argument as a table.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @return The argument's value.
* @throws LuaException If the value is not a table.
*/
@Nonnull
public static Map<?, ?> getTable( @Nonnull Object[] args, int index ) throws LuaException
{
if( index >= args.length ) throw badArgument( index, "table", "nil" );
Object value = args[index];
if( !(value instanceof Map) ) throw badArgumentOf( index, "table", value );
return (Map<?, ?>) value;
}
/**
* Get an argument as a double.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number.
*/
public static double optDouble( @Nonnull Object[] args, int index, double def ) throws LuaException
{
Object value = index < args.length ? args[index] : null;
if( value == null ) return def;
if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
return ((Number) value).doubleValue();
}
/**
* Get an argument as an int.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number.
*/
public static int optInt( @Nonnull Object[] args, int index, int def ) throws LuaException
{
return (int) optLong( args, index, def );
}
/**
* Get an argument as a long.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number.
*/
public static long optLong( @Nonnull Object[] args, int index, long def ) throws LuaException
{
Object value = index < args.length ? args[index] : null;
if( value == null ) return def;
if( !(value instanceof Number) ) throw badArgumentOf( index, "number", value );
return checkFinite( index, (Number) value ).longValue();
}
/**
* Get an argument as a finite number (not infinite or NaN).
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not finite.
*/
public static double optFiniteDouble( @Nonnull Object[] args, int index, double def ) throws LuaException
{
return checkFinite( index, optDouble( args, index, def ) );
}
/**
* Get an argument as a boolean.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a boolean.
*/
public static boolean optBoolean( @Nonnull Object[] args, int index, boolean def ) throws LuaException
{
Object value = index < args.length ? args[index] : null;
if( value == null ) return def;
if( !(value instanceof Boolean) ) throw badArgumentOf( index, "boolean", value );
return (Boolean) value;
}
/**
* Get an argument as a string.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a string.
*/
public static String optString( @Nonnull Object[] args, int index, String def ) throws LuaException
{
Object value = index < args.length ? args[index] : null;
if( value == null ) return def;
if( !(value instanceof String) ) throw badArgumentOf( index, "string", value );
return (String) value;
}
/**
* Get an argument as a table.
*
* @param args The arguments to extract from.
* @param index The index into the argument array to read from.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a table.
*/
public static Map<?, ?> optTable( @Nonnull Object[] args, int index, Map<Object, Object> def ) throws LuaException
{
Object value = index < args.length ? args[index] : null;
if( value == null ) return def;
if( !(value instanceof Map) ) throw badArgumentOf( index, "table", value );
return (Map<?, ?>) value;
}
private static Number checkFinite( int index, Number value ) throws LuaException
{
checkFinite( index, value.doubleValue() );
return value;
}
private static double checkFinite( int index, double value ) throws LuaException
{
if( !Double.isFinite( value ) ) throw badArgument( index, "number", getNumericType( value ) );
return value;
}
/**
* Returns a more detailed representation of this number's type. If this is finite, it will just return "number",
* otherwise it returns whether it is infinite or NaN.
*
* @param value The value to extract the type for.
* @return This value's numeric type.
*/
@Nonnull
public static String getNumericType( double value )
{
if( Double.isNaN( value ) ) return "nan";
if( value == Double.POSITIVE_INFINITY ) return "inf";
if( value == Double.NEGATIVE_INFINITY ) return "-inf";
return "number";
}
}

View File

@@ -0,0 +1,407 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Optional;
import static dan200.computercraft.api.lua.LuaValues.checkFinite;
/**
* The arguments passed to a function.
*/
public interface IArguments
{
/**
* Get the number of arguments passed to this function.
*
* @return The number of passed arguments.
*/
int count();
/**
* Get the argument at the specific index. The returned value must obey the following conversion rules:
*
* <ul>
* <li>Lua values of type "string" will be represented by a {@link String}.</li>
* <li>Lua values of type "number" will be represented by a {@link Number}.</li>
* <li>Lua values of type "boolean" will be represented by a {@link Boolean}.</li>
* <li>Lua values of type "table" will be represented by a {@link Map}.</li>
* <li>Lua values of any other type will be represented by a {@code null} value.</li>
* </ul>
*
* @param index The argument number.
* @return The argument's value, or {@code null} if not present.
*/
@Nullable
Object get( int index );
/**
* Drop a number of arguments. The returned arguments instance will access arguments at position {@code i + count},
* rather than {@code i}. However, errors will still use the given argument index.
*
* @param count The number of arguments to drop.
* @return The new {@link IArguments} instance.
*/
IArguments drop( int count );
default Object[] getAll()
{
Object[] result = new Object[count()];
for( int i = 0; i < result.length; i++ ) result[i] = get( i );
return result;
}
/**
* Get an argument as a double.
*
* @param index The argument number.
* @return The argument's value.
* @throws LuaException If the value is not a number.
* @see #getFiniteDouble(int) if you require this to be finite (i.e. not infinite or NaN).
*/
default double getDouble( int index ) throws LuaException
{
Object value = get( index );
if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
return ((Number) value).doubleValue();
}
/**
* Get an argument as an integer.
*
* @param index The argument number.
* @return The argument's value.
* @throws LuaException If the value is not an integer.
*/
default int getInt( int index ) throws LuaException
{
return (int) getLong( index );
}
/**
* Get an argument as a long.
*
* @param index The argument number.
* @return The argument's value.
* @throws LuaException If the value is not a long.
*/
default long getLong( int index ) throws LuaException
{
Object value = get( index );
if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
return LuaValues.checkFiniteNum( index, (Number) value ).longValue();
}
/**
* Get an argument as a finite number (not infinite or NaN).
*
* @param index The argument number.
* @return The argument's value.
* @throws LuaException If the value is not finite.
*/
default double getFiniteDouble( int index ) throws LuaException
{
return checkFinite( index, getDouble( index ) );
}
/**
* Get an argument as a boolean.
*
* @param index The argument number.
* @return The argument's value.
* @throws LuaException If the value is not a boolean.
*/
default boolean getBoolean( int index ) throws LuaException
{
Object value = get( index );
if( !(value instanceof Boolean) ) throw LuaValues.badArgumentOf( index, "boolean", value );
return (Boolean) value;
}
/**
* Get an argument as a string.
*
* @param index The argument number.
* @return The argument's value.
* @throws LuaException If the value is not a string.
*/
@Nonnull
default String getString( int index ) throws LuaException
{
Object value = get( index );
if( !(value instanceof String) ) throw LuaValues.badArgumentOf( index, "string", value );
return (String) value;
}
/**
* Get a string argument as a byte array.
*
* @param index The argument number.
* @return The argument's value. This is a <em>read only</em> buffer.
* @throws LuaException If the value is not a string.
*/
@Nonnull
default ByteBuffer getBytes( int index ) throws LuaException
{
return LuaValues.encode( getString( index ) );
}
/**
* Get a string argument as an enum value.
*
* @param index The argument number.
* @param klass The type of enum to parse.
* @param <T> The type of enum to parse.
* @return The argument's value.
* @throws LuaException If the value is not a string or not a valid option for this enum.
*/
@Nonnull
default <T extends Enum<T>> T getEnum( int index, Class<T> klass ) throws LuaException
{
return LuaValues.checkEnum( index, klass, getString( index ) );
}
/**
* Get an argument as a table.
*
* @param index The argument number.
* @return The argument's value.
* @throws LuaException If the value is not a table.
*/
@Nonnull
default Map<?, ?> getTable( int index ) throws LuaException
{
Object value = get( index );
if( !(value instanceof Map) ) throw LuaValues.badArgumentOf( index, "table", value );
return (Map<?, ?>) value;
}
/**
* Get an argument as a double.
*
* @param index The argument number.
* @return The argument's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a number.
*/
@Nonnull
default Optional<Double> optDouble( int index ) throws LuaException
{
Object value = get( index );
if( value == null ) return Optional.empty();
if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
return Optional.of( ((Number) value).doubleValue() );
}
/**
* Get an argument as an int.
*
* @param index The argument number.
* @return The argument's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a number.
*/
@Nonnull
default Optional<Integer> optInt( int index ) throws LuaException
{
return optLong( index ).map( Long::intValue );
}
/**
* Get an argument as a long.
*
* @param index The argument number.
* @return The argument's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a number.
*/
default Optional<Long> optLong( int index ) throws LuaException
{
Object value = get( index );
if( value == null ) return Optional.empty();
if( !(value instanceof Number) ) throw LuaValues.badArgumentOf( index, "number", value );
return Optional.of( LuaValues.checkFiniteNum( index, (Number) value ).longValue() );
}
/**
* Get an argument as a finite number (not infinite or NaN).
*
* @param index The argument number.
* @return The argument's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not finite.
*/
default Optional<Double> optFiniteDouble( int index ) throws LuaException
{
Optional<Double> value = optDouble( index );
if( value.isPresent() ) LuaValues.checkFiniteNum( index, value.get() );
return value;
}
/**
* Get an argument as a boolean.
*
* @param index The argument number.
* @return The argument's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a boolean.
*/
default Optional<Boolean> optBoolean( int index ) throws LuaException
{
Object value = get( index );
if( value == null ) return Optional.empty();
if( !(value instanceof Boolean) ) throw LuaValues.badArgumentOf( index, "boolean", value );
return Optional.of( (Boolean) value );
}
/**
* Get an argument as a string.
*
* @param index The argument number.
* @return The argument's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a string.
*/
default Optional<String> optString( int index ) throws LuaException
{
Object value = get( index );
if( value == null ) return Optional.empty();
if( !(value instanceof String) ) throw LuaValues.badArgumentOf( index, "string", value );
return Optional.of( (String) value );
}
/**
* Get a string argument as a byte array.
*
* @param index The argument number.
* @return The argument's value, or {@link Optional#empty()} if not present. This is a <em>read only</em> buffer.
* @throws LuaException If the value is not a string.
*/
default Optional<ByteBuffer> optBytes( int index ) throws LuaException
{
return optString( index ).map( LuaValues::encode );
}
/**
* Get a string argument as an enum value.
*
* @param index The argument number.
* @param klass The type of enum to parse.
* @param <T> The type of enum to parse.
* @return The argument's value.
* @throws LuaException If the value is not a string or not a valid option for this enum.
*/
@Nonnull
default <T extends Enum<T>> Optional<T> optEnum( int index, Class<T> klass ) throws LuaException
{
Optional<String> str = optString( index );
return str.isPresent() ? Optional.of( LuaValues.checkEnum( index, klass, str.get() ) ) : Optional.empty();
}
/**
* Get an argument as a table.
*
* @param index The argument number.
* @return The argument's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a table.
*/
default Optional<Map<?, ?>> optTable( int index ) throws LuaException
{
Object value = get( index );
if( value == null ) return Optional.empty();
if( !(value instanceof Map) ) throw LuaValues.badArgumentOf( index, "map", value );
return Optional.of( (Map<?, ?>) value );
}
/**
* Get an argument as a double.
*
* @param index The argument number.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number.
*/
default double optDouble( int index, double def ) throws LuaException
{
return optDouble( index ).orElse( def );
}
/**
* Get an argument as an int.
*
* @param index The argument number.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number.
*/
default int optInt( int index, int def ) throws LuaException
{
return optInt( index ).orElse( def );
}
/**
* Get an argument as a long.
*
* @param index The argument number.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number.
*/
default long optLong( int index, long def ) throws LuaException
{
return optLong( index ).orElse( def );
}
/**
* Get an argument as a finite number (not infinite or NaN).
*
* @param index The argument number.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not finite.
*/
default double optFiniteDouble( int index, double def ) throws LuaException
{
return optFiniteDouble( index ).orElse( def );
}
/**
* Get an argument as a boolean.
*
* @param index The argument number.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a boolean.
*/
default boolean optBoolean( int index, boolean def ) throws LuaException
{
return optBoolean( index ).orElse( def );
}
/**
* Get an argument as a string.
*
* @param index The argument number.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a string.
*/
default String optString( int index, String def ) throws LuaException
{
return optString( index ).orElse( def );
}
/**
* Get an argument as a table.
*
* @param index The argument number.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a table.
*/
default Map<?, ?> optTable( int index, Map<Object, Object> def ) throws LuaException
{
return optTable( index ).orElse( def );
}
}

View File

@@ -0,0 +1,45 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
import javax.annotation.Nonnull;
/**
* An interface for representing custom objects returned by peripherals or other Lua objects.
*
* Generally, one does not need to implement this type - it is sufficient to return an object with some methods
* annotated with {@link LuaFunction}. {@link IDynamicLuaObject} is useful when you wish your available methods to
* change at runtime.
*/
public interface IDynamicLuaObject
{
/**
* Get the names of the methods that this object implements. This should not change over the course of the object's
* lifetime.
*
* @return The method names this object provides.
* @see IDynamicPeripheral#getMethodNames()
*/
@Nonnull
String[] getMethodNames();
/**
* Called when a user calls one of the methods that this object implements.
*
* @param context The context of the currently running lua thread. This can be used to wait for events
* or otherwise yield.
* @param method An integer identifying which method index from {@link #getMethodNames()} the computer wishes
* to call.
* @param arguments The arguments for this method.
* @return The result of this function. Either an immediate value ({@link MethodResult#of(Object...)} or an
* instruction to yield.
* @throws LuaException If the function threw an exception.
*/
@Nonnull
MethodResult callMethod( @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments ) throws LuaException;
}

View File

@@ -8,7 +8,8 @@ package dan200.computercraft.api.lua;
import dan200.computercraft.api.ComputerCraftAPI;
/**
* Represents a {@link ILuaObject} which is stored as a global variable on computer startup.
* Represents a Lua object which is stored as a global variable on computer startup. This must either provide
* {@link LuaFunction} annotated functions or implement {@link IDynamicLuaObject}.
*
* Before implementing this interface, consider alternative methods of providing methods. It is generally preferred
* to use peripherals to provide functionality to users.
@@ -16,7 +17,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
* @see ILuaAPIFactory
* @see ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory)
*/
public interface ILuaAPI extends ILuaObject
public interface ILuaAPI
{
/**
* Get the globals this API will be assigned to. This will override any other global, so you should

View File

@@ -0,0 +1,27 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
/**
* A continuation which is called when this coroutine is resumed.
*
* @see MethodResult#yield(Object[], ILuaCallback)
*/
public interface ILuaCallback
{
/**
* Resume this coroutine.
*
* @param args The result of resuming this coroutine. These will have the same form as described in
* {@link LuaFunction}.
* @return The result of this continuation. Either the result to return to the callee, or another yield.
* @throws LuaException On an error.
*/
@Nonnull
MethodResult resume( Object[] args ) throws LuaException;
}

View File

@@ -6,99 +6,25 @@
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An interface passed to peripherals and {@link ILuaObject}s by computers or turtles, providing methods
* that allow the peripheral call to wait for events before returning, just like in lua. This is very useful if you need
* to signal work to be performed on the main thread, and don't want to return until the work has been completed.
* An interface passed to peripherals and {@link IDynamicLuaObject}s by computers or turtles, providing methods
* that allow the peripheral call to interface with the computer.
*/
public interface ILuaContext
{
/**
* Wait for an event to occur on the computer, suspending the thread until it arises. This method is exactly
* equivalent to {@code os.pullEvent()} in lua.
*
* @param filter A specific event to wait for, or null to wait for any event.
* @return An object array containing the name of the event that occurred, and any event parameters.
* @throws LuaException If the user presses CTRL+T to terminate the current program while pullEvent() is
* waiting for an event, a "Terminated" exception will be thrown here.
*
* Do not attempt to catch this exception. You should use {@link #pullEventRaw(String)}
* should you wish to disable termination.
* @throws InterruptedException If the user shuts down or reboots the computer while pullEvent() is waiting for an
* event, InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
*/
@Nonnull
default Object[] pullEvent( @Nullable String filter ) throws LuaException, InterruptedException
{
Object[] results = pullEventRaw( filter );
if( results.length >= 1 && results[0].equals( "terminate" ) ) throw new LuaException( "Terminated", 0 );
return results;
}
/**
* The same as {@link #pullEvent(String)}, except "terminated" events are ignored. Only use this if you want to
* prevent program termination, which is not recommended. This method is exactly equivalent to
* {@code os.pullEventRaw()} in lua.
*
* @param filter A specific event to wait for, or null to wait for any event.
* @return An object array containing the name of the event that occurred, and any event parameters.
* @throws InterruptedException If the user shuts down or reboots the computer while pullEventRaw() is waiting for
* an event, InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @see #pullEvent(String)
*/
@Nonnull
default Object[] pullEventRaw( @Nullable String filter ) throws InterruptedException
{
return yield( new Object[] { filter } );
}
/**
* Yield the current coroutine with some arguments until it is resumed. This method is exactly equivalent to
* {@code coroutine.yield()} in lua. Use {@code pullEvent()} if you wish to wait for events.
*
* @param arguments An object array containing the arguments to pass to coroutine.yield()
* @return An object array containing the return values from coroutine.yield()
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @see #pullEvent(String)
*/
@Nonnull
Object[] yield( @Nullable Object[] arguments ) throws InterruptedException;
/**
* Queue a task to be executed on the main server thread at the beginning of next tick, waiting for it to complete.
* This should be used when you need to interact with the world in a thread-safe manner.
*
* Note that the return values of your task are handled as events, meaning more complex objects such as maps or
* {@link ILuaObject} will not preserve their identities.
*
* @param task The task to execute on the main thread.
* @return The objects returned by {@code task}.
* @throws LuaException If the task could not be queued, or if the task threw an exception.
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
*/
@Nullable
Object[] executeMainThreadTask( @Nonnull ILuaTask task ) throws LuaException, InterruptedException;
/**
* Queue a task to be executed on the main server thread at the beginning of next tick, but do not wait for it to
* complete. This should be used when you need to interact with the world in a thread-safe manner but do not care
* about the result or you wish to run asynchronously.
*
* When the task has finished, it will enqueue a {@code task_completed} event, which takes the task id, a success
* value and the return values, or an error message if it failed. If you need to wait on this event, it may be
* better to use {@link #executeMainThreadTask(ILuaTask)}.
* value and the return values, or an error message if it failed.
*
* @param task The task to execute on the main thread.
* @return The "id" of the task. This will be the first argument to the {@code task_completed} event.
* @throws LuaException If the task could not be queued.
* @see LuaFunction#mainThread() To run functions on the main thread and return their results synchronously.
*/
long issueMainThreadTask( @Nonnull ILuaTask task ) throws LuaException;
}

View File

@@ -0,0 +1,29 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
/**
* A function, which can be called from Lua. If you need to return a table of functions, it is recommended to use
* an object with {@link LuaFunction} methods, or implement {@link IDynamicLuaObject}.
*
* @see MethodResult#of(Object)
*/
@FunctionalInterface
public interface ILuaFunction
{
/**
* Call this function with a series of arguments. Note, this will <em>always</em> be called on the computer thread,
* and so its implementation must be thread-safe.
*
* @param arguments The arguments for this function
* @return The result of calling this function.
* @throws LuaException Upon Lua errors.
*/
@Nonnull
MethodResult call( @Nonnull IArguments arguments ) throws LuaException;
}

View File

@@ -1,55 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An interface for representing custom objects returned by {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}
* calls.
*
* Return objects implementing this interface to expose objects with methods to lua.
*/
public interface ILuaObject
{
/**
* Get the names of the methods that this object implements. This works the same as {@link IPeripheral#getMethodNames()}.
* See that method for detailed documentation.
*
* @return The method names this object provides.
* @see IPeripheral#getMethodNames()
*/
@Nonnull
String[] getMethodNames();
/**
* Called when a user calls one of the methods that this object implements. This works the same as
* {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}}. See that method for detailed
* documentation.
*
* @param context The context of the currently running lua thread. This can be used to wait for events
* or otherwise yield.
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
* wishes to call. The integer indicates the index into the getMethodNames() table
* that corresponds to the string passed into peripheral.call()
* @param arguments The arguments for this method. See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}
* the possible values and conversion rules.
* @return An array of objects, representing the values you wish to return to the Lua program.
* See {@link IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])} for the valid values and
* conversion rules.
* @throws LuaException If the task could not be queued, or if the task threw an exception.
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.w
* @see IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])
*/
@Nullable
Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
}

View File

@@ -8,11 +8,10 @@ package dan200.computercraft.api.lua;
import javax.annotation.Nullable;
/**
* A task which can be executed via {@link ILuaContext#executeMainThreadTask(ILuaTask)} or
* {@link ILuaContext#issueMainThreadTask(ILuaTask)}. This will be run on the main thread, at the beginning of the
* A task which can be executed via {@link ILuaContext#issueMainThreadTask(ILuaTask)} This will be run on the main
* thread, at the beginning of the
* next tick.
*
* @see ILuaContext#executeMainThreadTask(ILuaTask)
* @see ILuaContext#issueMainThreadTask(ILuaTask)
*/
@FunctionalInterface
@@ -21,8 +20,7 @@ public interface ILuaTask
/**
* Execute this task.
*
* @return The arguments to add to the {@code task_completed} event. These will be returned by
* {@link ILuaContext#executeMainThreadTask(ILuaTask)}.
* @return The arguments to add to the {@code task_completed} event.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.

View File

@@ -13,24 +13,33 @@ import javax.annotation.Nullable;
public class LuaException extends Exception
{
private static final long serialVersionUID = -6136063076818512651L;
private final boolean hasLevel;
private final int level;
public LuaException()
{
this( "error", 1 );
}
public LuaException( @Nullable String message )
{
this( message, 1 );
super( message );
this.hasLevel = false;
this.level = 1;
}
public LuaException( @Nullable String message, int level )
{
super( message );
this.hasLevel = true;
this.level = level;
}
/**
* Whether a level was explicitly specified when constructing. This is used to determine
*
* @return Whether this has an explicit level.
*/
public boolean hasLevel()
{
return hasLevel;
}
/**
* The level this error is raised at. Level 1 is the function's caller, level 2 is that function's caller, and so
* on.

View File

@@ -0,0 +1,58 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import java.lang.annotation.*;
import java.util.Map;
import java.util.Optional;
/**
* Used to mark a Java function which is callable from Lua.
*
* Methods annotated with {@link LuaFunction} must be public final instance methods. They can have any number of
* parameters, but they must be of the following types:
*
* <ul>
* <li>{@link ILuaContext} (and {@link IComputerAccess} if on a {@link IPeripheral})</li>
* <li>{@link IArguments}: The arguments supplied to this function.</li>
* <li>
* Alternatively, one may specify the desired arguments as normal parameters and the argument parsing code will
* be generated automatically.
*
* Each parameter must be one of the given types supported by {@link IArguments} (for instance, {@link int} or
* {@link Map}). Optional values are supported by accepting a parameter of type {@link Optional}.
* </li>
* </ul>
*
* This function may return {@link MethodResult}. However, if you simply return a value (rather than having to yield),
* you may return {@code void}, a single value (either an object or a primitive like {@code int}) or array of objects.
* These will be treated the same as {@link MethodResult#of()}, {@link MethodResult#of(Object)} and
* {@link MethodResult#of(Object...)}.
*/
@Documented
@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.METHOD )
public @interface LuaFunction
{
/**
* Explicitly specify the method names of this function. If not given, it uses the name of the annotated method.
*
* @return This function's name(s).
*/
String[] value() default {};
/**
* Run this function on the main server thread. This should be specified for any method which interacts with
* Minecraft in a thread-unsafe manner.
*
* @return Whether this functi
* @see ILuaContext#issueMainThreadTask(ILuaTask)
*/
boolean mainThread() default false;
}

View File

@@ -0,0 +1,152 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Map;
/**
* Various utility functions for operating with Lua values.
*
* @see IArguments
*/
public final class LuaValues
{
private LuaValues()
{
}
/**
* Encode a Lua string into a read-only {@link ByteBuffer}.
*
* @param string The string to encode.
* @return The encoded string.
*/
@Nonnull
public static ByteBuffer encode( @Nonnull String string )
{
byte[] chars = new byte[string.length()];
for( int i = 0; i < chars.length; i++ )
{
char c = string.charAt( i );
chars[i] = c < 256 ? (byte) c : 63;
}
return ByteBuffer.wrap( chars ).asReadOnlyBuffer();
}
/**
* Returns a more detailed representation of this number's type. If this is finite, it will just return "number",
* otherwise it returns whether it is infinite or NaN.
*
* @param value The value to extract the type for.
* @return This value's numeric type.
*/
@Nonnull
public static String getNumericType( double value )
{
if( Double.isNaN( value ) ) return "nan";
if( value == Double.POSITIVE_INFINITY ) return "inf";
if( value == Double.NEGATIVE_INFINITY ) return "-inf";
return "number";
}
/**
* Get a string representation of the given value's type.
*
* @param value The value whose type we are trying to compute.
* @return A string representation of the given value's type, in a similar format to that provided by Lua's
* {@code type} function.
*/
@Nonnull
public static String getType( @Nullable Object value )
{
if( value == null ) return "nil";
if( value instanceof String ) return "string";
if( value instanceof Boolean ) return "boolean";
if( value instanceof Number ) return "number";
if( value instanceof Map ) return "table";
return "userdata";
}
/**
* Construct a "bad argument" exception, from an expected type and the actual value provided.
*
* @param index The argument number, starting from 0.
* @param expected The expected type for this argument.
* @param actual The actual value provided for this argument.
* @return The constructed exception, which should be thrown immediately.
*/
@Nonnull
public static LuaException badArgumentOf( int index, @Nonnull String expected, @Nullable Object actual )
{
return badArgument( index, expected, getType( actual ) );
}
/**
* Construct a "bad argument" exception, from an expected and actual type.
*
* @param index The argument number, starting from 0.
* @param expected The expected type for this argument.
* @param actual The provided type for this argument.
* @return The constructed exception, which should be thrown immediately.
*/
@Nonnull
public static LuaException badArgument( int index, @Nonnull String expected, @Nonnull String actual )
{
return new LuaException( "bad argument #" + (index + 1) + " (" + expected + " expected, got " + actual + ")" );
}
/**
* Ensure a numeric argument is finite (i.e. not infinite or {@link Double#NaN}.
*
* @param index The argument index to check.
* @param value The value to check.
* @return The input {@code value}.
* @throws LuaException If this is not a finite number.
*/
public static Number checkFiniteNum( int index, Number value ) throws LuaException
{
checkFinite( index, value.doubleValue() );
return value;
}
/**
* Ensure a numeric argument is finite (i.e. not infinite or {@link Double#NaN}.
*
* @param index The argument index to check.
* @param value The value to check.
* @return The input {@code value}.
* @throws LuaException If this is not a finite number.
*/
public static double checkFinite( int index, double value ) throws LuaException
{
if( !Double.isFinite( value ) ) throw badArgument( index, "number", getNumericType( value ) );
return value;
}
/**
* Ensure a string is a valid enum value.
*
* @param index The argument index to check.
* @param klass The class of the enum instance.
* @param value The value to extract.
* @param <T> The type of enum we are extracting.
* @return The parsed enum value.
* @throws LuaException If this is not a known enum value.
*/
public static <T extends Enum<T>> T checkEnum( int index, Class<T> klass, String value ) throws LuaException
{
for( T possibility : klass.getEnumConstants() )
{
if( possibility.name().equalsIgnoreCase( value ) ) return possibility;
}
throw new LuaException( "bad argument #" + (index + 1) + " (unknown option " + value + ")" );
}
}

View File

@@ -0,0 +1,170 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import dan200.computercraft.api.peripheral.IComputerAccess;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
/**
* The result of invoking a Lua method.
*
* Method results either return a value immediately ({@link #of(Object...)} or yield control to the parent coroutine.
* When the current coroutine is resumed, we invoke the provided {@link ILuaCallback#resume(Object[])} callback.
*/
public final class MethodResult
{
private static final MethodResult empty = new MethodResult( null, null );
private final Object[] result;
private final ILuaCallback callback;
private final int adjust;
private MethodResult( Object[] arguments, ILuaCallback callback )
{
this.result = arguments;
this.callback = callback;
this.adjust = 0;
}
private MethodResult( Object[] arguments, ILuaCallback callback, int adjust )
{
this.result = arguments;
this.callback = callback;
this.adjust = adjust;
}
/**
* Return no values immediately.
*
* @return A method result which returns immediately with no values.
*/
@Nonnull
public static MethodResult of()
{
return empty;
}
/**
* Return a single value immediately.
*
* Integers, doubles, floats, strings, booleans, {@link Map}, {@link Collection}s, arrays and {@code null} will be
* converted to their corresponding Lua type. {@code byte[]} and {@link ByteBuffer} will be treated as binary
* strings. {@link ILuaFunction} will be treated as a function.
*
* In order to provide a custom object with methods, one may return a {@link IDynamicLuaObject}, or an arbitrary
* class with {@link LuaFunction} annotations. Anything else will be converted to {@code nil}.
*
* @param value The value to return to the calling Lua function.
* @return A method result which returns immediately with the given value.
*/
@Nonnull
public static MethodResult of( @Nullable Object value )
{
return new MethodResult( new Object[] { value }, null );
}
/**
* Return any number of values immediately.
*
* @param values The values to return. See {@link #of(Object)} for acceptable values.
* @return A method result which returns immediately with the given values.
*/
@Nonnull
public static MethodResult of( @Nullable Object... values )
{
return values == null || values.length == 0 ? empty : new MethodResult( values, null );
}
/**
* Wait for an event to occur on the computer, suspending the thread until it arises. This method is exactly
* equivalent to {@code os.pullEvent()} in lua.
*
* @param filter A specific event to wait for, or null to wait for any event.
* @param callback The callback to resume with the name of the event that occurred, and any event parameters.
* @return The method result which represents this yield.
* @see IComputerAccess#queueEvent(String, Object[])
*/
@Nonnull
public static MethodResult pullEvent( @Nullable String filter, @Nonnull ILuaCallback callback )
{
Objects.requireNonNull( callback, "callback cannot be null" );
return new MethodResult( new Object[] { filter }, results -> {
if( results.length >= 1 && results[0].equals( "terminate" ) ) throw new LuaException( "Terminated", 0 );
return callback.resume( results );
} );
}
/**
* The same as {@link #pullEvent(String, ILuaCallback)}, except "terminated" events are ignored. Only use this if
* you want to prevent program termination, which is not recommended. This method is exactly equivalent to
* {@code os.pullEventRaw()} in Lua.
*
* @param filter A specific event to wait for, or null to wait for any event.
* @param callback The callback to resume with the name of the event that occurred, and any event parameters.
* @return The method result which represents this yield.
* @see #pullEvent(String, ILuaCallback)
*/
@Nonnull
public static MethodResult pullEventRaw( @Nullable String filter, @Nonnull ILuaCallback callback )
{
Objects.requireNonNull( callback, "callback cannot be null" );
return new MethodResult( new Object[] { filter }, callback );
}
/**
* Yield the current coroutine with some arguments until it is resumed. This method is exactly equivalent to
* {@code coroutine.yield()} in lua. Use {@code pullEvent()} if you wish to wait for events.
*
* @param arguments An object array containing the arguments to pass to coroutine.yield()
* @param callback The callback to resume with an array containing the return values from coroutine.yield()
* @return The method result which represents this yield.
* @see #pullEvent(String, ILuaCallback)
*/
@Nonnull
public static MethodResult yield( @Nullable Object[] arguments, @Nonnull ILuaCallback callback )
{
Objects.requireNonNull( callback, "callback cannot be null" );
return new MethodResult( arguments, callback );
}
@Nullable
public Object[] getResult()
{
return result;
}
@Nullable
public ILuaCallback getCallback()
{
return callback;
}
public int getErrorAdjust()
{
return adjust;
}
/**
* Increase the Lua error by a specific amount. One should never need to use this function - it largely exists for
* some CC internal code.
*
* @param adjust The amount to increase the level by.
* @return The new {@link MethodResult} with an adjusted error. This has no effect on immediate results.
*/
@Nonnull
public MethodResult adjustError( int adjust )
{
if( adjust < 0 ) throw new IllegalArgumentException( "cannot adjust by a negative amount" );
if( adjust == 0 || callback == null ) return this;
return new MethodResult( result, callback, this.adjust + adjust );
}
}

View File

@@ -0,0 +1,66 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.lua;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* An implementation of {@link IArguments} which wraps an array of {@link Object}.
*/
public final class ObjectArguments implements IArguments
{
private static final IArguments EMPTY = new ObjectArguments();
private final List<Object> args;
@Deprecated
@SuppressWarnings( "unused" )
public ObjectArguments( IArguments arguments )
{
throw new IllegalStateException();
}
public ObjectArguments( Object... args )
{
this.args = Arrays.asList( args );
}
public ObjectArguments( List<Object> args )
{
this.args = Objects.requireNonNull( args );
}
@Override
public int count()
{
return args.size();
}
@Override
public IArguments drop( int count )
{
if( count < 0 ) throw new IllegalStateException( "count cannot be negative" );
if( count == 0 ) return this;
if( count >= args.size() ) return EMPTY;
return new ObjectArguments( args.subList( count, args.size() ) );
}
@Nullable
@Override
public Object get( int index )
{
return index >= args.size() ? null : args.get( index );
}
@Override
public Object[] getAll()
{
return args.toArray();
}
}

View File

@@ -79,7 +79,7 @@ public interface IMedia
* @see IMount
* @see dan200.computercraft.api.filesystem.IWritableMount
* @see dan200.computercraft.api.ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see dan200.computercraft.api.ComputerCraftAPI#createResourceMount(Class, String, String)
* @see dan200.computercraft.api.ComputerCraftAPI#createResourceMount(String, String)
*/
@Nullable
default IMount createDataMount( @Nonnull ItemStack stack, @Nonnull World world )

View File

@@ -8,13 +8,14 @@ package dan200.computercraft.api.peripheral;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaCallback;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.MethodResult;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Map;
/**
@@ -31,9 +32,9 @@ public interface IComputerAccess
* @param mount The mount object to mount on the computer.
* @return The location on the computer's file system where you the mount mounted, or {@code null} if there was already a
* file in the desired location. Store this value if you wish to unmount the mount later.
* @throws RuntimeException If the peripheral has been detached.
* @throws NotAttachedException If the peripheral has been detached.
* @see ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see ComputerCraftAPI#createResourceMount(Class, String, String)
* @see ComputerCraftAPI#createResourceMount(String, String)
* @see #mount(String, IMount, String)
* @see #mountWritable(String, IWritableMount)
* @see #unmount(String)
@@ -53,9 +54,9 @@ public interface IComputerAccess
* @param driveName A custom name to give for this mount location, as returned by {@code fs.getDrive()}.
* @return The location on the computer's file system where you the mount mounted, or {@code null} if there was already a
* file in the desired location. Store this value if you wish to unmount the mount later.
* @throws RuntimeException If the peripheral has been detached.
* @throws NotAttachedException If the peripheral has been detached.
* @see ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see ComputerCraftAPI#createResourceMount(Class, String, String)
* @see ComputerCraftAPI#createResourceMount(String, String)
* @see #mount(String, IMount)
* @see #mountWritable(String, IWritableMount)
* @see #unmount(String)
@@ -71,9 +72,9 @@ public interface IComputerAccess
* @param mount The mount object to mount on the computer.
* @return The location on the computer's file system where you the mount mounted, or null if there was already a
* file in the desired location. Store this value if you wish to unmount the mount later.
* @throws RuntimeException If the peripheral has been detached.
* @throws NotAttachedException If the peripheral has been detached.
* @see ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see ComputerCraftAPI#createResourceMount(Class, String, String)
* @see ComputerCraftAPI#createResourceMount(String, String)
* @see #mount(String, IMount)
* @see #unmount(String)
* @see IMount
@@ -92,9 +93,9 @@ public interface IComputerAccess
* @param driveName A custom name to give for this mount location, as returned by {@code fs.getDrive()}.
* @return The location on the computer's file system where you the mount mounted, or null if there was already a
* file in the desired location. Store this value if you wish to unmount the mount later.
* @throws RuntimeException If the peripheral has been detached.
* @throws NotAttachedException If the peripheral has been detached.
* @see ComputerCraftAPI#createSaveDirMount(World, String, long)
* @see ComputerCraftAPI#createResourceMount(Class, String, String)
* @see ComputerCraftAPI#createResourceMount(String, String)
* @see #mount(String, IMount)
* @see #unmount(String)
* @see IMount
@@ -114,8 +115,8 @@ public interface IComputerAccess
* @param location The desired location in the computers file system of the directory to unmount.
* This must be the location of a directory previously mounted by {@link #mount(String, IMount)} or
* {@link #mountWritable(String, IWritableMount)}, as indicated by their return value.
* @throws RuntimeException If the peripheral has been detached.
* @throws RuntimeException If the mount does not exist, or was mounted by another peripheral.
* @throws NotAttachedException If the peripheral has been detached.
* @throws IllegalStateException If the mount does not exist, or was mounted by another peripheral.
* @see #mount(String, IMount)
* @see #mountWritable(String, IWritableMount)
*/
@@ -146,10 +147,10 @@ public interface IComputerAccess
* to lua data types in the same fashion as the return values of IPeripheral.callMethod().
*
* You may supply {@code null} to indicate that no arguments are to be supplied.
* @throws RuntimeException If the peripheral has been detached.
* @see IPeripheral#callMethod
* @throws NotAttachedException If the peripheral has been detached.
* @see MethodResult#pullEvent(String, ILuaCallback)
*/
void queueEvent( @Nonnull String event, @Nullable Object[] arguments );
void queueEvent( @Nonnull String event, @Nullable Object... arguments );
/**
* Get a string, unique to the computer, by which the computer refers to this peripheral.
@@ -159,7 +160,7 @@ public interface IComputerAccess
* which peripheral the event came.
*
* @return A string unique to the computer, but not globally.
* @throws RuntimeException If the peripheral has been detached.
* @throws NotAttachedException If the peripheral has been detached.
*/
@Nonnull
String getAttachmentName();
@@ -170,14 +171,12 @@ public interface IComputerAccess
* This may include other peripherals on the wired network or peripherals on other sides of the computer.
*
* @return All reachable peripherals
* @throws NotAttachedException If the peripheral has been detached.
* @see #getAttachmentName()
* @see #getAvailablePeripheral(String)
*/
@Nonnull
default Map<String, IPeripheral> getAvailablePeripherals()
{
return Collections.emptyMap();
}
Map<String, IPeripheral> getAvailablePeripherals();
/**
* Get a reachable peripheral with the given attachment name. This is a equivalent to
@@ -188,10 +187,7 @@ public interface IComputerAccess
* @see #getAvailablePeripherals()
*/
@Nullable
default IPeripheral getAvailablePeripheral( @Nonnull String name )
{
return null;
}
IPeripheral getAvailablePeripheral( @Nonnull String name );
/**
* Get a {@link IWorkMonitor} for tasks your peripheral might execute on the main (server) thread.
@@ -205,10 +201,8 @@ public interface IComputerAccess
* thread.
*
* @return The work monitor for the main thread, or {@code null} if this computer does not have one.
* @throws NotAttachedException If the peripheral has been detached.
*/
@Nullable
default IWorkMonitor getMainThreadMonitor()
{
return null;
}
@Nonnull
IWorkMonitor getMainThreadMonitor();
}

View File

@@ -0,0 +1,53 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.peripheral;
import dan200.computercraft.api.lua.*;
import javax.annotation.Nonnull;
/**
* A peripheral whose methods are not known at runtime.
*
* This behaves similarly to {@link IDynamicLuaObject}, though also accepting the current {@link IComputerAccess}.
* Generally one may use {@link LuaFunction} instead of implementing this interface.
*/
public interface IDynamicPeripheral extends IPeripheral
{
/**
* Should return an array of strings that identify the methods that this peripheral exposes to Lua. This will be
* called once before each attachment, and should not change when called multiple times.
*
* @return An array of strings representing method names.
* @see #callMethod
*/
@Nonnull
String[] getMethodNames();
/**
* This is called when a lua program on an attached computer calls {@code peripheral.call()} with
* one of the methods exposed by {@link #getMethodNames()}.
*
* Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe when interacting
* with Minecraft objects.
*
* @param computer The interface to the computer that is making the call. Remember that multiple
* computers can be attached to a peripheral at once.
* @param context The context of the currently running lua thread. This can be used to wait for events
* or otherwise yield.
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
* wishes to call. The integer indicates the index into the getMethodNames() table
* that corresponds to the string passed into peripheral.call()
* @param arguments The arguments for this method.
* @return A {@link MethodResult} containing the values to return or the action to perform.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
* @see #getMethodNames()
*/
@Nonnull
MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments ) throws LuaException;
}

View File

@@ -5,15 +5,21 @@
*/
package dan200.computercraft.api.peripheral;
import dan200.computercraft.api.lua.ArgumentHelper;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import net.minecraftforge.common.capabilities.Capability;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* The interface that defines a peripheral. See {@link IPeripheralProvider} for how to associate blocks with peripherals.
* The interface that defines a peripheral.
*
* In order to expose a peripheral for your block or tile entity, you may either attach a {@link Capability}, or
* register a {@link IPeripheralProvider}. It is <em>not</em> recommended to implement {@link IPeripheral} directly on
* the tile.
*
* Peripherals should provide a series of methods to the user, either using {@link LuaFunction} or by implementing
* {@link IDynamicPeripheral}.
*/
public interface IPeripheral
{
@@ -26,57 +32,6 @@ public interface IPeripheral
@Nonnull
String getType();
/**
* Should return an array of strings that identify the methods that this
* peripheral exposes to Lua. This will be called once before each attachment,
* and should not change when called multiple times.
*
* @return An array of strings representing method names.
* @see #callMethod
*/
@Nonnull
String[] getMethodNames();
/**
* This is called when a lua program on an attached computer calls {@code peripheral.call()} with
* one of the methods exposed by {@link #getMethodNames()}.
*
* Be aware that this will be called from the ComputerCraft Lua thread, and must be thread-safe when interacting
* with Minecraft objects.
*
* @param computer The interface to the computer that is making the call. Remember that multiple
* computers can be attached to a peripheral at once.
* @param context The context of the currently running lua thread. This can be used to wait for events
* or otherwise yield.
* @param method An integer identifying which of the methods from getMethodNames() the computercraft
* wishes to call. The integer indicates the index into the getMethodNames() table
* that corresponds to the string passed into peripheral.call()
* @param arguments An array of objects, representing the arguments passed into {@code peripheral.call()}.<br>
* Lua values of type "string" will be represented by Object type String.<br>
* Lua values of type "number" will be represented by Object type Double.<br>
* Lua values of type "boolean" will be represented by Object type Boolean.<br>
* Lua values of type "table" will be represented by Object type Map.<br>
* Lua values of any other type will be represented by a null object.<br>
* This array will be empty if no arguments are passed.
*
* It is recommended you use {@link ArgumentHelper} in order to validate and process arguments.
* @return An array of objects, representing values you wish to return to the lua program. Integers, Doubles, Floats,
* Strings, Booleans, Maps, ILuaObject and null be converted to their corresponding lua type. All other types will
* be converted to nil.
*
* You may return null to indicate no values should be returned.
* @throws LuaException If you throw any exception from this function, a lua error will be raised with the
* same message as your exception. Use this to throw appropriate errors if the wrong
* arguments are supplied to your method.
* @throws InterruptedException If the user shuts down or reboots the computer the coroutine is suspended,
* InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @see #getMethodNames
* @see ArgumentHelper
*/
@Nullable
Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException;
/**
* Is called when when a computer is attaching to the peripheral.
*
@@ -126,10 +81,10 @@ public interface IPeripheral
*
* @return The object this peripheral targets
*/
@Nonnull
@Nullable
default Object getTarget()
{
return this;
return null;
}
/**

View File

@@ -9,14 +9,15 @@ import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraftforge.common.util.LazyOptional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This interface is used to create peripheral implementations for blocks.
*
* If you have a {@link TileEntity} which acts as a peripheral, you may alternatively implement {@link IPeripheralTile}.
* If you have a {@link TileEntity} which acts as a peripheral, you may alternatively expose the {@link IPeripheral}
* capability.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)
*/
@@ -29,9 +30,9 @@ public interface IPeripheralProvider
* @param world The world the block is in.
* @param pos The position the block is at.
* @param side The side to get the peripheral from.
* @return A peripheral, or {@code null} if there is not a peripheral here you'd like to handle.
* @return A peripheral, or {@link LazyOptional#empty()} if there is not a peripheral here you'd like to handle.
* @see dan200.computercraft.api.ComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)
*/
@Nullable
IPeripheral getPeripheral( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side );
@Nonnull
LazyOptional<IPeripheral> getPeripheral( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side );
}

View File

@@ -1,32 +0,0 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.peripheral;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A {@link net.minecraft.tileentity.TileEntity} which may act as a peripheral.
*
* If you need more complex capabilities (such as handling TEs not belonging to your mod), you should use
* {@link IPeripheralProvider}.
*/
public interface IPeripheralTile
{
/**
* Get the peripheral on the given {@code side}.
*
* @param side The side to get the peripheral from.
* @return A peripheral, or {@code null} if there is not a peripheral here.
* @see IPeripheralProvider#getPeripheral(World, BlockPos, Direction)
*/
@Nullable
IPeripheral getPeripheral( @Nonnull Direction side );
}

View File

@@ -0,0 +1,25 @@
/*
* This file is part of the public ComputerCraft API - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. This API may be redistributed unmodified and in full only.
* For help using the API, and posting your mods, visit the forums at computercraft.info.
*/
package dan200.computercraft.api.peripheral;
/**
* Thrown when performing operations on {@link IComputerAccess} when the current peripheral is no longer attached to
* the computer.
*/
public class NotAttachedException extends IllegalStateException
{
private static final long serialVersionUID = 1221244785535553536L;
public NotAttachedException()
{
super( "You are not attached to this computer" );
}
public NotAttachedException( String s )
{
super( s );
}
}

View File

@@ -3,10 +3,8 @@
* Copyright Daniel Ratcliffe, 2011-2020. 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;
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeType;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IItemProvider;
import net.minecraft.util.ResourceLocation;

View File

@@ -6,8 +6,8 @@
package dan200.computercraft.api.turtle;
import com.mojang.authlib.GameProfile;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.ILuaCallback;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.inventory.IInventory;
import net.minecraft.nbt.CompoundNBT;
@@ -228,21 +228,15 @@ public interface ITurtleAccess
* be supplied as a parameter to a "turtle_response" event issued to the turtle after the command has completed. Look at the
* lua source code for "rom/apis/turtle" for how to build a lua wrapper around this functionality.
*
* @param context The Lua context to pull events from.
* @param command An object which will execute the custom command when its point in the queue is reached
* @return The objects the command returned when executed. you should probably return these to the player
* unchanged if called from a peripheral method.
* @throws UnsupportedOperationException When attempting to execute a command on the client side.
* @throws LuaException If the user presses CTRL+T to terminate the current program while {@code executeCommand()} is
* waiting for an event, a "Terminated" exception will be thrown here.
* @throws InterruptedException If the user shuts down or reboots the computer while pullEvent() is waiting for an
* event, InterruptedException will be thrown. This exception must not be caught or
* intercepted, or the computer will leak memory and end up in a broken state.
* @see ITurtleCommand
* @see ILuaContext#pullEvent(String)
* @see MethodResult#pullEvent(String, ILuaCallback)
*/
@Nonnull
Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException;
MethodResult executeCommand( @Nonnull ITurtleCommand command );
/**
* Start playing a specific animation. This will prevent other turtle commands from executing until

View File

@@ -5,14 +5,12 @@
*/
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.lua.ILuaContext;
import javax.annotation.Nonnull;
/**
* An interface for objects executing custom turtle commands, used with {@link ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)}.
* An interface for objects executing custom turtle commands, used with {@link ITurtleAccess#executeCommand(ITurtleCommand)}.
*
* @see ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)
* @see ITurtleAccess#executeCommand(ITurtleCommand)
*/
@FunctionalInterface
public interface ITurtleCommand
@@ -25,7 +23,7 @@ public interface ITurtleCommand
*
* @param turtle Access to the turtle for whom the command was issued.
* @return A result, indicating whether this action succeeded or not.
* @see ITurtleAccess#executeCommand(ILuaContext, ITurtleCommand)
* @see ITurtleAccess#executeCommand(ITurtleCommand)
* @see TurtleCommandResult#success()
* @see TurtleCommandResult#failure(String)
* @see TurtleCommandResult

View File

@@ -6,10 +6,10 @@
package dan200.computercraft.api.turtle;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.TransformedModel;
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.model.IBakedModel;
import net.minecraft.client.renderer.model.ModelResourceLocation;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
@@ -18,11 +18,9 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.world.BlockEvent;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
/**
* The primary interface for defining an update for Turtles. A turtle update
@@ -126,12 +124,11 @@ public interface ITurtleUpgrade
*
* @param turtle Access to the turtle that the upgrade resides on. This will be null when getting item models!
* @param side Which side of the turtle (left or right) the upgrade resides on.
* @return The model that you wish to be used to render your upgrade, and a transformation to apply to it. Returning
* a transformation of {@code null} has the same effect as the identify matrix.
* @return The model that you wish to be used to render your upgrade.
*/
@Nonnull
@OnlyIn( Dist.CLIENT )
Pair<IBakedModel, Matrix4f> getModel( @Nullable ITurtleAccess turtle, @Nonnull TurtleSide side );
TransformedModel getModel( @Nullable ITurtleAccess turtle, @Nonnull TurtleSide side );
/**
* Called once per tick for each turtle which has the upgrade equipped.

View File

@@ -17,70 +17,70 @@ public enum TurtleAnimation
/**
* An animation which does nothing. This takes no time to complete.
*
* @see #Wait
* @see #ShortWait
* @see #WAIT
* @see #SHORT_WAIT
*/
None,
NONE,
/**
* Make the turtle move forward. Note that the animation starts from the block <em>behind</em> it, and
* moves into this one.
*/
MoveForward,
MOVE_FORWARD,
/**
* Make the turtle move backwards. Note that the animation starts from the block <em>in front</em> it, and
* moves into this one.
*/
MoveBack,
MOVE_BACK,
/**
* Make the turtle move backwards. Note that the animation starts from the block <em>above</em> it, and
* moves into this one.
*/
MoveUp,
MOVE_UP,
/**
* Make the turtle move backwards. Note that the animation starts from the block <em>below</em> it, and
* moves into this one.
*/
MoveDown,
MOVE_DOWN,
/**
* Turn the turtle to the left. Note that the animation starts with the turtle facing <em>right</em>, and
* the turtle turns to face in the current direction.
*/
TurnLeft,
TURN_LEFT,
/**
* Turn the turtle to the left. Note that the animation starts with the turtle facing <em>right</em>, and
* the turtle turns to face in the current direction.
*/
TurnRight,
TURN_RIGHT,
/**
* Swing the tool on the left.
*/
SwingLeftTool,
SWING_LEFT_TOOL,
/**
* Swing the tool on the right.
*/
SwingRightTool,
SWING_RIGHT_TOOL,
/**
* Wait until the animation has finished, performing no movement.
*
* @see #ShortWait
* @see #None
* @see #SHORT_WAIT
* @see #NONE
*/
Wait,
WAIT,
/**
* Wait until the animation has finished, performing no movement. This takes 4 ticks to complete.
*
* @see #Wait
* @see #None
* @see #WAIT
* @see #NONE
*/
ShortWait,
SHORT_WAIT,
}

View File

@@ -13,10 +13,10 @@ public enum TurtleSide
/**
* The turtle's left side (where the pickaxe usually is on a Wireless Mining Turtle).
*/
Left,
LEFT,
/**
* The turtle's right side (where the modem usually is on a Wireless Mining Turtle).
*/
Right,
RIGHT,
}

View File

@@ -16,28 +16,28 @@ public enum TurtleUpgradeType
* A tool is rendered as an item on the side of the turtle, and responds to the {@code turtle.dig()}
* and {@code turtle.attack()} methods (Such as pickaxe or sword on Mining and Melee turtles).
*/
Tool,
TOOL,
/**
* A peripheral adds a special peripheral which is attached to the side of the turtle,
* and can be interacted with the {@code peripheral} API (Such as the modem on Wireless Turtles).
*/
Peripheral,
PERIPHERAL,
/**
* An upgrade which provides both a tool and a peripheral. This can be used when you wish
* your upgrade to also provide methods. For example, a pickaxe could provide methods
* determining whether it can break the given block or not.
*/
Both;
BOTH;
public boolean isTool()
{
return this == Tool || this == Both;
return this == TOOL || this == BOTH;
}
public boolean isPeripheral()
{
return this == Peripheral || this == Both;
return this == PERIPHERAL || this == BOTH;
}
}

View File

@@ -19,10 +19,10 @@ public enum TurtleVerb
/**
* The turtle called {@code turtle.dig()}, {@code turtle.digUp()} or {@code turtle.digDown()}.
*/
Dig,
DIG,
/**
* The turtle called {@code turtle.attack()}, {@code turtle.attackUp()} or {@code turtle.attackDown()}.
*/
Attack,
ATTACK,
}

View File

@@ -5,8 +5,7 @@
*/
package dan200.computercraft.api.turtle.event;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
@@ -223,7 +222,7 @@ public abstract class TurtleBlockEvent extends TurtlePlayerEvent
* 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 convertible to Lua (see
* {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
* {@link MethodResult#of(Object)}).
*/
public void addData( @Nonnull Map<String, ?> newData )
{

View File

@@ -5,8 +5,7 @@
*/
package dan200.computercraft.api.turtle.event;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.turtle.ITurtleAccess;
import net.minecraft.item.ItemStack;
@@ -63,7 +62,7 @@ public class TurtleInspectItemEvent extends TurtleActionEvent
* 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 convertible to Lua (see
* {@link dan200.computercraft.api.peripheral.IPeripheral#callMethod(IComputerAccess, ILuaContext, int, Object[])}).
* {@link MethodResult#of(Object)}).
*/
public void addData( @Nonnull Map<String, ?> newData )
{

View File

@@ -9,22 +9,22 @@ import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.render.TurtleModelLoader;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.media.items.ItemDisk;
import dan200.computercraft.shared.media.items.ItemTreasureDisk;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.util.Colour;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.IUnbakedModel;
import net.minecraft.client.renderer.model.ModelResourceLocation;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.inventory.container.PlayerContainer;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ColorHandlerEvent;
import net.minecraftforge.client.event.ModelBakeEvent;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.client.model.BasicState;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.client.model.ModelLoaderRegistry;
import net.minecraftforge.client.model.SimpleModelTransform;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@@ -71,13 +71,13 @@ public final class ClientRegistry
@SubscribeEvent
public static void registerModels( ModelRegistryEvent event )
{
ModelLoaderRegistry.registerLoader( TurtleModelLoader.INSTANCE );
ModelLoaderRegistry.registerLoader( new ResourceLocation( ComputerCraft.MOD_ID, "turtle" ), TurtleModelLoader.INSTANCE );
}
@SubscribeEvent
public static void onTextureStitchEvent( TextureStitchEvent.Pre event )
{
if( event.getMap() != Minecraft.getInstance().getTextureMap() ) return;
if( !event.getMap().getTextureLocation().equals( PlayerContainer.LOCATION_BLOCKS_TEXTURE ) ) return;
for( String extra : EXTRA_TEXTURES )
{
@@ -92,29 +92,18 @@ public final class ClientRegistry
ModelLoader loader = event.getModelLoader();
Map<ResourceLocation, IBakedModel> registry = event.getModelRegistry();
for( String model : EXTRA_MODELS )
for( String modelName : EXTRA_MODELS )
{
IBakedModel bakedModel = bake( loader, loader.getUnbakedModel( new ResourceLocation( ComputerCraft.MOD_ID, "item/" + model ) ) );
ResourceLocation location = new ResourceLocation( ComputerCraft.MOD_ID, "item/" + modelName );
IUnbakedModel model = loader.getUnbakedModel( location );
model.getTextures( loader::getUnbakedModel, new HashSet<>() );
if( bakedModel != null )
IBakedModel baked = model.bakeModel( loader, ModelLoader.defaultTextureGetter(), SimpleModelTransform.IDENTITY, location );
if( baked != null )
{
registry.put(
new ModelResourceLocation( new ResourceLocation( ComputerCraft.MOD_ID, model ), "inventory" ),
bakedModel
);
registry.put( new ModelResourceLocation( new ResourceLocation( ComputerCraft.MOD_ID, modelName ), "inventory" ), baked );
}
}
// And load the custom turtle models in too.
registry.put(
new ModelResourceLocation( new ResourceLocation( ComputerCraft.MOD_ID, "turtle_normal" ), "inventory" ),
bake( loader, TurtleModelLoader.INSTANCE.loadModel( new ResourceLocation( ComputerCraft.MOD_ID, "item/turtle_normal" ) ) )
);
registry.put(
new ModelResourceLocation( new ResourceLocation( ComputerCraft.MOD_ID, "turtle_advanced" ), "inventory" ),
bake( loader, TurtleModelLoader.INSTANCE.loadModel( new ResourceLocation( ComputerCraft.MOD_ID, "item/turtle_advanced" ) ) )
);
}
@SubscribeEvent
@@ -131,6 +120,11 @@ public final class ClientRegistry
ComputerCraft.Items.disk
);
event.getItemColors().register(
( stack, layer ) -> layer == 1 ? ItemTreasureDisk.getColour( stack ) : 0xFFFFFF,
ComputerCraft.Items.treasureDisk
);
event.getItemColors().register( ( stack, layer ) -> {
switch( layer )
{
@@ -142,7 +136,7 @@ public final class ClientRegistry
case 2: // Light colour
{
int light = ItemPocketComputer.getLightState( stack );
return light == -1 ? Colour.Black.getHex() : light;
return light == -1 ? Colour.BLACK.getHex() : light;
}
}
}, ComputerCraft.Items.pocketComputerNormal, ComputerCraft.Items.pocketComputerAdvanced );
@@ -153,15 +147,4 @@ public final class ClientRegistry
ComputerCraft.Blocks.turtleNormal, ComputerCraft.Blocks.turtleAdvanced
);
}
private static IBakedModel bake( ModelLoader loader, IUnbakedModel model )
{
model.getTextures( loader::getUnbakedModel, new HashSet<>() );
return model.bake(
loader,
ModelLoader.defaultTextureGetter(),
new BasicState( model.getDefaultState(), false ), DefaultVertexFormats.BLOCK
);
}
}

View File

@@ -5,15 +5,15 @@
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.ResourceLocation;
@@ -24,21 +24,10 @@ import javax.annotation.Nullable;
public final class FixedWidthFontRenderer
{
private static final Matrix4f IDENTITY = TransformationMatrix.identity().getMatrix();
private static final ResourceLocation FONT = new ResourceLocation( "computercraft", "textures/gui/term_font.png" );
/**
* Like {@link DefaultVertexFormats#POSITION_TEX_COLOR}, but flipped. This is backported from 1.15, hence the
* custom format.
*/
public static final VertexFormat POSITION_COLOR_TEX = new VertexFormat();
static
{
POSITION_COLOR_TEX.addElement( DefaultVertexFormats.POSITION_3F );
POSITION_COLOR_TEX.addElement( DefaultVertexFormats.COLOR_4UB );
POSITION_COLOR_TEX.addElement( DefaultVertexFormats.TEX_2F );
}
public static final int FONT_HEIGHT = 9;
public static final int FONT_WIDTH = 6;
public static final float WIDTH = 256.0f;
@@ -46,22 +35,23 @@ public final class FixedWidthFontRenderer
public static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH;
public static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH;
public static final RenderType TYPE = Type.MAIN;
private FixedWidthFontRenderer()
{
}
private static float toGreyscale( double[] rgb )
public static float toGreyscale( double[] rgb )
{
return (float) ((rgb[0] + rgb[1] + rgb[2]) / 3);
}
private static int getColour( char c )
public static int getColour( char c, Colour def )
{
int i = "0123456789abcdef".indexOf( c );
return i < 0 ? 0 : 15 - i;
return 15 - Terminal.getColour( c, def );
}
private static void drawChar( BufferBuilder buffer, float x, float y, int index, float r, float g, float b )
private static void drawChar( Matrix4f transform, IVertexBuilder buffer, float x, float y, int index, float r, float g, float b )
{
// Short circuit to avoid the common case - the texture should be blank here after all.
if( index == '\0' || index == ' ' ) return;
@@ -72,27 +62,27 @@ public final class FixedWidthFontRenderer
int xStart = 1 + column * (FONT_WIDTH + 2);
int yStart = 1 + row * (FONT_HEIGHT + 2);
buffer.pos( x, y, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, yStart / WIDTH ).endVertex();
buffer.pos( x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
buffer.pos( x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).endVertex();
buffer.pos( x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).endVertex();
buffer.pos( x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
buffer.pos( x + FONT_WIDTH, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
buffer.pos( transform, x, y, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, yStart / WIDTH ).endVertex();
buffer.pos( transform, x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
buffer.pos( transform, x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).endVertex();
buffer.pos( transform, x + FONT_WIDTH, y, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, yStart / WIDTH ).endVertex();
buffer.pos( transform, x, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( xStart / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
buffer.pos( transform, x + FONT_WIDTH, y + FONT_HEIGHT, 0f ).color( r, g, b, 1.0f ).tex( (xStart + FONT_WIDTH) / WIDTH, (yStart + FONT_HEIGHT) / WIDTH ).endVertex();
}
private static void drawQuad( BufferBuilder buffer, float x, float y, float width, float height, float r, float g, float b )
private static void drawQuad( Matrix4f transform, IVertexBuilder buffer, float x, float y, float width, float height, float r, float g, float b )
{
buffer.pos( x, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_START ).endVertex();
buffer.pos( x, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_END ).endVertex();
buffer.pos( x + width, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_START ).endVertex();
buffer.pos( x + width, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_START ).endVertex();
buffer.pos( x, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_END ).endVertex();
buffer.pos( x + width, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_END ).endVertex();
buffer.pos( transform, x, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_START ).endVertex();
buffer.pos( transform, x, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_END ).endVertex();
buffer.pos( transform, x + width, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_START ).endVertex();
buffer.pos( transform, x + width, y, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_START ).endVertex();
buffer.pos( transform, x, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_START, BACKGROUND_END ).endVertex();
buffer.pos( transform, x + width, y + height, 0 ).color( r, g, b, 1.0f ).tex( BACKGROUND_END, BACKGROUND_END ).endVertex();
}
private static void drawQuad( BufferBuilder buffer, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex )
private static void drawQuad( Matrix4f transform, IVertexBuilder buffer, float x, float y, float width, float height, Palette palette, boolean greyscale, char colourIndex )
{
double[] colour = palette.getColour( getColour( colourIndex ) );
double[] colour = palette.getColour( getColour( colourIndex, Colour.BLACK ) );
float r, g, b;
if( greyscale )
{
@@ -105,23 +95,23 @@ public final class FixedWidthFontRenderer
b = (float) colour[2];
}
drawQuad( buffer, x, y, width, height, r, g, b );
drawQuad( transform, buffer, x, y, width, height, r, g, b );
}
private static void drawBackground(
@Nonnull BufferBuilder renderer, float x, float y,
@Nonnull Matrix4f transform, @Nonnull IVertexBuilder renderer, float x, float y,
@Nonnull TextBuffer backgroundColour, @Nonnull Palette palette, boolean greyscale,
float leftMarginSize, float rightMarginSize, float height
)
{
if( leftMarginSize > 0 )
{
drawQuad( renderer, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ) );
drawQuad( transform, renderer, x - leftMarginSize, y, leftMarginSize, height, palette, greyscale, backgroundColour.charAt( 0 ) );
}
if( rightMarginSize > 0 )
{
drawQuad( renderer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, greyscale, backgroundColour.charAt( backgroundColour.length() - 1 ) );
drawQuad( transform, renderer, x + backgroundColour.length() * FONT_WIDTH, y, rightMarginSize, height, palette, greyscale, backgroundColour.charAt( backgroundColour.length() - 1 ) );
}
// Batch together runs of identical background cells.
@@ -134,7 +124,7 @@ public final class FixedWidthFontRenderer
if( blockColour != '\0' )
{
drawQuad( renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour );
drawQuad( transform, renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (i - blockStart), height, palette, greyscale, blockColour );
}
blockColour = colourIndex;
@@ -143,24 +133,24 @@ public final class FixedWidthFontRenderer
if( blockColour != '\0' )
{
drawQuad( renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour );
drawQuad( transform, renderer, x + blockStart * FONT_WIDTH, y, FONT_WIDTH * (backgroundColour.length() - blockStart), height, palette, greyscale, blockColour );
}
}
public static void drawString(
@Nonnull BufferBuilder renderer, float x, float y,
@Nonnull Matrix4f transform, @Nonnull IVertexBuilder renderer, float x, float y,
@Nonnull TextBuffer text, @Nonnull TextBuffer textColour, @Nullable TextBuffer backgroundColour,
@Nonnull Palette palette, boolean greyscale, float leftMarginSize, float rightMarginSize
)
{
if( backgroundColour != null )
{
drawBackground( renderer, x, y, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize, FONT_HEIGHT );
drawBackground( transform, renderer, x, y, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize, FONT_HEIGHT );
}
for( int i = 0; i < text.length(); i++ )
{
double[] colour = palette.getColour( getColour( textColour.charAt( i ) ) );
double[] colour = palette.getColour( getColour( textColour.charAt( i ), Colour.BLACK ) );
float r, g, b;
if( greyscale )
{
@@ -176,7 +166,7 @@ public final class FixedWidthFontRenderer
// Draw char
int index = text.charAt( i );
if( index > 255 ) index = '?';
drawChar( renderer, x + i * FONT_WIDTH, y, index, r, g, b );
drawChar( transform, renderer, x + i * FONT_WIDTH, y, index, r, g, b );
}
}
@@ -188,15 +178,13 @@ public final class FixedWidthFontRenderer
{
bindFont();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
begin( buffer );
drawString( buffer, x, y, text, textColour, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize );
tessellator.draw();
IRenderTypeBuffer.Impl renderer = Minecraft.getInstance().getRenderTypeBuffers().getBufferSource();
drawString( IDENTITY, ((IRenderTypeBuffer) renderer).getBuffer( TYPE ), x, y, text, textColour, backgroundColour, palette, greyscale, leftMarginSize, rightMarginSize );
renderer.finish();
}
public static void drawTerminalWithoutCursor(
@Nonnull BufferBuilder buffer, float x, float y,
@Nonnull Matrix4f transform, @Nonnull IVertexBuilder buffer, float x, float y,
@Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
@@ -206,13 +194,13 @@ public final class FixedWidthFontRenderer
// Top and bottom margins
drawBackground(
buffer, x, y - topMarginSize,
transform, buffer, x, y - topMarginSize,
terminal.getBackgroundColourLine( 0 ), palette, greyscale,
leftMarginSize, rightMarginSize, topMarginSize
);
drawBackground(
buffer, x, y + height * FONT_HEIGHT,
transform, buffer, x, y + height * FONT_HEIGHT,
terminal.getBackgroundColourLine( height - 1 ), palette, greyscale,
leftMarginSize, rightMarginSize, bottomMarginSize
);
@@ -221,7 +209,7 @@ public final class FixedWidthFontRenderer
for( int i = 0; i < height; i++ )
{
drawString(
buffer, x, y + FixedWidthFontRenderer.FONT_HEIGHT * i,
transform, buffer, x, y + FixedWidthFontRenderer.FONT_HEIGHT * i,
terminal.getLine( i ), terminal.getTextColourLine( i ), terminal.getBackgroundColourLine( i ),
palette, greyscale, leftMarginSize, rightMarginSize
);
@@ -229,7 +217,7 @@ public final class FixedWidthFontRenderer
}
public static void drawCursor(
@Nonnull BufferBuilder buffer, float x, float y,
@Nonnull Matrix4f transform, @Nonnull IVertexBuilder buffer, float x, float y,
@Nonnull Terminal terminal, boolean greyscale
)
{
@@ -254,75 +242,104 @@ public final class FixedWidthFontRenderer
b = (float) colour[2];
}
drawChar( buffer, x + cursorX * FONT_WIDTH, y + cursorY * FONT_HEIGHT, '_', r, g, b );
drawChar( transform, buffer, x + cursorX * FONT_WIDTH, y + cursorY * FONT_HEIGHT, '_', r, g, b );
}
}
public static void drawTerminal(
@Nonnull BufferBuilder buffer, float x, float y,
@Nonnull Matrix4f transform, @Nonnull IVertexBuilder buffer, float x, float y,
@Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{
drawTerminalWithoutCursor( buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
drawCursor( buffer, x, y, terminal, greyscale );
drawTerminalWithoutCursor( transform, buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
drawCursor( transform, buffer, x, y, terminal, greyscale );
}
public static void drawTerminal(
@Nonnull Matrix4f transform, float x, float y, @Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{
bindFont();
IRenderTypeBuffer.Impl renderer = Minecraft.getInstance().getRenderTypeBuffers().getBufferSource();
IVertexBuilder buffer = renderer.getBuffer( TYPE );
drawTerminal( transform, buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
renderer.finish( TYPE );
}
public static void drawTerminal(
float x, float y, @Nonnull Terminal terminal, boolean greyscale,
float topMarginSize, float bottomMarginSize, float leftMarginSize, float rightMarginSize
)
{
drawTerminal( IDENTITY, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
}
public static void drawEmptyTerminal( @Nonnull Matrix4f transform, @Nonnull IRenderTypeBuffer renderer, float x, float y, float width, float height )
{
Colour colour = Colour.BLACK;
drawQuad( transform, renderer.getBuffer( TYPE ), x, y, width, height, colour.getR(), colour.getG(), colour.getB() );
}
public static void drawEmptyTerminal( @Nonnull Matrix4f transform, float x, float y, float width, float height )
{
bindFont();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
begin( buffer );
drawTerminal( buffer, x, y, terminal, greyscale, topMarginSize, bottomMarginSize, leftMarginSize, rightMarginSize );
tessellator.draw();
IRenderTypeBuffer.Impl renderer = Minecraft.getInstance().getRenderTypeBuffers().getBufferSource();
drawEmptyTerminal( transform, renderer, x, y, width, height );
renderer.finish();
}
public static void drawEmptyTerminal( float x, float y, float width, float height )
{
bindFont();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
begin( buffer );
Colour colour = Colour.Black;
drawQuad( buffer, x, y, width, height, colour.getR(), colour.getG(), colour.getB() );
tessellator.draw();
drawEmptyTerminal( IDENTITY, x, y, width, height );
}
public static void drawBlocker( @Nonnull BufferBuilder buffer, float x, float y, float width, float height )
public static void drawBlocker( @Nonnull Matrix4f transform, @Nonnull IRenderTypeBuffer renderer, float x, float y, float width, float height )
{
Colour colour = Colour.Black;
drawQuad( buffer, x, y, width, height, colour.getR(), colour.getG(), colour.getB() );
Colour colour = Colour.BLACK;
drawQuad( transform, renderer.getBuffer( Type.BLOCKER ), x, y, width, height, colour.getR(), colour.getG(), colour.getB() );
}
public static void drawBlocker( float x, float y, float width, float height )
{
bindFont();
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
begin( buffer );
drawBlocker( buffer, x, y, width, height );
tessellator.draw();
}
public static void bindFont()
private static void bindFont()
{
Minecraft.getInstance().getTextureManager().bindTexture( FONT );
GlStateManager.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP );
GlStateManager.enableTexture();
RenderSystem.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP );
}
public static void begin( BufferBuilder buffer )
private static final class Type extends RenderState
{
buffer.begin( GL11.GL_TRIANGLES, POSITION_COLOR_TEX );
private static final int GL_MODE = GL11.GL_TRIANGLES;
private static final VertexFormat FORMAT = DefaultVertexFormats.POSITION_COLOR_TEX;
static final RenderType MAIN = RenderType.makeType(
"terminal_font", FORMAT, GL_MODE, 1024,
false, false, // useDelegate, needsSorting
RenderType.State.getBuilder()
.texture( new RenderState.TextureState( FONT, false, false ) ) // blur, minimap
.alpha( DEFAULT_ALPHA )
.lightmap( LIGHTMAP_DISABLED )
.writeMask( COLOR_WRITE )
.build( false )
);
static final RenderType BLOCKER = RenderType.makeType(
"terminal_blocker", FORMAT, GL_MODE, 256,
false, false, // useDelegate, needsSorting
RenderType.State.getBuilder()
.texture( new RenderState.TextureState( FONT, false, false ) ) // blur, minimap
.alpha( DEFAULT_ALPHA )
.writeMask( DEPTH_WRITE )
.lightmap( LIGHTMAP_DISABLED )
.build( false )
);
private Type( String name, Runnable setup, Runnable destroy )
{
super( name, setup, destroy );
}
}
}

View File

@@ -5,7 +5,7 @@
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
import dan200.computercraft.client.gui.widgets.WidgetWrapper;
@@ -134,17 +134,17 @@ public final class GuiComputer<T extends ContainerComputerBase> extends Containe
terminal.draw( terminalWrapper.getX(), terminalWrapper.getY() );
// Draw a border around the terminal
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
RenderSystem.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
switch( m_family )
{
case Normal:
case NORMAL:
default:
minecraft.getTextureManager().bindTexture( BACKGROUND_NORMAL );
break;
case Advanced:
case ADVANCED:
minecraft.getTextureManager().bindTexture( BACKGROUND_ADVANCED );
break;
case Command:
case COMMAND:
minecraft.getTextureManager().bindTexture( BACKGROUND_COMMAND );
break;
}

View File

@@ -5,7 +5,7 @@
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.entity.player.PlayerInventory;
@@ -32,7 +32,7 @@ public class GuiDiskDrive extends ContainerScreen<ContainerDiskDrive>
@Override
protected void drawGuiContainerBackgroundLayer( float partialTicks, int mouseX, int mouseY )
{
GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
minecraft.getTextureManager().bindTexture( BACKGROUND );
blit( guiLeft, guiTop, 0, 0, xSize, ySize );
}

View File

@@ -5,7 +5,7 @@
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import dan200.computercraft.shared.peripheral.printer.ContainerPrinter;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.client.resources.I18n;
@@ -33,7 +33,7 @@ public class GuiPrinter extends ContainerScreen<ContainerPrinter>
@Override
protected void drawGuiContainerBackgroundLayer( float partialTicks, int mouseX, int mouseY )
{
GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
minecraft.getTextureManager().bindTexture( BACKGROUND );
blit( guiLeft, guiTop, 0, 0, xSize, ySize );

View File

@@ -5,11 +5,15 @@
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.common.ContainerHeldItem;
import dan200.computercraft.shared.media.items.ItemPrintout;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.TransformationMatrix;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.text.ITextComponent;
import org.lwjgl.glfw.GLFW;
@@ -18,6 +22,8 @@ import static dan200.computercraft.client.render.PrintoutRenderer.*;
public class GuiPrintout extends ContainerScreen<ContainerHeldItem>
{
private static final Matrix4f IDENTITY = TransformationMatrix.identity().getMatrix();
private final boolean m_book;
private final int m_pages;
private final TextBuffer[] m_text;
@@ -88,20 +94,22 @@ public class GuiPrintout extends ContainerScreen<ContainerHeldItem>
public void drawGuiContainerBackgroundLayer( float partialTicks, int mouseX, int mouseY )
{
// Draw the printout
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
GlStateManager.enableDepthTest();
RenderSystem.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
RenderSystem.enableDepthTest();
drawBorder( guiLeft, guiTop, blitOffset, m_page, m_pages, m_book );
drawText( guiLeft + X_TEXT_MARGIN, guiTop + Y_TEXT_MARGIN, ItemPrintout.LINES_PER_PAGE * m_page, m_text, m_colours );
IRenderTypeBuffer.Impl renderer = Minecraft.getInstance().getRenderTypeBuffers().getBufferSource();
drawBorder( IDENTITY, renderer, guiLeft, guiTop, getBlitOffset(), m_page, m_pages, m_book );
drawText( IDENTITY, renderer, guiLeft + X_TEXT_MARGIN, guiTop + Y_TEXT_MARGIN, ItemPrintout.LINES_PER_PAGE * m_page, m_text, m_colours );
renderer.finish();
}
@Override
public void render( int mouseX, int mouseY, float partialTicks )
{
// We must take the background further back in order to not overlap with our printed pages.
blitOffset--;
setBlitOffset( getBlitOffset() - 1 );
renderBackground();
blitOffset++;
setBlitOffset( getBlitOffset() + 1 );
super.render( mouseX, mouseY, partialTicks );
renderHoveredToolTip( mouseX, mouseY );

View File

@@ -5,7 +5,7 @@
*/
package dan200.computercraft.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
import dan200.computercraft.client.gui.widgets.WidgetWrapper;
@@ -98,7 +98,7 @@ public class GuiTurtle extends ContainerScreen<ContainerTurtle>
int slot = m_container.getSelectedSlot();
if( slot >= 0 )
{
GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
int slotX = slot % 4;
int slotY = slot / 4;
minecraft.getTextureManager().bindTexture( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
@@ -110,11 +110,11 @@ public class GuiTurtle extends ContainerScreen<ContainerTurtle>
protected void drawGuiContainerBackgroundLayer( float partialTicks, int mouseX, int mouseY )
{
// Draw term
boolean advanced = m_family == ComputerFamily.Advanced;
boolean advanced = m_family == ComputerFamily.ADVANCED;
terminal.draw( terminalWrapper.getX(), terminalWrapper.getY() );
// Draw border/inventory
GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
minecraft.getTextureManager().bindTexture( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
blit( guiLeft, guiTop, 0, 0, xSize, ySize );

View File

@@ -17,6 +17,9 @@ import org.lwjgl.glfw.GLFW;
import java.util.BitSet;
import java.util.function.Supplier;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
public class WidgetTerminal implements IGuiEventListener
{
private static final float TERMINATE_TIME = 0.5f;
@@ -173,8 +176,8 @@ public class WidgetTerminal implements IGuiEventListener
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FixedWidthFontRenderer.FONT_WIDTH);
int charY = (int) (mouseY / FixedWidthFontRenderer.FONT_HEIGHT);
int charX = (int) (mouseX / FONT_WIDTH);
int charY = (int) (mouseY / FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
@@ -197,8 +200,8 @@ public class WidgetTerminal implements IGuiEventListener
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FixedWidthFontRenderer.FONT_WIDTH);
int charY = (int) (mouseY / FixedWidthFontRenderer.FONT_HEIGHT);
int charX = (int) (mouseX / FONT_WIDTH);
int charY = (int) (mouseY / FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
@@ -224,8 +227,8 @@ public class WidgetTerminal implements IGuiEventListener
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FixedWidthFontRenderer.FONT_WIDTH);
int charY = (int) (mouseY / FixedWidthFontRenderer.FONT_HEIGHT);
int charX = (int) (mouseX / FONT_WIDTH);
int charY = (int) (mouseY / FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
@@ -249,8 +252,8 @@ public class WidgetTerminal implements IGuiEventListener
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FixedWidthFontRenderer.FONT_WIDTH);
int charY = (int) (mouseY / FixedWidthFontRenderer.FONT_HEIGHT);
int charX = (int) (mouseX / FONT_WIDTH);
int charY = (int) (mouseY / FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
@@ -318,19 +321,15 @@ public class WidgetTerminal implements IGuiEventListener
Terminal terminal = computer != null ? computer.getTerminal() : null;
if( terminal != null )
{
FixedWidthFontRenderer.drawTerminal(
originX, originY,
terminal, !computer.isColour(), topMargin, bottomMargin, leftMargin, rightMargin
);
FixedWidthFontRenderer.drawTerminal( originX, originY, terminal, !computer.isColour(), topMargin, bottomMargin, leftMargin, rightMargin );
}
else
{
int x = originX - leftMargin;
int y = originY - rightMargin;
int width = termWidth * FixedWidthFontRenderer.FONT_WIDTH + leftMargin + rightMargin;
int height = termHeight * FixedWidthFontRenderer.FONT_HEIGHT + topMargin + bottomMargin;
FixedWidthFontRenderer.drawEmptyTerminal( x, y, width, height );
FixedWidthFontRenderer.drawEmptyTerminal(
originX - leftMargin, originY - rightMargin,
termWidth * FONT_WIDTH + leftMargin + rightMargin,
termHeight * FONT_HEIGHT + topMargin + bottomMargin
);
}
}
}

View File

@@ -7,7 +7,6 @@ package dan200.computercraft.client.proxy;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.render.TileEntityCableRenderer;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.client.render.TurtlePlayerRenderer;
@@ -15,7 +14,6 @@ import dan200.computercraft.shared.common.ContainerHeldItem;
import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive;
import dan200.computercraft.shared.peripheral.modem.wired.TileCable;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.peripheral.printer.ContainerPrinter;
@@ -24,6 +22,8 @@ import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.turtle.core.TurtlePlayer;
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
import net.minecraft.client.gui.ScreenManager;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderTypeLookup;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
@@ -40,12 +40,22 @@ public final class ComputerCraftProxyClient
{
registerContainers();
// Setup TESRs
ClientRegistry.bindTileEntitySpecialRenderer( TileMonitor.class, new TileEntityMonitorRenderer() );
ClientRegistry.bindTileEntitySpecialRenderer( TileCable.class, new TileEntityCableRenderer() );
ClientRegistry.bindTileEntitySpecialRenderer( TileTurtle.class, new TileEntityTurtleRenderer() );
// While turtles themselves are not transparent, their upgrades may be.
RenderTypeLookup.setRenderLayer( ComputerCraft.Blocks.turtleNormal, RenderType.getTranslucent() );
RenderTypeLookup.setRenderLayer( ComputerCraft.Blocks.turtleAdvanced, RenderType.getTranslucent() );
RenderingRegistry.registerEntityRenderingHandler( TurtlePlayer.class, TurtlePlayerRenderer::new );
// Monitors' textures have transparent fronts and so count as cutouts.
RenderTypeLookup.setRenderLayer( ComputerCraft.Blocks.monitorNormal, RenderType.getCutout() );
RenderTypeLookup.setRenderLayer( ComputerCraft.Blocks.monitorAdvanced, RenderType.getCutout() );
// Setup TESRs
ClientRegistry.bindTileEntityRenderer( TileMonitor.FACTORY_NORMAL, TileEntityMonitorRenderer::new );
ClientRegistry.bindTileEntityRenderer( TileMonitor.FACTORY_ADVANCED, TileEntityMonitorRenderer::new );
ClientRegistry.bindTileEntityRenderer( TileTurtle.FACTORY_NORMAL, TileEntityTurtleRenderer::new );
ClientRegistry.bindTileEntityRenderer( TileTurtle.FACTORY_ADVANCED, TileEntityTurtleRenderer::new );
// TODO: ClientRegistry.bindTileEntityRenderer( TileCable.FACTORY, x -> new TileEntityCableRenderer() );
RenderingRegistry.registerEntityRenderingHandler( TurtlePlayer.TYPE, TurtlePlayerRenderer::new );
}
private static void registerContainers()

View File

@@ -5,26 +5,27 @@
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.modem.wired.BlockCable;
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.util.math.shapes.VoxelShape;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.DrawBlockHighlightEvent;
import net.minecraftforge.client.event.DrawHighlightEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.lwjgl.opengl.GL11;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT )
public final class CableHighlightRenderer
@@ -37,14 +38,12 @@ public final class CableHighlightRenderer
* Draw an outline for a specific part of a cable "Multipart".
*
* @param event The event to observe
* @see WorldRenderer#drawSelectionBox(ActiveRenderInfo, RayTraceResult, int)
* @see WorldRenderer#drawSelectionBox(MatrixStack, IVertexBuilder, Entity, double, double, double, BlockPos, BlockState)
*/
@SubscribeEvent
public static void drawHighlight( DrawBlockHighlightEvent event )
public static void drawHighlight( DrawHighlightEvent.HighlightBlock event )
{
if( event.getTarget().getType() != RayTraceResult.Type.BLOCK ) return;
BlockRayTraceResult hit = (BlockRayTraceResult) event.getTarget();
BlockRayTraceResult hit = event.getTarget();
BlockPos pos = hit.getPos();
World world = event.getInfo().getRenderViewEntity().getEntityWorld();
ActiveRenderInfo info = event.getInfo();
@@ -59,31 +58,22 @@ public final class CableHighlightRenderer
event.setCanceled( true );
Minecraft mc = Minecraft.getInstance();
GlStateManager.enableBlend();
GlStateManager.blendFuncSeparate( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO );
GlStateManager.lineWidth( Math.max( 2.5F, mc.mainWindow.getFramebufferWidth() / 1920.0F * 2.5F ) );
GlStateManager.disableTexture();
GlStateManager.depthMask( false );
GlStateManager.matrixMode( GL11.GL_PROJECTION );
GlStateManager.pushMatrix();
GlStateManager.scalef( 1.0F, 1.0F, 0.999F );
VoxelShape shape = WorldUtil.isVecInside( CableShapes.getModemShape( state ), hit.getHitVec().subtract( pos.getX(), pos.getY(), pos.getZ() ) )
? CableShapes.getModemShape( state )
: CableShapes.getCableShape( state );
Vec3d cameraPos = info.getProjectedView();
WorldRenderer.drawShape(
shape, pos.getX() - cameraPos.getX(), pos.getY() - cameraPos.getY(), pos.getZ() - cameraPos.getZ(),
0.0F, 0.0F, 0.0F, 0.4F
);
double xOffset = pos.getX() - cameraPos.getX();
double yOffset = pos.getY() - cameraPos.getY();
double zOffset = pos.getZ() - cameraPos.getZ();
GlStateManager.popMatrix();
GlStateManager.matrixMode( GL11.GL_MODELVIEW );
GlStateManager.depthMask( true );
GlStateManager.enableTexture();
GlStateManager.disableBlend();
IVertexBuilder buffer = event.getBuffers().getBuffer( RenderType.getLines() );
Matrix4f matrix4f = event.getMatrix().getLast().getMatrix();
shape.forEachEdge( ( x1, y1, z1, x2, y2, z2 ) -> {
buffer.pos( matrix4f, (float) (x1 + xOffset), (float) (y1 + yOffset), (float) (z1 + zOffset) )
.color( 0, 0, 0, 0.4f ).endVertex();
buffer.pos( matrix4f, (float) (x2 + xOffset), (float) (y2 + yOffset), (float) (z2 + zOffset) )
.color( 0, 0, 0, 0.4f ).endVertex();
} );
}
}

View File

@@ -5,9 +5,12 @@
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.matrix.MatrixStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.player.AbstractClientPlayerEntity;
import net.minecraft.client.renderer.FirstPersonRenderer;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.Vector3f;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Hand;
@@ -19,99 +22,118 @@ public abstract class ItemMapLikeRenderer
/**
* The main rendering method for the item.
*
* @param stack The stack to render
* @see FirstPersonRenderer#renderMapFirstPerson(ItemStack)
* @param transform The matrix transformation stack
* @param render The buffer to render to
* @param stack The stack to render
* @see FirstPersonRenderer#renderItemInFirstPerson(AbstractClientPlayerEntity, float, float, Hand, float, ItemStack, float, MatrixStack, IRenderTypeBuffer, int)
*/
protected abstract void renderItem( ItemStack stack );
protected abstract void renderItem( MatrixStack transform, IRenderTypeBuffer render, ItemStack stack );
protected void renderItemFirstPerson( Hand hand, float pitch, float equipProgress, float swingProgress, ItemStack stack )
protected void renderItemFirstPerson( MatrixStack transform, IRenderTypeBuffer render, int lightTexture, Hand hand, float pitch, float equipProgress, float swingProgress, ItemStack stack )
{
PlayerEntity player = Minecraft.getInstance().player;
GlStateManager.pushMatrix();
transform.push();
if( hand == Hand.MAIN_HAND && player.getHeldItemOffhand().isEmpty() )
{
renderItemFirstPersonCenter( pitch, equipProgress, swingProgress, stack );
renderItemFirstPersonCenter( transform, render, lightTexture, pitch, equipProgress, swingProgress, stack );
}
else
{
renderItemFirstPersonSide(
transform, render, lightTexture,
hand == Hand.MAIN_HAND ? player.getPrimaryHand() : player.getPrimaryHand().opposite(),
equipProgress, swingProgress, stack
);
}
GlStateManager.popMatrix();
transform.pop();
}
/**
* Renders the item to one side of the player.
*
* @param transform The matrix transformation stack
* @param render The buffer to render to
* @param combinedLight The current light level
* @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 FirstPersonRenderer#renderMapFirstPersonSide(float, HandSide, float, ItemStack)
* @see FirstPersonRenderer#renderMapFirstPersonSide(MatrixStack, IRenderTypeBuffer, int, float, HandSide, float, ItemStack)
*/
private void renderItemFirstPersonSide( HandSide side, float equipProgress, float swingProgress, ItemStack stack )
private void renderItemFirstPersonSide( MatrixStack transform, IRenderTypeBuffer render, int combinedLight, HandSide side, float equipProgress, float swingProgress, ItemStack stack )
{
Minecraft minecraft = Minecraft.getInstance();
float offset = side == HandSide.RIGHT ? 1f : -1f;
GlStateManager.translatef( offset * 0.125f, -0.125f, 0f );
transform.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.rotatef( offset * 10f, 0f, 0f, 1f );
minecraft.getFirstPersonRenderer().renderArmFirstPerson( equipProgress, swingProgress, side );
GlStateManager.popMatrix();
transform.push();
transform.rotate( Vector3f.ZP.rotationDegrees( offset * 10f ) );
minecraft.getFirstPersonRenderer().renderArmFirstPerson( transform, render, combinedLight, equipProgress, swingProgress, side );
transform.pop();
}
// Setup the appropriate transformations. This is just copied from the
// corresponding method in ItemRenderer.
GlStateManager.pushMatrix();
GlStateManager.translatef( offset * 0.51f, -0.08f + equipProgress * -1.2f, -0.75f );
transform.push();
transform.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.translatef( offset * f3, f4 - 0.3f * f2, f5 );
GlStateManager.rotatef( f2 * -45f, 1f, 0f, 0f );
GlStateManager.rotatef( offset * f2 * -30f, 0f, 1f, 0f );
transform.translate( offset * f3, f4 - 0.3f * f2, f5 );
transform.rotate( Vector3f.XP.rotationDegrees( f2 * -45f ) );
transform.rotate( Vector3f.YP.rotationDegrees( offset * f2 * -30f ) );
renderItem( stack );
renderItem( transform, render, stack );
GlStateManager.popMatrix();
transform.pop();
}
/**
* Render an item in the middle of the screen.
*
* @param transform The matrix transformation stack
* @param render The buffer to render to
* @param combinedLight The current light level
* @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 FirstPersonRenderer#renderMapFirstPerson(float, float, float)
* @see FirstPersonRenderer#renderMapFirstPerson(MatrixStack, IRenderTypeBuffer, int, float, float, float)
*/
private void renderItemFirstPersonCenter( float pitch, float equipProgress, float swingProgress, ItemStack stack )
private void renderItemFirstPersonCenter( MatrixStack transform, IRenderTypeBuffer render, int combinedLight, float pitch, float equipProgress, float swingProgress, ItemStack stack )
{
FirstPersonRenderer renderer = Minecraft.getInstance().getFirstPersonRenderer();
Minecraft minecraft = Minecraft.getInstance();
FirstPersonRenderer renderer = minecraft.getFirstPersonRenderer();
// 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.translatef( 0f, -tX / 2f, tZ );
float pitchAngle = renderer.getMapAngleFromPitch( pitch );
GlStateManager.translatef( 0f, 0.04f + equipProgress * -1.2f + pitchAngle * -0.5f, -0.72f );
GlStateManager.rotatef( pitchAngle * -85f, 1f, 0f, 0f );
renderer.renderArms();
float rX = MathHelper.sin( swingRt * (float) Math.PI );
GlStateManager.rotatef( rX * 20f, 1f, 0f, 0f );
GlStateManager.scalef( 2f, 2f, 2f );
transform.translate( 0, -tX / 2, tZ );
renderItem( stack );
float pitchAngle = renderer.getMapAngleFromPitch( pitch );
transform.translate( 0, 0.04F + equipProgress * -1.2f + pitchAngle * -0.5f, -0.72f );
transform.rotate( Vector3f.XP.rotationDegrees( pitchAngle * -85.0f ) );
if( !minecraft.player.isInvisible() )
{
transform.push();
transform.rotate( Vector3f.YP.rotationDegrees( 90.0F ) );
renderer.renderArm( transform, render, combinedLight, HandSide.RIGHT );
renderer.renderArm( transform, render, combinedLight, HandSide.LEFT );
transform.pop();
}
float rX = MathHelper.sin( swingRt * (float) Math.PI );
transform.rotate( Vector3f.XP.rotationDegrees( rX * 20.0F ) );
transform.scale( 2.0F, 2.0F, 2.0F );
renderItem( transform, render, stack );
}
}

View File

@@ -5,7 +5,9 @@
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
@@ -14,12 +16,11 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.util.Colour;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RenderSpecificHandEvent;
import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.lwjgl.opengl.GL11;
@@ -45,17 +46,20 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
}
@SubscribeEvent
public static void renderItem( RenderSpecificHandEvent event )
public static void onRenderInHand( RenderHandEvent event )
{
ItemStack stack = event.getItemStack();
if( !(stack.getItem() instanceof ItemPocketComputer) ) return;
event.setCanceled( true );
INSTANCE.renderItemFirstPerson( event.getHand(), event.getInterpolatedPitch(), event.getEquipProgress(), event.getSwingProgress(), event.getItemStack() );
INSTANCE.renderItemFirstPerson(
event.getMatrixStack(), event.getBuffers(), event.getLight(),
event.getHand(), event.getInterpolatedPitch(), event.getEquipProgress(), event.getSwingProgress(), event.getItemStack()
);
}
@Override
protected void renderItem( ItemStack stack )
protected void renderItem( MatrixStack transform, IRenderTypeBuffer render, ItemStack stack )
{
ClientComputer computer = ItemPocketComputer.createClientComputer( stack );
Terminal terminal = computer == null ? null : computer.getTerminal();
@@ -77,50 +81,45 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
// Setup various transformations. Note that these are partially adapted from the corresponding method
// in ItemRenderer
GlStateManager.pushMatrix();
transform.push();
transform.rotate( Vector3f.YP.rotationDegrees( 180f ) );
transform.rotate( Vector3f.ZP.rotationDegrees( 180f ) );
transform.scale( 0.5f, 0.5f, 0.5f );
GlStateManager.disableLighting();
GlStateManager.disableDepthTest();
GlStateManager.rotatef( 180f, 0f, 1f, 0f );
GlStateManager.rotatef( 180f, 0f, 0f, 1f );
GlStateManager.scalef( 0.5f, 0.5f, 0.5f );
double scale = 0.75 / Math.max( width + FRAME * 2, height + FRAME * 2 + LIGHT_HEIGHT );
GlStateManager.scaled( scale, scale, 0 );
GlStateManager.translated( -0.5 * width, -0.5 * height, 0 );
float scale = 0.75f / Math.max( width + FRAME * 2, height + FRAME * 2 + LIGHT_HEIGHT );
transform.scale( scale, scale, 0 );
transform.translate( -0.5 * width, -0.5 * height, 0 );
// Render the main frame
ItemPocketComputer item = (ItemPocketComputer) stack.getItem();
ComputerFamily family = item.getFamily();
int frameColour = item.getColour( stack );
renderFrame( family, frameColour, width, height );
Matrix4f matrix = transform.getLast().getMatrix();
renderFrame( matrix, family, frameColour, width, height );
// Render the light
int lightColour = ItemPocketComputer.getLightState( stack );
if( lightColour == -1 ) lightColour = Colour.Black.getHex();
renderLight( lightColour, width, height );
if( lightColour == -1 ) lightColour = Colour.BLACK.getHex();
renderLight( matrix, lightColour, width, height );
if( computer != null && terminal != null )
{
FixedWidthFontRenderer.drawTerminal( MARGIN, MARGIN, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN );
FixedWidthFontRenderer.drawTerminal( matrix, MARGIN, MARGIN, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN );
}
else
{
FixedWidthFontRenderer.drawEmptyTerminal( 0, 0, width, height );
FixedWidthFontRenderer.drawEmptyTerminal( matrix, 0, 0, width, height );
}
GlStateManager.enableDepthTest();
GlStateManager.enableLighting();
GlStateManager.popMatrix();
transform.pop();
}
private static void renderFrame( ComputerFamily family, int colour, int width, int height )
private static void renderFrame( Matrix4f transform, ComputerFamily family, int colour, int width, int height )
{
Minecraft.getInstance().getTextureManager().bindTexture( colour != -1
? BACKGROUND_COLOUR
: family == ComputerFamily.Normal ? BACKGROUND_NORMAL : BACKGROUND_ADVANCED
: family == ComputerFamily.NORMAL ? BACKGROUND_NORMAL : BACKGROUND_ADVANCED
);
float r = ((colour >>> 16) & 0xFF) / 255.0f;
@@ -129,38 +128,38 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR );
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR_TEX );
// Top left, middle, right
renderTexture( buffer, -FRAME, -FRAME, 12, 28, FRAME, FRAME, r, g, b );
renderTexture( buffer, 0, -FRAME, 0, 0, width, FRAME, r, g, b );
renderTexture( buffer, width, -FRAME, 24, 28, FRAME, FRAME, r, g, b );
renderTexture( transform, buffer, -FRAME, -FRAME, 12, 28, FRAME, FRAME, r, g, b );
renderTexture( transform, buffer, 0, -FRAME, 0, 0, width, FRAME, r, g, b );
renderTexture( transform, buffer, width, -FRAME, 24, 28, FRAME, FRAME, r, g, b );
// Left and bright border
renderTexture( buffer, -FRAME, 0, 0, 28, FRAME, height, r, g, b );
renderTexture( buffer, width, 0, 36, 28, FRAME, height, r, g, b );
renderTexture( transform, buffer, -FRAME, 0, 0, 28, FRAME, height, r, g, b );
renderTexture( transform, buffer, width, 0, 36, 28, FRAME, height, r, g, b );
// Bottom left, middle, right. We do this in three portions: the top inner corners, an extended region for
// lights, and then the bottom outer corners.
renderTexture( buffer, -FRAME, height, 12, 40, FRAME, FRAME / 2, r, g, b );
renderTexture( buffer, 0, height, 0, 12, width, FRAME / 2, r, g, b );
renderTexture( buffer, width, height, 24, 40, FRAME, FRAME / 2, r, g, b );
renderTexture( transform, buffer, -FRAME, height, 12, 40, FRAME, FRAME / 2, r, g, b );
renderTexture( transform, buffer, 0, height, 0, 12, width, FRAME / 2, r, g, b );
renderTexture( transform, buffer, width, height, 24, 40, FRAME, FRAME / 2, r, g, b );
renderTexture( buffer, -FRAME, height + FRAME / 2, 12, 44, FRAME, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( buffer, 0, height + FRAME / 2, 0, 16, width, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( buffer, width, height + FRAME / 2, 24, 44, FRAME, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( transform, buffer, -FRAME, height + FRAME / 2, 12, 44, FRAME, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( transform, buffer, 0, height + FRAME / 2, 0, 16, width, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( transform, buffer, width, height + FRAME / 2, 24, 44, FRAME, LIGHT_HEIGHT, FRAME, 4, r, g, b );
renderTexture( buffer, -FRAME, height + LIGHT_HEIGHT + FRAME / 2, 12, 40 + FRAME / 2, FRAME, FRAME / 2, r, g, b );
renderTexture( buffer, 0, height + LIGHT_HEIGHT + FRAME / 2, 0, 12 + FRAME / 2, width, FRAME / 2, r, g, b );
renderTexture( buffer, width, height + LIGHT_HEIGHT + FRAME / 2, 24, 40 + FRAME / 2, FRAME, FRAME / 2, r, g, b );
renderTexture( transform, buffer, -FRAME, height + LIGHT_HEIGHT + FRAME / 2, 12, 40 + FRAME / 2, FRAME, FRAME / 2, r, g, b );
renderTexture( transform, buffer, 0, height + LIGHT_HEIGHT + FRAME / 2, 0, 12 + FRAME / 2, width, FRAME / 2, r, g, b );
renderTexture( transform, buffer, width, height + LIGHT_HEIGHT + FRAME / 2, 24, 40 + FRAME / 2, FRAME, FRAME / 2, r, g, b );
tessellator.draw();
}
private static void renderLight( int colour, int width, int height )
private static void renderLight( Matrix4f transform, int colour, int width, int height )
{
GlStateManager.enableBlend();
GlStateManager.disableTexture();
RenderSystem.enableBlend();
RenderSystem.disableTexture();
float r = ((colour >>> 16) & 0xFF) / 255.0f;
float g = ((colour >>> 8) & 0xFF) / 255.0f;
@@ -169,26 +168,26 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin( GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR );
buffer.pos( width - LIGHT_HEIGHT * 2, height + LIGHT_HEIGHT + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
buffer.pos( width, height + LIGHT_HEIGHT + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
buffer.pos( width, height + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
buffer.pos( width - LIGHT_HEIGHT * 2, height + FRAME / 2.0f, 0.0D ).color( r, g, b, 1.0f ).endVertex();
buffer.pos( transform, width - LIGHT_HEIGHT * 2, height + LIGHT_HEIGHT + FRAME / 2.0f, 0 ).color( r, g, b, 1.0f ).endVertex();
buffer.pos( transform, width, height + LIGHT_HEIGHT + FRAME / 2.0f, 0 ).color( r, g, b, 1.0f ).endVertex();
buffer.pos( transform, width, height + FRAME / 2.0f, 0 ).color( r, g, b, 1.0f ).endVertex();
buffer.pos( transform, width - LIGHT_HEIGHT * 2, height + FRAME / 2.0f, 0 ).color( r, g, b, 1.0f ).endVertex();
tessellator.draw();
GlStateManager.enableTexture();
RenderSystem.enableTexture();
}
private static void renderTexture( BufferBuilder builder, int x, int y, int textureX, int textureY, int width, int height, float r, float g, float b )
private static void renderTexture( Matrix4f transform, IVertexBuilder builder, int x, int y, int textureX, int textureY, int width, int height, float r, float g, float b )
{
renderTexture( builder, x, y, textureX, textureY, width, height, width, height, r, g, b );
renderTexture( transform, builder, x, y, textureX, textureY, width, height, width, height, r, g, b );
}
private static void renderTexture( BufferBuilder builder, int x, int y, int textureX, int textureY, int width, int height, int textureWidth, int textureHeight, float r, float g, float b )
private static void renderTexture( Matrix4f transform, IVertexBuilder builder, int x, int y, int textureX, int textureY, int width, int height, int textureWidth, int textureHeight, float r, float g, float b )
{
float scale = 1 / 255.0f;
builder.pos( x, y + height, 0 ).tex( textureX * scale, (textureY + textureHeight) * scale ).color( r, g, b, 1.0f ).endVertex();
builder.pos( x + width, y + height, 0 ).tex( (textureX + textureWidth) * scale, (textureY + textureHeight) * scale ).color( r, g, b, 1.0f ).endVertex();
builder.pos( x + width, y, 0 ).tex( (textureX + textureWidth) * scale, textureY * scale ).color( r, g, b, 1.0f ).endVertex();
builder.pos( x, y, 0 ).tex( textureX * scale, textureY * scale ).color( r, g, b, 1.0f ).endVertex();
builder.pos( transform, x, y + height, 0 ).color( r, g, b, 1.0f ).tex( textureX * scale, (textureY + textureHeight) * scale ).endVertex();
builder.pos( transform, x + width, y + height, 0 ).color( r, g, b, 1.0f ).tex( (textureX + textureWidth) * scale, (textureY + textureHeight) * scale ).endVertex();
builder.pos( transform, x + width, y, 0 ).color( r, g, b, 1.0f ).tex( (textureX + textureWidth) * scale, textureY * scale ).endVertex();
builder.pos( transform, x, y, 0 ).color( r, g, b, 1.0f ).tex( textureX * scale, textureY * scale ).endVertex();
}
}

View File

@@ -5,13 +5,16 @@
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.matrix.MatrixStack;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.media.items.ItemPrintout;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.Vector3f;
import net.minecraft.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.client.event.RenderItemInFrameEvent;
import net.minecraftforge.client.event.RenderSpecificHandEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@@ -34,30 +37,26 @@ public final class ItemPrintoutRenderer extends ItemMapLikeRenderer
}
@SubscribeEvent
public static void onRenderInHand( RenderSpecificHandEvent event )
public static void onRenderInHand( RenderHandEvent event )
{
ItemStack stack = event.getItemStack();
if( !(stack.getItem() instanceof ItemPrintout) ) return;
event.setCanceled( true );
INSTANCE.renderItemFirstPerson( event.getHand(), event.getInterpolatedPitch(), event.getEquipProgress(), event.getSwingProgress(), event.getItemStack() );
INSTANCE.renderItemFirstPerson(
event.getMatrixStack(), event.getBuffers(), event.getLight(),
event.getHand(), event.getInterpolatedPitch(), event.getEquipProgress(), event.getSwingProgress(), event.getItemStack()
);
}
@Override
protected void renderItem( ItemStack stack )
protected void renderItem( MatrixStack transform, IRenderTypeBuffer render, ItemStack stack )
{
// Setup various transformations. Note that these are partially adapated from the corresponding method
// in FirstPersonRenderer.renderFirstPersonMap
GlStateManager.disableLighting();
transform.rotate( Vector3f.XP.rotationDegrees( 180f ) );
transform.scale( 0.42f, 0.42f, -0.42f );
transform.translate( -0.5f, -0.48f, 0.0f );
GlStateManager.rotatef( 180f, 0f, 1f, 0f );
GlStateManager.rotatef( 180f, 0f, 0f, 1f );
GlStateManager.scalef( 0.42f, 0.42f, -0.42f );
GlStateManager.translatef( -0.5f, -0.48f, 0.0f );
drawPrintout( stack );
GlStateManager.enableLighting();
drawPrintout( transform, render, stack );
}
@SubscribeEvent
@@ -65,24 +64,20 @@ public final class ItemPrintoutRenderer extends ItemMapLikeRenderer
{
ItemStack stack = event.getItem();
if( !(stack.getItem() instanceof ItemPrintout) ) return;
event.setCanceled( true );
GlStateManager.disableLighting();
MatrixStack transform = event.getMatrix();
// Move a little bit forward to ensure we're not clipping with the frame
GlStateManager.translatef( 0.0f, 0.0f, -0.001f );
GlStateManager.rotatef( 180f, 0f, 0f, 1f );
GlStateManager.scalef( 0.95f, 0.95f, -0.95f );
GlStateManager.translatef( -0.5f, -0.5f, 0.0f );
transform.translate( 0.0f, 0.0f, -0.001f );
transform.rotate( Vector3f.ZP.rotationDegrees( 180f ) );
transform.scale( 0.95f, 0.95f, -0.95f );
transform.translate( -0.5f, -0.5f, 0.0f );
drawPrintout( stack );
GlStateManager.enableLighting();
GlStateManager.disableBlend();
drawPrintout( transform, event.getBuffers(), stack );
}
private static void drawPrintout( ItemStack stack )
private static void drawPrintout( MatrixStack transform, IRenderTypeBuffer render, ItemStack stack )
{
int pages = ItemPrintout.getPageCount( stack );
boolean book = ((ItemPrintout) stack.getItem()).getType() == ItemPrintout.Type.BOOK;
@@ -105,11 +100,14 @@ public final class ItemPrintoutRenderer extends ItemMapLikeRenderer
double max = Math.max( visualHeight, visualWidth );
// Scale the printout to fit correctly.
double scale = 1.0 / max;
GlStateManager.scaled( scale, scale, scale );
GlStateManager.translated( (max - width) / 2.0, (max - height) / 2.0, 0.0 );
float scale = (float) (1.0 / max);
transform.scale( scale, scale, scale );
transform.translate( (max - width) / 2.0, (max - height) / 2.0, 0.0 );
drawBorder( 0, 0, -0.01, 0, pages, book );
drawText( X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, ItemPrintout.getText( stack ), ItemPrintout.getColours( stack ) );
Matrix4f matrix = transform.getLast().getMatrix();
drawBorder( matrix, render, 0, 0, -0.01f, 0, pages, book );
drawText( matrix, render,
X_TEXT_MARGIN, Y_TEXT_MARGIN, 0, ItemPrintout.getText( stack ), ItemPrintout.getColours( stack )
);
}
}

View File

@@ -1,269 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.util.Direction;
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];
NormalAwareTransformer( IVertexConsumer parent, Matrix4f positionMatrix, Matrix4f normalMatrix )
{
super( parent );
this.positionMatrix = positionMatrix;
this.normalMatrix = normalMatrix;
}
@Override
public void setQuadOrientation( @Nonnull Direction 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 final class BakedQuadBuilder implements IVertexConsumer
{
private final VertexFormat format;
private final int[] vertexData;
private int vertexIndex = 0, elementIndex = 0;
private Direction orientation;
private int quadTint;
private boolean diffuse;
private TextureAtlasSprite texture;
private BakedQuadBuilder( VertexFormat format )
{
this.format = format;
vertexData = new int[format.getSize()];
}
@Nonnull
@Override
public VertexFormat getVertexFormat()
{
return format;
}
@Override
public void setQuadTint( int tint )
{
quadTint = tint;
}
@Override
public void setQuadOrientation( @Nonnull Direction 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

@@ -5,49 +5,45 @@
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.DrawBlockHighlightEvent;
import net.minecraftforge.client.event.DrawHighlightEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.lwjgl.opengl.GL11;
import java.util.EnumSet;
import static net.minecraft.util.Direction.*;
/**
* Overrides monitor highlighting to only render the outline of the <em>whole</em> monitor, rather than the current
* block. This means you do not get an intrusive outline on top of the screen.
*/
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT )
public final class MonitorHighlightRenderer
{
private static final float EXPAND = 0.002f;
private MonitorHighlightRenderer()
{
}
@SubscribeEvent
public static void drawHighlight( DrawBlockHighlightEvent event )
public static void drawHighlight( DrawHighlightEvent.HighlightBlock event )
{
if( event.getTarget().getType() != RayTraceResult.Type.BLOCK || event.getInfo().getRenderViewEntity().isSneaking() )
{
return;
}
// Preserve normal behaviour when crouching.
if( event.getInfo().getRenderViewEntity().isCrouching() ) return;
World world = event.getInfo().getRenderViewEntity().getEntityWorld();
BlockPos pos = ((BlockRayTraceResult) event.getTarget()).getPos();
BlockPos pos = event.getTarget().getPos();
TileEntity tile = world.getTileEntity( pos );
if( !(tile instanceof TileMonitor) ) return;
@@ -64,53 +60,37 @@ public final class MonitorHighlightRenderer
if( monitor.getYIndex() != 0 ) faces.remove( monitor.getDown().getOpposite() );
if( monitor.getYIndex() != monitor.getHeight() - 1 ) faces.remove( monitor.getDown() );
GlStateManager.enableBlend();
GlStateManager.blendFuncSeparate( GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO );
GlStateManager.lineWidth( Math.max( 2.5F, (float) Minecraft.getInstance().mainWindow.getFramebufferWidth() / 1920.0F * 2.5F ) );
GlStateManager.disableTexture();
GlStateManager.depthMask( false );
GlStateManager.pushMatrix();
MatrixStack transformStack = event.getMatrix();
Vec3d cameraPos = event.getInfo().getProjectedView();
GlStateManager.translated( pos.getX() - cameraPos.getX(), pos.getY() - cameraPos.getY(), pos.getZ() - cameraPos.getZ() );
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin( GL11.GL_LINES, DefaultVertexFormats.POSITION_COLOR );
transformStack.push();
transformStack.translate( pos.getX() - cameraPos.getX(), pos.getY() - cameraPos.getY(), pos.getZ() - cameraPos.getZ() );
// I wish I could think of a better way to do this
if( faces.contains( NORTH ) || faces.contains( WEST ) ) line( buffer, 0, 0, 0, UP );
if( faces.contains( SOUTH ) || faces.contains( WEST ) ) line( buffer, 0, 0, 1, UP );
if( faces.contains( NORTH ) || faces.contains( EAST ) ) line( buffer, 1, 0, 0, UP );
if( faces.contains( SOUTH ) || faces.contains( EAST ) ) line( buffer, 1, 0, 1, UP );
if( faces.contains( NORTH ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 0, EAST );
if( faces.contains( SOUTH ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 1, EAST );
if( faces.contains( NORTH ) || faces.contains( UP ) ) line( buffer, 0, 1, 0, EAST );
if( faces.contains( SOUTH ) || faces.contains( UP ) ) line( buffer, 0, 1, 1, EAST );
if( faces.contains( WEST ) || faces.contains( DOWN ) ) line( buffer, 0, 0, 0, SOUTH );
if( faces.contains( EAST ) || faces.contains( DOWN ) ) line( buffer, 1, 0, 0, SOUTH );
if( faces.contains( WEST ) || faces.contains( UP ) ) line( buffer, 0, 1, 0, SOUTH );
if( faces.contains( EAST ) || faces.contains( UP ) ) line( buffer, 1, 1, 0, SOUTH );
IVertexBuilder buffer = event.getBuffers().getBuffer( RenderType.getLines() );
Matrix4f transform = transformStack.getLast().getMatrix();
if( faces.contains( NORTH ) || faces.contains( WEST ) ) line( buffer, transform, 0, 0, 0, UP );
if( faces.contains( SOUTH ) || faces.contains( WEST ) ) line( buffer, transform, 0, 0, 1, UP );
if( faces.contains( NORTH ) || faces.contains( EAST ) ) line( buffer, transform, 1, 0, 0, UP );
if( faces.contains( SOUTH ) || faces.contains( EAST ) ) line( buffer, transform, 1, 0, 1, UP );
if( faces.contains( NORTH ) || faces.contains( DOWN ) ) line( buffer, transform, 0, 0, 0, EAST );
if( faces.contains( SOUTH ) || faces.contains( DOWN ) ) line( buffer, transform, 0, 0, 1, EAST );
if( faces.contains( NORTH ) || faces.contains( UP ) ) line( buffer, transform, 0, 1, 0, EAST );
if( faces.contains( SOUTH ) || faces.contains( UP ) ) line( buffer, transform, 0, 1, 1, EAST );
if( faces.contains( WEST ) || faces.contains( DOWN ) ) line( buffer, transform, 0, 0, 0, SOUTH );
if( faces.contains( EAST ) || faces.contains( DOWN ) ) line( buffer, transform, 1, 0, 0, SOUTH );
if( faces.contains( WEST ) || faces.contains( UP ) ) line( buffer, transform, 0, 1, 0, SOUTH );
if( faces.contains( EAST ) || faces.contains( UP ) ) line( buffer, transform, 1, 1, 0, SOUTH );
tessellator.draw();
GlStateManager.popMatrix();
GlStateManager.depthMask( true );
GlStateManager.enableTexture();
GlStateManager.disableBlend();
transformStack.pop();
}
private static void line( BufferBuilder buffer, int x, int y, int z, Direction direction )
private static void line( IVertexBuilder buffer, Matrix4f transform, float x, float y, float z, Direction direction )
{
double minX = x == 0 ? -EXPAND : 1 + EXPAND;
double minY = y == 0 ? -EXPAND : 1 + EXPAND;
double minZ = z == 0 ? -EXPAND : 1 + EXPAND;
buffer.pos( minX, minY, minZ ).color( 0, 0, 0, 0.4f ).endVertex();
buffer.pos(
minX + direction.getXOffset() * (1 + EXPAND * 2),
minY + direction.getYOffset() * (1 + EXPAND * 2),
minZ + direction.getZOffset() * (1 + EXPAND * 2)
buffer.pos( transform, x, y, z ).color( 0, 0, 0, 0.4f ).endVertex();
buffer.pos( transform,
x + direction.getXOffset(),
y + direction.getYOffset(),
z + direction.getZOffset()
).color( 0, 0, 0, 0.4f ).endVertex();
}
}

View File

@@ -0,0 +1,164 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.google.common.base.Strings;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.shared.util.Palette;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.texture.TextureUtil;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import java.io.InputStream;
import java.nio.FloatBuffer;
class MonitorTextureBufferShader
{
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer( 16 );
private static final FloatBuffer PALETTE_BUFFER = BufferUtils.createFloatBuffer( 16 * 3 );
private static int uniformMv;
private static int uniformFont;
private static int uniformWidth;
private static int uniformHeight;
private static int uniformTbo;
private static int uniformPalette;
private static boolean initialised;
private static boolean ok;
private static int program;
static void setupUniform( Matrix4f transform, int width, int height, Palette palette, boolean greyscale )
{
MATRIX_BUFFER.rewind();
transform.write( MATRIX_BUFFER );
MATRIX_BUFFER.rewind();
RenderSystem.glUniformMatrix4( uniformMv, false, MATRIX_BUFFER );
RenderSystem.glUniform1i( uniformWidth, width );
RenderSystem.glUniform1i( uniformHeight, height );
// TODO: Cache this? Maybe??
PALETTE_BUFFER.rewind();
for( int i = 0; i < 16; i++ )
{
double[] colour = palette.getColour( i );
if( greyscale )
{
float f = FixedWidthFontRenderer.toGreyscale( colour );
PALETTE_BUFFER.put( f ).put( f ).put( f );
}
else
{
PALETTE_BUFFER.put( (float) colour[0] ).put( (float) colour[1] ).put( (float) colour[2] );
}
}
PALETTE_BUFFER.flip();
RenderSystem.glUniform3( uniformPalette, PALETTE_BUFFER );
}
static boolean use()
{
if( initialised )
{
if( ok ) GlStateManager.useProgram( program );
return ok;
}
if( ok = load() )
{
GL20.glUseProgram( program );
RenderSystem.glUniform1i( uniformFont, 0 );
RenderSystem.glUniform1i( uniformTbo, TEXTURE_INDEX - GL13.GL_TEXTURE0 );
}
return ok;
}
private static boolean load()
{
initialised = true;
try
{
int vertexShader = loadShader( GL20.GL_VERTEX_SHADER, "assets/computercraft/shaders/monitor.vert" );
int fragmentShader = loadShader( GL20.GL_FRAGMENT_SHADER, "assets/computercraft/shaders/monitor.frag" );
program = GlStateManager.createProgram();
GlStateManager.attachShader( program, vertexShader );
GlStateManager.attachShader( program, fragmentShader );
GL20.glBindAttribLocation( program, 0, "v_pos" );
GlStateManager.linkProgram( program );
boolean ok = GlStateManager.getProgram( program, GL20.GL_LINK_STATUS ) != 0;
String log = GlStateManager.getProgramInfoLog( program, Short.MAX_VALUE ).trim();
if( !Strings.isNullOrEmpty( log ) )
{
ComputerCraft.log.warn( "Problems when linking monitor shader: {}", log );
}
GL20.glDetachShader( program, vertexShader );
GL20.glDetachShader( program, fragmentShader );
GlStateManager.deleteShader( vertexShader );
GlStateManager.deleteShader( fragmentShader );
if( !ok ) return false;
uniformMv = getUniformLocation( program, "u_mv" );
uniformFont = getUniformLocation( program, "u_font" );
uniformWidth = getUniformLocation( program, "u_width" );
uniformHeight = getUniformLocation( program, "u_height" );
uniformTbo = getUniformLocation( program, "u_tbo" );
uniformPalette = getUniformLocation( program, "u_palette" );
ComputerCraft.log.info( "Loaded monitor shader." );
return true;
}
catch( Exception e )
{
ComputerCraft.log.error( "Cannot load monitor shaders", e );
return false;
}
}
private static int loadShader( int kind, String path )
{
InputStream stream = TileEntityMonitorRenderer.class.getClassLoader().getResourceAsStream( path );
if( stream == null ) throw new IllegalArgumentException( "Cannot find " + path );
String contents = TextureUtil.readResourceAsString( stream );
int shader = GlStateManager.createShader( kind );
GlStateManager.shaderSource( shader, contents );
GlStateManager.compileShader( shader );
boolean ok = GlStateManager.getShader( shader, GL20.GL_COMPILE_STATUS ) != 0;
String log = GlStateManager.getShaderInfoLog( shader, Short.MAX_VALUE ).trim();
if( !Strings.isNullOrEmpty( log ) )
{
ComputerCraft.log.warn( "Problems when loading monitor shader {}: {}", path, log );
}
if( !ok ) throw new IllegalStateException( "Cannot compile shader " + path );
return shader;
}
private static int getUniformLocation( int program, String name )
{
int uniform = GlStateManager.getUniformLocation( program, name );
if( uniform == -1 ) throw new IllegalStateException( "Cannot find uniform " + name );
return uniform;
}
}

View File

@@ -5,15 +5,14 @@
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.platform.GlStateManager.DestFactor;
import com.mojang.blaze3d.platform.GlStateManager.SourceFactor;
import com.mojang.blaze3d.vertex.IVertexBuilder;
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.Tessellator;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.RenderState;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
@@ -24,7 +23,7 @@ import static dan200.computercraft.shared.media.items.ItemPrintout.LINES_PER_PAG
public final class PrintoutRenderer
{
private static final ResourceLocation BG = new ResourceLocation( "computercraft", "textures/gui/printout.png" );
private static final double BG_SIZE = 256.0;
private static final float BG_SIZE = 256.0f;
/**
* Width of a page.
@@ -61,27 +60,24 @@ public final class PrintoutRenderer
private PrintoutRenderer() {}
public static void drawText( int x, int y, int start, TextBuffer[] text, TextBuffer[] colours )
public static void drawText( Matrix4f transform, IRenderTypeBuffer renderer, int x, int y, int start, TextBuffer[] text, TextBuffer[] colours )
{
IVertexBuilder buffer = renderer.getBuffer( FixedWidthFontRenderer.TYPE );
for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ )
{
FixedWidthFontRenderer.drawString(
FixedWidthFontRenderer.drawString( transform, buffer,
x, y + line * FONT_HEIGHT, text[start + line], colours[start + line], null, Palette.DEFAULT,
false, 0, 0
);
}
}
public static void drawText( int x, int y, int start, String[] text, String[] colours )
public static void drawText( Matrix4f transform, IRenderTypeBuffer renderer, int x, int y, int start, String[] text, String[] colours )
{
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
GlStateManager.enableBlend();
GlStateManager.enableTexture();
GlStateManager.blendFuncSeparate( SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ZERO );
IVertexBuilder buffer = renderer.getBuffer( FixedWidthFontRenderer.TYPE );
for( int line = 0; line < LINES_PER_PAGE && line < text.length; line++ )
{
FixedWidthFontRenderer.drawString(
FixedWidthFontRenderer.drawString( transform, buffer,
x, y + line * FONT_HEIGHT,
new TextBuffer( text[start + line] ), new TextBuffer( colours[start + line] ),
null, Palette.DEFAULT, false, 0, 0
@@ -89,55 +85,46 @@ public final class PrintoutRenderer
}
}
public static void drawBorder( double x, double y, double z, int page, int pages, boolean isBook )
public static void drawBorder( Matrix4f transform, IRenderTypeBuffer renderer, float x, float y, float z, int page, int pages, boolean isBook )
{
GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f );
GlStateManager.enableBlend();
GlStateManager.enableTexture();
GlStateManager.blendFuncSeparate( SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ZERO );
Minecraft.getInstance().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;
IVertexBuilder buffer = renderer.getBuffer( Type.TYPE );
if( isBook )
{
// Border
double offset = offsetAt( pages );
final double left = x - 4 - offset;
final double right = x + X_SIZE + offset - 4;
float offset = offsetAt( pages );
float left = x - 4 - offset;
float 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 );
drawTexture( transform, buffer, left - 4, y - 8, z - 0.02f, COVER_X, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2 );
drawTexture( transform, buffer, right, y - 8, z - 0.02f, 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,
drawTexture( transform, buffer,
x - offset, y, z - 0.02f, X_SIZE + offset * 2, Y_SIZE,
COVER_X + COVER_SIZE / 2.0f, COVER_SIZE, COVER_SIZE, Y_SIZE
);
double borderX = left;
float 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 );
drawTexture( transform, buffer, borderX, y - 8, z - 0.02f, 0, COVER_Y, (float) thisWidth, COVER_SIZE );
drawTexture( transform, buffer, borderX, y + Y_SIZE - 4, z - 0.02f, 0, COVER_Y + COVER_SIZE, (float) thisWidth, COVER_SIZE );
borderX += thisWidth;
}
}
// Left half
drawTexture( buffer, x, y, z, X_FOLD_SIZE * 2, 0, X_SIZE / 2.0f, Y_SIZE );
drawTexture( transform, buffer, x, y, z, X_FOLD_SIZE * 2, 0, X_SIZE / 2.0f, Y_SIZE );
for( int n = 0; n <= leftPages; n++ )
{
drawTexture( buffer,
x - offsetAt( n ), y, z - 1e-3 * n,
drawTexture( transform, buffer,
x - offsetAt( n ), y, z - 1e-3f * n,
// Use the left "bold" fold for the outermost page
n == leftPages ? 0 : X_FOLD_SIZE, 0,
X_FOLD_SIZE, Y_SIZE
@@ -145,38 +132,54 @@ public final class PrintoutRenderer
}
// Right half
drawTexture( buffer, x + X_SIZE / 2.0f, y, z, X_FOLD_SIZE * 2 + X_SIZE / 2.0f, 0, X_SIZE / 2.0f, Y_SIZE );
drawTexture( transform, buffer, x + X_SIZE / 2.0f, y, z, X_FOLD_SIZE * 2 + X_SIZE / 2.0f, 0, X_SIZE / 2.0f, Y_SIZE );
for( int n = 0; n <= rightPages; n++ )
{
drawTexture( buffer,
x + (X_SIZE - X_FOLD_SIZE) + offsetAt( n ), y, z - 1e-3 * n,
drawTexture( transform, buffer,
x + (X_SIZE - X_FOLD_SIZE) + offsetAt( n ), y, z - 1e-3f * 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 )
private static void drawTexture( Matrix4f matrix, IVertexBuilder buffer, float x, float y, float z, float u, float v, float width, float 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();
buffer.pos( matrix, x, y + height, z ).tex( u / BG_SIZE, (v + height) / BG_SIZE ).endVertex();
buffer.pos( matrix, x + width, y + height, z ).tex( (u + width) / BG_SIZE, (v + height) / BG_SIZE ).endVertex();
buffer.pos( matrix, x + width, y, z ).tex( (u + width) / BG_SIZE, v / BG_SIZE ).endVertex();
buffer.pos( matrix, 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 )
private static void drawTexture( Matrix4f matrix, IVertexBuilder buffer, float x, float y, float z, float width, float height, float u, float v, float tWidth, float 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();
buffer.pos( matrix, x, y + height, z ).tex( u / BG_SIZE, (v + tHeight) / BG_SIZE ).endVertex();
buffer.pos( matrix, x + width, y + height, z ).tex( (u + tWidth) / BG_SIZE, (v + tHeight) / BG_SIZE ).endVertex();
buffer.pos( matrix, x + width, y, z ).tex( (u + tWidth) / BG_SIZE, v / BG_SIZE ).endVertex();
buffer.pos( matrix, x, y, z ).tex( u / BG_SIZE, v / BG_SIZE ).endVertex();
}
public static double offsetAt( int page )
public static float offsetAt( int page )
{
return 32 * (1 - Math.pow( 1.2, -page ));
return (float) (32 * (1 - Math.pow( 1.2, -page )));
}
private static final class Type extends RenderState
{
static final RenderType TYPE = RenderType.makeType(
"printout_background", DefaultVertexFormats.POSITION_TEX, GL11.GL_QUADS, 1024,
false, false, // useDelegate, needsSorting
RenderType.State.getBuilder()
.texture( new RenderState.TextureState( BG, false, false ) ) // blur, minimap
.alpha( DEFAULT_ALPHA )
.lightmap( LIGHTMAP_DISABLED )
.build( false )
);
private Type( String name, Runnable setup, Runnable destroy )
{
super( name, setup, destroy );
}
}
}

View File

@@ -1,140 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.modem.wired.BlockCable;
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
import dan200.computercraft.shared.peripheral.modem.wired.TileCable;
import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.util.BlockRenderLayer;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.world.World;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.model.data.EmptyModelData;
import org.lwjgl.opengl.GL11;
import javax.annotation.Nonnull;
import java.util.Random;
/**
* Render breaking animation only over part of a {@link TileCable}.
*/
public class TileEntityCableRenderer extends TileEntityRenderer<TileCable>
{
private static final ResourceLocation[] DESTROY_STAGES = new ResourceLocation[10];
private static final Random random = new Random();
static
{
for( int i = 0; i < DESTROY_STAGES.length; i++ )
{
DESTROY_STAGES[i] = new ResourceLocation( "block/destroy_stage_" + i );
}
}
@Override
public void render( @Nonnull TileCable te, double x, double y, double z, float partialTicks, int destroyStage )
{
if( destroyStage < 0 ) return;
BlockPos pos = te.getPos();
Minecraft mc = Minecraft.getInstance();
RayTraceResult hit = mc.objectMouseOver;
if( hit == null || hit.getType() != RayTraceResult.Type.BLOCK || !((BlockRayTraceResult) hit).getPos().equals( pos ) )
{
return;
}
World world = te.getWorld();
BlockState state = world.getBlockState( pos );
Block block = state.getBlock();
if( block != ComputerCraft.Blocks.cable ) return;
state = WorldUtil.isVecInside( CableShapes.getModemShape( state ), hit.getHitVec().subtract( pos.getX(), pos.getY(), pos.getZ() ) )
? block.getDefaultState().with( BlockCable.MODEM, state.get( BlockCable.MODEM ) )
: state.with( BlockCable.MODEM, CableModemVariant.None );
IBakedModel model = mc.getBlockRendererDispatcher().getModelForState( state );
preRenderDamagedBlocks();
ForgeHooksClient.setRenderLayer( block.getRenderLayer() );
// See BlockRendererDispatcher#renderBlockDamage
TextureAtlasSprite breakingTexture = mc.getTextureMap().getSprite( DESTROY_STAGES[destroyStage] );
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();
mc.getBlockRendererDispatcher().getBlockModelRenderer().renderModel(
world,
ForgeHooksClient.getDamageModel( model, breakingTexture, state, world, pos, 0 ),
state, pos, buffer, true, random, state.getPositionRandom( pos ), EmptyModelData.INSTANCE
);
ForgeHooksClient.setRenderLayer( BlockRenderLayer.SOLID );
buffer.setTranslation( 0, 0, 0 );
Tessellator.getInstance().draw();
postRenderDamagedBlocks();
}
/**
* Set up the state for rendering block-breaking progress.
*
* @see WorldRenderer#preRenderDamagedBlocks()
*/
private void preRenderDamagedBlocks()
{
GlStateManager.disableLighting();
GlStateManager.enableBlend();
GlStateManager.blendFuncSeparate( GlStateManager.SourceFactor.DST_COLOR, GlStateManager.DestFactor.SRC_COLOR, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO );
GlStateManager.enableBlend();
GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 0.5F );
GlStateManager.polygonOffset( -3.0F, -3.0F );
GlStateManager.enablePolygonOffset();
GlStateManager.alphaFunc( 516, 0.1F );
GlStateManager.enableAlphaTest();
GlStateManager.pushMatrix();
}
/**
* Tear down the state for rendering block-breaking progress.
*
* @see WorldRenderer#postRenderDamagedBlocks()
*/
private void postRenderDamagedBlocks()
{
GlStateManager.disableAlphaTest();
GlStateManager.polygonOffset( 0.0F, 0.0F );
GlStateManager.disablePolygonOffset();
GlStateManager.disablePolygonOffset();
GlStateManager.depthMask( true );
GlStateManager.popMatrix();
}
}

View File

@@ -5,34 +5,53 @@
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GLX;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import dan200.computercraft.client.FrameInfo;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.DirectionUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.*;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL31;
import javax.annotation.Nonnull;
import java.nio.ByteBuffer;
import static dan200.computercraft.shared.peripheral.monitor.TileMonitor.RENDER_MARGIN;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.*;
public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
{
/**
* {@link TileMonitor#RENDER_MARGIN}, but a tiny bit of additional padding to ensure that there is no space between
* the monitor frame and contents.
*/
private static final float MARGIN = (float) (TileMonitor.RENDER_MARGIN * 1.1);
private static ByteBuffer tboContents;
private static final Matrix4f IDENTITY = TransformationMatrix.identity().getMatrix();
public TileEntityMonitorRenderer( TileEntityRendererDispatcher rendererDispatcher )
{
super( rendererDispatcher );
}
@Override
public void render( @Nonnull TileMonitor monitor, double posX, double posY, double posZ, float f, int i )
public void render( @Nonnull TileMonitor monitor, float partialTicks, @Nonnull MatrixStack transform, @Nonnull IRenderTypeBuffer renderer, int lightmapCoord, int overlayLight )
{
// Render from the origin monitor
ClientMonitor originTerminal = monitor.getClientMonitor();
@@ -54,9 +73,6 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
originTerminal.lastRenderPos = monitorPos;
BlockPos originPos = origin.getPos();
posX += originPos.getX() - monitorPos.getX();
posY += originPos.getY() - monitorPos.getY();
posZ += originPos.getZ() - monitorPos.getZ();
// Determine orientation
Direction dir = origin.getDirection();
@@ -64,130 +80,158 @@ public class TileEntityMonitorRenderer extends TileEntityRenderer<TileMonitor>
float yaw = dir.getHorizontalAngle();
float pitch = DirectionUtil.toPitchAngle( front );
GlStateManager.pushMatrix();
// Setup initial transform
GlStateManager.translated( posX + 0.5, posY + 0.5, posZ + 0.5 );
GlStateManager.rotatef( -yaw, 0.0f, 1.0f, 0.0f );
GlStateManager.rotatef( pitch, 1.0f, 0.0f, 0.0f );
GlStateManager.translated(
-0.5 + TileMonitor.RENDER_BORDER + RENDER_MARGIN,
origin.getHeight() - 0.5 - (TileMonitor.RENDER_BORDER + RENDER_MARGIN) + 0,
transform.push();
transform.translate(
originPos.getX() - monitorPos.getX() + 0.5,
originPos.getY() - monitorPos.getY() + 0.5,
originPos.getZ() - monitorPos.getZ() + 0.5
);
transform.rotate( Vector3f.YN.rotationDegrees( yaw ) );
transform.rotate( Vector3f.XP.rotationDegrees( pitch ) );
transform.translate(
-0.5 + TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN,
origin.getHeight() - 0.5 - (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN) + 0,
0.5
);
double xSize = origin.getWidth() - 2.0 * (RENDER_MARGIN + TileMonitor.RENDER_BORDER);
double ySize = origin.getHeight() - 2.0 * (RENDER_MARGIN + TileMonitor.RENDER_BORDER);
// Get renderers
Minecraft mc = Minecraft.getInstance();
// Set up render state for monitors. We disable writing to the depth buffer (we draw a "blocker" later),
// and setup lighting so that we render with a glow.
GlStateManager.depthMask( false );
GLX.glMultiTexCoord2f( GLX.GL_TEXTURE1, 0xFFFF, 0xFFFF );
GlStateManager.disableLighting();
mc.gameRenderer.disableLightmap();
double xSize = origin.getWidth() - 2.0 * (TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER);
double ySize = origin.getHeight() - 2.0 * (TileMonitor.RENDER_MARGIN + TileMonitor.RENDER_BORDER);
// Draw the contents
Terminal terminal = originTerminal.getTerminal();
if( terminal != null )
{
// Draw a terminal
double xScale = xSize / (terminal.getWidth() * FixedWidthFontRenderer.FONT_WIDTH);
double yScale = ySize / (terminal.getHeight() * FixedWidthFontRenderer.FONT_HEIGHT);
int width = terminal.getWidth(), height = terminal.getHeight();
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
double xScale = xSize / pixelWidth;
double yScale = ySize / pixelHeight;
transform.push();
transform.scale( (float) xScale, (float) -yScale, 1.0f );
GlStateManager.pushMatrix();
GlStateManager.scaled( (float) xScale, (float) -yScale, 1.0f );
Matrix4f matrix = transform.getLast().getMatrix();
renderTerminal( originTerminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale) );
// Sneaky hack here: we get a buffer now in order to flush existing ones and set up the appropriate
// render state. I've no clue how well this'll work in future versions of Minecraft, but it does the trick
// for now.
IVertexBuilder buffer = renderer.getBuffer( FixedWidthFontRenderer.TYPE );
FixedWidthFontRenderer.TYPE.setupRenderState();
GlStateManager.popMatrix();
renderTerminal( matrix, originTerminal, (float) (MARGIN / xScale), (float) (MARGIN / yScale) );
// We don't draw the cursor with the VBO, as it's dynamic and so we'll end up refreshing far more than is
// reasonable.
FixedWidthFontRenderer.drawCursor( matrix, buffer, 0, 0, terminal, !originTerminal.isColour() );
transform.pop();
}
else
{
FixedWidthFontRenderer.drawEmptyTerminal(
transform.getLast().getMatrix(), renderer,
-MARGIN, MARGIN,
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
);
}
// Tear down render state for monitors.
GlStateManager.depthMask( true );
mc.gameRenderer.enableLightmap();
GlStateManager.enableLighting();
// Draw the depth blocker
GlStateManager.colorMask( false, false, false, false );
FixedWidthFontRenderer.drawBlocker(
transform.getLast().getMatrix(), renderer,
(float) -TileMonitor.RENDER_MARGIN, (float) TileMonitor.RENDER_MARGIN,
(float) (xSize + 2 * TileMonitor.RENDER_MARGIN), (float) -(ySize + TileMonitor.RENDER_MARGIN * 2)
);
GlStateManager.colorMask( true, true, true, true );
GlStateManager.popMatrix();
transform.pop();
}
private static void renderTerminal( ClientMonitor monitor, float xMargin, float yMargin )
private static void renderTerminal( Matrix4f matrix, ClientMonitor monitor, float xMargin, float yMargin )
{
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
Terminal terminal = monitor.getTerminal();
MonitorRenderer renderType = MonitorRenderer.current();
boolean redraw = monitor.pollTerminalChanged();
if( monitor.createBuffer( renderType ) ) redraw = true;
// Setup the buffers if needed. We get the renderer here, to avoid the (unlikely) race condition between
// creating the buffers and rendering.
MonitorRenderer renderer = MonitorRenderer.current();
if( monitor.createBuffer( renderer ) ) redraw = true;
FixedWidthFontRenderer.bindFont();
switch( renderer )
switch( renderType )
{
case TBO:
{
if( !MonitorTextureBufferShader.use() ) return;
int width = terminal.getWidth(), height = terminal.getHeight();
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
if( redraw )
{
int size = width * height * 3;
if( tboContents == null || tboContents.capacity() < size )
{
tboContents = GLAllocation.createDirectByteBuffer( size );
}
ByteBuffer monitorBuffer = tboContents;
monitorBuffer.clear();
for( int y = 0; y < height; y++ )
{
TextBuffer text = terminal.getLine( y ), textColour = terminal.getTextColourLine( y ), background = terminal.getBackgroundColourLine( y );
for( int x = 0; x < width; x++ )
{
monitorBuffer.put( (byte) (text.charAt( x ) & 0xFF) );
monitorBuffer.put( (byte) getColour( textColour.charAt( x ), Colour.WHITE ) );
monitorBuffer.put( (byte) getColour( background.charAt( x ), Colour.BLACK ) );
}
}
monitorBuffer.flip();
GlStateManager.bindBuffer( GL31.GL_TEXTURE_BUFFER, monitor.tboBuffer );
GlStateManager.bufferData( GL31.GL_TEXTURE_BUFFER, monitorBuffer, GL20.GL_STATIC_DRAW );
GlStateManager.bindBuffer( GL31.GL_TEXTURE_BUFFER, 0 );
}
// Nobody knows what they're doing!
GlStateManager.activeTexture( MonitorTextureBufferShader.TEXTURE_INDEX );
GL11.glBindTexture( GL31.GL_TEXTURE_BUFFER, monitor.tboTexture );
GlStateManager.activeTexture( GL13.GL_TEXTURE0 );
MonitorTextureBufferShader.setupUniform( matrix, width, height, terminal.getPalette(), !monitor.isColour() );
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder buffer = tessellator.getBuffer();
buffer.begin( GL11.GL_TRIANGLE_STRIP, DefaultVertexFormats.POSITION );
buffer.pos( -xMargin, -yMargin, 0 ).endVertex();
buffer.pos( -xMargin, pixelHeight + yMargin, 0 ).endVertex();
buffer.pos( pixelWidth + xMargin, -yMargin, 0 ).endVertex();
buffer.pos( pixelWidth + xMargin, pixelHeight + yMargin, 0 ).endVertex();
tessellator.draw();
GlStateManager.useProgram( 0 );
break;
}
case VBO:
{
VertexBuffer vbo = monitor.buffer;
if( redraw )
{
renderTerminalTo( monitor, buffer, xMargin, yMargin );
buffer.finishDrawing();
buffer.reset();
vbo.bufferData( buffer.getByteBuffer() );
Tessellator tessellator = Tessellator.getInstance();
BufferBuilder builder = tessellator.getBuffer();
builder.begin( FixedWidthFontRenderer.TYPE.getDrawMode(), FixedWidthFontRenderer.TYPE.getVertexFormat() );
FixedWidthFontRenderer.drawTerminalWithoutCursor(
IDENTITY, builder, 0, 0,
terminal, !monitor.isColour(), yMargin, yMargin, xMargin, xMargin
);
builder.finishDrawing();
vbo.upload( builder );
}
vbo.bindBuffer();
setupBufferFormat();
vbo.drawArrays( GL11.GL_TRIANGLES );
FixedWidthFontRenderer.TYPE.getVertexFormat().setupBufferState( 0L );
vbo.draw( matrix, FixedWidthFontRenderer.TYPE.getDrawMode() );
VertexBuffer.unbindBuffer();
FixedWidthFontRenderer.TYPE.getVertexFormat().clearBufferState();
break;
}
}
// We don't draw the cursor with a buffer, as it's dynamic and so we'll end up refreshing far more than is
// reasonable.
FixedWidthFontRenderer.begin( buffer );
FixedWidthFontRenderer.drawCursor( buffer, 0, 0, monitor.getTerminal(), !monitor.isColour() );
tessellator.draw();
}
private static void renderTerminalTo( ClientMonitor monitor, BufferBuilder buffer, float xMargin, float yMargin )
{
FixedWidthFontRenderer.begin( buffer );
FixedWidthFontRenderer.drawTerminalWithoutCursor(
buffer, 0, 0,
monitor.getTerminal(), !monitor.isColour(), yMargin, yMargin, xMargin, xMargin
);
}
public static void setupBufferFormat()
{
int stride = FixedWidthFontRenderer.POSITION_COLOR_TEX.getSize();
GlStateManager.vertexPointer( 3, GL11.GL_FLOAT, stride, 0 );
GlStateManager.enableClientState( GL11.GL_VERTEX_ARRAY );
GlStateManager.colorPointer( 4, GL11.GL_UNSIGNED_BYTE, stride, 12 );
GlStateManager.enableClientState( GL11.GL_COLOR_ARRAY );
GlStateManager.texCoordPointer( 2, GL11.GL_FLOAT, stride, 16 );
GlStateManager.enableClientState( GL11.GL_TEXTURE_COORD_ARRAY );
}
}

View File

@@ -5,7 +5,9 @@
*/
package dan200.computercraft.client.render;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.computer.core.ComputerFamily;
@@ -13,31 +15,26 @@ import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.Holiday;
import dan200.computercraft.shared.util.HolidayUtil;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.Atlases;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.Matrix4f;
import net.minecraft.client.renderer.Vector3f;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.ModelManager;
import net.minecraft.client.renderer.model.ModelResourceLocation;
import net.minecraft.client.renderer.texture.AtlasTexture;
import net.minecraft.client.renderer.tileentity.TileEntityRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockRayTraceResult;
import net.minecraft.util.math.RayTraceResult;
import net.minecraft.util.math.Vec3d;
import net.minecraftforge.client.ForgeHooksClient;
import net.minecraftforge.client.model.data.EmptyModelData;
import net.minecraftforge.client.model.pipeline.LightUtil;
import org.apache.commons.lang3.tuple.Pair;
import org.lwjgl.opengl.GL11;
import javax.vecmath.Matrix4f;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Random;
@@ -48,189 +45,147 @@ public class TileEntityTurtleRenderer extends TileEntityRenderer<TileTurtle>
private static final ModelResourceLocation COLOUR_TURTLE_MODEL = new ModelResourceLocation( "computercraft:turtle_colour", "inventory" );
private static final ModelResourceLocation ELF_OVERLAY_MODEL = new ModelResourceLocation( "computercraft:turtle_elf_overlay", "inventory" );
@Override
public void render( TileTurtle tileEntity, double posX, double posY, double posZ, float partialTicks, int breaking )
private final Random random = new Random( 0 );
public TileEntityTurtleRenderer( TileEntityRendererDispatcher renderDispatcher )
{
if( tileEntity != null ) renderTurtleAt( tileEntity, posX, posY, posZ, partialTicks );
super( renderDispatcher );
}
public static ModelResourceLocation getTurtleModel( ComputerFamily family, boolean coloured )
{
switch( family )
{
case Normal:
case NORMAL:
default:
return coloured ? COLOUR_TURTLE_MODEL : NORMAL_TURTLE_MODEL;
case Advanced:
case ADVANCED:
return coloured ? COLOUR_TURTLE_MODEL : ADVANCED_TURTLE_MODEL;
}
}
public static ModelResourceLocation getTurtleOverlayModel( ResourceLocation overlay, boolean christmas )
{
if( overlay != null )
{
return new ModelResourceLocation( overlay, "inventory" );
}
else if( christmas )
{
return ELF_OVERLAY_MODEL;
}
else
{
return null;
}
if( overlay != null ) return new ModelResourceLocation( overlay, "inventory" );
if( christmas ) return ELF_OVERLAY_MODEL;
return null;
}
private void renderTurtleAt( TileTurtle turtle, double posX, double posY, double posZ, float partialTicks )
@Override
public void render( @Nonnull TileTurtle turtle, float partialTicks, @Nonnull MatrixStack transform, @Nonnull IRenderTypeBuffer renderer, int lightmapCoord, int overlayLight )
{
// Render the label
String label = turtle.createProxy().getLabel();
RayTraceResult hit = rendererDispatcher.cameraHitResult;
RayTraceResult hit = renderDispatcher.cameraHitResult;
if( label != null && hit.getType() == RayTraceResult.Type.BLOCK && turtle.getPos().equals( ((BlockRayTraceResult) hit).getPos() ) )
{
setLightmapDisabled( true );
GameRenderer.drawNameplate(
getFontRenderer(), label,
(float) posX + 0.5F, (float) posY + 1.2F, (float) posZ + 0.5F, 0,
rendererDispatcher.renderInfo.getYaw(), rendererDispatcher.renderInfo.getPitch(), false
);
setLightmapDisabled( false );
Minecraft mc = Minecraft.getInstance();
FontRenderer font = renderDispatcher.fontRenderer;
transform.push();
transform.translate( 0.5, 1.2, 0.5 );
transform.rotate( mc.getRenderManager().getCameraOrientation() );
transform.scale( -0.025f, -0.025f, 0.025f );
Matrix4f matrix = transform.getLast().getMatrix();
int opacity = (int) (mc.gameSettings.getTextBackgroundOpacity( 0.25f ) * 255) << 24;
float width = -font.getStringWidth( label ) / 2.0f;
font.renderString( label, width, (float) 0, 0x20ffffff, false, matrix, renderer, true, opacity, lightmapCoord );
font.renderString( label, width, (float) 0, 0xffffffff, false, matrix, renderer, false, 0, lightmapCoord );
transform.pop();
}
GlStateManager.pushMatrix();
try
transform.push();
// Setup the transform.
Vec3d offset = turtle.getRenderOffset( partialTicks );
float yaw = turtle.getRenderYaw( partialTicks );
transform.translate( offset.x, offset.y, offset.z );
transform.translate( 0.5f, 0.5f, 0.5f );
transform.rotate( Vector3f.YP.rotationDegrees( 180.0f - yaw ) );
if( label != null && (label.equals( "Dinnerbone" ) || label.equals( "Grumm" )) )
{
BlockState state = turtle.getBlockState();
// Setup the transform
Vec3d offset = turtle.getRenderOffset( partialTicks );
float yaw = turtle.getRenderYaw( partialTicks );
GlStateManager.translated( posX + offset.x, posY + offset.y, posZ + offset.z );
// Render the turtle
GlStateManager.translatef( 0.5f, 0.5f, 0.5f );
GlStateManager.rotatef( 180.0f - yaw, 0.0f, 1.0f, 0.0f );
if( label != null && (label.equals( "Dinnerbone" ) || label.equals( "Grumm" )) )
{
// Flip the model and swap the cull face as winding order will have changed.
GlStateManager.scalef( 1.0f, -1.0f, 1.0f );
GlStateManager.cullFace( GlStateManager.CullFace.FRONT );
}
GlStateManager.translatef( -0.5f, -0.5f, -0.5f );
// Render the turtle
int colour = turtle.getColour();
ComputerFamily family = turtle.getFamily();
ResourceLocation overlay = turtle.getOverlay();
renderModel( state, getTurtleModel( family, colour != -1 ), colour == -1 ? null : new int[] { colour } );
// Render the overlay
ModelResourceLocation overlayModel = getTurtleOverlayModel(
overlay,
HolidayUtil.getCurrentHoliday() == Holiday.Christmas
);
if( overlayModel != null )
{
GlStateManager.disableCull();
GlStateManager.enableBlend();
GlStateManager.blendFunc( GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA );
try
{
renderModel( state, overlayModel, null );
}
finally
{
GlStateManager.disableBlend();
GlStateManager.enableCull();
}
}
// Render the upgrades
renderUpgrade( state, turtle, TurtleSide.Left, partialTicks );
renderUpgrade( state, turtle, TurtleSide.Right, partialTicks );
// Flip the model
transform.scale( 1.0f, -1.0f, 1.0f );
}
finally
transform.translate( -0.5f, -0.5f, -0.5f );
// Render the turtle
int colour = turtle.getColour();
ComputerFamily family = turtle.getFamily();
ResourceLocation overlay = turtle.getOverlay();
IVertexBuilder buffer = renderer.getBuffer( Atlases.getTranslucentCullBlockType() );
renderModel( transform, buffer, lightmapCoord, overlayLight, getTurtleModel( family, colour != -1 ), colour == -1 ? null : new int[] { colour } );
// Render the overlay
ModelResourceLocation overlayModel = getTurtleOverlayModel( overlay, HolidayUtil.getCurrentHoliday() == Holiday.CHRISTMAS );
if( overlayModel != null )
{
GlStateManager.popMatrix();
GlStateManager.cullFace( GlStateManager.CullFace.BACK );
renderModel( transform, buffer, lightmapCoord, overlayLight, overlayModel, null );
}
// Render the upgrades
renderUpgrade( transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.LEFT, partialTicks );
renderUpgrade( transform, buffer, lightmapCoord, overlayLight, turtle, TurtleSide.RIGHT, partialTicks );
transform.pop();
}
private void renderUpgrade( BlockState state, TileTurtle turtle, TurtleSide side, float f )
private void renderUpgrade( @Nonnull MatrixStack transform, @Nonnull IVertexBuilder renderer, int lightmapCoord, int overlayLight, TileTurtle turtle, TurtleSide side, float f )
{
ITurtleUpgrade upgrade = turtle.getUpgrade( side );
if( upgrade != null )
{
GlStateManager.pushMatrix();
try
{
float toolAngle = turtle.getToolRenderAngle( side, f );
GlStateManager.translatef( 0.0f, 0.5f, 0.5f );
GlStateManager.rotatef( -toolAngle, 1.0f, 0.0f, 0.0f );
GlStateManager.translatef( 0.0f, -0.5f, -0.5f );
if( upgrade == null ) return;
transform.push();
Pair<IBakedModel, Matrix4f> pair = upgrade.getModel( turtle.getAccess(), side );
if( pair != null )
{
if( pair.getRight() != null )
{
ForgeHooksClient.multiplyCurrentGlMatrix( pair.getRight() );
}
if( pair.getLeft() != null )
{
renderModel( state, pair.getLeft(), null );
}
}
}
finally
{
GlStateManager.popMatrix();
}
}
float toolAngle = turtle.getToolRenderAngle( side, f );
transform.translate( 0.0f, 0.5f, 0.5f );
transform.rotate( Vector3f.XN.rotationDegrees( toolAngle ) );
transform.translate( 0.0f, -0.5f, -0.5f );
TransformedModel model = upgrade.getModel( turtle.getAccess(), side );
model.getMatrix().push( transform );
renderModel( transform, renderer, lightmapCoord, overlayLight, model.getModel(), null );
transform.pop();
transform.pop();
}
private void renderModel( BlockState state, ModelResourceLocation modelLocation, int[] tints )
private void renderModel( @Nonnull MatrixStack transform, @Nonnull IVertexBuilder renderer, int lightmapCoord, int overlayLight, ModelResourceLocation modelLocation, int[] tints )
{
Minecraft mc = Minecraft.getInstance();
ModelManager modelManager = mc.getItemRenderer().getItemModelMesher().getModelManager();
renderModel( state, modelManager.getModel( modelLocation ), tints );
ModelManager modelManager = Minecraft.getInstance().getItemRenderer().getItemModelMesher().getModelManager();
renderModel( transform, renderer, lightmapCoord, overlayLight, modelManager.getModel( modelLocation ), tints );
}
private void renderModel( BlockState state, IBakedModel model, int[] tints )
private void renderModel( @Nonnull MatrixStack transform, @Nonnull IVertexBuilder renderer, int lightmapCoord, int overlayLight, IBakedModel model, int[] tints )
{
Random random = new Random( 0 );
Tessellator tessellator = Tessellator.getInstance();
rendererDispatcher.textureManager.bindTexture( AtlasTexture.LOCATION_BLOCKS_TEXTURE );
renderQuads( tessellator, model.getQuads( state, null, random, EmptyModelData.INSTANCE ), tints );
random.setSeed( 0 );
renderQuads( transform, renderer, lightmapCoord, overlayLight, model.getQuads( null, null, random, EmptyModelData.INSTANCE ), tints );
for( Direction facing : DirectionUtil.FACINGS )
{
renderQuads( tessellator, model.getQuads( state, facing, random, EmptyModelData.INSTANCE ), tints );
renderQuads( transform, renderer, lightmapCoord, overlayLight, model.getQuads( null, facing, random, EmptyModelData.INSTANCE ), tints );
}
}
private static void renderQuads( Tessellator tessellator, List<BakedQuad> quads, int[] tints )
private static void renderQuads( @Nonnull MatrixStack transform, @Nonnull IVertexBuilder buffer, int lightmapCoord, int overlayLight, List<BakedQuad> quads, int[] tints )
{
BufferBuilder buffer = tessellator.getBuffer();
VertexFormat format = DefaultVertexFormats.ITEM;
buffer.begin( GL11.GL_QUADS, format );
for( BakedQuad quad : quads )
MatrixStack.Entry matrix = transform.getLast();
for( BakedQuad bakedquad : quads )
{
VertexFormat quadFormat = quad.getFormat();
if( quadFormat != format )
int tint = -1;
if( tints != null && bakedquad.hasTintIndex() )
{
tessellator.draw();
format = quadFormat;
buffer.begin( GL11.GL_QUADS, format );
int idx = bakedquad.getTintIndex();
if( idx >= 0 && idx < tints.length ) tint = tints[bakedquad.getTintIndex()];
}
int colour = 0xFFFFFFFF;
if( quad.hasTintIndex() && tints != null )
{
int index = quad.getTintIndex();
if( index >= 0 && index < tints.length ) colour = tints[index] | 0xFF000000;
}
LightUtil.renderQuadColor( buffer, quad, colour );
float f = (float) (tint >> 16 & 255) / 255.0F;
float f1 = (float) (tint >> 8 & 255) / 255.0F;
float f2 = (float) (tint & 255) / 255.0F;
buffer.addVertexData( matrix, bakedquad, f, f1, f2, lightmapCoord, overlayLight, true );
}
tessellator.draw();
}
}

View File

@@ -5,28 +5,27 @@
*/
package dan200.computercraft.client.render;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.mojang.datafixers.util.Pair;
import dan200.computercraft.ComputerCraft;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.IUnbakedModel;
import net.minecraft.client.renderer.model.ModelBakery;
import net.minecraft.client.renderer.texture.ISprite;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.resources.IResourceManager;
import net.minecraft.util.JSONUtils;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.model.ICustomModelLoader;
import net.minecraftforge.client.model.IModelConfiguration;
import net.minecraftforge.client.model.IModelLoader;
import net.minecraftforge.client.model.geometry.IModelGeometry;
import javax.annotation.Nonnull;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public final class TurtleModelLoader implements ICustomModelLoader
public final class TurtleModelLoader implements IModelLoader<TurtleModelLoader.TurtleModel>
{
private static final ResourceLocation NORMAL_TURTLE_MODEL = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_normal" );
private static final ResourceLocation ADVANCED_TURTLE_MODEL = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_advanced" );
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_colour" );
public static final TurtleModelLoader INSTANCE = new TurtleModelLoader();
@@ -40,32 +39,15 @@ public final class TurtleModelLoader implements ICustomModelLoader
{
}
@Override
public boolean accepts( @Nonnull ResourceLocation name )
{
return name.getNamespace().equals( ComputerCraft.MOD_ID )
&& (name.getPath().equals( "item/turtle_normal" ) || name.getPath().equals( "item/turtle_advanced" ));
}
@Nonnull
@Override
public IUnbakedModel loadModel( @Nonnull ResourceLocation name )
public TurtleModel read( @Nonnull JsonDeserializationContext deserializationContext, @Nonnull JsonObject modelContents )
{
if( name.getNamespace().equals( ComputerCraft.MOD_ID ) )
{
switch( name.getPath() )
{
case "item/turtle_normal":
return new TurtleModel( NORMAL_TURTLE_MODEL );
case "item/turtle_advanced":
return new TurtleModel( ADVANCED_TURTLE_MODEL );
}
}
throw new IllegalStateException( "Loader does not accept " + name );
ResourceLocation model = new ResourceLocation( JSONUtils.getString( modelContents, "model" ) );
return new TurtleModel( model );
}
private static final class TurtleModel implements IUnbakedModel
public static final class TurtleModel implements IModelGeometry<TurtleModel>
{
private final ResourceLocation family;
@@ -74,29 +56,21 @@ public final class TurtleModelLoader implements ICustomModelLoader
this.family = family;
}
@Nonnull
@Override
public Collection<ResourceLocation> getDependencies()
public Collection<Material> getTextures( IModelConfiguration owner, Function<ResourceLocation, IUnbakedModel> modelGetter, Set<Pair<String, String>> missingTextureErrors )
{
return Arrays.asList( family, COLOUR_TURTLE_MODEL );
Set<Material> materials = new HashSet<>();
materials.addAll( modelGetter.apply( family ).getTextures( modelGetter, missingTextureErrors ) );
materials.addAll( modelGetter.apply( COLOUR_TURTLE_MODEL ).getTextures( modelGetter, missingTextureErrors ) );
return materials;
}
@Nonnull
@Override
public Collection<ResourceLocation> getTextures( @Nonnull Function<ResourceLocation, IUnbakedModel> modelGetter, @Nonnull Set<String> missingTextureErrors )
{
return getDependencies().stream()
.flatMap( x -> modelGetter.apply( x ).getTextures( modelGetter, missingTextureErrors ).stream() )
.collect( Collectors.toSet() );
}
@Nonnull
@Override
public IBakedModel bake( @Nonnull ModelBakery bakery, @Nonnull Function<ResourceLocation, TextureAtlasSprite> spriteGetter, @Nonnull ISprite sprite, @Nonnull VertexFormat format )
public IBakedModel bake( IModelConfiguration owner, ModelBakery bakery, Function<Material, TextureAtlasSprite> spriteGetter, IModelTransform transform, ItemOverrideList overrides, ResourceLocation modelLocation )
{
return new TurtleSmartItemModel(
bakery.getBakedModel( family, sprite, spriteGetter, format ),
bakery.getBakedModel( COLOUR_TURTLE_MODEL, sprite, spriteGetter, format )
bakery.getBakedModel( family, transform, spriteGetter ),
bakery.getBakedModel( COLOUR_TURTLE_MODEL, transform, spriteGetter )
);
}
}

View File

@@ -5,7 +5,9 @@
*/
package dan200.computercraft.client.render;
import dan200.computercraft.api.client.TransformedModel;
import net.minecraft.block.BlockState;
import net.minecraft.client.renderer.TransformationMatrix;
import net.minecraft.client.renderer.model.BakedQuad;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.ItemOverrideList;
@@ -13,32 +15,29 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.util.Direction;
import net.minecraftforge.client.model.data.EmptyModelData;
import net.minecraftforge.client.model.data.IModelData;
import net.minecraftforge.client.model.pipeline.BakedQuadBuilder;
import net.minecraftforge.client.model.pipeline.TRSRTransformer;
import javax.annotation.Nonnull;
import javax.vecmath.Matrix4f;
import java.util.*;
public class TurtleMultiModel implements IBakedModel
{
private final IBakedModel m_baseModel;
private final IBakedModel m_overlayModel;
private final Matrix4f m_generalTransform;
private final IBakedModel m_leftUpgradeModel;
private final Matrix4f m_leftUpgradeTransform;
private final IBakedModel m_rightUpgradeModel;
private final Matrix4f m_rightUpgradeTransform;
private final TransformationMatrix m_generalTransform;
private final TransformedModel m_leftUpgradeModel;
private final TransformedModel m_rightUpgradeModel;
private List<BakedQuad> m_generalQuads = null;
private Map<Direction, List<BakedQuad>> m_faceQuads = new EnumMap<>( Direction.class );
public TurtleMultiModel( IBakedModel baseModel, IBakedModel overlayModel, Matrix4f generalTransform, IBakedModel leftUpgradeModel, Matrix4f leftUpgradeTransform, IBakedModel rightUpgradeModel, Matrix4f rightUpgradeTransform )
public TurtleMultiModel( IBakedModel baseModel, IBakedModel overlayModel, TransformationMatrix generalTransform, TransformedModel leftUpgradeModel, TransformedModel rightUpgradeModel )
{
// Get the models
m_baseModel = baseModel;
m_overlayModel = overlayModel;
m_leftUpgradeModel = leftUpgradeModel;
m_leftUpgradeTransform = leftUpgradeTransform;
m_rightUpgradeModel = rightUpgradeModel;
m_rightUpgradeTransform = rightUpgradeTransform;
m_generalTransform = generalTransform;
}
@@ -69,30 +68,22 @@ public class TurtleMultiModel implements IBakedModel
private List<BakedQuad> buildQuads( BlockState state, Direction side, Random rand )
{
ArrayList<BakedQuad> quads = new ArrayList<>();
ModelTransformer.transformQuadsTo( quads, m_baseModel.getQuads( state, side, rand, EmptyModelData.INSTANCE ), m_generalTransform );
transformQuadsTo( quads, m_baseModel.getQuads( state, side, rand, EmptyModelData.INSTANCE ), m_generalTransform );
if( m_overlayModel != null )
{
ModelTransformer.transformQuadsTo( quads, m_overlayModel.getQuads( state, side, rand, EmptyModelData.INSTANCE ), m_generalTransform );
transformQuadsTo( quads, m_overlayModel.getQuads( state, side, rand, EmptyModelData.INSTANCE ), 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, EmptyModelData.INSTANCE ), upgradeTransform );
TransformationMatrix upgradeTransform = m_generalTransform.compose( m_leftUpgradeModel.getMatrix() );
transformQuadsTo( quads, m_leftUpgradeModel.getModel().getQuads( state, side, rand, EmptyModelData.INSTANCE ), 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, EmptyModelData.INSTANCE ), upgradeTransform );
TransformationMatrix upgradeTransform = m_generalTransform.compose( m_rightUpgradeModel.getMatrix() );
transformQuadsTo( quads, m_rightUpgradeModel.getModel().getQuads( state, side, rand, EmptyModelData.INSTANCE ), upgradeTransform );
}
quads.trimToSize();
return quads;
@@ -116,6 +107,12 @@ public class TurtleMultiModel implements IBakedModel
return m_baseModel.isBuiltInRenderer();
}
@Override
public boolean func_230044_c_()
{
return m_baseModel.func_230044_c_();
}
@Nonnull
@Override
@Deprecated
@@ -138,4 +135,15 @@ public class TurtleMultiModel implements IBakedModel
{
return ItemOverrideList.EMPTY;
}
private void transformQuadsTo( List<BakedQuad> output, List<BakedQuad> quads, TransformationMatrix transform )
{
for( BakedQuad quad : quads )
{
BakedQuadBuilder builder = new BakedQuadBuilder();
TRSRTransformer transformer = new TRSRTransformer( builder, transform );
quad.pipe( transformer );
output.add( builder.build() );
}
}
}

View File

@@ -6,14 +6,15 @@
package dan200.computercraft.client.render;
import dan200.computercraft.ComputerCraft;
import com.mojang.blaze3d.matrix.MatrixStack;
import dan200.computercraft.client.gui.GuiComputer;
import dan200.computercraft.shared.turtle.core.TurtlePlayer;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.EntityRendererManager;
import net.minecraft.util.ResourceLocation;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class TurtlePlayerRenderer extends EntityRenderer<TurtlePlayer>
{
@@ -22,16 +23,15 @@ public class TurtlePlayerRenderer extends EntityRenderer<TurtlePlayer>
super( renderManager );
}
@Nonnull
@Override
public void doRender( @Nonnull TurtlePlayer entity, double x, double y, double z, float entityYaw, float partialTicks )
public ResourceLocation getEntityTexture( @Nonnull TurtlePlayer entity )
{
ComputerCraft.log.error( "Rendering TurtlePlayer on the client side, at {}", entity.getPosition() );
return GuiComputer.BACKGROUND_NORMAL;
}
@Nullable
@Override
protected ResourceLocation getEntityTexture( @Nonnull TurtlePlayer entity )
public void render( @Nonnull TurtlePlayer entityIn, float entityYaw, float partialTicks, @Nonnull MatrixStack transform, @Nonnull IRenderTypeBuffer buffer, int packedLightIn )
{
return null;
}
}

View File

@@ -6,6 +6,8 @@
package dan200.computercraft.client.render;
import com.google.common.base.Objects;
import com.mojang.blaze3d.matrix.MatrixStack;
import dan200.computercraft.api.client.TransformedModel;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.turtle.items.ItemTurtle;
@@ -13,6 +15,7 @@ import dan200.computercraft.shared.util.Holiday;
import dan200.computercraft.shared.util.HolidayUtil;
import net.minecraft.block.BlockState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.TransformationMatrix;
import net.minecraft.client.renderer.model.*;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.entity.LivingEntity;
@@ -21,28 +24,25 @@ import net.minecraft.util.Direction;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.model.data.IModelData;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.vecmath.Matrix4f;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
public class TurtleSmartItemModel implements IBakedModel
{
private static final Matrix4f s_identity, s_flip;
private static final TransformationMatrix identity, flip;
static
{
s_identity = new Matrix4f();
s_identity.setIdentity();
MatrixStack stack = new MatrixStack();
stack.scale( 0, -1, 0 );
stack.translate( 0, 0, 1 );
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.
identity = TransformationMatrix.identity();
flip = new TransformationMatrix( stack.getLast().getMatrix() );
}
private static class TurtleModelCombination
@@ -97,15 +97,14 @@ public class TurtleSmartItemModel implements IBakedModel
private final IBakedModel familyModel;
private final IBakedModel colourModel;
private HashMap<TurtleModelCombination, IBakedModel> m_cachedModels;
private ItemOverrideList m_overrides;
private final HashMap<TurtleModelCombination, IBakedModel> m_cachedModels = new HashMap<>();
private final ItemOverrideList m_overrides;
public TurtleSmartItemModel( IBakedModel familyModel, IBakedModel colourModel )
{
this.familyModel = familyModel;
this.colourModel = colourModel;
m_cachedModels = new HashMap<>();
m_overrides = new ItemOverrideList()
{
@Nonnull
@@ -114,10 +113,10 @@ public class TurtleSmartItemModel implements IBakedModel
{
ItemTurtle turtle = (ItemTurtle) stack.getItem();
int colour = turtle.getColour( stack );
ITurtleUpgrade leftUpgrade = turtle.getUpgrade( stack, TurtleSide.Left );
ITurtleUpgrade rightUpgrade = turtle.getUpgrade( stack, TurtleSide.Right );
ITurtleUpgrade leftUpgrade = turtle.getUpgrade( stack, TurtleSide.LEFT );
ITurtleUpgrade rightUpgrade = turtle.getUpgrade( stack, TurtleSide.RIGHT );
ResourceLocation overlay = turtle.getOverlay( stack );
boolean christmas = HolidayUtil.getCurrentHoliday() == Holiday.Christmas;
boolean christmas = HolidayUtil.getCurrentHoliday() == Holiday.CHRISTMAS;
String label = turtle.getLabel( stack );
boolean flip = label != null && (label.equals( "Dinnerbone" ) || label.equals( "Grumm" ));
TurtleModelCombination combo = new TurtleModelCombination( colour != -1, leftUpgrade, rightUpgrade, overlay, christmas, flip );
@@ -144,25 +143,10 @@ public class TurtleSmartItemModel implements IBakedModel
IBakedModel baseModel = combo.m_colour ? colourModel : familyModel;
IBakedModel overlayModel = overlayModelLocation != null ? modelManager.getModel( overlayModelLocation ) : null;
Matrix4f transform = combo.m_flip ? s_flip : s_identity;
Pair<IBakedModel, Matrix4f> leftModel = combo.m_leftUpgrade != null ? combo.m_leftUpgrade.getModel( null, TurtleSide.Left ) : null;
Pair<IBakedModel, Matrix4f> rightModel = combo.m_rightUpgrade != null ? combo.m_rightUpgrade.getModel( null, TurtleSide.Right ) : null;
if( leftModel != null && rightModel != null )
{
return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel.getLeft(), leftModel.getRight(), rightModel.getLeft(), rightModel.getRight() );
}
else if( leftModel != null )
{
return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel.getLeft(), leftModel.getRight(), null, null );
}
else if( rightModel != null )
{
return new TurtleMultiModel( baseModel, overlayModel, transform, null, null, rightModel.getLeft(), rightModel.getRight() );
}
else
{
return new TurtleMultiModel( baseModel, overlayModel, transform, null, null, null, null );
}
TransformationMatrix transform = combo.m_flip ? flip : identity;
TransformedModel leftModel = combo.m_leftUpgrade != null ? combo.m_leftUpgrade.getModel( null, TurtleSide.LEFT ) : null;
TransformedModel rightModel = combo.m_rightUpgrade != null ? combo.m_rightUpgrade.getModel( null, TurtleSide.RIGHT ) : null;
return new TurtleMultiModel( baseModel, overlayModel, transform, leftModel, rightModel );
}
@Nonnull
@@ -199,6 +183,12 @@ public class TurtleSmartItemModel implements IBakedModel
return familyModel.isBuiltInRenderer();
}
@Override
public boolean func_230044_c_()
{
return familyModel.func_230044_c_();
}
@Nonnull
@Override
@Deprecated

View File

@@ -1,181 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import com.google.common.net.InetAddresses;
import dan200.computercraft.ComputerCraft;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
* Used to determine whether a domain or IP address matches a series of patterns.
*/
public class AddressPredicate
{
private static final class HostRange
{
private final byte[] min;
private final byte[] max;
private HostRange( byte[] min, byte[] max )
{
this.min = min;
this.max = max;
}
public boolean contains( InetAddress address )
{
byte[] entry = address.getAddress();
if( entry.length != min.length ) return false;
for( int i = 0; i < entry.length; i++ )
{
int value = 0xFF & entry[i];
if( value < (0xFF & min[i]) || value > (0xFF & max[i]) ) return false;
}
return true;
}
}
private final List<Pattern> wildcards;
private final List<HostRange> ranges;
public AddressPredicate( String... filters )
{
this( Arrays.asList( filters ) );
}
public AddressPredicate( Iterable<? extends String> filters )
{
List<Pattern> wildcards = this.wildcards = new ArrayList<>();
List<HostRange> ranges = this.ranges = new ArrayList<>();
for( String filter : filters )
{
int cidr = filter.indexOf( '/' );
if( cidr >= 0 )
{
String addressStr = filter.substring( 0, cidr );
String prefixSizeStr = filter.substring( cidr + 1 );
int prefixSize;
try
{
prefixSize = Integer.parseInt( prefixSizeStr );
}
catch( NumberFormatException e )
{
ComputerCraft.log.error(
"Malformed http whitelist/blacklist entry '{}': Cannot extract size of CIDR mask from '{}'.",
filter, prefixSizeStr
);
continue;
}
InetAddress address;
try
{
address = InetAddresses.forString( addressStr );
}
catch( IllegalArgumentException e )
{
ComputerCraft.log.error(
"Malformed http whitelist/blacklist entry '{}': Cannot extract IP address from '{}'.",
filter, prefixSizeStr
);
continue;
}
// Mask the bytes of the IP address.
byte[] minBytes = address.getAddress(), maxBytes = address.getAddress();
int size = prefixSize;
for( int i = 0; i < minBytes.length; i++ )
{
if( size <= 0 )
{
minBytes[i] &= 0;
maxBytes[i] |= 0xFF;
}
else if( size < 8 )
{
minBytes[i] &= 0xFF << (8 - size);
maxBytes[i] |= ~(0xFF << (8 - size));
}
size -= 8;
}
ranges.add( new HostRange( minBytes, maxBytes ) );
}
else
{
wildcards.add( Pattern.compile( "^\\Q" + filter.replaceAll( "\\*", "\\\\E.*\\\\Q" ) + "\\E$" ) );
}
}
}
/**
* Determine whether a host name matches a series of patterns.
*
* This is intended to allow early exiting, before one has to look up the IP address. You should use
* {@link #matches(InetAddress)} instead of/in addition to this one.
*
* @param domain The domain to match.
* @return Whether the patterns were matched.
*/
public boolean matches( String domain )
{
for( Pattern domainPattern : wildcards )
{
if( domainPattern.matcher( domain ).matches() ) return true;
}
return false;
}
private boolean matchesAddress( InetAddress address )
{
String addressString = address.getHostAddress();
for( Pattern domainPattern : wildcards )
{
if( domainPattern.matcher( addressString ).matches() ) return true;
}
for( HostRange range : ranges )
{
if( range.contains( address ) ) return true;
}
return false;
}
/**
* Determine whether the given address matches a series of patterns.
*
* @param address The address to check.
* @return Whether it matches any of these patterns.
*/
public boolean matches( InetAddress address )
{
// Match the host name
String host = address.getHostName();
if( host != null && matches( host ) ) return true;
// Match the normal address
if( matchesAddress( address ) ) return true;
// If we're an IPv4 address in disguise then let's check that.
return address instanceof Inet6Address && InetAddresses.is6to4Address( (Inet6Address) address )
&& matchesAddress( InetAddresses.get6to4IPv4Address( (Inet6Address) address ) );
}
}

View File

@@ -1,117 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
/**
* A stub for any mods which depended on this version of the argument helper.
*
* @deprecated Use {@link dan200.computercraft.api.lua.ArgumentHelper}.
*/
@Deprecated
public final class ArgumentHelper
{
private ArgumentHelper()
{
}
@Nonnull
public static String getType( @Nullable Object type )
{
return dan200.computercraft.api.lua.ArgumentHelper.getType( type );
}
@Nonnull
public static LuaException badArgument( int index, @Nonnull String expected, @Nullable Object actual )
{
return dan200.computercraft.api.lua.ArgumentHelper.badArgumentOf( index, expected, actual );
}
@Nonnull
public static LuaException badArgument( int index, @Nonnull String expected, @Nonnull String actual )
{
return dan200.computercraft.api.lua.ArgumentHelper.badArgument( index, expected, actual );
}
public static double getNumber( @Nonnull Object[] args, int index ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.getDouble( args, index );
}
public static int getInt( @Nonnull Object[] args, int index ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.getInt( args, index );
}
public static long getLong( @Nonnull Object[] args, int index ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.getLong( args, index );
}
public static double getReal( @Nonnull Object[] args, int index ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.getFiniteDouble( args, index );
}
public static boolean getBoolean( @Nonnull Object[] args, int index ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.getBoolean( args, index );
}
@Nonnull
public static String getString( @Nonnull Object[] args, int index ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.getString( args, index );
}
@Nonnull
@SuppressWarnings( "unchecked" )
public static Map<Object, Object> getTable( @Nonnull Object[] args, int index ) throws LuaException
{
return (Map<Object, Object>) dan200.computercraft.api.lua.ArgumentHelper.getTable( args, index );
}
public static double optNumber( @Nonnull Object[] args, int index, double def ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.optDouble( args, index, def );
}
public static int optInt( @Nonnull Object[] args, int index, int def ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.optInt( args, index, def );
}
public static long optLong( @Nonnull Object[] args, int index, long def ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.optLong( args, index, def );
}
public static double optReal( @Nonnull Object[] args, int index, double def ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.optFiniteDouble( args, index, def );
}
public static boolean optBoolean( @Nonnull Object[] args, int index, boolean def ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.optBoolean( args, index, def );
}
public static String optString( @Nonnull Object[] args, int index, String def ) throws LuaException
{
return dan200.computercraft.api.lua.ArgumentHelper.optString( args, index, def );
}
@SuppressWarnings( "unchecked" )
public static Map<Object, Object> optTable( @Nonnull Object[] args, int index, Map<Object, Object> def ) throws LuaException
{
return (Map<Object, Object>) dan200.computercraft.api.lua.ArgumentHelper.optTable( args, index, def );
}
}

View File

@@ -13,7 +13,6 @@ 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.Objects;
import java.util.Set;
@@ -117,13 +116,13 @@ public abstract class ComputerAccess implements IComputerAccess
}
@Override
public void queueEvent( @Nonnull final String event, final Object[] arguments )
public void queueEvent( @Nonnull String event, Object... arguments )
{
Objects.requireNonNull( event, "event cannot be null" );
m_environment.queueEvent( event, arguments );
}
@Nullable
@Nonnull
@Override
public IWorkMonitor getMainThreadMonitor()
{

View File

@@ -6,8 +6,8 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.apis.handles.BinaryReadableHandle;
import dan200.computercraft.core.apis.handles.BinaryWritableHandle;
import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
@@ -17,7 +17,6 @@ import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.filesystem.FileSystemWrapper;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.nio.channels.ReadableByteChannel;
@@ -29,17 +28,14 @@ import java.util.Map;
import java.util.OptionalLong;
import java.util.function.Function;
import static dan200.computercraft.api.lua.ArgumentHelper.getString;
public class FSAPI implements ILuaAPI
{
private IAPIEnvironment m_env;
private FileSystem m_fileSystem;
private final IAPIEnvironment environment;
private FileSystem fileSystem = null;
public FSAPI( IAPIEnvironment env )
{
m_env = env;
m_fileSystem = null;
environment = env;
}
@Override
@@ -51,329 +47,280 @@ public class FSAPI implements ILuaAPI
@Override
public void startup()
{
m_fileSystem = m_env.getFileSystem();
fileSystem = environment.getFileSystem();
}
@Override
public void shutdown()
{
m_fileSystem = null;
fileSystem = null;
}
@Nonnull
@Override
public String[] getMethodNames()
@LuaFunction
public final String[] list( String path ) throws LuaException
{
return new String[] {
"list",
"combine",
"getName",
"getSize",
"exists",
"isDir",
"isReadOnly",
"makeDir",
"move",
"copy",
"delete",
"open",
"getDrive",
"getFreeSpace",
"find",
"getDir",
"getCapacity",
"attributes",
};
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
environment.addTrackingChange( TrackingField.FS_OPS );
try
{
case 0:
return fileSystem.list( path );
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final String combine( String pathA, String pathB )
{
return fileSystem.combine( pathA, pathB );
}
@LuaFunction
public final String getName( String path )
{
return FileSystem.getName( path );
}
@LuaFunction
public final String getDir( String path )
{
return FileSystem.getDirectory( path );
}
@LuaFunction
public final long getSize( String path ) throws LuaException
{
try
{
return fileSystem.getSize( path );
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final boolean exists( String path )
{
try
{
return fileSystem.exists( path );
}
catch( FileSystemException e )
{
return false;
}
}
@LuaFunction
public final boolean isDir( String path )
{
try
{
return fileSystem.isDir( path );
}
catch( FileSystemException e )
{
return false;
}
}
@LuaFunction
public final boolean isReadOnly( String path )
{
try
{
return fileSystem.isReadOnly( path );
}
catch( FileSystemException e )
{
return false;
}
}
@LuaFunction
public final void makeDir( String path ) throws LuaException
{
try
{
environment.addTrackingChange( TrackingField.FS_OPS );
fileSystem.makeDir( path );
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final void move( String path, String dest ) throws LuaException
{
try
{
environment.addTrackingChange( TrackingField.FS_OPS );
fileSystem.move( path, dest );
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final void copy( String path, String dest ) throws LuaException
{
try
{
environment.addTrackingChange( TrackingField.FS_OPS );
fileSystem.copy( path, dest );
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final void delete( String path ) throws LuaException
{
try
{
environment.addTrackingChange( TrackingField.FS_OPS );
fileSystem.delete( path );
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final Object[] open( String path, String mode ) throws LuaException
{
environment.addTrackingChange( TrackingField.FS_OPS );
try
{
switch( mode )
{
// list
String path = getString( args, 0 );
m_env.addTrackingChange( TrackingField.FS_OPS );
try
case "r":
{
return new Object[] { m_fileSystem.list( path ) };
// Open the file for reading, then create a wrapper around the reader
FileSystemWrapper<BufferedReader> reader = fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 );
return new Object[] { new EncodedReadableHandle( reader.get(), reader ) };
}
catch( FileSystemException e )
case "w":
{
throw new LuaException( e.getMessage() );
// Open the file for writing, then create a wrapper around the writer
FileSystemWrapper<BufferedWriter> writer = fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 );
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
}
case "a":
{
// Open the file for appending, then create a wrapper around the writer
FileSystemWrapper<BufferedWriter> writer = fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 );
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
}
case "rb":
{
// Open the file for binary reading, then create a wrapper around the reader
FileSystemWrapper<ReadableByteChannel> reader = fileSystem.openForRead( path, Function.identity() );
return new Object[] { BinaryReadableHandle.of( reader.get(), reader ) };
}
case "wb":
{
// Open the file for binary writing, then create a wrapper around the writer
FileSystemWrapper<WritableByteChannel> writer = fileSystem.openForWrite( path, false, Function.identity() );
return new Object[] { BinaryWritableHandle.of( writer.get(), writer ) };
}
case "ab":
{
// Open the file for binary appending, then create a wrapper around the reader
FileSystemWrapper<WritableByteChannel> writer = fileSystem.openForWrite( path, true, Function.identity() );
return new Object[] { BinaryWritableHandle.of( writer.get(), writer ) };
}
default:
throw new LuaException( "Unsupported mode" );
}
case 1:
{
// combine
String pathA = getString( args, 0 );
String pathB = getString( args, 1 );
return new Object[] { m_fileSystem.combine( pathA, pathB ) };
}
case 2:
{
// getName
String path = getString( args, 0 );
return new Object[] { FileSystem.getName( path ) };
}
case 3:
{
// getSize
String path = getString( args, 0 );
try
{
return new Object[] { m_fileSystem.getSize( path ) };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 4:
{
// exists
String path = getString( args, 0 );
try
{
return new Object[] { m_fileSystem.exists( path ) };
}
catch( FileSystemException e )
{
return new Object[] { false };
}
}
case 5:
{
// isDir
String path = getString( args, 0 );
try
{
return new Object[] { m_fileSystem.isDir( path ) };
}
catch( FileSystemException e )
{
return new Object[] { false };
}
}
case 6:
{
// isReadOnly
String path = getString( args, 0 );
try
{
return new Object[] { m_fileSystem.isReadOnly( path ) };
}
catch( FileSystemException e )
{
return new Object[] { false };
}
}
case 7:
{
// makeDir
String path = getString( args, 0 );
try
{
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.makeDir( path );
return null;
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 8:
{
// move
String path = getString( args, 0 );
String dest = getString( args, 1 );
try
{
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.move( path, dest );
return null;
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 9:
{
// copy
String path = getString( args, 0 );
String dest = getString( args, 1 );
try
{
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.copy( path, dest );
return null;
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 10:
{
// delete
String path = getString( args, 0 );
try
{
m_env.addTrackingChange( TrackingField.FS_OPS );
m_fileSystem.delete( path );
return null;
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 11:
{
// open
String path = getString( args, 0 );
String mode = getString( args, 1 );
m_env.addTrackingChange( TrackingField.FS_OPS );
try
{
switch( mode )
{
case "r":
{
// Open the file for reading, then create a wrapper around the reader
FileSystemWrapper<BufferedReader> reader = m_fileSystem.openForRead( path, EncodedReadableHandle::openUtf8 );
return new Object[] { new EncodedReadableHandle( reader.get(), reader ) };
}
case "w":
{
// Open the file for writing, then create a wrapper around the writer
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, false, EncodedWritableHandle::openUtf8 );
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
}
case "a":
{
// Open the file for appending, then create a wrapper around the writer
FileSystemWrapper<BufferedWriter> writer = m_fileSystem.openForWrite( path, true, EncodedWritableHandle::openUtf8 );
return new Object[] { new EncodedWritableHandle( writer.get(), writer ) };
}
case "rb":
{
// Open the file for binary reading, then create a wrapper around the reader
FileSystemWrapper<ReadableByteChannel> reader = m_fileSystem.openForRead( path, Function.identity() );
return new Object[] { new BinaryReadableHandle( reader.get(), reader ) };
}
case "wb":
{
// Open the file for binary writing, then create a wrapper around the writer
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, false, Function.identity() );
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
}
case "ab":
{
// Open the file for binary appending, then create a wrapper around the reader
FileSystemWrapper<WritableByteChannel> writer = m_fileSystem.openForWrite( path, true, Function.identity() );
return new Object[] { new BinaryWritableHandle( writer.get(), writer ) };
}
default:
throw new LuaException( "Unsupported mode" );
}
}
catch( FileSystemException e )
{
return new Object[] { null, e.getMessage() };
}
}
case 12:
{
// getDrive
String path = getString( args, 0 );
try
{
if( !m_fileSystem.exists( path ) )
{
return null;
}
return new Object[] { m_fileSystem.getMountLabel( path ) };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 13:
{
// getFreeSpace
String path = getString( args, 0 );
try
{
long freeSpace = m_fileSystem.getFreeSpace( path );
if( freeSpace >= 0 )
{
return new Object[] { freeSpace };
}
return new Object[] { "unlimited" };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 14: // find
{
String path = getString( args, 0 );
try
{
m_env.addTrackingChange( TrackingField.FS_OPS );
return new Object[] { m_fileSystem.find( path ) };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 15: // getDir
{
String path = getString( args, 0 );
return new Object[] { FileSystem.getDirectory( path ) };
}
case 16: // getCapacity
{
String path = getString( args, 0 );
try
{
OptionalLong capacity = m_fileSystem.getCapacity( path );
return new Object[] { capacity.isPresent() ? capacity.getAsLong() : null };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
case 17: // attributes
{
String path = getString( args, 0 );
try
{
BasicFileAttributes attributes = m_fileSystem.getAttributes( path );
Map<String, Object> result = new HashMap<>();
result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
result.put( "created", getFileTime( attributes.creationTime() ) );
result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
result.put( "isDir", attributes.isDirectory() );
return new Object[] { result };
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
default:
assert false;
return null;
}
catch( FileSystemException e )
{
return new Object[] { null, e.getMessage() };
}
}
@LuaFunction
public final Object[] getDrive( String path ) throws LuaException
{
try
{
return fileSystem.exists( path ) ? new Object[] { fileSystem.getMountLabel( path ) } : null;
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final Object getFreeSpace( String path ) throws LuaException
{
try
{
long freeSpace = fileSystem.getFreeSpace( path );
return freeSpace >= 0 ? freeSpace : "unlimited";
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final String[] find( String path ) throws LuaException
{
try
{
environment.addTrackingChange( TrackingField.FS_OPS );
return fileSystem.find( path );
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final Object getCapacity( String path ) throws LuaException
{
try
{
OptionalLong capacity = fileSystem.getCapacity( path );
return capacity.isPresent() ? capacity.getAsLong() : null;
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final Map<String, Object> attributes( String path ) throws LuaException
{
try
{
BasicFileAttributes attributes = fileSystem.getAttributes( path );
Map<String, Object> result = new HashMap<>();
result.put( "modification", getFileTime( attributes.lastModifiedTime() ) );
result.put( "created", getFileTime( attributes.creationTime() ) );
result.put( "size", attributes.isDirectory() ? 0 : attributes.size() );
result.put( "isDir", attributes.isDirectory() );
return result;
}
catch( FileSystemException e )
{
throw new LuaException( e.getMessage() );
}
}

View File

@@ -6,9 +6,10 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.apis.http.*;
import dan200.computercraft.core.apis.http.request.HttpRequest;
import dan200.computercraft.core.apis.http.websocket.Websocket;
@@ -21,8 +22,8 @@ import java.net.URI;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import static dan200.computercraft.api.lua.ArgumentHelper.*;
import static dan200.computercraft.core.apis.TableHelper.*;
public class HTTPAPI implements ILuaAPI
@@ -68,135 +69,106 @@ public class HTTPAPI implements ILuaAPI
Resource.cleanup();
}
@Nonnull
@Override
public String[] getMethodNames()
@LuaFunction
public final Object[] request( IArguments args ) throws LuaException
{
return new String[] {
"request",
"checkURL",
"websocket",
};
String address, postString, requestMethod;
Map<?, ?> headerTable;
boolean binary, redirect;
if( args.get( 0 ) instanceof Map )
{
Map<?, ?> options = args.getTable( 0 );
address = getStringField( options, "url" );
postString = optStringField( options, "body", null );
headerTable = optTableField( options, "headers", Collections.emptyMap() );
binary = optBooleanField( options, "binary", false );
requestMethod = optStringField( options, "method", null );
redirect = optBooleanField( options, "redirect", true );
}
else
{
// Get URL and post information
address = args.getString( 0 );
postString = args.optString( 1, null );
headerTable = args.optTable( 2, Collections.emptyMap() );
binary = args.optBoolean( 3, false );
requestMethod = null;
redirect = true;
}
HttpHeaders headers = getHeaders( headerTable );
HttpMethod httpMethod;
if( requestMethod == null )
{
httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST;
}
else
{
httpMethod = HttpMethod.valueOf( requestMethod.toUpperCase( Locale.ROOT ) );
if( httpMethod == null || requestMethod.equalsIgnoreCase( "CONNECT" ) )
{
throw new LuaException( "Unsupported HTTP method" );
}
}
try
{
URI uri = HttpRequest.checkUri( address );
HttpRequest request = new HttpRequest( requests, m_apiEnvironment, address, postString, headers, binary, redirect );
// Make the request
request.queue( r -> r.request( uri, httpMethod ) );
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
@Override
@SuppressWarnings( "resource" )
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
@LuaFunction
public final Object[] checkURL( String address )
{
switch( method )
try
{
case 0: // request
URI uri = HttpRequest.checkUri( address );
new CheckUrl( checkUrls, m_apiEnvironment, address, uri ).queue( CheckUrl::run );
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
@LuaFunction
public final Object[] websocket( String address, Optional<Map<?, ?>> headerTbl ) throws LuaException
{
if( !ComputerCraft.httpWebsocketEnabled )
{
throw new LuaException( "Websocket connections are disabled" );
}
HttpHeaders headers = getHeaders( headerTbl.orElse( Collections.emptyMap() ) );
try
{
URI uri = Websocket.checkUri( address );
if( !new Websocket( websockets, m_apiEnvironment, uri, address, headers ).queue( Websocket::connect ) )
{
String address, postString, requestMethod;
Map<?, ?> headerTable;
boolean binary, redirect;
if( args.length >= 1 && args[0] instanceof Map )
{
Map<?, ?> options = (Map<?, ?>) args[0];
address = getStringField( options, "url" );
postString = optStringField( options, "body", null );
headerTable = optTableField( options, "headers", Collections.emptyMap() );
binary = optBooleanField( options, "binary", false );
requestMethod = optStringField( options, "method", null );
redirect = optBooleanField( options, "redirect", true );
}
else
{
// Get URL and post information
address = getString( args, 0 );
postString = optString( args, 1, null );
headerTable = optTable( args, 2, Collections.emptyMap() );
binary = optBoolean( args, 3, false );
requestMethod = null;
redirect = true;
}
HttpHeaders headers = getHeaders( headerTable );
HttpMethod httpMethod;
if( requestMethod == null )
{
httpMethod = postString == null ? HttpMethod.GET : HttpMethod.POST;
}
else
{
httpMethod = HttpMethod.valueOf( requestMethod.toUpperCase( Locale.ROOT ) );
if( httpMethod == null || requestMethod.equalsIgnoreCase( "CONNECT" ) )
{
throw new LuaException( "Unsupported HTTP method" );
}
}
try
{
URI uri = HttpRequest.checkUri( address );
HttpRequest request = new HttpRequest( requests, m_apiEnvironment, address, postString, headers, binary, redirect );
long requestBody = request.body().readableBytes() + HttpRequest.getHeaderSize( headers );
if( ComputerCraft.httpMaxUpload != 0 && requestBody > ComputerCraft.httpMaxUpload )
{
throw new HTTPRequestException( "Request body is too large" );
}
// Make the request
request.queue( r -> r.request( uri, httpMethod ) );
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
throw new LuaException( "Too many websockets already open" );
}
case 1: // checkURL
{
String address = getString( args, 0 );
// Check URL
try
{
URI uri = HttpRequest.checkUri( address );
new CheckUrl( checkUrls, m_apiEnvironment, address, uri ).queue( CheckUrl::run );
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
case 2: // websocket
{
String address = getString( args, 0 );
Map<?, ?> headerTbl = optTable( args, 1, Collections.emptyMap() );
if( !ComputerCraft.http_websocket_enable )
{
throw new LuaException( "Websocket connections are disabled" );
}
HttpHeaders headers = getHeaders( headerTbl );
try
{
URI uri = Websocket.checkUri( address );
if( !new Websocket( websockets, m_apiEnvironment, uri, address, headers ).queue( Websocket::connect ) )
{
throw new LuaException( "Too many websockets already open" );
}
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}
default:
return null;
return new Object[] { true };
}
catch( HTTPRequestException e )
{
return new Object[] { false, e.getMessage() };
}
}

View File

@@ -43,7 +43,7 @@ public interface IAPIEnvironment
void reboot();
void queueEvent( String event, Object[] args );
void queueEvent( String event, Object... args );
void setOutput( ComputerSide side, int output );

View File

@@ -1,24 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
/**
* This exists purely to ensure binary compatibility.
*
* @see dan200.computercraft.api.lua.ILuaAPI
* @deprecated Use the version in the public API. Only exists for compatibility with CCEmuX.
*/
@Deprecated
public interface ILuaAPI extends dan200.computercraft.api.lua.ILuaAPI
{
void advance( double v );
@Override
default void update()
{
advance( 0.05 );
}
}

View File

@@ -5,9 +5,10 @@
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.shared.util.StringUtil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -20,11 +21,11 @@ import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatterBuilder;
import java.util.*;
import static dan200.computercraft.api.lua.ArgumentHelper.*;
import static dan200.computercraft.api.lua.LuaValues.checkFinite;
public class OSAPI implements ILuaAPI
{
private IAPIEnvironment m_apiEnvironment;
private final IAPIEnvironment apiEnvironment;
private final Int2ObjectMap<Alarm> m_alarms = new Int2ObjectOpenHashMap<>();
private int m_clock;
@@ -55,11 +56,9 @@ public class OSAPI implements ILuaAPI
public OSAPI( IAPIEnvironment environment )
{
m_apiEnvironment = environment;
apiEnvironment = environment;
}
// ILuaAPI implementation
@Override
public String[] getNames()
{
@@ -69,8 +68,8 @@ public class OSAPI implements ILuaAPI
@Override
public void startup()
{
m_time = m_apiEnvironment.getComputerEnvironment().getTimeOfDay();
m_day = m_apiEnvironment.getComputerEnvironment().getDay();
m_time = apiEnvironment.getComputerEnvironment().getTimeOfDay();
m_day = apiEnvironment.getComputerEnvironment().getDay();
m_clock = 0;
synchronized( m_alarms )
@@ -89,8 +88,8 @@ public class OSAPI implements ILuaAPI
{
double previousTime = m_time;
int previousDay = m_day;
double time = m_apiEnvironment.getComputerEnvironment().getTimeOfDay();
int day = m_apiEnvironment.getComputerEnvironment().getDay();
double time = apiEnvironment.getComputerEnvironment().getTimeOfDay();
int day = apiEnvironment.getComputerEnvironment().getDay();
if( time > previousTime || day > previousDay )
{
@@ -103,7 +102,7 @@ public class OSAPI implements ILuaAPI
double t = alarm.m_day * 24.0 + alarm.m_time;
if( now >= t )
{
queueLuaEvent( "alarm", new Object[] { entry.getKey() } );
apiEnvironment.queueEvent( "alarm", entry.getIntKey() );
it.remove();
}
}
@@ -123,31 +122,6 @@ public class OSAPI implements ILuaAPI
}
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
"queueEvent",
"startTimer",
"setAlarm",
"shutdown",
"reboot",
"computerID",
"getComputerID",
"setComputerLabel",
"computerLabel",
"getComputerLabel",
"clock",
"time",
"day",
"cancelTimer",
"cancelAlarm",
"epoch",
"date",
};
}
private static float getTimeForCalendar( Calendar c )
{
float time = c.get( Calendar.HOUR_OF_DAY );
@@ -174,214 +148,174 @@ public class OSAPI implements ILuaAPI
return c.getTime().getTime();
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
@LuaFunction
public final void queueEvent( String name, IArguments args )
{
switch( method )
apiEnvironment.queueEvent( name, args.drop( 1 ).getAll() );
}
@LuaFunction
public final int startTimer( double timer ) throws LuaException
{
return apiEnvironment.startTimer( Math.round( checkFinite( 0, timer ) / 0.05 ) );
}
@LuaFunction
public final void cancelTimer( int token )
{
apiEnvironment.cancelTimer( token );
}
@LuaFunction
public final int setAlarm( double time ) throws LuaException
{
checkFinite( 0, time );
if( time < 0.0 || time >= 24.0 ) throw new LuaException( "Number out of range" );
synchronized( m_alarms )
{
case 0: // queueEvent
queueLuaEvent( getString( args, 0 ), trimArray( args, 1 ) );
return null;
case 1:
{
// startTimer
double timer = getFiniteDouble( args, 0 );
int id = m_apiEnvironment.startTimer( Math.round( timer / 0.05 ) );
return new Object[] { id };
}
case 2:
{
// setAlarm
double time = getFiniteDouble( args, 0 );
if( time < 0.0 || time >= 24.0 )
{
throw new LuaException( "Number out of range" );
}
synchronized( m_alarms )
{
int day = time > m_time ? m_day : m_day + 1;
m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
return new Object[] { m_nextAlarmToken++ };
}
}
case 3: // shutdown
m_apiEnvironment.shutdown();
return null;
case 4: // reboot
m_apiEnvironment.reboot();
return null;
case 5:
case 6: // computerID/getComputerID
return new Object[] { getComputerID() };
case 7:
{
// setComputerLabel
String label = optString( args, 0, null );
m_apiEnvironment.setLabel( StringUtil.normaliseLabel( label ) );
return null;
}
case 8:
case 9:
{
// computerLabel/getComputerLabel
String label = m_apiEnvironment.getLabel();
if( label != null )
{
return new Object[] { label };
}
return null;
}
case 10: // clock
return new Object[] { m_clock * 0.05 };
case 11:
{
// time
Object value = args.length > 0 ? args[0] : null;
if( value instanceof Map ) return new Object[] { LuaDateTime.fromTable( (Map<?, ?>) value ) };
String param = optString( args, 0, "ingame" );
switch( param.toLowerCase( Locale.ROOT ) )
{
case "utc":
{
// Get Hour of day (UTC)
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getTimeForCalendar( c ) };
}
case "local":
{
// Get Hour of day (local time)
Calendar c = Calendar.getInstance();
return new Object[] { getTimeForCalendar( c ) };
}
case "ingame":
// Get ingame hour
synchronized( m_alarms )
{
return new Object[] { m_time };
}
default:
throw new LuaException( "Unsupported operation" );
}
}
case 12:
{
// day
String param = optString( args, 0, "ingame" );
switch( param.toLowerCase( Locale.ROOT ) )
{
case "utc":
{
// Get numbers of days since 1970-01-01 (utc)
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getDayForCalendar( c ) };
}
case "local":
{
// Get numbers of days since 1970-01-01 (local time)
Calendar c = Calendar.getInstance();
return new Object[] { getDayForCalendar( c ) };
}
case "ingame":
// Get game day
synchronized( m_alarms )
{
return new Object[] { m_day };
}
default:
throw new LuaException( "Unsupported operation" );
}
}
case 13:
{
// cancelTimer
int token = getInt( args, 0 );
m_apiEnvironment.cancelTimer( token );
return null;
}
case 14:
{
// cancelAlarm
int token = getInt( args, 0 );
synchronized( m_alarms )
{
m_alarms.remove( token );
}
return null;
}
case 15: // epoch
{
String param = optString( args, 0, "ingame" );
switch( param.toLowerCase( Locale.ROOT ) )
{
case "utc":
{
// Get utc epoch
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return new Object[] { getEpochForCalendar( c ) };
}
case "local":
{
// Get local epoch
Calendar c = Calendar.getInstance();
return new Object[] { getEpochForCalendar( c ) };
}
case "ingame":
// Get in-game epoch
synchronized( m_alarms )
{
return new Object[] { m_day * 86400000 + (int) (m_time * 3600000.0f) };
}
default:
throw new LuaException( "Unsupported operation" );
}
}
case 16: // date
{
String format = optString( args, 0, "%c" );
long time = optLong( args, 1, Instant.now().getEpochSecond() );
Instant instant = Instant.ofEpochSecond( time );
ZonedDateTime date;
ZoneOffset offset;
if( format.startsWith( "!" ) )
{
offset = ZoneOffset.UTC;
date = ZonedDateTime.ofInstant( instant, offset );
format = format.substring( 1 );
}
else
{
ZoneId id = ZoneId.systemDefault();
offset = id.getRules().getOffset( instant );
date = ZonedDateTime.ofInstant( instant, id );
}
if( format.equals( "*t" ) ) return new Object[] { LuaDateTime.toTable( date, offset, instant ) };
DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
LuaDateTime.format( formatter, format, offset );
return new Object[] { formatter.toFormatter( Locale.ROOT ).format( date ) };
}
default:
return null;
int day = time > m_time ? m_day : m_day + 1;
m_alarms.put( m_nextAlarmToken, new Alarm( time, day ) );
return m_nextAlarmToken++;
}
}
// Private methods
private void queueLuaEvent( String event, Object[] args )
@LuaFunction
public final void cancelAlarm( int token )
{
m_apiEnvironment.queueEvent( event, args );
synchronized( m_alarms )
{
m_alarms.remove( token );
}
}
private Object[] trimArray( Object[] array, int skip )
@LuaFunction( "shutdown" )
public final void doShutdown()
{
return Arrays.copyOfRange( array, skip, array.length );
apiEnvironment.shutdown();
}
private int getComputerID()
@LuaFunction( "reboot" )
public final void doReboot()
{
return m_apiEnvironment.getComputerID();
apiEnvironment.reboot();
}
@LuaFunction( { "getComputerID", "computerID" } )
public final int getComputerID()
{
return apiEnvironment.getComputerID();
}
@LuaFunction( { "getComputerLabel", "computerLabel" } )
public final Object[] getComputerLabel()
{
String label = apiEnvironment.getLabel();
return label == null ? null : new Object[] { label };
}
@LuaFunction
public final void setComputerLabel( Optional<String> label )
{
apiEnvironment.setLabel( StringUtil.normaliseLabel( label.orElse( null ) ) );
}
@LuaFunction
public final double clock()
{
return m_clock * 0.05;
}
@LuaFunction
public final Object time( IArguments args ) throws LuaException
{
Object value = args.get( 0 );
if( value instanceof Map ) return LuaDateTime.fromTable( (Map<?, ?>) value );
String param = args.optString( 0, "ingame" );
switch( param.toLowerCase( Locale.ROOT ) )
{
case "utc": // Get Hour of day (UTC)
return getTimeForCalendar( Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ) );
case "local": // Get Hour of day (local time)
return getTimeForCalendar( Calendar.getInstance() );
case "ingame": // Get in-game hour
return m_time;
default:
throw new LuaException( "Unsupported operation" );
}
}
@LuaFunction
public final int day( Optional<String> args ) throws LuaException
{
switch( args.orElse( "ingame" ).toLowerCase( Locale.ROOT ) )
{
case "utc": // Get numbers of days since 1970-01-01 (utc)
return getDayForCalendar( Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ) );
case "local": // Get numbers of days since 1970-01-01 (local time)
return getDayForCalendar( Calendar.getInstance() );
case "ingame":// Get game day
return m_day;
default:
throw new LuaException( "Unsupported operation" );
}
}
@LuaFunction
public final long epoch( Optional<String> args ) throws LuaException
{
switch( args.orElse( "ingame" ).toLowerCase( Locale.ROOT ) )
{
case "utc":
{
// Get utc epoch
Calendar c = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
return getEpochForCalendar( c );
}
case "local":
{
// Get local epoch
Calendar c = Calendar.getInstance();
return getEpochForCalendar( c );
}
case "ingame":
// Get in-game epoch
synchronized( m_alarms )
{
return m_day * 86400000 + (int) (m_time * 3600000.0f);
}
default:
throw new LuaException( "Unsupported operation" );
}
}
@LuaFunction
public final Object date( Optional<String> formatA, Optional<Long> timeA ) throws LuaException
{
String format = formatA.orElse( "%c" );
long time = timeA.orElseGet( () -> Instant.now().getEpochSecond() );
Instant instant = Instant.ofEpochSecond( time );
ZonedDateTime date;
ZoneOffset offset;
if( format.startsWith( "!" ) )
{
offset = ZoneOffset.UTC;
date = ZonedDateTime.ofInstant( instant, offset );
format = format.substring( 1 );
}
else
{
ZoneId id = ZoneId.systemDefault();
offset = id.getRules().getOffset( instant );
date = ZonedDateTime.ofInstant( instant, id );
}
if( format.equals( "*t" ) ) return LuaDateTime.toTable( date, offset, instant );
DateTimeFormatterBuilder formatter = new DateTimeFormatterBuilder();
LuaDateTime.format( formatter, format, offset );
return formatter.toFormatter( Locale.ROOT ).format( date );
}
}

View File

@@ -7,86 +7,74 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IWorkMonitor;
import dan200.computercraft.api.peripheral.NotAttachedException;
import dan200.computercraft.core.asm.LuaMethod;
import dan200.computercraft.core.asm.NamedMethod;
import dan200.computercraft.core.asm.PeripheralMethod;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.tracking.TrackingField;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static dan200.computercraft.api.lua.ArgumentHelper.getString;
import java.util.*;
public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChangeListener
{
private class PeripheralWrapper extends ComputerAccess
{
private final String m_side;
private final IPeripheral m_peripheral;
private final String side;
private final IPeripheral peripheral;
private String m_type;
private String[] m_methods;
private Map<String, Integer> m_methodMap;
private boolean m_attached;
private final String type;
private final Map<String, PeripheralMethod> methodMap;
private boolean attached;
PeripheralWrapper( IPeripheral peripheral, String side )
{
super( m_environment );
m_side = side;
m_peripheral = peripheral;
m_attached = false;
super( environment );
this.side = side;
this.peripheral = peripheral;
attached = false;
m_type = peripheral.getType();
m_methods = peripheral.getMethodNames();
assert m_type != null;
assert m_methods != null;
type = Objects.requireNonNull( peripheral.getType(), "Peripheral type cannot be null" );
m_methodMap = new HashMap<>();
for( int i = 0; i < m_methods.length; i++ )
{
if( m_methods[i] != null )
{
m_methodMap.put( m_methods[i], i );
}
}
methodMap = PeripheralAPI.getMethods( peripheral );
}
public IPeripheral getPeripheral()
{
return m_peripheral;
return peripheral;
}
public String getType()
{
return m_type;
return type;
}
public String[] getMethods()
public Collection<String> getMethods()
{
return m_methods;
return methodMap.keySet();
}
public synchronized boolean isAttached()
{
return m_attached;
return attached;
}
public synchronized void attach()
{
m_attached = true;
m_peripheral.attach( this );
attached = true;
peripheral.attach( this );
}
public void detach()
{
// Call detach
m_peripheral.detach( this );
peripheral.detach( this );
synchronized( this )
{
@@ -94,81 +82,56 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
unmountAll();
}
m_attached = false;
attached = false;
}
public Object[] call( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException
public MethodResult call( ILuaContext context, String methodName, IArguments arguments ) throws LuaException
{
int method = -1;
PeripheralMethod method;
synchronized( this )
{
if( m_methodMap.containsKey( methodName ) )
{
method = m_methodMap.get( methodName );
}
}
if( method >= 0 )
{
m_environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
return m_peripheral.callMethod( this, context, method, arguments );
}
else
{
throw new LuaException( "No such method " + methodName );
method = methodMap.get( methodName );
}
if( method == null ) throw new LuaException( "No such method " + methodName );
environment.addTrackingChange( TrackingField.PERIPHERAL_OPS );
return method.apply( peripheral, context, this, arguments );
}
// IComputerAccess implementation
@Override
public synchronized String mount( @Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
if( !attached ) throw new NotAttachedException();
return super.mount( desiredLoc, mount, driveName );
}
@Override
public synchronized String mountWritable( @Nonnull String desiredLoc, @Nonnull IWritableMount mount, @Nonnull String driveName )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
if( !attached ) throw new NotAttachedException();
return super.mountWritable( desiredLoc, mount, driveName );
}
@Override
public synchronized void unmount( String location )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
if( !attached ) throw new NotAttachedException();
super.unmount( location );
}
@Override
public int getID()
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
if( !attached ) throw new NotAttachedException();
return super.getID();
}
@Override
public void queueEvent( @Nonnull final String event, final Object[] arguments )
public void queueEvent( @Nonnull String event, Object... arguments )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
if( !attached ) throw new NotAttachedException();
super.queueEvent( event, arguments );
}
@@ -176,24 +139,18 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
@Override
public String getAttachmentName()
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
return m_side;
if( !attached ) throw new NotAttachedException();
return side;
}
@Nonnull
@Override
public Map<String, IPeripheral> getAvailablePeripherals()
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
if( !attached ) throw new NotAttachedException();
Map<String, IPeripheral> peripherals = new HashMap<>();
for( PeripheralWrapper wrapper : m_peripherals )
for( PeripheralWrapper wrapper : PeripheralAPI.this.peripherals )
{
if( wrapper != null && wrapper.isAttached() )
{
@@ -208,12 +165,9 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
@Override
public IPeripheral getAvailablePeripheral( @Nonnull String name )
{
if( !m_attached )
{
throw new RuntimeException( "You are not attached to this Computer" );
}
if( !attached ) throw new NotAttachedException();
for( PeripheralWrapper wrapper : m_peripherals )
for( PeripheralWrapper wrapper : peripherals )
{
if( wrapper != null && wrapper.isAttached() && wrapper.getAttachmentName().equals( name ) )
{
@@ -222,24 +176,25 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
}
return null;
}
@Nonnull
@Override
public IWorkMonitor getMainThreadMonitor()
{
if( !attached ) throw new NotAttachedException();
return super.getMainThreadMonitor();
}
}
private final IAPIEnvironment m_environment;
private final PeripheralWrapper[] m_peripherals;
private boolean m_running;
private final IAPIEnvironment environment;
private final PeripheralWrapper[] peripherals = new PeripheralWrapper[6];
private boolean running;
public PeripheralAPI( IAPIEnvironment environment )
{
m_environment = environment;
m_environment.setPeripheralChangeListener( this );
m_peripherals = new PeripheralWrapper[6];
for( int i = 0; i < 6; i++ )
{
m_peripherals[i] = null;
}
m_running = false;
this.environment = environment;
this.environment.setPeripheralChangeListener( this );
running = false;
}
// IPeripheralChangeListener
@@ -247,37 +202,35 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
@Override
public void onPeripheralChanged( ComputerSide side, IPeripheral newPeripheral )
{
synchronized( m_peripherals )
synchronized( peripherals )
{
int index = side.ordinal();
if( m_peripherals[index] != null )
if( peripherals[index] != null )
{
// Queue a detachment
final PeripheralWrapper wrapper = m_peripherals[index];
final PeripheralWrapper wrapper = peripherals[index];
if( wrapper.isAttached() ) wrapper.detach();
// Queue a detachment event
m_environment.queueEvent( "peripheral_detach", new Object[] { side.getName() } );
environment.queueEvent( "peripheral_detach", side.getName() );
}
// Assign the new peripheral
m_peripherals[index] = newPeripheral == null ? null
peripherals[index] = newPeripheral == null ? null
: new PeripheralWrapper( newPeripheral, side.getName() );
if( m_peripherals[index] != null )
if( peripherals[index] != null )
{
// Queue an attachment
final PeripheralWrapper wrapper = m_peripherals[index];
if( m_running && !wrapper.isAttached() ) wrapper.attach();
final PeripheralWrapper wrapper = peripherals[index];
if( running && !wrapper.isAttached() ) wrapper.attach();
// Queue an attachment event
m_environment.queueEvent( "peripheral", new Object[] { side.getName() } );
environment.queueEvent( "peripheral", side.getName() );
}
}
}
// ILuaAPI implementation
@Override
public String[] getNames()
{
@@ -287,12 +240,12 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
@Override
public void startup()
{
synchronized( m_peripherals )
synchronized( peripherals )
{
m_running = true;
running = true;
for( int i = 0; i < 6; i++ )
{
PeripheralWrapper wrapper = m_peripherals[i];
PeripheralWrapper wrapper = peripherals[i];
if( wrapper != null && !wrapper.isAttached() ) wrapper.attach();
}
}
@@ -301,12 +254,12 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
@Override
public void shutdown()
{
synchronized( m_peripherals )
synchronized( peripherals )
{
m_running = false;
running = false;
for( int i = 0; i < 6; i++ )
{
PeripheralWrapper wrapper = m_peripherals[i];
PeripheralWrapper wrapper = peripherals[i];
if( wrapper != null && wrapper.isAttached() )
{
wrapper.detach();
@@ -315,98 +268,95 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
}
}
@Nonnull
@Override
public String[] getMethodNames()
@LuaFunction
public final boolean isPresent( String sideName )
{
return new String[] {
"isPresent",
"getType",
"getMethods",
"call",
};
ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
if( side != null )
{
synchronized( peripherals )
{
PeripheralWrapper p = peripherals[side.ordinal()];
if( p != null ) return true;
}
}
return false;
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
@LuaFunction
public final Object[] getType( String sideName )
{
switch( method )
ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
if( side == null ) return null;
synchronized( peripherals )
{
case 0:
{
// isPresent
boolean present = false;
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side != null )
{
synchronized( m_peripherals )
{
PeripheralWrapper p = m_peripherals[side.ordinal()];
if( p != null ) present = true;
}
}
return new Object[] { present };
}
case 1:
{
// getType
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side != null )
{
synchronized( m_peripherals )
{
PeripheralWrapper p = m_peripherals[side.ordinal()];
if( p != null ) return new Object[] { p.getType() };
}
}
return null;
}
case 2:
{
// getMethods
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side != null )
{
synchronized( m_peripherals )
{
PeripheralWrapper p = m_peripherals[side.ordinal()];
if( p != null ) return new Object[] { p.getMethods() };
}
}
PeripheralWrapper p = peripherals[side.ordinal()];
if( p != null ) return new Object[] { p.getType() };
}
return null;
}
return null;
}
case 3:
{
// call
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
String methodName = getString( args, 1 );
Object[] methodArgs = Arrays.copyOfRange( args, 2, args.length );
@LuaFunction
public final Object[] getMethods( String sideName )
{
ComputerSide side = ComputerSide.valueOfInsensitive( sideName );
if( side == null ) return null;
if( side == null ) throw new LuaException( "No peripheral attached" );
synchronized( peripherals )
{
PeripheralWrapper p = peripherals[side.ordinal()];
if( p != null ) return new Object[] { p.getMethods() };
}
return null;
}
PeripheralWrapper p;
synchronized( m_peripherals )
{
p = m_peripherals[side.ordinal()];
}
if( p == null ) throw new LuaException( "No peripheral attached" );
@LuaFunction
public final MethodResult call( ILuaContext context, IArguments args ) throws LuaException
{
ComputerSide side = ComputerSide.valueOfInsensitive( args.getString( 0 ) );
String methodName = args.getString( 1 );
IArguments methodArgs = args.drop( 2 );
try
{
return p.call( context, methodName, methodArgs );
}
catch( LuaException e )
{
// We increase the error level by one in order to shift the error level to where peripheral.call was
// invoked. It would be possible to do it in Lua code, but would add significantly more overhead.
if( e.getLevel() > 0 ) throw new FastLuaException( e.getMessage(), e.getLevel() + 1 );
throw e;
}
}
default:
return null;
if( side == null ) throw new LuaException( "No peripheral attached" );
PeripheralWrapper p;
synchronized( peripherals )
{
p = peripherals[side.ordinal()];
}
if( p == null ) throw new LuaException( "No peripheral attached" );
try
{
return p.call( context, methodName, methodArgs ).adjustError( 1 );
}
catch( LuaException e )
{
// We increase the error level by one in order to shift the error level to where peripheral.call was
// invoked. It would be possible to do it in Lua code, but would add significantly more overhead.
if( e.getLevel() > 0 ) throw new FastLuaException( e.getMessage(), e.getLevel() + 1 );
throw e;
}
}
public static Map<String, PeripheralMethod> getMethods( IPeripheral peripheral )
{
String[] dynamicMethods = peripheral instanceof IDynamicPeripheral
? Objects.requireNonNull( ((IDynamicPeripheral) peripheral).getMethodNames(), "Peripheral methods cannot be null" )
: LuaMethod.EMPTY_METHODS;
List<NamedMethod<PeripheralMethod>> methods = PeripheralMethod.GENERATOR.getMethods( peripheral.getClass() );
Map<String, PeripheralMethod> methodMap = new HashMap<>( methods.size() + dynamicMethods.length );
for( int i = 0; i < dynamicMethods.length; i++ )
{
methodMap.put( dynamicMethods[i], PeripheralMethod.DYNAMIC.get( i ) );
}
for( NamedMethod<PeripheralMethod> method : methods )
{
methodMap.put( method.getName(), method.getMethod() );
}
return methodMap;
}
}

View File

@@ -6,21 +6,17 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.computer.ComputerSide;
import javax.annotation.Nonnull;
import static dan200.computercraft.api.lua.ArgumentHelper.*;
public class RedstoneAPI implements ILuaAPI
{
private IAPIEnvironment m_environment;
private final IAPIEnvironment environment;
public RedstoneAPI( IAPIEnvironment environment )
{
m_environment = environment;
this.environment = environment;
}
@Override
@@ -29,95 +25,71 @@ public class RedstoneAPI implements ILuaAPI
return new String[] { "rs", "redstone" };
}
@Nonnull
@Override
public String[] getMethodNames()
@LuaFunction
public final String[] getSides()
{
return new String[] {
"getSides",
"setOutput",
"getOutput",
"getInput",
"setBundledOutput",
"getBundledOutput",
"getBundledInput",
"testBundledInput",
"setAnalogOutput",
"setAnalogueOutput",
"getAnalogOutput",
"getAnalogueOutput",
"getAnalogInput",
"getAnalogueInput",
};
return ComputerSide.NAMES;
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
@LuaFunction
public final void setOutput( ComputerSide side, boolean output )
{
switch( method )
{
case 0: // getSides
return new Object[] { ComputerSide.NAMES };
case 1:
{
// setOutput
ComputerSide side = parseSide( args );
boolean output = getBoolean( args, 1 );
m_environment.setOutput( side, output ? 15 : 0 );
return null;
}
case 2: // getOutput
return new Object[] { m_environment.getOutput( parseSide( args ) ) > 0 };
case 3: // getInput
return new Object[] { m_environment.getInput( parseSide( args ) ) > 0 };
case 4:
{
// setBundledOutput
ComputerSide side = parseSide( args );
int output = getInt( args, 1 );
m_environment.setBundledOutput( side, output );
return null;
}
case 5: // getBundledOutput
return new Object[] { m_environment.getBundledOutput( parseSide( args ) ) };
case 6: // getBundledInput
return new Object[] { m_environment.getBundledInput( parseSide( args ) ) };
case 7:
{
// testBundledInput
ComputerSide side = parseSide( args );
int mask = getInt( args, 1 );
int input = m_environment.getBundledInput( side );
return new Object[] { (input & mask) == mask };
}
case 8:
case 9:
{
// setAnalogOutput/setAnalogueOutput
ComputerSide side = parseSide( args );
int output = getInt( args, 1 );
if( output < 0 || output > 15 )
{
throw new LuaException( "Expected number in range 0-15" );
}
m_environment.setOutput( side, output );
return null;
}
case 10:
case 11: // getAnalogOutput/getAnalogueOutput
return new Object[] { m_environment.getOutput( parseSide( args ) ) };
case 12:
case 13: // getAnalogInput/getAnalogueInput
return new Object[] { m_environment.getInput( parseSide( args ) ) };
default:
return null;
}
environment.setOutput( side, output ? 15 : 0 );
}
private static ComputerSide parseSide( Object[] args ) throws LuaException
@LuaFunction
public final boolean getOutput( ComputerSide side )
{
ComputerSide side = ComputerSide.valueOfInsensitive( getString( args, 0 ) );
if( side == null ) throw new LuaException( "Invalid side." );
return side;
return environment.getOutput( side ) > 0;
}
@LuaFunction
public final boolean getInput( ComputerSide side )
{
return environment.getInput( side ) > 0;
}
@LuaFunction( { "setAnalogOutput", "setAnalogueOutput" } )
public final void setAnalogOutput( ComputerSide side, int output ) throws LuaException
{
if( output < 0 || output > 15 ) throw new LuaException( "Expected number in range 0-15" );
environment.setOutput( side, output );
}
@LuaFunction( { "getAnalogOutput", "getAnalogueOutput" } )
public final int getAnalogOutput( ComputerSide side )
{
return environment.getOutput( side );
}
@LuaFunction( { "getAnalogInput", "getAnalogueInput" } )
public final int getAnalogInput( ComputerSide side )
{
return environment.getInput( side );
}
@LuaFunction
public final void setBundledOutput( ComputerSide side, int output )
{
environment.setBundledOutput( side, output );
}
@LuaFunction
public final int getBundledOutput( ComputerSide side )
{
return environment.getBundledOutput( side );
}
@LuaFunction
public final int getBundledInput( ComputerSide side )
{
return environment.getBundledOutput( side );
}
@LuaFunction
public final boolean testBundledInput( ComputerSide side, int mask )
{
int input = environment.getBundledInput( side );
return (input & mask) == mask;
}
}

View File

@@ -5,14 +5,14 @@
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ArgumentHelper;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaValues;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Map;
import static dan200.computercraft.api.lua.ArgumentHelper.getNumericType;
import static dan200.computercraft.api.lua.LuaValues.getNumericType;
/**
* Various helpers for tables.
@@ -27,7 +27,7 @@ public final class TableHelper
@Nonnull
public static LuaException badKey( @Nonnull String key, @Nonnull String expected, @Nullable Object actual )
{
return badKey( key, expected, ArgumentHelper.getType( actual ) );
return badKey( key, expected, LuaValues.getType( actual ) );
}
@Nonnull

View File

@@ -6,27 +6,23 @@
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.computer.IComputerEnvironment;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.shared.util.Palette;
import org.apache.commons.lang3.ArrayUtils;
import javax.annotation.Nonnull;
import static dan200.computercraft.api.lua.ArgumentHelper.*;
public class TermAPI implements ILuaAPI
public class TermAPI extends TermMethods implements ILuaAPI
{
private final Terminal m_terminal;
private final IComputerEnvironment m_environment;
private final Terminal terminal;
private final IComputerEnvironment environment;
public TermAPI( IAPIEnvironment environment )
{
m_terminal = environment.getTerminal();
m_environment = environment.getComputerEnvironment();
terminal = environment.getTerminal();
this.environment = environment.getComputerEnvironment();
}
@Override
@@ -35,262 +31,29 @@ public class TermAPI implements ILuaAPI
return new String[] { "term" };
}
@LuaFunction( { "nativePaletteColour", "nativePaletteColor" } )
public final Object[] nativePaletteColour( int colourArg ) throws LuaException
{
int colour = 15 - parseColour( colourArg );
Colour c = Colour.fromInt( colour );
float[] rgb = c.getRGB();
Object[] rgbObj = new Object[rgb.length];
for( int i = 0; i < rgbObj.length; ++i ) rgbObj[i] = rgb[i];
return rgbObj;
}
@Nonnull
@Override
public String[] getMethodNames()
public Terminal getTerminal()
{
return new String[] {
"write",
"scroll",
"setCursorPos",
"setCursorBlink",
"getCursorPos",
"getSize",
"clear",
"clearLine",
"setTextColour",
"setTextColor",
"setBackgroundColour",
"setBackgroundColor",
"isColour",
"isColor",
"getTextColour",
"getTextColor",
"getBackgroundColour",
"getBackgroundColor",
"blit",
"setPaletteColour",
"setPaletteColor",
"getPaletteColour",
"getPaletteColor",
"nativePaletteColour",
"nativePaletteColor",
"getCursorBlink",
};
}
public static int parseColour( Object[] args ) throws LuaException
{
int colour = getInt( args, 0 );
if( colour <= 0 )
{
throw new LuaException( "Colour out of range" );
}
colour = getHighestBit( colour ) - 1;
if( colour < 0 || colour > 15 )
{
throw new LuaException( "Colour out of range" );
}
return colour;
}
public static Object[] encodeColour( int colour ) throws LuaException
{
return new Object[] { 1 << colour };
}
public static void setColour( Terminal terminal, int colour, double r, double g, double b )
{
if( terminal.getPalette() != null )
{
terminal.getPalette().setColour( colour, r, g, b );
terminal.setChanged();
}
return terminal;
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
public boolean isColour()
{
switch( method )
{
case 0:
{
// write
String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
synchronized( m_terminal )
{
m_terminal.write( text );
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
}
return null;
}
case 1:
{
// scroll
int y = getInt( args, 0 );
synchronized( m_terminal )
{
m_terminal.scroll( y );
}
return null;
}
case 2:
{
// setCursorPos
int x = getInt( args, 0 ) - 1;
int y = getInt( args, 1 ) - 1;
synchronized( m_terminal )
{
m_terminal.setCursorPos( x, y );
}
return null;
}
case 3:
{
// setCursorBlink
boolean b = getBoolean( args, 0 );
synchronized( m_terminal )
{
m_terminal.setCursorBlink( b );
}
return null;
}
case 4:
{
// getCursorPos
int x, y;
synchronized( m_terminal )
{
x = m_terminal.getCursorX();
y = m_terminal.getCursorY();
}
return new Object[] { x + 1, y + 1 };
}
case 5:
{
// getSize
int width, height;
synchronized( m_terminal )
{
width = m_terminal.getWidth();
height = m_terminal.getHeight();
}
return new Object[] { width, height };
}
case 6: // clear
synchronized( m_terminal )
{
m_terminal.clear();
}
return null;
case 7: // clearLine
synchronized( m_terminal )
{
m_terminal.clearLine();
}
return null;
case 8:
case 9:
{
// setTextColour/setTextColor
int colour = parseColour( args );
synchronized( m_terminal )
{
m_terminal.setTextColour( colour );
}
return null;
}
case 10:
case 11:
{
// setBackgroundColour/setBackgroundColor
int colour = parseColour( args );
synchronized( m_terminal )
{
m_terminal.setBackgroundColour( colour );
}
return null;
}
case 12:
case 13: // isColour/isColor
return new Object[] { m_environment.isColour() };
case 14:
case 15: // getTextColour/getTextColor
return encodeColour( m_terminal.getTextColour() );
case 16:
case 17: // getBackgroundColour/getBackgroundColor
return encodeColour( m_terminal.getBackgroundColour() );
case 18:
{
// blit
String text = getString( args, 0 );
String textColour = getString( args, 1 );
String backgroundColour = getString( args, 2 );
if( textColour.length() != text.length() || backgroundColour.length() != text.length() )
{
throw new LuaException( "Arguments must be the same length" );
}
synchronized( m_terminal )
{
m_terminal.blit( text, textColour, backgroundColour );
m_terminal.setCursorPos( m_terminal.getCursorX() + text.length(), m_terminal.getCursorY() );
}
return null;
}
case 19:
case 20:
{
// setPaletteColour/setPaletteColor
int colour = 15 - parseColour( args );
if( args.length == 2 )
{
int hex = getInt( args, 1 );
double[] rgb = Palette.decodeRGB8( hex );
setColour( m_terminal, colour, rgb[0], rgb[1], rgb[2] );
}
else
{
double r = getFiniteDouble( args, 1 );
double g = getFiniteDouble( args, 2 );
double b = getFiniteDouble( args, 3 );
setColour( m_terminal, colour, r, g, b );
}
return null;
}
case 21:
case 22:
{
// getPaletteColour/getPaletteColor
int colour = 15 - parseColour( args );
synchronized( m_terminal )
{
if( m_terminal.getPalette() != null )
{
return ArrayUtils.toObject( m_terminal.getPalette().getColour( colour ) );
}
}
return null;
}
case 23:
case 24:
{
// nativePaletteColour/nativePaletteColor
int colour = 15 - parseColour( args );
Colour c = Colour.fromInt( colour );
float[] rgb = c.getRGB();
Object[] rgbObj = new Object[rgb.length];
for( int i = 0; i < rgbObj.length; ++i ) rgbObj[i] = rgb[i];
return rgbObj;
}
case 25:
// getCursorBlink
return new Object[] { m_terminal.getCursorBlink() };
default:
return null;
}
}
private static int getHighestBit( int group )
{
int bit = 0;
while( group > 0 )
{
group >>= 1;
bit++;
}
return bit;
return environment.isColour();
}
}

View File

@@ -0,0 +1,222 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.util.Palette;
import dan200.computercraft.shared.util.StringUtil;
import org.apache.commons.lang3.ArrayUtils;
import javax.annotation.Nonnull;
/**
* A base class for all objects which interact with a terminal. Namely the {@link TermAPI} and monitors.
*/
public abstract class TermMethods
{
private static int getHighestBit( int group )
{
int bit = 0;
while( group > 0 )
{
group >>= 1;
bit++;
}
return bit;
}
@Nonnull
public abstract Terminal getTerminal() throws LuaException;
public abstract boolean isColour() throws LuaException;
@LuaFunction
public final void write( IArguments arguments ) throws LuaException
{
String text = StringUtil.toString( arguments.get( 0 ) );
Terminal terminal = getTerminal();
synchronized( terminal )
{
terminal.write( text );
terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
}
}
@LuaFunction
public final void scroll( int y ) throws LuaException
{
getTerminal().scroll( y );
}
@LuaFunction
public final Object[] getCursorPos() throws LuaException
{
Terminal terminal = getTerminal();
return new Object[] { terminal.getCursorX() + 1, terminal.getCursorY() + 1 };
}
@LuaFunction
public final void setCursorPos( int x, int y ) throws LuaException
{
Terminal terminal = getTerminal();
synchronized( terminal )
{
terminal.setCursorPos( x - 1, y - 1 );
}
}
@LuaFunction
public final boolean getCursorBlink() throws LuaException
{
return getTerminal().getCursorBlink();
}
@LuaFunction
public final void setCursorBlink( boolean blink ) throws LuaException
{
Terminal terminal = getTerminal();
synchronized( terminal )
{
terminal.setCursorBlink( blink );
}
}
@LuaFunction
public final Object[] getSize() throws LuaException
{
Terminal terminal = getTerminal();
return new Object[] { terminal.getWidth(), terminal.getHeight() };
}
@LuaFunction
public final void clear() throws LuaException
{
getTerminal().clear();
}
@LuaFunction
public final void clearLine() throws LuaException
{
getTerminal().clearLine();
}
@LuaFunction( { "getTextColour", "getTextColor" } )
public final int getTextColour() throws LuaException
{
return encodeColour( getTerminal().getTextColour() );
}
@LuaFunction( { "setTextColour", "setTextColor" } )
public final void setTextColour( int colourArg ) throws LuaException
{
int colour = parseColour( colourArg );
Terminal terminal = getTerminal();
synchronized( terminal )
{
terminal.setTextColour( colour );
}
}
@LuaFunction( { "getBackgroundColour", "getBackgroundColor" } )
public final int getBackgroundColour() throws LuaException
{
return encodeColour( getTerminal().getBackgroundColour() );
}
@LuaFunction( { "setBackgroundColour", "setBackgroundColor" } )
public final void setBackgroundColour( int colourArg ) throws LuaException
{
int colour = parseColour( colourArg );
Terminal terminal = getTerminal();
synchronized( terminal )
{
terminal.setBackgroundColour( colour );
}
}
@LuaFunction( { "isColour", "isColor" } )
public final boolean getIsColour() throws LuaException
{
return isColour();
}
@LuaFunction
public final void blit( String text, String textColour, String backgroundColour ) throws LuaException
{
if( textColour.length() != text.length() || backgroundColour.length() != text.length() )
{
throw new LuaException( "Arguments must be the same length" );
}
Terminal terminal = getTerminal();
synchronized( terminal )
{
terminal.blit( text, textColour, backgroundColour );
terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() );
}
}
@LuaFunction( { "setPaletteColour", "setPaletteColor" } )
public final void setPaletteColour( IArguments args ) throws LuaException
{
int colour = 15 - parseColour( args.getInt( 0 ) );
if( args.count() == 2 )
{
int hex = args.getInt( 1 );
double[] rgb = Palette.decodeRGB8( hex );
setColour( getTerminal(), colour, rgb[0], rgb[1], rgb[2] );
}
else
{
double r = args.getFiniteDouble( 1 );
double g = args.getFiniteDouble( 2 );
double b = args.getFiniteDouble( 3 );
setColour( getTerminal(), colour, r, g, b );
}
}
@LuaFunction( { "getPaletteColour", "getPaletteColor" } )
public final Object[] getPaletteColour( int colourArg ) throws LuaException
{
int colour = 15 - parseColour( colourArg );
Terminal terminal = getTerminal();
synchronized( terminal )
{
if( terminal.getPalette() != null )
{
return ArrayUtils.toObject( terminal.getPalette().getColour( colour ) );
}
}
return null;
}
public static int parseColour( int colour ) throws LuaException
{
if( colour <= 0 ) throw new LuaException( "Colour out of range" );
colour = getHighestBit( colour ) - 1;
if( colour < 0 || colour > 15 ) throw new LuaException( "Colour out of range" );
return colour;
}
public static int encodeColour( int colour )
{
return 1 << colour;
}
public static void setColour( Terminal terminal, int colour, double r, double g, double b )
{
if( terminal.getPalette() != null )
{
terminal.getPalette().setColour( colour, r, g, b );
terminal.setChanged();
}
}
}

View File

@@ -5,11 +5,10 @@
*/
package dan200.computercraft.core.apis.handles;
import com.google.common.collect.ObjectArrays;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
@@ -17,208 +16,205 @@ import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static dan200.computercraft.api.lua.ArgumentHelper.getInt;
import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean;
import java.util.Optional;
public class BinaryReadableHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
private static final String[] METHOD_NAMES = new String[] { "read", "readAll", "readLine", "close" };
private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
private final ReadableByteChannel m_reader;
private final SeekableByteChannel m_seekable;
private final ReadableByteChannel reader;
final SeekableByteChannel seekable;
private final ByteBuffer single = ByteBuffer.allocate( 1 );
public BinaryReadableHandle( ReadableByteChannel channel, Closeable closeable )
protected BinaryReadableHandle( ReadableByteChannel reader, SeekableByteChannel seekable, Closeable closeable )
{
super( closeable );
m_reader = channel;
m_seekable = asSeekable( channel );
this.reader = reader;
this.seekable = seekable;
}
public BinaryReadableHandle( ReadableByteChannel channel )
public static BinaryReadableHandle of( ReadableByteChannel channel, Closeable closeable )
{
this( channel, channel );
SeekableByteChannel seekable = asSeekable( channel );
return seekable == null ? new BinaryReadableHandle( channel, null, closeable ) : new Seekable( seekable, closeable );
}
@Nonnull
@Override
public String[] getMethodNames()
public static BinaryReadableHandle of( ReadableByteChannel channel )
{
return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
return of( channel, channel );
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
@LuaFunction
public final Object[] read( Optional<Integer> countArg ) throws LuaException
{
switch( method )
checkOpen();
try
{
case 0: // read
checkOpen();
try
{
if( args.length > 0 && args[0] != null )
{
int count = getInt( args, 0 );
if( count < 0 )
{
throw new LuaException( "Cannot read a negative number of bytes" );
}
else if( count == 0 && m_seekable != null )
{
return m_seekable.position() >= m_seekable.size() ? null : new Object[] { "" };
}
if( count <= BUFFER_SIZE )
{
ByteBuffer buffer = ByteBuffer.allocate( count );
int read = m_reader.read( buffer );
if( read < 0 ) return null;
return new Object[] { read < count ? Arrays.copyOf( buffer.array(), read ) : buffer.array() };
}
else
{
// Read the initial set of characters, failing if none are read.
ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
int read = m_reader.read( buffer );
if( read < 0 ) return null;
// If we failed to read "enough" here, let's just abort
if( read >= count || read < BUFFER_SIZE )
{
return new Object[] { Arrays.copyOf( buffer.array(), read ) };
}
// Build up an array of ByteBuffers. Hopefully this means we can perform less allocation
// than doubling up the buffer each time.
int totalRead = read;
List<ByteBuffer> parts = new ArrayList<>( 4 );
parts.add( buffer );
while( read >= BUFFER_SIZE && totalRead < count )
{
buffer = ByteBuffer.allocate( Math.min( BUFFER_SIZE, count - totalRead ) );
read = m_reader.read( buffer );
if( read < 0 ) break;
totalRead += read;
parts.add( buffer );
}
// Now just copy all the bytes across!
byte[] bytes = new byte[totalRead];
int pos = 0;
for( ByteBuffer part : parts )
{
System.arraycopy( part.array(), 0, bytes, pos, part.position() );
pos += part.position();
}
return new Object[] { bytes };
}
}
else
{
single.clear();
int b = m_reader.read( single );
return b == -1 ? null : new Object[] { single.get( 0 ) & 0xFF };
}
}
catch( IOException e )
{
return null;
}
case 1: // readAll
checkOpen();
try
{
int expected = 32;
if( m_seekable != null )
{
expected = Math.max( expected, (int) (m_seekable.size() - m_seekable.position()) );
}
ByteArrayOutputStream stream = new ByteArrayOutputStream( expected );
ByteBuffer buf = ByteBuffer.allocate( 8192 );
boolean readAnything = false;
while( true )
{
buf.clear();
int r = m_reader.read( buf );
if( r == -1 ) break;
readAnything = true;
stream.write( buf.array(), 0, r );
}
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
catch( IOException e )
{
return null;
}
case 2: // readLine
if( countArg.isPresent() )
{
checkOpen();
boolean withTrailing = optBoolean( args, 0, false );
try
int count = countArg.get();
if( count < 0 ) throw new LuaException( "Cannot read a negative number of bytes" );
if( count == 0 && seekable != null )
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
boolean readAnything = false, readRc = false;
while( true )
{
single.clear();
int read = m_reader.read( single );
if( read <= 0 )
{
// Nothing else to read, and we saw no \n. Return the array. If we saw a \r, then add it
// back.
if( readRc ) stream.write( '\r' );
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
readAnything = true;
byte chr = single.get( 0 );
if( chr == '\n' )
{
if( withTrailing )
{
if( readRc ) stream.write( '\r' );
stream.write( chr );
}
return new Object[] { stream.toByteArray() };
}
else
{
// We want to skip \r\n, but obviously need to include cases where \r is not followed by \n.
// Note, this behaviour is non-standard compliant (strictly speaking we should have no
// special logic for \r), but we preserve compatibility with EncodedReadableHandle and
// previous behaviour of the io library.
if( readRc ) stream.write( '\r' );
readRc = chr == '\r';
if( !readRc ) stream.write( chr );
}
}
return seekable.position() >= seekable.size() ? null : new Object[] { "" };
}
catch( IOException e )
if( count <= BUFFER_SIZE )
{
return null;
ByteBuffer buffer = ByteBuffer.allocate( count );
int read = reader.read( buffer );
if( read < 0 ) return null;
buffer.flip();
return new Object[] { buffer };
}
else
{
// Read the initial set of characters, failing if none are read.
ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
int read = reader.read( buffer );
if( read < 0 ) return null;
// If we failed to read "enough" here, let's just abort
if( read >= count || read < BUFFER_SIZE )
{
buffer.flip();
return new Object[] { buffer };
}
// Build up an array of ByteBuffers. Hopefully this means we can perform less allocation
// than doubling up the buffer each time.
int totalRead = read;
List<ByteBuffer> parts = new ArrayList<>( 4 );
parts.add( buffer );
while( read >= BUFFER_SIZE && totalRead < count )
{
buffer = ByteBuffer.allocate( Math.min( BUFFER_SIZE, count - totalRead ) );
read = reader.read( buffer );
if( read < 0 ) break;
totalRead += read;
parts.add( buffer );
}
// Now just copy all the bytes across!
byte[] bytes = new byte[totalRead];
int pos = 0;
for( ByteBuffer part : parts )
{
System.arraycopy( part.array(), 0, bytes, pos, part.position() );
pos += part.position();
}
return new Object[] { bytes };
}
}
case 3: // close
checkOpen();
close();
return null;
case 4: // seek
checkOpen();
return handleSeek( m_seekable, args );
default:
return null;
else
{
single.clear();
int b = reader.read( single );
return b == -1 ? null : new Object[] { single.get( 0 ) & 0xFF };
}
}
catch( IOException e )
{
return null;
}
}
@LuaFunction
public final Object[] readAll() throws LuaException
{
checkOpen();
try
{
int expected = 32;
if( seekable != null ) expected = Math.max( expected, (int) (seekable.size() - seekable.position()) );
ByteArrayOutputStream stream = new ByteArrayOutputStream( expected );
ByteBuffer buf = ByteBuffer.allocate( 8192 );
boolean readAnything = false;
while( true )
{
buf.clear();
int r = reader.read( buf );
if( r == -1 ) break;
readAnything = true;
stream.write( buf.array(), 0, r );
}
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
catch( IOException e )
{
return null;
}
}
@LuaFunction
public final Object[] readLine( Optional<Boolean> withTrailingArg ) throws LuaException
{
checkOpen();
boolean withTrailing = withTrailingArg.orElse( false );
try
{
ByteArrayOutputStream stream = new ByteArrayOutputStream();
boolean readAnything = false, readRc = false;
while( true )
{
single.clear();
int read = reader.read( single );
if( read <= 0 )
{
// Nothing else to read, and we saw no \n. Return the array. If we saw a \r, then add it
// back.
if( readRc ) stream.write( '\r' );
return readAnything ? new Object[] { stream.toByteArray() } : null;
}
readAnything = true;
byte chr = single.get( 0 );
if( chr == '\n' )
{
if( withTrailing )
{
if( readRc ) stream.write( '\r' );
stream.write( chr );
}
return new Object[] { stream.toByteArray() };
}
else
{
// We want to skip \r\n, but obviously need to include cases where \r is not followed by \n.
// Note, this behaviour is non-standard compliant (strictly speaking we should have no
// special logic for \r), but we preserve compatibility with EncodedReadableHandle and
// previous behaviour of the io library.
if( readRc ) stream.write( '\r' );
readRc = chr == '\r';
if( !readRc ) stream.write( chr );
}
}
}
catch( IOException e )
{
return null;
}
}
public static class Seekable extends BinaryReadableHandle
{
public Seekable( SeekableByteChannel seekable, Closeable closeable )
{
super( seekable, seekable, closeable );
}
@LuaFunction
public final Object[] seek( IArguments arguments ) throws LuaException
{
checkOpen();
return handleSeek( seekable, arguments );
}
}
}

View File

@@ -5,13 +5,11 @@
*/
package dan200.computercraft.core.apis.handles;
import com.google.common.collect.ObjectArrays;
import dan200.computercraft.api.lua.ArgumentHelper;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.shared.util.StringUtil;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.lua.LuaValues;
import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -21,87 +19,85 @@ import java.nio.channels.WritableByteChannel;
public class BinaryWritableHandle extends HandleGeneric
{
private static final String[] METHOD_NAMES = new String[] { "write", "flush", "close" };
private static final String[] METHOD_SEEK_NAMES = ObjectArrays.concat( METHOD_NAMES, new String[] { "seek" }, String.class );
private final WritableByteChannel m_writer;
private final SeekableByteChannel m_seekable;
private final WritableByteChannel writer;
final SeekableByteChannel seekable;
private final ByteBuffer single = ByteBuffer.allocate( 1 );
public BinaryWritableHandle( WritableByteChannel channel, Closeable closeable )
protected BinaryWritableHandle( WritableByteChannel writer, SeekableByteChannel seekable, Closeable closeable )
{
super( closeable );
m_writer = channel;
m_seekable = asSeekable( channel );
this.writer = writer;
this.seekable = seekable;
}
public BinaryWritableHandle( WritableByteChannel channel )
public static BinaryWritableHandle of( WritableByteChannel channel, Closeable closeable )
{
this( channel, channel );
SeekableByteChannel seekable = asSeekable( channel );
return seekable == null ? new BinaryWritableHandle( channel, null, closeable ) : new Seekable( seekable, closeable );
}
@Nonnull
@Override
public String[] getMethodNames()
public static BinaryWritableHandle of( WritableByteChannel channel )
{
return m_seekable == null ? METHOD_NAMES : METHOD_SEEK_NAMES;
return of( channel, channel );
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
@LuaFunction
public final void write( IArguments arguments ) throws LuaException
{
switch( method )
checkOpen();
try
{
case 0: // write
checkOpen();
try
{
if( args.length > 0 && args[0] instanceof Number )
{
int number = ((Number) args[0]).intValue();
single.clear();
single.put( (byte) number );
single.flip();
Object arg = arguments.get( 0 );
if( arg instanceof Number )
{
int number = ((Number) arg).intValue();
single.clear();
single.put( (byte) number );
single.flip();
m_writer.write( single );
}
else if( args.length > 0 && args[0] instanceof String )
{
String value = (String) args[0];
m_writer.write( ByteBuffer.wrap( StringUtil.encodeString( value ) ) );
}
else
{
throw ArgumentHelper.badArgumentOf( 0, "string or number", args.length > 0 ? args[0] : null );
}
return null;
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
case 1: // flush
checkOpen();
try
{
// Technically this is not needed
if( m_writer instanceof FileChannel ) ((FileChannel) m_writer).force( false );
writer.write( single );
}
else if( arg instanceof String )
{
writer.write( arguments.getBytes( 0 ) );
}
else
{
throw LuaValues.badArgumentOf( 0, "string or number", arg );
}
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
}
return null;
}
catch( IOException e )
{
return null;
}
case 2: // close
checkOpen();
close();
return null;
case 3: // seek
checkOpen();
return handleSeek( m_seekable, args );
default:
return null;
@LuaFunction
public final void flush() throws LuaException
{
checkOpen();
try
{
// Technically this is not needed
if( writer instanceof FileChannel ) ((FileChannel) writer).force( false );
}
catch( IOException ignored )
{
}
}
public static class Seekable extends BinaryWritableHandle
{
public Seekable( SeekableByteChannel seekable, Closeable closeable )
{
super( seekable, seekable, closeable );
}
@LuaFunction
public final Object[] seek( IArguments args ) throws LuaException
{
checkOpen();
return handleSeek( seekable, args );
}
}
}

View File

@@ -5,8 +5,8 @@
*/
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import javax.annotation.Nonnull;
import java.io.BufferedReader;
@@ -18,20 +18,18 @@ import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import static dan200.computercraft.api.lua.ArgumentHelper.optBoolean;
import static dan200.computercraft.api.lua.ArgumentHelper.optInt;
import java.util.Optional;
public class EncodedReadableHandle extends HandleGeneric
{
private static final int BUFFER_SIZE = 8192;
private BufferedReader m_reader;
private final BufferedReader reader;
public EncodedReadableHandle( @Nonnull BufferedReader reader, @Nonnull Closeable closable )
{
super( closable );
m_reader = reader;
this.reader = reader;
}
public EncodedReadableHandle( @Nonnull BufferedReader reader )
@@ -39,123 +37,107 @@ public class EncodedReadableHandle extends HandleGeneric
this( reader, reader );
}
@Nonnull
@Override
public String[] getMethodNames()
@LuaFunction
public final Object[] readLine( Optional<Boolean> withTrailingArg ) throws LuaException
{
return new String[] {
"readLine",
"readAll",
"read",
"close",
};
checkOpen();
boolean withTrailing = withTrailingArg.orElse( false );
try
{
String line = reader.readLine();
if( line != null )
{
// While this is technically inaccurate, it's better than nothing
if( withTrailing ) line += "\n";
return new Object[] { line };
}
else
{
return null;
}
}
catch( IOException e )
{
return null;
}
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
@LuaFunction
public final Object[] readAll() throws LuaException
{
switch( method )
checkOpen();
try
{
case 0: // readLine
StringBuilder result = new StringBuilder();
String line = reader.readLine();
while( line != null )
{
checkOpen();
boolean withTrailing = optBoolean( args, 0, false );
try
result.append( line );
line = reader.readLine();
if( line != null )
{
String line = m_reader.readLine();
if( line != null )
{
// While this is technically inaccurate, it's better than nothing
if( withTrailing ) line += "\n";
return new Object[] { line };
}
else
{
return null;
}
}
catch( IOException e )
{
return null;
result.append( "\n" );
}
}
case 1: // readAll
checkOpen();
try
return new Object[] { result.toString() };
}
catch( IOException e )
{
return null;
}
}
@LuaFunction
public final Object[] read( Optional<Integer> countA ) throws LuaException
{
checkOpen();
try
{
int count = countA.orElse( 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 = 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 = reader.read( buffer, 0, Math.min( buffer.length, count ) );
if( read < 0 ) return null;
StringBuilder out = new StringBuilder( read );
int totalRead = 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 && totalRead < count )
{
StringBuilder result = new StringBuilder();
String line = m_reader.readLine();
while( line != null )
{
result.append( line );
line = m_reader.readLine();
if( line != null )
{
result.append( "\n" );
}
}
return new Object[] { result.toString() };
read = reader.read( buffer, 0, Math.min( BUFFER_SIZE, count - totalRead ) );
if( read < 0 ) break;
totalRead += read;
out.append( buffer, 0, read );
}
catch( IOException e )
{
return null;
}
case 2: // 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 < 0 ) return null;
StringBuilder out = new StringBuilder( read );
int totalRead = 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 && totalRead < count )
{
read = m_reader.read( buffer, 0, Math.min( BUFFER_SIZE, count - totalRead ) );
if( read < 0 ) break;
totalRead += read;
out.append( buffer, 0, read );
}
return new Object[] { out.toString() };
}
}
catch( IOException e )
{
return null;
}
case 3: // close
checkOpen();
close();
return null;
default:
return null;
return new Object[] { out.toString() };
}
}
catch( IOException e )
{
return null;
}
}

View File

@@ -5,8 +5,10 @@
*/
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.shared.util.StringUtil;
import javax.annotation.Nonnull;
import java.io.BufferedWriter;
@@ -21,85 +23,57 @@ import java.nio.charset.StandardCharsets;
public class EncodedWritableHandle extends HandleGeneric
{
private BufferedWriter m_writer;
private final BufferedWriter writer;
public EncodedWritableHandle( @Nonnull BufferedWriter writer, @Nonnull Closeable closable )
{
super( closable );
m_writer = writer;
this.writer = writer;
}
public EncodedWritableHandle( @Nonnull BufferedWriter writer )
@LuaFunction
public final void write( IArguments args ) throws LuaException
{
this( writer, writer );
}
@Nonnull
@Override
public String[] getMethodNames()
{
return new String[] {
"write",
"writeLine",
"flush",
"close",
};
}
@Override
public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
{
switch( method )
checkOpen();
String text = StringUtil.toString( args.get( 0 ) );
try
{
case 0: // write
{
checkOpen();
String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
try
{
m_writer.write( text, 0, text.length() );
return null;
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
}
case 1: // writeLine
{
checkOpen();
String text = args.length > 0 && args[0] != null ? args[0].toString() : "";
try
{
m_writer.write( text, 0, text.length() );
m_writer.newLine();
return null;
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
}
case 2: // flush
checkOpen();
try
{
m_writer.flush();
return null;
}
catch( IOException e )
{
return null;
}
case 3: // close
checkOpen();
close();
return null;
default:
return null;
writer.write( text, 0, text.length() );
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final void writeLine( IArguments args ) throws LuaException
{
checkOpen();
String text = StringUtil.toString( args.get( 0 ) );
try
{
writer.write( text, 0, text.length() );
writer.newLine();
}
catch( IOException e )
{
throw new LuaException( e.getMessage() );
}
}
@LuaFunction
public final void flush() throws LuaException
{
checkOpen();
try
{
writer.flush();
}
catch( IOException ignored )
{
}
}
public static BufferedWriter openUtf8( WritableByteChannel channel )
{

View File

@@ -5,8 +5,9 @@
*/
package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.ILuaObject;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.shared.util.IoUtil;
import javax.annotation.Nonnull;
@@ -15,36 +16,37 @@ import java.io.IOException;
import java.nio.channels.Channel;
import java.nio.channels.SeekableByteChannel;
import static dan200.computercraft.api.lua.ArgumentHelper.optLong;
import static dan200.computercraft.api.lua.ArgumentHelper.optString;
public abstract class HandleGeneric implements ILuaObject
public abstract class HandleGeneric
{
private Closeable m_closable;
private boolean m_open = true;
private Closeable closable;
private boolean open = true;
protected HandleGeneric( @Nonnull Closeable closable )
{
m_closable = closable;
this.closable = closable;
}
protected void checkOpen() throws LuaException
{
if( !m_open ) throw new LuaException( "attempt to use a closed file" );
if( !open ) throw new LuaException( "attempt to use a closed file" );
}
protected final void close()
{
m_open = false;
open = false;
Closeable closeable = m_closable;
if( closeable != null )
{
IoUtil.closeQuietly( closeable );
m_closable = null;
}
IoUtil.closeQuietly( closable );
closable = null;
}
@LuaFunction( "close" )
public final void doClose() throws LuaException
{
checkOpen();
close();
}
/**
* Shared implementation for various file handle types.
*
@@ -54,12 +56,12 @@ public abstract class HandleGeneric implements ILuaObject
* @throws LuaException If the arguments were invalid
* @see <a href="https://www.lua.org/manual/5.1/manual.html#pdf-file:seek">{@code file:seek} in the Lua manual.</a>
*/
protected static Object[] handleSeek( SeekableByteChannel channel, Object[] args ) throws LuaException
protected static Object[] handleSeek( SeekableByteChannel channel, IArguments args ) throws LuaException
{
String whence = args.optString( 0, "cur" );
long offset = args.optLong( 1, 0 );
try
{
String whence = optString( args, 0, "cur" );
long offset = optLong( args, 1, 0 );
switch( whence )
{
case "set":

View File

@@ -7,6 +7,7 @@ package dan200.computercraft.core.apis.http;
import dan200.computercraft.core.apis.IAPIEnvironment;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.concurrent.Future;
@@ -46,12 +47,14 @@ public class CheckUrl extends Resource<CheckUrl>
try
{
NetworkUtils.getAddress( host, 80, false );
if( tryClose() ) environment.queueEvent( EVENT, new Object[] { address, true } );
InetSocketAddress netAddress = NetworkUtils.getAddress( host, 80, false );
NetworkUtils.getOptions( host, netAddress );
if( tryClose() ) environment.queueEvent( EVENT, address, true );
}
catch( HTTPRequestException e )
{
if( tryClose() ) environment.queueEvent( EVENT, new Object[] { address, false, e.getMessage() } );
if( tryClose() ) environment.queueEvent( EVENT, address, false, e.getMessage() );
}
}

View File

@@ -6,6 +6,9 @@
package dan200.computercraft.core.apis.http;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.apis.http.options.Action;
import dan200.computercraft.core.apis.http.options.AddressRule;
import dan200.computercraft.core.apis.http.options.Options;
import dan200.computercraft.shared.util.ThreadUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.EventLoopGroup;
@@ -15,7 +18,6 @@ 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.security.KeyStore;
import java.util.concurrent.ExecutorService;
@@ -97,20 +99,6 @@ public final class NetworkUtils
}
}
/**
* Checks a host is allowed.
*
* @param host The domain to check against
* @throws HTTPRequestException If the host is not permitted.
*/
public static void checkHost( String host ) throws HTTPRequestException
{
if( !ComputerCraft.http_whitelist.matches( host ) || ComputerCraft.http_blacklist.matches( host ) )
{
throw new HTTPRequestException( "Domain not permitted" );
}
}
/**
* Create a {@link InetSocketAddress} from the resolved {@code host} and port.
*
@@ -120,24 +108,31 @@ public final class NetworkUtils
* @param port The port, or -1 if not defined.
* @param ssl Whether to connect with SSL. This is used to find the default port if not otherwise specified.
* @return The resolved address.
* @throws HTTPRequestException If the host is not permitted.
* @throws HTTPRequestException If the host is not malformed.
*/
public static InetSocketAddress getAddress( String host, int port, boolean ssl ) throws HTTPRequestException
{
if( port < 0 ) port = ssl ? 443 : 80;
InetSocketAddress socketAddress = new InetSocketAddress( host, port );
if( socketAddress.isUnresolved() ) throw new HTTPRequestException( "Unknown host" );
InetAddress address = socketAddress.getAddress();
if( !ComputerCraft.http_whitelist.matches( address ) || ComputerCraft.http_blacklist.matches( address ) )
{
throw new HTTPRequestException( "Domain not permitted" );
}
return socketAddress;
}
/**
* Get options for a specific domain.
*
* @param host The host to resolve.
* @param address The address, resolved by {@link #getAddress(String, int, boolean)}.
* @return The options for this host.
* @throws HTTPRequestException If the host is not permitted
*/
public static Options getOptions( String host, InetSocketAddress address ) throws HTTPRequestException
{
Options options = AddressRule.apply( ComputerCraft.httpRules, host, address.getAddress() );
if( options.action == Action.DENY ) throw new HTTPRequestException( "Domain not permitted" );
return options;
}
/**
* Read a {@link ByteBuf} into a byte array.
*

View File

@@ -106,7 +106,7 @@ public abstract class Resource<T extends Resource<T>> implements Closeable
protected static <T extends Closeable> T closeCloseable( T closeable )
{
if( closeable != null ) IoUtil.closeQuietly( closeable );
IoUtil.closeQuietly( closeable );
return null;
}

View File

@@ -0,0 +1,23 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.http.options;
import javax.annotation.Nonnull;
public enum Action
{
ALLOW,
DENY;
private final PartialOptions partial = new PartialOptions( this, null, null, null, null );
@Nonnull
public PartialOptions toPartial()
{
return partial;
}
}

View File

@@ -0,0 +1,186 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.http.options;
import com.google.common.net.InetAddresses;
import dan200.computercraft.ComputerCraft;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.regex.Pattern;
/**
* A pattern which matches an address, and controls whether it is accessible or not.
*/
public final class AddressRule
{
public static final long MAX_DOWNLOAD = 16 * 1024 * 1024;
public static final long MAX_UPLOAD = 4 * 1024 * 1024;
public static final int TIMEOUT = 30_000;
public static final int WEBSOCKET_MESSAGE = 128 * 1024;
private static final class HostRange
{
private final byte[] min;
private final byte[] max;
private HostRange( byte[] min, byte[] max )
{
this.min = min;
this.max = max;
}
public boolean contains( InetAddress address )
{
byte[] entry = address.getAddress();
if( entry.length != min.length ) return false;
for( int i = 0; i < entry.length; i++ )
{
int value = 0xFF & entry[i];
if( value < (0xFF & min[i]) || value > (0xFF & max[i]) ) return false;
}
return true;
}
}
private final HostRange ip;
private final Pattern domainPattern;
private final PartialOptions partial;
private AddressRule( @Nullable HostRange ip, @Nullable Pattern domainPattern, @Nonnull PartialOptions partial )
{
this.ip = ip;
this.domainPattern = domainPattern;
this.partial = partial;
}
@Nullable
public static AddressRule parse( String filter, @Nonnull PartialOptions partial )
{
int cidr = filter.indexOf( '/' );
if( cidr >= 0 )
{
String addressStr = filter.substring( 0, cidr );
String prefixSizeStr = filter.substring( cidr + 1 );
int prefixSize;
try
{
prefixSize = Integer.parseInt( prefixSizeStr );
}
catch( NumberFormatException e )
{
ComputerCraft.log.error(
"Malformed http whitelist/blacklist entry '{}': Cannot extract size of CIDR mask from '{}'.",
filter, prefixSizeStr
);
return null;
}
InetAddress address;
try
{
address = InetAddresses.forString( addressStr );
}
catch( IllegalArgumentException e )
{
ComputerCraft.log.error(
"Malformed http whitelist/blacklist entry '{}': Cannot extract IP address from '{}'.",
filter, prefixSizeStr
);
return null;
}
// Mask the bytes of the IP address.
byte[] minBytes = address.getAddress(), maxBytes = address.getAddress();
int size = prefixSize;
for( int i = 0; i < minBytes.length; i++ )
{
if( size <= 0 )
{
minBytes[i] &= 0;
maxBytes[i] |= 0xFF;
}
else if( size < 8 )
{
minBytes[i] &= 0xFF << (8 - size);
maxBytes[i] |= ~(0xFF << (8 - size));
}
size -= 8;
}
return new AddressRule( new HostRange( minBytes, maxBytes ), null, partial );
}
else
{
Pattern pattern = Pattern.compile( "^\\Q" + filter.replaceAll( "\\*", "\\\\E.*\\\\Q" ) + "\\E$" );
return new AddressRule( null, pattern, partial );
}
}
/**
* Determine whether the given address matches a series of patterns.
*
* @param domain The domain to match
* @param address The address to check.
* @return Whether it matches any of these patterns.
*/
private boolean matches( String domain, InetAddress address )
{
if( domainPattern != null )
{
if( domainPattern.matcher( domain ).matches() ) return true;
if( domainPattern.matcher( address.getHostName() ).matches() ) return true;
}
// Match the normal address
if( matchesAddress( address ) ) return true;
// If we're an IPv4 address in disguise then let's check that.
return address instanceof Inet6Address && InetAddresses.is6to4Address( (Inet6Address) address )
&& matchesAddress( InetAddresses.get6to4IPv4Address( (Inet6Address) address ) );
}
private boolean matchesAddress( InetAddress address )
{
if( domainPattern != null && domainPattern.matcher( address.getHostAddress() ).matches() ) return true;
return ip != null && ip.contains( address );
}
public static Options apply( Iterable<? extends AddressRule> rules, String domain, InetAddress address )
{
PartialOptions options = null;
boolean hasMany = false;
for( AddressRule rule : rules )
{
if( !rule.matches( domain, address ) ) continue;
if( options == null )
{
options = rule.partial;
}
else
{
if( !hasMany )
{
options = options.copy();
hasMany = true;
}
options.merge( rule.partial );
}
}
return (options == null ? PartialOptions.DEFAULT : options).toOptions();
}
}

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