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

Compare commits

...

62 Commits

Author SHA1 Message Date
Merith-TK
d8b0037cfa CC:R 1.94.1-beta
Update version info, re-brand from CC:Tweaked For Fabric, to CC:Restitched
2021-03-28 22:49:15 -07:00
Merith-TK
91a9e7fdf7 Update .gitignore 2021-03-27 16:40:59 -07:00
Merith-TK
2c3573719b [Patchwork] Fix Network Config
Network config should now work in a stable mannor, the previous person did not port over the imports and thats why this broke in the first place, possibly required import did not exist for fabric at the time?
2021-03-27 15:19:09 -07:00
Devan-Kerman
46bc42d5a7 Update README.md 2021-03-04 19:23:36 -06:00
Devan-Kerman
1b9e93a80f Merge pull request #31 from davidqueneau/fabric
Revert "Cable modems can be placed against all blocks, including ches…
2021-03-04 19:23:07 -06:00
Devan-Kerman
7b93f91ffa Merge pull request #36 from Merith-TK/fabric
Update to Match CC:T version
2021-03-04 19:22:59 -06:00
Merith-TK
31a1027401 Clean up some examples a little bit
Would be good if they didn't crash and burn on entry :).
2021-03-02 23:12:53 -08:00
Merith-TK
e0fc994819 [TODO] 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
2021-03-02 22:47:44 -08:00
Merith-TK
43408bf085 CC:R 1.94.0 2021-03-02 19:21:12 -08:00
Merith-TK
d28f42e8b7 use arg[0] in all usage printouts (#571) 2021-03-02 18:22:26 -08:00
Merith-TK
ac452582c1 Use blit to draw boxes, add colors.toBlit (#570) 2021-02-22 18:49:39 -08:00
Merith-TK
7e65c6b25c Fix JSON objects failing to pass 2021-02-22 18:11:27 -08:00
Merith-TK
457a863842 Dont fail when codecov is being finicky 2021-02-22 18:09:31 -08:00
Merith-TK
eef36e1358 Various SNBT parsing improvements 2021-02-22 17:45:44 -08:00
Merith-TK
feda08b915 Draw in-hand pocket computers with blending 2021-02-22 17:43:54 -08:00
Merith-TK
0240ce50ce Bump cct-javadoc version 2021-02-22 17:34:20 -08:00
Merith-TK
592b83e784 [TODO] Fix players not getting advancements when they own turtles 2021-02-22 17:32:26 -08:00
Merith-TK
5d91491ec7 Remove superfluous imports 2021-02-22 12:25:13 -08:00
Merith-TK
7326d1110d Make generic peripherals on by default 2021-02-22 12:17:03 -08:00
Merith-TK
0aa6ac05a0 Add function to get window visibility 2021-02-22 11:56:22 -08:00
Merith-TK
27a2c063b9 Update configuration to match latest illuaminate 2021-02-22 11:50:33 -08:00
Merith-TK
89a195ec06 CC:R 1.93.1 2021-02-22 11:05:23 -08:00
Merith-TK
0e5fd4e8e0 Fix TBO norm issues on old GPUs 2021-02-22 10:56:52 -08:00
Merith-TK
aa4ec53bb6 Bump JEI/crafttweaker versions 2021-02-22 10:50:57 -08:00
Merith-TK
6b49327462 Document remaining OS functions (#554) 2021-02-22 10:49:52 -08:00
Merith-TK
74ad934889 Add color table to docs (#553) 2021-02-22 10:43:27 -08:00
Merith-TK
08b3dbbad5 Fix My Docs, Update Patchwork Format 2021-02-22 10:38:52 -08:00
Merith-TK
54eac0a2f8 CC:R 1.93 2021-02-22 10:27:15 -08:00
Merith-TK
51ca5e620c Don't propagate adjacent redstone signals for computers (#549)
[PatchWork] Also updated patchwork format information
2021-02-22 10:22:10 -08:00
Merith-TK
076d572831 Fix io.open documentation 2021-02-22 03:25:08 -08:00
Merith-TK
75f2b91fef Translations for Swedish 2021-02-22 03:23:58 -08:00
Merith-TK
79534e5630 Fix incorrect open container check 2021-02-22 03:22:39 -08:00
Merith-TK
d483a91459 Use tags to check if something is a dye 2021-02-22 03:08:41 -08:00
Merith-TK
71d764f122 HTTP rules now allow filtering by port 2021-02-22 02:56:04 -08:00
Merith-TK
2e527eb11e Fix additional - in docs 2021-02-22 01:45:26 -08:00
Merith-TK
19273b3696 CC:R 1.92.0 2021-02-22 01:42:34 -08:00
Merith-TK
4643641d51 Update Patchwork, Handle Tabs when Parsing Json 2021-02-22 01:23:39 -08:00
Merith-TK
63cd9c5bc7 Update Patchwork.md with format 2021-02-22 01:20:51 -08:00
Merith-TK
e9c11ff325 Translations for Vienamese 2021-02-22 01:13:56 -08:00
Merith-TK
452464aa01 add changelog + vienamese support 2021-02-22 01:11:29 -08:00
Merith-TK
8885462175 Don't use entity.captureDrops at all (removed line) 2021-02-22 01:05:22 -08:00
Merith-TK
b8bd64913b Add date-specific MOTDs (like Minecraft) (CCT#533) 2021-02-22 01:02:56 -08:00
David Queneau
00b458c39a Revert "Cable modems can be placed against all blocks, including chests."
This reverts commit 664df62d
2021-02-07 10:47:50 -08:00
Devan-Kerman
88722d484f Merge pull request #29 from davidqueneau/fabric
Porting GenericPeripherals from upstream CC: Tweaked
2021-02-03 10:44:02 -06:00
David Queneau
6d103e2114 Item movement methods now respect inventory slot rules (i.e. no pickaxes in the fuel slot of a furnace). 2021-02-01 23:21:25 -08:00
David Queneau
42f23d56ae Double chest inventories are now handled correctly. 2021-02-01 19:04:02 -08:00
David Queneau
89d5211bd7 Fixed bad assumption about empty ItemStacks being reset to ItemStack.EMPTY. Items no longer transfer into an inventory as a different item than they began. 2021-02-01 00:02:28 -08:00
David Queneau
83e70377f7 Fixed off by one error. Commented not very nice inventory code. 2021-01-31 20:13:59 -08:00
David Queneau
f6a26f75c3 Reverted change to how GenericPeripherals report type. Instead, added generic lua function that returns the name of Nameable targets. Might not be at home in InventoryMethods since it could apply to other types of targets. 2021-01-30 20:45:08 -08:00
David Queneau
664df62d5d Cable modems can be placed against all blocks, including chests. 2021-01-30 17:12:09 -08:00
David Queneau
1348ee0588 Ported the generic peripheral feature from upstream forge version along with initial implementation of generic inventory peripherals. 2021-01-30 15:28:11 -08:00
Jacob Farley
145dce7653 Merge pull request #27 from techninja1008/fix-http-config
Fix http config
2021-01-21 20:09:23 -06:00
Jacob Farley
7f2651c23e Merge pull request #28 from techninja1008/fix-turtle-breaking-computer
Change turtle block breaking to call onBreak
2021-01-20 15:06:17 -06:00
Danny Wensley
05464107a8 Update ComputerCraft.httpRules on config sync.
Also removes a bit of http config related legacy code.
2021-01-20 20:03:00 +00:00
Danny Wensley
86705787f0 Change turtle block breaking to call onBreak
Fixes #25
2021-01-20 14:30:11 +00:00
Devan-Kerman
b34d8387d9 Update README.md 2020-12-29 15:17:00 -06:00
Devan-Kerman
4d00969ef0 fix #10
Signed-off-by: Devan-Kerman <devan@cleverpath.com>
2020-09-15 11:52:23 -05:00
Devan-Kerman
01d3d12992 update commits
Signed-off-by: Devan-Kerman <devan@cleverpath.com>
2020-09-15 11:38:16 -05:00
Devan-Kerman
5e31dcde83 fix CCE
Signed-off-by: Devan-Kerman <devan@cleverpath.com>
2020-09-12 20:20:38 -05:00
Devan-Kerman
5184883af1 fix potential bug
Signed-off-by: Devan-Kerman <devan@cleverpath.com>
2020-09-09 10:07:38 -05:00
Devan-Kerman
0c45112262 fix npe 2020-09-09 09:36:47 -05:00
Jacob Farley
0bf1672f45 Update README.md 2020-09-08 12:31:17 -05:00
105 changed files with 2710 additions and 569 deletions

50
.github/workflows/main-ci.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
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: Generate Java documentation stubs
run: ./gradlew luaJavadoc --no-daemon
- 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
View File

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

@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "automatic"
}

View File

@@ -1,7 +1,5 @@
# 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-tweaked-fabric)
A fork of [CC: Tweaked](https://github.com/SquidDev-CC/CC-Tweaked) for use with the latest Fabric.
NOTE: This project is currently in alpha stage. It may not be well-tested and stable, so use it at your own peril!
PRs welcome

View File

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

View File

@@ -2,7 +2,7 @@
org.gradle.jvmargs=-Xmx1G
# Mod properties
mod_version=1.91.1
mod_version=1.94.1-beta
# Minecraft properties
mc_version=1.16.2

313
patchwork.md Normal file
View File

@@ -0,0 +1,313 @@
# 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 :).
```

View File

@@ -9,4 +9,4 @@ pluginManagement {
}
}
rootProject.name = "cc-tweaked-fabric-${mc_version}"
rootProject.name = "cc-restiched"

View File

@@ -10,7 +10,6 @@ import static dan200.computercraft.shared.ComputerCraftRegistry.ModBlocks;
import static dan200.computercraft.shared.ComputerCraftRegistry.init;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
@@ -19,10 +18,10 @@ 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;
@@ -50,6 +49,7 @@ 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;
@@ -72,6 +72,7 @@ public final class ComputerCraft implements ModInitializer {
"172.16.0.0/12",
"192.168.0.0/16",
"fd00::/8",
"0.0.0.0/8"
};
public static final int terminalWidth_computer = 51;
public static final int terminalHeight_computer = 19;
@@ -85,13 +86,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 +100,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 +122,18 @@ public final class ComputerCraft implements ModInitializer {
public static int monitorHeight = 6;
public static double monitorDistanceSq = 4096;
public static List<AddressRule> httpRules = buildHttpRulesFromConfig(DEFAULT_HTTP_BLACKLIST, DEFAULT_HTTP_WHITELIST);
public static List<AddressRule> buildHttpRulesFromConfig(String[] blacklist, String[] whitelist) {
return Stream.concat(Stream.of(blacklist)
.map( x -> AddressRule.parse( x, null, Action.DENY.toPartial()))
.filter(Objects::nonNull),
Stream.of(whitelist)
.map( x -> AddressRule.parse( x, null, Action.ALLOW.toPartial()))
.filter(Objects::nonNull))
.collect(Collectors.toList());
}
@Override
public void onInitialize() {
Config.load(Paths.get(FabricLoader.getInstance()
@@ -152,6 +156,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 ));
}
}

View File

@@ -135,7 +135,7 @@ public interface ITurtleAccess {
*
* @return This turtle's owner.
*/
@Nonnull
@Nullable
GameProfile getOwningPlayer();
/**

View File

@@ -68,7 +68,7 @@ public abstract class ItemMapLikeRenderer {
float tZ = -0.4f * MathHelper.sin(swingRt * (float) Math.PI);
transform.translate(0, -tX / 2, tZ);
HeldItemRendererAccess access = (HeldItemRendererAccess) render;
HeldItemRendererAccess access = (HeldItemRendererAccess) renderer;
float pitchAngle = access.callGetMapAngle(pitch);
transform.translate(0, 0.04F + equipProgress * -1.2f + pitchAngle * -0.5f, -0.72f);
transform.multiply(Vector3f.POSITIVE_X.getDegreesQuaternion(pitchAngle * -85.0f));

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ package dan200.computercraft.core.apis.http.options;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
@@ -26,15 +27,22 @@ public final class AddressRule {
public static final int WEBSOCKET_MESSAGE = 128 * 1024;
private final HostRange ip;
private final Pattern domainPattern;
private final Integer port;
private final PartialOptions partial;
private AddressRule(@Nullable HostRange ip, @Nullable Pattern domainPattern, @Nonnull PartialOptions partial) {
private AddressRule(
@Nullable HostRange ip,
@Nullable Pattern domainPattern,
@Nullable Integer port,
@Nonnull PartialOptions partial )
{
this.ip = ip;
this.domainPattern = domainPattern;
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);
@@ -73,14 +81,14 @@ public final class AddressRule {
size -= 8;
}
return new AddressRule(new HostRange(minBytes, maxBytes), null, partial);
return new AddressRule(new HostRange(minBytes, maxBytes), null, port, partial);
} else {
Pattern pattern = Pattern.compile("^\\Q" + filter.replaceAll("\\*", "\\\\E.*\\\\Q") + "\\E$");
return new AddressRule(null, pattern, partial);
return new AddressRule(null, 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 address) {
PartialOptions options = null;
boolean hasMany = false;
@@ -108,11 +116,14 @@ 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 socketAddress The address to check.
* @return Whether it matches any of these patterns.
*/
private boolean matches(String domain, InetAddress address) {
private boolean matches(String domain, InetSocketAddress socketAddress) {
InetAddress address = socketAddress.getAddress();
if( port != null && port != socketAddress.getPort() ) return false;
if (this.domainPattern != null) {
if (this.domainPattern.matcher(domain)
.matches()) {

View File

@@ -6,116 +6,129 @@
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.Config;
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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,384 @@
/*
* 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);
}
}

View File

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

View File

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

View File

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

View File

@@ -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;
@@ -341,7 +342,7 @@ public class TurtleBrain implements ITurtleAccess {
}
}
@Nonnull
@Nullable
@Override
public GameProfile getOwningPlayer() {
return this.m_owningPlayer;

View File

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

View File

@@ -228,9 +228,9 @@ public class TurtlePlaceCommand implements ITurtleCommand {
cancelResult = hitEntity.interactAt(turtlePlayer, hitPos, Hand.MAIN_HAND);
}
if (cancelResult.isAccepted()) {
if (cancelResult != null && cancelResult.isAccepted()) {
placed = true;
} else {
} else if (hitEntity instanceof LivingEntity) {
// See EntityPlayer.interactOn
cancelResult = stackCopy.useOnEntity(turtlePlayer, (LivingEntity) hitEntity, Hand.MAIN_HAND);
if (cancelResult != null && cancelResult.isAccepted()) {
@@ -238,7 +238,7 @@ public class TurtlePlaceCommand implements ITurtleCommand {
} else if (cancelResult == null) {
if (hitEntity.interact(turtlePlayer, Hand.MAIN_HAND) == ActionResult.CONSUME) {
placed = true;
} else if (hitEntity instanceof LivingEntity) {
} else {
placed = stackCopy.useOnEntity(turtlePlayer, (LivingEntity) hitEntity, Hand.MAIN_HAND)
.isAccepted();
if (placed) {
@@ -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());
}
}

View File

@@ -26,12 +26,15 @@ import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityDimensions;
import net.minecraft.entity.EntityPose;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.passive.HorseBaseEntity;
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;
import net.minecraft.util.math.Direction;
@@ -40,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();
@@ -69,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);
}

View File

@@ -210,7 +210,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);

View File

@@ -13,7 +13,6 @@ 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.websocket.Websocket;
public class Config {
@@ -84,8 +83,7 @@ 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 = ComputerCraft.buildHttpRulesFromConfig(config.http.blacklist, config.http.whitelist);
ComputerCraft.httpTimeout = Math.max(0, config.http.timeout);
ComputerCraft.httpMaxRequests = Math.max(1, config.http.max_requests);

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -3,10 +3,6 @@
"package": "dan200.computercraft.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"AffineTransformationAccess",
"BakedQuadAccess",
"ChatHudAccess",
"HeldItemRendererAccess",
"MinecraftServerAccess",
"MixinBlock",
"MixinEntity",
@@ -17,6 +13,10 @@
"WorldSavePathAccess"
],
"client": [
"AffineTransformationAccess",
"BakedQuadAccess",
"ChatHudAccess",
"HeldItemRendererAccess",
"MixinHeldItemRenderer",
"MixinItemFrameEntityRenderer",
"MixinMinecraftGame",

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -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.
@@ -71,9 +90,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
@@ -111,17 +127,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?
@@ -177,37 +183,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.
--
@@ -233,29 +235,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

View File

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

View File

@@ -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
@@ -432,7 +432,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 +453,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 +472,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 +485,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 +501,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 +530,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 +562,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 +706,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 +719,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 +744,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")

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
-- 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 cc.completion For more general helpers, suitable for use with @{_G.read}.
-- @see shell.setCompletionFunction
local expect = require "cc.expect".expect

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 = { ... }

View File

@@ -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 = { ... }

View File

@@ -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 = { ... }

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
local function printUsage()
print("Usage: monitor <name> <program> <arguments>")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <name> <program> <arguments>")
return
end

View File

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

View File

@@ -1,6 +1,7 @@
local tArgs = { ... }
if #tArgs < 2 then
print("Usage: mv <source> <destination>")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <source> <destination>")
return
end

View File

@@ -1,9 +1,10 @@
local tArgs = { ... }
local function printUsage()
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usages:")
print("chat host <hostname>")
print("chat join <hostname> <nickname>")
print(programName .. " host <hostname>")
print(programName .. " join <hostname> <nickname>")
end
local sOpenedModem = nil

View File

@@ -1,11 +1,12 @@
local tArgs = { ... }
local function printUsage()
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usages:")
print("redstone probe")
print("redstone set <side> <value>")
print("redstone set <side> <color> <value>")
print("redstone pulse <side> <count> <period>")
print(programName .. " probe")
print(programName .. " set <side> <value>")
print(programName .. " set <side> <color> <value>")
print(programName .. " pulse <side> <count> <period>")
end
local sCommand = tArgs[1]

View File

@@ -1,6 +1,7 @@
local tArgs = { ... }
if #tArgs < 2 then
print("Usage: rename <source> <destination>")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <source> <destination>")
return
end

View File

@@ -383,7 +383,7 @@ end
--
-- @tparam string sLine The input to complete.
-- @treturn { string }|nil The list of possible completions.
-- @see read For more information about completion.
-- @see _G.read For more information about completion.
-- @see shell.completeProgram
-- @see shell.setCompletionFunction
-- @see shell.getCompletionInfo
@@ -461,7 +461,7 @@ end
-- The completion function.
-- @see cc.shell.completion Various utilities to help with writing completion functions.
-- @see shell.complete
-- @see read For more information about completion.
-- @see _G.read For more information about completion.
function shell.setCompletionFunction(program, complete)
expect(1, program, "string")
expect(2, complete, "function")

View File

@@ -1,5 +1,6 @@
if not turtle then
printError("Requires a Turtle")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " [number]")
return
end

View File

@@ -5,7 +5,8 @@ end
local tArgs = { ... }
local function printUsage()
print("Usage: equip <slot> <side>")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <slot> <side>")
end
if #tArgs ~= 2 then

View File

@@ -5,7 +5,8 @@ end
local tArgs = { ... }
if #tArgs ~= 1 then
print("Usage: excavate <diameter>")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <diameter>")
return
end

View File

@@ -5,7 +5,8 @@ end
local tArgs = { ... }
if #tArgs < 1 then
print("Usage: go <direction> <distance>")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <direction> <distance>")
return
end

View File

@@ -6,7 +6,8 @@ end
local tArgs = { ... }
local nLimit = 1
if #tArgs > 1 then
print("Usage: refuel [number]")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " [number]")
return
elseif #tArgs > 0 then
if tArgs[1] == "all" then

View File

@@ -5,7 +5,8 @@ end
local tArgs = { ... }
if #tArgs ~= 1 then
print("Usage: tunnel <length>")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <length>")
return
end

View File

@@ -5,7 +5,8 @@ end
local tArgs = { ... }
if #tArgs < 1 then
print("Usage: turn <direction> <turns>")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <direction> <turns>")
return
end

View File

@@ -5,7 +5,8 @@ end
local tArgs = { ... }
local function printUsage()
print("Usage: unequip <side>")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <side>")
end
if #tArgs ~= 1 then

View File

@@ -1,6 +1,7 @@
local tArgs = { ... }
if #tArgs < 1 then
print("Usage: type <path>")
local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <path>")
return
end

View File

@@ -1,21 +1,21 @@
{
"schemaVersion": 1,
"id": "computercraft",
"name": "CC:T for Fabric",
"name": "CC:Restitched",
"version": "${version}",
"description": "A fork of CC: Tweaked for use with the latest Fabric.",
"description": "CC: Tweaked for Fabric.",
"license": "ComputerCraft Public License",
"icon": "assets/computercraft/pack.png",
"contact": {
"homepage": "https://github.com/mystiacraft/cc-tweaked-fabric",
"issues": "https://github.com/mystiacraft/cc-tweaked-fabric/issues"
"homepage": "https://github.com/Merith-TK/cc-restiched",
"issues": "https://github.com/Merith-TK/cc-restiched/issues"
},
"authors": [
"Daniel Ratcliffe",
"Aaron Mills",
"SquidDev",
"parly"
"parly",
"Merith.TK"
],
"depends": {
"fabricloader": ">=0.4.0",
@@ -26,8 +26,12 @@
},
"environment": "*",
"entrypoints": {
"main": [ "dan200.computercraft.ComputerCraft" ],
"client": ["dan200.computercraft.client.proxy.ComputerCraftProxyClient"]
"main": [
"dan200.computercraft.ComputerCraft"
],
"client": [
"dan200.computercraft.client.proxy.ComputerCraftProxyClient"
]
},
"mixins": [
"computercraft.mixins.json"

View File

@@ -0,0 +1,34 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.apis.http.options;
import org.junit.jupiter.api.Test;
import java.net.InetSocketAddress;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class AddressRuleTest
{
@Test
public void matchesPort()
{
Iterable<AddressRule> rules = Collections.singletonList( AddressRule.parse(
"127.0.0.1", 8080,
new PartialOptions( Action.ALLOW, null, null, null, null )
) );
assertEquals( apply( rules, "localhost", 8080 ).action, Action.ALLOW );
assertEquals( apply( rules, "localhost", 8081 ).action, Action.DENY );
}
private Options apply( Iterable<AddressRule> rules, String host, int port )
{
return AddressRule.apply( rules, host, new InetSocketAddress( host, port ) );
}
}

View File

@@ -73,4 +73,20 @@ describe("The colors library", function()
expect(colors.rgb8(0.3, 0.5, 0.6)):equals(0x4c7f99)
expect({ colors.rgb8(0x4c7f99) }):same { 0x4c / 0xFF, 0x7f / 0xFF, 0.6 }
end)
describe("colors.toBlit", function()
it("validates arguments", function()
expect.error(colors.toBlit, nil):eq("bad argument #1 (expected number, got nil)")
end)
it("converts all colors", function()
for i = 0, 15 do
expect(colors.toBlit(2 ^ i)):eq(string.format("%x", i))
end
end)
it("floors colors", function()
expect(colors.toBlit(16385)):eq("e")
end)
end)
end)

View File

@@ -1,4 +1,19 @@
local with_window = require "test_helpers".with_window
describe("The paintutils library", function()
-- Verifies that a window's lines are equal to the given table of blit
-- strings ({{"text", "fg", "bg"}, {"text", "fg", "bg"}...})
local function window_eq(w, state)
-- Verification of the size isn't really important in the tests, but
-- better safe than sorry.
local _, height = w.getSize()
expect(#state):eq(height)
for line = 1, height do
expect({ w.getLine(line) }):same(state[line])
end
end
describe("paintutils.parseImage", function()
it("validates arguments", function()
paintutils.parseImage("")
@@ -28,6 +43,30 @@ describe("The paintutils library", function()
expect.error(paintutils.drawLine, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
expect.error(paintutils.drawLine, 1, 1, 1, 1, false):eq("bad argument #5 (expected number, got boolean)")
end)
it("draws a line going across with custom colour", function()
local w = with_window(3, 2, function()
paintutils.drawLine(1, 1, 3, 1, colours.red)
end)
window_eq(w, {
{ " ", "000", "eee" },
{ " ", "000", "fff" },
})
end)
it("draws a line going diagonally with term colour", function()
local w = with_window(3, 3, function()
term.setBackgroundColour(colours.red)
paintutils.drawLine(1, 1, 3, 3)
end)
window_eq(w, {
{ " ", "000", "eff" },
{ " ", "000", "fef" },
{ " ", "000", "ffe" },
})
end)
end)
describe("paintutils.drawBox", function()
@@ -38,6 +77,45 @@ describe("The paintutils library", function()
expect.error(paintutils.drawBox, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
expect.error(paintutils.drawBox, 1, 1, 1, 1, false):eq("bad argument #5 (expected number, got boolean)")
end)
it("draws a box with term colour", function()
local w = with_window(3, 3, function()
term.setBackgroundColour(colours.red)
paintutils.drawBox(1, 1, 3, 3)
end)
window_eq(w, {
{ " ", "eee", "eee" },
{ " ", "e0e", "efe" },
{ " ", "eee", "eee" },
})
end)
it("draws a box with custom colour", function()
local w = with_window(3, 3, function()
paintutils.drawBox(1, 1, 3, 3, colours.red)
end)
window_eq(w, {
{ " ", "eee", "eee" },
{ " ", "e0e", "efe" },
{ " ", "eee", "eee" },
})
end)
it("draws a box without overwriting existing content", function()
local w = with_window(3, 3, function()
term.setCursorPos(2, 2)
term.write("a")
paintutils.drawBox(1, 1, 3, 3, colours.red)
end)
window_eq(w, {
{ " ", "eee", "eee" },
{ " a ", "e0e", "efe" },
{ " ", "eee", "eee" },
})
end)
end)
describe("paintutils.drawFilledBox", function()
@@ -48,6 +126,31 @@ describe("The paintutils library", function()
expect.error(paintutils.drawFilledBox, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
expect.error(paintutils.drawFilledBox, 1, 1, 1, 1, false):eq("bad argument #5 (expected number, got boolean)")
end)
it("draws a filled box with term colour", function()
local w = with_window(3, 3, function()
term.setBackgroundColour(colours.red)
paintutils.drawFilledBox(1, 1, 3, 3)
end)
window_eq(w, {
{ " ", "eee", "eee" },
{ " ", "eee", "eee" },
{ " ", "eee", "eee" },
})
end)
it("draws a filled box with custom colour", function()
local w = with_window(3, 3, function()
paintutils.drawFilledBox(1, 1, 3, 3, colours.red)
end)
window_eq(w, {
{ " ", "eee", "eee" },
{ " ", "eee", "eee" },
{ " ", "eee", "eee" },
})
end)
end)
describe("paintutils.drawImage", function()

View File

@@ -118,6 +118,56 @@ describe("The window library", function()
expect.error(w.reposition, 1, 1, false, 1):eq("bad argument #3 (expected number, got boolean)")
expect.error(w.reposition, 1, 1, nil, 1):eq("bad argument #3 (expected number, got nil)")
expect.error(w.reposition, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
expect.error(w.reposition, 1, 1, 1, 1, true):eq("bad argument #5 (expected table, got boolean)")
end)
it("can change the buffer", function()
local a, b = mk(), mk()
local target = window.create(a, 1, 1, a.getSize())
target.write("Test")
expect((a.getLine(1))):equal("Test ")
expect({ a.getCursorPos() }):same { 5, 1 }
target.reposition(1, 1, nil, nil, b)
target.redraw()
expect((a.getLine(1))):equal("Test ")
expect({ a.getCursorPos() }):same { 5, 1 }
target.setCursorPos(1, 1) target.write("More")
expect((a.getLine(1))):equal("Test ")
expect((b.getLine(1))):equal("More ")
end)
end)
describe("Window.getLine", function()
it("validates arguments", function()
local w = mk()
w.getLine(1)
local _, y = w.getSize()
expect.error(w.getLine, nil):eq("bad argument #1 (expected number, got nil)")
expect.error(w.getLine, 0):eq("Line is out of range.")
expect.error(w.getLine, y + 1):eq("Line is out of range.")
end)
it("provides a line's contents", function()
local w = mk()
w.blit("test", "aaaa", "4444")
expect({ w.getLine(1) }):same { "test ", "aaaa0", "4444f" }
end)
end)
describe("Window.setVisible", function()
it("validates arguments", function()
local w = mk()
expect.error(w.setVisible, nil):eq("bad argument #1 (expected boolean, got nil)")
end)
end)
describe("Window.isVisible", function()
it("gets window visibility", function()
local w = mk()
w.setVisible(false)
expect(w.isVisible()):same(false)
end)
end)
end)

View File

@@ -0,0 +1,33 @@
local capture = require "test_helpers".capture_program
describe("The exec program", function()
it("displays an error without the commands api", function()
stub(_G, "commands", nil)
expect(capture(stub, "/rom/programs/command/exec.lua"))
:matches { ok = true, output = "", error = "Requires a Command Computer.\n" }
end)
it("displays its usage when given no argument", function()
stub(_G, "commands", {})
expect(capture(stub, "/rom/programs/command/exec.lua"))
:matches { ok = true, output = "", error = "Usage: /rom/programs/command/exec.lua <command>\n" }
end)
it("runs a command", function()
stub(_G, "commands", {
exec = function() return true, { "Hello World!" } end,
})
expect(capture(stub, "/rom/programs/command/exec.lua computercraft"))
:matches { ok = true, output = "Success\nHello World!\n", error = "" }
end)
it("reports command failures", function()
stub(_G, "commands", {
exec = function() return false, { "Hello World!" } end,
})
expect(capture(stub, "/rom/programs/command/exec.lua computercraft"))
:matches { ok = true, output = "Hello World!\n", error = "Failed\n" }
end)
end)

View File

@@ -0,0 +1,40 @@
local capture = require "test_helpers".capture_program
describe("The copy program", function()
local function touch(file)
io.open(file, "w"):close()
end
it("copies a file", function()
touch("/test-files/copy/a.txt")
shell.run("copy /test-files/copy/a.txt /test-files/copy/b.txt")
expect(fs.exists("/test-files/copy/a.txt")):eq(true)
expect(fs.exists("/test-files/copy/b.txt")):eq(true)
end)
it("fails when copying a non-existent file", function()
expect(capture(stub, "copy nothing destination"))
:matches { ok = true, output = "", error = "No matching files\n" }
end)
it("fails when overwriting an existing file", function()
touch("/test-files/copy/c.txt")
expect(capture(stub, "copy /test-files/copy/c.txt /test-files/copy/c.txt"))
:matches { ok = true, output = "", error = "Destination exists\n" }
end)
it("fails when copying into read-only locations", function()
touch("/test-files/copy/d.txt")
expect(capture(stub, "copy /test-files/copy/d.txt /rom/test.txt"))
:matches { ok = true, output = "", error = "Destination is read-only\n" }
end)
it("displays the usage when given no arguments", function()
expect(capture(stub, "copy"))
:matches { ok = true, output = "Usage: copy <source> <destination>\n", error = "" }
end)
end)

View File

@@ -0,0 +1,36 @@
local capture = require "test_helpers".capture_program
describe("The motd program", function()
local function setup_date(month, day)
stub(os, "date", function() return { month = month, day = day } end)
end
it("displays MOTD", function()
setup_date(0, 0)
local file = fs.open("/motd_check.txt", "w")
file.write("Hello World!")
file.close()
settings.set("motd.path", "/motd_check.txt")
expect(capture(stub, "motd"))
:matches { ok = true, output = "Hello World!\n", error = "" }
end)
it("displays date-specific MOTD (1/1)", function()
setup_date(1, 1)
expect(capture(stub, "motd"))
:matches { ok = true, output = "Happy new year!\n", error = "" }
end)
it("displays date-specific MOTD (10/31)", function()
setup_date(10, 31)
expect(capture(stub, "motd"))
:matches { ok = true, output = "OOoooOOOoooo! Spooky!\n", error = "" }
end)
it("displays date-specific MOTD (12/24)", function()
setup_date(12, 24)
expect(capture(stub, "motd"))
:matches { ok = true, output = "Merry X-mas!\n", error = "" }
end)
end)

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