mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-15 14:07:38 +00:00
Compare commits
92 Commits
v1.91.2
...
v1.95.0-be
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2a290be248 | ||
![]() |
c4a6c16330 | ||
![]() |
f8b3b544d1 | ||
![]() |
53ae689468 | ||
![]() |
73e460e533 | ||
![]() |
a4e68d637a | ||
![]() |
1f1b20c81e | ||
![]() |
20a9ff7a3f | ||
![]() |
58cd5e3df7 | ||
![]() |
5922d7548c | ||
![]() |
386e01364e | ||
![]() |
70eff6aa63 | ||
![]() |
45ac601946 | ||
![]() |
d6c5a5bd27 | ||
![]() |
02c7903cb7 | ||
![]() |
37e856efdc | ||
![]() |
8a38097887 | ||
![]() |
884b417ec2 | ||
![]() |
1d1d6227eb | ||
![]() |
d5eb82db60 | ||
![]() |
3575654d02 | ||
![]() |
79fcc7241b | ||
![]() |
b203d3aa0a | ||
![]() |
f1176af9d1 | ||
![]() |
717686d855 | ||
![]() |
f8a58dbcb1 | ||
![]() |
98aabe2cfb | ||
![]() |
f938ed9779 | ||
![]() |
03c9274b27 | ||
![]() |
92c94ac039 | ||
![]() |
5c6fd80b0c | ||
![]() |
f1ec59df15 | ||
![]() |
624b23c7ac | ||
![]() |
9b77a4aaee | ||
![]() |
d8b0037cfa | ||
![]() |
91a9e7fdf7 | ||
![]() |
2c3573719b | ||
![]() |
46bc42d5a7 | ||
![]() |
1b9e93a80f | ||
![]() |
7b93f91ffa | ||
![]() |
31a1027401 | ||
![]() |
e0fc994819 | ||
![]() |
43408bf085 | ||
![]() |
d28f42e8b7 | ||
![]() |
ac452582c1 | ||
![]() |
7e65c6b25c | ||
![]() |
457a863842 | ||
![]() |
eef36e1358 | ||
![]() |
feda08b915 | ||
![]() |
0240ce50ce | ||
![]() |
592b83e784 | ||
![]() |
5d91491ec7 | ||
![]() |
7326d1110d | ||
![]() |
0aa6ac05a0 | ||
![]() |
27a2c063b9 | ||
![]() |
89a195ec06 | ||
![]() |
0e5fd4e8e0 | ||
![]() |
aa4ec53bb6 | ||
![]() |
6b49327462 | ||
![]() |
74ad934889 | ||
![]() |
08b3dbbad5 | ||
![]() |
54eac0a2f8 | ||
![]() |
51ca5e620c | ||
![]() |
076d572831 | ||
![]() |
75f2b91fef | ||
![]() |
79534e5630 | ||
![]() |
d483a91459 | ||
![]() |
71d764f122 | ||
![]() |
2e527eb11e | ||
![]() |
19273b3696 | ||
![]() |
4643641d51 | ||
![]() |
63cd9c5bc7 | ||
![]() |
e9c11ff325 | ||
![]() |
452464aa01 | ||
![]() |
8885462175 | ||
![]() |
b8bd64913b | ||
![]() |
00b458c39a | ||
![]() |
88722d484f | ||
![]() |
6d103e2114 | ||
![]() |
42f23d56ae | ||
![]() |
89d5211bd7 | ||
![]() |
83e70377f7 | ||
![]() |
f6a26f75c3 | ||
![]() |
664df62d5d | ||
![]() |
1348ee0588 | ||
![]() |
145dce7653 | ||
![]() |
7f2651c23e | ||
![]() |
05464107a8 | ||
![]() |
86705787f0 | ||
![]() |
b34d8387d9 | ||
![]() |
4d00969ef0 | ||
![]() |
01d3d12992 |
47
.github/workflows/main-ci.yml
vendored
Normal file
47
.github/workflows/main-ci.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Java 8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
|
||||
- name: Cache gradle dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('gradle.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build --no-daemon || ./gradlew build --no-daemon
|
||||
|
||||
- name: Upload Jar
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: cc-restiched
|
||||
path: build/libs
|
||||
|
||||
- name: Upload Coverage
|
||||
run: bash <(curl -s https://codecov.io/bash)
|
||||
continue-on-error: true
|
||||
|
||||
- name: Lint Lua code
|
||||
run: |
|
||||
test -d bin || mkdir bin
|
||||
test -f bin/illuaminate || wget -q -Obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
|
||||
chmod +x bin/illuaminate
|
||||
bin/illuaminate lint
|
||||
|
||||
- name: Check whitespace
|
||||
run: python3 tools/check-lines.py
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -9,9 +9,15 @@
|
||||
/run-*
|
||||
/test-files
|
||||
|
||||
# Autogenerated by IDE
|
||||
/bin
|
||||
/.settings
|
||||
.classpath
|
||||
|
||||
*.ipr
|
||||
*.iws
|
||||
*.iml
|
||||
.idea
|
||||
.gradle
|
||||
*.DS_Store
|
||||
.project
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
18
README.md
18
README.md
@@ -1,7 +1,17 @@
|
||||
# CC:T for Fabric
|
||||
# CC:Restitched Patchwork
|
||||
# This is a Work In Progress Port
|
||||
*it runs and works-ish*
|
||||
|
||||
* [Download on CurseForge](https://www.curseforge.com/minecraft/mc-mods/cc-restitched)
|
||||
PRs welcome
|
||||
|
||||
A fork of [CC: Tweaked](https://github.com/SquidDev-CC/CC-Tweaked) for use with the latest Fabric.
|
||||
## Known Issues
|
||||
Main Known issue
|
||||
* Mods that add blocks that can be used as peripherals for CC:T On forge, dont work with CC:R.
|
||||
* This is because of the differences between forge and fabric, and that mod devs, to my knowledge have not agreed upon a standard method in which to implement cross compatibility between mods,
|
||||
* Storage Peripherals throw a java "StackOverflowError" when using `pushItems()`,
|
||||
* Work around, you are probably using `pushItems(chest, 1)` or simular. please use `pushItems(chest, 1, nil, 1)`.
|
||||
|
||||
NOTE: This project is currently in alpha stage. It may not be well-tested and stable, so use it at your own peril!
|
||||
## Known Working mods that add Peripherals
|
||||
* Please let me know of other mods that work with this one
|
||||
* Better End
|
||||
* Better Nether
|
||||
|
12
build.gradle
12
build.gradle
@@ -9,7 +9,7 @@ targetCompatibility = JavaVersion.VERSION_1_8
|
||||
version = mod_version
|
||||
|
||||
group = "dan200.computercraft"
|
||||
archivesBaseName = "cc-tweaked-fabric-${mc_version}"
|
||||
archivesBaseName = "cc-restiched"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@@ -29,6 +29,8 @@ dependencies {
|
||||
modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}"
|
||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
||||
|
||||
compile 'com.electronwill.night-config:json:3.6.0'
|
||||
|
||||
modImplementation "me.shedaniel.cloth:config-2:${cloth_config_version}"
|
||||
modImplementation "io.github.prospector:modmenu:${modmenu_version}"
|
||||
|
||||
@@ -38,6 +40,9 @@ dependencies {
|
||||
implementation "blue.endless:jankson:${jankson_version}"
|
||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
|
||||
compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
|
||||
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
|
||||
|
||||
include "me.shedaniel.cloth:config-2:${cloth_config_version}"
|
||||
include "blue.endless:jankson:${jankson_version}"
|
||||
include 'javax.vecmath:vecmath:1.5.2'
|
||||
@@ -46,8 +51,9 @@ dependencies {
|
||||
|
||||
shade 'org.squiddev:Cobalt:0.5.1-SNAPSHOT'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.1.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.1.0'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.7.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
|
||||
|
||||
modRuntime "me.shedaniel:RoughlyEnoughItems-api:5.2.10"
|
||||
modRuntime "me.shedaniel:RoughlyEnoughItems:5.2.10"
|
||||
|
@@ -2,7 +2,7 @@
|
||||
org.gradle.jvmargs=-Xmx1G
|
||||
|
||||
# Mod properties
|
||||
mod_version=1.91.1
|
||||
mod_version=1.95.0-beta
|
||||
|
||||
# Minecraft properties
|
||||
mc_version=1.16.2
|
||||
|
539
patchwork.md
Normal file
539
patchwork.md
Normal file
@@ -0,0 +1,539 @@
|
||||
# Just my list of things I have ported over
|
||||
|
||||
Format for the changelog of ported stuff
|
||||
```
|
||||
commit // Shows commit from CC:T
|
||||
commit2 // Shows a commit that is the same thing, just a clean up, only if right after
|
||||
Title // Commit Title
|
||||
SubScript // Desc of commit
|
||||
```
|
||||
|
||||
If a edit that is present in CC:T is not needed, I will skip over it.
|
||||
Any and all references to an issue number, are to be found on CC:T's repo.
|
||||
|
||||
Any commit that starts with `[Patchwork]` are purely edits made by my hand, and not based on other commits from CC:T, this is to help differentiate my changes from the official changes
|
||||
|
||||
Lines that are found above a commit in this log like this one, (excluding this one) are comments about how i had to implement things that are not a simple 1:1 (excluding fabric/forge differences) conversion
|
||||
|
||||
```md
|
||||
5155e18de279a193c558aa029963486fd1294769
|
||||
Added translation for Vietnamese
|
||||
Co-authored-by: Boom <boom@flyingpackets.net>
|
||||
```
|
||||
|
||||
```
|
||||
7e121ff72f2b1504cd6af47b57500876682bac45
|
||||
ae6124d1f477487abab1858abde8c4ec49dfee3c
|
||||
Translations for Vienamese
|
||||
Co-authored-by: Boom <boom@flyingpackets.net>
|
||||
```
|
||||
|
||||
```
|
||||
59de21eae29849988e77fad6bc335f5ce78dfec7
|
||||
Handle tabs when parsing JSON
|
||||
Fixes #539
|
||||
```
|
||||
|
||||
```
|
||||
748ebbe66bf0a4239bde34f557e4b4b75d61d990
|
||||
Bump to 1.92.0
|
||||
A tiny release, but there's new features so it's technically a minor
|
||||
bump.
|
||||
```
|
||||
|
||||
Cherry Picked because this update was partially related to forge updates rather than mod updates
|
||||
```
|
||||
8b4a01df27ff7f6fa9ffd9c2188c6e3166edd515
|
||||
Update to Minecraft 1.16.3
|
||||
|
||||
I hope the Fabric folks now realise this is gonna be a race of who can
|
||||
update first :p. Either way, this was a very easy update - only changes
|
||||
were due to unrelated Forge changes.
|
||||
```
|
||||
|
||||
```
|
||||
87393e8aef9ddfaca465d626ee7cff5ff499a7e8
|
||||
Fix additional `-` in docs
|
||||
|
||||
Why isn't this automatically stripped! Bad squid.
|
||||
```
|
||||
|
||||
```
|
||||
275ca58a82c627128a145a8754cbe32568536bd9
|
||||
HTTP rules now allow filtering by port
|
||||
|
||||
The HTTP filtering system becomes even more complex! Though in this
|
||||
case, it's pretty minimal, and definitely worth doing.
|
||||
|
||||
For instance, the following rule will allow connecting to localhost on
|
||||
port :8080.
|
||||
|
||||
[[http.rules]]
|
||||
host = "127.0.0.1"
|
||||
port = 8080
|
||||
action = "allow"
|
||||
|
||||
# Other rules as before.
|
||||
|
||||
Closes #540
|
||||
```
|
||||
|
||||
The alterations in ColourUtils.java were not needed so they were not ported over
|
||||
```
|
||||
6f868849ab2f264508e12c184cc56f2632aaf5bc
|
||||
Use tags to check if something is a dye
|
||||
|
||||
We half did this already, just needed to change a couple of checks.
|
||||
Closes #541.
|
||||
```
|
||||
|
||||
```
|
||||
6cee4efcd3610536ee74330cd728f7371011e5a8
|
||||
Fix incorrect open container check
|
||||
|
||||
Was this always broken, or did it happen in a Minecraft update? Don't
|
||||
know, but it's a very silly mistake either way. Fixes #544
|
||||
```
|
||||
|
||||
```
|
||||
0832974725b2478c5227b81f82c35bbf03cf6aba
|
||||
Translations for Swedish
|
||||
|
||||
Co-authored-by: David Isaksson <davidisaksson93@gmail.com>
|
||||
```
|
||||
|
||||
```
|
||||
84036d97d99efd8762e0170002060ae3471508bf
|
||||
Fix io.open documentation
|
||||
|
||||
Well, that was silly.
|
||||
```
|
||||
|
||||
I set the default properties for computers as `Block.GLASS` and then set their strength to `2F` and their soundgroup to stone
|
||||
```
|
||||
8472112fc1eaad18ed6ed2c6c62b040fe421e81a
|
||||
Don't propagate adjacent redstone signals for computers (#549)
|
||||
|
||||
Minecraft propagates "strong" redstone signals (such as those directly
|
||||
from comparators or repeaters) through solid blocks. This includes
|
||||
computers, which is a little annoying as it means one cannot feed
|
||||
redstone wire from one side and a repeater from another.
|
||||
|
||||
This changes computers to not propagate strong redstone signals, in the
|
||||
same way transparent blocks like glass do.
|
||||
|
||||
Closes #548.
|
||||
```
|
||||
|
||||
```
|
||||
30d35883b83831900b34040f0131c7e06f5c3e52
|
||||
Fix my docs
|
||||
|
||||
Thanks @plt-hokusai. Kinda embarrassing this slipped through - I
|
||||
evidently need to lint examples too.
|
||||
```
|
||||
|
||||
```
|
||||
34a2c835d412c0d9e1fb20a42b7f2cd2738289c7
|
||||
Add color table to docs (#553)
|
||||
```
|
||||
|
||||
All API Documentation updates,
|
||||
`Not Needed` for this repo.
|
||||
```
|
||||
93068402a2ffec00eedb8fe2d859ebdc005a1989
|
||||
Document remaining OS functions (#554)
|
||||
|
||||
01d81cb91da938836f953b290ad6b8fc87cb7e35
|
||||
Update illuaminate CSS for deprecation (#556)
|
||||
```
|
||||
|
||||
```
|
||||
Not Needed
|
||||
4766833cf2d041ed179529eecb9402ad09b2b79b
|
||||
Bump JEI/crafttweaker versions
|
||||
|
||||
In my defence, they weren't out when I started the 1.15 update.
|
||||
```
|
||||
|
||||
```
|
||||
bf6053906dc6a3c7b0d40d5b097e745dce1f33bc
|
||||
Fix TBO norm issues on old GPUs
|
||||
```
|
||||
|
||||
```
|
||||
Not Needed
|
||||
113b560a201dbdea9de2a2ef536bcce1d6e51978
|
||||
Update configuration to match latest illuaminate
|
||||
|
||||
Ooooooh, it's all fancy now. Well, that or horrifically broken.
|
||||
```
|
||||
|
||||
```
|
||||
c334423d42ba3b653ac3a8c27bce7970457f8f96
|
||||
Add function to get window visibility
|
||||
|
||||
Closes #562
|
||||
|
||||
Co-authored-by: devomaa <lmao@distruzione.org>
|
||||
```
|
||||
|
||||
[WARN] Could not implement changes to the following files
|
||||
* `src/main/java/dan200/computercraft/ComputerCraft.java` < Structure too different, cannot find equivalent to alter
|
||||
* `src/main/java/dan200/computercraft/shared/Config.java` < Files Does not exist in this repo
|
||||
```
|
||||
84a6bb1cf3b0668ddc7d8c409a2477a42390e3f7
|
||||
Make generic peripherals on by default
|
||||
|
||||
This is a long way away from "feature complete" as it were. However,
|
||||
it's definitely at a point where it's suitable for general usage - I'm
|
||||
happy with the API, and don't think I'm going to be breaking things any
|
||||
time soon.
|
||||
|
||||
That said, things aren't exposed yet for Java-side public consumption. I
|
||||
was kinda waiting until working on Plethora to actually do that, but not
|
||||
sure if/when that'll happen.
|
||||
|
||||
If someone else wants to work on an integration mod (or just adding
|
||||
integrations for their own mod), do get in touch and I can work out how
|
||||
to expose this.
|
||||
|
||||
Closes #452
|
||||
```
|
||||
|
||||
```
|
||||
Not Needed
|
||||
6aae4e576621090840724e094aa25e51696530fc
|
||||
Remove superfluous imports
|
||||
|
||||
Hah, this is embarassing
|
||||
```
|
||||
|
||||
[TODO] [M3R1-01] Code has been applied, players still dont get achievments
|
||||
```
|
||||
f6160bdc57b3d9850607c2c7c2ce9734b4963478
|
||||
Fix players not getting advancements when they own turtles
|
||||
|
||||
When we construct a new ServerPlayerEntity (and thus TurtlePlayer), we
|
||||
get the current (global) advancement state and call .setPlayer() on it.
|
||||
|
||||
As grantCriterion blocks FakePlayers from getting advancements, this
|
||||
means a player will no longer receive any advancements, as the "wrong"
|
||||
player object is being consulted.
|
||||
|
||||
As a temporary work around, we attempt to restore the previous player to
|
||||
the advancement store. I'll try to upstream something into Forge to
|
||||
resolve this properly.
|
||||
|
||||
Fixes #564
|
||||
```
|
||||
|
||||
```
|
||||
17a932920711a5c0361a5048c9e0a5e7a58e6364
|
||||
Bump cct-javadoc version
|
||||
|
||||
Documentation will now be sorted (somewhat) correctly!
|
||||
```
|
||||
|
||||
```
|
||||
a6fcfb6af2fc1bef8ca3a19122c9267549202424
|
||||
Draw in-hand pocket computers with blending
|
||||
|
||||
It might be worth switching to RenderTypes here, rather than a pure
|
||||
Tesselator, but this'll do for now.
|
||||
|
||||
Fixes Zundrel/cc-tweaked-fabric#20.
|
||||
```
|
||||
|
||||
```
|
||||
c58441b29c3715f092e7f3747bb3ec65ae5a3d29
|
||||
Various SNBT parsing improvements
|
||||
|
||||
Correctly handle:
|
||||
- Typed arrays ([I; 1, 2, 3])
|
||||
- All suffixed numbers (1.2d)
|
||||
- Single-quoted strings
|
||||
|
||||
Fixes #559
|
||||
```
|
||||
|
||||
```
|
||||
e2a635b6e5f5942f999213434054e06833c5cb06
|
||||
Dont fail when codecov is being finicky
|
||||
```
|
||||
|
||||
```
|
||||
666e83cf4fd0eb327f465d5b919a708790f99b00
|
||||
Fix JSON objects failing to pass
|
||||
|
||||
Maybe I should run the whole test suite, not just the things I think
|
||||
matter? Nah....
|
||||
```
|
||||
|
||||
```
|
||||
741adfa7bb2b950d2851c3f0072d6a4769f22773
|
||||
|
||||
Use blit to draw boxes, add colors.toBlit (#570)
|
||||
```
|
||||
|
||||
```
|
||||
d13bd2cce8d102ad7f61f557e707d6fe3731bc37
|
||||
|
||||
use arg[0] in all usage printouts (#571)
|
||||
```
|
||||
|
||||
```
|
||||
74ac5bb3d17e5bee30643a5d6702696600c06229
|
||||
|
||||
Bump to 1.94.0
|
||||
```
|
||||
|
||||
[TODO] [M3R1-02] Zero Clue how to reimplement this in fabric.
|
||||
```
|
||||
c8aeddedd4ed430f9cb6428676ebb4fa39834182
|
||||
|
||||
Auto-generate monitor models
|
||||
|
||||
I didn't think it was worth it, and then I found myself needing to
|
||||
update a dozen of them. The code isn't especially pretty, but it works,
|
||||
so that's fine.
|
||||
|
||||
Also fixes several issues with us using the wrong texture (closes #572).
|
||||
I've put together a wiki page[1] which describes each texture in a
|
||||
little more detail.
|
||||
|
||||
[1] https://github.com/SquidDev-CC/CC-Tweaked/wiki/Monitor-texture-reference
|
||||
```
|
||||
|
||||
```
|
||||
7f90f2f7cadce0d5b9177b16626979591bce8137
|
||||
|
||||
Clean up some examples a little bit
|
||||
|
||||
Would be good if they didn't crash and burn on entry :).
|
||||
```
|
||||
|
||||
```
|
||||
f194f4fa3a17c48ff1a9088d50063f4a675a23b6
|
||||
|
||||
Fix epoch documentation to use milliseconds (#580)
|
||||
```
|
||||
|
||||
```
|
||||
d2a1a00dc43e5b65f6b64111ce76dd3db16c919f
|
||||
|
||||
Clear gets an option to reset the palette (#582)
|
||||
|
||||
Fixes #555.
|
||||
```
|
||||
|
||||
```
|
||||
aab0cd34cd64fdf837ff1c3b91a957a25c2cf7f9
|
||||
|
||||
Use term.blit on original paint render
|
||||
|
||||
This makes it super speedy, meaning an initial refresh doesn't take ages
|
||||
to load.
|
||||
```
|
||||
|
||||
```
|
||||
b0651082f472baee8f0fa8ec7ba95f433e2637bb
|
||||
|
||||
Cleanup examples for the various modules
|
||||
```
|
||||
|
||||
Ignored Documentation Changes, these are locate
|
||||
|
||||
```
|
||||
9a749642d294506095e697a3a4345dfe260bd68c
|
||||
|
||||
Strict Globals (#583)
|
||||
```
|
||||
|
||||
```
|
||||
fff8353451451be5ae31e0f63d8e529b127fd186
|
||||
|
||||
Remove extra space (#586)
|
||||
```
|
||||
|
||||
```
|
||||
486f41f08286ddcfad91d72b83a9361bd9c215cb
|
||||
|
||||
Fixed length check on function name in `expect` (#589)
|
||||
```
|
||||
|
||||
```
|
||||
04f9644ae75dafc72da4c4790f334d2e90b03e6f
|
||||
|
||||
Allow strings or numbers in textutils.*tabulate
|
||||
|
||||
A little dubious, but apparently CC used to support it. This means we're
|
||||
consistent with methods like io.write or string.len which accept strings
|
||||
or numbers.
|
||||
|
||||
Fixes #591
|
||||
```
|
||||
|
||||
```
|
||||
d4199064ae5ae8023c589f80f12d94e1c6bbc2b5
|
||||
|
||||
Make fs.combine accept multiple arguments
|
||||
|
||||
Means we can now do fs.combine("a", "b", "c"). Of course, one may just
|
||||
write "a/b/c" in this case, but it's definitely useful elsewhere.
|
||||
|
||||
This is /technically/ a breaking change as fs.combine(a, b:gsub(...))
|
||||
will no longer function (as gsub returns multiple arguments). However,
|
||||
I've done a quick search through GH and my Pastebin archives and can't
|
||||
find any programs which would break. Fingers crossed.
|
||||
```
|
||||
|
||||
```
|
||||
24af36743d08fcdb58439c52bf587b33ed828263
|
||||
|
||||
Try to handle a turtle being broken while ticked
|
||||
|
||||
Hopefully fixes #585. Hopefully.
|
||||
```
|
||||
|
||||
```
|
||||
511eea39a11956c82e2c11a47b2e7cad27f9887e
|
||||
|
||||
Remove <!-- -->s in usages
|
||||
```
|
||||
|
||||
```
|
||||
826797cbd579e867f0f35f0be44b6a28c8c094a9
|
||||
|
||||
Added documentation for global functions (#592)
|
||||
```
|
||||
Didn't port the docs over.
|
||||
|
||||
```
|
||||
d83a68f3ff6e3833278a38798d06215293656e85
|
||||
|
||||
Allow $private HTTP rule to block any private IP
|
||||
```
|
||||
The config still uses a `blacklist` and `whitelist` array.
|
||||
|
||||
```
|
||||
24d3777722812f975d2bc4594437fbbb0431d910
|
||||
|
||||
Added improved help viewer (#595)
|
||||
```
|
||||
Didn't port the lua tests over.
|
||||
|
||||
```
|
||||
737b3cb57696fb5517252e7db38bc88ce960b4d8
|
||||
|
||||
Don't use capabilities for generic peripherals
|
||||
```
|
||||
Not ported, related to forges capability system which is not used in the port.
|
||||
|
||||
```
|
||||
ea3a16036794357c3a44edffc90fdb652e03881e
|
||||
|
||||
Remove a couple of todos
|
||||
```
|
||||
|
||||
```
|
||||
bb8f4c624bf87169b73fb631d8250cfc38181e15
|
||||
|
||||
Some sanity checks for get{Direction,Orientation}
|
||||
```
|
||||
Use `getCachedState` instead of forge's `getBlockState` and `contains` instead of `has`.
|
||||
|
||||
```
|
||||
05c3c8ad3269c9025757f9261e7f609889fb6bdc
|
||||
|
||||
Generate docs for generic peripherals
|
||||
```
|
||||
Skipped everything except some removed whitespace.
|
||||
|
||||
```
|
||||
85cf2d5ff1b63010de4661301801aa504e5b9015
|
||||
|
||||
Docs for energy and inventory methods
|
||||
```
|
||||
and
|
||||
```
|
||||
5865e9c41a0140b9f1acdd2fb095353c467fbb45
|
||||
|
||||
Not sure what irritates me more
|
||||
```
|
||||
both skipped because the changes where already ported.
|
||||
|
||||
```
|
||||
4ae370b9dbaf1de0ed32a5f32340b1448136c9cc
|
||||
|
||||
Merge pull request #606 from TheWireLord/numpadenter-support
|
||||
```
|
||||
Just lua changes.
|
||||
|
||||
```
|
||||
f5eb6ce03e0d9bbbf77130452afd4b49e758f7bd
|
||||
|
||||
Fix copy-paste error in inventory docs
|
||||
```
|
||||
Skipped because it was already ported.
|
||||
|
||||
```
|
||||
663859d2e5a97edefebf9ac36206903d7dd33a3e
|
||||
Fix double URL decode
|
||||
```
|
||||
|
||||
```
|
||||
abf425dfb5553483cdc51c50a6b7d8b5e44814f4
|
||||
|
||||
Fix overflow in os.epoch
|
||||
```
|
||||
|
||||
```
|
||||
e3a672099c1b5d2c06f9fe4d8ccd024fef0873a2
|
||||
|
||||
Fix JEI integration with turtle/pocket upgrades
|
||||
```
|
||||
Skipped because there seems to be no REI integration.
|
||||
|
||||
```
|
||||
2f0cae0bc1b038ac092bafa7f65a317537203cd8
|
||||
|
||||
Make upgrade recipe requirements a little more lax
|
||||
```
|
||||
[TODO] [JUMT-01] Crafting is still messed up, but this port didn't change the behavior.
|
||||
[TODO] [JUMT-02] Tag comparison code doesn't need to be that verbose, a simple `isEqual` check would suffice.
|
||||
|
||||
```
|
||||
7f9a707f75636d5816f752dc93d7b6b998c61a03
|
||||
|
||||
Bump version to 1.95.0
|
||||
```
|
||||
Changed the name from CC: Tweaked to CC: Restitched in the changelog and whatsnew files. New version is 1.95.0-beta.
|
||||
|
||||
```
|
||||
4af5bcc0b0ff464e7e7428c389d47140580ea7a7
|
||||
|
||||
Fix serveral 1.15 -> 1.16 issues
|
||||
```
|
||||
Skipped, changes where already made.
|
||||
|
||||
```
|
||||
b8d5a89446ac02fc5b38cc5c0b4805de9d11a7d5
|
||||
|
||||
Add explicit @module annotation
|
||||
```
|
||||
Tiny lua change.
|
||||
|
||||
```
|
||||
8b17ec76a8e94251803e6f4ba4e65970c6a70b7f
|
||||
|
||||
Fixed missing argument names in file handle docs (#632)
|
||||
```
|
||||
A java doc change.
|
||||
|
||||
```
|
||||
e4b0a5b3ce035eb23feb4191432fc49af5772c5b
|
||||
|
||||
2020 -> 2021
|
||||
```
|
||||
A huge amount of changes.
|
@@ -9,4 +9,4 @@ pluginManagement {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "cc-tweaked-fabric-${mc_version}"
|
||||
rootProject.name = "cc-restiched"
|
||||
|
@@ -10,19 +10,18 @@ import static dan200.computercraft.shared.ComputerCraftRegistry.ModBlocks;
|
||||
import static dan200.computercraft.shared.ComputerCraftRegistry.init;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
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;
|
||||
|
||||
|
||||
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.core.apis.http.websocket.Websocket;
|
||||
import dan200.computercraft.core.asm.GenericSource;
|
||||
import dan200.computercraft.shared.common.ColourableRecipe;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputerRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
|
||||
@@ -33,23 +32,14 @@ import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
|
||||
import dan200.computercraft.shared.media.recipes.DiskRecipe;
|
||||
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||
import dan200.computercraft.shared.pocket.peripherals.PocketModem;
|
||||
import dan200.computercraft.shared.pocket.peripherals.PocketSpeaker;
|
||||
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
|
||||
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
|
||||
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
|
||||
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleAxe;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleCraftingTable;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleHoe;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleShovel;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleSpeaker;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleSword;
|
||||
import dan200.computercraft.shared.turtle.upgrades.TurtleTool;
|
||||
import dan200.computercraft.shared.util.Config;
|
||||
import dan200.computercraft.shared.util.ImpostorRecipe;
|
||||
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
|
||||
import dan200.computercraft.shared.util.ServiceUtil;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@@ -65,14 +55,6 @@ import net.fabricmc.loader.api.FabricLoader;
|
||||
public final class ComputerCraft implements ModInitializer {
|
||||
public static final String MOD_ID = "computercraft";
|
||||
// Configuration options
|
||||
public static final String[] DEFAULT_HTTP_WHITELIST = new String[] {"*"};
|
||||
public static final String[] DEFAULT_HTTP_BLACKLIST = new String[] {
|
||||
"127.0.0.0/8",
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"fd00::/8",
|
||||
};
|
||||
public static final int terminalWidth_computer = 51;
|
||||
public static final int terminalHeight_computer = 19;
|
||||
public static final int terminalWidth_turtle = 39;
|
||||
@@ -85,13 +67,6 @@ public final class ComputerCraft implements ModInitializer {
|
||||
// Logging
|
||||
public static final Logger log = LogManager.getLogger(MOD_ID);
|
||||
public static ItemGroup MAIN_GROUP = FabricItemGroupBuilder.build(new Identifier(MOD_ID, "main"), () -> new ItemStack(ModBlocks.COMPUTER_NORMAL));
|
||||
public static List<AddressRule> httpRules = Collections.unmodifiableList(Stream.concat(Stream.of(DEFAULT_HTTP_BLACKLIST)
|
||||
.map(x -> AddressRule.parse(x, Action.DENY.toPartial()))
|
||||
.filter(Objects::nonNull),
|
||||
Stream.of(DEFAULT_HTTP_WHITELIST)
|
||||
.map(x -> AddressRule.parse(x, Action.ALLOW.toPartial()))
|
||||
.filter(Objects::nonNull))
|
||||
.collect(Collectors.toList()));
|
||||
public static boolean commandRequireCreative = false;
|
||||
public static MonitorRenderer monitorRenderer = MonitorRenderer.BEST;
|
||||
public static int computerSpaceLimit = 1000 * 1000;
|
||||
@@ -106,8 +81,6 @@ public final class ComputerCraft implements ModInitializer {
|
||||
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 int httpTimeout = 30000;
|
||||
public static int httpMaxRequests = 16;
|
||||
public static long httpMaxDownload = 16 * 1024 * 1024;
|
||||
@@ -130,6 +103,11 @@ public final class ComputerCraft implements ModInitializer {
|
||||
public static int monitorHeight = 6;
|
||||
public static double monitorDistanceSq = 4096;
|
||||
|
||||
public static List<AddressRule> httpRules = Collections.unmodifiableList( Arrays.asList(
|
||||
AddressRule.parse( "$private", null, Action.DENY.toPartial() ),
|
||||
AddressRule.parse( "*", null, Action.ALLOW.toPartial() )
|
||||
) );
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
Config.load(Paths.get(FabricLoader.getInstance()
|
||||
@@ -152,6 +130,7 @@ public final class ComputerCraft implements ModInitializer {
|
||||
Registry.register(Registry.LOOT_CONDITION_TYPE, new Identifier(ComputerCraft.MOD_ID, "player_creative"), PlayerCreativeLootCondition.TYPE);
|
||||
Registry.register(Registry.LOOT_CONDITION_TYPE, new Identifier(ComputerCraft.MOD_ID, "has_id"), HasComputerIdLootCondition.TYPE);
|
||||
init();
|
||||
GenericSource.setup( () -> ServiceUtil.loadServices( GenericSource.class ));
|
||||
}
|
||||
|
||||
}
|
||||
|
81
src/main/java/dan200/computercraft/api/IUpgradeBase.java
Normal file
81
src/main/java/dan200/computercraft/api/IUpgradeBase.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package dan200.computercraft.api;
|
||||
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Common functionality between {@link ITurtleUpgrade} and {@link IPocketUpgrade}.
|
||||
*/
|
||||
public interface IUpgradeBase
|
||||
{
|
||||
/**
|
||||
* Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem"
|
||||
* or "my_mod:my_upgrade".
|
||||
*
|
||||
* You should use a unique resource domain to ensure this upgrade is uniquely identified.
|
||||
* The upgrade will fail registration if an already used ID is specified.
|
||||
*
|
||||
* @return The unique ID for this upgrade.
|
||||
*/
|
||||
@Nonnull
|
||||
Identifier getUpgradeID();
|
||||
|
||||
/**
|
||||
* Return an unlocalised string to describe this type of computer in item names.
|
||||
*
|
||||
* Examples of built-in adjectives are "Wireless", "Mining" and "Crafty".
|
||||
*
|
||||
* @return The localisation key for this upgrade's adjective.
|
||||
*/
|
||||
@Nonnull
|
||||
String getUnlocalisedAdjective();
|
||||
|
||||
/**
|
||||
* Return an item stack representing the type of item that a computer must be crafted
|
||||
* with to create a version which holds this upgrade. This item stack is also used
|
||||
* to determine the upgrade given by {@code turtle.equipLeft()} or {@code pocket.equipBack()}
|
||||
*
|
||||
* This should be constant over a session (or at least a datapack reload). It is recommended
|
||||
* that you cache the stack too, in order to prevent constructing it every time the method
|
||||
* is called.
|
||||
*
|
||||
* @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted.
|
||||
*/
|
||||
@Nonnull
|
||||
ItemStack getCraftingItem();
|
||||
|
||||
/**
|
||||
* Determine if an item is suitable for being used for this upgrade.
|
||||
*
|
||||
* When un-equipping an upgrade, we return {@link #getCraftingItem()} rather than
|
||||
* the original stack. In order to prevent people losing items with enchantments (or
|
||||
* repairing items with non-0 damage), we impose additional checks on the item.
|
||||
*
|
||||
* The default check requires that any non-capability NBT is exactly the same as the
|
||||
* crafting item, but this may be relaxed for your upgrade.
|
||||
*
|
||||
* @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
|
||||
* {@link #getCraftingItem()}.
|
||||
* @return If this stack may be used to equip this upgrade.
|
||||
* @see net.minecraftforge.common.crafting.NBTIngredient#test(ItemStack) For the implementation of the default
|
||||
* check.
|
||||
*/
|
||||
default boolean isItemSuitable( @Nonnull ItemStack stack )
|
||||
{
|
||||
ItemStack crafting = getCraftingItem();
|
||||
|
||||
// A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
|
||||
// null one.
|
||||
CompoundTag shareTag = stack.getTag();
|
||||
CompoundTag craftingShareTag = crafting.getTag();
|
||||
if( shareTag == craftingShareTag ) return true;
|
||||
if( shareTag == null ) return craftingShareTag.isEmpty();
|
||||
if( craftingShareTag == null ) return shareTag.isEmpty();
|
||||
return shareTag.equals( craftingShareTag );
|
||||
}
|
||||
}
|
@@ -10,56 +10,18 @@ import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.IUpgradeBase;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/**
|
||||
* Additional peripherals for pocket computers.
|
||||
*
|
||||
* This is similar to {@link ITurtleUpgrade}.
|
||||
* @see ComputerCraftAPI#registerPocketUpgrade(IPocketUpgrade)
|
||||
*/
|
||||
public interface IPocketUpgrade {
|
||||
|
||||
/**
|
||||
* Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem" or "my_mod:my_upgrade".
|
||||
*
|
||||
* You should use a unique resource domain to ensure this upgrade is uniquely identified. The upgrade will fail registration if an already used ID is
|
||||
* specified.
|
||||
*
|
||||
* @return The upgrade's id.
|
||||
* @see IPocketUpgrade#getUpgradeID()
|
||||
* @see ComputerCraftAPI#registerPocketUpgrade(IPocketUpgrade)
|
||||
*/
|
||||
@Nonnull
|
||||
Identifier getUpgradeID();
|
||||
|
||||
/**
|
||||
* Return an unlocalised string to describe the type of pocket computer this upgrade provides.
|
||||
*
|
||||
* An example of a built-in adjectives is "Wireless" - this is converted to "Wireless Pocket Computer".
|
||||
*
|
||||
* @return The unlocalised adjective.
|
||||
* @see ITurtleUpgrade#getUnlocalisedAdjective()
|
||||
*/
|
||||
@Nonnull
|
||||
String getUnlocalisedAdjective();
|
||||
|
||||
/**
|
||||
* Return an item stack representing the type of item that a pocket computer must be crafted with to create a pocket computer which holds this upgrade.
|
||||
* This item stack is also used to determine the upgrade given by {@code pocket.equip()}/{@code pocket.unequip()}.
|
||||
*
|
||||
* Ideally this should be constant over a session. It is recommended that you cache the item too, in order to prevent constructing it every time the
|
||||
* method is called.
|
||||
*
|
||||
* @return The item stack used for crafting. This can be {@link ItemStack#EMPTY} if crafting is disabled.
|
||||
*/
|
||||
@Nonnull
|
||||
ItemStack getCraftingItem();
|
||||
|
||||
public interface IPocketUpgrade extends IUpgradeBase
|
||||
{
|
||||
/**
|
||||
* Creates a peripheral for the pocket computer.
|
||||
*
|
||||
|
@@ -135,7 +135,7 @@ public interface ITurtleAccess {
|
||||
*
|
||||
* @return This turtle's owner.
|
||||
*/
|
||||
@Nonnull
|
||||
@Nullable
|
||||
GameProfile getOwningPlayer();
|
||||
|
||||
/**
|
||||
|
@@ -10,11 +10,10 @@ import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.IUpgradeBase;
|
||||
import dan200.computercraft.api.client.TransformedModel;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.Direction;
|
||||
|
||||
import net.fabricmc.api.EnvType;
|
||||
@@ -25,27 +24,8 @@ import net.fabricmc.api.Environment;
|
||||
*
|
||||
* @see ComputerCraftAPI#registerTurtleUpgrade(ITurtleUpgrade)
|
||||
*/
|
||||
public interface ITurtleUpgrade {
|
||||
/**
|
||||
* Gets a unique identifier representing this type of turtle upgrade. eg: "computercraft:wireless_modem" or "my_mod:my_upgrade". You should use a unique
|
||||
* resource domain to ensure this upgrade is uniquely identified. The turtle will fail registration if an already used ID is specified.
|
||||
*
|
||||
* @return The unique ID for this upgrade.
|
||||
* @see ComputerCraftAPI#registerTurtleUpgrade(ITurtleUpgrade)
|
||||
*/
|
||||
@Nonnull
|
||||
Identifier getUpgradeID();
|
||||
|
||||
/**
|
||||
* Return an unlocalised string to describe this type of turtle in turtle item names.
|
||||
*
|
||||
* Examples of built-in adjectives are "Wireless", "Mining" and "Crafty".
|
||||
*
|
||||
* @return The localisation key for this upgrade's adjective.
|
||||
*/
|
||||
@Nonnull
|
||||
String getUnlocalisedAdjective();
|
||||
|
||||
public interface ITurtleUpgrade extends IUpgradeBase
|
||||
{
|
||||
/**
|
||||
* Return whether this turtle adds a tool or a peripheral to the turtle.
|
||||
*
|
||||
@@ -55,18 +35,6 @@ public interface ITurtleUpgrade {
|
||||
@Nonnull
|
||||
TurtleUpgradeType getType();
|
||||
|
||||
/**
|
||||
* Return an item stack representing the type of item that a turtle must be crafted with to create a turtle which holds this upgrade. This item stack is
|
||||
* also used to determine the upgrade given by {@code turtle.equip()}
|
||||
*
|
||||
* Ideally this should be constant over a session. It is recommended that you cache the item too, in order to prevent constructing it every time the
|
||||
* method is called.
|
||||
*
|
||||
* @return The item stack to craft with, or {@link ItemStack#EMPTY} if it cannot be crafted.
|
||||
*/
|
||||
@Nonnull
|
||||
ItemStack getCraftingItem();
|
||||
|
||||
/**
|
||||
* Will only be called for peripheral upgrades. Creates a peripheral for a turtle being placed using this upgrade.
|
||||
*
|
||||
|
@@ -76,7 +76,6 @@ public final class ComputerCraftProxyClient implements ClientModInitializer {
|
||||
BlockEntityRendererRegistry.INSTANCE.register(ComputerCraftRegistry.ModTiles.MONITOR_ADVANCED, TileEntityMonitorRenderer::new);
|
||||
BlockEntityRendererRegistry.INSTANCE.register(ComputerCraftRegistry.ModTiles.TURTLE_NORMAL, TileEntityTurtleRenderer::new);
|
||||
BlockEntityRendererRegistry.INSTANCE.register(ComputerCraftRegistry.ModTiles.TURTLE_ADVANCED, TileEntityTurtleRenderer::new);
|
||||
// TODO: ClientRegistry.bindTileEntityRenderer( TileCable.FACTORY, x -> new TileEntityCableRenderer() );
|
||||
|
||||
ClientSpriteRegistryCallback.event(PlayerScreenHandler.BLOCK_ATLAS_TEXTURE)
|
||||
.register(ClientRegistry::onTextureStitchEvent);
|
||||
|
@@ -95,6 +95,7 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer {
|
||||
}
|
||||
|
||||
private static void renderFrame(Matrix4f transform, ComputerFamily family, int colour, int width, int height) {
|
||||
RenderSystem.enableBlend();
|
||||
MinecraftClient.getInstance()
|
||||
.getTextureManager()
|
||||
.bindTexture(colour != -1 ? ComputerBorderRenderer.BACKGROUND_COLOUR : ComputerBorderRenderer.getTexture(family));
|
||||
@@ -113,7 +114,6 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer {
|
||||
}
|
||||
|
||||
private static void renderLight(Matrix4f transform, int colour, int width, int height) {
|
||||
RenderSystem.enableBlend();
|
||||
RenderSystem.disableTexture();
|
||||
|
||||
float r = ((colour >>> 16) & 0xFF) / 255.0f;
|
||||
|
@@ -49,7 +49,6 @@ class MonitorTextureBufferShader {
|
||||
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);
|
||||
|
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.core.apis;
|
||||
|
||||
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;
|
||||
|
||||
import com.google.common.net.InetAddresses;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
|
||||
/**
|
||||
* Used to determine whether a domain or IP address matches a series of patterns.
|
||||
*/
|
||||
public class AddressPredicate {
|
||||
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 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 && this.matches(host)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Match the normal address
|
||||
if (this.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) && this.matchesAddress(InetAddresses.get6to4IPv4Address(
|
||||
(Inet6Address) address));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 : this.wildcards) {
|
||||
if (domainPattern.matcher(domain)
|
||||
.matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean matchesAddress(InetAddress address) {
|
||||
String addressString = address.getHostAddress();
|
||||
for (Pattern domainPattern : this.wildcards) {
|
||||
if (domainPattern.matcher(addressString)
|
||||
.matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (HostRange range : this.ranges) {
|
||||
if (range.contains(address)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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 != this.min.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < entry.length; i++) {
|
||||
int value = 0xFF & entry[i];
|
||||
if (value < (0xFF & this.min[i]) || value > (0xFF & this.max[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,6 +17,8 @@ import java.util.Map;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.function.Function;
|
||||
|
||||
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
@@ -75,15 +77,27 @@ public class FSAPI implements ILuaAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines two parts of a path into one full path, adding separators as needed.
|
||||
* Combines several parts of a path into one full path, adding separators as
|
||||
* needed
|
||||
*
|
||||
* @param pathA The first part of the path. For example, a parent directory path.
|
||||
* @param pathB The second part of the path. For example, a file name.
|
||||
* @param arguments The paths to combine.
|
||||
* @return The new path, with separators added between parts as needed.
|
||||
* @cc.tparam string path The first part of the path. For example, a parent directory path.
|
||||
* @cc.tparam string ... Additional parts of the path to combine.
|
||||
* @throws LuaException On argument errors.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final String combine(String pathA, String pathB) {
|
||||
return this.fileSystem.combine(pathA, pathB);
|
||||
public final String combine( IArguments arguments ) throws LuaException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(FileSystem.sanitizePath(arguments.getString(0), true));
|
||||
|
||||
for (int i = 1, n = arguments.count(); i < n; i++) {
|
||||
String part = FileSystem.sanitizePath(arguments.getString(i), true);
|
||||
if (result.length() != 0 && !part.isEmpty()) result.append('/');
|
||||
result.append(part);
|
||||
}
|
||||
|
||||
return FileSystem.sanitizePath(result.toString(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -326,14 +326,14 @@ public class OSAPI implements ILuaAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of seconds since an epoch depending on the locale.
|
||||
* Returns the number of milliseconds since an epoch depending on the locale.
|
||||
*
|
||||
* * If called with {@code ingame}, returns the number of seconds since the world was created. This is the default. * If called with {@code utc},
|
||||
* returns the number of seconds since 1 January 1970 in the UTC timezone. * If called with {@code local}, returns the number of seconds since 1 January
|
||||
* * If called with {@code ingame}, returns the number of milliseconds since the world was created. This is the default. * If called with {@code utc},
|
||||
* returns the number of milliseconds since 1 January 1970 in the UTC timezone. * If called with {@code local}, returns the number of seconds since 1 January
|
||||
* 1970 in the server's local timezone.
|
||||
*
|
||||
* @param args The locale to get the seconds for. Defaults to {@code ingame} if not set.
|
||||
* @return The seconds since the epoch depending on the selected locale.
|
||||
* @param args The locale to get the milliseconds for. Defaults to {@code ingame} if not set.
|
||||
* @return The milliseconds since the epoch depending on the selected locale.
|
||||
* @throws LuaException If an invalid locale is passed.
|
||||
*/
|
||||
@LuaFunction
|
||||
@@ -353,7 +353,7 @@ public class OSAPI implements ILuaAPI {
|
||||
case "ingame":
|
||||
// Get in-game epoch
|
||||
synchronized (this.m_alarms) {
|
||||
return this.m_day * 86400000 + (int) (this.m_time * 3600000.0f);
|
||||
return this.m_day * 86400000L + (long) (this.m_time * 3600000.0);
|
||||
}
|
||||
default:
|
||||
throw new LuaException("Unsupported operation");
|
||||
|
@@ -21,14 +21,14 @@ public class CheckUrl extends Resource<CheckUrl> {
|
||||
private static final String EVENT = "http_check";
|
||||
private final IAPIEnvironment environment;
|
||||
private final String address;
|
||||
private final String host;
|
||||
private final URI uri;
|
||||
private Future<?> future;
|
||||
|
||||
public CheckUrl(ResourceGroup<CheckUrl> limiter, IAPIEnvironment environment, String address, URI uri) {
|
||||
super(limiter);
|
||||
this.environment = environment;
|
||||
this.address = address;
|
||||
this.host = uri.getHost();
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
@@ -45,8 +45,9 @@ public class CheckUrl extends Resource<CheckUrl> {
|
||||
}
|
||||
|
||||
try {
|
||||
InetSocketAddress netAddress = NetworkUtils.getAddress(this.host, 80, false);
|
||||
NetworkUtils.getOptions(this.host, netAddress);
|
||||
boolean ssl = uri.getScheme().equalsIgnoreCase( "https" );
|
||||
InetSocketAddress netAddress = NetworkUtils.getAddress( uri, ssl );
|
||||
NetworkUtils.getOptions( uri.getHost(), netAddress );
|
||||
|
||||
if (this.tryClose()) {
|
||||
this.environment.queueEvent(EVENT, this.address, true);
|
||||
|
@@ -7,6 +7,7 @@
|
||||
package dan200.computercraft.core.apis.http;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.security.KeyStore;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
@@ -94,6 +95,21 @@ public final class NetworkUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link InetSocketAddress} from a {@link java.net.URI}.
|
||||
*
|
||||
* Note, this may require a DNS lookup, and so should not be executed on the main CC thread.
|
||||
*
|
||||
* @param uri The URI to fetch.
|
||||
* @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 malformed.
|
||||
*/
|
||||
public static InetSocketAddress getAddress( URI uri, boolean ssl ) throws HTTPRequestException
|
||||
{
|
||||
return getAddress( uri.getHost(), uri.getPort(), ssl );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link InetSocketAddress} from the resolved {@code host} and port.
|
||||
*
|
||||
@@ -125,7 +141,7 @@ public final class NetworkUtils {
|
||||
* @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());
|
||||
Options options = AddressRule.apply( ComputerCraft.httpRules, host, address );
|
||||
if (options.action == Action.DENY) {
|
||||
throw new HTTPRequestException("Domain not permitted");
|
||||
}
|
||||
|
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. 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 java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A predicate on an address. Matches against a domain and an ip address.
|
||||
*
|
||||
* @see AddressRule#apply(Iterable, String, InetSocketAddress) for the actual handling of this rule.
|
||||
*/
|
||||
interface AddressPredicate
|
||||
{
|
||||
default boolean matches( String domain )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean matches( InetAddress socketAddress )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
final class HostRange implements AddressPredicate
|
||||
{
|
||||
private final byte[] min;
|
||||
private final byte[] max;
|
||||
|
||||
HostRange( byte[] min, byte[] max )
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches( 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;
|
||||
}
|
||||
|
||||
public static HostRange parse( String addressStr, String prefixSizeStr )
|
||||
{
|
||||
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 '{}'.",
|
||||
addressStr + '/' + prefixSizeStr, 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 '{}'.",
|
||||
addressStr + '/' + prefixSizeStr, 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 HostRange( minBytes, maxBytes );
|
||||
}
|
||||
}
|
||||
|
||||
final class DomainPattern implements AddressPredicate
|
||||
{
|
||||
private final Pattern pattern;
|
||||
|
||||
DomainPattern( Pattern pattern )
|
||||
{
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches( String domain )
|
||||
{
|
||||
return pattern.matcher( domain ).matches();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches( InetAddress socketAddress )
|
||||
{
|
||||
return pattern.matcher( socketAddress.getHostAddress() ).matches();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class PrivatePattern implements AddressPredicate
|
||||
{
|
||||
static final PrivatePattern INSTANCE = new PrivatePattern();
|
||||
|
||||
@Override
|
||||
public boolean matches( InetAddress socketAddress )
|
||||
{
|
||||
return socketAddress.isAnyLocalAddress()
|
||||
|| socketAddress.isLoopbackAddress()
|
||||
|| socketAddress.isLinkLocalAddress()
|
||||
|| socketAddress.isSiteLocalAddress();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -6,15 +6,20 @@
|
||||
|
||||
package dan200.computercraft.core.apis.http.options;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.net.InetAddresses;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
|
||||
import dan200.computercraft.core.apis.http.options.AddressPredicate.DomainPattern;
|
||||
import dan200.computercraft.core.apis.http.options.AddressPredicate.HostRange;
|
||||
import dan200.computercraft.core.apis.http.options.AddressPredicate.PrivatePattern;
|
||||
|
||||
/**
|
||||
* A pattern which matches an address, and controls whether it is accessible or not.
|
||||
@@ -24,70 +29,45 @@ public final class AddressRule {
|
||||
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 final HostRange ip;
|
||||
private final Pattern domainPattern;
|
||||
|
||||
private final AddressPredicate predicate;
|
||||
private final Integer port;
|
||||
private final PartialOptions partial;
|
||||
private AddressRule(@Nullable HostRange ip, @Nullable Pattern domainPattern, @Nonnull PartialOptions partial) {
|
||||
this.ip = ip;
|
||||
this.domainPattern = domainPattern;
|
||||
private AddressRule( @Nonnull AddressPredicate predicate, @Nullable Integer port, @Nonnull PartialOptions partial ) {
|
||||
this.predicate = predicate;
|
||||
this.partial = partial;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AddressRule parse(String filter, @Nonnull PartialOptions partial) {
|
||||
public static AddressRule parse( String filter, @Nullable Integer port, @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);
|
||||
HostRange range = HostRange.parse( addressStr, prefixSizeStr );
|
||||
return range == null ? null : new AddressRule( range, port, partial );
|
||||
}
|
||||
else if( filter.equalsIgnoreCase( "$private" ) )
|
||||
{
|
||||
return new AddressRule( PrivatePattern.INSTANCE, port, partial );
|
||||
} else {
|
||||
Pattern pattern = Pattern.compile("^\\Q" + filter.replaceAll("\\*", "\\\\E.*\\\\Q") + "\\E$");
|
||||
return new AddressRule(null, pattern, partial);
|
||||
Pattern pattern = Pattern.compile( "^\\Q" + filter.replaceAll( "\\*", "\\\\E.*\\\\Q" ) + "\\E$", Pattern.CASE_INSENSITIVE );
|
||||
return new AddressRule( new DomainPattern( pattern ), port, partial );
|
||||
}
|
||||
}
|
||||
|
||||
public static Options apply(Iterable<? extends AddressRule> rules, String domain, InetAddress address) {
|
||||
public static Options apply( Iterable<? extends AddressRule> rules, String domain, InetSocketAddress socketAddress ) {
|
||||
PartialOptions options = null;
|
||||
boolean hasMany = false;
|
||||
|
||||
int port = socketAddress.getPort();
|
||||
InetAddress address = socketAddress.getAddress();
|
||||
Inet4Address ipv4Address = address instanceof Inet6Address && InetAddresses.is6to4Address( (Inet6Address) address )
|
||||
? InetAddresses.get6to4IPv4Address( (Inet6Address) address ) : null;
|
||||
|
||||
for (AddressRule rule : rules) {
|
||||
if (!rule.matches(domain, address)) {
|
||||
continue;
|
||||
}
|
||||
if( !rule.matches( domain, port, address, ipv4Address ) ) continue;
|
||||
|
||||
if (options == null) {
|
||||
options = rule.partial;
|
||||
@@ -108,62 +88,16 @@ public final class AddressRule {
|
||||
/**
|
||||
* Determine whether the given address matches a series of patterns.
|
||||
*
|
||||
* @param domain The domain to match
|
||||
* @param address The address to check.
|
||||
* @param domain The domain to match
|
||||
* @param port The port of the address.
|
||||
* @param address The address to check.
|
||||
* @param ipv4Address An ipv4 version of the address, if the original was an ipv6 address.
|
||||
* @return Whether it matches any of these patterns.
|
||||
*/
|
||||
private boolean matches(String domain, InetAddress address) {
|
||||
if (this.domainPattern != null) {
|
||||
if (this.domainPattern.matcher(domain)
|
||||
.matches()) {
|
||||
return true;
|
||||
}
|
||||
if (this.domainPattern.matcher(address.getHostName())
|
||||
.matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Match the normal address
|
||||
if (this.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) && this.matchesAddress(InetAddresses.get6to4IPv4Address((Inet6Address) address));
|
||||
}
|
||||
|
||||
private boolean matchesAddress(InetAddress address) {
|
||||
if (this.domainPattern != null && this.domainPattern.matcher(address.getHostAddress())
|
||||
.matches()) {
|
||||
return true;
|
||||
}
|
||||
return this.ip != null && this.ip.contains(address);
|
||||
}
|
||||
|
||||
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 != this.min.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < entry.length; i++) {
|
||||
int value = 0xFF & entry[i];
|
||||
if (value < (0xFF & this.min[i]) || value > (0xFF & this.max[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
private boolean matches(String domain, int port, InetAddress address, Inet4Address ipv4Address) {
|
||||
if( this.port != null && this.port != port ) return false;
|
||||
return predicate.matches( domain )
|
||||
|| predicate.matches( address )
|
||||
|| (ipv4Address != null && predicate.matches( ipv4Address ));
|
||||
}
|
||||
}
|
||||
|
@@ -6,116 +6,128 @@
|
||||
|
||||
package dan200.computercraft.core.apis.http.options;
|
||||
|
||||
public class AddressRuleConfig {
|
||||
// TODO haha config is gone, do fix
|
||||
|
||||
// public static UnmodifiableConfig makeRule( String host, Action action )
|
||||
// {
|
||||
// CommentedConfig config = InMemoryCommentedFormat.defaultInstance().createConfig( ConcurrentHashMap::new );
|
||||
// config.add( "host", host );
|
||||
// config.add( "action", action.name().toLowerCase( Locale.ROOT ) );
|
||||
//
|
||||
// if( host.equals( "*" ) && action == Action.ALLOW )
|
||||
// {
|
||||
// config.setComment( "timeout", "The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited." );
|
||||
// config.add( "timeout", AddressRule.TIMEOUT );
|
||||
//
|
||||
// config.setComment( "max_download", "The maximum size (in bytes) that a computer can download in a single request. Note that responses
|
||||
// may receive more data than allowed, but this data will not be returned to the client." );
|
||||
// config.set( "max_download", AddressRule.MAX_DOWNLOAD );
|
||||
//
|
||||
// config.setComment( "max_upload", "The maximum size (in bytes) that a computer can upload in a single request. This includes headers and
|
||||
// POST text." );
|
||||
// config.set( "max_upload", AddressRule.MAX_UPLOAD );
|
||||
//
|
||||
// config.setComment( "max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet." );
|
||||
// config.set( "max_websocket_message", AddressRule.WEBSOCKET_MESSAGE );
|
||||
// }
|
||||
//
|
||||
// return config;
|
||||
// }
|
||||
//
|
||||
// public static boolean checkRule( UnmodifiableConfig builder )
|
||||
// {
|
||||
// String hostObj = get( builder, "host", String.class ).orElse( null );
|
||||
// return hostObj != null && checkEnum( builder, "action", Action.class )
|
||||
// && check( builder, "timeout", Number.class )
|
||||
// && check( builder, "max_upload", Number.class )
|
||||
// && check( builder, "max_download", Number.class )
|
||||
// && check( builder, "websocket_message", Number.class )
|
||||
// && AddressRule.parse( hostObj, PartialOptions.DEFAULT ) != null;
|
||||
// }
|
||||
//
|
||||
// @Nullable
|
||||
// public static AddressRule parseRule( UnmodifiableConfig builder )
|
||||
// {
|
||||
// String hostObj = get( builder, "host", String.class ).orElse( null );
|
||||
// if( hostObj == null ) return null;
|
||||
//
|
||||
// Action action = getEnum( builder, "action", Action.class ).orElse( null );
|
||||
// Integer timeout = get( builder, "timeout", Number.class ).map( Number::intValue ).orElse( null );
|
||||
// Long maxUpload = get( builder, "max_upload", Number.class ).map( Number::longValue ).orElse( null );
|
||||
// Long maxDownload = get( builder, "max_download", Number.class ).map( Number::longValue ).orElse( null );
|
||||
// Integer websocketMessage = get( builder, "websocket_message", Number.class ).map( Number::intValue ).orElse( null );
|
||||
//
|
||||
// PartialOptions options = new PartialOptions(
|
||||
// action,
|
||||
// maxUpload,
|
||||
// maxDownload,
|
||||
// timeout,
|
||||
// websocketMessage
|
||||
// );
|
||||
//
|
||||
// return AddressRule.parse( hostObj, options );
|
||||
// }
|
||||
//
|
||||
// private static <T> boolean check( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
// {
|
||||
// Object value = config.get( field );
|
||||
// if( value == null || klass.isInstance( value ) ) return true;
|
||||
//
|
||||
// ComputerCraft.log.warn( "HTTP rule's {} is not a {}.", field, klass.getSimpleName() );
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// private static <T extends Enum<T>> boolean checkEnum( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
// {
|
||||
// Object value = config.get( field );
|
||||
// if( value == null ) return true;
|
||||
//
|
||||
// if( !(value instanceof String) )
|
||||
// {
|
||||
// ComputerCraft.log.warn( "HTTP rule's {} is not a string", field );
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// if( parseEnum( klass, (String) value ) == null )
|
||||
// {
|
||||
// ComputerCraft.log.warn( "HTTP rule's {} is not a known option", field );
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// private static <T> Optional<T> get( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
// {
|
||||
// Object value = config.get( field );
|
||||
// return klass.isInstance( value ) ? Optional.of( klass.cast( value ) ) : Optional.empty();
|
||||
// }
|
||||
//
|
||||
// private static <T extends Enum<T>> Optional<T> getEnum( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
// {
|
||||
// return get( config, field, String.class ).map( x -> parseEnum( klass, x ) );
|
||||
// }
|
||||
//
|
||||
// @Nullable
|
||||
// private static <T extends Enum<T>> T parseEnum( Class<T> klass, String x )
|
||||
// {
|
||||
// for( T value : klass.getEnumConstants() )
|
||||
// {
|
||||
// if( value.name().equalsIgnoreCase( x ) ) return value;
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||
import com.electronwill.nightconfig.core.InMemoryCommentedFormat;
|
||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
public class AddressRuleConfig {
|
||||
|
||||
public static UnmodifiableConfig makeRule( String host, Action action )
|
||||
{
|
||||
CommentedConfig config = InMemoryCommentedFormat.defaultInstance().createConfig( ConcurrentHashMap::new );
|
||||
config.add( "host", host );
|
||||
config.add( "action", action.name().toLowerCase( Locale.ROOT ) );
|
||||
|
||||
if( host.equals( "*" ) && action == Action.ALLOW )
|
||||
{
|
||||
config.setComment( "timeout", "The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited." );
|
||||
config.add( "timeout", AddressRule.TIMEOUT );
|
||||
|
||||
config.setComment( "max_download", "The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client." );
|
||||
config.set( "max_download", AddressRule.MAX_DOWNLOAD );
|
||||
|
||||
config.setComment( "max_upload", "The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text." );
|
||||
config.set( "max_upload", AddressRule.MAX_UPLOAD );
|
||||
|
||||
config.setComment( "max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet." );
|
||||
config.set( "max_websocket_message", AddressRule.WEBSOCKET_MESSAGE );
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public static boolean checkRule( UnmodifiableConfig builder )
|
||||
{
|
||||
String hostObj = get( builder, "host", String.class ).orElse( null );
|
||||
Integer port = get( builder, "port", Number.class ).map( Number::intValue ).orElse( null );
|
||||
return hostObj != null && checkEnum( builder, "action", Action.class )
|
||||
&& check( builder, "port", Number.class )
|
||||
&& check( builder, "timeout", Number.class )
|
||||
&& check( builder, "max_upload", Number.class )
|
||||
&& check( builder, "max_download", Number.class )
|
||||
&& check( builder, "websocket_message", Number.class )
|
||||
&& AddressRule.parse( hostObj, port, PartialOptions.DEFAULT ) != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static AddressRule parseRule( UnmodifiableConfig builder )
|
||||
{
|
||||
String hostObj = get( builder, "host", String.class ).orElse( null );
|
||||
Integer port = get( builder, "port", Number.class ).map( Number::intValue ).orElse( null );
|
||||
if( hostObj == null ) return null;
|
||||
|
||||
Action action = getEnum( builder, "action", Action.class ).orElse( null );
|
||||
Integer timeout = get( builder, "timeout", Number.class ).map( Number::intValue ).orElse( null );
|
||||
Long maxUpload = get( builder, "max_upload", Number.class ).map( Number::longValue ).orElse( null );
|
||||
Long maxDownload = get( builder, "max_download", Number.class ).map( Number::longValue ).orElse( null );
|
||||
Integer websocketMessage = get( builder, "websocket_message", Number.class ).map( Number::intValue ).orElse( null );
|
||||
|
||||
PartialOptions options = new PartialOptions(
|
||||
action,
|
||||
maxUpload,
|
||||
maxDownload,
|
||||
timeout,
|
||||
websocketMessage
|
||||
);
|
||||
|
||||
return AddressRule.parse( hostObj, port, options );
|
||||
}
|
||||
|
||||
private static <T> boolean check( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
{
|
||||
Object value = config.get( field );
|
||||
if( value == null || klass.isInstance( value ) ) return true;
|
||||
|
||||
ComputerCraft.log.warn( "HTTP rule's {} is not a {}.", field, klass.getSimpleName() );
|
||||
return false;
|
||||
}
|
||||
|
||||
private static <T extends Enum<T>> boolean checkEnum( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
{
|
||||
Object value = config.get( field );
|
||||
if( value == null ) return true;
|
||||
|
||||
if( !(value instanceof String) )
|
||||
{
|
||||
ComputerCraft.log.warn( "HTTP rule's {} is not a string", field );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( parseEnum( klass, (String) value ) == null )
|
||||
{
|
||||
ComputerCraft.log.warn( "HTTP rule's {} is not a known option", field );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static <T> Optional<T> get( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
{
|
||||
Object value = config.get( field );
|
||||
return klass.isInstance( value ) ? Optional.of( klass.cast( value ) ) : Optional.empty();
|
||||
}
|
||||
|
||||
private static <T extends Enum<T>> Optional<T> getEnum( UnmodifiableConfig config, String field, Class<T> klass )
|
||||
{
|
||||
return get( config, field, String.class ).map( x -> parseEnum( klass, x ) );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static <T extends Enum<T>> T parseEnum( Class<T> klass, String x )
|
||||
{
|
||||
for( T value : klass.getEnumConstants() )
|
||||
{
|
||||
if( value.name().equalsIgnoreCase( x ) ) return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -131,7 +131,7 @@ public class HttpRequest extends Resource<HttpRequest> {
|
||||
try {
|
||||
boolean ssl = uri.getScheme()
|
||||
.equalsIgnoreCase("https");
|
||||
InetSocketAddress socketAddress = NetworkUtils.getAddress(uri.getHost(), uri.getPort(), ssl);
|
||||
InetSocketAddress socketAddress = NetworkUtils.getAddress(uri, ssl);
|
||||
Options options = NetworkUtils.getOptions(uri.getHost(), socketAddress);
|
||||
SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null;
|
||||
|
||||
|
@@ -9,10 +9,8 @@ package dan200.computercraft.core.apis.http.request;
|
||||
import static dan200.computercraft.core.apis.http.request.HttpRequest.getHeaderSize;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
@@ -228,8 +226,8 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
|
||||
}
|
||||
|
||||
try {
|
||||
return this.uri.resolve(new URI(URLDecoder.decode(location, "UTF-8")));
|
||||
} catch (UnsupportedEncodingException | IllegalArgumentException | URISyntaxException e) {
|
||||
return this.uri.resolve(new URI( location ));
|
||||
} catch( IllegalArgumentException | URISyntaxException e ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -117,7 +117,7 @@ public class Websocket extends Resource<Websocket> {
|
||||
boolean ssl = this.uri.getScheme()
|
||||
.equalsIgnoreCase("wss");
|
||||
|
||||
InetSocketAddress socketAddress = NetworkUtils.getAddress(this.uri.getHost(), this.uri.getPort(), ssl);
|
||||
InetSocketAddress socketAddress = NetworkUtils.getAddress(uri, ssl);
|
||||
Options options = NetworkUtils.getOptions(this.uri.getHost(), socketAddress);
|
||||
SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null;
|
||||
|
||||
|
@@ -76,7 +76,7 @@ public class FileSystem {
|
||||
this.mounts.put(location, wrapper);
|
||||
}
|
||||
|
||||
private static String sanitizePath(String path, boolean allowWildcards) {
|
||||
public static String sanitizePath(String path, boolean allowWildcards) {
|
||||
// Allow windowsy slashes
|
||||
path = path.replace('\\', '/');
|
||||
|
||||
@@ -231,7 +231,7 @@ public class FileSystem {
|
||||
this.mounts.remove(sanitizePath(path));
|
||||
}
|
||||
|
||||
public synchronized String combine(String path, String childPath) {
|
||||
public String combine( String path, String childPath ) {
|
||||
path = sanitizePath(path, true);
|
||||
childPath = sanitizePath(childPath, true);
|
||||
|
||||
|
@@ -106,7 +106,7 @@ public class FileSystemWrapperMount implements IFileSystem {
|
||||
@Override
|
||||
public boolean isDirectory(@Nonnull String path) throws IOException {
|
||||
try {
|
||||
return this.m_filesystem.exists(path);
|
||||
return this.m_filesystem.isDir(path);
|
||||
} catch (FileSystemException e) {
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
|
@@ -55,6 +55,7 @@ import dan200.computercraft.shared.turtle.items.ItemTurtle;
|
||||
import dan200.computercraft.shared.turtle.upgrades.*;
|
||||
import dan200.computercraft.shared.util.FixedPointTileEntityType;
|
||||
|
||||
import net.minecraft.block.AbstractBlock;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.Material;
|
||||
@@ -68,6 +69,7 @@ import net.minecraft.item.ItemGroup;
|
||||
import net.minecraft.item.Items;
|
||||
import net.minecraft.screen.ScreenHandler;
|
||||
import net.minecraft.screen.ScreenHandlerType;
|
||||
import net.minecraft.sound.BlockSoundGroup;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
|
||||
@@ -121,8 +123,12 @@ public final class ComputerCraftRegistry {
|
||||
public static final BlockCable CABLE = register("cable", new BlockCable(emProperties()));
|
||||
|
||||
private static Block.Settings properties() {
|
||||
return FabricBlockSettings.copyOf(Blocks.STONE)
|
||||
.strength(2);
|
||||
//return FabricBlockSettings.copyOf(Blocks.GLASS)
|
||||
// .strength(2);
|
||||
return AbstractBlock.Settings.of(Material.GLASS)
|
||||
.strength(2F)
|
||||
.sounds(BlockSoundGroup.STONE)
|
||||
.nonOpaque();
|
||||
}
|
||||
|
||||
private static Block.Settings turtleProperties() {
|
||||
|
@@ -17,6 +17,7 @@ import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheralProvider;
|
||||
|
||||
import dan200.computercraft.shared.peripheral.generic.GenericPeripheralProvider;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
@@ -53,7 +54,7 @@ public final class Peripherals {
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return GenericPeripheralProvider.getPeripheral(world, pos, side);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -59,7 +59,8 @@ public final class PocketUpgrades {
|
||||
|
||||
for (IPocketUpgrade upgrade : upgrades.values()) {
|
||||
ItemStack craftingStack = upgrade.getCraftingItem();
|
||||
if (!craftingStack.isEmpty() && InventoryUtil.areItemsSimilar(stack, craftingStack)) {
|
||||
if( !craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && upgrade.isItemSuitable( stack ) )
|
||||
{
|
||||
return upgrade;
|
||||
}
|
||||
}
|
||||
|
@@ -19,7 +19,6 @@ import javax.annotation.Nullable;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
@@ -96,7 +95,8 @@ public final class TurtleUpgrades {
|
||||
}
|
||||
|
||||
ItemStack craftingStack = wrapper.upgrade.getCraftingItem();
|
||||
if (!craftingStack.isEmpty() && InventoryUtil.areItemsSimilar(stack, craftingStack)) {
|
||||
if( !craftingStack.isEmpty() && craftingStack.getItem() == stack.getItem() && wrapper.upgrade.isItemSuitable( stack ) )
|
||||
{
|
||||
return wrapper.upgrade;
|
||||
}
|
||||
}
|
||||
|
@@ -117,7 +117,6 @@ public class CommandAPI implements ILuaAPI {
|
||||
* @param command The command to execute.
|
||||
* @return The "task id". When this command has been executed, it will queue a `task_complete` event with a matching id.
|
||||
* @throws LuaException (hidden) If the task cannot be created.
|
||||
* @cc.tparam string command The command to execute.
|
||||
* @cc.usage Asynchronously sets the block above the computer to stone.
|
||||
* <pre>
|
||||
* commands.execAsync("~ ~1 ~ minecraft:stone")
|
||||
|
@@ -155,7 +155,8 @@ public class DiskDrivePeripheral implements IPeripheral {
|
||||
/**
|
||||
* Returns the title of the inserted audio disk.
|
||||
*
|
||||
* @return The title of the audio, or {@code nil} if no audio disk is inserted.
|
||||
* @return The title of the audio, or {@code false} if no audio disk is inserted.
|
||||
* @cc.treturn string|nil|false The title of the audio, {@code false} if no disk is inserted, or {@code nil} if the disk has no audio.
|
||||
*/
|
||||
@LuaFunction
|
||||
@Nullable
|
||||
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.peripheral.generic;
|
||||
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IDynamicPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.block.entity.BlockEntityType;
|
||||
import net.minecraft.block.entity.LockableContainerBlockEntity;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.Nameable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
class GenericPeripheral implements IDynamicPeripheral
|
||||
{
|
||||
private final String type;
|
||||
private final BlockEntity tile;
|
||||
private final List<SaturatedMethod> methods;
|
||||
|
||||
GenericPeripheral( BlockEntity tile, List<SaturatedMethod> methods )
|
||||
{
|
||||
Identifier type = BlockEntityType.getId(tile.getType());
|
||||
this.tile = tile;
|
||||
this.type = type == null ? "unknown" : type.toString();
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String[] getMethodNames()
|
||||
{
|
||||
String[] names = new String[methods.size()];
|
||||
for( int i = 0; i < methods.size(); i++ ) names[i] = methods.get( i ).getName();
|
||||
return names;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MethodResult callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments ) throws LuaException
|
||||
{
|
||||
return methods.get( method ).apply( context, computer, arguments );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getTarget()
|
||||
{
|
||||
return tile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( @Nullable IPeripheral other )
|
||||
{
|
||||
if( other == this ) return true;
|
||||
if( !(other instanceof GenericPeripheral) ) return false;
|
||||
|
||||
GenericPeripheral generic = (GenericPeripheral) other;
|
||||
return tile == generic.tile && methods.equals( generic.methods );
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.peripheral.generic;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.asm.NamedMethod;
|
||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GenericPeripheralProvider
|
||||
{
|
||||
@Nullable
|
||||
public static IPeripheral getPeripheral( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side )
|
||||
{
|
||||
BlockEntity tile = world.getBlockEntity( pos );
|
||||
if( tile == null ) return null;
|
||||
|
||||
ArrayList<SaturatedMethod> saturated = new ArrayList<>( 0 );
|
||||
|
||||
// This seems to add inventory methods, how???
|
||||
List<NamedMethod<PeripheralMethod>> tileMethods = PeripheralMethod.GENERATOR.getMethods( tile.getClass() );
|
||||
if( !tileMethods.isEmpty() ) addSaturated( saturated, tile, tileMethods );
|
||||
|
||||
return saturated.isEmpty() ? null : new GenericPeripheral( tile, saturated );
|
||||
}
|
||||
|
||||
private static void addSaturated( ArrayList<SaturatedMethod> saturated, Object target, List<NamedMethod<PeripheralMethod>> methods )
|
||||
{
|
||||
saturated.ensureCapacity( saturated.size() + methods.size() );
|
||||
for( NamedMethod<PeripheralMethod> method : methods )
|
||||
{
|
||||
saturated.add( new SaturatedMethod( target, method ) );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.peripheral.generic;
|
||||
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.core.asm.NamedMethod;
|
||||
import dan200.computercraft.core.asm.PeripheralMethod;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
final class SaturatedMethod
|
||||
{
|
||||
private final Object target;
|
||||
private final String name;
|
||||
private final PeripheralMethod method;
|
||||
|
||||
SaturatedMethod( Object target, NamedMethod<PeripheralMethod> method )
|
||||
{
|
||||
this.target = target;
|
||||
this.name = method.getName();
|
||||
this.method = method.getMethod();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
MethodResult apply( @Nonnull ILuaContext context, @Nonnull IComputerAccess computer, @Nonnull IArguments args ) throws LuaException
|
||||
{
|
||||
return method.apply( target, context, computer, args );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( Object obj )
|
||||
{
|
||||
if( obj == this ) return true;
|
||||
if( !(obj instanceof SaturatedMethod) ) return false;
|
||||
|
||||
SaturatedMethod other = (SaturatedMethod) obj;
|
||||
return method == other.method && target.equals( other.target );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return 31 * target.hashCode() + method.hashCode();
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.peripheral.generic.data;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.state.property.Property;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BlockData
|
||||
{
|
||||
@Nonnull
|
||||
public static <T extends Map<? super String, Object>> T fill( @Nonnull T data, @Nonnull BlockState state )
|
||||
{
|
||||
data.put("name", DataHelpers.getId( state.getBlock() ) );
|
||||
|
||||
Map<Object, Object> stateTable = new HashMap<>();
|
||||
for (ImmutableMap.Entry<Property<?>, ? extends Comparable<?>> entry : state.getEntries().entrySet()) {
|
||||
Property<?> property = entry.getKey();
|
||||
stateTable.put(property.getName(), getPropertyValue(property, entry.getValue()));
|
||||
}
|
||||
data.put("state", stateTable);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@SuppressWarnings( { "unchecked", "rawtypes" } )
|
||||
private static Object getPropertyValue( Property property, Comparable value )
|
||||
{
|
||||
if( value instanceof String || value instanceof Number || value instanceof Boolean ) return value;
|
||||
return property.name( value );
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.peripheral.generic.data;
|
||||
|
||||
import net.minecraft.block.AbstractBlock;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.enchantment.Enchantment;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.registry.Registry;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class DataHelpers
|
||||
{
|
||||
private DataHelpers()
|
||||
{ }
|
||||
|
||||
@Nonnull
|
||||
public static Map<String, Boolean> getTags( @Nonnull Collection<Identifier> tags )
|
||||
{
|
||||
Map<String, Boolean> result = new HashMap<>( tags.size() );
|
||||
for( Identifier location : tags ) result.put( location.toString(), true );
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getId( @Nonnull Block block )
|
||||
{
|
||||
Identifier id = Registry.BLOCK.getId(block);
|
||||
return id == null ? null : id.toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getId( @Nonnull Item item )
|
||||
{
|
||||
Identifier id = Registry.ITEM.getId(item);
|
||||
return id == null ? null : id.toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getId( @Nonnull Enchantment enchantment)
|
||||
{
|
||||
Identifier id = Registry.ENCHANTMENT.getId(enchantment);
|
||||
return id == null ? null : id.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.peripheral.generic.data;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import dan200.computercraft.shared.util.NBTUtil;
|
||||
import net.minecraft.enchantment.Enchantment;
|
||||
import net.minecraft.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.item.EnchantedBookItem;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.text.Text;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Data providers for items.
|
||||
*/
|
||||
public class ItemData
|
||||
{
|
||||
@Nonnull
|
||||
public static <T extends Map<? super String, Object>> T fillBasicSafe( @Nonnull T data, @Nonnull ItemStack stack )
|
||||
{
|
||||
data.put( "name", DataHelpers.getId( stack.getItem() ) );
|
||||
data.put( "count", stack.getCount() );
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static <T extends Map<? super String, Object>> T fillBasic( @Nonnull T data, @Nonnull ItemStack stack )
|
||||
{
|
||||
fillBasicSafe( data, stack );
|
||||
String hash = NBTUtil.getNBTHash( stack.getTag() );
|
||||
if( hash != null ) data.put( "nbt", hash );
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static <T extends Map<? super String, Object>> T fill( @Nonnull T data, @Nonnull ItemStack stack )
|
||||
{
|
||||
if( stack.isEmpty() ) return data;
|
||||
|
||||
fillBasic( data, stack );
|
||||
|
||||
data.put( "displayName", stack.toHoverableText().getString() );
|
||||
data.put( "maxCount", stack.getMaxCount() );
|
||||
|
||||
if( stack.isDamageable() )
|
||||
{
|
||||
data.put( "damage", stack.getDamage() );
|
||||
data.put( "maxDamage", stack.getMaxDamage() );
|
||||
}
|
||||
|
||||
if( stack.isDamaged() )
|
||||
{
|
||||
data.put( "durability", 1.0 - ( stack.getDamage() / stack.getMaxDamage() ) );
|
||||
}
|
||||
|
||||
/*
|
||||
* Used to hide some data from ItemStack tooltip.
|
||||
* @see https://minecraft.gamepedia.com/Tutorials/Command_NBT_tags
|
||||
* @see ItemStack#getTooltip
|
||||
*/
|
||||
CompoundTag tag = stack.getTag();
|
||||
int hideFlags = tag != null ? tag.getInt( "HideFlags" ) : 0;
|
||||
|
||||
List<Map<String, Object>> enchants = getAllEnchants( stack, hideFlags );
|
||||
if( !enchants.isEmpty() ) data.put( "enchantments", enchants );
|
||||
|
||||
if( tag != null && tag.getBoolean( "Unbreakable" ) && (hideFlags & 4) == 0 )
|
||||
{
|
||||
data.put( "unbreakable", true );
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Text parseTextComponent( @Nonnull Tag x )
|
||||
{
|
||||
try
|
||||
{
|
||||
return Text.Serializer.fromJson( x.toString() );
|
||||
}
|
||||
catch( JsonParseException e )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all visible enchantments from given stack. Try to follow all tooltip rules : order and visibility.
|
||||
*
|
||||
* @param stack Stack to analyse
|
||||
* @param hideFlags An int used as bit field to provide visibility rules.
|
||||
* @return A filled list that contain all visible enchantments.
|
||||
*/
|
||||
@Nonnull
|
||||
private static List<Map<String, Object>> getAllEnchants( @Nonnull ItemStack stack, int hideFlags )
|
||||
{
|
||||
ArrayList<Map<String, Object>> enchants = new ArrayList<>( 0 );
|
||||
|
||||
if( stack.getItem() instanceof EnchantedBookItem && (hideFlags & 32) == 0 )
|
||||
{
|
||||
addEnchantments( EnchantedBookItem.getEnchantmentTag( stack ), enchants );
|
||||
}
|
||||
|
||||
if( stack.hasEnchantments() && (hideFlags & 1) == 0 )
|
||||
{
|
||||
/*
|
||||
* Mimic the EnchantmentHelper.getEnchantments(ItemStack stack) behavior without special case for Enchanted book.
|
||||
* I'll do that to have the same data than ones displayed in tooltip.
|
||||
* @see EnchantmentHelper.getEnchantments(ItemStack stack)
|
||||
*/
|
||||
addEnchantments( stack.getEnchantments(), enchants );
|
||||
}
|
||||
|
||||
return enchants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Mojang enchant map to a Lua list.
|
||||
*
|
||||
* @param rawEnchants The raw NBT list of enchantments
|
||||
* @param enchants The enchantment map to add it to.
|
||||
* @see EnchantmentHelper
|
||||
*/
|
||||
private static void addEnchantments( @Nonnull ListTag rawEnchants, @Nonnull ArrayList<Map<String, Object>> enchants )
|
||||
{
|
||||
if( rawEnchants.isEmpty() ) return;
|
||||
|
||||
enchants.ensureCapacity( enchants.size() + rawEnchants.size() );
|
||||
|
||||
|
||||
for( Map.Entry<Enchantment, Integer> entry : EnchantmentHelper.fromTag( rawEnchants ).entrySet() )
|
||||
{
|
||||
Enchantment enchantment = entry.getKey();
|
||||
Integer level = entry.getValue();
|
||||
HashMap<String, Object> enchant = new HashMap<>( 3 );
|
||||
enchant.put( "name", DataHelpers.getId( enchantment ) );
|
||||
enchant.put( "level", level );
|
||||
enchant.put( "displayName", enchantment.getName( level ).getString() );
|
||||
enchants.add( enchant );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
|
||||
/**
|
||||
* A few helpers for working with arguments.
|
||||
*
|
||||
* This should really be moved into the public API. However, until I have settled on a suitable format, we'll keep it
|
||||
* where it is used.
|
||||
*/
|
||||
final class ArgumentHelpers
|
||||
{
|
||||
private ArgumentHelpers()
|
||||
{
|
||||
}
|
||||
|
||||
public static void assertBetween( double value, double min, double max, String message ) throws LuaException
|
||||
{
|
||||
if( value < min || value > max || Double.isNaN( value ) )
|
||||
{
|
||||
throw new LuaException( String.format( message, "between " + min + " and " + max ) );
|
||||
}
|
||||
}
|
||||
|
||||
public static void assertBetween( int value, int min, int max, String message ) throws LuaException
|
||||
{
|
||||
if( value < min || value > max )
|
||||
{
|
||||
throw new LuaException( String.format( message, "between " + min + " and " + max ) );
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.asm.GenericSource;
|
||||
import dan200.computercraft.shared.peripheral.generic.data.ItemData;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.ChestBlock;
|
||||
import net.minecraft.block.InventoryProvider;
|
||||
import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.block.entity.ChestBlockEntity;
|
||||
import net.minecraft.inventory.Inventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.text.TranslatableText;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.Nameable;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dan200.computercraft.shared.peripheral.generic.methods.ArgumentHelpers.assertBetween;
|
||||
|
||||
/**
|
||||
* Methods for interacting with inventories.
|
||||
*
|
||||
* @cc.module inventory
|
||||
*/
|
||||
@AutoService( GenericSource.class )
|
||||
public class InventoryMethods implements GenericSource
|
||||
{
|
||||
@Nonnull
|
||||
@Override
|
||||
public Identifier id()
|
||||
{
|
||||
return new Identifier(ComputerCraft.MOD_ID, "inventory" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of this inventory.
|
||||
*
|
||||
* @param inventory The current inventory.
|
||||
* @return The number of slots in this inventory.
|
||||
*/
|
||||
@LuaFunction( mainThread = true )
|
||||
public static int size( Inventory inventory )
|
||||
{
|
||||
// Get appropriate inventory for source peripheral
|
||||
inventory = extractHandler(inventory);
|
||||
|
||||
return inventory.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this inventory.
|
||||
*
|
||||
* @param inventory The current inventory.
|
||||
* @return The name of this inventory, or {@code nil} if not present.
|
||||
*/
|
||||
@LuaFunction( mainThread = true )
|
||||
public static String name( Nameable inventory )
|
||||
{
|
||||
return inventory.hasCustomName() ? inventory.getName().asString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all items in this inventory. This returns a table, with an entry for each slot.
|
||||
*
|
||||
* Each item in the inventory is represented by a table containing some basic information, much like
|
||||
* @link dan200.computercraft.shared.turtle.apis.TurtleAPI#getItemDetail includes. More information can be fetched
|
||||
* with {@link #getItemDetail}.
|
||||
*
|
||||
* The table is sparse, and so empty slots will be `nil` - it is recommended to loop over using `pairs` rather than
|
||||
* `ipairs`.
|
||||
*
|
||||
* @param inventory The current inventory.
|
||||
* @return All items in this inventory.
|
||||
* @cc.treturn { (table|nil)... } All items in this inventory.
|
||||
*/
|
||||
@LuaFunction( mainThread = true )
|
||||
public static Map<Integer, Map<String, ?>> list( Inventory inventory )
|
||||
{
|
||||
// Get appropriate inventory for source peripheral
|
||||
inventory = extractHandler(inventory);
|
||||
|
||||
Map<Integer, Map<String, ?>> result = new HashMap<>();
|
||||
int size = inventory.size();
|
||||
for( int i = 0; i < size; i++ )
|
||||
{
|
||||
ItemStack stack = inventory.getStack( i );
|
||||
if( !stack.isEmpty() ) result.put( i + 1, ItemData.fillBasic( new HashMap<>( 4 ), stack ) );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed information about an item.
|
||||
*
|
||||
* @param inventory The current inventory.
|
||||
* @param slot The slot to get information about.
|
||||
* @return Information about the item in this slot, or {@code nil} if not present.
|
||||
* @throws LuaException If the slot is out of range.
|
||||
* @cc.treturn table Information about the item in this slot, or {@code nil} if not present.
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction( mainThread = true )
|
||||
public static Map<String, ?> getItemDetail( Inventory inventory, int slot ) throws LuaException
|
||||
{
|
||||
// Get appropriate inventory
|
||||
inventory = extractHandler(inventory);
|
||||
|
||||
assertBetween( slot, 1, inventory.size(), "Slot out of range (%s)" );
|
||||
|
||||
ItemStack stack = inventory.getStack( slot - 1 );
|
||||
return stack.isEmpty() ? null : ItemData.fill( new HashMap<>(), stack );
|
||||
}
|
||||
|
||||
/**
|
||||
* Push items from one inventory to another connected one.
|
||||
*
|
||||
* This allows you to push an item in an inventory to another inventory <em>on the same wired network</em>. Both
|
||||
* inventories must attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param from Inventory to move items from.
|
||||
* @param computer The current computer.
|
||||
* @param toName The name of the peripheral/inventory to push to. This is the string given to @{peripheral.wrap},
|
||||
* and displayed by the wired modem.
|
||||
* @param fromSlot The slot in the current inventory to move items to.
|
||||
* @param limit The maximum number of items to move. Defaults to the current stack limit.
|
||||
* @param toSlot The slot in the target inventory to move to. If not given, the item will be inserted into any slot.
|
||||
* @return The number of transferred items.
|
||||
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
|
||||
* @throws LuaException If either source or destination slot is out of range.
|
||||
* @cc.see peripheral.getName Allows you to get the name of a @{peripheral.wrap|wrapped} peripheral.
|
||||
* @cc.usage Wrap two chests, and push an item from one to another.
|
||||
* <pre>{@code
|
||||
* local chest_a = peripheral.wrap("minecraft:chest_0")
|
||||
* local chest_b = peripheral.wrap("minecraft:chest_1")
|
||||
*
|
||||
* chest_a.pushItems(peripheral.getName(chest_b), 1)
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction( mainThread = true )
|
||||
public static int pushItems(
|
||||
Inventory from, IComputerAccess computer,
|
||||
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException
|
||||
{
|
||||
// Get appropriate inventory for source peripheral
|
||||
from = extractHandler(from);
|
||||
|
||||
// Find location to transfer to
|
||||
IPeripheral location = computer.getAvailablePeripheral( toName );
|
||||
if( location == null ) throw new LuaException( "Target '" + toName + "' does not exist" );
|
||||
|
||||
Inventory to = extractHandler( location.getTarget() );
|
||||
if( to == null ) throw new LuaException( "Target '" + toName + "' is not an inventory" );
|
||||
|
||||
// Validate slots
|
||||
int actualLimit = limit.orElse( Integer.MAX_VALUE );
|
||||
assertBetween( fromSlot, 1, from.size(), "From slot out of range (%s)" );
|
||||
if( toSlot.isPresent() ) assertBetween( toSlot.get(), 1, to.size(), "To slot out of range (%s)" );
|
||||
|
||||
if( actualLimit <= 0 ) return 0;
|
||||
return moveItem( from, fromSlot - 1, to, toSlot.orElse( 0 ) - 1, actualLimit );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull items from a connected inventory into this one.
|
||||
*
|
||||
* This allows you to transfer items between inventories <em>on the same wired network</em>. Both this and the source
|
||||
* inventory must attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param to Inventory to move items to.
|
||||
* @param computer The current computer.
|
||||
* @param fromName The name of the peripheral/inventory to pull from. This is the string given to @{peripheral.wrap},
|
||||
* and displayed by the wired modem.
|
||||
* @param fromSlot The slot in the source inventory to move items from.
|
||||
* @param limit The maximum number of items to move. Defaults to the current stack limit.
|
||||
* @param toSlot The slot in current inventory to move to. If not given, the item will be inserted into any slot.
|
||||
* @return The number of transferred items.
|
||||
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
|
||||
* @throws LuaException If either source or destination slot is out of range.
|
||||
* @cc.see peripheral.getName Allows you to get the name of a @{peripheral.wrap|wrapped} peripheral.
|
||||
* @cc.usage Wrap two chests, and push an item from one to another.
|
||||
* <pre>{@code
|
||||
* local chest_a = peripheral.wrap("minecraft:chest_0")
|
||||
* local chest_b = peripheral.wrap("minecraft:chest_1")
|
||||
*
|
||||
* chest_a.pullItems(peripheral.getName(chest_b), 1)
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction( mainThread = true )
|
||||
public static int pullItems(
|
||||
Inventory to, IComputerAccess computer,
|
||||
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException
|
||||
{
|
||||
// Get appropriate inventory for source peripheral
|
||||
to = extractHandler(to);
|
||||
|
||||
// Find location to transfer to
|
||||
IPeripheral location = computer.getAvailablePeripheral( fromName );
|
||||
if( location == null ) throw new LuaException( "Source '" + fromName + "' does not exist" );
|
||||
|
||||
Inventory from = extractHandler( location.getTarget() );
|
||||
if( from == null ) throw new LuaException( "Source '" + fromName + "' is not an inventory" );
|
||||
|
||||
// Validate slots
|
||||
int actualLimit = limit.orElse( Integer.MAX_VALUE );
|
||||
assertBetween( fromSlot, 1, from.size(), "From slot out of range (%s)" );
|
||||
if( toSlot.isPresent() ) assertBetween( toSlot.get(), 1, to.size(), "To slot out of range (%s)" );
|
||||
|
||||
if( actualLimit <= 0 ) return 0;
|
||||
return moveItem( from, fromSlot - 1, to, toSlot.orElse( 0 ) - 1, actualLimit );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extracts the most appropriate inventory from the object
|
||||
* e.g., the correct inventory for a double chest or a sided inventory.
|
||||
*
|
||||
* @param object The handler to move from.
|
||||
* @return The appropriate Inventory.
|
||||
*/
|
||||
@Nullable
|
||||
private static Inventory extractHandler( @Nullable Object object )
|
||||
{
|
||||
Inventory inventory = null;
|
||||
|
||||
if (object instanceof BlockEntity ) {
|
||||
BlockEntity blockEntity = (BlockEntity) object;
|
||||
World world = blockEntity.getWorld();
|
||||
BlockPos blockPos = blockEntity.getPos();
|
||||
BlockState blockState = world.getBlockState(blockPos);
|
||||
Block block = blockState.getBlock();
|
||||
|
||||
if (block instanceof InventoryProvider) {
|
||||
inventory = ((InventoryProvider)block).getInventory(blockState, world, blockPos);
|
||||
} else if (blockEntity instanceof Inventory) {
|
||||
inventory = (Inventory)blockEntity;
|
||||
if (inventory instanceof ChestBlockEntity && block instanceof ChestBlock) {
|
||||
inventory = ChestBlock.getInventory((ChestBlock) block, blockState, world, blockPos, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inventory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an item from one handler to another.
|
||||
*
|
||||
* @param from The handler to move from.
|
||||
* @param fromSlot The slot to move from.
|
||||
* @param to The handler to move to.
|
||||
* @param toSlot The slot to move to. Use any number < 0 to represent any slot.
|
||||
* @param limit The max number to move. {@link Integer#MAX_VALUE} for no limit.
|
||||
* @return The number of items moved.
|
||||
*/
|
||||
private static int moveItem( Inventory from, int fromSlot, Inventory to, int toSlot, final int limit )
|
||||
{
|
||||
|
||||
/* ORIGINAL FORGE CODE
|
||||
// See how much we can get out of this slot
|
||||
// ItemStack extracted = from.extractItem( fromSlot, limit, true );
|
||||
if( extracted.isEmpty() ) return 0;
|
||||
|
||||
// Limit the amount to extract
|
||||
int extractCount = Math.min( extracted.getCount(), limit );
|
||||
extracted.setCount( extractCount );
|
||||
|
||||
// ItemStack remainder = toSlot < 0 ? bItemHandlerHelper.insertItem( to, extracted, false ) : to.insertItem( toSlot, extracted, false );
|
||||
int inserted = remainder.isEmpty() ? extractCount : extractCount - remainder.getCount();
|
||||
if( inserted <= 0 ) return 0;
|
||||
|
||||
// Remove the item from the original inventory. Technically this could fail, but there's little we can do
|
||||
// about that.
|
||||
from.extractItem( fromSlot, inserted, false );
|
||||
*/
|
||||
|
||||
// Vanilla minecraft inventory manipulation code
|
||||
Boolean recurse = false;
|
||||
ItemStack source = from.getStack( fromSlot );
|
||||
int count = 0;
|
||||
|
||||
// If target slot was selected, only push items to that slot.
|
||||
if (toSlot >= 0) {
|
||||
int space = amountStackCanAddFrom(to.getStack(toSlot), source, to);
|
||||
if (space == 0) return 0;
|
||||
count = space;
|
||||
}
|
||||
// If target slot not selected, push items where they will fit, possibly
|
||||
// across slots (by recurring on this method).
|
||||
else if (toSlot < 0) {
|
||||
recurse = true;
|
||||
int[] result = getFirstValidSlotAndSpace(source, to);
|
||||
toSlot = result[0];
|
||||
if(toSlot < 0) return 0;
|
||||
count = result[1];
|
||||
}
|
||||
|
||||
// Respect slot restrictions
|
||||
if (!to.isValid(toSlot, source)) { return 0; }
|
||||
|
||||
// Compare count available in target ItemStack to limit specified.
|
||||
count = Math.min(count, limit);
|
||||
if (count == 0) return 0;
|
||||
|
||||
// Mutate destination and source ItemStack
|
||||
ItemStack destination = to.getStack(toSlot);
|
||||
if (destination.isEmpty()) {
|
||||
ItemStack newStack = source.copy();
|
||||
newStack.setCount(count);
|
||||
to.setStack(toSlot, newStack);
|
||||
} else {
|
||||
destination.increment(count);
|
||||
}
|
||||
source.decrement(count);
|
||||
if (source.isEmpty()) from.setStack(fromSlot, ItemStack.EMPTY);
|
||||
|
||||
to.markDirty();
|
||||
from.markDirty();
|
||||
|
||||
// Recurse if no explicit destination slot and more items exist in source slot
|
||||
// and limit hasn't been reached. Else, return items moved.
|
||||
if (recurse && !source.isEmpty()) return count + moveItem(from, fromSlot, to, -1, limit - count);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Maybe there is a nicer existing way to do this in the minecraft codebase. I couldn't find it.
|
||||
private static int[] getFirstValidSlotAndSpace(ItemStack fromStack, Inventory inventory) {
|
||||
for (int i = 0; i < inventory.size(); i++) {
|
||||
ItemStack stack = inventory.getStack(i);
|
||||
int space = amountStackCanAddFrom(stack, fromStack, inventory);
|
||||
if (space > 0) {
|
||||
return new int[]{i, space};
|
||||
}
|
||||
}
|
||||
return new int[]{-1, 0};
|
||||
}
|
||||
|
||||
private static int amountStackCanAddFrom(ItemStack existingStack, ItemStack fromStack, Inventory inventory) {
|
||||
if (fromStack.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
else if (existingStack.isEmpty()) {
|
||||
return Math.min(Math.min(existingStack.getMaxCount(),
|
||||
inventory.getMaxCountPerStack()),
|
||||
fromStack.getCount());
|
||||
}
|
||||
else if (InventoryMethods.areItemsEqual(existingStack, fromStack) &&
|
||||
existingStack.isStackable() &&
|
||||
existingStack.getCount() < existingStack.getMaxCount() &&
|
||||
existingStack.getCount() < inventory.getMaxCountPerStack()) {
|
||||
int stackSpace = existingStack.getMaxCount() - existingStack.getCount();
|
||||
int invSpace = inventory.getMaxCountPerStack() - existingStack.getCount();
|
||||
return Math.min(Math.min(stackSpace, invSpace), fromStack.getCount());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static boolean areItemsEqual(ItemStack stack1, ItemStack stack2) {
|
||||
return stack1.getItem() == stack2.getItem() && ItemStack.areTagsEqual(stack1, stack2);
|
||||
}
|
||||
}
|
@@ -80,7 +80,7 @@ public final class ClientMonitor extends ClientTerminal {
|
||||
GL15.glBufferData(GL31.GL_TEXTURE_BUFFER, 0, GL15.GL_STATIC_DRAW);
|
||||
this.tboTexture = GlStateManager.genTextures();
|
||||
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, this.tboTexture);
|
||||
GL31.glTexBuffer(GL31.GL_TEXTURE_BUFFER, GL30.GL_R8, this.tboBuffer);
|
||||
GL31.glTexBuffer(GL31.GL_TEXTURE_BUFFER, GL30.GL_R8UI, this.tboBuffer);
|
||||
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, 0);
|
||||
|
||||
GlStateManager.bindBuffers(GL31.GL_TEXTURE_BUFFER, 0);
|
||||
|
@@ -218,7 +218,10 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile {
|
||||
|
||||
// region Sizing and placement stuff
|
||||
public Direction getDirection() {
|
||||
return this.getCachedState().get(BlockMonitor.FACING);
|
||||
// Ensure we're actually a monitor block. This _should_ always be the case, but sometimes there's
|
||||
// fun problems with the block being missing on the client.
|
||||
BlockState state = getCachedState();
|
||||
return state.contains( BlockMonitor.FACING ) ? state.get( BlockMonitor.FACING ) : Direction.NORTH;
|
||||
}
|
||||
|
||||
public Direction getOrientation() {
|
||||
|
@@ -15,7 +15,6 @@ import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.inventory.Inventory;
|
||||
import net.minecraft.inventory.SimpleInventory;
|
||||
import net.minecraft.item.DyeItem;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.screen.ArrayPropertyDelegate;
|
||||
import net.minecraft.screen.PropertyDelegate;
|
||||
@@ -87,7 +86,7 @@ public class ContainerPrinter extends ScreenHandler {
|
||||
}
|
||||
} else {
|
||||
// Transfer from inventory to printer
|
||||
if (stack.getItem() instanceof DyeItem) {
|
||||
if( TilePrinter.isInk( stack ) ) {
|
||||
if (!this.insertItem(stack, 0, 1, false)) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
@@ -278,8 +278,8 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isInk(@Nonnull ItemStack stack) {
|
||||
return stack.getItem() instanceof DyeItem;
|
||||
static boolean isInk(@Nonnull ItemStack stack) {
|
||||
return ColourUtils.getStackColour( stack ) != null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@@ -380,9 +380,8 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
|
||||
private boolean inputPage() {
|
||||
ItemStack inkStack = this.m_inventory.get(0);
|
||||
if (!isInk(inkStack)) {
|
||||
return false;
|
||||
}
|
||||
DyeColor dye = ColourUtils.getStackColour( inkStack );
|
||||
if( dye == null ) return false;
|
||||
|
||||
for (int i = 1; i < 7; i++) {
|
||||
ItemStack paperStack = this.m_inventory.get(i);
|
||||
@@ -391,8 +390,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
}
|
||||
|
||||
// Setup the new page
|
||||
DyeColor dye = ColourUtils.getStackColour(inkStack);
|
||||
this.m_page.setTextColour(dye != null ? dye.getId() : 15);
|
||||
m_page.setTextColour( dye.getId() );
|
||||
|
||||
this.m_page.clear();
|
||||
if (paperStack.getItem() instanceof ItemPrintout) {
|
||||
|
@@ -66,6 +66,9 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
|
||||
}
|
||||
|
||||
public static ServerComputer getServerComputer(@Nonnull ItemStack stack) {
|
||||
int session = getSessionID( stack );
|
||||
if( session != ComputerCraft.serverComputerRegistry.getSessionID() ) return null;
|
||||
|
||||
int instanceID = getInstanceID(stack);
|
||||
return instanceID >= 0 ? ComputerCraft.serverComputerRegistry.get(instanceID) : null;
|
||||
}
|
||||
|
@@ -505,7 +505,7 @@ public class TurtleAPI implements ILuaAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently sleected slot.
|
||||
* Get the currently selected slot.
|
||||
*
|
||||
* @return The current slot.
|
||||
* @see #select
|
||||
|
@@ -19,6 +19,7 @@ import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
@@ -162,6 +163,10 @@ public class TurtleBrain implements ITurtleAccess {
|
||||
if (!world.isClient) {
|
||||
// Advance movement
|
||||
this.updateCommands();
|
||||
|
||||
// The block may have been broken while the command was executing (for instance, if a block explodes
|
||||
// when being mined). If so, abort.
|
||||
if( m_owner.isRemoved() ) return;
|
||||
}
|
||||
|
||||
// Advance animation
|
||||
@@ -341,7 +346,7 @@ public class TurtleBrain implements ITurtleAccess {
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Nullable
|
||||
@Override
|
||||
public GameProfile getOwningPlayer() {
|
||||
return this.m_owningPlayer;
|
||||
|
@@ -18,6 +18,7 @@ import dan200.computercraft.api.turtle.TurtleCommandResult;
|
||||
import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
|
||||
import dan200.computercraft.api.turtle.event.TurtleEvent;
|
||||
|
||||
import dan200.computercraft.shared.peripheral.generic.data.BlockData;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.state.property.Property;
|
||||
@@ -49,20 +50,7 @@ public class TurtleInspectCommand implements ITurtleCommand {
|
||||
return TurtleCommandResult.failure("No block to inspect");
|
||||
}
|
||||
|
||||
Block block = state.getBlock();
|
||||
String name = Registry.BLOCK.getId(block)
|
||||
.toString();
|
||||
|
||||
Map<String, Object> table = new HashMap<>();
|
||||
table.put("name", name);
|
||||
|
||||
Map<Object, Object> stateTable = new HashMap<>();
|
||||
for (ImmutableMap.Entry<Property<?>, ? extends Comparable<?>> entry : state.getEntries()
|
||||
.entrySet()) {
|
||||
Property<?> property = entry.getKey();
|
||||
stateTable.put(property.getName(), getPropertyValue(property, entry.getValue()));
|
||||
}
|
||||
table.put("state", stateTable);
|
||||
Map<String, Object> table = BlockData.fill( new HashMap<>(), state );
|
||||
|
||||
// Fire the event, exiting if it is cancelled
|
||||
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer(turtle, oldPosition, direction);
|
||||
@@ -72,7 +60,6 @@ public class TurtleInspectCommand implements ITurtleCommand {
|
||||
}
|
||||
|
||||
return TurtleCommandResult.success(new Object[] {table});
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings ({
|
||||
|
@@ -312,17 +312,11 @@ public class TurtlePlaceCommand implements ITurtleCommand {
|
||||
}
|
||||
|
||||
if (!placed && (item instanceof BucketItem || item instanceof BoatItem || item instanceof LilyPadItem || item instanceof GlassBottleItem)) {
|
||||
TypedActionResult<ItemStack> actionResult = stackCopy.use(turtle.getWorld(), turtlePlayer, Hand.MAIN_HAND);
|
||||
if (actionResult != null && actionResult.getResult()
|
||||
.isAccepted()) {
|
||||
TypedActionResult<ItemStack> result = stackCopy.use(turtle.getWorld(), turtlePlayer, Hand.MAIN_HAND);
|
||||
if (result.getResult()
|
||||
.isAccepted() && !ItemStack.areEqual(stack, result.getValue())) {
|
||||
placed = true;
|
||||
} else if (actionResult == null) {
|
||||
TypedActionResult<ItemStack> result = stackCopy.use(turtle.getWorld(), turtlePlayer, Hand.MAIN_HAND);
|
||||
if (result.getResult()
|
||||
.isAccepted() && !ItemStack.areEqual(stack, result.getValue())) {
|
||||
placed = true;
|
||||
turtlePlayer.loadInventory(result.getValue());
|
||||
}
|
||||
turtlePlayer.loadInventory(result.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -33,6 +33,7 @@ import net.minecraft.inventory.Inventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.screen.NamedScreenHandlerFactory;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.util.ActionResult;
|
||||
import net.minecraft.util.Hand;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
@@ -42,22 +43,41 @@ import net.minecraft.util.math.Vec3d;
|
||||
@SuppressWarnings ("EntityConstructor")
|
||||
public final class TurtlePlayer extends FakePlayer {
|
||||
private static final GameProfile DEFAULT_PROFILE = new GameProfile(UUID.fromString("0d0c4ca0-4ff1-11e4-916c-0800200c9a66"), "[ComputerCraft]");
|
||||
|
||||
private TurtlePlayer(ITurtleAccess turtle) {
|
||||
super((ServerWorld) turtle.getWorld(), getProfile(turtle.getOwningPlayer()));
|
||||
this.networkHandler = new FakeNetHandler(this);
|
||||
this.setState(turtle);
|
||||
// TODO [M3R1-01] Fix Turtle not giving player achievement for actions
|
||||
private TurtlePlayer( ServerWorld world, GameProfile name )
|
||||
{
|
||||
super( world, name );
|
||||
}
|
||||
|
||||
private static TurtlePlayer create( ITurtleAccess turtle )
|
||||
{
|
||||
ServerWorld world = (ServerWorld) turtle.getWorld();
|
||||
GameProfile profile = turtle.getOwningPlayer();
|
||||
|
||||
TurtlePlayer player = new TurtlePlayer( world, getProfile( profile ) );
|
||||
player.networkHandler = new FakeNetHandler( player );
|
||||
player.setState( turtle );
|
||||
|
||||
if( profile != null && profile.getId() != null )
|
||||
{
|
||||
// Constructing a player overrides the "active player" variable in advancements. As fake players cannot
|
||||
// get advancements, this prevents a normal player who has placed a turtle from getting advancements.
|
||||
// We try to locate the "actual" player and restore them.
|
||||
ServerPlayerEntity actualPlayer = world.getServer().getPlayerManager().getPlayer(player.getUuid());
|
||||
if( actualPlayer != null ) player.getAdvancementTracker().setOwner(actualPlayer);
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
private static GameProfile getProfile(@Nullable GameProfile profile) {
|
||||
return profile != null && profile.isComplete() ? profile : DEFAULT_PROFILE;
|
||||
}
|
||||
|
||||
private void setState(ITurtleAccess turtle) {
|
||||
if (this.currentScreenHandler != null) {
|
||||
if (this.currentScreenHandler != playerScreenHandler) {
|
||||
ComputerCraft.log.warn("Turtle has open container ({})", this.currentScreenHandler);
|
||||
this.currentScreenHandler.close(this);
|
||||
this.currentScreenHandler = null;
|
||||
closeCurrentScreen();
|
||||
}
|
||||
|
||||
BlockPos position = turtle.getPosition();
|
||||
@@ -71,14 +91,17 @@ public final class TurtlePlayer extends FakePlayer {
|
||||
}
|
||||
|
||||
public static TurtlePlayer get(ITurtleAccess access) {
|
||||
if (!(access instanceof TurtleBrain)) {
|
||||
return new TurtlePlayer(access);
|
||||
}
|
||||
ServerWorld world = (ServerWorld) access.getWorld();
|
||||
if( !(access instanceof TurtleBrain) ) return create( access );
|
||||
|
||||
/*if (!(access instanceof TurtleBrain)) {
|
||||
return new TurtlePlayer(world, access.getOwningPlayer());
|
||||
}*/
|
||||
|
||||
TurtleBrain brain = (TurtleBrain) access;
|
||||
TurtlePlayer player = brain.m_cachedPlayer;
|
||||
if (player == null || player.getGameProfile() != getProfile(access.getOwningPlayer()) || player.getEntityWorld() != access.getWorld()) {
|
||||
player = brain.m_cachedPlayer = new TurtlePlayer(brain);
|
||||
player = brain.m_cachedPlayer = create(brain);
|
||||
} else {
|
||||
player.setState(access);
|
||||
}
|
||||
|
@@ -11,6 +11,8 @@ import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.client.TransformedModel;
|
||||
import dan200.computercraft.api.turtle.AbstractTurtleUpgrade;
|
||||
@@ -23,13 +25,12 @@ import dan200.computercraft.api.turtle.event.TurtleAttackEvent;
|
||||
import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
|
||||
import dan200.computercraft.api.turtle.event.TurtleEvent;
|
||||
import dan200.computercraft.shared.TurtlePermissions;
|
||||
import dan200.computercraft.shared.turtle.core.TurtleBrain;
|
||||
import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand;
|
||||
import dan200.computercraft.shared.turtle.core.TurtlePlayer;
|
||||
import dan200.computercraft.shared.util.DropConsumer;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
@@ -40,9 +41,9 @@ import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.attribute.EntityAttributes;
|
||||
import net.minecraft.entity.damage.DamageSource;
|
||||
import net.minecraft.entity.decoration.ArmorStandEntity;
|
||||
import net.minecraft.fluid.FluidState;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.util.ActionResult;
|
||||
import net.minecraft.util.Hand;
|
||||
import net.minecraft.util.Identifier;
|
||||
@@ -58,6 +59,9 @@ import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
|
||||
public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
protected final ItemStack item;
|
||||
|
||||
private static final int TAG_LIST = 9;
|
||||
private static final int TAG_COMPOUND = 10;
|
||||
|
||||
public TurtleTool(Identifier id, String adjective, Item item) {
|
||||
super(id, TurtleUpgradeType.TOOL, adjective, item);
|
||||
this.item = new ItemStack(item);
|
||||
@@ -73,6 +77,24 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
this.item = toolItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isItemSuitable( @Nonnull ItemStack stack )
|
||||
{
|
||||
CompoundTag tag = stack.getTag();
|
||||
if( tag == null || tag.isEmpty() ) return true;
|
||||
|
||||
// Check we've not got anything vaguely interesting on the item. We allow other mods to add their
|
||||
// own NBT, with the understanding such details will be lost to the mist of time.
|
||||
if( stack.isDamaged() || stack.hasEnchantments() || stack.hasCustomName() ) return false;
|
||||
if( tag.contains( "AttributeModifiers", TAG_LIST ) &&
|
||||
!tag.getList( "AttributeModifiers", TAG_COMPOUND ).isEmpty() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public TurtleCommandResult useTool(@Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side, @Nonnull TurtleVerb verb, @Nonnull Direction direction) {
|
||||
@@ -94,10 +116,13 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
return TransformedModel.of(this.getCraftingItem(), new AffineTransformation(new Vector3f(xOffset + 1, 0, 1), Vector3f.POSITIVE_Y.getDegreesQuaternion(270), new Vector3f(1, 1, 1), Vector3f.POSITIVE_Z.getDegreesQuaternion(90)));
|
||||
}
|
||||
|
||||
private TurtleCommandResult attack(final ITurtleAccess turtle, Direction direction, TurtleSide side) {
|
||||
private TurtleCommandResult attack( ITurtleAccess turtle, Direction direction, TurtleSide side ) {
|
||||
// Create a fake player, and orient it appropriately
|
||||
final World world = turtle.getWorld();
|
||||
final BlockPos position = turtle.getPosition();
|
||||
World world = turtle.getWorld();
|
||||
BlockPos position = turtle.getPosition();
|
||||
BlockEntity turtleBlock = turtle instanceof TurtleBrain ? ((TurtleBrain) turtle).getOwner() : world.getBlockEntity( position );
|
||||
if( turtleBlock == null ) return TurtleCommandResult.failure( "Turtle has vanished from existence." );
|
||||
|
||||
final TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer(turtle, position, direction);
|
||||
|
||||
// See if there is an entity present
|
||||
@@ -105,7 +130,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
Vec3d rayDir = turtlePlayer.getRotationVec(1.0f);
|
||||
Pair<Entity, Vec3d> hit = WorldUtil.rayTraceEntities(world, turtlePos, rayDir, 1.5);
|
||||
if (hit != null) {
|
||||
// Load up the turtle's inventory
|
||||
// Load up the turtle's inventoryf
|
||||
ItemStack stackCopy = this.item.copy();
|
||||
turtlePlayer.loadInventory(stackCopy);
|
||||
|
||||
@@ -127,7 +152,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
}
|
||||
|
||||
// Start claiming entity drops
|
||||
DropConsumer.set(hitEntity, turtleDropConsumer(turtle));
|
||||
DropConsumer.set( hitEntity, turtleDropConsumer( turtleBlock, turtle ) );
|
||||
|
||||
// Attack the entity
|
||||
boolean attacked = false;
|
||||
@@ -152,7 +177,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
}
|
||||
|
||||
// Stop claiming drops
|
||||
stopConsuming(turtle);
|
||||
stopConsuming( turtleBlock, turtle );
|
||||
|
||||
// Put everything we collected into the turtles inventory, then return
|
||||
if (attacked) {
|
||||
@@ -168,6 +193,10 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
// Get ready to dig
|
||||
World world = turtle.getWorld();
|
||||
BlockPos turtlePosition = turtle.getPosition();
|
||||
BlockEntity turtleBlock = turtle instanceof TurtleBrain ? ((TurtleBrain) turtle).getOwner() : world.getBlockEntity( turtlePosition );
|
||||
if( turtleBlock == null ) return TurtleCommandResult.failure( "Turtle has vanished from existence." );
|
||||
|
||||
|
||||
BlockPos blockPosition = turtlePosition.offset(direction);
|
||||
|
||||
if (world.isAir(blockPosition) || WorldUtil.isLiquidBlock(world, blockPosition)) {
|
||||
@@ -175,7 +204,6 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
}
|
||||
|
||||
BlockState state = world.getBlockState(blockPosition);
|
||||
FluidState fluidState = world.getFluidState(blockPosition);
|
||||
|
||||
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer(turtle, turtlePosition, direction);
|
||||
turtlePlayer.loadInventory(this.item.copy());
|
||||
@@ -198,7 +226,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
}
|
||||
|
||||
// Consume the items the block drops
|
||||
DropConsumer.set(world, blockPosition, turtleDropConsumer(turtle));
|
||||
DropConsumer.set( world, blockPosition, turtleDropConsumer( turtleBlock, turtle ) );
|
||||
|
||||
BlockEntity tile = world.getBlockEntity(blockPosition);
|
||||
|
||||
@@ -210,7 +238,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
|
||||
// Destroy the block
|
||||
state.getBlock()
|
||||
.onBroken(world, blockPosition, state);
|
||||
.onBreak(world, blockPosition, state, turtlePlayer);
|
||||
if (world.removeBlock(blockPosition, false)) {
|
||||
state.getBlock()
|
||||
.onBroken(world, blockPosition, state);
|
||||
@@ -220,28 +248,28 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
}
|
||||
}
|
||||
|
||||
stopConsuming(turtle);
|
||||
stopConsuming(turtleBlock, turtle);
|
||||
|
||||
return TurtleCommandResult.success();
|
||||
|
||||
}
|
||||
|
||||
private static Function<ItemStack, ItemStack> turtleDropConsumer(ITurtleAccess turtle) {
|
||||
return drop -> InventoryUtil.storeItems(drop, turtle.getItemHandler(), turtle.getSelectedSlot());
|
||||
private static Function<ItemStack, ItemStack> turtleDropConsumer(BlockEntity turtleBlock, ITurtleAccess turtle) {
|
||||
return drop -> turtleBlock.isRemoved() ? drop : InventoryUtil.storeItems(drop, turtle.getItemHandler(), turtle.getSelectedSlot());
|
||||
}
|
||||
|
||||
protected float getDamageMultiplier() {
|
||||
return 3.0f;
|
||||
}
|
||||
|
||||
private static void stopConsuming(ITurtleAccess turtle) {
|
||||
private static void stopConsuming(BlockEntity turtleBlock, ITurtleAccess turtle) {
|
||||
Direction direction = turtleBlock.isRemoved() ? null : turtle.getDirection().getOpposite();
|
||||
List<ItemStack> extra = DropConsumer.clear();
|
||||
for (ItemStack remainder : extra) {
|
||||
WorldUtil.dropItemStack(remainder,
|
||||
turtle.getWorld(),
|
||||
turtle.getPosition(),
|
||||
turtle.getDirection()
|
||||
.getOpposite());
|
||||
direction);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,10 @@ package dan200.computercraft.shared.util;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import blue.endless.jankson.Comment;
|
||||
import blue.endless.jankson.Jankson;
|
||||
@@ -13,7 +16,8 @@ import com.google.common.base.CaseFormat;
|
||||
import com.google.common.base.Converter;
|
||||
import dan200.computercraft.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.core.apis.http.websocket.Websocket;
|
||||
|
||||
public class Config {
|
||||
@@ -84,8 +88,13 @@ public class Config {
|
||||
// HTTP
|
||||
ComputerCraft.http_enable = config.http.enabled;
|
||||
ComputerCraft.http_websocket_enable = config.http.websocket_enabled;
|
||||
ComputerCraft.http_whitelist = new AddressPredicate(config.http.whitelist);
|
||||
ComputerCraft.http_blacklist = new AddressPredicate(config.http.blacklist);
|
||||
ComputerCraft.httpRules = Stream.concat(Stream.of(config.http.blacklist)
|
||||
.map( x -> AddressRule.parse( x, null, Action.DENY.toPartial()))
|
||||
.filter(Objects::nonNull),
|
||||
Stream.of(config.http.whitelist)
|
||||
.map( x -> AddressRule.parse( x, null, Action.ALLOW.toPartial()))
|
||||
.filter(Objects::nonNull))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
ComputerCraft.httpTimeout = Math.max(0, config.http.timeout);
|
||||
ComputerCraft.httpMaxRequests = Math.max(1, config.http.max_requests);
|
||||
@@ -160,9 +169,9 @@ public class Config {
|
||||
ComputerCraft.http_websocket_enable;
|
||||
|
||||
@Comment ("\nA list of wildcards for domains or IP ranges that can be accessed through the " + "\"http\" API on Computers.\n" + "Set this to " +
|
||||
"\"*\" to access to the entire internet. Example: \"*.pastebin.com\" will restrict access to " + "just subdomains of pastebin.com.\n" + "You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\").") public String[] whitelist = ComputerCraft.DEFAULT_HTTP_WHITELIST.clone();
|
||||
"\"*\" to access to the entire internet. Example: \"*.pastebin.com\" will restrict access to " + "just subdomains of pastebin.com.\n" + "You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\").") public String[] whitelist = new String[] {"*"};
|
||||
|
||||
@Comment ("\nA list of wildcards for domains or IP ranges that cannot be accessed through the " + "\"http\" API on Computers.\n" + "If this is " + "empty then all whitelisted domains will be accessible. Example: \"*.github.com\" will block " + "access to all subdomains of github" + ".com.\n" + "You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\").") public String[] blacklist = ComputerCraft.DEFAULT_HTTP_BLACKLIST.clone();
|
||||
@Comment ("\nA list of wildcards for domains or IP ranges that cannot be accessed through the " + "\"http\" API on Computers.\n" + "If this is " + "empty then all whitelisted domains will be accessible. Example: \"*.github.com\" will block " + "access to all subdomains of github" + ".com.\n" + "You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\").") public String[] blacklist = new String[] {"$private"};
|
||||
|
||||
@Comment ("\nThe period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited.") public int timeout =
|
||||
ComputerCraft.httpTimeout;
|
||||
|
@@ -35,8 +35,6 @@ public final class DropConsumer {
|
||||
dropWorld = new WeakReference<>(entity.world);
|
||||
dropPos = null;
|
||||
dropBounds = new Box(entity.getBlockPos()).expand(2, 2, 2);
|
||||
|
||||
// entity.captureDrops( new ArrayList<>() );
|
||||
}
|
||||
|
||||
public static void set(World world, BlockPos pos, Function<ItemStack, ItemStack> consumer) {
|
||||
|
@@ -14,7 +14,6 @@ import net.minecraft.block.entity.BlockEntity;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.inventory.Inventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
@@ -28,53 +27,6 @@ public final class InventoryUtil {
|
||||
return a == b || (a.getItem() == b.getItem() && ItemStack.areTagsEqual(a, b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if two items are "mostly" equivalent. Namely, they have the same item and damage, and identical share stacks.
|
||||
*
|
||||
* sufficient to ensure basic information (such as enchantments) are the same, while not having to worry about capabilities.
|
||||
*
|
||||
* @param a The first stack to check
|
||||
* @param b The second stack to check
|
||||
* @return If these items are largely the same.
|
||||
*/
|
||||
public static boolean areItemsSimilar(@Nonnull ItemStack a, @Nonnull ItemStack b) {
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
if (a.isEmpty()) {
|
||||
return !b.isEmpty();
|
||||
}
|
||||
|
||||
if (a.getItem() != b.getItem()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
|
||||
// null one.
|
||||
CompoundTag shareTagA = a.getTag();
|
||||
CompoundTag shareTagB = b.getTag();
|
||||
if (shareTagA == shareTagB) {
|
||||
return true;
|
||||
}
|
||||
if (shareTagA == null) {
|
||||
return shareTagB.isEmpty();
|
||||
}
|
||||
if (shareTagB == null) {
|
||||
return shareTagA.isEmpty();
|
||||
}
|
||||
return shareTagA.equals(shareTagB);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static ItemStack copyItem(@Nonnull ItemStack a) {
|
||||
return a.copy();
|
||||
}
|
||||
|
||||
public static ItemStorage getStorage(World world, BlockPos pos, Direction side) {
|
||||
Inventory inventory = getInventory(world, pos, side);
|
||||
return inventory == null ? null : ItemStorage.wrap(inventory, side);
|
||||
}
|
||||
|
||||
// Methods for finding inventories:
|
||||
|
||||
public static Inventory getInventory(World world, BlockPos pos, Direction side) {
|
||||
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public final class ServiceUtil
|
||||
{
|
||||
private static final Type AUTO_SERVICE = Type.getType( "Lcom/google/auto/service/AutoService;" );
|
||||
|
||||
private ServiceUtil()
|
||||
{
|
||||
}
|
||||
|
||||
public static <T> Stream<T> loadServices( Class<T> target )
|
||||
{
|
||||
return StreamSupport.stream( ServiceLoader.load( target, ServiceUtil.class.getClassLoader() ).spliterator(), false );
|
||||
}
|
||||
}
|
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"block.computercraft.computer_normal": "Dator",
|
||||
"block.computercraft.computer_advanced": "Avancerad Dator",
|
||||
"block.computercraft.computer_command": "Kommando Dator",
|
||||
"block.computercraft.computer_command": "Kommandodator",
|
||||
"block.computercraft.disk_drive": "Diskettläsare",
|
||||
"block.computercraft.printer": "Skrivare",
|
||||
"block.computercraft.speaker": "Högtalare",
|
||||
"block.computercraft.monitor_normal": "Skärm",
|
||||
"block.computercraft.monitor_advanced": "Avancerad Skärm",
|
||||
"block.computercraft.wireless_modem_normal": "Trådlöst Modem",
|
||||
"block.computercraft.wireless_modem_advanced": "Ender Modem",
|
||||
"block.computercraft.wireless_modem_advanced": "Endermodem",
|
||||
"block.computercraft.wired_modem": "Trådat Modem",
|
||||
"block.computercraft.cable": "Nätverkskabel",
|
||||
"block.computercraft.wired_modem_full": "Trådat Modem",
|
||||
@@ -38,5 +38,76 @@
|
||||
"upgrade.computercraft.speaker.adjective": "Högljudd",
|
||||
"chat.computercraft.wired_modem.peripheral_connected": "Kringutrustning \"%s\" är kopplad till nätverket",
|
||||
"chat.computercraft.wired_modem.peripheral_disconnected": "Kringutrustning \"%s\" är frånkopplad från nätverket",
|
||||
"gui.computercraft.tooltip.copy": "Kopiera till urklipp"
|
||||
"gui.computercraft.tooltip.copy": "Kopiera till urklipp",
|
||||
"gui.computercraft.tooltip.disk_id": "Diskett-ID: %s",
|
||||
"gui.computercraft.tooltip.computer_id": "Dator-ID: %s",
|
||||
"tracking_field.computercraft.coroutines_dead.name": "Coroutines borttagna",
|
||||
"tracking_field.computercraft.coroutines_created.name": "Coroutines skapade",
|
||||
"tracking_field.computercraft.websocket_outgoing.name": "Websocket utgående",
|
||||
"tracking_field.computercraft.websocket_incoming.name": "Websocket ingående",
|
||||
"tracking_field.computercraft.http_download.name": "HTTP-nedladdning",
|
||||
"tracking_field.computercraft.http_upload.name": "HTTP-uppladdning",
|
||||
"tracking_field.computercraft.http.name": "HTTP-förfrågningar",
|
||||
"tracking_field.computercraft.turtle.name": "Turtle-operationer",
|
||||
"tracking_field.computercraft.fs.name": "Filsystemoperationer",
|
||||
"tracking_field.computercraft.peripheral.name": "Samtal till kringutrustning",
|
||||
"tracking_field.computercraft.server_time.name": "Serveraktivitetstid",
|
||||
"tracking_field.computercraft.server_count.name": "Antal serveruppgifter",
|
||||
"tracking_field.computercraft.max.name": "Max tid",
|
||||
"tracking_field.computercraft.average.name": "Genomsnittlig tid",
|
||||
"tracking_field.computercraft.total.name": "Total tid",
|
||||
"tracking_field.computercraft.tasks.name": "Uppgifter",
|
||||
"argument.computercraft.argument_expected": "Argument förväntas",
|
||||
"argument.computercraft.tracking_field.no_field": "Okänt fält '%s'",
|
||||
"argument.computercraft.computer.many_matching": "Flera datorer matchar '%s' (%s träffar)",
|
||||
"argument.computercraft.computer.no_matching": "Inga datorer matchar '%s'",
|
||||
"commands.computercraft.generic.additional_rows": "%d ytterligare rader…",
|
||||
"commands.computercraft.generic.exception": "Ohanterat felfall (%s)",
|
||||
"commands.computercraft.generic.no": "N",
|
||||
"commands.computercraft.generic.yes": "J",
|
||||
"commands.computercraft.generic.position": "%s, %s, %s",
|
||||
"commands.computercraft.generic.no_position": "<no pos>",
|
||||
"commands.computercraft.queue.desc": "Skicka ett computer_command event till en kommandodator, skicka vidare ytterligare argument. Detta är mestadels utformat för kartmarkörer som fungerar som en mer datorvänlig version av /trigger. Alla spelare kan köra kommandot, vilket sannolikt skulle göras genom en textkomponents klick-event.",
|
||||
"commands.computercraft.queue.synopsis": "Skicka ett computer_command event till en kommandodator",
|
||||
"commands.computercraft.reload.done": "Konfiguration omladdad",
|
||||
"commands.computercraft.reload.desc": "Ladda om ComputerCrafts konfigurationsfil",
|
||||
"commands.computercraft.reload.synopsis": "Ladda om ComputerCrafts konfigurationsfil",
|
||||
"commands.computercraft.track.dump.computer": "Dator",
|
||||
"commands.computercraft.track.dump.no_timings": "Inga tidtagningar tillgängliga",
|
||||
"commands.computercraft.track.dump.desc": "Dumpa de senaste resultaten av datorspårning.",
|
||||
"commands.computercraft.track.dump.synopsis": "Dumpa de senaste spårningsresultaten",
|
||||
"commands.computercraft.track.stop.not_enabled": "Spårar för tillfället inga datorer",
|
||||
"commands.computercraft.track.stop.action": "Klicka för att stoppa spårning",
|
||||
"commands.computercraft.track.stop.desc": "Stoppa spårning av alla datorers körtider och eventräkningar",
|
||||
"commands.computercraft.track.stop.synopsis": "Stoppa spårning för alla datorer",
|
||||
"commands.computercraft.track.start.stop": "Kör %s för att stoppa spårning och visa resultaten",
|
||||
"commands.computercraft.track.start.desc": "Börja spåra alla dators körtider och eventräkningar. Detta kommer återställa resultaten från tidigare körningar.",
|
||||
"commands.computercraft.track.start.synopsis": "Starta spårning för alla datorer",
|
||||
"commands.computercraft.track.desc": "Spåra hur länge datorer exekverar, och även hur många event de hanterar. Detta presenterar information på liknande sätt som /forge track och kan vara användbart för att undersöka lagg.",
|
||||
"commands.computercraft.track.synopsis": "Spåra körningstider för denna dator.",
|
||||
"commands.computercraft.view.not_player": "Kan inte öppna terminalen för en ickespelare",
|
||||
"commands.computercraft.view.action": "Titta på denna dator",
|
||||
"commands.computercraft.view.desc": "Öppna datorns terminal för att möjligöra fjärrstyrning. Detta ger inte tillgång till turtlens inventory. Du kan ange en dators instans-id (t.ex. 123) eller dator-id (t.ex. #123).",
|
||||
"commands.computercraft.view.synopsis": "Titta på datorns terminal.",
|
||||
"commands.computercraft.tp.not_there": "Kan inte hitta datorn i världen",
|
||||
"commands.computercraft.tp.not_player": "Kan inte öppna terminalen för en ickespelare",
|
||||
"commands.computercraft.tp.action": "Teleportera till den här datorn",
|
||||
"commands.computercraft.tp.desc": "Teleportera till datorns position. Du kan ange en dators instans-id (t.ex. 123), dator-id (t.ex. #123) eller etikett (t.ex. \"@Min dator\").",
|
||||
"commands.computercraft.tp.synopsis": "Teleportera till en specifik dator.",
|
||||
"commands.computercraft.turn_on.done": "Startade %s/%s datorer",
|
||||
"commands.computercraft.turn_on.desc": "Starta de listade datorerna eller alla om ingen anges. Du kan ange en dators instans-id (t.ex. 123), dator-id (t.ex. #123) eller etikett (t.ex. \"@Min dator\").",
|
||||
"commands.computercraft.turn_on.synopsis": "Starta på datorer på distans.",
|
||||
"commands.computercraft.shutdown.done": "Stängde av %s/%s datorer",
|
||||
"commands.computercraft.dump.desc": "Visa status för alla datorer eller specifik information för en dator. Du kan ange en dators instans-id (t.ex. 123), dator-id (t.ex. #123) eller etikett (t.ex. \"@Min dator\").",
|
||||
"commands.computercraft.shutdown.desc": "Stäng av de listade datorerna eller alla om ingen anges. Du kan ange en dators instans-id (t.ex. 123), dator-id (t.ex. #123) eller etikett (t.ex. \"@Min dator\").",
|
||||
"commands.computercraft.shutdown.synopsis": "Stäng av datorer på distans.",
|
||||
"commands.computercraft.dump.action": "Visa mer information om den här datorn",
|
||||
"commands.computercraft.dump.synopsis": "Visa status för datorer.",
|
||||
"commands.computercraft.help.no_command": "Inget sådant kommando '%s'",
|
||||
"commands.computercraft.help.no_children": "%s har inget underkommando",
|
||||
"commands.computercraft.help.desc": "Visa detta hjälpmeddelande",
|
||||
"commands.computercraft.help.synopsis": "Tillhandahåll hjälp för ett specifikt kommando",
|
||||
"commands.computercraft.desc": "/computercraft kommandot tillhandahåller olika debugging- och administrationsverktyg för att kontrollera och interagera med datorer.",
|
||||
"commands.computercraft.synopsis": "Olika kommandon för att kontrollera datorer.",
|
||||
"itemGroup.computercraft": "ComputerCraft"
|
||||
}
|
||||
|
48
src/main/resources/assets/computercraft/lang/vi.json
Normal file
48
src/main/resources/assets/computercraft/lang/vi.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"gui.computercraft.tooltip.disk_id": "ID của đĩa: %s",
|
||||
"upgrade.computercraft.speaker.adjective": "Ồn ào",
|
||||
"upgrade.computercraft.wireless_modem_advanced.adjective": "Ender",
|
||||
"upgrade.computercraft.wireless_modem_normal.adjective": "Không dây",
|
||||
"upgrade.minecraft.crafting_table.adjective": "Chế tạo",
|
||||
"upgrade.minecraft.diamond_hoe.adjective": "Trồng trọt",
|
||||
"upgrade.minecraft.diamond_axe.adjective": "Đốn",
|
||||
"upgrade.minecraft.diamond_pickaxe.adjective": "Khai thác",
|
||||
"upgrade.minecraft.diamond_shovel.adjective": "Đào",
|
||||
"item.computercraft.pocket_computer_advanced.upgraded": "Máy tính bỏ túi tiên tiến %s",
|
||||
"item.computercraft.pocket_computer_advanced": "Máy tính bỏ túi tiên tiến",
|
||||
"item.computercraft.pocket_computer_normal.upgraded": "Máy tính bỏ túi %s",
|
||||
"item.computercraft.pocket_computer_normal": "Máy tính bỏ túi",
|
||||
"item.computercraft.printed_book": "Sách in",
|
||||
"item.computercraft.printed_page": "Trang in",
|
||||
"item.computercraft.treasure_disk": "Đĩa mềm",
|
||||
"item.computercraft.disk": "Đĩa mềm",
|
||||
"block.computercraft.turtle_advanced.upgraded_twice": "Rùa tiên tiến %s %s",
|
||||
"block.computercraft.turtle_advanced.upgraded": "Rùa tiên tiến %s",
|
||||
"block.computercraft.turtle_advanced": "Rùa tiên tiến",
|
||||
"block.computercraft.turtle_normal.upgraded_twice": "Rùa %s %s",
|
||||
"block.computercraft.turtle_normal.upgraded": "Rùa %s",
|
||||
"block.computercraft.turtle_normal": "Rùa",
|
||||
"block.computercraft.wired_modem_full": "Modem có dây",
|
||||
"block.computercraft.cable": "Dây cáp mạng",
|
||||
"block.computercraft.wired_modem": "Modem có dây",
|
||||
"block.computercraft.wireless_modem_advanced": "Modem Ender",
|
||||
"block.computercraft.wireless_modem_normal": "Modem không dây",
|
||||
"block.computercraft.monitor_advanced": "Màn hình tiên tiếng",
|
||||
"block.computercraft.monitor_normal": "Màn hình",
|
||||
"block.computercraft.speaker": "Loa",
|
||||
"block.computercraft.printer": "Máy in",
|
||||
"block.computercraft.disk_drive": "Ỗ đĩa",
|
||||
"block.computercraft.computer_command": "Máy tính điều khiển",
|
||||
"block.computercraft.computer_normal": "Máy tính",
|
||||
"itemGroup.computercraft": "ComputerCraft",
|
||||
"block.computercraft.computer_advanced": "Máy tính tiên tiến",
|
||||
"tracking_field.computercraft.websocket_incoming.name": "Websocket đến",
|
||||
"tracking_field.computercraft.websocket_outgoing.name": "Websocket đi",
|
||||
"gui.computercraft.tooltip.computer_id": "ID của máy tính: %s",
|
||||
"tracking_field.computercraft.coroutines_dead.name": "Coroutine bỏ đi",
|
||||
"tracking_field.computercraft.coroutines_created.name": "Coroutine đã tạo",
|
||||
"tracking_field.computercraft.http_download.name": "HTTP tải xuống",
|
||||
"tracking_field.computercraft.http_upload.name": "HTTP tải lên",
|
||||
"tracking_field.computercraft.http.name": "Yêu cầu HTTP",
|
||||
"gui.computercraft.tooltip.copy": "Sao chép vào clipboard"
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
View the source code at https://github.com/mystiacraft/cc-tweaked-fabric
|
||||
View the source code at https://github.com/Merith-TK/cc-restiched
|
||||
View the documentation at https://wiki.computercraft.cc
|
||||
Visit the forum at https://forums.computercraft.cc
|
||||
You can disable these messages by running "set motd.enable false"
|
||||
|
@@ -1,15 +1,24 @@
|
||||
local tMotd = {}
|
||||
local date = os.date("*t")
|
||||
if date.month == 1 and date.day == 1 then
|
||||
print("Happy new year!")
|
||||
elseif date.month == 12 and date.day == 24 then
|
||||
print("Merry X-mas!")
|
||||
elseif date.month == 10 and date.day == 31 then
|
||||
print("OOoooOOOoooo! Spooky!")
|
||||
else
|
||||
local tMotd = {}
|
||||
|
||||
for sPath in string.gmatch(settings.get( "motd.path" ), "[^:]+") do
|
||||
if fs.exists(sPath) then
|
||||
for sLine in io.lines(sPath) do
|
||||
table.insert(tMotd,sLine)
|
||||
for sPath in string.gmatch(settings.get("motd.path"), "[^:]+") do
|
||||
if fs.exists(sPath) then
|
||||
for sLine in io.lines(sPath) do
|
||||
table.insert(tMotd, sLine)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if #tMotd == 0 then
|
||||
print("missingno")
|
||||
else
|
||||
print(tMotd[math.random(1,#tMotd)])
|
||||
if #tMotd == 0 then
|
||||
print("missingno")
|
||||
else
|
||||
print(tMotd[math.random(1, #tMotd)])
|
||||
end
|
||||
end
|
||||
|
@@ -6,7 +6,7 @@
|
||||
uniform sampler2D u_font;
|
||||
uniform int u_width;
|
||||
uniform int u_height;
|
||||
uniform samplerBuffer u_tbo;
|
||||
uniform usamplerBuffer u_tbo;
|
||||
uniform vec3 u_palette[16];
|
||||
|
||||
in vec2 f_pos;
|
||||
@@ -30,9 +30,9 @@ void main() {
|
||||
vec2 outside = step(vec2(0.0, 0.0), vec2(cell)) * step(vec2(cell), vec2(float(u_width) - 1.0, float(u_height) - 1.0));
|
||||
float mult = outside.x * outside.y;
|
||||
|
||||
int character = int(texelFetch(u_tbo, index).r * 255.0);
|
||||
int fg = int(texelFetch(u_tbo, index + 1).r * 255.0);
|
||||
int bg = int(texelFetch(u_tbo, index + 2).r * 255.0);
|
||||
int character = int(texelFetch(u_tbo, index).r);
|
||||
int fg = int(texelFetch(u_tbo, index + 1).r);
|
||||
int bg = int(texelFetch(u_tbo, index + 2).r);
|
||||
|
||||
vec2 pos = (term_pos - corner) * vec2(FONT_WIDTH, FONT_HEIGHT);
|
||||
vec4 img = texture(u_font, (texture_corner(character) + pos) / 256.0);
|
||||
|
@@ -338,8 +338,8 @@ function read(_sReplaceChar, _tHistory, _fnComplete, _sDefault)
|
||||
redraw()
|
||||
|
||||
elseif sEvent == "key" then
|
||||
if param == keys.enter then
|
||||
-- Enter
|
||||
if param == keys.enter or param == keys.numPadEnter then
|
||||
-- Enter/Numpad Enter
|
||||
if nCompletion then
|
||||
clear()
|
||||
uncomplete()
|
||||
@@ -523,6 +523,16 @@ function os.run(_tEnv, _sPath, ...)
|
||||
|
||||
local tEnv = _tEnv
|
||||
setmetatable(tEnv, { __index = _G })
|
||||
|
||||
if settings.get("bios.strict_globals", false) then
|
||||
-- load will attempt to set _ENV on this environment, which
|
||||
-- throws an error with this protection enabled. Thus we set it here first.
|
||||
tEnv._ENV = tEnv
|
||||
getmetatable(tEnv).__newindex = function(_, name)
|
||||
error("Attempt to create global " .. tostring(name), 2)
|
||||
end
|
||||
end
|
||||
|
||||
local fnFile, err = loadfile(_sPath, nil, tEnv)
|
||||
if fnFile then
|
||||
local ok, err = pcall(fnFile, ...)
|
||||
@@ -954,6 +964,11 @@ settings.define("lua.function_source", {
|
||||
description = "Show where a function was defined when printing functions.",
|
||||
type = "boolean",
|
||||
})
|
||||
settings.define("bios.strict_globals", {
|
||||
default = false,
|
||||
description = "Prevents assigning variables into a program's environment. Make sure you use the local keyword or assign to _G explicitly.",
|
||||
type = "boolean",
|
||||
})
|
||||
|
||||
if term.isColour() then
|
||||
settings.define("bios.use_multishell", {
|
||||
|
@@ -1,15 +1,137 @@
|
||||
--- The Colors API allows you to manipulate sets of colors.
|
||||
--
|
||||
-- This is useful in conjunction with Bundled Cables from the RedPower mod,
|
||||
-- RedNet Cables from the MineFactory Reloaded mod, and colors on Advanced
|
||||
-- Computers and Advanced Monitors.
|
||||
--
|
||||
-- For the non-American English version just replace @{colors} with @{colours}
|
||||
-- and it will use the other API, colours which is exactly the same, except in
|
||||
-- British English (e.g. @{colors.gray} is spelt @{colours.grey}).
|
||||
--
|
||||
-- @see colours
|
||||
-- @module colors
|
||||
--[[- The Colors API allows you to manipulate sets of colors.
|
||||
|
||||
This is useful in conjunction with Bundled Cables from the RedPower mod, RedNet
|
||||
Cables from the MineFactory Reloaded mod, and colors on Advanced Computers and
|
||||
Advanced Monitors.
|
||||
|
||||
For the non-American English version just replace @{colors} with @{colours} and
|
||||
it will use the other API, colours which is exactly the same, except in British
|
||||
English (e.g. @{colors.gray} is spelt @{colours.grey}).
|
||||
|
||||
On basic terminals (such as the Computer and Monitor), all the colors are
|
||||
converted to grayscale. This means you can still use all 16 colors on the
|
||||
screen, but they will appear as the nearest tint of gray. You can check if a
|
||||
terminal supports color by using the function @{term.isColor}.
|
||||
|
||||
Grayscale colors are calculated by taking the average of the three components,
|
||||
i.e. `(red + green + blue) / 3`.
|
||||
|
||||
<table class="pretty-table">
|
||||
<thead>
|
||||
<tr><th colspan="8" align="center">Default Colors</th></tr>
|
||||
<tr>
|
||||
<th rowspan="2" align="center">Color</th>
|
||||
<th colspan="3" align="center">Value</th>
|
||||
<th colspan="4" align="center">Default Palette Color</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Dec</th><th>Hex</th><th>Paint/Blit</th>
|
||||
<th>Preview</th><th>Hex</th><th>RGB</th><th>Grayscale</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>colors.white</code></td>
|
||||
<td align="right">1</td><td align="right">0x1</td><td align="right">0</td>
|
||||
<td style="background:#F0F0F0"></td><td>#F0F0F0</td><td>240, 240, 240</td>
|
||||
<td style="background:#F0F0F0"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.orange</code></td>
|
||||
<td align="right">2</td><td align="right">0x2</td><td align="right">1</td>
|
||||
<td style="background:#F2B233"></td><td>#F2B233</td><td>242, 178, 51</td>
|
||||
<td style="background:#9D9D9D"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.magenta</code></td>
|
||||
<td align="right">4</td><td align="right">0x4</td><td align="right">2</td>
|
||||
<td style="background:#E57FD8"></td><td>#E57FD8</td><td>229, 127, 216</td>
|
||||
<td style="background:#BEBEBE"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.lightBlue</code></td>
|
||||
<td align="right">8</td><td align="right">0x8</td><td align="right">3</td>
|
||||
<td style="background:#99B2F2"></td><td>#99B2F2</td><td>153, 178, 242</td>
|
||||
<td style="background:#BFBFBF"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.yellow</code></td>
|
||||
<td align="right">16</td><td align="right">0x10</td><td align="right">4</td>
|
||||
<td style="background:#DEDE6C"></td><td>#DEDE6C</td><td>222, 222, 108</td>
|
||||
<td style="background:#B8B8B8"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.lime</code></td>
|
||||
<td align="right">32</td><td align="right">0x20</td><td align="right">5</td>
|
||||
<td style="background:#7FCC19"></td><td>#7FCC19</td><td>127, 204, 25</td>
|
||||
<td style="background:#767676"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.pink</code></td>
|
||||
<td align="right">64</td><td align="right">0x40</td><td align="right">6</td>
|
||||
<td style="background:#F2B2CC"></td><td>#F2B2CC</td><td>242, 178, 204</td>
|
||||
<td style="background:#D0D0D0"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.gray</code></td>
|
||||
<td align="right">128</td><td align="right">0x80</td><td align="right">7</td>
|
||||
<td style="background:#4C4C4C"></td><td>#4C4C4C</td><td>76, 76, 76</td>
|
||||
<td style="background:#4C4C4C"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.lightGray</code></td>
|
||||
<td align="right">256</td><td align="right">0x100</td><td align="right">8</td>
|
||||
<td style="background:#999999"></td><td>#999999</td><td>153, 153, 153</td>
|
||||
<td style="background:#999999"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.cyan</code></td>
|
||||
<td align="right">512</td><td align="right">0x200</td><td align="right">9</td>
|
||||
<td style="background:#4C99B2"></td><td>#4C99B2</td><td>76, 153, 178</td>
|
||||
<td style="background:#878787"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.purple</code></td>
|
||||
<td align="right">1024</td><td align="right">0x400</td><td align="right">a</td>
|
||||
<td style="background:#B266E5"></td><td>#B266E5</td><td>178, 102, 229</td>
|
||||
<td style="background:#A9A9A9"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.blue</code></td>
|
||||
<td align="right">2048</td><td align="right">0x800</td><td align="right">b</td>
|
||||
<td style="background:#3366CC"></td><td>#3366CC</td><td>51, 102, 204</td>
|
||||
<td style="background:#777777"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.brown</code></td>
|
||||
<td align="right">4096</td><td align="right">0x1000</td><td align="right">c</td>
|
||||
<td style="background:#7F664C"></td><td>#7F664C</td><td>127, 102, 76</td>
|
||||
<td style="background:#656565"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.green</code></td>
|
||||
<td align="right">8192</td><td align="right">0x2000</td><td align="right">d</td>
|
||||
<td style="background:#57A64E"></td><td>#57A64E</td><td>87, 166, 78</td>
|
||||
<td style="background:#6E6E6E"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.red</code></td>
|
||||
<td align="right">16384</td><td align="right">0x4000</td><td align="right">e</td>
|
||||
<td style="background:#CC4C4C"></td><td>#CC4C4C</td><td>204, 76, 76</td>
|
||||
<td style="background:#767676"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>colors.black</code></td>
|
||||
<td align="right">32768</td><td align="right">0x8000</td><td align="right">f</td>
|
||||
<td style="background:#111111"></td><td>#111111</td><td>17, 17, 17</td>
|
||||
<td style="background:#111111"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@see colours
|
||||
@module colors
|
||||
]]
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
@@ -37,7 +159,7 @@ yellow = 0x10
|
||||
-- terminal colour of #7FCC19.
|
||||
lime = 0x20
|
||||
|
||||
--- Pink. Written as `6` in paint files and @{term.blit}, has a default
|
||||
--- Pink: Written as `6` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #F2B2CC.
|
||||
pink = 0x40
|
||||
|
||||
@@ -74,10 +196,11 @@ green = 0x2000
|
||||
red = 0x4000
|
||||
|
||||
--- Black: Written as `f` in paint files and @{term.blit}, has a default
|
||||
-- terminal colour of #191919.
|
||||
-- terminal colour of #111111.
|
||||
black = 0x8000
|
||||
|
||||
--- Combines a set of colors (or sets of colors) into a larger set.
|
||||
--- Combines a set of colors (or sets of colors) into a larger set. Useful for
|
||||
-- Bundled Cables.
|
||||
--
|
||||
-- @tparam number ... The colors to combine.
|
||||
-- @treturn number The union of the color sets given in `...`
|
||||
@@ -96,7 +219,8 @@ function combine(...)
|
||||
return r
|
||||
end
|
||||
|
||||
--- Removes one or more colors (or sets of colors) from an initial set.
|
||||
--- Removes one or more colors (or sets of colors) from an initial set. Useful
|
||||
-- for Bundled Cables.
|
||||
--
|
||||
-- Each parameter beyond the first may be a single color or may be a set of
|
||||
-- colors (in the latter case, all colors in the set are removed from the
|
||||
@@ -121,7 +245,8 @@ function subtract(colors, ...)
|
||||
return r
|
||||
end
|
||||
|
||||
--- Tests whether `color` is contained within `colors`.
|
||||
--- Tests whether `color` is contained within `colors`. Useful for Bundled
|
||||
-- Cables.
|
||||
--
|
||||
-- @tparam number colors A color, or color set
|
||||
-- @tparam number color A color or set of colors that `colors` should contain.
|
||||
@@ -145,7 +270,7 @@ end
|
||||
-- @treturn number The combined hexadecimal colour.
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colors.rgb(0.7, 0.2, 0.6)
|
||||
-- colors.unpackRGB(0.7, 0.2, 0.6)
|
||||
-- -- => 0xb23399
|
||||
-- ```
|
||||
function packRGB(r, g, b)
|
||||
@@ -153,7 +278,7 @@ function packRGB(r, g, b)
|
||||
expect(2, g, "number")
|
||||
expect(3, b, "number")
|
||||
return
|
||||
bit32.band(r * 255, 0xFF) * 2 ^ 16 +
|
||||
bit32.band(r * 255, 0xFF) * 2 ^ 16 +
|
||||
bit32.band(g * 255, 0xFF) * 2 ^ 8 +
|
||||
bit32.band(b * 255, 0xFF)
|
||||
end
|
||||
@@ -166,16 +291,16 @@ end
|
||||
-- @treturn number The blue channel, will be between 0 and 1.
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colors.rgb(0xb23399)
|
||||
-- colors.unpackRGB(0xb23399)
|
||||
-- -- => 0.7, 0.2, 0.6
|
||||
-- ```
|
||||
-- @see colors.packRGB
|
||||
function unpackRGB(rgb)
|
||||
expect(1, rgb, "number")
|
||||
return
|
||||
bit32.band(bit32.rshift(rgb, 16), 0xFF) / 255,
|
||||
bit32.band(bit32.rshift(rgb, 8), 0xFF) / 255,
|
||||
bit32.band(rgb, 0xFF) / 255
|
||||
bit32.band(bit32.rshift(rgb, 16), 0xFF) / 255,
|
||||
bit32.band(bit32.rshift(rgb, 8), 0xFF) / 255,
|
||||
bit32.band(rgb, 0xFF) / 255
|
||||
end
|
||||
|
||||
--- Either calls @{colors.packRGB} or @{colors.unpackRGB}, depending on how many
|
||||
@@ -192,12 +317,12 @@ end
|
||||
-- @deprecated Use @{packRGB} or @{unpackRGB} directly.
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colors.rgb(0xb23399)
|
||||
-- colors.unpackRGB(0xb23399)
|
||||
-- -- => 0.7, 0.2, 0.6
|
||||
-- ```
|
||||
-- @usage
|
||||
-- ```lua
|
||||
-- colors.rgb(0.7, 0.2, 0.6)
|
||||
-- colors.unpackRGB(0.7, 0.2, 0.6)
|
||||
-- -- => 0xb23399
|
||||
-- ```
|
||||
function rgb8(r, g, b)
|
||||
@@ -207,3 +332,21 @@ function rgb8(r, g, b)
|
||||
return packRGB(r, g, b)
|
||||
end
|
||||
end
|
||||
|
||||
-- Colour to hex lookup table for toBlit
|
||||
local color_hex_lookup = {}
|
||||
for i = 0, 15 do
|
||||
color_hex_lookup[2 ^ i] = string.format("%x", i)
|
||||
end
|
||||
|
||||
--- Converts the given color to a paint/blit hex character (0-9a-f).
|
||||
--
|
||||
-- This is equivalent to converting floor(log_2(color)) to hexadecimal.
|
||||
--
|
||||
-- @tparam number color The color to convert.
|
||||
-- @treturn string The blit hex code of the color.
|
||||
function toBlit(color)
|
||||
expect(1, color, "number")
|
||||
return color_hex_lookup[color] or
|
||||
string.format("%x", math.floor(math.log(color) / math.log(2)))
|
||||
end
|
||||
|
@@ -22,7 +22,7 @@ end
|
||||
--
|
||||
-- @tparam string name The name of the disk drive.
|
||||
-- @treturn boolean If something is in the disk drive.
|
||||
-- @usage disk.isPresent(false)
|
||||
-- @usage disk.isPresent("top")
|
||||
function isPresent(name)
|
||||
if isDrive(name) then
|
||||
return peripheral.call(name, "isDiskPresent")
|
||||
|
@@ -32,7 +32,7 @@ end
|
||||
-- @tparam string topic The topic to find
|
||||
-- @treturn string|nil The path to the given topic's help file, or `nil` if it
|
||||
-- cannot be found.
|
||||
-- @usage print(help.lookup("disk"))
|
||||
-- @usage help.lookup("disk")
|
||||
function lookup(_sTopic)
|
||||
expect(1, _sTopic, "string")
|
||||
-- Look on the path variable
|
||||
|
@@ -289,7 +289,7 @@ end
|
||||
-- The `mode` string can be any of the following:
|
||||
-- - **"r"**: Read mode
|
||||
-- - **"w"**: Write mode
|
||||
-- - **"w"**: Append mode
|
||||
-- - **"a"**: Append mode
|
||||
--
|
||||
-- The mode may also have a `b` at the end, which opens the file in "binary
|
||||
-- mode". This allows you to read binary files, as well as seek within a file.
|
||||
|
@@ -145,6 +145,7 @@ keys.cimcumflex = keys.circumflex --- @local
|
||||
--
|
||||
-- @tparam number code The key code to look up.
|
||||
-- @treturn string|nil The name of the key, or `nil` if not a valid key code.
|
||||
-- @usage keys.getName(keys.enter)
|
||||
function getName(_nKey)
|
||||
expect(1, _nKey, "number")
|
||||
return tKeys[_nKey]
|
||||
|
@@ -23,6 +23,25 @@ local function parseLine(tImageArg, sLine)
|
||||
table.insert(tImageArg, tLine)
|
||||
end
|
||||
|
||||
-- Sorts pairs of startX/startY/endX/endY such that the start is always the min
|
||||
local function sortCoords(startX, startY, endX, endY)
|
||||
local minX, maxX, minY, maxY
|
||||
|
||||
if startX <= endX then
|
||||
minX, maxX = startX, endX
|
||||
else
|
||||
minX, maxX = endX, startX
|
||||
end
|
||||
|
||||
if startY <= endY then
|
||||
minY, maxY = startY, endY
|
||||
else
|
||||
minY, maxY = endY, startY
|
||||
end
|
||||
|
||||
return minX, maxX, minY, maxY
|
||||
end
|
||||
|
||||
--- Parses an image from a multi-line string
|
||||
--
|
||||
-- @tparam string image The string containing the raw-image data.
|
||||
@@ -45,6 +64,10 @@ end
|
||||
--
|
||||
-- @treturn table|nil The parsed image data, suitable for use with
|
||||
-- @{paintutils.drawImage}, or `nil` if the file does not exist.
|
||||
-- @usage Load an image and draw it.
|
||||
--
|
||||
-- local image = paintutils.loadImage("test-image.nfp")
|
||||
-- paintutils.drawImage(image, term.getCursorPos())
|
||||
function loadImage(path)
|
||||
expect(1, path, "string")
|
||||
|
||||
@@ -71,9 +94,6 @@ function drawPixel(xPos, yPos, colour)
|
||||
expect(2, yPos, "number")
|
||||
expect(3, colour, "number", "nil")
|
||||
|
||||
if type(xPos) ~= "number" then error("bad argument #1 (expected number, got " .. type(xPos) .. ")", 2) end
|
||||
if type(yPos) ~= "number" then error("bad argument #2 (expected number, got " .. type(yPos) .. ")", 2) end
|
||||
if colour ~= nil and type(colour) ~= "number" then error("bad argument #3 (expected number, got " .. type(colour) .. ")", 2) end
|
||||
if colour then
|
||||
term.setBackgroundColor(colour)
|
||||
end
|
||||
@@ -91,6 +111,7 @@ end
|
||||
-- @tparam number endY The end y position of the line.
|
||||
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
|
||||
-- the current background colour if not specified.
|
||||
-- @usage paintutils.drawLine(2, 3, 30, 7, colors.red)
|
||||
function drawLine(startX, startY, endX, endY, colour)
|
||||
expect(1, startX, "number")
|
||||
expect(2, startY, "number")
|
||||
@@ -111,17 +132,7 @@ function drawLine(startX, startY, endX, endY, colour)
|
||||
return
|
||||
end
|
||||
|
||||
local minX = math.min(startX, endX)
|
||||
local maxX, minY, maxY
|
||||
if minX == startX then
|
||||
minY = startY
|
||||
maxX = endX
|
||||
maxY = endY
|
||||
else
|
||||
minY = endY
|
||||
maxX = startX
|
||||
maxY = startY
|
||||
end
|
||||
local minX, maxX, minY, maxY = sortCoords(startX, startY, endX, endY)
|
||||
|
||||
-- TODO: clip to screen rectangle?
|
||||
|
||||
@@ -164,6 +175,7 @@ end
|
||||
-- @tparam number endY The end y position of the line.
|
||||
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
|
||||
-- the current background colour if not specified.
|
||||
-- @usage paintutils.drawBox(2, 3, 30, 7, colors.red)
|
||||
function drawBox(startX, startY, endX, endY, nColour)
|
||||
expect(1, startX, "number")
|
||||
expect(2, startY, "number")
|
||||
@@ -177,37 +189,33 @@ function drawBox(startX, startY, endX, endY, nColour)
|
||||
endY = math.floor(endY)
|
||||
|
||||
if nColour then
|
||||
term.setBackgroundColor(nColour)
|
||||
term.setBackgroundColor(nColour) -- Maintain legacy behaviour
|
||||
else
|
||||
nColour = term.getBackgroundColour()
|
||||
end
|
||||
local colourHex = colours.toBlit(nColour)
|
||||
|
||||
if startX == endX and startY == endY then
|
||||
drawPixelInternal(startX, startY)
|
||||
return
|
||||
end
|
||||
|
||||
local minX = math.min(startX, endX)
|
||||
local maxX, minY, maxY
|
||||
if minX == startX then
|
||||
minY = startY
|
||||
maxX = endX
|
||||
maxY = endY
|
||||
else
|
||||
minY = endY
|
||||
maxX = startX
|
||||
maxY = startY
|
||||
end
|
||||
local minX, maxX, minY, maxY = sortCoords(startX, startY, endX, endY)
|
||||
local width = maxX - minX + 1
|
||||
|
||||
for x = minX, maxX do
|
||||
drawPixelInternal(x, minY)
|
||||
drawPixelInternal(x, maxY)
|
||||
end
|
||||
|
||||
if maxY - minY >= 2 then
|
||||
for y = minY + 1, maxY - 1 do
|
||||
drawPixelInternal(minX, y)
|
||||
drawPixelInternal(maxX, y)
|
||||
for y = minY, maxY do
|
||||
if y == minY or y == maxY then
|
||||
term.setCursorPos(minX, y)
|
||||
term.blit((" "):rep(width), colourHex:rep(width), colourHex:rep(width))
|
||||
else
|
||||
term.setCursorPos(minX, y)
|
||||
term.blit(" ", colourHex, colourHex)
|
||||
term.setCursorPos(maxX, y)
|
||||
term.blit(" ", colourHex, colourHex)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Draws a filled box on the current term from the specified start position to
|
||||
-- the specified end position.
|
||||
--
|
||||
@@ -220,6 +228,7 @@ end
|
||||
-- @tparam number endY The end y position of the line.
|
||||
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
|
||||
-- the current background colour if not specified.
|
||||
-- @usage paintutils.drawFilledBox(2, 3, 30, 7, colors.red)
|
||||
function drawFilledBox(startX, startY, endX, endY, nColour)
|
||||
expect(1, startX, "number")
|
||||
expect(2, startY, "number")
|
||||
@@ -233,29 +242,23 @@ function drawFilledBox(startX, startY, endX, endY, nColour)
|
||||
endY = math.floor(endY)
|
||||
|
||||
if nColour then
|
||||
term.setBackgroundColor(nColour)
|
||||
term.setBackgroundColor(nColour) -- Maintain legacy behaviour
|
||||
else
|
||||
nColour = term.getBackgroundColour()
|
||||
end
|
||||
local colourHex = colours.toBlit(nColour)
|
||||
|
||||
if startX == endX and startY == endY then
|
||||
drawPixelInternal(startX, startY)
|
||||
return
|
||||
end
|
||||
|
||||
local minX = math.min(startX, endX)
|
||||
local maxX, minY, maxY
|
||||
if minX == startX then
|
||||
minY = startY
|
||||
maxX = endX
|
||||
maxY = endY
|
||||
else
|
||||
minY = endY
|
||||
maxX = startX
|
||||
maxY = startY
|
||||
end
|
||||
local minX, maxX, minY, maxY = sortCoords(startX, startY, endX, endY)
|
||||
local width = maxX - minX + 1
|
||||
|
||||
for x = minX, maxX do
|
||||
for y = minY, maxY do
|
||||
drawPixelInternal(x, y)
|
||||
end
|
||||
for y = minY, maxY do
|
||||
term.setCursorPos(minX, y)
|
||||
term.blit((" "):rep(width), colourHex:rep(width), colourHex:rep(width))
|
||||
end
|
||||
end
|
||||
|
||||
|
@@ -1,18 +1,19 @@
|
||||
--- Provides a simple implementation of multitasking.
|
||||
--
|
||||
-- Functions are not actually executed simultaniously, but rather this API will
|
||||
-- automatically switch between them whenever they yield (eg whenever they call
|
||||
-- @{coroutine.yield}, or functions that call that - eg `os.pullEvent` - or
|
||||
-- functions that call that, etc - basically, anything that causes the function
|
||||
-- to "pause").
|
||||
--
|
||||
-- Each function executed in "parallel" gets its own copy of the event queue,
|
||||
-- and so "event consuming" functions (again, mostly anything that causes the
|
||||
-- script to pause - eg `sleep`, `rednet.receive`, most of the `turtle` API,
|
||||
-- etc) can safely be used in one without affecting the event queue accessed by
|
||||
-- the other.
|
||||
--
|
||||
-- @module parallel
|
||||
--[[- Provides a simple implementation of multitasking.
|
||||
|
||||
Functions are not actually executed simultaniously, but rather this API will
|
||||
automatically switch between them whenever they yield (eg whenever they call
|
||||
@{coroutine.yield}, or functions that call that - eg `os.pullEvent` - or
|
||||
functions that call that, etc - basically, anything that causes the function
|
||||
to "pause").
|
||||
|
||||
Each function executed in "parallel" gets its own copy of the event queue,
|
||||
and so "event consuming" functions (again, mostly anything that causes the
|
||||
script to pause - eg `sleep`, `rednet.receive`, most of the `turtle` API,
|
||||
etc) can safely be used in one without affecting the event queue accessed by
|
||||
the other.
|
||||
|
||||
@module parallel
|
||||
]]
|
||||
|
||||
local function create(...)
|
||||
local tFns = table.pack(...)
|
||||
@@ -70,21 +71,53 @@ local function runUntilLimit(_routines, _limit)
|
||||
end
|
||||
end
|
||||
|
||||
--- Switches between execution of the functions, until any of them
|
||||
-- finishes. If any of the functions errors, the message is propagated upwards
|
||||
-- from the @{parallel.waitForAny} call.
|
||||
--
|
||||
-- @tparam function ... The functions this task will run
|
||||
--[[- Switches between execution of the functions, until any of them
|
||||
finishes. If any of the functions errors, the message is propagated upwards
|
||||
from the @{parallel.waitForAny} call.
|
||||
|
||||
@tparam function ... The functions this task will run
|
||||
@usage Print a message every second until the `q` key is pressed.
|
||||
|
||||
local function tick()
|
||||
while true do
|
||||
os.sleep(1)
|
||||
print("Tick")
|
||||
end
|
||||
end
|
||||
local function wait_for_q()
|
||||
repeat
|
||||
local _, key = os.pullEvent("key")
|
||||
until key == keys.q
|
||||
print("Q was pressed!")
|
||||
end
|
||||
|
||||
parallel.waitForAny(tick, wait_for_q)
|
||||
print("Everything done!")
|
||||
]]
|
||||
function waitForAny(...)
|
||||
local routines = create(...)
|
||||
return runUntilLimit(routines, #routines - 1)
|
||||
end
|
||||
|
||||
--- Switches between execution of the functions, until all of them are
|
||||
-- finished. If any of the functions errors, the message is propagated upwards
|
||||
-- from the @{parallel.waitForAll} call.
|
||||
--
|
||||
-- @tparam function ... The functions this task will run
|
||||
--[[- Switches between execution of the functions, until all of them are
|
||||
finished. If any of the functions errors, the message is propagated upwards
|
||||
from the @{parallel.waitForAll} call.
|
||||
|
||||
@tparam function ... The functions this task will run
|
||||
@usage Start off two timers and wait for them both to run.
|
||||
|
||||
local function a()
|
||||
os.sleep(1)
|
||||
print("A is done")
|
||||
end
|
||||
local function b()
|
||||
os.sleep(3)
|
||||
print("B is done")
|
||||
end
|
||||
|
||||
parallel.waitForAll(a, b)
|
||||
print("Everything done!")
|
||||
]]
|
||||
function waitForAll(...)
|
||||
local routines = create(...)
|
||||
return runUntilLimit(routines, 0)
|
||||
|
@@ -191,7 +191,7 @@ end
|
||||
-- filter function, which takes the peripheral's name and wrapped table
|
||||
-- and returns if it should be included in the result.
|
||||
-- @treturn table... 0 or more wrapped peripherals matching the given filters.
|
||||
-- @usage local monitors = { peripheral.find("monitor") }
|
||||
-- @usage { peripheral.find("monitor") }
|
||||
-- @usage peripheral.find("modem", rednet.open)
|
||||
function find(ty, filter)
|
||||
expect(1, ty, "string")
|
||||
|
@@ -9,7 +9,7 @@ local expect, field = expect.expect, expect.field
|
||||
--- Slowly writes string text at current cursor position,
|
||||
-- character-by-character.
|
||||
--
|
||||
-- Like @{write}, this does not insert a newline at the end.
|
||||
-- Like @{_G.write}, this does not insert a newline at the end.
|
||||
--
|
||||
-- @tparam string sText The the text to write to the screen
|
||||
-- @tparam[opt] number nRate The number of characters to write each second,
|
||||
@@ -119,8 +119,8 @@ end
|
||||
-- displayed before prompting.
|
||||
-- @treturn number The number of lines printed.
|
||||
-- @usage
|
||||
-- local width, height = term.getSize()
|
||||
-- textutils.pagedPrint(("This is a rather verbose dose of repetition.\n"):rep(30), height - 2)
|
||||
-- local width, height = term.getSize()
|
||||
-- textutils.pagedPrint(("This is a rather verbose dose of repetition.\n"):rep(30), height - 2)
|
||||
function pagedPrint(_sText, _nFreeLines)
|
||||
expect(2, _nFreeLines, "number", "nil")
|
||||
-- Setup a redirector
|
||||
@@ -163,10 +163,11 @@ local function tabulateCommon(bPaged, ...)
|
||||
for n, t in ipairs(tAll) do
|
||||
if type(t) == "table" then
|
||||
for nu, sItem in pairs(t) do
|
||||
if type(sItem) ~= "string" then
|
||||
error("bad argument #" .. n .. "." .. nu .. " (expected string, got " .. type(sItem) .. ")", 3)
|
||||
local ty = type(sItem)
|
||||
if ty ~= "string" and ty ~= "number" then
|
||||
error("bad argument #" .. n .. "." .. nu .. " (expected string, got " .. ty .. ")", 3)
|
||||
end
|
||||
nMaxLen = math.max(#sItem + 1, nMaxLen)
|
||||
nMaxLen = math.max(#tostring(sItem) + 1, nMaxLen)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -432,7 +433,7 @@ do
|
||||
|
||||
--- Skip any whitespace
|
||||
local function skip(str, pos)
|
||||
local _, last = find(str, "^[ \n\r\v]+", pos)
|
||||
local _, last = find(str, "^[ \n\r\t]+", pos)
|
||||
if last then return last + 1 else return pos end
|
||||
end
|
||||
|
||||
@@ -453,13 +454,13 @@ do
|
||||
error_at(pos, "Unexpected %s, expected %s.", actual, exp)
|
||||
end
|
||||
|
||||
local function parse_string(str, pos)
|
||||
local function parse_string(str, pos, terminate)
|
||||
local buf, n = {}, 1
|
||||
|
||||
while true do
|
||||
local c = sub(str, pos, pos)
|
||||
if c == "" then error_at(pos, "Unexpected end of input, expected '\"'.") end
|
||||
if c == '"' then break end
|
||||
if c == terminate then break end
|
||||
|
||||
if c == '\\' then
|
||||
-- Handle the various escapes
|
||||
@@ -472,7 +473,7 @@ do
|
||||
buf[n], n, pos = utf8.char(tonumber(num_str, 16)), n + 1, pos + 6
|
||||
else
|
||||
local unesc = escapes[c]
|
||||
if not unesc then error_at(pos + 1, "Unknown escape character %q.", unesc) end
|
||||
if not unesc then error_at(pos + 1, "Unknown escape character %q.", c) end
|
||||
buf[n], n, pos = unesc, n + 1, pos + 2
|
||||
end
|
||||
elseif c >= '\x20' then
|
||||
@@ -485,13 +486,13 @@ do
|
||||
return concat(buf, "", 1, n - 1), pos + 1
|
||||
end
|
||||
|
||||
local valid = { b = true, B = true, s = true, S = true, l = true, L = true, f = true, F = true, d = true, D = true }
|
||||
local num_types = { b = true, B = true, s = true, S = true, l = true, L = true, f = true, F = true, d = true, D = true }
|
||||
local function parse_number(str, pos, opts)
|
||||
local _, last, num_str = find(str, '^(-?%d+%.?%d*[eE]?[+-]?%d*)', pos)
|
||||
local val = tonumber(num_str)
|
||||
if not val then error_at(pos, "Malformed number %q.", num_str) end
|
||||
|
||||
if opts.nbt_style and valid[sub(str, pos + 1, pos + 1)] then return val, last + 2 end
|
||||
if opts.nbt_style and num_types[sub(str, last + 1, last + 1)] then return val, last + 2 end
|
||||
|
||||
return val, last + 1
|
||||
end
|
||||
@@ -501,9 +502,11 @@ do
|
||||
return val, last + 1
|
||||
end
|
||||
|
||||
local arr_types = { I = true, L = true, B = true }
|
||||
local function decode_impl(str, pos, opts)
|
||||
local c = sub(str, pos, pos)
|
||||
if c == '"' then return parse_string(str, pos + 1)
|
||||
if c == '"' then return parse_string(str, pos + 1, '"')
|
||||
elseif c == "'" and opts.nbt_style then return parse_string(str, pos + 1, "\'")
|
||||
elseif c == "-" or c >= "0" and c <= "9" then return parse_number(str, pos, opts)
|
||||
elseif c == "t" then
|
||||
if sub(str, pos + 1, pos + 3) == "rue" then return true, pos + 4 end
|
||||
@@ -528,7 +531,7 @@ do
|
||||
|
||||
while true do
|
||||
local key, value
|
||||
if c == "\"" then key, pos = parse_string(str, pos + 1)
|
||||
if c == "\"" then key, pos = parse_string(str, pos + 1, "\"")
|
||||
elseif opts.nbt_style then key, pos = parse_ident(str, pos)
|
||||
else return expected(pos, c, "object key")
|
||||
end
|
||||
@@ -560,6 +563,11 @@ do
|
||||
pos = skip(str, pos + 1)
|
||||
c = sub(str, pos, pos)
|
||||
|
||||
if arr_types[c] and sub(str, pos + 1, pos + 1) == ";" and opts.nbt_style then
|
||||
pos = skip(str, pos + 2)
|
||||
c = sub(str, pos, pos)
|
||||
end
|
||||
|
||||
if c == "" then return expected(pos, c, "']'") end
|
||||
if c == "]" then return empty_json_array, pos + 1 end
|
||||
|
||||
@@ -699,7 +707,7 @@ unserialiseJSON = unserialise_json
|
||||
--
|
||||
-- @tparam string str The string to encode
|
||||
-- @treturn string The encoded string.
|
||||
-- @usage print("https://example.com/?view=" .. textutils.urlEncode(read()))
|
||||
-- @usage print("https://example.com/?view=" .. textutils.urlEncode("some text&things"))
|
||||
function urlEncode(str)
|
||||
expect(1, str, "string")
|
||||
if str then
|
||||
@@ -712,7 +720,7 @@ function urlEncode(str)
|
||||
else
|
||||
-- Non-ASCII (encode as UTF-8)
|
||||
return
|
||||
string.format("%%%02X", 192 + bit32.band(bit32.arshift(n, 6), 31)) ..
|
||||
string.format("%%%02X", 192 + bit32.band(bit32.arshift(n, 6), 31)) ..
|
||||
string.format("%%%02X", 128 + bit32.band(n, 63))
|
||||
end
|
||||
end)
|
||||
@@ -737,8 +745,8 @@ local tEmpty = {}
|
||||
--
|
||||
-- @treturn { string... } The (possibly empty) list of completions.
|
||||
-- @see shell.setCompletionFunction
|
||||
-- @see read
|
||||
-- @usage textutils.complete( "pa", getfenv() )
|
||||
-- @see _G.read
|
||||
-- @usage textutils.complete( "pa", _ENV )
|
||||
function complete(sSearchText, tSearchTable)
|
||||
expect(1, sSearchText, "string")
|
||||
expect(2, tSearchTable, "table", "nil")
|
||||
|
@@ -125,7 +125,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
-- Helper functions
|
||||
local function updateCursorPos()
|
||||
if nCursorX >= 1 and nCursorY >= 1 and
|
||||
nCursorX <= nWidth and nCursorY <= nHeight then
|
||||
nCursorX <= nWidth and nCursorY <= nHeight then
|
||||
parent.setCursorPos(nX + nCursorX - 1, nY + nCursorY - 1)
|
||||
else
|
||||
parent.setCursorPos(0, 0)
|
||||
@@ -440,7 +440,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
end
|
||||
|
||||
--- Get the buffered contents of a line in this window.
|
||||
---
|
||||
--
|
||||
-- @tparam number y The y position of the line to get.
|
||||
-- @treturn string The textual content of this line.
|
||||
-- @treturn string The text colours of this line, suitable for use with @{term.blit}.
|
||||
@@ -474,6 +474,14 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
|
||||
end
|
||||
end
|
||||
|
||||
--- Get whether this window is visible. Invisible windows will not be
|
||||
-- drawn to the screen until they are made visible again.
|
||||
--
|
||||
-- @treturn boolean Whether this window is visible.
|
||||
-- @see Window:setVisible
|
||||
function window.isVisible()
|
||||
return bVisible
|
||||
end
|
||||
--- Draw this window. This does nothing if the window is not visible.
|
||||
--
|
||||
-- @see Window:setVisible
|
||||
|
@@ -1,3 +1,63 @@
|
||||
# New features in CC: Restitched 1.95.0
|
||||
|
||||
* Optimise the paint program's initial render.
|
||||
* Several documentation improvments (Gibbo3771, MCJack123).
|
||||
* `fs.combine` now accepts multiple arguments.
|
||||
* Add a setting (`bios.strict_globals`) to error when accidentally declaring a global. (Lupus590).
|
||||
* Add an improved help viewer which allows scrolling up and down (MCJack123).
|
||||
* Add `cc.strings` module, with utilities for wrapping text (Lupus590).
|
||||
* The `clear` program now allows resetting the palette too (Luca0208).
|
||||
|
||||
And several bug fixes:
|
||||
* Fix memory leak in generic peripherals.
|
||||
* Fix crash when a turtle is broken while being ticked.
|
||||
* `textutils.*tabulate` now accepts strings _or_ numbers.
|
||||
* We now deny _all_ local IPs, using the magic `$private` host. Previously the IPv6 loopback interface was not blocked.
|
||||
* Fix crash when rendering monitors if the block has not yet been synced. You will need to regenerate the config file to apply this change.
|
||||
* `read` now supports numpad enter (TheWireLord)
|
||||
* Correctly handle HTTP redirects to URLs containing escape characters.
|
||||
* Fix integer overflow in `os.epoch`.
|
||||
* Allow using pickaxes (and other items) for turtle upgrades which have mod-specific NBT.
|
||||
* Fix duplicate turtle/pocket upgrade recipes appearing in JEI.
|
||||
|
||||
# New features in CC: Tweaked 1.94.0
|
||||
|
||||
* Add getter for window visibility (devomaa)
|
||||
* Generic peripherals are no longer experimental, and on by default.
|
||||
* Use term.blit to draw boxes in paintutils (Lemmmy).
|
||||
|
||||
And several bug fixes:
|
||||
* Fix turtles not getting advancements when turtles are on.
|
||||
* Draw in-hand pocket computers with the correct transparent flags enabled.
|
||||
* Several bug fixes to SNBT parsing.
|
||||
* Fix several programs using their original name instead of aliases in usage hints (Lupus590).
|
||||
|
||||
# New features in CC: Tweaked 1.93.1
|
||||
|
||||
* Various documentation improvements (Lemmmy).
|
||||
* Fix TBO monitor renderer on some older graphics cards (Lemmmy).
|
||||
|
||||
# New features in CC: Tweaked 1.93.0
|
||||
|
||||
* Update Swedish translations (Granddave).
|
||||
* Printers use item tags to check dyes.
|
||||
* HTTP rules may now be targetted for a specific port.
|
||||
* Don't propagate adjacent redstone signals through computers.
|
||||
|
||||
And several bug fixes:
|
||||
* Fix NPEs when turtles interact with containers.
|
||||
|
||||
# New features in CC: Tweaked 1.92.0
|
||||
|
||||
* Bump Cobalt version:
|
||||
* Add support for the __pairs metamethod.
|
||||
* string.format now uses the __tostring metamethod.
|
||||
* Add date-specific MOTDs (MCJack123).
|
||||
|
||||
And several bug fixes:
|
||||
* Correctly handle tabs within textutils.unserailizeJSON.
|
||||
* Fix sheep not dropping items when sheared by turtles.
|
||||
|
||||
# New features in CC: Tweaked 1.91.0
|
||||
|
||||
* [Generic peripherals] Expose NBT hashes of items to inventory methods.
|
||||
|
@@ -1 +1,6 @@
|
||||
clear clears the screen.
|
||||
clear clears the screen and/or resets the palette.
|
||||
ex:
|
||||
"clear" clears the screen, but keeps the palette.
|
||||
"clear screen" does the same as "clear"
|
||||
"clear palette" resets the palette, but doesn't clear the screen
|
||||
"clear all" clears the screen and resets the palette
|
||||
|
@@ -1,15 +1,23 @@
|
||||
New features in CC: Tweaked 1.91.0
|
||||
New features in CC: Restitched 1.95.0
|
||||
|
||||
* [Generic peripherals] Expose NBT hashes of items to inventory methods.
|
||||
* Bump Cobalt version
|
||||
* Optimise handling of string concatenation.
|
||||
* Add string.{pack,unpack,packsize} (MCJack123)
|
||||
* Update to 1.16.2
|
||||
* Optimise the paint program's initial render.
|
||||
* Several documentation improvments (Gibbo3771, MCJack123).
|
||||
* `fs.combine` now accepts multiple arguments.
|
||||
* Add a setting (`bios.strict_globals`) to error when accidentally declaring a global. (Lupus590).
|
||||
* Add an improved help viewer which allows scrolling up and down (MCJack123).
|
||||
* Add `cc.strings` module, with utilities for wrapping text (Lupus590).
|
||||
* The `clear` program now allows resetting the palette too (Luca0208).
|
||||
|
||||
And several bug fixes:
|
||||
* Escape non-ASCII characters in JSON strings (neumond)
|
||||
* Make field names in fs.attributes more consistent (abby)
|
||||
* Fix textutils.formatTime correctly handle 12 AM (R93950X)
|
||||
* Fix turtles placing buckets multiple times.
|
||||
* Fix memory leak in generic peripherals.
|
||||
* Fix crash when a turtle is broken while being ticked.
|
||||
* `textutils.*tabulate` now accepts strings _or_ numbers.
|
||||
* We now deny _all_ local IPs, using the magic `$private` host. Previously the IPv6 loopback interface was not blocked.
|
||||
* Fix crash when rendering monitors if the block has not yet been synced. You will need to regenerate the config file to apply this change.
|
||||
* `read` now supports numpad enter (TheWireLord)
|
||||
* Correctly handle HTTP redirects to URLs containing escape characters.
|
||||
* Fix integer overflow in `os.epoch`.
|
||||
* Allow using pickaxes (and other items) for turtle upgrades which have mod-specific NBT.
|
||||
* Fix duplicate turtle/pocket upgrade recipes appearing in JEI.
|
||||
|
||||
Type "help changelog" to see the full version history.
|
||||
|
@@ -1,5 +1,5 @@
|
||||
--- A collection of helper methods for working with input completion, such
|
||||
-- as that require by @{read}.
|
||||
-- as that require by @{_G.read}.
|
||||
--
|
||||
-- @module cc.completion
|
||||
-- @see cc.shell.completion For additional helpers to use with
|
||||
@@ -29,7 +29,7 @@ end
|
||||
-- @tparam { string... } choices The list of choices to complete from.
|
||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed item.
|
||||
-- @treturn { string... } A list of suffixes of matching strings.
|
||||
-- @usage Call @{read}, completing the names of various animals.
|
||||
-- @usage Call @{_G.read}, completing the names of various animals.
|
||||
--
|
||||
-- local animals = { "dog", "cat", "lion", "unicorn" }
|
||||
-- read(nil, nil, function(text) return choice(text, animals) end)
|
||||
@@ -45,7 +45,9 @@ end
|
||||
-- @tparam string text The input string to complete.
|
||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed name.
|
||||
-- @treturn { string... } A list of suffixes of matching peripherals.
|
||||
-- @usage read(nil, nil, peripheral)
|
||||
-- @usage
|
||||
-- local completion = require "cc.completion"
|
||||
-- read(nil, nil, completion.peripheral)
|
||||
local function peripheral_(text, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, add_space, "boolean", "nil")
|
||||
@@ -59,7 +61,9 @@ local sides = redstone.getSides()
|
||||
-- @tparam string text The input string to complete.
|
||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed side.
|
||||
-- @treturn { string... } A list of suffixes of matching sides.
|
||||
-- @usage read(nil, nil, side)
|
||||
-- @usage
|
||||
-- local completion = require "cc.completion"
|
||||
-- read(nil, nil, completion.side)
|
||||
local function side(text, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, add_space, "boolean", "nil")
|
||||
@@ -71,7 +75,9 @@ end
|
||||
-- @tparam string text The input string to complete.
|
||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed settings.
|
||||
-- @treturn { string... } A list of suffixes of matching settings.
|
||||
-- @usage read(nil, nil, setting)
|
||||
-- @usage
|
||||
-- local completion = require "cc.completion"
|
||||
-- read(nil, nil, completion.setting)
|
||||
local function setting(text, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, add_space, "boolean", "nil")
|
||||
@@ -85,7 +91,9 @@ local command_list
|
||||
-- @tparam string text The input string to complete.
|
||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed command.
|
||||
-- @treturn { string... } A list of suffixes of matching commands.
|
||||
-- @usage read(nil, nil, command)
|
||||
-- @usage
|
||||
-- local completion = require "cc.completion"
|
||||
-- read(nil, nil, completion.command)
|
||||
local function command(text, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, add_space, "boolean", "nil")
|
||||
|
@@ -1,7 +1,26 @@
|
||||
--- The @{cc.expect} library provides helper functions for verifying that
|
||||
-- function arguments are well-formed and of the correct type.
|
||||
--
|
||||
-- @module cc.expect
|
||||
--[[- The @{cc.expect} library provides helper functions for verifying that
|
||||
function arguments are well-formed and of the correct type.
|
||||
|
||||
@module cc.expect
|
||||
@usage Define a basic function and check it has the correct arguments.
|
||||
|
||||
local expect = require "cc.expect"
|
||||
local expect, field = expect.expect, expect.field
|
||||
|
||||
local function add_person(name, info)
|
||||
expect(1, name, "string")
|
||||
expect(2, info, "table", "nil")
|
||||
|
||||
if info then
|
||||
print("Got age=", field(info, "age", "number"))
|
||||
print("Got gender=", field(info, "gender", "string", "nil"))
|
||||
end
|
||||
end
|
||||
|
||||
add_person("Anastazja") -- `info' is optional
|
||||
add_person("Kion", { age = 23 }) -- `gender' is optional
|
||||
add_person("Caoimhin", { age = 23, gender = true }) -- error!
|
||||
]]
|
||||
|
||||
local native_select, native_type = select, type
|
||||
|
||||
@@ -34,7 +53,7 @@ local function expect(index, value, ...)
|
||||
local name
|
||||
if native_type(debug) == "table" and native_type(debug.getinfo) == "function" then
|
||||
local ok, info = pcall(debug.getinfo, 3, "nS")
|
||||
if ok and info.name and #info.name ~= "" and info.what ~= "C" then name = info.name end
|
||||
if ok and info.name and info.name ~= "" and info.what ~= "C" then name = info.name end
|
||||
end
|
||||
|
||||
local type_names = get_type_names(...)
|
||||
|
@@ -13,11 +13,11 @@
|
||||
-- @module cc.pretty
|
||||
-- @usage Print a table to the terminal
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- pretty.write(pretty.dump({ 1, 2, 3 }))
|
||||
-- pretty.print(pretty.pretty({ 1, 2, 3 }))
|
||||
--
|
||||
-- @usage Build a custom document and display it
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- pretty.write(pretty.group(pretty.text("hello") .. pretty.space_line .. pretty.text("world")))
|
||||
-- pretty.print(pretty.group(pretty.text("hello") .. pretty.space_line .. pretty.text("world")))
|
||||
|
||||
local expect = require "cc.expect"
|
||||
local expect, field = expect.expect, expect.field
|
||||
@@ -67,6 +67,9 @@ end
|
||||
-- @tparam[opt] number colour The colour this text should be printed with. If not given, we default to the current
|
||||
-- colour.
|
||||
-- @treturn Doc The document with the provided text.
|
||||
-- @usage Write some blue text.
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- pretty.print(pretty.text("Hello!", colours.blue))
|
||||
local function text(text, colour)
|
||||
expect(1, text, "string")
|
||||
expect(2, colour, "number", "nil")
|
||||
@@ -101,8 +104,11 @@ end
|
||||
--
|
||||
-- @tparam Doc|string ... The documents to concatenate.
|
||||
-- @treturn Doc The concatenated documents.
|
||||
-- @usage pretty.concat(doc1, " - ", doc2)
|
||||
-- @usage doc1 .. " - " .. doc2
|
||||
-- @usage
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- local doc1, doc2 = pretty.text("doc1"), pretty.text("doc2")
|
||||
-- print(pretty.concat(doc1, " - ", doc2))
|
||||
-- print(doc1 .. " - " .. doc2) -- Also supports ..
|
||||
local function concat(...)
|
||||
local args = table.pack(...)
|
||||
for i = 1, args.n do
|
||||
@@ -135,7 +141,9 @@ Doc.__concat = concat --- @local
|
||||
-- @tparam number depth The number of spaces with which the document should be indented.
|
||||
-- @tparam Doc doc The document to indent.
|
||||
-- @treturn Doc The nested document.
|
||||
-- @usage pretty.nest(2, pretty.text("foo\nbar"))
|
||||
-- @usage
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- print(pretty.nest(2, pretty.text("foo\nbar")))
|
||||
local function nest(depth, doc)
|
||||
expect(1, depth, "number")
|
||||
if getmetatable(doc) ~= Doc then expect(2, doc, "document") end
|
||||
@@ -169,6 +177,12 @@ end
|
||||
--
|
||||
-- @tparam Doc doc The document to group.
|
||||
-- @treturn Doc The grouped document.
|
||||
-- @usage Uses group to show things being displayed on one or multiple lines.
|
||||
--
|
||||
-- local pretty = require "cc.pretty"
|
||||
-- local doc = pretty.group("Hello" .. pretty.space_line .. "World")
|
||||
-- print(pretty.render(doc, 5)) -- On multiple lines
|
||||
-- print(pretty.render(doc, 20)) -- Collapsed onto one.
|
||||
local function group(doc)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
|
||||
|
@@ -9,8 +9,8 @@
|
||||
-- @usage Construct the package and require function, and insert them into a
|
||||
-- custom environment.
|
||||
--
|
||||
-- local env = setmetatable({}, { __index = _ENV })
|
||||
-- local r = require "cc.require"
|
||||
-- local env = setmetatable({}, { __index = _ENV })
|
||||
-- env.require, env.package = r.make(env, "/")
|
||||
|
||||
local expect = require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua")
|
||||
|
@@ -1,15 +1,28 @@
|
||||
--- A collection of helper methods for working with shell completion.
|
||||
--
|
||||
-- Most programs may be completed using the @{build} helper method, rather than
|
||||
-- manually switching on the argument index.
|
||||
--
|
||||
-- Note, the helper functions within this module do not accept an argument index,
|
||||
-- and so are not directly usable with the @{shell.setCompletionFunction}. Instead,
|
||||
-- wrap them using @{build}, or your own custom function.
|
||||
--
|
||||
-- @module cc.shell.completion
|
||||
-- @see cc.completion For more general helpers, suitable for use with @{read}.
|
||||
-- @see shell.setCompletionFunction
|
||||
--[[- A collection of helper methods for working with shell completion.
|
||||
|
||||
Most programs may be completed using the @{build} helper method, rather than
|
||||
manually switching on the argument index.
|
||||
|
||||
Note, the helper functions within this module do not accept an argument index,
|
||||
and so are not directly usable with the @{shell.setCompletionFunction}. Instead,
|
||||
wrap them using @{build}, or your own custom function.
|
||||
|
||||
@module cc.shell.completion
|
||||
@see cc.completion For more general helpers, suitable for use with @{_G.read}.
|
||||
@see shell.setCompletionFunction
|
||||
|
||||
@usage Register a completion handler for example.lua which prompts for a
|
||||
choice of options, followed by a directory, and then multiple files.
|
||||
|
||||
local completion = require "cc.shell.completion"
|
||||
local complete = completion.build(
|
||||
{ completion.choice, { "get", "put" } },
|
||||
completion.dir,
|
||||
{ completion.file, many = true }
|
||||
)
|
||||
shell.setCompletionFunction("example.lua", complete)
|
||||
read(nil, nil, shell.complete, "example ")
|
||||
]]
|
||||
|
||||
local expect = require "cc.expect".expect
|
||||
local completion = require "cc.completion"
|
||||
@@ -69,37 +82,29 @@ local function program(shell, text)
|
||||
return shell.completeProgram(text)
|
||||
end
|
||||
|
||||
--- A helper function for building shell completion arguments.
|
||||
--
|
||||
-- This accepts a series of single-argument completion functions, and combines
|
||||
-- them into a function suitable for use with @{shell.setCompletionFunction}.
|
||||
--
|
||||
-- @tparam nil|table|function ... Every argument to @{build} represents an argument
|
||||
-- to the program you wish to complete. Each argument can be one of three types:
|
||||
--
|
||||
-- - `nil`: This argument will not be completed.
|
||||
--
|
||||
-- - A function: This argument will be completed with the given function. It is
|
||||
-- called with the @{shell} object, the string to complete and the arguments
|
||||
-- before this one.
|
||||
--
|
||||
-- - A table: This acts as a more powerful version of the function case. The table
|
||||
-- must have a function as the first item - this will be called with the shell,
|
||||
-- string and preceding arguments as above, but also followed by any additional
|
||||
-- items in the table. This provides a more convenient interface to pass
|
||||
-- options to your completion functions.
|
||||
--
|
||||
-- If this table is the last argument, it may also set the `many` key to true,
|
||||
-- which states this function should be used to complete any remaining arguments.
|
||||
--
|
||||
-- @usage Prompt for a choice of options, followed by a directory, and then multiple
|
||||
-- files.
|
||||
--
|
||||
-- complete.build(
|
||||
-- { complete.choice, { "get", "put" } },
|
||||
-- complete.dir,
|
||||
-- { complete.file, many = true }
|
||||
-- )
|
||||
--[[- A helper function for building shell completion arguments.
|
||||
|
||||
This accepts a series of single-argument completion functions, and combines
|
||||
them into a function suitable for use with @{shell.setCompletionFunction}.
|
||||
|
||||
@tparam nil|table|function ... Every argument to @{build} represents an argument
|
||||
to the program you wish to complete. Each argument can be one of three types:
|
||||
|
||||
- `nil`: This argument will not be completed.
|
||||
|
||||
- A function: This argument will be completed with the given function. It is
|
||||
called with the @{shell} object, the string to complete and the arguments
|
||||
before this one.
|
||||
|
||||
- A table: This acts as a more powerful version of the function case. The table
|
||||
must have a function as the first item - this will be called with the shell,
|
||||
string and preceding arguments as above, but also followed by any additional
|
||||
items in the table. This provides a more convenient interface to pass
|
||||
options to your completion functions.
|
||||
|
||||
If this table is the last argument, it may also set the `many` key to true,
|
||||
which states this function should be used to complete any remaining arguments.
|
||||
]]
|
||||
local function build(...)
|
||||
local arguments = table.pack(...)
|
||||
for i = 1, arguments.n do
|
||||
|
@@ -0,0 +1,102 @@
|
||||
--- Various utilities for working with strings and text.
|
||||
--
|
||||
-- @see textutils For additional string related utilities.
|
||||
|
||||
local expect = require "cc.expect".expect
|
||||
|
||||
--- Wraps a block of text, so that each line fits within the given width.
|
||||
--
|
||||
-- This may be useful if you want to wrap text before displaying it to a
|
||||
-- @{monitor} or @{printer} without using @{_G.print|print}.
|
||||
--
|
||||
-- @tparam string text The string to wrap.
|
||||
-- @tparam[opt] number width The width to constrain to, defaults to the width of
|
||||
-- the terminal.
|
||||
--
|
||||
-- @treturn { string... } The wrapped input string.
|
||||
-- @usage require "cc.strings".wrap("This is a long piece of text", 10)
|
||||
local function wrap(text, width)
|
||||
expect(1, text, "string")
|
||||
expect(2, width, "number", "nil")
|
||||
width = width or term.getSize()
|
||||
|
||||
|
||||
local lines, lines_n, current_line = {}, 0, ""
|
||||
local function push_line()
|
||||
lines_n = lines_n + 1
|
||||
lines[lines_n] = current_line
|
||||
current_line = ""
|
||||
end
|
||||
|
||||
local pos, length = 1, #text
|
||||
local sub, match = string.sub, string.match
|
||||
while pos <= length do
|
||||
local head = sub(text, pos, pos)
|
||||
if head == " " or head == "\t" then
|
||||
local whitespace = match(text, "^[ \t]+", pos)
|
||||
current_line = current_line .. whitespace
|
||||
pos = pos + #whitespace
|
||||
elseif head == "\n" then
|
||||
push_line()
|
||||
pos = pos + 1
|
||||
else
|
||||
local word = match(text, "^[^ \t\n]+", pos)
|
||||
pos = pos + #word
|
||||
if #word > width then
|
||||
-- Print a multiline word
|
||||
while #word > 0 do
|
||||
local space_remaining = width - #current_line - 1
|
||||
if space_remaining <= 0 then
|
||||
push_line()
|
||||
space_remaining = width
|
||||
end
|
||||
|
||||
current_line = current_line .. sub(word, 1, space_remaining)
|
||||
word = sub(word, space_remaining + 1)
|
||||
end
|
||||
else
|
||||
-- Print a word normally
|
||||
if width - #current_line < #word then push_line() end
|
||||
current_line = current_line .. word
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
push_line()
|
||||
|
||||
-- Trim whitespace longer than width.
|
||||
for k, line in pairs(lines) do
|
||||
line = line:sub(1, width)
|
||||
lines[k] = line
|
||||
end
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
--- Makes the input string a fixed width. This either truncates it, or pads it
|
||||
-- with spaces.
|
||||
--
|
||||
-- @tparam string line The string to normalise.
|
||||
-- @tparam[opt] number width The width to constrain to, defaults to the width of
|
||||
-- the terminal.
|
||||
--
|
||||
-- @treturn string The string with a specific width.
|
||||
-- @usage require "cc.strings".ensure_width("a short string", 20)
|
||||
-- @usage require "cc.strings".ensure_width("a rather long string which is truncated", 20)
|
||||
local function ensure_width(line, width)
|
||||
expect(1, line, "string")
|
||||
expect(2, width, "number", "nil")
|
||||
width = width or term.getSize()
|
||||
|
||||
line = line:sub(1, width)
|
||||
if #line < width then
|
||||
line = line .. (" "):rep(width - #line)
|
||||
end
|
||||
|
||||
return line
|
||||
end
|
||||
|
||||
return {
|
||||
wrap = wrap,
|
||||
ensure_width = ensure_width,
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
Please report bugs at https://github.com/Zundrel/cc-tweaked-fabric. Thanks!
|
||||
View the documentation at https://wiki.computercraft.cc
|
||||
Please report bugs at https://github.com/Merith-TK/cc-restiched. Thanks!
|
||||
View the documentation at https://tweaked.cc
|
||||
Show off your programs or ask for help at our forum: https://forums.computercraft.cc
|
||||
You can disable these messages by running "set motd.enable false".
|
||||
Use "pastebin put" to upload a program to pastebin.
|
||||
|
@@ -1,6 +1,7 @@
|
||||
local tArgs = { ... }
|
||||
if #tArgs > 2 then
|
||||
print("Usage: alias <alias> <program>")
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usage: " .. programName .. " <alias> <program>")
|
||||
return
|
||||
end
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
local tArgs = { ... }
|
||||
if #tArgs < 1 then
|
||||
print("Usage: cd <path>")
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usage: " .. programName .. " <path>")
|
||||
return
|
||||
end
|
||||
|
||||
|
@@ -1,2 +1,33 @@
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
local tArgs = { ... }
|
||||
|
||||
local function printUsage()
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usages:")
|
||||
print(programName)
|
||||
print(programName .. " screen")
|
||||
print(programName .. " palette")
|
||||
print(programName .. " all")
|
||||
end
|
||||
|
||||
local function clear()
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
end
|
||||
|
||||
local function resetPalette()
|
||||
for i = 0, 15 do
|
||||
term.setPaletteColour(math.pow(2, i), term.nativePaletteColour(math.pow(2, i)))
|
||||
end
|
||||
end
|
||||
|
||||
local sCommand = tArgs[1] or "screen"
|
||||
if sCommand == "screen" then
|
||||
clear()
|
||||
elseif sCommand == "palette" then
|
||||
resetPalette()
|
||||
elseif sCommand == "all" then
|
||||
clear()
|
||||
resetPalette()
|
||||
else
|
||||
printUsage()
|
||||
end
|
||||
|
@@ -4,7 +4,8 @@ if not commands then
|
||||
return
|
||||
end
|
||||
if #tArgs == 0 then
|
||||
printError("Usage: exec <command>")
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
printError("Usage: " .. programName .. " <command>")
|
||||
return
|
||||
end
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
local tArgs = { ... }
|
||||
if #tArgs < 2 then
|
||||
print("Usage: cp <source> <destination>")
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usage: " .. programName .. " <source> <destination>")
|
||||
return
|
||||
end
|
||||
|
||||
|
@@ -1,7 +1,8 @@
|
||||
local args = table.pack(...)
|
||||
|
||||
if args.n < 1 then
|
||||
print("Usage: rm <paths>")
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usage: " .. programName .. " <paths>")
|
||||
return
|
||||
end
|
||||
|
||||
|
@@ -1,7 +1,8 @@
|
||||
-- Get file to edit
|
||||
local tArgs = { ... }
|
||||
if #tArgs == 0 then
|
||||
print("Usage: edit <path>")
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usage: " .. programName .. " <path>")
|
||||
return
|
||||
end
|
||||
|
||||
|
@@ -1,7 +1,8 @@
|
||||
-- Get arguments
|
||||
local tArgs = { ... }
|
||||
if #tArgs == 0 then
|
||||
print("Usage: eject <drive>")
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usage: " .. programName .. " <drive>")
|
||||
return
|
||||
end
|
||||
|
||||
|
@@ -34,7 +34,8 @@ end
|
||||
-- Determines if the file exists, and can be edited on this computer
|
||||
local tArgs = { ... }
|
||||
if #tArgs == 0 then
|
||||
print("Usage: paint <path>")
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usage: " .. programName .. " <path>")
|
||||
return
|
||||
end
|
||||
local sPath = shell.resolve(tArgs[1])
|
||||
@@ -232,14 +233,31 @@ local function drawCanvasPixel(x, y)
|
||||
end
|
||||
end
|
||||
|
||||
local color_hex_lookup = {}
|
||||
for i = 0, 15 do
|
||||
color_hex_lookup[2 ^ i] = string.format("%x", i)
|
||||
end
|
||||
|
||||
--[[
|
||||
Converts each colour in a single line of the canvas and draws it
|
||||
returns: nil
|
||||
]]
|
||||
local text, fg, bg = "", "", ""
|
||||
local function drawCanvasLine(y)
|
||||
for x = 1, w - 2 do
|
||||
drawCanvasPixel(x, y)
|
||||
local pixel = getCanvasPixel(x, y)
|
||||
if pixel then
|
||||
text = text .. " "
|
||||
fg = fg .. "0"
|
||||
bg = bg .. color_hex_lookup[pixel or canvasColour]
|
||||
else
|
||||
text = text .. "\127"
|
||||
fg = fg .. color_hex_lookup[colours.grey]
|
||||
bg = bg .. color_hex_lookup[canvasColour]
|
||||
end
|
||||
end
|
||||
term.setCursorPos(1, y)
|
||||
term.blit(text, fg, bg)
|
||||
end
|
||||
|
||||
--[[
|
||||
|
@@ -1,10 +1,11 @@
|
||||
local tArgs = { ... }
|
||||
|
||||
local function printUsage()
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usages:")
|
||||
print("dj play")
|
||||
print("dj play <drive>")
|
||||
print("dj stop")
|
||||
print(programName .. " play")
|
||||
print(programName .. " play <drive>")
|
||||
print(programName .. " stop")
|
||||
end
|
||||
|
||||
if #tArgs > 2 then
|
||||
|
@@ -1,8 +1,9 @@
|
||||
local function printUsage()
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usages:")
|
||||
print("gps host")
|
||||
print("gps host <x> <y> <z>")
|
||||
print("gps locate")
|
||||
print(programName .. " host")
|
||||
print(programName .. " host <x> <y> <z>")
|
||||
print(programName .. " locate")
|
||||
end
|
||||
|
||||
local tArgs = { ... }
|
||||
|
@@ -13,14 +13,119 @@ if sTopic == "index" then
|
||||
return
|
||||
end
|
||||
|
||||
local strings = require "cc.strings"
|
||||
local function word_wrap(text, width)
|
||||
local lines = strings.wrap(text, width)
|
||||
|
||||
-- Normalise the strings suitable for use with blit. We could skip this and
|
||||
-- just use term.write, but saves us a clearLine call.
|
||||
for k, line in pairs(lines) do
|
||||
lines[k] = strings.ensure_width(line, width)
|
||||
end
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
local sFile = help.lookup(sTopic)
|
||||
local file = sFile ~= nil and io.open(sFile) or nil
|
||||
if file then
|
||||
local sContents = file:read("*a")
|
||||
file:close()
|
||||
|
||||
local _, nHeight = term.getSize()
|
||||
textutils.pagedPrint(sContents, nHeight - 3)
|
||||
else
|
||||
print("No help available")
|
||||
if not file then
|
||||
printError("No help available")
|
||||
return
|
||||
end
|
||||
|
||||
local contents = file:read("*a"):gsub("(\n *)[-*]( +)", "%1\7%2")
|
||||
file:close()
|
||||
|
||||
local width, height = term.getSize()
|
||||
local lines = word_wrap(contents, width)
|
||||
local print_height = #lines
|
||||
|
||||
-- If we fit within the screen, just display without pagination.
|
||||
if print_height <= height then
|
||||
print(contents)
|
||||
return
|
||||
end
|
||||
|
||||
local offset = 0
|
||||
|
||||
local function draw()
|
||||
local fg, bg = ("0"):rep(width), ("f"):rep(width)
|
||||
for y = 1, height - 1 do
|
||||
term.setCursorPos(1, y)
|
||||
if y + offset > print_height then
|
||||
-- Should only happen if we resize the terminal to a larger one
|
||||
-- than actually needed for the current text.
|
||||
term.clearLine()
|
||||
else
|
||||
term.blit(lines[y + offset], fg, bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function draw_menu()
|
||||
term.setTextColor(colors.yellow)
|
||||
term.setCursorPos(1, height)
|
||||
term.clearLine()
|
||||
|
||||
local tag = "Help: " .. sTopic
|
||||
term.write("Help: " .. sTopic)
|
||||
|
||||
if width >= #tag + 16 then
|
||||
term.setCursorPos(width - 14, height)
|
||||
term.write("Press Q to exit")
|
||||
end
|
||||
end
|
||||
|
||||
draw()
|
||||
draw_menu()
|
||||
|
||||
while true do
|
||||
local event, param = os.pullEvent()
|
||||
if event == "key" then
|
||||
if param == keys.up and offset > 0 then
|
||||
offset = offset - 1
|
||||
draw()
|
||||
elseif param == keys.down and offset < print_height - height then
|
||||
offset = offset + 1
|
||||
draw()
|
||||
elseif param == keys.pageUp and offset > 0 then
|
||||
offset = math.max(offset - height + 2, 0)
|
||||
draw()
|
||||
elseif param == keys.pageDown and offset < print_height - height then
|
||||
offset = math.min(offset + height - 2, print_height - height)
|
||||
draw()
|
||||
elseif param == keys.home then
|
||||
offset = 0
|
||||
draw()
|
||||
elseif param == keys["end"] then
|
||||
offset = print_height - height
|
||||
draw()
|
||||
elseif param == keys.q then
|
||||
sleep(0) -- Super janky, but consumes stray "char" events.
|
||||
break
|
||||
end
|
||||
elseif event == "mouse_scroll" then
|
||||
if param < 0 and offset > 0 then
|
||||
offset = offset - 1
|
||||
draw()
|
||||
elseif param > 0 and offset < print_height - height then
|
||||
offset = offset + 1
|
||||
draw()
|
||||
end
|
||||
elseif event == "term_resize" then
|
||||
local new_width, new_height = term.getSize()
|
||||
|
||||
if new_width ~= width then
|
||||
lines = word_wrap(contents, new_width)
|
||||
print_height = #lines
|
||||
end
|
||||
|
||||
width, height = new_width, new_height
|
||||
offset = math.max(math.min(offset, print_height - height), 0)
|
||||
draw()
|
||||
draw_menu()
|
||||
end
|
||||
end
|
||||
|
||||
term.setCursorPos(1, 1)
|
||||
term.clear()
|
||||
|
@@ -1,8 +1,9 @@
|
||||
local function printUsage()
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usages:")
|
||||
print("pastebin put <filename>")
|
||||
print("pastebin get <code> <filename>")
|
||||
print("pastebin run <code> <arguments>")
|
||||
print(programName .. " put <filename>")
|
||||
print(programName .. " get <code> <filename>")
|
||||
print(programName .. " run <code> <arguments>")
|
||||
end
|
||||
|
||||
local tArgs = { ... }
|
||||
|
@@ -1,7 +1,8 @@
|
||||
local function printUsage()
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usage:")
|
||||
print("wget <url> [filename]")
|
||||
print("wget run <url>")
|
||||
print(programName .. " <url> [filename]")
|
||||
print(programName .. " run <url>")
|
||||
end
|
||||
|
||||
local tArgs = { ... }
|
||||
|
@@ -1,13 +1,15 @@
|
||||
local function printUsage()
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usages:")
|
||||
print("label get")
|
||||
print("label get <drive>")
|
||||
print("label set <text>")
|
||||
print("label set <drive> <text>")
|
||||
print("label clear")
|
||||
print("label clear <drive>")
|
||||
print(programName .. " get")
|
||||
print(programName .. " get <drive>")
|
||||
print(programName .. " set <text>")
|
||||
print(programName .. " set <drive> <text>")
|
||||
print(programName .. " clear")
|
||||
print(programName .. " clear <drive>")
|
||||
end
|
||||
|
||||
|
||||
local function checkDrive(sDrive)
|
||||
if peripheral.getType(sDrive) == "drive" then
|
||||
-- Check the disk exists
|
||||
|
@@ -1,7 +1,8 @@
|
||||
local tArgs = { ... }
|
||||
|
||||
if #tArgs < 1 then
|
||||
print("Usage: mkdir <paths>")
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usage: " .. programName .. " <paths>")
|
||||
return
|
||||
end
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user