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

Compare commits

...

50 Commits

Author SHA1 Message Date
Jonathan Coates
1650b72edb Prepare for 1.97.0 release 2021-06-28 22:52:01 +01:00
Jonathan Coates
88f41314c7 Make io handles opaque to the docs
Yep, this is kinda gross. But also a nice refactor
2021-06-28 22:47:56 +01:00
Jonathan Coates
0b65d56ab0 Prevent some files from being eol converted
Closes #813

Well, hopefully. This hasn't been tested, as I don't have easy access to
Windows.
2021-06-28 18:16:30 +01:00
Jonathan Coates
a256b70685 Allow setting the subprotocol header
Fixes #828, closes #829
2021-06-24 20:48:28 +01:00
Jonathan Coates
f16d1499fe Woops 2021-06-24 09:07:22 +00:00
Jonathan Coates
79ca851e4f Like I said, 'there may be a few of these' 2021-06-24 08:34:38 +00:00
Jonathan Coates
d5c54d64a6 Hopefully fix the bug report template
There may be a few of these commits :D:.
2021-06-24 08:32:57 +00:00
Jonathan Coates
5cfdd2339f Try the new issue template system 2021-06-24 08:32:18 +00:00
Jonathan Coates
46c9840d00 Merge pull request #842 from Lupus590-CC/fix-extra-,-in-expect
remove extra ,
2021-06-23 15:22:08 +01:00
Lupus590
b3f2f14e96 remove extra , 2021-06-23 15:14:01 +01:00
Jonathan Coates
3ace49d27f Remove config option for debug API
And also remove the "is present" guards within the various APIs. I'm
happy at this point that debug is safe, and think we can guarantee its
presence.
2021-06-22 21:01:05 +01:00
Jonathan Coates
c489d4bc4f Fix exiting paint typing "e" in the shell
When exiting paint via the keyboard by typing "Ctrl" then "E"
separately, we consume the "key" event within paint, leaving the shell
to consume "read".

To avoid this, we run a sleep(0) to gobble any other left-over events.

Note, it's generally not enough to run a queueEvent/pullEvent here, as
the char event may not have ended up on the queue yet. Alas, as this
solution is pretty ugly.
2021-06-19 15:13:26 +01:00
Jonathan Coates
2b029bd506 Refactor some computer screen logic
- Move some shared Gui{Computer,Turtle} code into a new class. Using
   entirely different naming conventions because of course (they are
   consistent with MojMap, just not the rest of CC:T).
 - Fix some mouse scaling issues in the terminal.
2021-06-19 14:18:12 +01:00
Jonathan Coates
2227845658 Remove NetworkMessage.fromBytes
Always build packets using a PacketBuffer constructor instead.
2021-06-19 13:01:43 +01:00
Jonathan Coates
a735f23e1f Fix a couple of bugs with wget
- Provide a friendly message when writing to a file fails.
 - Correctly write empty files

Fixes #832, closes #833
2021-06-19 11:59:15 +01:00
Jonathan Coates
de6f27ceaf Rewrite speaker networking code
Speakers now play sounds using a custom set of packets.
 - When playing a sound, we send the resource id, position, volume,
   pitch and a UUID for the _speaker_ to all nearby clients.
 - This UUID is then used when we need to update the sound. When the
   speaker is moved or destroyed, we send a new packet to clients and
   update accordingly.

This does have one side effect, that speakers can now only play one
sound at a time. I think this is accceptable - otherwise it's possible
to spam ward in a loop.

Notes still use the old networking code, and so will not be affected.

Closes #823
2021-06-18 22:23:04 +01:00
Jonathan Coates
2fab1a3054 Minor code style fixups
- Add missing @Override annotations. I can't find a way to enforce this
   with checkstyle - maybe I need spotbugs too D:.
 - Remove superflous "this"es.
2021-06-12 22:18:35 +01:00
Jonathan Coates
d4745ae47e Don't override isMouseOver
Minecraft's Widget class implements this for us!
2021-06-12 22:02:03 +01:00
Jonathan Coates
dc21e2dbc9 Minor dockerfile tweaks
- Install npm
 - Use apt-get instead of apt
2021-06-12 21:38:01 +01:00
Merith
75dfa71275 Gitpod Support (#817) 2021-06-12 21:33:08 +01:00
Jonathan Coates
d71bf225cc Add very simple markdown support to the help viewer (#819)
- Allow help files to use the ".md" suffix, and move changelog/whatsnew
   to use them.

 - When files end with ".md", the "help" program attempts to highlight
   them. This involves:
   - Colour code blocks with a lightGrey background.
   - Replace lists to use bullet points instead of "-"/"*".
   - Colours headings yellow.
   The implementation of this is a bit janky because a) I wrote this and
   b) we need to run this step before text wrapping, but preserve
   colours and section positions over wrapping (thanks to Jack for
   getting this working).

 - Add section navigation to the help viewer, with left/right to move to
   the next/previous section.

Closes #569
2021-06-12 19:48:41 +01:00
Jonathan Coates
8644c4ebf6 More gradle tweaks
- Upgade to Gradle 7.0 and FG 5.0
 - Allow running with any Java version - this now correctly compiles
   Forge with the right version.
 - Upload to Modrinth too. This is entirely untested, so may need some
   tweaking.
2021-06-09 18:01:13 +01:00
Wojbie
b323db30ee Add cc.shell.completion programWithArgs completion. (#815)
And expand monitor, shell, fg and bg to use it
2021-06-07 22:02:45 +01:00
JackMacWindows
53efd6b303 Fixed error handling in scale subcommand (#816) 2021-06-07 20:47:15 +01:00
Jonathan Coates
97faa1b3bc Fix several sidebar textures
There was an off by one here!
2021-06-07 18:34:57 +01:00
Jonathan Coates
7404133d40 Fix alternative localhost domain
lvh.me is dead. Let's kick the can down the road!
2021-06-07 18:22:49 +01:00
Jonathan Coates
e18e24407e Add additional buttons to the computer GUI (#809)
Adds a sidebar to the computer and turtle GUI. This currently provides
 - A power indicator, which turns on/shuts down a computer.
 - Button to queue a "terminate" event
2021-06-06 19:32:52 +01:00
Jonathan Coates
026afa7f73 Put some limits on various external queues
Ideally turtle functions would error, but wrangling that is more pain
than it's worth.
2021-06-06 19:26:20 +01:00
Jonathan Coates
29cc5bb86b Shut down computers on errors
Previously we would attempt to resume them, which then caused confusing
behaviour if the Lua VM was in an inconsistent state.

Closes #811
2021-06-06 18:33:52 +01:00
Jonathan Coates
aa9d3c8269 Refactor LuaContext into a separate class
This isn't tied to Cobalt any more!
2021-06-06 18:33:52 +01:00
Matthew Wilbern
f8074636bc Allow craft program to craft unlimited items (#807) 2021-06-06 06:52:01 +01:00
Jonathan Coates
0f6db63020 Remove "*Proxy" classes
- Move registry code into the various *Registry classes.

   I'm not sure this is any more sensible, but things being registered
   in different places kinda irked me.

 - Everything else (i.e. event listeners) goes in a {Client,Common}Hooks
   class right now. It's not ideal, but I don't think we can split it up
   much.
2021-06-05 10:09:28 +01:00
Jonathan Coates
51fcd83b87 Clean up turtle model loading code
Looks like Forge has had hooks for this for years, I've just never
noticed.
2021-06-05 09:14:18 +01:00
Jonathan Coates
c2190e1318 Improve error message for SSL failures
Fixes #803
2021-06-01 22:10:11 +01:00
Ronan Hanley
c40a13558c Fixed side input consistency issue when a turtle upgrade is removed (#743) 2021-06-01 19:12:35 +01:00
Jonathan Coates
02695aea51 Reduce some block updates
- Fix double updateOutput() call in TileComputerBase - I guess a
   merge/rebase gone wrong in the past.
 - Don't call updateBlock() when creating a server computer. This used
   to be needed when we sent the computer to the client, but this is no
   longer the case.
 - Don't call updateBlock() on TileMonitors when updating from the
   client. We don't need to do a redraw here, as this is all stored in
   the block state now.
 - Don't update the block when reading turtle upgrades. See #643 for
   some background here.

See #658
2021-06-01 18:55:12 +01:00
Jonathan Coates
d5be1aca0e Always accept command output for command computers
Fixes #799
2021-05-31 19:36:43 +01:00
Jonathan Coates
8ff8b78ed8 Better error handling in treasure disks
- Return a more sensible string for empty treasure disks (i.e. those
   given by /give). This should help identify packs which are giving
   items in non-supported ways.
 - Fix NPE when the treasure mount doesn't exist.

Fixes #801
2021-05-31 19:24:38 +01:00
Matthew Wilbern
7fc55aa9a0 Add __eq metamethod and equals method to the vector api. (#800) 2021-05-31 13:58:46 +01:00
magiczocker10
38335ca187 Add some options for textutils.serialize (#664) 2021-05-29 17:46:58 +01:00
JackMacWindows
e0e194099c Add a scale subcommand to monitor.lua (#623) 2021-05-29 16:55:55 +01:00
Ronan Hanley
8063059764 Unit tests for Terminal (#740) 2021-05-29 16:24:42 +01:00
Jonathan Coates
f96d923b2a Allow cleaning dyed turtles in a cauldron
Fixes #771. This does not allow clearing pocket computers or disks right
now - neither normally responds very well to water!
2021-05-29 15:18:53 +01:00
Jonathan Coates
9142ccfc93 Rewrite turtle placing logic
- Simplify how the turtle's inventory is processed. We now copy all
   items into the player inventory, attempt to place, and then copy the
   items back.

   This also fixes the problem where turtle.place() wouldn't (always)
   update the item which was placed.

   I'm also hoping this is more "correct" in how we process drops from
   entities and whatnot. Though I've not had any reports of issues, so
   it's probably fine.

 - Replace the "error message" string array with an actual object. It'd
   be nicer all these functions returned a TurtleCommandResult, but
   merging error messages from that is a little harder.

Fun facts: the test suite was actually helpful here, and caught the fact
that hoeing was broken!
2021-05-29 15:18:26 +01:00
Jonathan Coates
9f7cc00fcb Add button to view computer's folder
Implementation is a little awkward, as we can't send OPEN_FILE links
from the server, so we ensure the client runs a
/computercraft open-computer ID command instead. We then intercept this
on the client side and use that to open the folder.
2021-05-28 22:19:04 +01:00
Jonathan Coates
b129ae627b Make cc.pretty internals more opaque
I wish I had an actual type system.
2021-05-28 21:28:56 +01:00
Jonathan Coates
f9fb0619fa Swap CSS over to use illuaminate's variables
Closes #797
2021-05-28 21:18:38 +01:00
Weblate
58ea7a275e Translations for French
Translations for French

Translations for German

Co-authored-by: Anavrins <xanavrins@gmail.com>
Co-authored-by: Jummit <jummit@web.de>
Co-authored-by: Naheulf <newheulf@gmail.com>
2021-05-24 01:01:57 +00:00
Jonathan Coates
8487a13764 Remove some funky buildscript code
- Strip out proguard
 - Drop the json compression code

I /think/ this may have had a bigger impact at the time, but Cobalt is
slimmer than it was.
2021-05-21 22:11:25 +01:00
Jonathan Coates
5d0daf9b2d Remove dependencies from gradle 2021-05-20 19:03:30 +01:00
148 changed files with 3585 additions and 1823 deletions

12
.gitattributes vendored
View File

@@ -1,3 +1,15 @@
# Ignore changes in generated files
src/generated/resources/data/** linguist-generated
src/test/server-files/structures linguist-generated
* text=auto
*.gradle eol=lf diff=java
*.java eol=lf diff=java
*.kt eol=lf diff=java
*.lua eol=lf
*.md eol=lf diff=markdown
*.txt eol=lf
*.png binary
*.jar binary

View File

@@ -1,16 +0,0 @@
---
name: Bug report
about: Report some misbehaviour in the mod
labels: bug
---
<!--
## Before reporting
- Search for the bug on the issue tracker. Make sure to look at closed issues too!
-->
## Useful information to include:
- Minecraft version
- CC: Tweaked version
- Logs: These will be located in the `logs/` directory of your Minecraft instance. Please upload them as a gist or directly into this editor.
- Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed.

33
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Bug report
description: Report some misbehaviour in the mod
labels: [ bug ]
body:
- type: dropdown
id: mc-version
attributes:
label: Minecraft Version
description: What version of Minecraft are you using?
options:
- 1.15.x
- 1.16.x
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: "What version of CC: Tweaked are you using?"
placeholder: "e.g. 1.96.0"
validations:
required: true
- type: textarea
id: details
attributes:
label: Details
description: |
Description of the bug. Please include the following:
- Logs: These will be located in the `logs/` directory of your Minecraft
instance. Please upload them as a gist or directly into this editor.
- Detailed reproduction steps: sometimes I can spot a bug pretty easily,
but often it's much more obscure. The more information I have to help
reproduce it, the quicker it'll get fixed.

22
.gitpod.yml Normal file
View File

@@ -0,0 +1,22 @@
image:
file: config/gitpod/Dockerfile
ports:
- port: 25565
onOpen: notify
vscode:
extensions:
- eamodio.gitlens
- github.vscode-pull-request-github
- ms-azuretools.vscode-docker
- redhat.java
- richardwillis.vscode-gradle
- vscjava.vscode-java-debug
- vscode.github
tasks:
- name: Setup pre-commit hool
init: pre-commit install --config config/pre-commit/config.yml --allow-missing-config
- name: Install npm packages
init: npm ci

View File

@@ -19,6 +19,10 @@ process. When building on Windows, Use `gradlew.bat` instead of `./gradlew`.
- **Clone the repository:** `git clone https://github.com/SquidDev-CC/CC-Tweaked.git && cd CC-Tweaked`
- **Setup Forge:** `./gradlew build`
- **Run Minecraft:** `./gradlew runClient` (or run the `GradleStart` class from your IDE).
- **Optionally:** For small PRs (especially those only touching Lua code), it may be easier to use GitPod, which
provides a pre-configured environment: [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-2b2b2b?logo=gitpod)](https://gitpod.io/#https://github.com/SquidDev-CC/CC-Tweaked/)
Do note you will need to download the mod after compiling to test.
If you want to run CC:T in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from `build/libs`.
These commands may take a few minutes to run the first time, as the environment is set up, but should be much faster

View File

@@ -1,6 +1,5 @@
buildscript {
repositories {
jcenter()
mavenCentral()
maven {
name = "forge"
@@ -9,8 +8,7 @@ buildscript {
}
dependencies {
classpath 'com.google.code.gson:gson:2.8.1'
classpath 'net.minecraftforge.gradle:ForgeGradle:4.1.9'
classpath 'net.sf.proguard:proguard-gradle:6.1.0beta2'
classpath 'net.minecraftforge.gradle:ForgeGradle:5.0.6'
}
}
@@ -18,10 +16,11 @@ plugins {
id "checkstyle"
id "jacoco"
id "maven-publish"
id "com.github.hierynomus.license" version "0.15.0"
id "com.github.hierynomus.license" version "0.16.1"
id "com.matthewprenger.cursegradle" version "1.4.0"
id "com.github.breadmoirai.github-release" version "2.2.12"
id "org.jetbrains.kotlin.jvm" version "1.3.72"
id "com.modrinth.minotaur" version "1.2.1"
}
apply plugin: 'net.minecraftforge.gradle'
@@ -31,15 +30,30 @@ version = mod_version
group = "org.squiddev"
archivesBaseName = "cc-tweaked-${mc_version}"
def javaVersion = JavaLanguageVersion.of(8)
java {
toolchain {
languageVersion = JavaLanguageVersion.of(8)
languageVersion = javaVersion
}
withSourcesJar()
withJavadocJar()
}
// Tragically java.toolchain.languageVersion doesn't apply to ForgeGradle's
// tasks, so we force all launchers to use the right Java version.
tasks.withType(JavaCompile).configureEach {
javaCompiler = javaToolchains.compilerFor {
languageVersion = javaVersion
}
}
tasks.withType(JavaExec).configureEach {
javaLauncher = javaToolchains.launcherFor {
languageVersion = javaVersion
}
}
minecraft {
runs {
client {
@@ -183,59 +197,6 @@ jar {
}
}
import java.nio.charset.StandardCharsets
import java.nio.file.*
import java.util.zip.*
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import com.hierynomus.gradle.license.tasks.LicenseCheck
import com.hierynomus.gradle.license.tasks.LicenseFormat
import proguard.gradle.ProGuardTask
task proguard(type: ProGuardTask, dependsOn: jar) {
description "Removes unused shadowed classes from the jar"
group "compact"
injars jar.archivePath
outjars "${jar.archivePath.absolutePath.replace(".jar", "")}-min.jar"
// Add the main runtime jar and all non-shadowed dependencies
libraryjars "${System.getProperty('java.home')}/lib/rt.jar"
libraryjars "${System.getProperty('java.home')}/lib/jce.jar"
doFirst {
sourceSets.main.compileClasspath
.filter { !it.name.contains("Cobalt") }
.each { libraryjars it }
}
// We want to avoid as much obfuscation as possible. We're only doing this to shrink code size.
dontobfuscate; dontoptimize; keepattributes; keepparameternames
// Proguard will remove directories by default, but that breaks JarMount.
keepdirectories 'data/computercraft/lua**'
// Preserve ComputerCraft classes - we only want to strip shadowed files.
keep 'class dan200.computercraft.** { *; }'
// LWJGL and Apache bundle Java 9 versions, which is great, but rather breaks Proguard
dontwarn 'module-info'
dontwarn 'org.apache.**,org.lwjgl.**'
}
task proguardMove(dependsOn: proguard) {
description "Replace the original jar with the minified version"
group "compact"
doLast {
Files.move(
file("${jar.archivePath.absolutePath.replace(".jar", "")}-min.jar").toPath(),
file(jar.archivePath).toPath(),
StandardCopyOption.REPLACE_EXISTING
)
}
}
processResources {
inputs.property "version", mod_version
inputs.property "mcversion", mc_version
@@ -253,6 +214,7 @@ processResources {
e.printStackTrace()
}
inputs.property "commithash", hash
duplicatesStrategy = DuplicatesStrategy.INCLUDE
from(sourceSets.main.resources.srcDirs) {
include 'META-INF/mods.toml'
@@ -269,49 +231,10 @@ processResources {
}
}
task compressJson(dependsOn: jar) {
group "compact"
description "Minifies all JSON files, stripping whitespace"
def jarPath = file(jar.archivePath)
def tempPath = File.createTempFile("input", ".jar", temporaryDir)
tempPath.deleteOnExit()
def gson = new GsonBuilder().create()
doLast {
// Copy over all files in the current jar to the new one, running json files from GSON. As pretty printing
// is turned off, they should be minified.
new ZipFile(jarPath).withCloseable { inJar ->
tempPath.getParentFile().mkdirs()
new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tempPath))).withCloseable { outJar ->
inJar.entries().each { entry ->
if(entry.directory) {
outJar.putNextEntry(entry)
} else if(!entry.name.endsWith(".json")) {
outJar.putNextEntry(entry)
inJar.getInputStream(entry).withCloseable { outJar << it }
} else {
ZipEntry newEntry = new ZipEntry(entry.name)
newEntry.setTime(entry.time)
outJar.putNextEntry(newEntry)
def element = inJar.getInputStream(entry).withCloseable { gson.fromJson(it.newReader("UTF8"), JsonElement.class) }
outJar.write(gson.toJson(element).getBytes(StandardCharsets.UTF_8))
}
}
}
}
// And replace the original jar again
Files.move(tempPath.toPath(), jarPath.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
sourcesJar {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}
assemble.dependsOn compressJson
// Web tasks
import org.apache.tools.ant.taskdefs.condition.Os
@@ -383,6 +306,10 @@ jacocoTestReport {
check.dependsOn jacocoTestReport
import com.hierynomus.gradle.license.tasks.LicenseCheck
import com.hierynomus.gradle.license.tasks.LicenseFormat
license {
mapping("java", "SLASHSTAR_STYLE")
strictCheck true
@@ -436,7 +363,7 @@ task setupServer(type: Copy) {
tasks.register('testInGame', JavaExec.class).configure {
it.group('test server')
it.description("Runs tests on a temporary Minecraft server.")
it.dependsOn(setupServer, 'prepareRunTestServer')
it.dependsOn(setupServer, 'prepareRunTestServer', 'cleanTestInGame')
// Copy from runTestServer. We do it in this slightly odd way as runTestServer
// isn't created until the task is configured (which is no good for us).
@@ -490,31 +417,31 @@ task checkRelease {
description "Verifies that everything is ready for a release"
inputs.property "version", mod_version
inputs.file("src/main/resources/data/computercraft/lua/rom/help/changelog.txt")
inputs.file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt")
inputs.file("src/main/resources/data/computercraft/lua/rom/help/changelog.md")
inputs.file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
doLast {
def ok = true
// Check we're targetting the current version
def whatsnew = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt").readLines()
def whatsnew = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/whatsnew.md").readLines()
if (whatsnew[0] != "New features in CC: Tweaked $mod_version") {
ok = false
project.logger.error("Expected `whatsnew.txt' to target $mod_version.")
project.logger.error("Expected `whatsnew.md' to target $mod_version.")
}
// Check "read more" exists and trim it
def idx = whatsnew.findIndexOf { it == 'Type "help changelog" to see the full version history.' }
if (idx == -1) {
ok = false
project.logger.error("Must mention the changelog in whatsnew.txt")
project.logger.error("Must mention the changelog in whatsnew.md")
} else {
whatsnew = whatsnew.getAt(0 ..< idx)
}
// Check whatsnew and changelog match.
def versionChangelog = "# " + whatsnew.join("\n")
def changelog = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/changelog.txt").getText()
def changelog = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/changelog.md").getText()
if (!changelog.startsWith(versionChangelog)) {
ok = false
project.logger.error("whatsnew and changelog are not in sync")
@@ -538,6 +465,26 @@ curseforge {
}
}
import com.modrinth.minotaur.TaskModrinthUpload
tasks.register('publishModrinth', TaskModrinthUpload.class).configure {
dependsOn('assemble', 'reobfJar')
onlyIf {
project.hasProperty('modrinthApiKey')
}
token = project.hasProperty('curseForgeApiKey')
projectId = 'gu7yAYhd'
versionNumber = project.mod_version
uploadFile = jar
addGameVersion(project.mc_version)
addLoader('forge')
}
tasks.withType(GenerateModuleMetadata) {
// We can't generate metadata as that includes Forge as a dependency.
enabled = false
}
publishing {
publications {
maven(MavenPublication) {
@@ -563,6 +510,8 @@ publishing {
url = 'https://github.com/SquidDev-CC/CC-Tweaked/blob/mc-1.15.x/LICENSE'
}
}
withXml { asNode().remove(asNode().get("dependencies")) }
}
}
}
@@ -605,10 +554,10 @@ githubRelease {
prerelease false
}
def uploadTasks = ["publish", "curseforge", "githubRelease"]
def uploadTasks = ["publish", "curseforge", "publishModrinth", "githubRelease"]
uploadTasks.forEach { tasks.getByName(it).dependsOn checkRelease }
task uploadAll(dependsOn: uploadTasks) {
group "upload"
description "Uploads to all repositories (Maven, Curse, GitHub release)"
description "Uploads to all repositories (Maven, Curse, Modrinth, GitHub release)"
}

View File

@@ -7,9 +7,6 @@
<suppress checks="StaticVariableName" files=".*[\\/]ComputerCraft.java" />
<suppress checks="StaticVariableName" files=".*[\\/]ComputerCraftAPI.java" />
<!-- Do not check for missing package Javadoc. -->
<suppress checks="JavadocStyle" files=".*[\\/]package-info.java" />
<!-- The commands API is documented in Lua. -->
<suppress checks="SummaryJavadocCheck" files=".*[\\/]CommandAPI.java" />
</suppressions>

8
config/gitpod/Dockerfile Normal file
View File

@@ -0,0 +1,8 @@
FROM gitpod/workspace-base
USER gitpod
RUN sudo apt-get -q update \
&& sudo apt-get install -yq openjdk-8-jdk openjdk-16-jdk python3-pip npm \
&& sudo pip3 install pre-commit \
&& sudo update-java-alternatives --set java-1.8.0-openjdk-amd64

View File

@@ -1,5 +1,5 @@
# Mod properties
mod_version=1.96.0
mod_version=1.97.0
# Minecraft properties (update mods.toml when changing)
mc_version=1.15.2

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -36,7 +36,6 @@ public final class ComputerCraft
public static int maximumFilesOpen = 128;
public static boolean disableLua51Features = false;
public static String defaultComputerSettings = "";
public static boolean debugEnable = true;
public static boolean logComputerErrors = true;
public static boolean commandRequireCreative = true;

View File

@@ -32,7 +32,7 @@ public final class TransformedModel
public TransformedModel( @Nonnull IBakedModel model )
{
this.model = Objects.requireNonNull( model );
this.matrix = TransformationMatrix.identity();
matrix = TransformationMatrix.identity();
}
public static TransformedModel of( @Nonnull ModelResourceLocation location )

View File

@@ -30,7 +30,7 @@ public class FileOperationException extends IOException
public FileOperationException( @Nonnull String message )
{
super( Objects.requireNonNull( message, "message cannot be null" ) );
this.filename = null;
filename = null;
}
@Nullable

View File

@@ -19,14 +19,14 @@ public class LuaException extends Exception
public LuaException( @Nullable String message )
{
super( message );
this.hasLevel = false;
this.level = 1;
hasLevel = false;
level = 1;
}
public LuaException( @Nullable String message, int level )
{
super( message );
this.hasLevel = true;
hasLevel = true;
this.level = level;
}

View File

@@ -30,14 +30,14 @@ public final class MethodResult
private MethodResult( Object[] arguments, ILuaCallback callback )
{
this.result = arguments;
result = arguments;
this.callback = callback;
this.adjust = 0;
adjust = 0;
}
private MethodResult( Object[] arguments, ILuaCallback callback, int adjust )
{
this.result = arguments;
result = arguments;
this.callback = callback;
this.adjust = adjust;
}

View File

@@ -0,0 +1,40 @@
/*
* 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.client;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT )
public class ClientHooks
{
@SubscribeEvent
public static void onWorldUnload( WorldEvent.Unload event )
{
if( event.getWorld().isClientSide() )
{
ClientMonitor.destroyAll();
SoundManager.reset();
}
}
@SubscribeEvent
public static void onLogIn( ClientPlayerNetworkEvent.LoggedInEvent event )
{
ComputerCraft.clientComputerRegistry.reset();
}
@SubscribeEvent
public static void onLogOut( ClientPlayerNetworkEvent.LoggedOutEvent event )
{
ComputerCraft.clientComputerRegistry.reset();
}
}

View File

@@ -6,31 +6,34 @@
package dan200.computercraft.client;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.client.render.TurtleModelLoader;
import dan200.computercraft.client.render.TurtlePlayerRenderer;
import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.media.items.ItemDisk;
import dan200.computercraft.shared.media.items.ItemTreasureDisk;
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.util.Colour;
import net.minecraft.client.renderer.model.IBakedModel;
import net.minecraft.client.renderer.model.IUnbakedModel;
import net.minecraft.client.gui.ScreenManager;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderTypeLookup;
import net.minecraft.client.renderer.model.ModelResourceLocation;
import net.minecraft.inventory.container.PlayerContainer;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ColorHandlerEvent;
import net.minecraftforge.client.event.ModelBakeEvent;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.client.model.ModelLoaderRegistry;
import net.minecraftforge.client.model.SimpleModelTransform;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.client.registry.RenderingRegistry;
import net.minecraftforge.fml.common.Mod;
import java.util.HashSet;
import java.util.Map;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
/**
* Registers textures and models for items.
@@ -39,6 +42,7 @@ import java.util.Map;
public final class ClientRegistry
{
private static final String[] EXTRA_MODELS = new String[] {
// Turtle upgrades
"turtle_modem_normal_off_left",
"turtle_modem_normal_on_left",
"turtle_modem_normal_off_right",
@@ -54,56 +58,20 @@ public final class ClientRegistry
"turtle_speaker_upgrade_left",
"turtle_speaker_upgrade_right",
// Turtle block renderer
"turtle_colour",
"turtle_elf_overlay",
};
private static final String[] EXTRA_TEXTURES = new String[] {
// TODO: Gather these automatically from the model. Sadly the model loader isn't available
// when stitching textures.
"block/turtle_colour",
"block/turtle_elf_overlay",
"block/turtle_crafty_face",
"block/turtle_speaker_face",
};
private ClientRegistry() {}
@SubscribeEvent
public static void registerModels( ModelRegistryEvent event )
{
ModelLoaderRegistry.registerLoader( new ResourceLocation( ComputerCraft.MOD_ID, "turtle" ), TurtleModelLoader.INSTANCE );
}
@SubscribeEvent
public static void onTextureStitchEvent( TextureStitchEvent.Pre event )
{
if( !event.getMap().location().equals( PlayerContainer.BLOCK_ATLAS ) ) return;
for( String extra : EXTRA_TEXTURES )
for( String model : EXTRA_MODELS )
{
event.addSprite( new ResourceLocation( ComputerCraft.MOD_ID, extra ) );
}
}
@SubscribeEvent
public static void onModelBakeEvent( ModelBakeEvent event )
{
// Load all extra models
ModelLoader loader = event.getModelLoader();
Map<ResourceLocation, IBakedModel> registry = event.getModelRegistry();
for( String modelName : EXTRA_MODELS )
{
ResourceLocation location = new ResourceLocation( ComputerCraft.MOD_ID, "item/" + modelName );
IUnbakedModel model = loader.getModel( location );
model.getMaterials( loader::getModel, new HashSet<>() );
IBakedModel baked = model.bake( loader, ModelLoader.defaultTextureGetter(), SimpleModelTransform.IDENTITY, location );
if( baked != null )
{
registry.put( new ModelResourceLocation( new ResourceLocation( ComputerCraft.MOD_ID, modelName ), "inventory" ), baked );
}
ModelLoader.addSpecialModel( new ModelResourceLocation( new ResourceLocation( ComputerCraft.MOD_ID, model ), "inventory" ) );
}
}
@@ -148,4 +116,41 @@ public final class ClientRegistry
Registry.ModBlocks.TURTLE_NORMAL.get(), Registry.ModBlocks.TURTLE_ADVANCED.get()
);
}
@SubscribeEvent
public static void setupClient( FMLClientSetupEvent event )
{
registerContainers();
// While turtles themselves are not transparent, their upgrades may be.
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_NORMAL.get(), RenderType.translucent() );
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_ADVANCED.get(), RenderType.translucent() );
// Monitors' textures have transparent fronts and so count as cutouts.
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.MONITOR_NORMAL.get(), RenderType.cutout() );
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.MONITOR_ADVANCED.get(), RenderType.cutout() );
// Setup TESRs
net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.MONITOR_NORMAL.get(), TileEntityMonitorRenderer::new );
net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.MONITOR_ADVANCED.get(), TileEntityMonitorRenderer::new );
net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_NORMAL.get(), TileEntityTurtleRenderer::new );
net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_ADVANCED.get(), TileEntityTurtleRenderer::new );
RenderingRegistry.registerEntityRenderingHandler( Registry.ModEntities.TURTLE_PLAYER.get(), TurtlePlayerRenderer::new );
}
private static void registerContainers()
{
// My IDE doesn't think so, but we do actually need these generics.
ScreenManager.<ContainerComputer, GuiComputer<ContainerComputer>>register( Registry.ModContainers.COMPUTER.get(), GuiComputer::create );
ScreenManager.<ContainerPocketComputer, GuiComputer<ContainerPocketComputer>>register( Registry.ModContainers.POCKET_COMPUTER.get(), GuiComputer::createPocket );
ScreenManager.register( Registry.ModContainers.TURTLE.get(), GuiTurtle::new );
ScreenManager.register( Registry.ModContainers.PRINTER.get(), GuiPrinter::new );
ScreenManager.register( Registry.ModContainers.DISK_DRIVE.get(), GuiDiskDrive::new );
ScreenManager.register( Registry.ModContainers.PRINTOUT.get(), GuiPrintout::new );
ScreenManager.<ContainerViewComputer, GuiComputer<ContainerViewComputer>>register( Registry.ModContainers.VIEW_COMPUTER.get(), GuiComputer::createView );
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.client;
import net.minecraft.client.Minecraft;
import net.minecraft.client.audio.ISound;
import net.minecraft.client.audio.ITickableSound;
import net.minecraft.client.audio.LocatableSound;
import net.minecraft.client.audio.SoundHandler;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.Vec3d;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class SoundManager
{
private static final Map<UUID, MoveableSound> sounds = new HashMap<>();
public static void playSound( UUID source, Vec3d position, SoundEvent event, float volume, float pitch )
{
SoundHandler soundManager = Minecraft.getInstance().getSoundManager();
MoveableSound oldSound = sounds.get( source );
if( oldSound != null ) soundManager.stop( oldSound );
MoveableSound newSound = new MoveableSound( event, position, volume, pitch );
sounds.put( source, newSound );
soundManager.play( newSound );
}
public static void stopSound( UUID source )
{
ISound sound = sounds.remove( source );
if( sound == null ) return;
Minecraft.getInstance().getSoundManager().stop( sound );
}
public static void moveSound( UUID source, Vec3d position )
{
MoveableSound sound = sounds.get( source );
if( sound != null ) sound.setPosition( position );
}
public static void reset()
{
sounds.clear();
}
private static class MoveableSound extends LocatableSound implements ITickableSound
{
protected MoveableSound( SoundEvent sound, Vec3d position, float volume, float pitch )
{
super( sound, SoundCategory.RECORDS );
setPosition( position );
this.volume = volume;
this.pitch = pitch;
}
void setPosition( Vec3d position )
{
x = (float) position.x();
y = (float) position.y();
z = (float) position.z();
}
@Override
public boolean isStopped()
{
return false;
}
@Override
public void tick()
{
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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.client.gui;
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.inventory.ContainerComputerBase;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.client.gui.widget.Widget;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.text.ITextComponent;
import org.lwjgl.glfw.GLFW;
public abstract class ComputerScreenBase<T extends ContainerComputerBase> extends ContainerScreen<T>
{
protected WidgetTerminal terminal;
protected final ClientComputer computer;
protected final ComputerFamily family;
protected final int sidebarYOffset;
public ComputerScreenBase( T container, PlayerInventory player, ITextComponent title, int sidebarYOffset )
{
super( container, player, title );
computer = (ClientComputer) container.getComputer();
family = container.getFamily();
this.sidebarYOffset = sidebarYOffset;
}
protected abstract WidgetTerminal createTerminal();
@Override
protected final void init()
{
super.init();
minecraft.keyboardHandler.setSendRepeatsToGui( true );
terminal = addButton( createTerminal() );
ComputerSidebar.addButtons( this, computer, this::addButton, leftPos, topPos + sidebarYOffset );
setFocused( terminal );
}
@Override
public final void removed()
{
super.removed();
minecraft.keyboardHandler.setSendRepeatsToGui( false );
}
@Override
public final void tick()
{
super.tick();
terminal.update();
}
@Override
public final boolean keyPressed( int key, int scancode, int modifiers )
{
// Forward the tab key to the terminal, rather than moving between controls.
if( key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminal )
{
return getFocused().keyPressed( key, scancode, modifiers );
}
return super.keyPressed( key, scancode, modifiers );
}
@Override
public final void render( int mouseX, int mouseY, float partialTicks )
{
renderBackground();
super.render( mouseX, mouseY, partialTicks );
renderTooltip( mouseX, mouseY );
for( Widget widget : buttons )
{
if( widget.isHovered() ) widget.renderToolTip( mouseX, mouseY );
}
}
@Override
public final boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
{
return (getFocused() != null && getFocused().mouseDragged( x, y, button, deltaX, deltaY ))
|| super.mouseDragged( x, y, button, deltaX, deltaY );
}
}

View File

@@ -7,43 +7,33 @@ package dan200.computercraft.client.gui;
import com.mojang.blaze3d.systems.RenderSystem;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
import dan200.computercraft.client.gui.widgets.WidgetWrapper;
import dan200.computercraft.client.render.ComputerBorderRenderer;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerComputerBase;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.text.ITextComponent;
import org.lwjgl.glfw.GLFW;
import static dan200.computercraft.client.render.ComputerBorderRenderer.BORDER;
import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN;
public final class GuiComputer<T extends ContainerComputerBase> extends ContainerScreen<T>
public final class GuiComputer<T extends ContainerComputerBase> extends ComputerScreenBase<T>
{
private final ComputerFamily family;
private final ClientComputer computer;
private final int termWidth;
private final int termHeight;
private WidgetTerminal terminal;
private WidgetWrapper terminalWrapper;
private GuiComputer(
T container, PlayerInventory player, ITextComponent title, int termWidth, int termHeight
)
{
super( container, player, title );
family = container.getFamily();
computer = (ClientComputer) container.getComputer();
super( container, player, title, BORDER );
this.termWidth = termWidth;
this.termHeight = termHeight;
terminal = null;
imageWidth = WidgetTerminal.getWidth( termWidth ) + BORDER * 2 + ComputerSidebar.WIDTH;
imageHeight = WidgetTerminal.getHeight( termHeight ) + BORDER * 2;
}
public static GuiComputer<ContainerComputer> create( ContainerComputer container, PlayerInventory inventory, ITextComponent component )
@@ -70,82 +60,21 @@ public final class GuiComputer<T extends ContainerComputerBase> extends Containe
);
}
@Override
protected void init()
protected WidgetTerminal createTerminal()
{
minecraft.keyboardHandler.setSendRepeatsToGui( true );
int termPxWidth = termWidth * FixedWidthFontRenderer.FONT_WIDTH;
int termPxHeight = termHeight * FixedWidthFontRenderer.FONT_HEIGHT;
imageWidth = termPxWidth + MARGIN * 2 + BORDER * 2;
imageHeight = termPxHeight + MARGIN * 2 + BORDER * 2;
super.init();
terminal = new WidgetTerminal( minecraft, () -> computer, termWidth, termHeight, MARGIN, MARGIN, MARGIN, MARGIN );
terminalWrapper = new WidgetWrapper( terminal, MARGIN + BORDER + leftPos, MARGIN + BORDER + topPos, termPxWidth, termPxHeight );
children.add( terminalWrapper );
setFocused( terminalWrapper );
}
@Override
public void removed()
{
super.removed();
children.remove( terminal );
terminal = null;
minecraft.keyboardHandler.setSendRepeatsToGui( false );
}
@Override
public void tick()
{
super.tick();
terminal.update();
}
@Override
public boolean keyPressed( int key, int scancode, int modifiers )
{
// Forward the tab key to the terminal, rather than moving between controls.
if( key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminalWrapper )
{
return getFocused().keyPressed( key, scancode, modifiers );
}
return super.keyPressed( key, scancode, modifiers );
return new WidgetTerminal( computer,
leftPos + ComputerSidebar.WIDTH + BORDER, topPos + BORDER, termWidth, termHeight
);
}
@Override
public void renderBg( float partialTicks, int mouseX, int mouseY )
{
// Draw terminal
terminal.draw( terminalWrapper.getX(), terminalWrapper.getY() );
// Draw a border around the terminal
RenderSystem.color4f( 1, 1, 1, 1 );
minecraft.getTextureManager().bind( ComputerBorderRenderer.getTexture( family ) );
ComputerBorderRenderer.render(
terminalWrapper.getX() - MARGIN, terminalWrapper.getY() - MARGIN, getBlitOffset(),
terminalWrapper.getWidth() + MARGIN * 2, terminalWrapper.getHeight() + MARGIN * 2
);
}
@Override
public void render( int mouseX, int mouseY, float partialTicks )
{
renderBackground();
super.render( mouseX, mouseY, partialTicks );
renderTooltip( mouseX, mouseY );
}
@Override
public boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
{
return (getFocused() != null && getFocused().mouseDragged( x, y, button, deltaX, deltaY ))
|| super.mouseDragged( x, y, button, deltaX, deltaY );
ComputerBorderRenderer.render( terminal.x, terminal.y, getBlitOffset(), terminal.getWidth(), terminal.getHeight() );
ComputerSidebar.renderBackground( leftPos, topPos + sidebarYOffset );
}
}

View File

@@ -7,132 +7,67 @@ package dan200.computercraft.client.gui;
import com.mojang.blaze3d.systems.RenderSystem;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
import dan200.computercraft.client.gui.widgets.WidgetWrapper;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.client.render.ComputerBorderRenderer;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
import net.minecraft.client.gui.screen.inventory.ContainerScreen;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.ITextComponent;
import org.lwjgl.glfw.GLFW;
public class GuiTurtle extends ContainerScreen<ContainerTurtle>
import static dan200.computercraft.shared.turtle.inventory.ContainerTurtle.*;
public class GuiTurtle extends ComputerScreenBase<ContainerTurtle>
{
private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/turtle_normal.png" );
private static final ResourceLocation BACKGROUND_ADVANCED = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/turtle_advanced.png" );
private final ContainerTurtle container;
private static final int TEX_WIDTH = 254;
private static final int TEX_HEIGHT = 217;
private final ComputerFamily family;
private final ClientComputer computer;
private WidgetTerminal terminal;
private WidgetWrapper terminalWrapper;
public GuiTurtle( ContainerTurtle container, PlayerInventory player, ITextComponent title )
{
super( container, player, title );
this.container = container;
super( container, player, title, BORDER );
family = container.getFamily();
computer = (ClientComputer) container.getComputer();
imageWidth = 254;
imageHeight = 217;
imageWidth = TEX_WIDTH + ComputerSidebar.WIDTH;
imageHeight = TEX_HEIGHT;
}
@Override
protected void init()
protected WidgetTerminal createTerminal()
{
super.init();
minecraft.keyboardHandler.setSendRepeatsToGui( true );
int termPxWidth = ComputerCraft.turtleTermWidth * FixedWidthFontRenderer.FONT_WIDTH;
int termPxHeight = ComputerCraft.turtleTermHeight * FixedWidthFontRenderer.FONT_HEIGHT;
terminal = new WidgetTerminal(
minecraft, () -> computer,
ComputerCraft.turtleTermWidth,
ComputerCraft.turtleTermHeight,
2, 2, 2, 2
return new WidgetTerminal(
computer, leftPos + BORDER + ComputerSidebar.WIDTH, topPos + BORDER,
ComputerCraft.turtleTermWidth, ComputerCraft.turtleTermHeight
);
terminalWrapper = new WidgetWrapper( terminal, 2 + 8 + leftPos, 2 + 8 + topPos, termPxWidth, termPxHeight );
children.add( terminalWrapper );
setFocused( terminalWrapper );
}
@Override
public void removed()
{
super.removed();
children.remove( terminal );
terminal = null;
minecraft.keyboardHandler.setSendRepeatsToGui( false );
}
@Override
public void tick()
{
super.tick();
terminal.update();
}
@Override
public boolean keyPressed( int key, int scancode, int modifiers )
{
// Forward the tab key to the terminal, rather than moving between controls.
if( key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminalWrapper )
{
return getFocused().keyPressed( key, scancode, modifiers );
}
return super.keyPressed( key, scancode, modifiers );
}
private void drawSelectionSlot( boolean advanced )
{
// Draw selection slot
int slot = container.getSelectedSlot();
if( slot >= 0 )
{
RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
int slotX = slot % 4;
int slotY = slot / 4;
minecraft.getTextureManager().bind( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
blit( leftPos + ContainerTurtle.TURTLE_START_X - 2 + slotX * 18, topPos + ContainerTurtle.PLAYER_START_Y - 2 + slotY * 18, 0, 217, 24, 24 );
}
}
@Override
protected void renderBg( float partialTicks, int mouseX, int mouseY )
{
// Draw term
boolean advanced = family == ComputerFamily.ADVANCED;
terminal.draw( terminalWrapper.getX(), terminalWrapper.getY() );
// Draw border/inventory
RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
minecraft.getTextureManager().bind( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
blit( leftPos, topPos, 0, 0, imageWidth, imageHeight );
blit( leftPos + ComputerSidebar.WIDTH, topPos, 0, 0, TEX_WIDTH, TEX_HEIGHT );
drawSelectionSlot( advanced );
}
minecraft.getTextureManager().bind( advanced ? ComputerBorderRenderer.BACKGROUND_ADVANCED : ComputerBorderRenderer.BACKGROUND_NORMAL );
ComputerSidebar.renderBackground( leftPos, topPos + sidebarYOffset );
@Override
public void render( int mouseX, int mouseY, float partialTicks )
{
renderBackground();
super.render( mouseX, mouseY, partialTicks );
renderTooltip( mouseX, mouseY );
}
@Override
public boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
{
return (getFocused() != null && getFocused().mouseDragged( x, y, button, deltaX, deltaY ))
|| super.mouseDragged( x, y, button, deltaX, deltaY );
int slot = getMenu().getSelectedSlot();
if( slot >= 0 )
{
RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
int slotX = slot % 4;
int slotY = slot / 4;
blit(
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18,
0, 217, 24, 24
);
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.client.gui.widgets;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.render.ComputerBorderRenderer;
import dan200.computercraft.shared.computer.core.ClientComputer;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.Widget;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.text.TextFormatting;
import java.util.Arrays;
import java.util.function.Consumer;
/**
* Registers buttons to interact with a computer.
*/
public final class ComputerSidebar
{
private static final ResourceLocation TEXTURE = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/buttons.png" );
private static final int TEX_SIZE = 64;
private static final int ICON_WIDTH = 12;
private static final int ICON_HEIGHT = 12;
private static final int ICON_MARGIN = 2;
private static final int ICON_TEX_Y_DIFF = 14;
private static final int CORNERS_BORDER = 3;
private static final int FULL_BORDER = CORNERS_BORDER + ICON_MARGIN;
private static final int BUTTONS = 2;
private static final int HEIGHT = (ICON_HEIGHT + ICON_MARGIN * 2) * BUTTONS + CORNERS_BORDER * 2;
public static final int WIDTH = 17;
private ComputerSidebar()
{
}
public static void addButtons( Screen screen, ClientComputer computer, Consumer<Widget> add, int x, int y )
{
x += CORNERS_BORDER + 1;
y += CORNERS_BORDER + ICON_MARGIN;
add.accept( new DynamicImageButton(
screen, x, y, ICON_WIDTH, ICON_HEIGHT, () -> computer.isOn() ? 15 : 1, 1, ICON_TEX_Y_DIFF,
TEXTURE, TEX_SIZE, TEX_SIZE, b -> toggleComputer( computer ),
() -> computer.isOn() ? Arrays.asList(
I18n.get( "gui.computercraft.tooltip.turn_off" ),
TextFormatting.GRAY + I18n.get( "gui.computercraft.tooltip.turn_off.key" )
) : Arrays.asList(
I18n.get( "gui.computercraft.tooltip.turn_on" ),
TextFormatting.GRAY + I18n.get( "gui.computercraft.tooltip.turn_off.key" )
)
) );
y += ICON_HEIGHT + ICON_MARGIN * 2;
add.accept( new DynamicImageButton(
screen, x, y, ICON_WIDTH, ICON_HEIGHT, 29, 1, ICON_TEX_Y_DIFF,
TEXTURE, TEX_SIZE, TEX_SIZE, b -> computer.queueEvent( "terminate" ),
Arrays.asList(
I18n.get( "gui.computercraft.tooltip.terminate" ),
TextFormatting.GRAY + I18n.get( "gui.computercraft.tooltip.terminate.key" )
)
) );
}
public static void renderBackground( int x, int y )
{
Screen.blit(
x, y, 0, 102, WIDTH, FULL_BORDER,
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
);
Screen.blit(
x, y + FULL_BORDER, WIDTH, HEIGHT - FULL_BORDER * 2,
0, 107, WIDTH, 4,
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
);
Screen.blit(
x, y + HEIGHT - FULL_BORDER, 0, 111, WIDTH, FULL_BORDER,
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
);
}
private static void toggleComputer( ClientComputer computer )
{
if( computer.isOn() )
{
computer.shutdown();
}
else
{
computer.turnOn();
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.client.gui.widgets;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.widget.button.Button;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.util.NonNullSupplier;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.function.IntSupplier;
/**
* Version of {@link net.minecraft.client.gui.widget.button.ImageButton} which allows changing some properties
* dynamically.
*/
public class DynamicImageButton extends Button
{
private final Screen screen;
private final ResourceLocation texture;
private final IntSupplier xTexStart;
private final int yTexStart;
private final int yDiffTex;
private final int textureWidth;
private final int textureHeight;
private final NonNullSupplier<List<String>> tooltip;
public DynamicImageButton(
Screen screen, int x, int y, int width, int height, int xTexStart, int yTexStart, int yDiffTex,
ResourceLocation texture, int textureWidth, int textureHeight,
IPressable onPress, List<String> tooltip
)
{
this(
screen, x, y, width, height, () -> xTexStart, yTexStart, yDiffTex,
texture, textureWidth, textureHeight,
onPress, () -> tooltip
);
}
public DynamicImageButton(
Screen screen, int x, int y, int width, int height, IntSupplier xTexStart, int yTexStart, int yDiffTex,
ResourceLocation texture, int textureWidth, int textureHeight,
IPressable onPress, NonNullSupplier<List<String>> tooltip
)
{
super( x, y, width, height, "", onPress );
this.screen = screen;
this.textureWidth = textureWidth;
this.textureHeight = textureHeight;
this.xTexStart = xTexStart;
this.yTexStart = yTexStart;
this.yDiffTex = yDiffTex;
this.texture = texture;
this.tooltip = tooltip;
}
@Override
public void renderButton( int mouseX, int mouseY, float partialTicks )
{
Minecraft minecraft = Minecraft.getInstance();
minecraft.getTextureManager().bind( texture );
RenderSystem.disableDepthTest();
int yTex = yTexStart;
if( isHovered() ) yTex += yDiffTex;
blit( x, y, xTexStart.getAsInt(), yTex, width, height, textureWidth, textureHeight );
RenderSystem.enableDepthTest();
}
@Nonnull
@Override
public String getMessage()
{
List<String> tooltip = this.tooltip.get();
return tooltip.isEmpty() ? "" : tooltip.get( 0 );
}
@Override
public void renderToolTip( int mouseX, int mouseY )
{
List<String> tooltip = this.tooltip.get();
if( !tooltip.isEmpty() ) screen.renderTooltip( tooltip, mouseX, mouseY );
}
}

View File

@@ -8,29 +8,29 @@ package dan200.computercraft.client.gui.widgets;
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.shared.computer.core.ClientComputer;
import dan200.computercraft.shared.computer.core.IComputer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.IGuiEventListener;
import net.minecraft.client.gui.widget.Widget;
import net.minecraft.util.SharedConstants;
import org.lwjgl.glfw.GLFW;
import javax.annotation.Nonnull;
import java.util.BitSet;
import java.util.function.Supplier;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN;
public class WidgetTerminal implements IGuiEventListener
public class WidgetTerminal extends Widget
{
private static final float TERMINATE_TIME = 0.5f;
private final Minecraft client;
private final ClientComputer computer;
private boolean focused;
private final Supplier<ClientComputer> computer;
private final int termWidth;
private final int termHeight;
// The positions of the actual terminal
private final int innerX;
private final int innerY;
private final int innerWidth;
private final int innerHeight;
private float terminateTimer = -1;
private float rebootTimer = -1;
@@ -40,23 +40,18 @@ public class WidgetTerminal implements IGuiEventListener
private int lastMouseX = -1;
private int lastMouseY = -1;
private final int leftMargin;
private final int rightMargin;
private final int topMargin;
private final int bottomMargin;
private final BitSet keysDown = new BitSet( 256 );
public WidgetTerminal( Minecraft client, Supplier<ClientComputer> computer, int termWidth, int termHeight, int leftMargin, int rightMargin, int topMargin, int bottomMargin )
public WidgetTerminal( @Nonnull ClientComputer computer, int x, int y, int termWidth, int termHeight )
{
this.client = client;
super( x, y, termWidth * FONT_WIDTH + MARGIN * 2, termHeight * FONT_HEIGHT + MARGIN * 2, "" );
this.computer = computer;
this.termWidth = termWidth;
this.termHeight = termHeight;
this.leftMargin = leftMargin;
this.rightMargin = rightMargin;
this.topMargin = topMargin;
this.bottomMargin = bottomMargin;
innerX = x + MARGIN;
innerY = y + MARGIN;
innerWidth = termWidth * FONT_WIDTH;
innerHeight = termHeight * FONT_HEIGHT;
}
@Override
@@ -65,7 +60,7 @@ public class WidgetTerminal implements IGuiEventListener
if( ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255 ) // printable chars in byte range
{
// Queue the "char" event
queueEvent( "char", Character.toString( ch ) );
computer.queueEvent( "char", new Object[] { Character.toString( ch ) } );
}
return true;
@@ -91,7 +86,7 @@ public class WidgetTerminal implements IGuiEventListener
case GLFW.GLFW_KEY_V:
// Ctrl+V for paste
String clipboard = client.keyboardHandler.getClipboard();
String clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
if( clipboard != null )
{
// Clip to the first occurrence of \r or \n
@@ -116,7 +111,7 @@ public class WidgetTerminal implements IGuiEventListener
{
// Clip to 512 characters and queue the event
if( clipboard.length() > 512 ) clipboard = clipboard.substring( 0, 512 );
queueEvent( "paste", clipboard );
computer.queueEvent( "paste", new Object[] { clipboard } );
}
return true;
@@ -129,8 +124,7 @@ public class WidgetTerminal implements IGuiEventListener
// Queue the "key" event and add to the down set
boolean repeat = keysDown.get( key );
keysDown.set( key );
IComputer computer = this.computer.get();
if( computer != null ) computer.keyDown( key, repeat );
computer.keyDown( key, repeat );
}
return true;
@@ -143,8 +137,7 @@ public class WidgetTerminal implements IGuiEventListener
if( key >= 0 && keysDown.get( key ) )
{
keysDown.set( key, false );
IComputer computer = this.computer.get();
if( computer != null ) computer.keyUp( key );
computer.keyUp( key );
}
switch( key )
@@ -170,14 +163,14 @@ public class WidgetTerminal implements IGuiEventListener
@Override
public boolean mouseClicked( double mouseX, double mouseY, int button )
{
ClientComputer computer = this.computer.get();
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
if( !inTermRegion( mouseX, mouseY ) ) return false;
if( !computer.isColour() || button < 0 || button > 2 ) return false;
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FONT_WIDTH);
int charY = (int) (mouseY / FONT_HEIGHT);
int charX = (int) ((mouseX - innerX) / FONT_WIDTH);
int charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
@@ -194,14 +187,14 @@ public class WidgetTerminal implements IGuiEventListener
@Override
public boolean mouseReleased( double mouseX, double mouseY, int button )
{
ClientComputer computer = this.computer.get();
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
if( !inTermRegion( mouseX, mouseY ) ) return false;
if( !computer.isColour() || button < 0 || button > 2 ) return false;
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FONT_WIDTH);
int charY = (int) (mouseY / FONT_HEIGHT);
int charX = (int) ((mouseX - innerX) / FONT_WIDTH);
int charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
@@ -221,14 +214,14 @@ public class WidgetTerminal implements IGuiEventListener
@Override
public boolean mouseDragged( double mouseX, double mouseY, int button, double v2, double v3 )
{
ClientComputer computer = this.computer.get();
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
if( !inTermRegion( mouseX, mouseY ) ) return false;
if( !computer.isColour() || button < 0 || button > 2 ) return false;
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FONT_WIDTH);
int charY = (int) (mouseY / FONT_HEIGHT);
int charX = (int) ((mouseX - innerX) / FONT_WIDTH);
int charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
@@ -246,14 +239,14 @@ public class WidgetTerminal implements IGuiEventListener
@Override
public boolean mouseScrolled( double mouseX, double mouseY, double delta )
{
ClientComputer computer = this.computer.get();
if( computer == null || !computer.isColour() || delta == 0 ) return false;
if( !inTermRegion( mouseX, mouseY ) ) return false;
if( !computer.isColour() || delta == 0 ) return false;
Terminal term = computer.getTerminal();
if( term != null )
{
int charX = (int) (mouseX / FONT_WIDTH);
int charY = (int) (mouseY / FONT_HEIGHT);
int charX = (int) ((mouseX - innerX) / FONT_WIDTH);
int charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
@@ -266,89 +259,74 @@ public class WidgetTerminal implements IGuiEventListener
return true;
}
private boolean inTermRegion( double mouseX, double mouseY )
{
return active && visible && mouseX >= innerX && mouseY >= innerY && mouseX < innerX + innerWidth && mouseY < innerY + innerHeight;
}
public void update()
{
if( terminateTimer >= 0 && terminateTimer < TERMINATE_TIME && (terminateTimer += 0.05f) > TERMINATE_TIME )
{
queueEvent( "terminate" );
computer.queueEvent( "terminate" );
}
if( shutdownTimer >= 0 && shutdownTimer < TERMINATE_TIME && (shutdownTimer += 0.05f) > TERMINATE_TIME )
{
ClientComputer computer = this.computer.get();
if( computer != null ) computer.shutdown();
computer.shutdown();
}
if( rebootTimer >= 0 && rebootTimer < TERMINATE_TIME && (rebootTimer += 0.05f) > TERMINATE_TIME )
{
ClientComputer computer = this.computer.get();
if( computer != null ) computer.reboot();
computer.reboot();
}
}
@Override
public boolean changeFocus( boolean reversed )
public void onFocusedChanged( boolean focused )
{
if( focused )
if( !focused )
{
// When blurring, we should make all keys go up
for( int key = 0; key < keysDown.size(); key++ )
{
if( keysDown.get( key ) ) queueEvent( "key_up", key );
if( keysDown.get( key ) ) computer.keyUp( key );
}
keysDown.clear();
// When blurring, we should make the last mouse button go up
if( lastMouseButton > 0 )
{
IComputer computer = this.computer.get();
if( computer != null ) computer.mouseUp( lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1 );
computer.mouseUp( lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1 );
lastMouseButton = -1;
}
shutdownTimer = terminateTimer = rebootTimer = -1;
}
focused = !focused;
return true;
}
public void draw( int originX, int originY )
{
synchronized( computer )
{
// Draw the screen contents
ClientComputer computer = this.computer.get();
Terminal terminal = computer != null ? computer.getTerminal() : null;
if( terminal != null )
{
FixedWidthFontRenderer.drawTerminal( originX, originY, terminal, !computer.isColour(), topMargin, bottomMargin, leftMargin, rightMargin );
}
else
{
FixedWidthFontRenderer.drawEmptyTerminal(
originX - leftMargin, originY - rightMargin,
termWidth * FONT_WIDTH + leftMargin + rightMargin,
termHeight * FONT_HEIGHT + topMargin + bottomMargin
);
}
}
}
private void queueEvent( String event )
{
ClientComputer computer = this.computer.get();
if( computer != null ) computer.queueEvent( event );
}
private void queueEvent( String event, Object... args )
{
ClientComputer computer = this.computer.get();
if( computer != null ) computer.queueEvent( event, args );
}
@Override
public boolean isMouseOver( double x, double y )
public void render( int mouseX, int mouseY, float partialTicks )
{
return true;
// Draw the screen contents
Terminal terminal = computer.getTerminal();
if( terminal != null )
{
FixedWidthFontRenderer.drawTerminal( innerX, innerY, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN );
}
else
{
FixedWidthFontRenderer.drawEmptyTerminal( x, y, width, height );
}
}
public static int getWidth( int termWidth )
{
return termWidth * FONT_WIDTH + MARGIN * 2;
}
public static int getHeight( int termHeight )
{
return termHeight * FONT_HEIGHT + MARGIN * 2;
}
}

View File

@@ -1,105 +0,0 @@
/*
* 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.client.gui.widgets;
import net.minecraft.client.gui.IGuiEventListener;
public class WidgetWrapper implements IGuiEventListener
{
private final IGuiEventListener listener;
private final int x;
private final int y;
private final int width;
private final int height;
public WidgetWrapper( IGuiEventListener listener, int x, int y, int width, int height )
{
this.listener = listener;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
@Override
public boolean changeFocus( boolean b )
{
return listener.changeFocus( b );
}
@Override
public boolean mouseClicked( double x, double y, int button )
{
double dx = x - this.x, dy = y - this.y;
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseClicked( dx, dy, button );
}
@Override
public boolean mouseReleased( double x, double y, int button )
{
double dx = x - this.x, dy = y - this.y;
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseReleased( dx, dy, button );
}
@Override
public boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
{
double dx = x - this.x, dy = y - this.y;
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseDragged( dx, dy, button, deltaX, deltaY );
}
@Override
public boolean mouseScrolled( double x, double y, double delta )
{
double dx = x - this.x, dy = y - this.y;
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseScrolled( dx, dy, delta );
}
@Override
public boolean keyPressed( int key, int scancode, int modifiers )
{
return listener.keyPressed( key, scancode, modifiers );
}
@Override
public boolean keyReleased( int key, int scancode, int modifiers )
{
return listener.keyReleased( key, scancode, modifiers );
}
@Override
public boolean charTyped( char character, int modifiers )
{
return listener.charTyped( character, modifiers );
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
@Override
public boolean isMouseOver( double x, double y )
{
double dx = x - this.x, dy = y - this.y;
return dx >= 0 && dx < width && dy >= 0 && dy < height;
}
}

View File

@@ -1,81 +0,0 @@
/*
* 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.client.proxy;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.client.render.TurtlePlayerRenderer;
import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
import net.minecraft.client.gui.ScreenManager;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderTypeLookup;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.client.registry.RenderingRegistry;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD )
public final class ComputerCraftProxyClient
{
@SubscribeEvent
public static void setupClient( FMLClientSetupEvent event )
{
registerContainers();
// While turtles themselves are not transparent, their upgrades may be.
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_NORMAL.get(), RenderType.translucent() );
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_ADVANCED.get(), RenderType.translucent() );
// Monitors' textures have transparent fronts and so count as cutouts.
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.MONITOR_NORMAL.get(), RenderType.cutout() );
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.MONITOR_ADVANCED.get(), RenderType.cutout() );
// Setup TESRs
ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.MONITOR_NORMAL.get(), TileEntityMonitorRenderer::new );
ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.MONITOR_ADVANCED.get(), TileEntityMonitorRenderer::new );
ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_NORMAL.get(), TileEntityTurtleRenderer::new );
ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_ADVANCED.get(), TileEntityTurtleRenderer::new );
RenderingRegistry.registerEntityRenderingHandler( Registry.ModEntities.TURTLE_PLAYER.get(), TurtlePlayerRenderer::new );
}
private static void registerContainers()
{
// My IDE doesn't think so, but we do actually need these generics.
ScreenManager.<ContainerComputer, GuiComputer<ContainerComputer>>register( Registry.ModContainers.COMPUTER.get(), GuiComputer::create );
ScreenManager.<ContainerPocketComputer, GuiComputer<ContainerPocketComputer>>register( Registry.ModContainers.POCKET_COMPUTER.get(), GuiComputer::createPocket );
ScreenManager.register( Registry.ModContainers.TURTLE.get(), GuiTurtle::new );
ScreenManager.register( Registry.ModContainers.PRINTER.get(), GuiPrinter::new );
ScreenManager.register( Registry.ModContainers.DISK_DRIVE.get(), GuiDiskDrive::new );
ScreenManager.register( Registry.ModContainers.PRINTOUT.get(), GuiPrintout::new );
ScreenManager.<ContainerViewComputer, GuiComputer<ContainerViewComputer>>register( Registry.ModContainers.VIEW_COMPUTER.get(), GuiComputer::createView );
}
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT )
public static final class ForgeHandlers
{
@SubscribeEvent
public static void onWorldUnload( WorldEvent.Unload event )
{
if( event.getWorld().isClientSide() )
{
ClientMonitor.destroyAll();
}
}
}
}

View File

@@ -52,7 +52,8 @@ public class ComputerBorderRenderer
public static final int LIGHT_HEIGHT = 8;
private static final float TEX_SCALE = 1 / 256.0f;
public static final int TEX_SIZE = 256;
private static final float TEX_SCALE = 1 / (float) TEX_SIZE;
private final Matrix4f transform;
private final IVertexBuilder builder;

View File

@@ -37,7 +37,7 @@ public class HTTPAPI implements ILuaAPI
{
private final IAPIEnvironment apiEnvironment;
private final ResourceGroup<CheckUrl> checkUrls = new ResourceGroup<>();
private final ResourceGroup<CheckUrl> checkUrls = new ResourceGroup<>( ResourceGroup.DEFAULT );
private final ResourceGroup<HttpRequest> requests = new ResourceQueue<>( () -> ComputerCraft.httpMaxRequests );
private final ResourceGroup<Websocket> websockets = new ResourceGroup<>( () -> ComputerCraft.httpMaxWebsockets );
@@ -127,7 +127,10 @@ public class HTTPAPI implements ILuaAPI
HttpRequest request = new HttpRequest( requests, apiEnvironment, address, postString, headers, binary, redirect );
// Make the request
request.queue( r -> r.request( uri, httpMethod ) );
if( !request.queue( r -> r.request( uri, httpMethod ) ) )
{
throw new LuaException( "Too many ongoing HTTP requests" );
}
return new Object[] { true };
}
@@ -138,12 +141,15 @@ public class HTTPAPI implements ILuaAPI
}
@LuaFunction
public final Object[] checkURL( String address )
public final Object[] checkURL( String address ) throws LuaException
{
try
{
URI uri = HttpRequest.checkUri( address );
new CheckUrl( checkUrls, apiEnvironment, address, uri ).queue( CheckUrl::run );
if( !new CheckUrl( checkUrls, apiEnvironment, address, uri ).queue( CheckUrl::run ) )
{
throw new LuaException( "Too many ongoing checkUrl calls" );
}
return new Object[] { true };
}

View File

@@ -11,12 +11,19 @@ import dan200.computercraft.core.apis.http.options.AddressRule;
import dan200.computercraft.core.apis.http.options.Options;
import dan200.computercraft.shared.util.ThreadUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ConnectTimeoutException;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.timeout.ReadTimeoutException;
import javax.annotation.Nonnull;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManagerFactory;
import java.net.InetSocketAddress;
import java.net.URI;
@@ -161,4 +168,29 @@ public final class NetworkUtils
buffer.readBytes( bytes );
return bytes;
}
@Nonnull
public static String toFriendlyError( @Nonnull Throwable cause )
{
if( cause instanceof WebSocketHandshakeException || cause instanceof HTTPRequestException )
{
return cause.getMessage();
}
else if( cause instanceof TooLongFrameException )
{
return "Message is too large";
}
else if( cause instanceof ReadTimeoutException || cause instanceof ConnectTimeoutException )
{
return "Timed out";
}
else if( cause instanceof SSLHandshakeException || (cause instanceof DecoderException && cause.getCause() instanceof SSLHandshakeException) )
{
return "Could not create a secure connection";
}
else
{
return "Could not connect";
}
}
}

View File

@@ -97,7 +97,7 @@ public abstract class Resource<T extends Resource<T>> implements Closeable
tryClose();
}
public boolean queue( Consumer<T> task )
public final boolean queue( Consumer<T> task )
{
@SuppressWarnings( "unchecked" )
T thisT = (T) this;

View File

@@ -18,6 +18,9 @@ import java.util.function.Supplier;
*/
public class ResourceGroup<T extends Resource<T>>
{
public static final int DEFAULT_LIMIT = 512;
public static final IntSupplier DEFAULT = () -> DEFAULT_LIMIT;
private static final IntSupplier ZERO = () -> 0;
final IntSupplier limit;

View File

@@ -38,8 +38,10 @@ public class ResourceQueue<T extends Resource<T>> extends ResourceGroup<T>
public synchronized boolean queue( Supplier<T> resource )
{
if( !active ) return false;
if( super.queue( resource ) ) return true;
if( pending.size() > DEFAULT_LIMIT ) return false;
if( !super.queue( resource ) ) pending.add( resource );
pending.add( resource );
return true;
}

View File

@@ -19,13 +19,10 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ConnectTimeoutException;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.net.InetSocketAddress;
@@ -190,7 +187,7 @@ public class HttpRequest extends Resource<HttpRequest>
.remoteAddress( socketAddress )
.connect()
.addListener( c -> {
if( !c.isSuccess() ) failure( c.cause() );
if( !c.isSuccess() ) failure( NetworkUtils.toFriendlyError( c.cause() ) );
} );
// Do an additional check for cancellation
@@ -202,7 +199,7 @@ public class HttpRequest extends Resource<HttpRequest>
}
catch( Exception e )
{
failure( "Could not connect" );
failure( NetworkUtils.toFriendlyError( e ) );
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error in HTTP request", e );
}
}
@@ -212,29 +209,6 @@ public class HttpRequest extends Resource<HttpRequest>
if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message );
}
void failure( Throwable cause )
{
String message;
if( cause instanceof HTTPRequestException )
{
message = cause.getMessage();
}
else if( cause instanceof TooLongFrameException )
{
message = "Response is too large";
}
else if( cause instanceof ReadTimeoutException || cause instanceof ConnectTimeoutException )
{
message = "Timed out";
}
else
{
message = "Could not connect";
}
failure( message );
}
void failure( String message, HttpResponseHandle object )
{
if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message, object );

View File

@@ -183,7 +183,7 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause )
{
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error handling HTTP response", cause );
request.failure( cause );
request.failure( NetworkUtils.toFriendlyError( cause ) );
}
private void sendResponse()

View File

@@ -22,6 +22,7 @@ import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
@@ -150,8 +151,9 @@ public class Websocket extends Resource<Websocket>
p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) );
}
String subprotocol = headers.get( HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL );
WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, true, headers,
uri, WebSocketVersion.V13, subprotocol, true, headers,
options.websocketMessage <= 0 ? MAX_MESSAGE_SIZE : options.websocketMessage
);
@@ -166,7 +168,7 @@ public class Websocket extends Resource<Websocket>
.remoteAddress( socketAddress )
.connect()
.addListener( c -> {
if( !c.isSuccess() ) failure( c.cause().getMessage() );
if( !c.isSuccess() ) failure( NetworkUtils.toFriendlyError( c.cause() ) );
} );
// Do an additional check for cancellation
@@ -178,7 +180,7 @@ public class Websocket extends Resource<Websocket>
}
catch( Exception e )
{
failure( "Could not connect" );
failure( NetworkUtils.toFriendlyError( e ) );
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error in websocket", e );
}
}

View File

@@ -5,17 +5,13 @@
*/
package dan200.computercraft.core.apis.http.websocket;
import dan200.computercraft.core.apis.http.HTTPRequestException;
import dan200.computercraft.core.apis.http.NetworkUtils;
import dan200.computercraft.core.apis.http.options.Options;
import dan200.computercraft.core.tracking.TrackingField;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ConnectTimeoutException;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.util.CharsetUtil;
import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT;
@@ -97,24 +93,7 @@ public class WebsocketHandler extends SimpleChannelInboundHandler<Object>
{
ctx.close();
String message;
if( cause instanceof WebSocketHandshakeException || cause instanceof HTTPRequestException )
{
message = cause.getMessage();
}
else if( cause instanceof TooLongFrameException )
{
message = "Message is too large";
}
else if( cause instanceof ReadTimeoutException || cause instanceof ConnectTimeoutException )
{
message = "Timed out";
}
else
{
message = "Could not connect";
}
String message = NetworkUtils.toFriendlyError( cause );
if( handshaker.isHandshakeComplete() )
{
websocket.close( -1, message );

View File

@@ -11,7 +11,10 @@ import com.google.common.cache.LoadingCache;
import com.google.common.primitives.Primitives;
import com.google.common.reflect.TypeToken;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.*;
import dan200.computercraft.api.lua.IArguments;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.lua.MethodResult;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
@@ -63,7 +66,7 @@ public final class Generator<T>
{
this.base = base;
this.context = context;
this.interfaces = new String[] { Type.getInternalName( base ) };
interfaces = new String[] { Type.getInternalName( base ) };
this.wrap = wrap;
StringBuilder methodDesc = new StringBuilder().append( "(Ljava/lang/Object;" );

View File

@@ -251,6 +251,20 @@ final class ComputerExecutor
* and then schedule a shutdown.
*/
void abort()
{
immediateFail( StateCommand.ABORT );
}
/**
* Abort this whole computer due to an internal error. This will immediately destroy the Lua machine,
* and then schedule a shutdown.
*/
void fastFail()
{
immediateFail( StateCommand.ERROR );
}
private void immediateFail( StateCommand command )
{
ILuaMachine machine = this.machine;
if( machine != null ) machine.close();
@@ -258,7 +272,7 @@ final class ComputerExecutor
synchronized( queueLock )
{
if( closed ) return;
command = StateCommand.ABORT;
this.command = command;
if( isOn ) enqueue();
}
}
@@ -596,6 +610,12 @@ final class ComputerExecutor
displayFailure( "Error running computer", TimeoutState.ABORT_MESSAGE );
shutdown();
break;
case ERROR:
if( !isOn ) return;
displayFailure( "Error running computer", "An internal error occurred, see logs." );
shutdown();
break;
}
}
else if( event != null )
@@ -644,6 +664,7 @@ final class ComputerExecutor
SHUTDOWN,
REBOOT,
ABORT,
ERROR,
}
private static final class Event

View File

@@ -506,6 +506,8 @@ public final class ComputerThread
catch( Exception | LinkageError | VirtualMachineError e )
{
ComputerCraft.log.error( "Error running task on computer #" + executor.getComputer().getID(), e );
// Tear down the computer immediately. There's no guarantee it's well behaved from now on.
executor.fastFail();
}
finally
{

View File

@@ -10,7 +10,6 @@ import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.asm.LuaMethod;
import dan200.computercraft.core.asm.ObjectSource;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.computer.TimeoutState;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.core.tracking.TrackingField;
@@ -53,7 +52,7 @@ public class CobaltLuaMachine implements ILuaMachine
private final Computer computer;
private final TimeoutState timeout;
private final TimeoutDebugHandler debug;
private final ILuaContext context = new CobaltLuaContext();
private final ILuaContext context;
private LuaState state;
private LuaTable globals;
@@ -65,6 +64,7 @@ public class CobaltLuaMachine implements ILuaMachine
{
this.computer = computer;
this.timeout = timeout;
context = new LuaContext( computer );
debug = new TimeoutDebugHandler();
// Create an environment to run in
@@ -97,7 +97,7 @@ public class CobaltLuaMachine implements ILuaMachine
globals.load( state, new CoroutineLib() );
globals.load( state, new Bit32Lib() );
globals.load( state, new Utf8Lib() );
if( ComputerCraft.debugEnable ) globals.load( state, new DebugLib() );
globals.load( state, new DebugLib() );
// Remove globals we don't want to expose
globals.rawset( "collectgarbage", Constants.NIL );
@@ -509,53 +509,6 @@ public class CobaltLuaMachine implements ILuaMachine
}
}
private class CobaltLuaContext implements ILuaContext
{
@Override
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
final Runnable iTask = () -> {
try
{
Object[] results = task.execute();
if( results != null )
{
Object[] eventArguments = new Object[results.length + 2];
eventArguments[0] = taskID;
eventArguments[1] = true;
System.arraycopy( results, 0, eventArguments, 2, results.length );
computer.queueEvent( "task_complete", eventArguments );
}
else
{
computer.queueEvent( "task_complete", new Object[] { taskID, true } );
}
}
catch( LuaException e )
{
computer.queueEvent( "task_complete", new Object[] { taskID, false, e.getMessage() } );
}
catch( Throwable t )
{
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error running task", t );
computer.queueEvent( "task_complete", new Object[] {
taskID, false, "Java Exception Thrown: " + t,
} );
}
};
if( computer.queueMainThread( iTask ) )
{
return taskID;
}
else
{
throw new LuaException( "Task limit exceeded" );
}
}
}
private static final class HardAbortError extends Error
{
private static final long serialVersionUID = 7954092008586367501L;

View File

@@ -0,0 +1,69 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.lua;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.ILuaTask;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.computer.MainThread;
import javax.annotation.Nonnull;
class LuaContext implements ILuaContext
{
private final Computer computer;
LuaContext( Computer computer )
{
this.computer = computer;
}
@Override
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
{
// Issue command
final long taskID = MainThread.getUniqueTaskID();
final Runnable iTask = () -> {
try
{
Object[] results = task.execute();
if( results != null )
{
Object[] eventArguments = new Object[results.length + 2];
eventArguments[0] = taskID;
eventArguments[1] = true;
System.arraycopy( results, 0, eventArguments, 2, results.length );
computer.queueEvent( "task_complete", eventArguments );
}
else
{
computer.queueEvent( "task_complete", new Object[] { taskID, true } );
}
}
catch( LuaException e )
{
computer.queueEvent( "task_complete", new Object[] { taskID, false, e.getMessage() } );
}
catch( Exception t )
{
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error running task", t );
computer.queueEvent( "task_complete", new Object[] {
taskID, false, "Java Exception Thrown: " + t,
} );
}
};
if( computer.queueMainThread( iTask ) )
{
return taskID;
}
else
{
throw new LuaException( "Task limit exceeded" );
}
}
}

View File

@@ -44,9 +44,9 @@ public class Terminal
this.height = height;
onChanged = changedCallback;
text = new TextBuffer[this.height];
textColour = new TextBuffer[this.height];
backgroundColour = new TextBuffer[this.height];
text = new TextBuffer[height];
textColour = new TextBuffer[height];
backgroundColour = new TextBuffer[height];
for( int i = 0; i < this.height; i++ )
{
text[i] = new TextBuffer( ' ', this.width );
@@ -93,9 +93,9 @@ public class Terminal
this.width = width;
this.height = height;
text = new TextBuffer[this.height];
textColour = new TextBuffer[this.height];
backgroundColour = new TextBuffer[this.height];
text = new TextBuffer[height];
textColour = new TextBuffer[height];
backgroundColour = new TextBuffer[height];
for( int i = 0; i < this.height; i++ )
{
if( i >= oldHeight )

View File

@@ -12,7 +12,7 @@ public class TextBuffer
public TextBuffer( char c, int length )
{
text = new char[length];
this.fill( c );
fill( c );
}
public TextBuffer( String text )
@@ -79,6 +79,7 @@ public class TextBuffer
}
}
@Override
public String toString()
{
return new String( text );

View File

@@ -5,7 +5,7 @@
*/
package dan200.computercraft.data;
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import dan200.computercraft.shared.Registry;
import net.minecraft.data.DataGenerator;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@@ -17,7 +17,7 @@ public class Generators
@SubscribeEvent
public static void gather( GatherDataEvent event )
{
ComputerCraftProxyCommon.registerLoot();
Registry.registerLoot();
DataGenerator generator = event.getGenerator();
generator.addProvider( new Recipes( generator ) );

View File

@@ -6,11 +6,11 @@
package dan200.computercraft.data;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
import net.minecraft.block.Block;
import net.minecraft.data.DataGenerator;
import net.minecraft.util.ResourceLocation;
@@ -46,7 +46,7 @@ public class LootTables extends LootTableProvider
computerDrop( add, Registry.ModBlocks.TURTLE_NORMAL );
computerDrop( add, Registry.ModBlocks.TURTLE_ADVANCED );
add.accept( ComputerCraftProxyCommon.ForgeHandlers.LOOT_TREASURE_DISK, LootTable
add.accept( CommonHooks.LOOT_TREASURE_DISK, LootTable
.lootTable()
.setParamSet( LootParameterSets.ALL_PARAMS )
.build() );

View File

@@ -0,0 +1,123 @@
/*
* 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;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.computer.core.IComputer;
import dan200.computercraft.shared.computer.core.IContainerComputer;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import net.minecraft.inventory.container.Container;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.storage.loot.ConstantRange;
import net.minecraft.world.storage.loot.LootPool;
import net.minecraft.world.storage.loot.LootTables;
import net.minecraft.world.storage.loot.TableLootEntry;
import net.minecraftforge.event.LootTableLoadEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Miscellaneous hooks which are present on the client and server.
*
* These should possibly be refactored into separate classes at some point, but are fine here for now.
*
* @see dan200.computercraft.client.ClientHooks For client-specific ones.
*/
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID )
public final class CommonHooks
{
private CommonHooks()
{
}
@SubscribeEvent
public static void onServerTick( TickEvent.ServerTickEvent event )
{
if( event.phase == TickEvent.Phase.START )
{
MainThread.executePendingTasks();
ComputerCraft.serverComputerRegistry.update();
}
}
@SubscribeEvent
public static void onContainerOpen( PlayerContainerEvent.Open event )
{
// If we're opening a computer container then broadcast the terminal state
Container container = event.getContainer();
if( container instanceof IContainerComputer )
{
IComputer computer = ((IContainerComputer) container).getComputer();
if( computer instanceof ServerComputer )
{
((ServerComputer) computer).sendTerminalState( event.getPlayer() );
}
}
}
@SubscribeEvent
public static void onServerStarting( FMLServerStartingEvent event )
{
CommandComputerCraft.register( event.getCommandDispatcher() );
}
@SubscribeEvent
public static void onServerStarted( FMLServerStartedEvent event )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
}
@SubscribeEvent
public static void onServerStopped( FMLServerStoppedEvent event )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
}
public static final ResourceLocation LOOT_TREASURE_DISK = new ResourceLocation( ComputerCraft.MOD_ID, "treasure_disk" );
private static final Set<ResourceLocation> TABLES = new HashSet<>( Arrays.asList(
LootTables.SIMPLE_DUNGEON,
LootTables.ABANDONED_MINESHAFT,
LootTables.STRONGHOLD_CORRIDOR,
LootTables.STRONGHOLD_CROSSING,
LootTables.STRONGHOLD_LIBRARY,
LootTables.DESERT_PYRAMID,
LootTables.JUNGLE_TEMPLE,
LootTables.IGLOO_CHEST,
LootTables.WOODLAND_MANSION,
LootTables.VILLAGE_CARTOGRAPHER
) );
@SubscribeEvent
public static void lootLoad( LootTableLoadEvent event )
{
ResourceLocation name = event.getName();
if( !name.getNamespace().equals( "minecraft" ) || !TABLES.contains( name ) ) return;
event.getTable().addPool( LootPool.lootPool()
.add( TableLootEntry.lootTableReference( LOOT_TREASURE_DISK ) )
.setRolls( ConstantRange.exactly( 1 ) )
.name( "computercraft_treasure" )
.build() );
}
}

View File

@@ -42,7 +42,6 @@ public final class Config
private static final ConfigValue<Integer> maximumFilesOpen;
private static final ConfigValue<Boolean> disableLua51Features;
private static final ConfigValue<String> defaultComputerSettings;
private static final ConfigValue<Boolean> debugEnabled;
private static final ConfigValue<Boolean> logComputerErrors;
private static final ConfigValue<Boolean> commandRequireCreative;
@@ -120,10 +119,6 @@ public final class Config
"autocompletion" )
.define( "default_computer_settings", ComputerCraft.defaultComputerSettings );
debugEnabled = builder
.comment( "Enable Lua's debug library. This is sandboxed to each computer, so is generally safe to be used by players." )
.define( "debug_enabled", ComputerCraft.debugEnable );
logComputerErrors = builder
.comment( "Log exceptions thrown by peripherals and other Lua objects.\n" +
"This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." )
@@ -317,7 +312,6 @@ public final class Config
ComputerCraft.maximumFilesOpen = maximumFilesOpen.get();
ComputerCraft.disableLua51Features = disableLua51Features.get();
ComputerCraft.defaultComputerSettings = defaultComputerSettings.get();
ComputerCraft.debugEnable = debugEnabled.get();
ComputerCraft.computerThreads = computerThreads.get();
ComputerCraft.logComputerErrors = logComputerErrors.get();
ComputerCraft.commandRequireCreative = commandRequireCreative.get();

View File

@@ -7,8 +7,13 @@ package dan200.computercraft.shared;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.command.arguments.ArgumentSerializers;
import dan200.computercraft.shared.common.ColourableRecipe;
import dan200.computercraft.shared.common.ContainerHeldItem;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.computer.blocks.BlockComputer;
import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
import dan200.computercraft.shared.computer.blocks.TileComputer;
@@ -17,11 +22,17 @@ import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.computer.items.ItemComputer;
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.ConstantLootConditionSerializer;
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.media.items.ItemDisk;
import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.media.items.ItemTreasureDisk;
import dan200.computercraft.shared.media.items.RecordMedia;
import dan200.computercraft.shared.media.recipes.DiskRecipe;
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.network.container.HeldItemContainerData;
@@ -29,6 +40,9 @@ import dan200.computercraft.shared.network.container.ViewComputerContainerData;
import dan200.computercraft.shared.peripheral.diskdrive.BlockDiskDrive;
import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive;
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
import dan200.computercraft.shared.peripheral.generic.methods.EnergyMethods;
import dan200.computercraft.shared.peripheral.generic.methods.FluidMethods;
import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
import dan200.computercraft.shared.peripheral.modem.wired.*;
import dan200.computercraft.shared.peripheral.modem.wireless.BlockWirelessModem;
import dan200.computercraft.shared.peripheral.modem.wireless.TileWirelessModem;
@@ -52,29 +66,30 @@ import dan200.computercraft.shared.turtle.items.ItemTurtle;
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.CreativeTabMain;
import dan200.computercraft.shared.util.FixedPointTileEntityType;
import dan200.computercraft.shared.util.ImpostorRecipe;
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
import dan200.computercraft.shared.util.*;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.entity.EntityClassification;
import net.minecraft.entity.EntityType;
import net.minecraft.inventory.container.ContainerType;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.Items;
import net.minecraft.item.*;
import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.storage.loot.conditions.LootConditionManager;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fml.DeferredWorkQueue;
import net.minecraftforge.fml.RegistryObject;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
@@ -333,6 +348,68 @@ public final class Registry
);
}
@SubscribeEvent
@SuppressWarnings( "deprecation" )
public static void init( FMLCommonSetupEvent event )
{
NetworkHandler.setup();
DeferredWorkQueue.runLater( () -> {
registerProviders();
ArgumentSerializers.register();
registerLoot();
} );
ComputerCraftAPI.registerGenericSource( new InventoryMethods() );
ComputerCraftAPI.registerGenericSource( new FluidMethods() );
ComputerCraftAPI.registerGenericSource( new EnergyMethods() );
}
private static void registerProviders()
{
// Register bundled power providers
ComputerCraftAPI.registerBundledRedstoneProvider( new DefaultBundledRedstoneProvider() );
// Register media providers
ComputerCraftAPI.registerMediaProvider( stack -> {
Item item = stack.getItem();
if( item instanceof IMedia ) return (IMedia) item;
if( item instanceof MusicDiscItem ) return RecordMedia.INSTANCE;
return null;
} );
// Register capabilities
CapabilityManager.INSTANCE.register( IWiredElement.class, new NullStorage<>(), () -> null );
CapabilityManager.INSTANCE.register( IPeripheral.class, new NullStorage<>(), () -> null );
// Register generic capabilities. This can technically be done off-thread, but we need it to happen
// after Forge's common setup, so this is easiest.
ComputerCraftAPI.registerGenericCapability( CapabilityItemHandler.ITEM_HANDLER_CAPABILITY );
ComputerCraftAPI.registerGenericCapability( CapabilityEnergy.ENERGY );
ComputerCraftAPI.registerGenericCapability( CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY );
}
public static void registerLoot()
{
LootConditionManager.register( ConstantLootConditionSerializer.of(
new ResourceLocation( ComputerCraft.MOD_ID, "block_named" ),
BlockNamedEntityLootCondition.class,
BlockNamedEntityLootCondition.INSTANCE
) );
LootConditionManager.register( ConstantLootConditionSerializer.of(
new ResourceLocation( ComputerCraft.MOD_ID, "player_creative" ),
PlayerCreativeLootCondition.class,
PlayerCreativeLootCondition.INSTANCE
) );
LootConditionManager.register( ConstantLootConditionSerializer.of(
new ResourceLocation( ComputerCraft.MOD_ID, "has_id" ),
HasComputerIdLootCondition.class,
HasComputerIdLootCondition.INSTANCE
) );
}
public static void setup()
{
IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();

View File

@@ -33,9 +33,9 @@ public final class TurtleUpgrades
Wrapper( ITurtleUpgrade upgrade )
{
this.upgrade = upgrade;
this.id = upgrade.getUpgradeID().toString();
this.modId = ModLoadingContext.get().getActiveNamespace();
this.enabled = true;
id = upgrade.getUpgradeID().toString();
modId = ModLoadingContext.get().getActiveNamespace();
enabled = true;
}
}

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.command;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.util.IDAssigner;
import net.minecraft.util.Util;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ClientChatEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.io.File;
/**
* Basic client-side commands.
*
* Simply hooks into client chat messages and intercepts matching strings.
*/
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT )
public final class ClientCommands
{
public static final String OPEN_COMPUTER = "/computercraft open-computer ";
private ClientCommands()
{
}
@SubscribeEvent
public static void onClientSendMessage( ClientChatEvent event )
{
// Emulate the command on the client side
if( event.getMessage().startsWith( OPEN_COMPUTER ) )
{
event.setCanceled( true );
String idStr = event.getMessage().substring( OPEN_COMPUTER.length() ).trim();
int id;
try
{
id = Integer.parseInt( idStr );
}
catch( NumberFormatException ignore )
{
return;
}
File file = new File( IDAssigner.getDir(), "computer/" + id );
if( !file.isDirectory() ) return;
Util.getPlatform().openFile( file );
}
}
}

View File

@@ -21,6 +21,7 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.network.container.ViewComputerContainerData;
import dan200.computercraft.shared.util.IDAssigner;
import net.minecraft.command.CommandSource;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
@@ -37,6 +38,7 @@ import net.minecraft.world.World;
import net.minecraft.world.server.ServerWorld;
import javax.annotation.Nonnull;
import java.io.File;
import java.util.*;
import static dan200.computercraft.shared.command.CommandUtils.isPlayer;
@@ -320,6 +322,12 @@ public final class CommandComputerCraft
) );
}
if( UserLevel.OWNER.test( source ) && isPlayer( source ) )
{
ITextComponent linkPath = linkStorage( computerId );
if( linkPath != null ) out.append( " " ).append( linkPath );
}
return out;
}
@@ -339,6 +347,18 @@ public final class CommandComputerCraft
}
}
private static ITextComponent linkStorage( int id )
{
File file = new File( IDAssigner.getDir(), "computer/" + id );
if( !file.isDirectory() ) return null;
return link(
text( "\u270E" ),
ClientCommands.OPEN_COMPUTER + id,
translate( "commands.computercraft.dump.open_path" )
);
}
@Nonnull
private static TrackingContext getTimingContext( CommandSource source )
{

View File

@@ -1,66 +0,0 @@
/*
* 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.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import dan200.computercraft.ComputerCraft;
import net.minecraft.client.Minecraft;
import net.minecraft.command.CommandSource;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.util.text.event.ClickEvent;
import net.minecraft.util.text.event.HoverEvent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ClientChatEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import static net.minecraft.command.Commands.argument;
import static net.minecraft.command.Commands.literal;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT )
public final class CommandCopy
{
private static final String PREFIX = "/computercraft copy ";
private CommandCopy()
{
}
public static void register( CommandDispatcher<CommandSource> registry )
{
registry.register( literal( "computercraft" )
.then( literal( "copy" ) )
.then( argument( "message", StringArgumentType.greedyString() ) )
.executes( context -> {
Minecraft.getInstance().keyboardHandler.setClipboard( context.getArgument( "message", String.class ) );
return 1;
} )
);
}
@SubscribeEvent
public static void onClientSendMessage( ClientChatEvent event )
{
// Emulate the command on the client side
if( event.getMessage().startsWith( PREFIX ) )
{
Minecraft.getInstance().keyboardHandler.setClipboard( event.getMessage().substring( PREFIX.length() ) );
event.setCanceled( true );
}
}
public static ITextComponent createCopyText( String text )
{
StringTextComponent name = new StringTextComponent( text );
name.getStyle()
.setClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, PREFIX + text ) )
.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new TranslationTextComponent( "gui.computercraft.tooltip.copy" ) ) );
return name;
}
}

View File

@@ -57,14 +57,15 @@ public enum UserLevel implements Predicate<CommandSource>
{
if( this == ANYONE ) return true;
// We *always* allow level 0 stuff, even if the
MinecraftServer server = source.getServer();
Entity sender = source.getEntity();
if( server.isSingleplayer() && sender instanceof PlayerEntity &&
((PlayerEntity) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerModName() ) )
if( this == OWNER || this == OWNER_OP )
{
if( this == OWNER || this == OWNER_OP ) return true;
MinecraftServer server = source.getServer();
Entity sender = source.getEntity();
if( server.isSingleplayer() && sender instanceof PlayerEntity &&
((PlayerEntity) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerModName() ) )
{
return true;
}
}
return source.hasPermission( toLevel() );

View File

@@ -71,11 +71,16 @@ public final class ChatHelpers
}
public static ITextComponent link( ITextComponent component, String command, ITextComponent toolTip )
{
return link( component, new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ), toolTip );
}
public static ITextComponent link( ITextComponent component, ClickEvent click, ITextComponent toolTip )
{
Style style = component.getStyle();
if( style.getColor() == null ) style.setColor( TextFormatting.YELLOW );
style.setClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ) );
style.setClickEvent( click );
style.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, toolTip ) );
return component;
@@ -85,4 +90,13 @@ public final class ChatHelpers
{
return coloured( text, HEADER );
}
public static ITextComponent copy( String text )
{
StringTextComponent name = new StringTextComponent( text );
name.getStyle()
.setClickEvent( new ClickEvent( ClickEvent.Action.COPY_TO_CLIPBOARD, text ) )
.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new TranslationTextComponent( "gui.computercraft.tooltip.copy" ) ) );
return name;
}
}

View File

@@ -61,7 +61,7 @@ public class ContainerHeldItem extends Container
public Factory( ContainerType<ContainerHeldItem> type, ItemStack stack, Hand hand )
{
this.type = type;
this.name = stack.getHoverName();
name = stack.getHoverName();
this.hand = hand;
}

View File

@@ -56,7 +56,7 @@ public class TileCommandComputer extends TileComputer
@Override
public boolean acceptsSuccess()
{
return getLevel().getGameRules().getBoolean( GameRules.RULE_SENDCOMMANDFEEDBACK );
return true;
}
@Override

View File

@@ -175,12 +175,12 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
label = computer.getLabel();
on = computer.isOn();
if( computer.hasOutputChanged() ) updateOutput();
// Update the block state if needed. We don't fire a block update intentionally,
// as this only really is needed on the client side.
updateBlockState( computer.getState() );
// TODO: This should ideally be split up into label/id/on (which should save NBT and sync to client) and
// redstone (which should update outputs)
if( computer.hasOutputChanged() ) updateOutput();
}
}
@@ -376,11 +376,8 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
fresh = true;
changed = true;
}
if( changed )
{
updateBlock();
updateInput();
}
if( changed ) updateInput();
return ComputerCraft.serverComputerRegistry.get( instanceID );
}

View File

@@ -25,14 +25,14 @@ public class ContainerViewComputer extends ContainerComputerBase implements ICon
public ContainerViewComputer( int id, ServerComputer computer )
{
super( Registry.ModContainers.VIEW_COMPUTER.get(), id, player -> canInteractWith( computer, player ), computer, computer.getFamily() );
this.width = this.height = 0;
width = height = 0;
}
public ContainerViewComputer( int id, PlayerInventory player, ViewComputerContainerData data )
{
super( Registry.ModContainers.VIEW_COMPUTER.get(), id, player, data );
this.width = data.getWidth();
this.height = data.getHeight();
width = data.getWidth();
height = data.getHeight();
}
private static boolean canInteractWith( @Nonnull ServerComputer computer, @Nonnull PlayerEntity player )

View File

@@ -99,6 +99,7 @@ public class AddTurtleTool implements IUndoableAction
return String.format( "Removing turtle upgrade %s.", id );
}
@Override
public boolean validate( ILogger logger )
{
TrackingLogger trackLog = new TrackingLogger( logger );

View File

@@ -41,7 +41,7 @@ public class RemoveTurtleUpgradeByItem implements IUndoableAction
@Override
public void undo()
{
if( this.upgrade != null ) TurtleUpgrades.enable( upgrade );
if( upgrade != null ) TurtleUpgrades.enable( upgrade );
}
@Override

View File

@@ -40,7 +40,7 @@ public class RemoveTurtleUpgradeByName implements IUndoableAction
@Override
public void undo()
{
if( this.upgrade != null ) TurtleUpgrades.enable( upgrade );
if( upgrade != null ) TurtleUpgrades.enable( upgrade );
}
@Override

View File

@@ -339,17 +339,17 @@ class RecipeResolver implements IRecipeManagerPlugin
UpgradeInfo( ItemStack stack, ITurtleUpgrade turtle )
{
this.stack = stack;
this.ingredient = of( stack );
this.upgrade = this.turtle = turtle;
this.pocket = null;
ingredient = of( stack );
upgrade = this.turtle = turtle;
pocket = null;
}
UpgradeInfo( ItemStack stack, IPocketUpgrade pocket )
{
this.stack = stack;
this.ingredient = of( stack );
this.turtle = null;
this.upgrade = this.pocket = pocket;
ingredient = of( stack );
turtle = null;
upgrade = this.pocket = pocket;
}
List<Shaped> getRecipes()

View File

@@ -68,6 +68,8 @@ public class ItemTreasureDisk extends Item implements IMedia
public IMount createDataMount( @Nonnull ItemStack stack, @Nonnull World world )
{
IMount rootTreasure = getTreasureMount();
if( rootTreasure == null ) return null;
String subPath = getSubPath( stack );
try
{
@@ -121,7 +123,7 @@ public class ItemTreasureDisk extends Item implements IMedia
private static String getTitle( @Nonnull ItemStack stack )
{
CompoundNBT nbt = stack.getTag();
return nbt != null && nbt.contains( NBT_TITLE ) ? nbt.getString( NBT_TITLE ) : "'alongtimeago' by dan200";
return nbt != null && nbt.contains( NBT_TITLE ) ? nbt.getString( NBT_TITLE ) : "'missingno' by how did you get this anyway?";
}
@Nonnull

View File

@@ -43,19 +43,22 @@ public final class NetworkHandler
.simpleChannel();
// Server messages
registerMainThread( 0, NetworkDirection.PLAY_TO_SERVER, ComputerActionServerMessage::new );
registerMainThread( 1, NetworkDirection.PLAY_TO_SERVER, QueueEventServerMessage::new );
registerMainThread( 2, NetworkDirection.PLAY_TO_SERVER, RequestComputerMessage::new );
registerMainThread( 3, NetworkDirection.PLAY_TO_SERVER, KeyEventServerMessage::new );
registerMainThread( 4, NetworkDirection.PLAY_TO_SERVER, MouseEventServerMessage::new );
registerMainThread( 0, NetworkDirection.PLAY_TO_SERVER, ComputerActionServerMessage.class, ComputerActionServerMessage::new );
registerMainThread( 1, NetworkDirection.PLAY_TO_SERVER, QueueEventServerMessage.class, QueueEventServerMessage::new );
registerMainThread( 2, NetworkDirection.PLAY_TO_SERVER, RequestComputerMessage.class, RequestComputerMessage::new );
registerMainThread( 3, NetworkDirection.PLAY_TO_SERVER, KeyEventServerMessage.class, KeyEventServerMessage::new );
registerMainThread( 4, NetworkDirection.PLAY_TO_SERVER, MouseEventServerMessage.class, MouseEventServerMessage::new );
// Client messages
registerMainThread( 10, NetworkDirection.PLAY_TO_CLIENT, ChatTableClientMessage::new );
registerMainThread( 11, NetworkDirection.PLAY_TO_CLIENT, ComputerDataClientMessage::new );
registerMainThread( 12, NetworkDirection.PLAY_TO_CLIENT, ComputerDeletedClientMessage::new );
registerMainThread( 13, NetworkDirection.PLAY_TO_CLIENT, ComputerTerminalClientMessage::new );
registerMainThread( 10, NetworkDirection.PLAY_TO_CLIENT, ChatTableClientMessage.class, ChatTableClientMessage::new );
registerMainThread( 11, NetworkDirection.PLAY_TO_CLIENT, ComputerDataClientMessage.class, ComputerDataClientMessage::new );
registerMainThread( 12, NetworkDirection.PLAY_TO_CLIENT, ComputerDeletedClientMessage.class, ComputerDeletedClientMessage::new );
registerMainThread( 13, NetworkDirection.PLAY_TO_CLIENT, ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new );
registerMainThread( 14, NetworkDirection.PLAY_TO_CLIENT, PlayRecordClientMessage.class, PlayRecordClientMessage::new );
registerMainThread( 15, NetworkDirection.PLAY_TO_CLIENT, MonitorClientMessage.class, MonitorClientMessage::new );
registerMainThread( 16, NetworkDirection.PLAY_TO_CLIENT, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new );
registerMainThread( 17, NetworkDirection.PLAY_TO_CLIENT, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new );
registerMainThread( 18, NetworkDirection.PLAY_TO_CLIENT, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new );
}
public static void sendToPlayer( PlayerEntity player, NetworkMessage packet )
@@ -87,24 +90,6 @@ public final class NetworkHandler
network.send( PacketDistributor.TRACKING_CHUNK.with( () -> chunk ), packet );
}
/**
* /**
* Register packet, and a thread-unsafe handler for it.
*
* @param <T> The type of the packet to send.
* @param id The identifier for this packet type.
* @param direction A network direction which will be asserted before any processing of this message occurs.
* @param factory The factory for this type of packet.
*/
private static <T extends NetworkMessage> void registerMainThread( int id, NetworkDirection direction, Supplier<T> factory )
{
registerMainThread( id, direction, getType( factory ), buf -> {
T instance = factory.get();
instance.fromBytes( buf );
return instance;
} );
}
/**
* /**
* Register packet, and a thread-unsafe handler for it.

View File

@@ -27,18 +27,6 @@ public interface NetworkMessage
*/
void toBytes( @Nonnull PacketBuffer buf );
/**
* Read this packet from a buffer.
*
* This may be called on any thread, so this should be a pure operation.
*
* @param buf The buffer to read data from.
*/
default void fromBytes( @Nonnull PacketBuffer buf )
{
throw new IllegalStateException( "Should have been registered using a \"from bytes\" method" );
}
/**
* Handle this {@link NetworkMessage}.
*

View File

@@ -16,7 +16,7 @@ import javax.annotation.Nonnull;
public class ChatTableClientMessage implements NetworkMessage
{
private TableBuilder table;
private final TableBuilder table;
public ChatTableClientMessage( TableBuilder table )
{
@@ -24,32 +24,7 @@ public class ChatTableClientMessage implements NetworkMessage
this.table = table;
}
public ChatTableClientMessage()
{
}
@Override
public void toBytes( @Nonnull PacketBuffer buf )
{
buf.writeVarInt( table.getId() );
buf.writeVarInt( table.getColumns() );
buf.writeBoolean( table.getHeaders() != null );
if( table.getHeaders() != null )
{
for( ITextComponent header : table.getHeaders() ) buf.writeComponent( header );
}
buf.writeVarInt( table.getRows().size() );
for( ITextComponent[] row : table.getRows() )
{
for( ITextComponent column : row ) buf.writeComponent( column );
}
buf.writeVarInt( table.getAdditional() );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
public ChatTableClientMessage( @Nonnull PacketBuffer buf )
{
int id = buf.readVarInt();
int columns = buf.readVarInt();
@@ -77,6 +52,26 @@ public class ChatTableClientMessage implements NetworkMessage
this.table = table;
}
@Override
public void toBytes( @Nonnull PacketBuffer buf )
{
buf.writeVarInt( table.getId() );
buf.writeVarInt( table.getColumns() );
buf.writeBoolean( table.getHeaders() != null );
if( table.getHeaders() != null )
{
for( ITextComponent header : table.getHeaders() ) buf.writeComponent( header );
}
buf.writeVarInt( table.getRows().size() );
for( ITextComponent[] row : table.getRows() )
{
for( ITextComponent column : row ) buf.writeComponent( column );
}
buf.writeVarInt( table.getAdditional() );
}
@Override
public void handle( NetworkEvent.Context context )
{

View File

@@ -17,15 +17,16 @@ import javax.annotation.Nonnull;
*/
public abstract class ComputerClientMessage implements NetworkMessage
{
private int instanceId;
private final int instanceId;
public ComputerClientMessage( int instanceId )
{
this.instanceId = instanceId;
}
public ComputerClientMessage()
public ComputerClientMessage( @Nonnull PacketBuffer buf )
{
instanceId = buf.readVarInt();
}
public int getInstanceId()
@@ -39,12 +40,6 @@ public abstract class ComputerClientMessage implements NetworkMessage
buf.writeVarInt( instanceId );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
{
instanceId = buf.readVarInt();
}
public ClientComputer getComputer()
{
ClientComputer computer = ComputerCraft.clientComputerRegistry.get( instanceId );

View File

@@ -18,8 +18,8 @@ import javax.annotation.Nonnull;
*/
public class ComputerDataClientMessage extends ComputerClientMessage
{
private ComputerState state;
private CompoundNBT userData;
private final ComputerState state;
private final CompoundNBT userData;
public ComputerDataClientMessage( ServerComputer computer )
{
@@ -28,8 +28,11 @@ public class ComputerDataClientMessage extends ComputerClientMessage
userData = computer.getUserData();
}
public ComputerDataClientMessage()
public ComputerDataClientMessage( @Nonnull PacketBuffer buf )
{
super( buf );
state = buf.readEnum( ComputerState.class );
userData = buf.readNbt();
}
@Override
@@ -40,14 +43,6 @@ public class ComputerDataClientMessage extends ComputerClientMessage
buf.writeNbt( userData );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
{
super.fromBytes( buf );
state = buf.readEnum( ComputerState.class );
userData = buf.readNbt();
}
@Override
public void handle( NetworkEvent.Context context )
{

View File

@@ -6,6 +6,7 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.ComputerCraft;
import net.minecraft.network.PacketBuffer;
import net.minecraftforge.fml.network.NetworkEvent;
public class ComputerDeletedClientMessage extends ComputerClientMessage
@@ -15,8 +16,9 @@ public class ComputerDeletedClientMessage extends ComputerClientMessage
super( instanceId );
}
public ComputerDeletedClientMessage()
public ComputerDeletedClientMessage( PacketBuffer buffer )
{
super( buffer );
}
@Override

View File

@@ -12,7 +12,7 @@ import javax.annotation.Nonnull;
public class ComputerTerminalClientMessage extends ComputerClientMessage
{
private TerminalState state;
private final TerminalState state;
public ComputerTerminalClientMessage( int instanceId, TerminalState state )
{
@@ -20,8 +20,10 @@ public class ComputerTerminalClientMessage extends ComputerClientMessage
this.state = state;
}
public ComputerTerminalClientMessage()
public ComputerTerminalClientMessage( @Nonnull PacketBuffer buf )
{
super( buf );
state = new TerminalState( buf );
}
@Override
@@ -31,13 +33,6 @@ public class ComputerTerminalClientMessage extends ComputerClientMessage
state.write( buf );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
{
super.fromBytes( buf );
state = new TerminalState( buf );
}
@Override
public void handle( NetworkEvent.Context context )
{

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.network.client;
import dan200.computercraft.client.SoundManager;
import dan200.computercraft.shared.network.NetworkMessage;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.math.Vec3d;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.network.NetworkEvent;
import javax.annotation.Nonnull;
import java.util.UUID;
/**
* Starts a sound on the client.
*
* Used by speakers to play sounds.
*
* @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker
*/
public class SpeakerMoveClientMessage implements NetworkMessage
{
private final UUID source;
private final Vec3d pos;
public SpeakerMoveClientMessage( UUID source, Vec3d pos )
{
this.source = source;
this.pos = pos;
}
public SpeakerMoveClientMessage( PacketBuffer buf )
{
source = buf.readUUID();
pos = new Vec3d( buf.readDouble(), buf.readDouble(), buf.readDouble() );
}
@Override
public void toBytes( @Nonnull PacketBuffer buf )
{
buf.writeUUID( source );
buf.writeDouble( pos.x() );
buf.writeDouble( pos.y() );
buf.writeDouble( pos.z() );
}
@Override
@OnlyIn( Dist.CLIENT )
public void handle( NetworkEvent.Context context )
{
SoundManager.moveSound( source, pos );
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.network.client;
import dan200.computercraft.client.SoundManager;
import dan200.computercraft.shared.network.NetworkMessage;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundEvent;
import net.minecraft.util.math.Vec3d;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.network.NetworkEvent;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nonnull;
import java.util.UUID;
/**
* Starts a sound on the client.
*
* Used by speakers to play sounds.
*
* @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker
*/
public class SpeakerPlayClientMessage implements NetworkMessage
{
private final UUID source;
private final Vec3d pos;
private final ResourceLocation sound;
private final float volume;
private final float pitch;
public SpeakerPlayClientMessage( UUID source, Vec3d pos, ResourceLocation event, float volume, float pitch )
{
this.source = source;
this.pos = pos;
sound = event;
this.volume = volume;
this.pitch = pitch;
}
public SpeakerPlayClientMessage( PacketBuffer buf )
{
source = buf.readUUID();
pos = new Vec3d( buf.readDouble(), buf.readDouble(), buf.readDouble() );
sound = buf.readResourceLocation();
volume = buf.readFloat();
pitch = buf.readFloat();
}
@Override
public void toBytes( @Nonnull PacketBuffer buf )
{
buf.writeUUID( source );
buf.writeDouble( pos.x() );
buf.writeDouble( pos.y() );
buf.writeDouble( pos.z() );
buf.writeResourceLocation( sound );
buf.writeFloat( volume );
buf.writeFloat( pitch );
}
@Override
@OnlyIn( Dist.CLIENT )
public void handle( NetworkEvent.Context context )
{
SoundEvent sound = ForgeRegistries.SOUND_EVENTS.getValue( this.sound );
if( sound != null ) SoundManager.playSound( source, pos, sound, volume, pitch );
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.network.client;
import dan200.computercraft.client.SoundManager;
import dan200.computercraft.shared.network.NetworkMessage;
import net.minecraft.network.PacketBuffer;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.network.NetworkEvent;
import javax.annotation.Nonnull;
import java.util.UUID;
/**
* Stops a sound on the client
*
* Called when a speaker is broken.
*
* @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker
*/
public class SpeakerStopClientMessage implements NetworkMessage
{
private final UUID source;
public SpeakerStopClientMessage( UUID source )
{
this.source = source;
}
public SpeakerStopClientMessage( PacketBuffer buf )
{
source = buf.readUUID();
}
@Override
public void toBytes( @Nonnull PacketBuffer buf )
{
buf.writeUUID( source );
}
@Override
@OnlyIn( Dist.CLIENT )
public void handle( NetworkEvent.Context context )
{
SoundManager.stopSound( source );
}
}

View File

@@ -54,36 +54,36 @@ public class TerminalState
if( terminal == null )
{
this.width = this.height = 0;
this.buffer = null;
width = height = 0;
buffer = null;
}
else
{
this.width = terminal.getWidth();
this.height = terminal.getHeight();
width = terminal.getWidth();
height = terminal.getHeight();
ByteBuf buf = this.buffer = Unpooled.buffer();
ByteBuf buf = buffer = Unpooled.buffer();
terminal.write( new PacketBuffer( buf ) );
}
}
public TerminalState( PacketBuffer buf )
{
this.colour = buf.readBoolean();
this.compress = buf.readBoolean();
colour = buf.readBoolean();
compress = buf.readBoolean();
if( buf.readBoolean() )
{
this.width = buf.readVarInt();
this.height = buf.readVarInt();
width = buf.readVarInt();
height = buf.readVarInt();
int length = buf.readVarInt();
this.buffer = readCompressed( buf, length, compress );
buffer = readCompressed( buf, length, compress );
}
else
{
this.width = this.height = 0;
this.buffer = null;
width = height = 0;
buffer = null;
}
}

View File

@@ -16,14 +16,14 @@ public class ComputerContainerData implements ContainerData
public ComputerContainerData( ServerComputer computer )
{
this.id = computer.getInstanceID();
this.family = computer.getFamily();
id = computer.getInstanceID();
family = computer.getFamily();
}
public ComputerContainerData( PacketBuffer buf )
{
this.id = buf.readInt();
this.family = buf.readEnum( ComputerFamily.class );
id = buf.readInt();
family = buf.readEnum( ComputerFamily.class );
}
@Override

View File

@@ -13,7 +13,7 @@ import javax.annotation.Nonnull;
public class ComputerActionServerMessage extends ComputerServerMessage
{
private Action action;
private final Action action;
public ComputerActionServerMessage( int instanceId, Action action )
{
@@ -21,8 +21,10 @@ public class ComputerActionServerMessage extends ComputerServerMessage
this.action = action;
}
public ComputerActionServerMessage()
public ComputerActionServerMessage( @Nonnull PacketBuffer buf )
{
super( buf );
action = buf.readEnum( Action.class );
}
@Override
@@ -32,13 +34,6 @@ public class ComputerActionServerMessage extends ComputerServerMessage
buf.writeEnum( action );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
{
super.fromBytes( buf );
action = buf.readEnum( Action.class );
}
@Override
protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
{

View File

@@ -22,15 +22,16 @@ import javax.annotation.Nonnull;
*/
public abstract class ComputerServerMessage implements NetworkMessage
{
private int instanceId;
private final int instanceId;
public ComputerServerMessage( int instanceId )
{
this.instanceId = instanceId;
}
public ComputerServerMessage()
public ComputerServerMessage( @Nonnull PacketBuffer buf )
{
instanceId = buf.readVarInt();
}
@Override
@@ -39,12 +40,6 @@ public abstract class ComputerServerMessage implements NetworkMessage
buf.writeVarInt( instanceId );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
{
instanceId = buf.readVarInt();
}
@Override
public void handle( NetworkEvent.Context context )
{

View File

@@ -18,8 +18,8 @@ public class KeyEventServerMessage extends ComputerServerMessage
public static final int TYPE_REPEAT = 1;
public static final int TYPE_UP = 2;
private int type;
private int key;
private final int type;
private final int key;
public KeyEventServerMessage( int instanceId, int type, int key )
{
@@ -28,8 +28,11 @@ public class KeyEventServerMessage extends ComputerServerMessage
this.key = key;
}
public KeyEventServerMessage()
public KeyEventServerMessage( @Nonnull PacketBuffer buf )
{
super( buf );
type = buf.readByte();
key = buf.readVarInt();
}
@Override
@@ -40,14 +43,6 @@ public class KeyEventServerMessage extends ComputerServerMessage
buf.writeVarInt( key );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
{
super.fromBytes( buf );
type = buf.readByte();
key = buf.readVarInt();
}
@Override
protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
{

View File

@@ -19,10 +19,10 @@ public class MouseEventServerMessage extends ComputerServerMessage
public static final int TYPE_UP = 2;
public static final int TYPE_SCROLL = 3;
private int type;
private int x;
private int y;
private int arg;
private final int type;
private final int x;
private final int y;
private final int arg;
public MouseEventServerMessage( int instanceId, int type, int arg, int x, int y )
{
@@ -33,8 +33,13 @@ public class MouseEventServerMessage extends ComputerServerMessage
this.y = y;
}
public MouseEventServerMessage()
public MouseEventServerMessage( @Nonnull PacketBuffer buf )
{
super( buf );
type = buf.readByte();
arg = buf.readVarInt();
x = buf.readVarInt();
y = buf.readVarInt();
}
@Override
@@ -47,16 +52,6 @@ public class MouseEventServerMessage extends ComputerServerMessage
buf.writeVarInt( y );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
{
super.fromBytes( buf );
type = buf.readByte();
arg = buf.readVarInt();
x = buf.readVarInt();
y = buf.readVarInt();
}
@Override
protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
{

View File

@@ -22,8 +22,8 @@ import javax.annotation.Nullable;
*/
public class QueueEventServerMessage extends ComputerServerMessage
{
private String event;
private Object[] args;
private final String event;
private final Object[] args;
public QueueEventServerMessage( int instanceId, @Nonnull String event, @Nullable Object[] args )
{
@@ -32,8 +32,13 @@ public class QueueEventServerMessage extends ComputerServerMessage
this.args = args;
}
public QueueEventServerMessage()
public QueueEventServerMessage( @Nonnull PacketBuffer buf )
{
super( buf );
event = buf.readUtf( Short.MAX_VALUE );
CompoundNBT args = buf.readNbt();
this.args = args == null ? null : NBTUtil.decodeObjects( args );
}
@Override
@@ -44,16 +49,6 @@ public class QueueEventServerMessage extends ComputerServerMessage
buf.writeNbt( args == null ? null : NBTUtil.encodeObjects( args ) );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
{
super.fromBytes( buf );
event = buf.readUtf( Short.MAX_VALUE );
CompoundNBT args = buf.readNbt();
this.args = args == null ? null : NBTUtil.decodeObjects( args );
}
@Override
protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container )
{

View File

@@ -15,15 +15,16 @@ import javax.annotation.Nonnull;
public class RequestComputerMessage implements NetworkMessage
{
private int instance;
private final int instance;
public RequestComputerMessage( int instance )
{
this.instance = instance;
}
public RequestComputerMessage()
public RequestComputerMessage( @Nonnull PacketBuffer buf )
{
instance = buf.readVarInt();
}
@Override
@@ -32,12 +33,6 @@ public class RequestComputerMessage implements NetworkMessage
buf.writeVarInt( instance );
}
@Override
public void fromBytes( @Nonnull PacketBuffer buf )
{
instance = buf.readVarInt();
}
@Override
public void handle( NetworkEvent.Context context )
{

View File

@@ -24,7 +24,7 @@ final class SaturatedMethod
SaturatedMethod( Object target, NamedMethod<PeripheralMethod> method )
{
this.target = target;
this.name = method.getName();
name = method.getName();
this.method = method.getMethod();
}

View File

@@ -11,7 +11,7 @@ import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.command.CommandCopy;
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.util.CapabilityUtil;
@@ -53,7 +53,7 @@ public class TileCable extends TileGeneric
@Override
public World getWorld()
{
return TileCable.this.getLevel();
return getLevel();
}
@Nonnull
@@ -268,12 +268,12 @@ public class TileCable extends TileGeneric
if( oldName != null )
{
player.displayClientMessage( new TranslationTextComponent( "chat.computercraft.wired_modem.peripheral_disconnected",
CommandCopy.createCopyText( oldName ) ), false );
ChatHelpers.copy( oldName ) ), false );
}
if( newName != null )
{
player.displayClientMessage( new TranslationTextComponent( "chat.computercraft.wired_modem.peripheral_connected",
CommandCopy.createCopyText( newName ) ), false );
ChatHelpers.copy( newName ) ), false );
}
}

View File

@@ -10,7 +10,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.command.CommandCopy;
import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.util.CapabilityUtil;
@@ -218,7 +218,7 @@ public class TileWiredModemFull extends TileGeneric
for( int i = 0; i < names.size(); i++ )
{
if( i > 0 ) base.append( ", " );
base.append( CommandCopy.createCopyText( names.get( i ) ) );
base.append( ChatHelpers.copy( names.get( i ) ) );
}
player.displayClientMessage( new TranslationTextComponent( kind, base ), false );

View File

@@ -87,7 +87,7 @@ public class BlockMonitor extends BlockGeneric
{
TileMonitor monitor = (TileMonitor) entity;
// Defer the block update if we're being placed by another TE. See #691
if ( livingEntity == null || livingEntity instanceof FakePlayer )
if( livingEntity == null || livingEntity instanceof FakePlayer )
{
monitor.updateNeighborsDeferred();
return;

View File

@@ -148,7 +148,7 @@ public class TileMonitor extends TileGeneric
@Override
public void blockTick()
{
if ( needsUpdate )
if( needsUpdate )
{
needsUpdate = false;
updateNeighbors();
@@ -278,8 +278,6 @@ public class TileMonitor extends TileGeneric
int oldXIndex = xIndex;
int oldYIndex = yIndex;
int oldWidth = width;
int oldHeight = height;
xIndex = nbt.getInt( NBT_X );
yIndex = nbt.getInt( NBT_Y );
@@ -299,13 +297,6 @@ public class TileMonitor extends TileGeneric
// If we're the origin terminal then create it.
if( clientMonitor == null ) clientMonitor = new ClientMonitor( advanced, this );
}
if( oldXIndex != xIndex || oldYIndex != yIndex ||
oldWidth != width || oldHeight != height )
{
// One of our properties has changed, so ensure we redraw the block
updateBlock();
}
}
public final void read( TerminalState state )

View File

@@ -10,17 +10,23 @@ import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.SpeakerMoveClientMessage;
import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage;
import net.minecraft.network.play.server.SPlaySoundPacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.state.properties.NoteBlockInstrument;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.ResourceLocationException;
import net.minecraft.util.SoundCategory;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import javax.annotation.Nonnull;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import static dan200.computercraft.api.lua.LuaValues.checkFinite;
@@ -32,20 +38,44 @@ import static dan200.computercraft.api.lua.LuaValues.checkFinite;
*/
public abstract class SpeakerPeripheral implements IPeripheral
{
private static final int MIN_TICKS_BETWEEN_SOUNDS = 1;
private long clock = 0;
private long lastPlayTime = 0;
private final AtomicInteger notesThisTick = new AtomicInteger();
private long lastPositionTime;
private Vec3d lastPosition;
public void update()
{
clock++;
notesThisTick.set( 0 );
// Push position updates to any speakers which have ever played a note,
// have moved by a non-trivial amount and haven't had a position update
// in the last second.
if( lastPlayTime > 0 && (clock - lastPositionTime) >= 20 )
{
Vec3d position = getPosition();
if( lastPosition == null || lastPosition.distanceToSqr( position ) >= 0.1 )
{
lastPosition = position;
lastPositionTime = clock;
NetworkHandler.sendToAllTracking(
new SpeakerMoveClientMessage( getSource(), position ),
getWorld().getChunkAt( new BlockPos( position ) )
);
}
}
}
public abstract World getWorld();
public abstract Vec3d getPosition();
protected abstract UUID getSource();
public boolean madeSound( long ticks )
{
return clock - lastPlayTime <= ticks;
@@ -135,26 +165,37 @@ public abstract class SpeakerPeripheral implements IPeripheral
private synchronized boolean playSound( ILuaContext context, ResourceLocation name, float volume, float pitch, boolean isNote ) throws LuaException
{
if( clock - lastPlayTime < TileSpeaker.MIN_TICKS_BETWEEN_SOUNDS &&
(!isNote || clock - lastPlayTime != 0 || notesThisTick.get() >= ComputerCraft.maxNotesPerTick) )
if( clock - lastPlayTime < MIN_TICKS_BETWEEN_SOUNDS )
{
// Rate limiting occurs when we've already played a sound within the last tick, or we've
// played more notes than allowable within the current tick.
return false;
// Rate limiting occurs when we've already played a sound within the last tick.
if( !isNote ) return false;
// Or we've played more notes than allowable within the current tick.
if( clock - lastPlayTime != 0 || notesThisTick.get() >= ComputerCraft.maxNotesPerTick ) return false;
}
World world = getWorld();
Vec3d pos = getPosition();
float range = MathHelper.clamp( volume, 1.0f, 3.0f ) * 16;
context.issueMainThreadTask( () -> {
MinecraftServer server = world.getServer();
if( server == null ) return null;
float adjVolume = Math.min( volume, 3.0f );
server.getPlayerList().broadcast(
null, pos.x, pos.y, pos.z, adjVolume > 1.0f ? 16 * adjVolume : 16.0, world.dimension.getType(),
new SPlaySoundPacket( name, SoundCategory.RECORDS, pos, adjVolume, pitch )
);
if( isNote )
{
server.getPlayerList().broadcast(
null, pos.x, pos.y, pos.z, range, world.dimension.getType(),
new SPlaySoundPacket( name, SoundCategory.RECORDS, pos, range, pitch )
);
}
else
{
NetworkHandler.sendToAllAround(
new SpeakerPlayClientMessage( getSource(), pos, name, range, pitch ),
world, pos, range
);
}
return null;
} );

View File

@@ -7,6 +7,8 @@ package dan200.computercraft.shared.peripheral.speaker;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
import dan200.computercraft.shared.util.CapabilityUtil;
import net.minecraft.tileentity.ITickableTileEntity;
import net.minecraft.tileentity.TileEntityType;
@@ -19,15 +21,15 @@ import net.minecraftforge.common.util.LazyOptional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.UUID;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
public class TileSpeaker extends TileGeneric implements ITickableTileEntity
{
public static final int MIN_TICKS_BETWEEN_SOUNDS = 1;
private final SpeakerPeripheral peripheral;
private LazyOptional<IPeripheral> peripheralCap;
private final UUID source = UUID.randomUUID();
public TileSpeaker( TileEntityType<TileSpeaker> type )
{
@@ -41,6 +43,13 @@ public class TileSpeaker extends TileGeneric implements ITickableTileEntity
peripheral.update();
}
@Override
public void setRemoved()
{
super.setRemoved();
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) );
}
@Nonnull
@Override
public <T> LazyOptional<T> getCapability( @Nonnull Capability<T> cap, @Nullable Direction side )
@@ -83,6 +92,12 @@ public class TileSpeaker extends TileGeneric implements ITickableTileEntity
return new Vec3d( pos.getX(), pos.getY(), pos.getZ() );
}
@Override
protected UUID getSource()
{
return speaker.source;
}
@Override
public boolean equals( @Nullable IPeripheral other )
{

View File

@@ -0,0 +1,33 @@
/*
* 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.speaker;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
import javax.annotation.Nonnull;
import java.util.UUID;
/**
* A speaker peripheral which is used on an upgrade, and so is only attached to one computer.
*/
public abstract class UpgradeSpeakerPeripheral extends SpeakerPeripheral
{
private final UUID source = UUID.randomUUID();
@Override
protected final UUID getSource()
{
return source;
}
@Override
public void detach( @Nonnull IComputerAccess computer )
{
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) );
}
}

View File

@@ -46,7 +46,7 @@ public final class ContainerPocketComputer extends ContainerComputerBase
public Factory( ServerComputer computer, ItemStack stack, ItemPocketComputer item, Hand hand )
{
this.computer = computer;
this.name = stack.getHoverName();
name = stack.getHoverName();
this.item = item;
this.hand = hand;
}

View File

@@ -6,11 +6,11 @@
package dan200.computercraft.shared.pocket.peripherals;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
public class PocketSpeakerPeripheral extends SpeakerPeripheral
public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral
{
private World world = null;
private Vec3d position = Vec3d.ZERO;

View File

@@ -1,221 +0,0 @@
/*
* 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.proxy;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.command.arguments.ArgumentSerializers;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.computer.core.IComputer;
import dan200.computercraft.shared.computer.core.IContainerComputer;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.ConstantLootConditionSerializer;
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.media.items.RecordMedia;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.peripheral.generic.methods.EnergyMethods;
import dan200.computercraft.shared.peripheral.generic.methods.FluidMethods;
import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import dan200.computercraft.shared.util.NullStorage;
import net.minecraft.inventory.container.Container;
import net.minecraft.item.Item;
import net.minecraft.item.MusicDiscItem;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.storage.loot.ConstantRange;
import net.minecraft.world.storage.loot.LootPool;
import net.minecraft.world.storage.loot.LootTables;
import net.minecraft.world.storage.loot.TableLootEntry;
import net.minecraft.world.storage.loot.conditions.LootConditionManager;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.event.LootTableLoadEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fml.DeferredWorkQueue;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
import net.minecraftforge.items.CapabilityItemHandler;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD )
public final class ComputerCraftProxyCommon
{
@SubscribeEvent
@SuppressWarnings( "deprecation" )
public static void init( FMLCommonSetupEvent event )
{
NetworkHandler.setup();
DeferredWorkQueue.runLater( () -> {
registerProviders();
ArgumentSerializers.register();
registerLoot();
} );
ComputerCraftAPI.registerGenericSource( new InventoryMethods() );
ComputerCraftAPI.registerGenericSource( new FluidMethods() );
ComputerCraftAPI.registerGenericSource( new EnergyMethods() );
}
public static void registerLoot()
{
LootConditionManager.register( ConstantLootConditionSerializer.of(
new ResourceLocation( ComputerCraft.MOD_ID, "block_named" ),
BlockNamedEntityLootCondition.class,
BlockNamedEntityLootCondition.INSTANCE
) );
LootConditionManager.register( ConstantLootConditionSerializer.of(
new ResourceLocation( ComputerCraft.MOD_ID, "player_creative" ),
PlayerCreativeLootCondition.class,
PlayerCreativeLootCondition.INSTANCE
) );
LootConditionManager.register( ConstantLootConditionSerializer.of(
new ResourceLocation( ComputerCraft.MOD_ID, "has_id" ),
HasComputerIdLootCondition.class,
HasComputerIdLootCondition.INSTANCE
) );
}
private static void registerProviders()
{
// Register bundled power providers
ComputerCraftAPI.registerBundledRedstoneProvider( new DefaultBundledRedstoneProvider() );
// Register media providers
ComputerCraftAPI.registerMediaProvider( stack -> {
Item item = stack.getItem();
if( item instanceof IMedia ) return (IMedia) item;
if( item instanceof MusicDiscItem ) return RecordMedia.INSTANCE;
return null;
} );
// Register capabilities
CapabilityManager.INSTANCE.register( IWiredElement.class, new NullStorage<>(), () -> null );
CapabilityManager.INSTANCE.register( IPeripheral.class, new NullStorage<>(), () -> null );
// Register generic capabilities. This can technically be done off-thread, but we need it to happen
// after Forge's common setup, so this is easiest.
ComputerCraftAPI.registerGenericCapability( CapabilityItemHandler.ITEM_HANDLER_CAPABILITY );
ComputerCraftAPI.registerGenericCapability( CapabilityEnergy.ENERGY );
ComputerCraftAPI.registerGenericCapability( CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY );
}
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID )
public static final class ForgeHandlers
{
private ForgeHandlers()
{
}
/*
@SubscribeEvent
public static void onConnectionOpened( FMLNetworkEvent.ClientConnectedToServerEvent event )
{
ComputerCraft.clientComputerRegistry.reset();
}
@SubscribeEvent
public static void onConnectionClosed( FMLNetworkEvent.ClientDisconnectionFromServerEvent event )
{
ComputerCraft.clientComputerRegistry.reset();
}
*/
@SubscribeEvent
public static void onServerTick( TickEvent.ServerTickEvent event )
{
if( event.phase == TickEvent.Phase.START )
{
MainThread.executePendingTasks();
ComputerCraft.serverComputerRegistry.update();
}
}
@SubscribeEvent
public static void onContainerOpen( PlayerContainerEvent.Open event )
{
// If we're opening a computer container then broadcast the terminal state
Container container = event.getContainer();
if( container instanceof IContainerComputer )
{
IComputer computer = ((IContainerComputer) container).getComputer();
if( computer instanceof ServerComputer )
{
((ServerComputer) computer).sendTerminalState( event.getPlayer() );
}
}
}
@SubscribeEvent
public static void onServerStarting( FMLServerStartingEvent event )
{
CommandComputerCraft.register( event.getCommandDispatcher() );
}
@SubscribeEvent
public static void onServerStarted( FMLServerStartedEvent event )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
}
@SubscribeEvent
public static void onServerStopped( FMLServerStoppedEvent event )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
}
public static final ResourceLocation LOOT_TREASURE_DISK = new ResourceLocation( ComputerCraft.MOD_ID, "treasure_disk" );
private static final Set<ResourceLocation> TABLES = new HashSet<>( Arrays.asList(
LootTables.SIMPLE_DUNGEON,
LootTables.ABANDONED_MINESHAFT,
LootTables.STRONGHOLD_CORRIDOR,
LootTables.STRONGHOLD_CROSSING,
LootTables.STRONGHOLD_LIBRARY,
LootTables.DESERT_PYRAMID,
LootTables.JUNGLE_TEMPLE,
LootTables.IGLOO_CHEST,
LootTables.WOODLAND_MANSION,
LootTables.VILLAGE_CARTOGRAPHER
) );
@SubscribeEvent
public static void lootLoad( LootTableLoadEvent event )
{
ResourceLocation name = event.getName();
if( !name.getNamespace().equals( "minecraft" ) || !TABLES.contains( name ) ) return;
event.getTable().addPool( LootPool.lootPool()
.add( TableLootEntry.lootTableReference( LOOT_TREASURE_DISK ) )
.setRolls( ConstantRange.exactly( 1 ) )
.name( "computercraft_treasure" )
.build() );
}
}
}

View File

@@ -715,7 +715,6 @@ public class TurtleAPI implements ILuaAPI
* more information about the item at the cost of taking longer to run.
* @return The command result.
* @throws LuaException If the slot is out of range.
* @see InventoryMethods#getItemDetail Describes the information returned by a detailed query.
* @cc.treturn nil|table Information about the given slot, or {@code nil} if it is empty.
* @cc.usage Print the current slot, assuming it contains 13 dirt.
*
@@ -726,6 +725,7 @@ public class TurtleAPI implements ILuaAPI
* -- count = 13,
* -- }
* }</pre>
* @see InventoryMethods#getItemDetail Describes the information returned by a detailed query.
*/
@LuaFunction
public final MethodResult getItemDetail( ILuaContext context, Optional<Integer> slot, Optional<Boolean> detailed ) throws LuaException

View File

@@ -160,8 +160,8 @@ public class TurtleBrain implements ITurtleAccess
overlay = nbt.contains( NBT_OVERLAY ) ? new ResourceLocation( nbt.getString( NBT_OVERLAY ) ) : null;
// Read upgrades
setUpgrade( TurtleSide.LEFT, nbt.contains( NBT_LEFT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_LEFT_UPGRADE ) ) : null );
setUpgrade( TurtleSide.RIGHT, nbt.contains( NBT_RIGHT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_RIGHT_UPGRADE ) ) : null );
setUpgradeDirect( TurtleSide.LEFT, nbt.contains( NBT_LEFT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_LEFT_UPGRADE ) ) : null );
setUpgradeDirect( TurtleSide.RIGHT, nbt.contains( NBT_RIGHT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_RIGHT_UPGRADE ) ) : null );
// NBT
upgradeNBTData.clear();
@@ -503,20 +503,15 @@ public class TurtleBrain implements ITurtleAccess
setFuelLevel( getFuelLevel() + addition );
}
private int issueCommand( ITurtleCommand command )
{
commandQueue.offer( new TurtleCommandQueueEntry( ++commandsIssued, command ) );
return commandsIssued;
}
@Nonnull
@Override
public MethodResult executeCommand( @Nonnull ITurtleCommand command )
{
if( getWorld().isClientSide ) throw new UnsupportedOperationException( "Cannot run commands on the client" );
if( commandQueue.size() > 16 ) return MethodResult.of( false, "Too many ongoing turtle commands" );
// Issue command
int commandID = issueCommand( command );
commandQueue.offer( new TurtleCommandQueueEntry( ++commandsIssued, command ) );
int commandID = commandsIssued;
return new CommandCallback( commandID ).pull;
}
@@ -618,16 +613,30 @@ public class TurtleBrain implements ITurtleAccess
@Override
public void setUpgrade( @Nonnull TurtleSide side, ITurtleUpgrade upgrade )
{
if( !setUpgradeDirect( side, upgrade ) ) return;
// This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as
// either the block is newly placed (and so won't have changed) or is being updated with /data, which calls
// updateBlock for us.
if( owner.getLevel() != null )
{
owner.updateBlock();
owner.updateInput();
}
}
private boolean setUpgradeDirect( @Nonnull TurtleSide side, ITurtleUpgrade upgrade )
{
// Remove old upgrade
if( upgrades.containsKey( side ) )
{
if( upgrades.get( side ) == upgrade ) return;
if( upgrades.get( side ) == upgrade ) return false;
upgrades.remove( side );
}
else
{
if( upgrade == null ) return;
if( upgrade == null ) return false;
}
upgradeNBTData.remove( side );
@@ -639,8 +648,9 @@ public class TurtleBrain implements ITurtleAccess
if( owner.getLevel() != null )
{
updatePeripherals( owner.createServerComputer() );
owner.updateBlock();
}
return true;
}
@Override

View File

@@ -62,7 +62,7 @@ public class TurtleDropCommand implements ITurtleCommand
IItemHandler inventory = InventoryUtil.getInventory( world, newPosition, side );
// Fire the event, restoring the inventory and exiting if it is cancelled.
TurtlePlayer player = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction );
TurtlePlayer player = TurtlePlayer.getWithPosition( turtle, oldPosition, direction );
TurtleInventoryEvent.Drop event = new TurtleInventoryEvent.Drop( turtle, player, world, newPosition, inventory, stack );
if( MinecraftForge.EVENT_BUS.post( event ) )
{

View File

@@ -50,7 +50,7 @@ public class TurtleInspectCommand implements ITurtleCommand
Map<String, Object> table = BlockData.fill( new HashMap<>(), state );
// Fire the event, exiting if it is cancelled
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction );
TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, oldPosition, direction );
TurtleBlockEvent.Inspect event = new TurtleBlockEvent.Inspect( turtle, turtlePlayer, world, newPosition, state, table );
if( MinecraftForge.EVENT_BUS.post( event ) ) return TurtleCommandResult.failure( event.getFailureMessage() );

View File

@@ -47,7 +47,7 @@ public class TurtleMoveCommand implements ITurtleCommand
BlockPos oldPosition = turtle.getPosition();
BlockPos newPosition = oldPosition.relative( direction );
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction );
TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, oldPosition, direction );
TurtleCommandResult canEnterResult = canEnter( turtlePlayer, oldWorld, newPosition );
if( !canEnterResult.isSuccess() )
{

View File

@@ -12,7 +12,6 @@ import dan200.computercraft.api.turtle.TurtleAnimation;
import dan200.computercraft.api.turtle.TurtleCommandResult;
import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
import dan200.computercraft.shared.TurtlePermissions;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.DropConsumer;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.WorldUtil;
@@ -20,6 +19,7 @@ import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.*;
import net.minecraft.network.play.client.CUseEntityPacket;
import net.minecraft.tileentity.SignTileEntity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ActionResult;
@@ -34,11 +34,13 @@ import net.minecraft.world.World;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.eventbus.api.Event;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.wrapper.InvWrapper;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull;
import java.util.List;
import static net.minecraftforge.eventbus.api.Event.Result;
public class TurtlePlaceCommand implements ITurtleCommand
{
@@ -57,10 +59,7 @@ public class TurtlePlaceCommand implements ITurtleCommand
{
// Get thing to place
ItemStack stack = turtle.getInventory().getItem( turtle.getSelectedSlot() );
if( stack.isEmpty() )
{
return TurtleCommandResult.failure( "No items to place" );
}
if( stack.isEmpty() ) return TurtleCommandResult.failure( "No items to place" );
// Remember old block
Direction direction = this.direction.toWorldDir( turtle );
@@ -68,144 +67,63 @@ public class TurtlePlaceCommand implements ITurtleCommand
// Create a fake player, and orient it appropriately
BlockPos playerPosition = turtle.getPosition().relative( direction );
TurtlePlayer turtlePlayer = createPlayer( turtle, playerPosition, direction );
TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, playerPosition, direction );
TurtleBlockEvent.Place place = new TurtleBlockEvent.Place( turtle, turtlePlayer, turtle.getWorld(), coordinates, stack );
if( MinecraftForge.EVENT_BUS.post( place ) )
{
return TurtleCommandResult.failure( place.getFailureMessage() );
}
if( MinecraftForge.EVENT_BUS.post( place ) ) return TurtleCommandResult.failure( place.getFailureMessage() );
// Do the deploying
String[] errorMessage = new String[1];
ItemStack remainder = deploy( stack, turtle, turtlePlayer, direction, extraArguments, errorMessage );
if( remainder != stack )
turtlePlayer.loadInventory( turtle );
ErrorMessage message = new ErrorMessage();
boolean result = deploy( stack, turtle, turtlePlayer, direction, extraArguments, message );
turtlePlayer.unloadInventory( turtle );
if( result )
{
// Put the remaining items back
turtle.getInventory().setItem( turtle.getSelectedSlot(), remainder );
turtle.getInventory().setChanged();
// Animate and return success
turtle.playAnimation( TurtleAnimation.WAIT );
return TurtleCommandResult.success();
}
else if( message.message != null )
{
return TurtleCommandResult.failure( message.message );
}
else
{
if( errorMessage[0] != null )
{
return TurtleCommandResult.failure( errorMessage[0] );
}
else if( stack.getItem() instanceof BlockItem )
{
return TurtleCommandResult.failure( "Cannot place block here" );
}
else
{
return TurtleCommandResult.failure( "Cannot place item here" );
}
return TurtleCommandResult.failure( stack.getItem() instanceof BlockItem ? "Cannot place block here" : "Cannot place item here" );
}
}
public static ItemStack deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, Direction direction, Object[] extraArguments, String[] outErrorMessage )
public static boolean deployCopiedItem( @Nonnull ItemStack stack, ITurtleAccess turtle, Direction direction, Object[] extraArguments, ErrorMessage outErrorMessage )
{
// Create a fake player, and orient it appropriately
BlockPos playerPosition = turtle.getPosition().relative( direction );
TurtlePlayer turtlePlayer = createPlayer( turtle, playerPosition, direction );
return deploy( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage );
TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, playerPosition, direction );
turtlePlayer.loadInventory( stack );
boolean result = deploy( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage );
turtlePlayer.inventory.clearContent();
return result;
}
public static ItemStack deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, Object[] extraArguments, String[] outErrorMessage )
private static boolean deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, Object[] extraArguments, ErrorMessage outErrorMessage )
{
// Deploy on an entity
ItemStack remainder = deployOnEntity( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage );
if( remainder != stack )
{
return remainder;
}
if( deployOnEntity( stack, turtle, turtlePlayer ) ) return true;
// Deploy on the block immediately in front
BlockPos position = turtle.getPosition();
BlockPos newPosition = position.relative( direction );
remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition, direction.getOpposite(), extraArguments, true, outErrorMessage );
if( remainder != stack )
{
return remainder;
}
// Deploy on the block one block away
remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition.relative( direction ), direction.getOpposite(), extraArguments, false, outErrorMessage );
if( remainder != stack )
{
return remainder;
}
if( direction.getAxis() != Direction.Axis.Y )
{
// Try to deploy against a block. Tries the following options:
// Deploy on the block immediately in front
return deployOnBlock( stack, turtle, turtlePlayer, newPosition, direction.getOpposite(), extraArguments, true, outErrorMessage )
// Deploy on the block one block away
|| deployOnBlock( stack, turtle, turtlePlayer, newPosition.relative( direction ), direction.getOpposite(), extraArguments, false, outErrorMessage )
// Deploy down on the block in front
remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition.below(), Direction.UP, extraArguments, false, outErrorMessage );
if( remainder != stack )
{
return remainder;
}
}
// Deploy back onto the turtle
remainder = deployOnBlock( stack, turtle, turtlePlayer, position, direction, extraArguments, false, outErrorMessage );
if( remainder != stack )
{
return remainder;
}
// If nothing worked, return the original stack unchanged
return stack;
|| (direction.getAxis() != Direction.Axis.Y && deployOnBlock( stack, turtle, turtlePlayer, newPosition.below(), Direction.UP, extraArguments, false, outErrorMessage ))
// Deploy back onto the turtle
|| deployOnBlock( stack, turtle, turtlePlayer, position, direction, extraArguments, false, outErrorMessage );
}
public static TurtlePlayer createPlayer( ITurtleAccess turtle, BlockPos position, Direction direction )
{
TurtlePlayer turtlePlayer = TurtlePlayer.get( turtle );
orientPlayer( turtle, turtlePlayer, position, direction );
return turtlePlayer;
}
private static void orientPlayer( ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction direction )
{
double posX = position.getX() + 0.5;
double posY = position.getY() + 0.5;
double posZ = position.getZ() + 0.5;
// Stop intersection with the turtle itself
if( turtle.getPosition().equals( position ) )
{
posX += 0.48 * direction.getStepX();
posY += 0.48 * direction.getStepY();
posZ += 0.48 * direction.getStepZ();
}
if( direction.getAxis() != Direction.Axis.Y )
{
turtlePlayer.yRot = direction.toYRot();
turtlePlayer.xRot = 0.0f;
}
else
{
turtlePlayer.yRot = turtle.getDirection().toYRot();
turtlePlayer.xRot = DirectionUtil.toPitchAngle( direction );
}
turtlePlayer.setPosRaw( posX, posY, posZ );
turtlePlayer.xo = posX;
turtlePlayer.yo = posY;
turtlePlayer.zo = posZ;
turtlePlayer.xRotO = turtlePlayer.xRot;
turtlePlayer.yRotO = turtlePlayer.yRot;
turtlePlayer.yHeadRot = turtlePlayer.yRot;
turtlePlayer.yHeadRotO = turtlePlayer.yHeadRot;
}
@Nonnull
private static ItemStack deployOnEntity( @Nonnull ItemStack stack, final ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, Object[] extraArguments, String[] outErrorMessage )
private static boolean deployOnEntity( @Nonnull ItemStack stack, final ITurtleAccess turtle, TurtlePlayer turtlePlayer )
{
// See if there is an entity present
final World world = turtle.getWorld();
@@ -213,81 +131,57 @@ public class TurtlePlaceCommand implements ITurtleCommand
Vec3d turtlePos = turtlePlayer.position();
Vec3d rayDir = turtlePlayer.getViewVector( 1.0f );
Pair<Entity, Vec3d> hit = WorldUtil.rayTraceEntities( world, turtlePos, rayDir, 1.5 );
if( hit == null )
{
return stack;
}
// Load up the turtle's inventory
ItemStack stackCopy = stack.copy();
turtlePlayer.loadInventory( stackCopy );
if( hit == null ) return false;
// Start claiming entity drops
Entity hitEntity = hit.getKey();
Vec3d hitPos = hit.getValue();
DropConsumer.set(
hitEntity,
drop -> InventoryUtil.storeItems( drop, turtle.getItemHandler(), turtle.getSelectedSlot() )
);
// Place on the entity
boolean placed = false;
ActionResultType cancelResult = ForgeHooks.onInteractEntityAt( turtlePlayer, hitEntity, hitPos, Hand.MAIN_HAND );
if( cancelResult == null )
{
cancelResult = hitEntity.interactAt( turtlePlayer, hitPos, Hand.MAIN_HAND );
}
IItemHandler itemHandler = new InvWrapper( turtlePlayer.inventory );
DropConsumer.set( hitEntity, drop -> InventoryUtil.storeItems( drop, itemHandler, 1 ) );
if( cancelResult == ActionResultType.SUCCESS )
{
placed = true;
}
else
{
// See EntityPlayer.interactOn
cancelResult = ForgeHooks.onInteractEntity( turtlePlayer, hitEntity, Hand.MAIN_HAND );
if( cancelResult == ActionResultType.SUCCESS )
{
placed = true;
}
else if( cancelResult == null )
{
if( hitEntity.interact( turtlePlayer, Hand.MAIN_HAND ) )
{
placed = true;
}
else if( hitEntity instanceof LivingEntity )
{
placed = stackCopy.interactEnemy( turtlePlayer, (LivingEntity) hitEntity, Hand.MAIN_HAND );
if( placed ) turtlePlayer.loadInventory( stackCopy );
}
}
}
boolean placed = doDeployOnEntity( stack, turtlePlayer, hitEntity, hitPos );
// Stop claiming drops
List<ItemStack> remainingDrops = DropConsumer.clear();
for( ItemStack remaining : remainingDrops )
{
WorldUtil.dropItemStack( remaining, world, position, turtle.getDirection().getOpposite() );
}
// Put everything we collected into the turtles inventory, then return
ItemStack remainder = turtlePlayer.unloadInventory( turtle );
if( !placed && ItemStack.matches( stack, remainder ) )
{
return stack;
}
else if( !remainder.isEmpty() )
{
return remainder;
}
else
{
return ItemStack.EMPTY;
}
DropConsumer.clearAndDrop( world, position, turtle.getDirection().getOpposite() );
return placed;
}
private static boolean canDeployOnBlock( @Nonnull BlockItemUseContext context, ITurtleAccess turtle, TurtlePlayer player, BlockPos position, Direction side, boolean allowReplaceable, String[] outErrorMessage )
/**
* Place a block onto an entity. For instance, feeding cows.
*
* @param stack The stack we're placing.
* @param turtlePlayer The player of the turtle we're placing.
* @param hitEntity The entity we're interacting with.
* @param hitPos The position our ray trace hit the entity.
* @return If this item was deployed.
* @see net.minecraft.network.play.ServerPlayNetHandler#handleInteract(CUseEntityPacket)
* @see net.minecraft.entity.player.PlayerEntity#interactOn(Entity, Hand)
*/
private static boolean doDeployOnEntity( @Nonnull ItemStack stack, TurtlePlayer turtlePlayer, @Nonnull Entity hitEntity, @Nonnull Vec3d hitPos )
{
// Placing "onto" a block follows two flows. First we try to interactAt. If that doesn't succeed, then we try to
// call the normal interact path. Cancelling an interactAt *does not* cancel a normal interact path.
ActionResultType interactAt = ForgeHooks.onInteractEntityAt( turtlePlayer, hitEntity, hitPos, Hand.MAIN_HAND );
if( interactAt == null ) interactAt = hitEntity.interactAt( turtlePlayer, hitPos, Hand.MAIN_HAND );
if( interactAt.consumesAction() ) return true;
ActionResultType interact = ForgeHooks.onInteractEntity( turtlePlayer, hitEntity, Hand.MAIN_HAND );
if( interact != null ) return interact.consumesAction();
if( hitEntity.interact( turtlePlayer, Hand.MAIN_HAND ) ) return true;
if( hitEntity instanceof LivingEntity )
{
return stack.interactEnemy( turtlePlayer, (LivingEntity) hitEntity, Hand.MAIN_HAND );
}
return false;
}
private static boolean canDeployOnBlock(
@Nonnull BlockItemUseContext context, ITurtleAccess turtle, TurtlePlayer player, BlockPos position,
Direction side, boolean allowReplaceable, ErrorMessage outErrorMessage
)
{
World world = turtle.getWorld();
if( !World.isInWorldBounds( position ) || world.isEmptyBlock( position ) ||
@@ -309,7 +203,7 @@ public class TurtlePlaceCommand implements ITurtleCommand
: TurtlePermissions.isBlockEditable( world, position.relative( side ), player );
if( !editable )
{
if( outErrorMessage != null ) outErrorMessage[0] = "Cannot place in protected area";
if( outErrorMessage != null ) outErrorMessage.message = "Cannot place in protected area";
return false;
}
}
@@ -317,131 +211,123 @@ public class TurtlePlaceCommand implements ITurtleCommand
return true;
}
@Nonnull
private static ItemStack deployOnBlock( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side, Object[] extraArguments, boolean allowReplace, String[] outErrorMessage )
private static boolean deployOnBlock(
@Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side,
Object[] extraArguments, boolean allowReplace, ErrorMessage outErrorMessage
)
{
// Re-orient the fake player
Direction playerDir = side.getOpposite();
BlockPos playerPosition = position.relative( side );
orientPlayer( turtle, turtlePlayer, playerPosition, playerDir );
ItemStack stackCopy = stack.copy();
turtlePlayer.loadInventory( stackCopy );
turtlePlayer.setPosition( turtle, playerPosition, playerDir );
// Calculate where the turtle would hit the block
float hitX = 0.5f + side.getStepX() * 0.5f;
float hitY = 0.5f + side.getStepY() * 0.5f;
float hitZ = 0.5f + side.getStepZ() * 0.5f;
if( Math.abs( hitY - 0.5f ) < 0.01f )
{
hitY = 0.45f;
}
if( Math.abs( hitY - 0.5f ) < 0.01f ) hitY = 0.45f;
// Check if there's something suitable to place onto
BlockRayTraceResult hit = new BlockRayTraceResult( new Vec3d( hitX, hitY, hitZ ), side, position, false );
ItemUseContext context = new ItemUseContext( turtlePlayer, Hand.MAIN_HAND, hit );
if( !canDeployOnBlock( new BlockItemUseContext( context ), turtle, turtlePlayer, position, side, allowReplace, outErrorMessage ) )
{
return stack;
return false;
}
// Load up the turtle's inventory
Item item = stack.getItem();
// Do the deploying (put everything in the players inventory)
boolean placed = false;
TileEntity existingTile = turtle.getWorld().getBlockEntity( position );
// See PlayerInteractionManager.processRightClickBlock
// TODO: ^ Check we're still consistent.
PlayerInteractEvent.RightClickBlock event = ForgeHooks.onRightClickBlock( turtlePlayer, Hand.MAIN_HAND, position, side );
if( !event.isCanceled() )
{
if( item.onItemUseFirst( stack, context ) == ActionResultType.SUCCESS )
{
placed = true;
turtlePlayer.loadInventory( stackCopy );
}
else if( event.getUseItem() != Event.Result.DENY &&
stackCopy.useOn( context ) == ActionResultType.SUCCESS )
{
placed = true;
turtlePlayer.loadInventory( stackCopy );
}
}
if( !placed && (item instanceof BucketItem || item instanceof BoatItem || item instanceof LilyPadItem || item instanceof GlassBottleItem) )
{
ActionResultType actionResult = ForgeHooks.onItemRightClick( turtlePlayer, Hand.MAIN_HAND );
if( actionResult == ActionResultType.SUCCESS )
{
placed = true;
}
else if( actionResult == null )
{
ActionResult<ItemStack> result = stackCopy.use( turtle.getWorld(), turtlePlayer, Hand.MAIN_HAND );
if( result.getResult() == ActionResultType.SUCCESS && !ItemStack.matches( stack, result.getObject() ) )
{
placed = true;
turtlePlayer.loadInventory( result.getObject() );
}
}
}
boolean placed = doDeployOnBlock( stack, turtlePlayer, position, side, context ).consumesAction();
// Set text on signs
if( placed && item instanceof SignItem )
if( placed && item instanceof SignItem && extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String )
{
if( extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String )
World world = turtle.getWorld();
TileEntity tile = world.getBlockEntity( position );
if( tile == null || tile == existingTile )
{
World world = turtle.getWorld();
TileEntity tile = world.getBlockEntity( position );
if( tile == null || tile == existingTile )
{
tile = world.getBlockEntity( position.relative( side ) );
}
if( tile instanceof SignTileEntity )
{
SignTileEntity signTile = (SignTileEntity) tile;
String s = (String) extraArguments[0];
String[] split = s.split( "\n" );
int firstLine = split.length <= 2 ? 1 : 0;
for( int i = 0; i < signTile.messages.length; i++ )
{
if( i >= firstLine && i < firstLine + split.length )
{
if( split[i - firstLine].length() > 15 )
{
signTile.messages[i] = new StringTextComponent( split[i - firstLine].substring( 0, 15 ) );
}
else
{
signTile.messages[i] = new StringTextComponent( split[i - firstLine] );
}
}
else
{
signTile.messages[i] = new StringTextComponent( "" );
}
}
signTile.setChanged();
world.sendBlockUpdated( tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), 3 );
}
tile = world.getBlockEntity( position.relative( side ) );
}
if( tile instanceof SignTileEntity ) setSignText( world, tile, (String) extraArguments[0] );
}
return placed;
}
/**
* Attempt to place an item into the world. Returns true/false if an item was placed.
*
* @param stack The stack the player is using.
* @param turtlePlayer The player which represents the turtle
* @param position The block we're deploying against's position.
* @param side The side of the block we're deploying against.
* @param context The context of this place action.
* @return If this item was deployed.
* @see net.minecraft.server.management.PlayerInteractionManager#useItemOn For the original implementation.
*/
private static ActionResultType doDeployOnBlock(
@Nonnull ItemStack stack, TurtlePlayer turtlePlayer, BlockPos position, Direction side, ItemUseContext context
)
{
PlayerInteractEvent.RightClickBlock event = ForgeHooks.onRightClickBlock( turtlePlayer, Hand.MAIN_HAND, position, side );
if( event.isCanceled() ) return event.getCancellationResult();
if( event.getUseItem() != Result.DENY )
{
ActionResultType result = stack.onItemUseFirst( context );
if( result != ActionResultType.PASS ) return result;
}
if( event.getUseItem() != Result.DENY )
{
ActionResultType result = stack.useOn( context );
if( result != ActionResultType.PASS ) return result;
}
Item item = stack.getItem();
if( item instanceof BucketItem || item instanceof BoatItem || item instanceof LilyPadItem || item instanceof GlassBottleItem )
{
ActionResultType actionResult = ForgeHooks.onItemRightClick( turtlePlayer, Hand.MAIN_HAND );
if( actionResult != null && actionResult != ActionResultType.PASS ) return actionResult;
ActionResult<ItemStack> result = stack.use( context.getLevel(), turtlePlayer, Hand.MAIN_HAND );
if( result.getResult().consumesAction() && !ItemStack.matches( stack, result.getObject() ) )
{
turtlePlayer.setItemInHand( Hand.MAIN_HAND, result.getObject() );
return result.getResult();
}
}
// Put everything we collected into the turtles inventory, then return
ItemStack remainder = turtlePlayer.unloadInventory( turtle );
if( !placed && ItemStack.matches( stack, remainder ) )
return ActionResultType.PASS;
}
private static void setSignText( World world, TileEntity tile, String message )
{
SignTileEntity signTile = (SignTileEntity) tile;
String[] split = message.split( "\n" );
int firstLine = split.length <= 2 ? 1 : 0;
for( int i = 0; i < signTile.messages.length; i++ )
{
return stack;
}
else if( !remainder.isEmpty() )
{
return remainder;
}
else
{
return ItemStack.EMPTY;
if( i >= firstLine && i < firstLine + split.length )
{
String line = split[i - firstLine];
signTile.messages[i] = line.length() > 15
? new StringTextComponent( line.substring( 0, 15 ) )
: new StringTextComponent( line );
}
else
{
signTile.messages[i] = new StringTextComponent( "" );
}
}
signTile.setChanged();
world.sendBlockUpdated( tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), 3 );
}
private static class ErrorMessage
{
String message;
}
}

View File

@@ -9,6 +9,7 @@ import com.mojang.authlib.GameProfile;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.FakeNetHandler;
import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.WorldUtil;
@@ -73,23 +74,6 @@ public final class TurtlePlayer extends FakePlayer
return profile != null && profile.isComplete() ? profile : DEFAULT_PROFILE;
}
private void setState( ITurtleAccess turtle )
{
if( containerMenu != inventoryMenu )
{
ComputerCraft.log.warn( "Turtle has open container ({})", containerMenu );
doCloseContainer();
}
BlockPos position = turtle.getPosition();
setPosRaw( position.getX() + 0.5, position.getY() + 0.5, position.getZ() + 0.5 );
yRot = turtle.getDirection().toYRot();
xRot = 0.0f;
inventory.clearContent();
}
public static TurtlePlayer get( ITurtleAccess access )
{
if( !(access instanceof TurtleBrain) ) return create( access );
@@ -109,37 +93,114 @@ public final class TurtlePlayer extends FakePlayer
return player;
}
public void loadInventory( @Nonnull ItemStack currentStack )
public static TurtlePlayer getWithPosition( ITurtleAccess turtle, BlockPos position, Direction direction )
{
// Load up the fake inventory
inventory.selected = 0;
inventory.setItem( 0, currentStack );
TurtlePlayer turtlePlayer = get( turtle );
turtlePlayer.setPosition( turtle, position, direction );
return turtlePlayer;
}
public ItemStack unloadInventory( ITurtleAccess turtle )
private void setState( ITurtleAccess turtle )
{
// Get the item we placed with
ItemStack results = inventory.getItem( 0 );
inventory.setItem( 0, ItemStack.EMPTY );
if( containerMenu != inventoryMenu )
{
ComputerCraft.log.warn( "Turtle has open container ({})", containerMenu );
doCloseContainer();
}
BlockPos position = turtle.getPosition();
setPosRaw( position.getX() + 0.5, position.getY() + 0.5, position.getZ() + 0.5 );
yRot = turtle.getDirection().toYRot();
xRot = 0.0f;
inventory.clearContent();
}
public void setPosition( ITurtleAccess turtle, BlockPos position, Direction direction )
{
double posX = position.getX() + 0.5;
double posY = position.getY() + 0.5;
double posZ = position.getZ() + 0.5;
// Stop intersection with the turtle itself
if( turtle.getPosition().equals( position ) )
{
posX += 0.48 * direction.getStepX();
posY += 0.48 * direction.getStepY();
posZ += 0.48 * direction.getStepZ();
}
if( direction.getAxis() != Direction.Axis.Y )
{
yRot = direction.toYRot();
xRot = 0.0f;
}
else
{
yRot = turtle.getDirection().toYRot();
xRot = DirectionUtil.toPitchAngle( direction );
}
setPosRaw( posX, posY, posZ );
xo = posX;
yo = posY;
zo = posZ;
xRotO = xRot;
yRotO = yRot;
yHeadRot = yRot;
yHeadRotO = yHeadRot;
}
public void loadInventory( @Nonnull ItemStack stack )
{
inventory.clearContent();
inventory.selected = 0;
inventory.setItem( 0, stack );
}
public void loadInventory( @Nonnull ITurtleAccess turtle )
{
inventory.clearContent();
int currentSlot = turtle.getSelectedSlot();
int slots = turtle.getItemHandler().getSlots();
// Load up the fake inventory
inventory.selected = 0;
for( int i = 0; i < slots; i++ )
{
inventory.setItem( i, turtle.getItemHandler().getStackInSlot( (currentSlot + i) % slots ) );
}
}
public void unloadInventory( ITurtleAccess turtle )
{
int currentSlot = turtle.getSelectedSlot();
int slots = turtle.getItemHandler().getSlots();
// Load up the fake inventory
inventory.selected = 0;
for( int i = 0; i < slots; i++ )
{
turtle.getItemHandler().setStackInSlot( (currentSlot + i) % slots, inventory.getItem( i ) );
}
// Store (or drop) anything else we found
BlockPos dropPosition = turtle.getPosition();
Direction dropDirection = turtle.getDirection().getOpposite();
for( int i = 0; i < inventory.getContainerSize(); i++ )
int totalSize = inventory.getContainerSize();
for( int i = slots; i < totalSize; i++ )
{
ItemStack stack = inventory.getItem( i );
if( !stack.isEmpty() )
ItemStack remainder = InventoryUtil.storeItems( inventory.getItem( i ), turtle.getItemHandler(), turtle.getSelectedSlot() );
if( !remainder.isEmpty() )
{
ItemStack remainder = InventoryUtil.storeItems( stack, turtle.getItemHandler(), turtle.getSelectedSlot() );
if( !remainder.isEmpty() )
{
WorldUtil.dropItemStack( remainder, turtle.getWorld(), dropPosition, dropDirection );
}
inventory.setItem( i, ItemStack.EMPTY );
WorldUtil.dropItemStack( remainder, turtle.getWorld(), dropPosition, dropDirection );
}
}
inventory.setChanged();
return results;
}
@Nonnull

View File

@@ -58,7 +58,7 @@ public class TurtleSuckCommand implements ITurtleCommand
IItemHandler inventory = InventoryUtil.getInventory( world, blockPosition, side );
// Fire the event, exiting if it is cancelled.
TurtlePlayer player = TurtlePlaceCommand.createPlayer( turtle, turtlePosition, direction );
TurtlePlayer player = TurtlePlayer.getWithPosition( turtle, turtlePosition, direction );
TurtleInventoryEvent.Suck event = new TurtleInventoryEvent.Suck( turtle, player, world, blockPosition, inventory );
if( MinecraftForge.EVENT_BUS.post( event ) )
{

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