mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-22 17:37:38 +00:00
Compare commits
34 Commits
v1.17.1-1.
...
v1.18.1-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2b901f2d5e | ||
![]() |
62f2cd5cb2 | ||
![]() |
e558b31b2b | ||
![]() |
afd82fbf1f | ||
![]() |
901d8d4c3b | ||
![]() |
f794ce42ab | ||
![]() |
f470478a0f | ||
![]() |
aa009df740 | ||
![]() |
0c6c0badde | ||
![]() |
bed2e0b658 | ||
![]() |
0f9ddac83c | ||
![]() |
932b77d7ee | ||
![]() |
5eedea1bbb | ||
![]() |
114261944a | ||
![]() |
4d10639efb | ||
![]() |
aa36b49c50 | ||
![]() |
8a1067940d | ||
![]() |
2562642664 | ||
![]() |
632db1cfa5 | ||
![]() |
aa0d544bba | ||
![]() |
2f6ad00764 | ||
![]() |
05da4dd362 | ||
![]() |
0477b2742c | ||
![]() |
fe3c42ce22 | ||
![]() |
f6fcba7a39 | ||
![]() |
82a7edee12 | ||
![]() |
b048b6666d | ||
![]() |
e16f66e128 | ||
![]() |
1cfad31a0d | ||
![]() |
7c373c6e06 | ||
![]() |
57c5d19f95 | ||
![]() |
23c17075be | ||
![]() |
87988a705b | ||
![]() |
179da1d8cf |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -13,3 +13,4 @@ src/testMod/server-files/structures linguist-generated
|
||||
|
||||
*.png binary
|
||||
*.jar binary
|
||||
*.dfpwm binary
|
||||
|
61
build.gradle
61
build.gradle
@@ -5,7 +5,8 @@ buildscript {
|
||||
maven { url = 'https://maven.parchmentmc.org' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'net.minecraftforge.gradle:ForgeGradle:5.1.24'
|
||||
classpath 'net.minecraftforge.gradle:ForgeGradle:5.1.+'
|
||||
classpath "org.spongepowered:mixingradle:0.7.+"
|
||||
classpath 'org.parchmentmc:librarian:1.+'
|
||||
}
|
||||
}
|
||||
@@ -22,6 +23,7 @@ plugins {
|
||||
}
|
||||
|
||||
apply plugin: 'net.minecraftforge.gradle'
|
||||
apply plugin: "org.spongepowered.mixin"
|
||||
apply plugin: 'org.parchmentmc.librarian.forgegradle'
|
||||
|
||||
version = mod_version
|
||||
@@ -29,7 +31,7 @@ version = mod_version
|
||||
group = "org.squiddev"
|
||||
archivesBaseName = "cc-tweaked-${mc_version}"
|
||||
|
||||
def javaVersion = JavaLanguageVersion.of(16)
|
||||
def javaVersion = JavaLanguageVersion.of(17)
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = javaVersion
|
||||
@@ -46,9 +48,6 @@ tasks.withType(JavaExec).configureEach {
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java {
|
||||
exclude 'dan200/computercraft/shared/integration/morered/**'
|
||||
}
|
||||
main.resources {
|
||||
srcDir 'src/generated/resources'
|
||||
}
|
||||
@@ -71,6 +70,8 @@ minecraft {
|
||||
source sourceSets.main
|
||||
}
|
||||
}
|
||||
|
||||
arg "-mixin.config=computercraft.mixins.json"
|
||||
}
|
||||
|
||||
client {
|
||||
@@ -122,11 +123,17 @@ minecraft {
|
||||
}
|
||||
}
|
||||
|
||||
mappings channel: 'parchment', version: "${mapping_version}-${mc_version}"
|
||||
// mappings channel: 'parchment', version: "${mapping_version}-${mc_version}"
|
||||
mappings channel: 'official', version: mc_version
|
||||
|
||||
accessTransformer file('src/main/resources/META-INF/accesstransformer.cfg')
|
||||
accessTransformer file('src/testMod/resources/META-INF/accesstransformer.cfg')
|
||||
}
|
||||
|
||||
mixin {
|
||||
add sourceSets.main, 'computercraft.mixins.refmap.json'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
@@ -150,11 +157,10 @@ dependencies {
|
||||
checkstyle "com.puppycrawl.tools:checkstyle:8.45"
|
||||
|
||||
minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}"
|
||||
annotationProcessor 'org.spongepowered:mixin:0.8.4:processor'
|
||||
|
||||
compileOnly fg.deobf("mezz.jei:jei-1.17.1:8.0.0.14:api")
|
||||
// compileOnly fg.deobf("commoble.morered:morered-1.16.5:2.1.1.0")
|
||||
|
||||
runtimeOnly fg.deobf("mezz.jei:jei-1.17.1:8.0.0.14")
|
||||
compileOnly fg.deobf("mezz.jei:jei-1.18.1:9.1.0.47:api")
|
||||
runtimeOnly fg.deobf("mezz.jei:jei-1.18.1:9.1.0.47")
|
||||
|
||||
shade 'org.squiddev:Cobalt:0.5.2-SNAPSHOT'
|
||||
|
||||
@@ -166,9 +172,11 @@ dependencies {
|
||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
|
||||
|
||||
testModImplementation sourceSets.main.output
|
||||
testModExtra 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.21'
|
||||
testModExtra('org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0') {
|
||||
exclude group: "org.jetbrains", module: "annotations"
|
||||
}
|
||||
|
||||
cctJavadoc 'cc.tweaked:cct-javadoc:1.4.2'
|
||||
cctJavadoc 'cc.tweaked:cct-javadoc:1.4.5'
|
||||
}
|
||||
|
||||
// Compile tasks
|
||||
@@ -208,6 +216,8 @@ jar {
|
||||
"Implementation-Version" : "${mod_version}",
|
||||
"Implementation-Vendor" : "SquidDev",
|
||||
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
,
|
||||
"MixinConfigs" : "computercraft.mixins.json",
|
||||
])
|
||||
}
|
||||
|
||||
@@ -280,18 +290,7 @@ task rollup(type: Exec) {
|
||||
commandLine mkCommand('"node_modules/.bin/rollup" --config rollup.config.js')
|
||||
}
|
||||
|
||||
task minifyWeb(type: Exec, dependsOn: rollup) {
|
||||
group = "build"
|
||||
description = "Bundles JS into rollup"
|
||||
|
||||
inputs.file("$buildDir/rollup/index.js").withPropertyName("sources")
|
||||
inputs.file("package-lock.json").withPropertyName("package-lock.json")
|
||||
outputs.file("$buildDir/rollup/index.min.js").withPropertyName("output")
|
||||
|
||||
commandLine mkCommand('"node_modules/.bin/terser"' + " -o '$buildDir/rollup/index.min.js' '$buildDir/rollup/index.js'")
|
||||
}
|
||||
|
||||
task illuaminateDocs(type: Exec, dependsOn: [minifyWeb, luaJavadoc]) {
|
||||
task illuaminateDocs(type: Exec, dependsOn: [rollup, luaJavadoc]) {
|
||||
group = "build"
|
||||
description = "Bundles JS into rollup"
|
||||
|
||||
@@ -299,7 +298,7 @@ task illuaminateDocs(type: Exec, dependsOn: [minifyWeb, luaJavadoc]) {
|
||||
inputs.files(fileTree("src/main/resources/data/computercraft/lua/rom")).withPropertyName("lua rom")
|
||||
inputs.file("illuaminate.sexp").withPropertyName("illuaminate.sexp")
|
||||
inputs.dir("$buildDir/docs/luaJavadoc")
|
||||
inputs.file("$buildDir/rollup/index.min.js").withPropertyName("scripts")
|
||||
inputs.file("$buildDir/rollup/index.js").withPropertyName("scripts")
|
||||
inputs.file("src/web/styles.css").withPropertyName("styles")
|
||||
outputs.dir("$buildDir/docs/lua")
|
||||
|
||||
@@ -307,9 +306,13 @@ task illuaminateDocs(type: Exec, dependsOn: [minifyWeb, luaJavadoc]) {
|
||||
}
|
||||
|
||||
task docWebsite(type: Copy, dependsOn: [illuaminateDocs]) {
|
||||
from 'doc'
|
||||
include 'logo.png'
|
||||
include 'images/**'
|
||||
from('doc') {
|
||||
include 'logo.png'
|
||||
include 'images/**'
|
||||
}
|
||||
from("$buildDir/rollup") {
|
||||
exclude 'index.js'
|
||||
}
|
||||
into "${project.docsDir}/lua"
|
||||
}
|
||||
|
||||
@@ -477,7 +480,7 @@ task checkRelease {
|
||||
}
|
||||
check.dependsOn checkRelease
|
||||
|
||||
def isStable = false
|
||||
def isStable = true
|
||||
|
||||
curseforge {
|
||||
apiKey = project.hasProperty('curseForgeApiKey') ? project.curseForgeApiKey : ''
|
||||
|
@@ -51,5 +51,6 @@ exclude: |
|
||||
src/generated|
|
||||
src/test/resources/test-rom/data/json-parsing/|
|
||||
src/testMod/server-files/|
|
||||
config/idea/
|
||||
config/idea/|
|
||||
.*\.dfpwm
|
||||
)
|
||||
|
@@ -2,7 +2,7 @@
|
||||
module: [kind=event] modem_message
|
||||
---
|
||||
|
||||
The @{modem_message} event is fired when a message is received on an open channel on any modem.
|
||||
The @{modem_message} event is fired when a message is received on an open channel on any @{modem}.
|
||||
|
||||
## Return Values
|
||||
1. @{string}: The event name.
|
||||
@@ -10,11 +10,15 @@ The @{modem_message} event is fired when a message is received on an open channe
|
||||
3. @{number}: The channel that the message was sent on.
|
||||
4. @{number}: The reply channel set by the sender.
|
||||
5. @{any}: The message as sent by the sender.
|
||||
6. @{number}: The distance between the sender and the receiver, in blocks (decimal).
|
||||
6. @{number}: The distance between the sender and the receiver, in blocks.
|
||||
|
||||
## Example
|
||||
Prints a message when one is sent:
|
||||
Wraps a @{modem} peripheral, opens channel 0 for listening, and prints all received messages.
|
||||
|
||||
```lua
|
||||
local modem = peripheral.find("modem") or error("No modem attached", 0)
|
||||
modem.open(0)
|
||||
|
||||
while true do
|
||||
local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
|
||||
print(("Message received on side %s on channel %d (reply to %d) from %f blocks away with message %s"):format(side, channel, replyChannel, distance, tostring(message)))
|
||||
|
27
doc/events/speaker_audio_empty.md
Normal file
27
doc/events/speaker_audio_empty.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
module: [kind=event] speaker_audio_empty
|
||||
see: speaker.playAudio To play audio using the speaker
|
||||
---
|
||||
|
||||
## Return Values
|
||||
1. @{string}: The event name.
|
||||
2. @{string}: The name of the speaker which is available to play more audio.
|
||||
|
||||
|
||||
## Example
|
||||
This uses @{io.lines} to read audio data in blocks of 16KiB from "example_song.dfpwm", and then attempts to play it
|
||||
using @{speaker.playAudio}. If the speaker's buffer is full, it waits for an event and tries again.
|
||||
|
||||
```lua {data-peripheral=speaker}
|
||||
local dfpwm = require("cc.audio.dfpwm")
|
||||
local speaker = peripheral.find("speaker")
|
||||
|
||||
local decoder = dfpwm.make_decoder()
|
||||
for chunk in io.lines("data/example.dfpwm", 16 * 1024) do
|
||||
local buffer = decoder(chunk)
|
||||
|
||||
while not speaker.playAudio(buffer) do
|
||||
os.pullEvent("speaker_audio_empty")
|
||||
end
|
||||
end
|
||||
```
|
200
doc/guides/speaker_audio.md
Normal file
200
doc/guides/speaker_audio.md
Normal file
@@ -0,0 +1,200 @@
|
||||
---
|
||||
module: [kind=guide] speaker_audio
|
||||
see: speaker.playAudio Play PCM audio using a speaker.
|
||||
see: cc.audio.dfpwm Provides utilities for encoding and decoding DFPWM files.
|
||||
---
|
||||
|
||||
# Playing audio with speakers
|
||||
CC: Tweaked's speaker peripheral provides a powerful way to play any audio you like with the @{speaker.playAudio}
|
||||
method. However, for people unfamiliar with digital audio, it's not the most intuitive thing to use. This guide provides
|
||||
an introduction to digital audio, demonstrates how to play music with CC: Tweaked's speakers, and then briefly discusses
|
||||
the more complex topic of audio processing.
|
||||
|
||||
## A short introduction to digital audio
|
||||
When sound is recorded it is captured as an analogue signal, effectively the electrical version of a sound
|
||||
wave. However, this signal is continuous, and so can't be used directly by a computer. Instead, we measure (or *sample*)
|
||||
the amplitude of the wave many times a second and then *quantise* that amplitude, rounding it to the nearest
|
||||
representable value.
|
||||
|
||||
This representation of sound - a long, uniformally sampled list of amplitudes is referred to as [Pulse-code
|
||||
Modulation][PCM] (PCM). PCM can be thought of as the "standard" audio format, as it's incredibly easy to work with. For
|
||||
instance, to mix two pieces of audio together, you can just samples from the two tracks together and take the average.
|
||||
|
||||
CC: Tweaked's speakers also work with PCM audio. It plays back 48,000 samples a second, where each sample is an integer
|
||||
between -128 and 127. This is more commonly referred to as 48kHz and an 8-bit resolution.
|
||||
|
||||
Let's now look at a quick example. We're going to generate a [Sine Wave] at 220Hz, which sounds like a low monotonous
|
||||
hum. First we wrap our speaker peripheral, and then we fill a table (also referred to as a *buffer*) with 128×1024
|
||||
samples - this is the maximum number of samples a speaker can accept in one go.
|
||||
|
||||
In order to fill this buffer, we need to do a little maths. We want to play 220 sine waves each second, where each sine
|
||||
wave completes a full oscillation in 2π "units". This means one seconds worth of audio is 2×π×220 "units" long. We then
|
||||
need to split this into 48k samples, basically meaning for each sample we move 2×π×220/48k "along" the sine curve.
|
||||
|
||||
```lua {data-peripheral=speaker}
|
||||
local speaker = peripheral.find("speaker")
|
||||
|
||||
local buffer = {}
|
||||
local t, dt = 0, 2 * math.pi * 220 / 48000
|
||||
for i = 1, 128 * 1024 do
|
||||
buffer[i] = math.floor(math.sin(t) * 127)
|
||||
t = (t + dt) % (math.pi * 2)
|
||||
end
|
||||
|
||||
speaker.playAudio(buffer)
|
||||
```
|
||||
|
||||
## Streaming audio
|
||||
You might notice that the above snippet only generates a short bit of audio - 2.7s seconds to be precise. While we could
|
||||
try increasing the number of loop iterations, we'll get an error when we try to play it through the speaker: the sound
|
||||
buffer is too large for it to handle.
|
||||
|
||||
Our 2.7 seconds of audio is stored in a table with over 130 _thousand_ elements. If we wanted to play a full minute of
|
||||
sine waves (and why wouldn't you?), you'd need a table with almost 3 _million_. Suddenly you find these numbers adding
|
||||
up very quickly, and these tables take up more and more memory.
|
||||
|
||||
Instead of building our entire song (well, sine wave) in one go, we can produce it in small batches, each of which get
|
||||
passed off to @{speaker.playAudio} when the time is right. This allows us to build a _stream_ of audio, where we read
|
||||
chunks of audio one at a time (either from a file or a tone generator like above), do some optional processing to each
|
||||
one, and then play them.
|
||||
|
||||
Let's adapt our example from above to do that instead.
|
||||
|
||||
```lua {data-peripheral=speaker}
|
||||
local speaker = peripheral.find("speaker")
|
||||
|
||||
local t, dt = 0, 2 * math.pi * 220 / 48000
|
||||
while true do
|
||||
local buffer = {}
|
||||
for i = 1, 16 * 1024 * 8 do
|
||||
buffer[i] = math.floor(math.sin(t) * 127)
|
||||
t = (t + dt) % (math.pi * 2)
|
||||
end
|
||||
|
||||
while not speaker.playAudio(buffer) do
|
||||
os.pullEvent("speaker_audio_empty")
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
It looks pretty similar to before, aside from we've wrapped the generation and playing code in a while loop, and added a
|
||||
rather odd loop with @{speaker.playAudio} and @{os.pullEvent}.
|
||||
|
||||
Let's talk about this loop, why do we need to keep calling @{speaker.playAudio}? Remember that what we're trying to do
|
||||
here is avoid keeping too much audio in memory at once. However, if we're generating audio quicker than the speakers can
|
||||
play it, we're not helping at all - all this audio is still hanging around waiting to be played!
|
||||
|
||||
In order to avoid this, the speaker rejects any new chunks of audio if its backlog is too large. When this happens,
|
||||
@{speaker.playAudio} returns false. Once enough audio has played, and the backlog has been reduced, a
|
||||
@{speaker_audio_empty} event is queued, and we can try to play our chunk once more.
|
||||
|
||||
## Storing audio
|
||||
PCM is a fantastic way of representing audio when we want to manipulate it, but it's not very efficient when we want to
|
||||
store it to disk. Compare the size of a WAV file (which uses PCM) to an equivalent MP3, it's often 5 times the size.
|
||||
Instead, we store audio in special formats (or *codecs*) and then convert them to PCM when we need to do processing on
|
||||
them.
|
||||
|
||||
Modern audio codecs use some incredibly impressive techniques to compress the audio as much as possible while preserving
|
||||
sound quality. However, due to CC: Tweaked's limited processing power, it's not really possible to use these from your
|
||||
computer. Instead, we need something much simpler.
|
||||
|
||||
DFPWM (Dynamic Filter Pulse Width Modulation) is the de facto standard audio format of the ComputerCraft (and
|
||||
OpenComputers) world. Originally popularised by the addon mod [Computronics], CC:T now has built-in support for it with
|
||||
the @{cc.audio.dfpwm} module. This allows you to read DFPWM files from disk, decode them to PCM, and then play them
|
||||
using the speaker.
|
||||
|
||||
Let's dive in with an example, and we'll explain things afterwards:
|
||||
|
||||
```lua {data-peripheral=speaker}
|
||||
local dfpwm = require("cc.audio.dfpwm")
|
||||
local speaker = peripheral.find("speaker")
|
||||
|
||||
local decoder = dfpwm.make_decoder()
|
||||
for chunk in io.lines("data/example.dfpwm", 16 * 1024) do
|
||||
local buffer = decoder(chunk)
|
||||
|
||||
while not speaker.playAudio(buffer) do
|
||||
os.pullEvent("speaker_audio_empty")
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Once again, we see the @{speaker.playAudio}/@{speaker_audio_empty} loop. However, the rest of the program is a little
|
||||
different.
|
||||
|
||||
First, we require the dfpwm module and call @{cc.audio.dfpwm.make_decoder} to construct a new decoder. This decoder
|
||||
accepts blocks of DFPWM data and converts it to a list of 8-bit amplitudes, which we can then play with our speaker.
|
||||
|
||||
As mentioned to above, @{speaker.playAudio} accepts at most 128×1024 samples in one go. DFPMW uses a single bit for each
|
||||
sample, which means we want to process our audio in chunks of 16×1024 bytes (16KiB). In order to do this, we use
|
||||
@{io.lines}, which provides a nice way to loop over chunks of a file. You can of course just use @{fs.open} and
|
||||
@{fs.BinaryReadHandle.read} if you prefer.
|
||||
|
||||
## Processing audio
|
||||
As mentioned near the beginning of this guide, PCM audio is pretty easy to work with as it's just a list of amplitudes.
|
||||
You can mix together samples from different streams by adding their amplitudes, change the rate of playback by removing
|
||||
samples, etc...
|
||||
|
||||
Let's put together a small demonstration here. We're going to add a small delay effect to the song above, so that you
|
||||
hear a faint echo about a second later.
|
||||
|
||||
In order to do this, we'll follow a format similar to the previous example, decoding the audio and then playing it.
|
||||
However, we'll also add some new logic between those two steps, which loops over every sample in our chunk of audio, and
|
||||
adds the sample from one second ago to it.
|
||||
|
||||
For this, we'll need to keep track of the last 48k samples - exactly one seconds worth of audio. We can do this using a
|
||||
[Ring Buffer], which helps makes things a little more efficient.
|
||||
|
||||
```lua {data-peripheral=speaker}
|
||||
local dfpwm = require("cc.audio.dfpwm")
|
||||
local speaker = peripheral.find("speaker")
|
||||
|
||||
-- Speakers play at 48kHz, so one second is 48k samples. We first fill our buffer
|
||||
-- with 0s, as there's nothing to echo at the start of the track!
|
||||
local samples_i, samples_n = 1, 48000
|
||||
local samples = {}
|
||||
for i = 1, samples_n do samples[i] = 0 end
|
||||
|
||||
local decoder = dfpwm.make_decoder()
|
||||
for chunk in io.lines("data/example.dfpwm", 16 * 1024) do
|
||||
local buffer = decoder(chunk)
|
||||
|
||||
for i = 1, #buffer do
|
||||
local original_value = buffer[i]
|
||||
|
||||
-- Replace this sample with its current amplitude plus the amplitude from one second ago.
|
||||
-- We scale both to ensure the resulting value is still between -128 and 127.
|
||||
buffer[i] = original_value * 0.6 + samples[samples_i] * 0.4
|
||||
|
||||
-- Now store the current sample, and move the "head" of our ring buffer forward one place.
|
||||
samples[samples_i] = original_value
|
||||
samples_i = samples_i + 1
|
||||
if samples_i > samples_n then samples_i = 1 end
|
||||
end
|
||||
|
||||
while not speaker.playAudio(buffer) do
|
||||
os.pullEvent("speaker_audio_empty")
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
:::note Confused?
|
||||
Don't worry if you don't understand this example. It's quite advanced, and does use some ideas that this guide doesn't
|
||||
cover. That said, don't be afraid to ask on [Discord] or [IRC] either!
|
||||
:::
|
||||
|
||||
It's worth noting that the examples of audio processing we've mentioned here are about manipulating the _amplitude_ of
|
||||
the wave. If you wanted to modify the _frequency_ (for instance, shifting the pitch), things get rather more complex.
|
||||
For this, you'd need to use the [Fast Fourier transform][FFT] to convert the stream of amplitudes to frequencies,
|
||||
process those, and then convert them back to amplitudes.
|
||||
|
||||
This is, I'm afraid, left as an exercise to the reader.
|
||||
|
||||
[Computronics]: https://github.com/Vexatos/Computronics/ "Computronics on GitHub"
|
||||
[FFT]: https://en.wikipedia.org/wiki/Fast_Fourier_transform "Fast Fourier transform - Wikipedia"
|
||||
[PCM]: https://en.wikipedia.org/wiki/Pulse-code_modulation "Pulse-code Modulation - Wikipedia"
|
||||
[Ring Buffer]: https://en.wikipedia.org/wiki/Circular_buffer "Circular buffer - Wikipedia"
|
||||
[Sine Wave]: https://en.wikipedia.org/wiki/Sine_wave "Sine wave - Wikipedia"
|
||||
|
||||
[Discord]: https://discord.computercraft.cc "The Minecraft Computer Mods Discord"
|
||||
[IRC]: http://webchat.esper.net/?channels=computercraft "IRC webchat on EsperNet"
|
83
doc/guides/using_require.md
Normal file
83
doc/guides/using_require.md
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
module: [kind=guide] using_require
|
||||
---
|
||||
|
||||
# Reusing code with require
|
||||
A library is a collection of useful functions and other definitions which is stored separately to your main program. You
|
||||
might want to create a library because you have some functions which are used in multiple programs, or just to split
|
||||
your program into multiple more modular files.
|
||||
|
||||
Let's say we want to create a small library to make working with the @{term|terminal} a little easier. We'll provide two
|
||||
functions: `reset`, which clears the terminal and sets the cursor to (1, 1), and `write_center`, which prints some text
|
||||
in the middle of the screen.
|
||||
|
||||
Start off by creating a file called `more_term.lua`:
|
||||
|
||||
```lua {data-snippet=more_term}
|
||||
local function reset()
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
end
|
||||
|
||||
local function write_center(text)
|
||||
local x, y = term.getCursorPos()
|
||||
local width, height = term.getSize()
|
||||
term.setCursorPos(math.floor((width - #text) / 2) + 1, y)
|
||||
term.write(text)
|
||||
end
|
||||
|
||||
return { reset = reset, write_center = write_center }
|
||||
```
|
||||
|
||||
Now, what's going on here? We define our two functions as one might expect, and then at the bottom return a table with
|
||||
the two functions. When we require this library, this table is what is returned. With that, we can then call the
|
||||
original functions. Now create a new file, with the following:
|
||||
|
||||
```lua {data-mount=more_term:more_term.lua}
|
||||
local more_term = require("more_term")
|
||||
more_term.reset()
|
||||
more_term.write_center("Hello, world!")
|
||||
```
|
||||
|
||||
When run, this'll clear the screen and print some text in the middle of the first line.
|
||||
|
||||
## require in depth
|
||||
While the previous section is a good introduction to how @{require} operates, there are a couple of remaining points
|
||||
which are worth mentioning for more advanced usage.
|
||||
|
||||
### Libraries can return anything
|
||||
In our above example, we return a table containing the functions we want to expose. However, it's worth pointing out
|
||||
that you can return ''anything'' from your library - a table, a function or even just a string! @{require} treats them
|
||||
all the same, and just returns whatever your library provides.
|
||||
|
||||
### Module resolution and the package path
|
||||
In the above examples, we defined our library in a file, and @{require} read from it. While this is what you'll do most
|
||||
of the time, it is possible to make @{require} look elsewhere for your library, such as downloading from a website or
|
||||
loading from an in-memory library store.
|
||||
|
||||
As a result, the *module name* you pass to @{require} doesn't correspond to a file path. One common mistake is to load
|
||||
code from a sub-directory using `require("folder/library")` or even `require("folder/library.lua")`, neither of which
|
||||
will do quite what you expect.
|
||||
|
||||
When loading libraries (also referred to as *modules*) from files, @{require} searches along the *@{package.path|module
|
||||
path}*. By default, this looks something like:
|
||||
|
||||
* `?.lua`
|
||||
* `?/init.lua`
|
||||
* `/rom/modules/main/?.lua`
|
||||
* etc...
|
||||
|
||||
When you call `require("my_library")`, @{require} replaces the `?` in each element of the path with your module name, and
|
||||
checks if the file exists. In this case, we'd look for `my_library.lua`, `my_library/init.lua`,
|
||||
`/rom/modules/main/my_library.lua` and so on. Note that this works *relative to the current program*, so if your
|
||||
program is actually called `folder/program`, then we'll look for `folder/my_library.lua`, etc...
|
||||
|
||||
One other caveat is loading libraries from sub-directories. For instance, say we have a file
|
||||
`my/fancy/library.lua`. This can be loaded by using `require("my.fancy.library")` - the '.'s are replaced with '/'
|
||||
before we start looking for the library.
|
||||
|
||||
## External links
|
||||
There are several external resources which go into require in a little more detail:
|
||||
|
||||
- The [Lua Module tutorial](http://lua-users.org/wiki/ModulesTutorial) on the Lua wiki.
|
||||
- [Lua's manual section on @{require}](https://www.lua.org/manual/5.1/manual.html#pdf-require).
|
@@ -12,16 +12,19 @@ rounded up to the nearest multiple of 0.05 seconds. If you are using coroutines
|
||||
or the @{parallel|parallel API}, it will only pause execution of the current
|
||||
thread, not the whole program.
|
||||
|
||||
**Note** Because sleep internally uses timers, it is a function that yields.
|
||||
This means that you can use it to prevent "Too long without yielding" errors,
|
||||
however, as the minimum sleep time is 0.05 seconds, it will slow your program
|
||||
down.
|
||||
:::tip
|
||||
Because sleep internally uses timers, it is a function that yields. This means
|
||||
that you can use it to prevent "Too long without yielding" errors, however, as
|
||||
the minimum sleep time is 0.05 seconds, it will slow your program down.
|
||||
:::
|
||||
|
||||
**Warning** Internally, this function queues and waits for a timer event (using
|
||||
:::caution
|
||||
Internally, this function queues and waits for a timer event (using
|
||||
@{os.startTimer}), however it does not listen for any other events. This means
|
||||
that any event that occurs while sleeping will be entirely discarded. If you
|
||||
need to receive events while sleeping, consider using @{os.startTimer|timers},
|
||||
or the @{parallel|parallel API}.
|
||||
:::
|
||||
|
||||
@tparam number time The number of seconds to sleep for, rounded up to the
|
||||
nearest multiple of 0.05.
|
||||
|
@@ -1,10 +1,10 @@
|
||||
org.gradle.jvmargs=-Xmx3G
|
||||
|
||||
# Mod properties
|
||||
mod_version=1.99.1
|
||||
mod_version=1.100.0
|
||||
|
||||
# Minecraft properties (update mods.toml when changing)
|
||||
mc_version=1.17.1
|
||||
mc_version=1.18.1
|
||||
mapping_version=2021.09.05
|
||||
forge_version=37.0.85
|
||||
forge_version=39.0.0
|
||||
# NO SERIOUSLY, UPDATE mods.toml WHEN CHANGING
|
||||
|
@@ -1,8 +1,9 @@
|
||||
; -*- mode: Lisp;-*-
|
||||
|
||||
(sources
|
||||
/doc/stub/
|
||||
/doc/events/
|
||||
/doc/guides/
|
||||
/doc/stub/
|
||||
/build/docs/luaJavadoc/
|
||||
/src/main/resources/*/computercraft/lua/bios.lua
|
||||
/src/main/resources/*/computercraft/lua/rom/
|
||||
@@ -27,7 +28,8 @@
|
||||
(module-kinds
|
||||
(peripheral Peripherals)
|
||||
(generic_peripheral "Generic Peripherals")
|
||||
(event Events))
|
||||
(event Events)
|
||||
(guide Guides))
|
||||
|
||||
(library-path
|
||||
/doc/stub/
|
||||
|
86
package-lock.json
generated
86
package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
"@rollup/plugin-url": "^6.1.0",
|
||||
"requirejs": "^2.3.6",
|
||||
"rollup": "^2.33.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
@@ -73,6 +74,23 @@
|
||||
"typescript": ">=3.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-url": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-url/-/plugin-url-6.1.0.tgz",
|
||||
"integrity": "sha512-FJNWBnBB7nLzbcaGmu1no+U/LlRR67TtgfRFP+VEKSrWlDTE6n9jMns/N4Q/VL6l4x6kTHQX4HQfwTcldaAfHQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^3.1.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"mime": "^2.4.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rollup": "^1.20.0||^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
|
||||
@@ -264,12 +282,39 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
@@ -382,6 +427,15 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
|
||||
@@ -512,6 +566,17 @@
|
||||
"resolve": "^1.17.0"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-url": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-url/-/plugin-url-6.1.0.tgz",
|
||||
"integrity": "sha512-FJNWBnBB7nLzbcaGmu1no+U/LlRR67TtgfRFP+VEKSrWlDTE6n9jMns/N4Q/VL6l4x6kTHQX4HQfwTcldaAfHQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.1.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"mime": "^2.4.6"
|
||||
}
|
||||
},
|
||||
"@rollup/pluginutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
|
||||
@@ -665,12 +730,27 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"semver": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
@@ -740,6 +820,12 @@
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
|
||||
|
@@ -10,6 +10,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
"@rollup/plugin-url": "^6.1.0",
|
||||
"requirejs": "^2.3.6",
|
||||
"rollup": "^2.33.1",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import { readFileSync } from "fs";
|
||||
import { readFileSync } from "fs";
|
||||
import path from "path";
|
||||
|
||||
import typescript from "@rollup/plugin-typescript";
|
||||
import url from '@rollup/plugin-url';
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
|
||||
const input = "src/web";
|
||||
@@ -10,7 +11,7 @@ const requirejs = readFileSync("node_modules/requirejs/require.js");
|
||||
export default {
|
||||
input: [`${input}/index.tsx`],
|
||||
output: {
|
||||
file: "build/rollup/index.js",
|
||||
dir: "build/rollup/",
|
||||
// We bundle requirejs (and config) into the header. It's rather gross
|
||||
// but also works reasonably well.
|
||||
// Also suffix a ?v=${date} onto the end in the event we need to require a specific copy-cat version.
|
||||
@@ -18,7 +19,7 @@ export default {
|
||||
${requirejs}
|
||||
require.config({
|
||||
paths: { copycat: "https://copy-cat.squiddev.cc" },
|
||||
urlArgs: function(id) { return id == "copycat/embed" ? "?v=20211127" : ""; }
|
||||
urlArgs: function(id) { return id == "copycat/embed" ? "?v=20211221" : ""; }
|
||||
});
|
||||
`,
|
||||
format: "amd",
|
||||
@@ -33,12 +34,18 @@ export default {
|
||||
plugins: [
|
||||
typescript(),
|
||||
|
||||
url({
|
||||
include: "**/*.dfpwm",
|
||||
fileName: "[name]-[hash][extname]",
|
||||
publicPath: "/",
|
||||
}),
|
||||
|
||||
{
|
||||
name: "cc-tweaked",
|
||||
async transform(code, file) {
|
||||
// Allow loading files in /mount.
|
||||
const ext = path.extname(file);
|
||||
return ext != '.tsx' && ext != '.ts' && path.dirname(file) === path.resolve(`${input}/mount`)
|
||||
return ext != '.dfpwm' && path.dirname(file) === path.resolve(`${input}/mount`)
|
||||
? `export default ${JSON.stringify(code)};\n`
|
||||
: null;
|
||||
},
|
||||
|
@@ -38,7 +38,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.minecraftforge.fmllegacy.server.ServerLifecycleHooks;
|
||||
import net.minecraftforge.server.ServerLifecycleHooks;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.File;
|
||||
|
@@ -5,9 +5,8 @@
|
||||
*/
|
||||
package dan200.computercraft.api.lua;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
import static dan200.computercraft.api.lua.LuaValues.*;
|
||||
|
@@ -23,7 +23,7 @@ public interface GenericPeripheral extends GenericSource
|
||||
* Get the type of the exposed peripheral.
|
||||
*
|
||||
* Unlike normal {@link IPeripheral}s, {@link GenericPeripheral} do not have to have a type. By default, the
|
||||
* resulting peripheral uses the resource name of the wrapped {@link BlockEntity} (for instance {@literal minecraft:chest}).
|
||||
* resulting peripheral uses the resource name of the wrapped {@link BlockEntity} (for instance {@code minecraft:chest}).
|
||||
*
|
||||
* However, in some cases it may be more appropriate to specify a more readable name. Overriding this method allows
|
||||
* you to do so.
|
||||
|
@@ -63,7 +63,7 @@ public final class PeripheralType
|
||||
* Create a new non-empty peripheral type with additional traits.
|
||||
*
|
||||
* @param type The name of the type.
|
||||
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@literal "inventory"}.
|
||||
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@code "inventory"}.
|
||||
* @return The constructed peripheral type.
|
||||
*/
|
||||
public static PeripheralType ofType( @Nonnull String type, Collection<String> additionalTypes )
|
||||
@@ -76,7 +76,7 @@ public final class PeripheralType
|
||||
* Create a new non-empty peripheral type with additional traits.
|
||||
*
|
||||
* @param type The name of the type.
|
||||
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@literal "inventory"}.
|
||||
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@code "inventory"}.
|
||||
* @return The constructed peripheral type.
|
||||
*/
|
||||
public static PeripheralType ofType( @Nonnull String type, @Nonnull String... additionalTypes )
|
||||
@@ -88,7 +88,7 @@ public final class PeripheralType
|
||||
/**
|
||||
* Create a new peripheral type with no primary type but additional traits.
|
||||
*
|
||||
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@literal "inventory"}.
|
||||
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@code "inventory"}.
|
||||
* @return The constructed peripheral type.
|
||||
*/
|
||||
public static PeripheralType ofAdditional( Collection<String> additionalTypes )
|
||||
@@ -99,7 +99,7 @@ public final class PeripheralType
|
||||
/**
|
||||
* Create a new peripheral type with no primary type but additional traits.
|
||||
*
|
||||
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@literal "inventory"}.
|
||||
* @param additionalTypes Additional types, or "traits" of this peripheral. For instance, {@code "inventory"}.
|
||||
* @return The constructed peripheral type.
|
||||
*/
|
||||
public static PeripheralType ofAdditional( @Nonnull String... additionalTypes )
|
||||
@@ -108,7 +108,7 @@ public final class PeripheralType
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of this peripheral type. This may be {@literal null}.
|
||||
* Get the name of this peripheral type. This may be {@code null}.
|
||||
*
|
||||
* @return The type of this peripheral.
|
||||
*/
|
||||
|
@@ -6,6 +6,7 @@
|
||||
package dan200.computercraft.client;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.sound.SpeakerManager;
|
||||
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
|
||||
@@ -22,7 +23,7 @@ public class ClientHooks
|
||||
if( event.getWorld().isClientSide() )
|
||||
{
|
||||
ClientMonitor.destroyAll();
|
||||
SoundManager.reset();
|
||||
SpeakerManager.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -32,7 +32,7 @@ import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ColorHandlerEvent;
|
||||
import net.minecraftforge.client.event.EntityRenderersEvent;
|
||||
import net.minecraftforge.client.event.ModelRegistryEvent;
|
||||
import net.minecraftforge.client.model.ModelLoader;
|
||||
import net.minecraftforge.client.model.ForgeModelBakery;
|
||||
import net.minecraftforge.client.model.ModelLoaderRegistry;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
@@ -76,7 +76,7 @@ public final class ClientRegistry
|
||||
ModelLoaderRegistry.registerLoader( new ResourceLocation( ComputerCraft.MOD_ID, "turtle" ), TurtleModelLoader.INSTANCE );
|
||||
for( String model : EXTRA_MODELS )
|
||||
{
|
||||
ModelLoader.addSpecialModel( new ModelResourceLocation( new ResourceLocation( ComputerCraft.MOD_ID, model ), "inventory" ) );
|
||||
ForgeModelBakery.addSpecialModel( new ModelResourceLocation( new ResourceLocation( ComputerCraft.MOD_ID, model ), "inventory" ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,84 +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;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.sounds.AbstractSoundInstance;
|
||||
import net.minecraft.client.resources.sounds.SoundInstance;
|
||||
import net.minecraft.client.resources.sounds.TickableSoundInstance;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
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, Vec3 position, ResourceLocation event, float volume, float pitch )
|
||||
{
|
||||
var 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 )
|
||||
{
|
||||
SoundInstance sound = sounds.remove( source );
|
||||
if( sound == null ) return;
|
||||
|
||||
Minecraft.getInstance().getSoundManager().stop( sound );
|
||||
}
|
||||
|
||||
public static void moveSound( UUID source, Vec3 position )
|
||||
{
|
||||
MoveableSound sound = sounds.get( source );
|
||||
if( sound != null ) sound.setPosition( position );
|
||||
}
|
||||
|
||||
public static void reset()
|
||||
{
|
||||
sounds.clear();
|
||||
}
|
||||
|
||||
private static class MoveableSound extends AbstractSoundInstance implements TickableSoundInstance
|
||||
{
|
||||
protected MoveableSound( ResourceLocation sound, Vec3 position, float volume, float pitch )
|
||||
{
|
||||
super( sound, SoundSource.RECORDS );
|
||||
setPosition( position );
|
||||
this.volume = volume;
|
||||
this.pitch = pitch;
|
||||
attenuation = Attenuation.LINEAR;
|
||||
}
|
||||
|
||||
void setPosition( Vec3 position )
|
||||
{
|
||||
x = (float) position.x();
|
||||
y = (float) position.y();
|
||||
z = (float) position.z();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStopped()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -173,7 +173,6 @@ public abstract class ComputerScreenBase<T extends ContainerComputerBase> extend
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.rewind();
|
||||
toUpload.add( new FileUpload( name, buffer, digest ) );
|
||||
}
|
||||
catch( IOException e )
|
||||
|
@@ -71,12 +71,12 @@ public class DynamicImageButton extends Button
|
||||
RenderSystem.disableDepthTest();
|
||||
|
||||
int yTex = yTexStart;
|
||||
if( isHovered() ) yTex += yDiffTex;
|
||||
if( isHoveredOrFocused() ) yTex += yDiffTex;
|
||||
|
||||
blit( stack, x, y, xTexStart.getAsInt(), yTex, width, height, textureWidth, textureHeight );
|
||||
RenderSystem.enableDepthTest();
|
||||
|
||||
if( isHovered() ) renderToolTip( stack, mouseX, mouseY );
|
||||
if( isHovered ) renderToolTip( stack, mouseX, mouseY );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@@ -45,8 +45,8 @@ public final class CableHighlightRenderer
|
||||
{
|
||||
BlockHitResult hit = event.getTarget();
|
||||
BlockPos pos = hit.getBlockPos();
|
||||
Level world = event.getInfo().getEntity().getCommandSenderWorld();
|
||||
Camera info = event.getInfo();
|
||||
Level world = event.getCamera().getEntity().getCommandSenderWorld();
|
||||
Camera info = event.getCamera();
|
||||
|
||||
BlockState state = world.getBlockState( pos );
|
||||
|
||||
@@ -67,9 +67,9 @@ public final class CableHighlightRenderer
|
||||
double yOffset = pos.getY() - cameraPos.y();
|
||||
double zOffset = pos.getZ() - cameraPos.z();
|
||||
|
||||
VertexConsumer buffer = event.getBuffers().getBuffer( RenderType.lines() );
|
||||
Matrix4f matrix4f = event.getMatrix().last().pose();
|
||||
Matrix3f normal = event.getMatrix().last().normal();
|
||||
VertexConsumer buffer = event.getMultiBufferSource().getBuffer( RenderType.lines() );
|
||||
Matrix4f matrix4f = event.getPoseStack().last().pose();
|
||||
Matrix3f normal = event.getPoseStack().last().normal();
|
||||
// TODO: Can we just accesstransformer out LevelRenderer.renderShape?
|
||||
shape.forAllEdges( ( x1, y1, z1, x2, y2, z2 ) -> {
|
||||
float xDelta = (float) (x2 - x1);
|
||||
|
@@ -48,7 +48,7 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer
|
||||
|
||||
event.setCanceled( true );
|
||||
INSTANCE.renderItemFirstPerson(
|
||||
event.getMatrixStack(), event.getBuffers(), event.getLight(),
|
||||
event.getPoseStack(), event.getMultiBufferSource(), event.getPackedLight(),
|
||||
event.getHand(), event.getInterpolatedPitch(), event.getEquipProgress(), event.getSwingProgress(), event.getItemStack()
|
||||
);
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ public final class ItemPrintoutRenderer extends ItemMapLikeRenderer
|
||||
|
||||
event.setCanceled( true );
|
||||
INSTANCE.renderItemFirstPerson(
|
||||
event.getMatrixStack(), event.getBuffers(), event.getLight(),
|
||||
event.getPoseStack(), event.getMultiBufferSource(), event.getPackedLight(),
|
||||
event.getHand(), event.getInterpolatedPitch(), event.getEquipProgress(), event.getSwingProgress(), event.getItemStack()
|
||||
);
|
||||
}
|
||||
@@ -63,11 +63,11 @@ public final class ItemPrintoutRenderer extends ItemMapLikeRenderer
|
||||
@SubscribeEvent
|
||||
public static void onRenderInFrame( RenderItemInFrameEvent event )
|
||||
{
|
||||
ItemStack stack = event.getItem();
|
||||
ItemStack stack = event.getItemStack();
|
||||
if( !(stack.getItem() instanceof ItemPrintout) ) return;
|
||||
event.setCanceled( true );
|
||||
|
||||
PoseStack transform = event.getMatrix();
|
||||
PoseStack transform = event.getPoseStack();
|
||||
|
||||
// Move a little bit forward to ensure we're not clipping with the frame
|
||||
transform.translate( 0.0f, 0.0f, -0.001f );
|
||||
@@ -75,8 +75,8 @@ public final class ItemPrintoutRenderer extends ItemMapLikeRenderer
|
||||
transform.scale( 0.95f, 0.95f, -0.95f );
|
||||
transform.translate( -0.5f, -0.5f, 0.0f );
|
||||
|
||||
int light = event.getEntityItemFrame().getType() == EntityType.GLOW_ITEM_FRAME ? 0xf000d2 : event.getLight(); // See getLightVal.
|
||||
drawPrintout( transform, event.getBuffers(), stack, light );
|
||||
int light = event.getItemFrameEntity().getType() == EntityType.GLOW_ITEM_FRAME ? 0xf000d2 : event.getPackedLight(); // See getLightVal.
|
||||
drawPrintout( transform, event.getMultiBufferSource(), stack, light );
|
||||
}
|
||||
|
||||
private static void drawPrintout( PoseStack transform, MultiBufferSource render, ItemStack stack, int light )
|
||||
|
@@ -41,9 +41,9 @@ public final class MonitorHighlightRenderer
|
||||
public static void drawHighlight( DrawSelectionEvent.HighlightBlock event )
|
||||
{
|
||||
// Preserve normal behaviour when crouching.
|
||||
if( event.getInfo().getEntity().isCrouching() ) return;
|
||||
if( event.getCamera().getEntity().isCrouching() ) return;
|
||||
|
||||
Level world = event.getInfo().getEntity().getCommandSenderWorld();
|
||||
Level world = event.getCamera().getEntity().getCommandSenderWorld();
|
||||
BlockPos pos = event.getTarget().getBlockPos();
|
||||
|
||||
BlockEntity tile = world.getBlockEntity( pos );
|
||||
@@ -60,13 +60,13 @@ public final class MonitorHighlightRenderer
|
||||
if( monitor.getYIndex() != 0 ) faces.remove( monitor.getDown().getOpposite() );
|
||||
if( monitor.getYIndex() != monitor.getHeight() - 1 ) faces.remove( monitor.getDown() );
|
||||
|
||||
PoseStack transformStack = event.getMatrix();
|
||||
Vec3 cameraPos = event.getInfo().getPosition();
|
||||
PoseStack transformStack = event.getPoseStack();
|
||||
Vec3 cameraPos = event.getCamera().getPosition();
|
||||
transformStack.pushPose();
|
||||
transformStack.translate( pos.getX() - cameraPos.x(), pos.getY() - cameraPos.y(), pos.getZ() - cameraPos.z() );
|
||||
|
||||
// I wish I could think of a better way to do this
|
||||
VertexConsumer buffer = event.getBuffers().getBuffer( RenderType.lines() );
|
||||
VertexConsumer buffer = event.getMultiBufferSource().getBuffer( RenderType.lines() );
|
||||
Matrix4f transform = transformStack.last().pose();
|
||||
Matrix3f normal = transformStack.last().normal();
|
||||
if( faces.contains( NORTH ) || faces.contains( WEST ) ) line( buffer, transform, normal, 0, 0, 0, UP );
|
||||
|
133
src/main/java/dan200/computercraft/client/sound/DfpwmStream.java
Normal file
133
src/main/java/dan200/computercraft/client/sound/DfpwmStream.java
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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.sound;
|
||||
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.client.sounds.AudioStream;
|
||||
import org.lwjgl.BufferUtils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
|
||||
class DfpwmStream implements AudioStream
|
||||
{
|
||||
public static final int SAMPLE_RATE = SpeakerPeripheral.SAMPLE_RATE;
|
||||
|
||||
private static final int PREC = 10;
|
||||
private static final int LPF_STRENGTH = 140;
|
||||
|
||||
private static final AudioFormat MONO_16 = new AudioFormat( SAMPLE_RATE, 16, 1, true, false );
|
||||
|
||||
private final Queue<ByteBuffer> buffers = new ArrayDeque<>( 2 );
|
||||
|
||||
private int charge = 0; // q
|
||||
private int strength = 0; // s
|
||||
private int lowPassCharge;
|
||||
private boolean previousBit = false;
|
||||
|
||||
DfpwmStream()
|
||||
{
|
||||
}
|
||||
|
||||
void push( @Nonnull ByteBuf input )
|
||||
{
|
||||
int readable = input.readableBytes();
|
||||
ByteBuffer output = ByteBuffer.allocate( readable * 16 ).order( ByteOrder.nativeOrder() );
|
||||
|
||||
for( int i = 0; i < readable; i++ )
|
||||
{
|
||||
byte inputByte = input.readByte();
|
||||
for( int j = 0; j < 8; j++ )
|
||||
{
|
||||
boolean currentBit = (inputByte & 1) != 0;
|
||||
int target = currentBit ? 127 : -128;
|
||||
|
||||
// q' <- q + (s * (t - q) + 128)/256
|
||||
int nextCharge = charge + ((strength * (target - charge) + (1 << (PREC - 1))) >> PREC);
|
||||
if( nextCharge == charge && nextCharge != target ) nextCharge += currentBit ? 1 : -1;
|
||||
|
||||
int z = currentBit == previousBit ? (1 << PREC) - 1 : 0;
|
||||
|
||||
int nextStrength = strength;
|
||||
if( strength != z ) nextStrength += currentBit == previousBit ? 1 : -1;
|
||||
if( nextStrength < 2 << (PREC - 8) ) nextStrength = 2 << (PREC - 8);
|
||||
|
||||
// Apply antijerk
|
||||
int chargeWithAntijerk = currentBit == previousBit
|
||||
? nextCharge
|
||||
: nextCharge + charge + 1 >> 1;
|
||||
|
||||
// And low pass filter: outQ <- outQ + ((expectedOutput - outQ) x 140 / 256)
|
||||
lowPassCharge += ((chargeWithAntijerk - lowPassCharge) * LPF_STRENGTH + 0x80) >> 8;
|
||||
|
||||
charge = nextCharge;
|
||||
strength = nextStrength;
|
||||
previousBit = currentBit;
|
||||
|
||||
// Ideally we'd generate an 8-bit audio buffer. However, as we're piggybacking on top of another
|
||||
// audio stream (which uses 16 bit audio), we need to keep in the same format.
|
||||
output.putShort( (short) ((byte) (lowPassCharge & 0xFF) << 8) );
|
||||
|
||||
inputByte >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
output.flip();
|
||||
synchronized( this )
|
||||
{
|
||||
buffers.add( output );
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public AudioFormat getFormat()
|
||||
{
|
||||
return MONO_16;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public synchronized ByteBuffer read( int capacity )
|
||||
{
|
||||
ByteBuffer result = BufferUtils.createByteBuffer( capacity );
|
||||
while( result.hasRemaining() )
|
||||
{
|
||||
ByteBuffer head = buffers.peek();
|
||||
if( head == null ) break;
|
||||
|
||||
int toRead = Math.min( head.remaining(), result.remaining() );
|
||||
result.put( result.position(), head, head.position(), toRead );
|
||||
result.position( result.position() + toRead );
|
||||
head.position( head.position() + toRead );
|
||||
|
||||
if( head.hasRemaining() ) break;
|
||||
buffers.remove();
|
||||
}
|
||||
|
||||
result.flip();
|
||||
|
||||
// This is naughty, but ensures we're not enqueuing empty buffers when the stream is exhausted.
|
||||
return result.remaining() == 0 ? null : result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
buffers.clear();
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return buffers.isEmpty();
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.sound;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/**
|
||||
* An instance of a speaker, which is either playing a {@link DfpwmStream} stream or a normal sound.
|
||||
*/
|
||||
public class SpeakerInstance
|
||||
{
|
||||
public static final ResourceLocation DFPWM_STREAM = new ResourceLocation( ComputerCraft.MOD_ID, "speaker.dfpwm_fake_audio_should_not_be_played" );
|
||||
|
||||
private DfpwmStream currentStream;
|
||||
private SpeakerSound sound;
|
||||
|
||||
SpeakerInstance()
|
||||
{
|
||||
}
|
||||
|
||||
public synchronized void pushAudio( ByteBuf buffer )
|
||||
{
|
||||
SpeakerSound sound = this.sound;
|
||||
|
||||
DfpwmStream stream = currentStream;
|
||||
if( stream == null ) stream = currentStream = new DfpwmStream();
|
||||
boolean exhausted = stream.isEmpty();
|
||||
currentStream.push( buffer );
|
||||
|
||||
// If we've got nothing left in the buffer, enqueue an additional one just in case.
|
||||
if( exhausted && sound != null && sound.stream == stream && sound.channel != null )
|
||||
{
|
||||
sound.executor.execute( () -> {
|
||||
if( !sound.channel.stopped() ) sound.channel.pumpBuffers( 1 );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
public void playAudio( Vec3 position, float volume )
|
||||
{
|
||||
var soundManager = Minecraft.getInstance().getSoundManager();
|
||||
|
||||
if( sound != null && sound.stream != currentStream )
|
||||
{
|
||||
soundManager.stop( sound );
|
||||
sound = null;
|
||||
}
|
||||
|
||||
if( sound != null && !soundManager.isActive( sound ) ) sound = null;
|
||||
|
||||
if( sound == null && currentStream != null )
|
||||
{
|
||||
sound = new SpeakerSound( DFPWM_STREAM, currentStream, position, volume, 1.0f );
|
||||
soundManager.play( sound );
|
||||
}
|
||||
}
|
||||
|
||||
public void playSound( Vec3 position, ResourceLocation location, float volume, float pitch )
|
||||
{
|
||||
var soundManager = Minecraft.getInstance().getSoundManager();
|
||||
currentStream = null;
|
||||
|
||||
if( sound != null )
|
||||
{
|
||||
soundManager.stop( sound );
|
||||
sound = null;
|
||||
}
|
||||
|
||||
sound = new SpeakerSound( location, null, position, volume, pitch );
|
||||
soundManager.play( sound );
|
||||
}
|
||||
|
||||
void setPosition( Vec3 position )
|
||||
{
|
||||
if( sound != null ) sound.setPosition( position );
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
if( sound != null ) Minecraft.getInstance().getSoundManager().stop( sound );
|
||||
|
||||
currentStream = null;
|
||||
sound = null;
|
||||
}
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.sound;
|
||||
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.sound.PlayStreamingSourceEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Maps speakers source IDs to a {@link SpeakerInstance}.
|
||||
*/
|
||||
@Mod.EventBusSubscriber( Dist.CLIENT )
|
||||
public class SpeakerManager
|
||||
{
|
||||
private static final Map<UUID, SpeakerInstance> sounds = new ConcurrentHashMap<>();
|
||||
|
||||
@SubscribeEvent
|
||||
public static void playStreaming( PlayStreamingSourceEvent event )
|
||||
{
|
||||
if( !(event.getSound() instanceof SpeakerSound sound) ) return;
|
||||
if( sound.stream == null ) return;
|
||||
|
||||
event.getChannel().attachBufferStream( sound.stream );
|
||||
event.getChannel().play();
|
||||
|
||||
sound.channel = event.getChannel();
|
||||
sound.executor = event.getEngine().executor;
|
||||
}
|
||||
|
||||
public static SpeakerInstance getSound( UUID source )
|
||||
{
|
||||
return sounds.computeIfAbsent( source, x -> new SpeakerInstance() );
|
||||
}
|
||||
|
||||
public static void stopSound( UUID source )
|
||||
{
|
||||
SpeakerInstance sound = sounds.remove( source );
|
||||
if( sound != null ) sound.stop();
|
||||
}
|
||||
|
||||
public static void moveSound( UUID source, Vec3 position )
|
||||
{
|
||||
SpeakerInstance sound = sounds.get( source );
|
||||
if( sound != null ) sound.setPosition( position );
|
||||
}
|
||||
|
||||
public static void reset()
|
||||
{
|
||||
sounds.clear();
|
||||
}
|
||||
}
|
@@ -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.client.sound;
|
||||
|
||||
import com.mojang.blaze3d.audio.Channel;
|
||||
import net.minecraft.client.resources.sounds.AbstractSoundInstance;
|
||||
import net.minecraft.client.resources.sounds.TickableSoundInstance;
|
||||
import net.minecraft.client.sounds.AudioStream;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class SpeakerSound extends AbstractSoundInstance implements TickableSoundInstance
|
||||
{
|
||||
Channel channel;
|
||||
Executor executor;
|
||||
DfpwmStream stream;
|
||||
|
||||
SpeakerSound( ResourceLocation sound, DfpwmStream stream, Vec3 position, float volume, float pitch )
|
||||
{
|
||||
super( sound, SoundSource.RECORDS );
|
||||
setPosition( position );
|
||||
this.stream = stream;
|
||||
this.volume = volume;
|
||||
this.pitch = pitch;
|
||||
attenuation = Attenuation.LINEAR;
|
||||
}
|
||||
|
||||
void setPosition( Vec3 position )
|
||||
{
|
||||
x = (float) position.x();
|
||||
y = (float) position.y();
|
||||
z = (float) position.z();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStopped()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick()
|
||||
{
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public AudioStream getStream()
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
}
|
@@ -30,7 +30,36 @@ import java.util.OptionalLong;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* The FS API allows you to manipulate files and the filesystem.
|
||||
* The FS API provides access to the computer's files and filesystem, allowing you to manipulate files, directories and
|
||||
* paths. This includes:
|
||||
*
|
||||
* <ul>
|
||||
* <li>**Reading and writing files:** Call {@link #open} to obtain a file "handle", which can be used to read from or
|
||||
* write to a file.</li>
|
||||
* <li>**Path manipulation:** {@link #combine}, {@link #getName} and {@link #getDir} allow you to manipulate file
|
||||
* paths, joining them together or extracting components.</li>
|
||||
* <li>**Querying paths:** For instance, checking if a file exists, or whether it's a directory. See {@link #getSize},
|
||||
* {@link #exists}, {@link #isDir}, {@link #isReadOnly} and {@link #attributes}.</li>
|
||||
* <li>**File and directory manipulation:** For instance, moving or copying files. See {@link #makeDir}, {@link #move},
|
||||
* {@link #copy} and {@link #delete}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* :::note
|
||||
* All functions in the API work on absolute paths, and do not take the @{shell.dir|current directory} into account.
|
||||
* You can use @{shell.resolve} to convert a relative path into an absolute one.
|
||||
* :::
|
||||
*
|
||||
* ## Mounts
|
||||
* While a computer can only have one hard drive and filesystem, other filesystems may be "mounted" inside it. For
|
||||
* instance, the {@link dan200.computercraft.shared.peripheral.diskdrive.DiskDrivePeripheral drive peripheral} mounts
|
||||
* its disk's contents at {@code "disk/"}, {@code "disk1/"}, etc...
|
||||
*
|
||||
* You can see which mount a path belongs to with the {@link #getDrive} function. This returns {@code "hdd"} for the
|
||||
* computer's main filesystem ({@code "/"}), {@code "rom"} for the rom ({@code "rom/"}).
|
||||
*
|
||||
* Most filesystems have a limited capacity, operations which would cause that capacity to be reached (such as writing
|
||||
* an incredibly large file) will fail. You can see a mount's capacity with {@link #getCapacity} and the remaining
|
||||
* space with {@link #getFreeSpace}.
|
||||
*
|
||||
* @cc.module fs
|
||||
*/
|
||||
@@ -440,6 +469,12 @@ public class FSAPI implements ILuaAPI
|
||||
* @return The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files.
|
||||
* @throws LuaException If the path doesn't exist.
|
||||
* @cc.treturn string The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files.
|
||||
* @cc.usage Print the drives of a couple of mounts:
|
||||
*
|
||||
* <pre>{@code
|
||||
* print("/: " .. fs.getDrive("/"))
|
||||
* print("/rom/: " .. fs.getDrive("rom"))
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction
|
||||
public final Object[] getDrive( String path ) throws LuaException
|
||||
|
@@ -22,7 +22,7 @@ import dan200.computercraft.core.computer.ComputerSide;
|
||||
* as those from Project:Red. These allow you to send 16 separate on/off signals. Each channel corresponds to a
|
||||
* colour, with the first being @{colors.white} and the last @{colors.black}.
|
||||
*
|
||||
* Whenever a redstone input changes, a {@code redstone} event will be fired. This may be used instead of repeativly
|
||||
* Whenever a redstone input changes, a @{event!redstone} event will be fired. This may be used instead of repeativly
|
||||
* polling.
|
||||
*
|
||||
* This module may also be referred to as {@code rs}. For example, one may call {@code rs.getSides()} instead of
|
||||
|
@@ -15,7 +15,7 @@ import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketCl
|
||||
import static io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker.MAX_WINDOW_SIZE;
|
||||
|
||||
/**
|
||||
* An alternative to {@link WebSocketClientCompressionHandler} which supports the {@literal client_no_context_takeover}
|
||||
* An alternative to {@link WebSocketClientCompressionHandler} which supports the {@code client_no_context_takeover}
|
||||
* extension. Makes CC <em>slightly</em> more flexible.
|
||||
*/
|
||||
@ChannelHandler.Sharable
|
||||
|
@@ -14,8 +14,8 @@ import dan200.computercraft.core.computer.TimeoutState;
|
||||
import dan200.computercraft.core.tracking.Tracking;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
import dan200.computercraft.shared.util.ThreadUtils;
|
||||
import org.squiddev.cobalt.*;
|
||||
import org.squiddev.cobalt.LuaTable;
|
||||
import org.squiddev.cobalt.*;
|
||||
import org.squiddev.cobalt.compiler.CompileException;
|
||||
import org.squiddev.cobalt.compiler.LoadState;
|
||||
import org.squiddev.cobalt.debug.DebugFrame;
|
||||
|
@@ -26,7 +26,7 @@ import net.minecraft.world.level.storage.loot.predicates.AlternativeLootItemCond
|
||||
import net.minecraft.world.level.storage.loot.predicates.ExplosionCondition;
|
||||
import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition;
|
||||
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
|
||||
import net.minecraftforge.fmllegacy.RegistryObject;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
|
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.mixin;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import dan200.computercraft.shared.Registry;
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.BlockCable;
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.block.BlockModelShaper;
|
||||
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
|
||||
import net.minecraft.client.renderer.block.ModelBlockRenderer;
|
||||
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraftforge.client.model.data.IModelData;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Provides custom block breaking progress for modems, so it only applies to the current part.
|
||||
*
|
||||
* @see BlockRenderDispatcher#renderBreakingTexture(BlockState, BlockPos, BlockAndTintGetter, PoseStack, VertexConsumer, IModelData)
|
||||
*/
|
||||
@Mixin( BlockRenderDispatcher.class )
|
||||
public class BlockRenderDispatcherMixin
|
||||
{
|
||||
@Shadow
|
||||
private final Random random;
|
||||
@Shadow
|
||||
private final BlockModelShaper blockModelShaper;
|
||||
@Shadow
|
||||
private final ModelBlockRenderer modelRenderer;
|
||||
|
||||
public BlockRenderDispatcherMixin( Random random, BlockModelShaper blockModelShaper, ModelBlockRenderer modelRenderer )
|
||||
{
|
||||
this.random = random;
|
||||
this.blockModelShaper = blockModelShaper;
|
||||
this.modelRenderer = modelRenderer;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "name=/^renderBreakingTexture/ desc=/IModelData;\\)V$/",
|
||||
at = @At( "HEAD" ),
|
||||
cancellable = true,
|
||||
require = 0 // This isn't critical functionality, so don't worry if we can't apply it.
|
||||
)
|
||||
public void renderBlockDamage(
|
||||
BlockState state, BlockPos pos, BlockAndTintGetter world, PoseStack pose, VertexConsumer buffers, IModelData modelData,
|
||||
CallbackInfo info
|
||||
)
|
||||
{
|
||||
// Only apply to cables which have both a cable and modem
|
||||
if( state.getBlock() != Registry.ModBlocks.CABLE.get()
|
||||
|| !state.getValue( BlockCable.CABLE )
|
||||
|| state.getValue( BlockCable.MODEM ) == CableModemVariant.None
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HitResult hit = Minecraft.getInstance().hitResult;
|
||||
if( hit == null || hit.getType() != HitResult.Type.BLOCK ) return;
|
||||
BlockPos hitPos = ((BlockHitResult) hit).getBlockPos();
|
||||
|
||||
if( !hitPos.equals( pos ) ) return;
|
||||
|
||||
info.cancel();
|
||||
BlockState newState = WorldUtil.isVecInside( CableShapes.getModemShape( state ), hit.getLocation().subtract( pos.getX(), pos.getY(), pos.getZ() ) )
|
||||
? state.getBlock().defaultBlockState().setValue( BlockCable.MODEM, state.getValue( BlockCable.MODEM ) )
|
||||
: state.setValue( BlockCable.MODEM, CableModemVariant.None );
|
||||
|
||||
BakedModel model = blockModelShaper.getBlockModel( newState );
|
||||
long seed = newState.getSeed( pos );
|
||||
modelRenderer.tesselateBlock( world, model, newState, pos, pose, buffers, true, random, seed, OverlayTexture.NO_OVERLAY, modelData );
|
||||
}
|
||||
}
|
@@ -28,11 +28,11 @@ import net.minecraft.world.level.storage.loot.entries.LootTableReference;
|
||||
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
|
||||
import net.minecraftforge.event.*;
|
||||
import net.minecraftforge.event.entity.player.PlayerContainerEvent;
|
||||
import net.minecraftforge.event.server.ServerStartedEvent;
|
||||
import net.minecraftforge.event.server.ServerStartingEvent;
|
||||
import net.minecraftforge.event.server.ServerStoppedEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fmlserverevents.FMLServerStartedEvent;
|
||||
import net.minecraftforge.fmlserverevents.FMLServerStartingEvent;
|
||||
import net.minecraftforge.fmlserverevents.FMLServerStoppedEvent;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
@@ -84,7 +84,7 @@ public final class CommonHooks
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerStarting( FMLServerStartingEvent event )
|
||||
public static void onServerStarting( ServerStartingEvent event )
|
||||
{
|
||||
MinecraftServer server = event.getServer();
|
||||
if( server instanceof DedicatedServer dediServer && dediServer.getProperties().enableJmxMonitoring )
|
||||
@@ -94,7 +94,7 @@ public final class CommonHooks
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerStarted( FMLServerStartedEvent event )
|
||||
public static void onServerStarted( ServerStartedEvent event )
|
||||
{
|
||||
ComputerCraft.serverComputerRegistry.reset();
|
||||
WirelessNetwork.resetNetworks();
|
||||
@@ -104,7 +104,7 @@ public final class CommonHooks
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerStopped( FMLServerStoppedEvent event )
|
||||
public static void onServerStopped( ServerStoppedEvent event )
|
||||
{
|
||||
ComputerCraft.serverComputerRegistry.reset();
|
||||
WirelessNetwork.resetNetworks();
|
||||
|
@@ -96,11 +96,11 @@ import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraftforge.fmllegacy.RegistryObject;
|
||||
import net.minecraftforge.items.CapabilityItemHandler;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
import net.minecraftforge.registries.RegistryBuilder;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@@ -400,9 +400,6 @@ public final class Registry
|
||||
|
||||
CauldronInteraction.WATER.put( ModItems.TURTLE_NORMAL.get(), ItemTurtle.CAULDRON_INTERACTION );
|
||||
CauldronInteraction.WATER.put( ModItems.TURTLE_ADVANCED.get(), ItemTurtle.CAULDRON_INTERACTION );
|
||||
|
||||
// Mod integration code.
|
||||
// TODO: if( ModList.get().isLoaded( MoreRedIntegration.MOD_ID ) ) MoreRedIntegration.initialise();
|
||||
}
|
||||
|
||||
public static void registerLoot()
|
||||
|
@@ -13,7 +13,7 @@ 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 net.minecraftforge.fmllegacy.server.ServerLifecycleHooks;
|
||||
import net.minecraftforge.server.ServerLifecycleHooks;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
@@ -19,7 +19,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraftforge.fmllegacy.RegistryObject;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
@@ -12,11 +12,11 @@ import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
@@ -36,7 +36,7 @@ public abstract class TileGeneric extends BlockEntity
|
||||
setChanged();
|
||||
BlockPos pos = getBlockPos();
|
||||
BlockState state = getBlockState();
|
||||
getLevel().sendBlockUpdated( pos, state, state, Constants.BlockFlags.DEFAULT );
|
||||
getLevel().sendBlockUpdated( pos, state, state, Block.UPDATE_ALL );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@@ -76,7 +76,8 @@ public abstract class TileGeneric extends BlockEntity
|
||||
@Override
|
||||
public final void onDataPacket( Connection net, ClientboundBlockEntityDataPacket packet )
|
||||
{
|
||||
if( packet.getType() == 0 ) handleUpdateTag( packet.getTag() );
|
||||
var tag = packet.getTag();
|
||||
if( tag != null ) handleUpdateTag( tag );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -18,7 +18,7 @@ import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||
import net.minecraft.world.level.block.state.properties.EnumProperty;
|
||||
import net.minecraftforge.fmllegacy.RegistryObject;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
@@ -32,7 +32,7 @@ import net.minecraft.world.level.storage.loot.LootContext;
|
||||
import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.fmllegacy.RegistryObject;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -119,7 +119,7 @@ public abstract class BlockComputerBase<T extends TileComputerBase> extends Bloc
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ItemStack getPickBlock( BlockState state, HitResult target, BlockGetter world, BlockPos pos, Player player )
|
||||
public ItemStack getCloneItemStack( BlockState state, HitResult target, BlockGetter world, BlockPos pos, Player player )
|
||||
{
|
||||
BlockEntity tile = world.getBlockEntity( pos );
|
||||
if( tile instanceof TileComputerBase )
|
||||
@@ -128,7 +128,7 @@ public abstract class BlockComputerBase<T extends TileComputerBase> extends Bloc
|
||||
if( !result.isEmpty() ) return result;
|
||||
}
|
||||
|
||||
return super.getPickBlock( state, target, world, pos, player );
|
||||
return super.getCloneItemStack( state, target, world, pos, player );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -188,16 +188,15 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
|
||||
|
||||
protected abstract void updateBlockState( ComputerState newState );
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public CompoundTag save( @Nonnull CompoundTag nbt )
|
||||
public void saveAdditional( @Nonnull CompoundTag nbt )
|
||||
{
|
||||
// Save ID, label and power state
|
||||
if( computerID >= 0 ) nbt.putInt( NBT_ID, computerID );
|
||||
if( label != null ) nbt.putString( NBT_LABEL, label );
|
||||
nbt.putBoolean( NBT_ON, on );
|
||||
|
||||
return super.save( nbt );
|
||||
super.saveAdditional( nbt );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -388,7 +387,7 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
|
||||
@Override
|
||||
public final ClientboundBlockEntityDataPacket getUpdatePacket()
|
||||
{
|
||||
return new ClientboundBlockEntityDataPacket( worldPosition, 0, getUpdateTag() );
|
||||
return ClientboundBlockEntityDataPacket.create( this );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@@ -56,7 +56,7 @@ public interface IContainerComputer
|
||||
void continueUpload( @Nonnull UUID uploadId, @Nonnull List<FileSlice> slices );
|
||||
|
||||
/**
|
||||
* Finish off an upload. This either writes the uploaded files or
|
||||
* Finish off an upload. This either writes the uploaded files or informs the user that files will be overwritten.
|
||||
*
|
||||
* @param uploader The player uploading files.
|
||||
* @param uploadId The unique ID of this upload.
|
||||
|
@@ -28,7 +28,7 @@ import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraftforge.fmllegacy.server.ServerLifecycleHooks;
|
||||
import net.minecraftforge.server.ServerLifecycleHooks;
|
||||
import net.minecraftforge.versions.mcp.MCPVersion;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
@@ -53,11 +53,6 @@ public class FileSlice
|
||||
return;
|
||||
}
|
||||
|
||||
bytes.rewind();
|
||||
file.position( offset );
|
||||
file.put( bytes );
|
||||
file.rewind();
|
||||
|
||||
if( bytes.remaining() != 0 ) throw new IllegalStateException( "Should have read the whole buffer" );
|
||||
file.put( offset, bytes, bytes.position(), bytes.remaining() );
|
||||
}
|
||||
}
|
||||
|
@@ -56,7 +56,7 @@ public class FileUpload
|
||||
|
||||
public boolean checksumMatches()
|
||||
{
|
||||
// This is meant to be a checksum. Doesn't need to be cryptographically secure, hence non constant time.
|
||||
// This is meant to be a checksum. Doesn't need to be cryptographically secure, hence non-constant time.
|
||||
byte[] digest = getDigest( bytes );
|
||||
return digest != null && Arrays.equals( checksum, digest );
|
||||
}
|
||||
@@ -66,9 +66,8 @@ public class FileUpload
|
||||
{
|
||||
try
|
||||
{
|
||||
bytes.rewind();
|
||||
MessageDigest digest = MessageDigest.getInstance( "SHA-256" );
|
||||
digest.update( bytes );
|
||||
digest.update( bytes.duplicate() );
|
||||
return digest.digest();
|
||||
}
|
||||
catch( NoSuchAlgorithmException e )
|
||||
|
@@ -1,123 +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.integration.morered;
|
||||
|
||||
import commoble.morered.api.ChanneledPowerSupplier;
|
||||
import commoble.morered.api.MoreRedAPI;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.shared.common.IBundledRedstoneBlock;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
import dan200.computercraft.shared.util.FixedPointTileEntityType;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.ICapabilityProvider;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.event.AttachCapabilitiesEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class MoreRedIntegration
|
||||
{
|
||||
public static final String MOD_ID = "morered";
|
||||
|
||||
private static final ResourceLocation ID = new ResourceLocation( ComputerCraft.MOD_ID, "morered" );
|
||||
|
||||
private static final class BundledPowerSupplier implements ICapabilityProvider, ChanneledPowerSupplier
|
||||
{
|
||||
private final IBundledRedstoneBlock block;
|
||||
private final BlockEntity tile;
|
||||
private LazyOptional<ChanneledPowerSupplier> instance;
|
||||
|
||||
private BundledPowerSupplier( IBundledRedstoneBlock block, BlockEntity tile )
|
||||
{
|
||||
this.block = block;
|
||||
this.tile = tile;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public <T> LazyOptional<T> getCapability( @Nonnull Capability<T> cap, @Nullable Direction side )
|
||||
{
|
||||
if( cap != MoreRedAPI.CHANNELED_POWER_CAPABILITY ) return LazyOptional.empty();
|
||||
|
||||
if( tile.isRemoved() || !block.getBundledRedstoneConnectivity( tile.getLevel(), tile.getBlockPos(), side ) )
|
||||
{
|
||||
return LazyOptional.empty();
|
||||
}
|
||||
|
||||
return (instance == null ? (instance = LazyOptional.of( () -> this )) : instance).cast();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPowerOnChannel( @Nonnull Level world, @Nonnull BlockPos wirePos, @Nonnull BlockState wireState, @Nullable Direction wireFace, int channel )
|
||||
{
|
||||
if( wireFace == null ) return 0;
|
||||
|
||||
BlockPos pos = wirePos.relative( wireFace );
|
||||
BlockState state = world.getBlockState( pos );
|
||||
if( !(state.getBlock() instanceof IBundledRedstoneBlock block) ) return 0;
|
||||
|
||||
return (block.getBundledRedstoneOutput( world, pos, wireFace.getOpposite() ) & (1 << channel)) != 0 ? 31 : 0;
|
||||
}
|
||||
|
||||
void invalidate()
|
||||
{
|
||||
instance = CapabilityUtil.invalidate( instance );
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void attachBlockCapabilities( AttachCapabilitiesEvent<BlockEntity> event )
|
||||
{
|
||||
BlockEntity tile = event.getObject();
|
||||
if( !(tile.getType() instanceof FixedPointTileEntityType) ) return;
|
||||
|
||||
Block block = ((FixedPointTileEntityType<?>) tile.getType()).getBlock();
|
||||
if( !(block instanceof IBundledRedstoneBlock) ) return;
|
||||
|
||||
BundledPowerSupplier provider = new BundledPowerSupplier( (IBundledRedstoneBlock) block, tile );
|
||||
event.addCapability( ID, provider );
|
||||
event.addListener( provider::invalidate );
|
||||
}
|
||||
|
||||
public static void initialise()
|
||||
{
|
||||
MinecraftForge.EVENT_BUS.register( MoreRedIntegration.class );
|
||||
ComputerCraftAPI.registerBundledRedstoneProvider( MoreRedIntegration::getBundledPower );
|
||||
}
|
||||
|
||||
private static int getBundledPower( Level world, BlockPos pos, Direction side )
|
||||
{
|
||||
BlockEntity tile = world.getBlockEntity( pos );
|
||||
if( tile == null ) return -1;
|
||||
|
||||
ChanneledPowerSupplier power = CapabilityUtil.unwrapUnsafe( tile.getCapability( MoreRedAPI.CHANNELED_POWER_CAPABILITY, side ) );
|
||||
if( power == null ) return -1;
|
||||
|
||||
BlockState state = tile.getBlockState();
|
||||
|
||||
// Skip ones already handled by CC. We can do this more efficiently.
|
||||
if( state.getBlock() instanceof IBundledRedstoneBlock ) return -1;
|
||||
|
||||
int mask = 0;
|
||||
for( int i = 0; i < 16; i++ )
|
||||
{
|
||||
mask |= power.getPowerOnChannel( world, pos, state, side, i ) > 0 ? (1 << i) : 0;
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
}
|
@@ -16,11 +16,11 @@ import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkDirection;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkRegistry;
|
||||
import net.minecraftforge.fmllegacy.network.PacketDistributor;
|
||||
import net.minecraftforge.fmllegacy.network.simple.SimpleChannel;
|
||||
import net.minecraftforge.network.NetworkDirection;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkRegistry;
|
||||
import net.minecraftforge.network.PacketDistributor;
|
||||
import net.minecraftforge.network.simple.SimpleChannel;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -57,10 +57,11 @@ public final class NetworkHandler
|
||||
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 );
|
||||
registerMainThread( 19, NetworkDirection.PLAY_TO_CLIENT, UploadResultMessage.class, UploadResultMessage::new );
|
||||
registerMainThread( 16, NetworkDirection.PLAY_TO_CLIENT, SpeakerAudioClientMessage.class, SpeakerAudioClientMessage::new );
|
||||
registerMainThread( 17, NetworkDirection.PLAY_TO_CLIENT, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new );
|
||||
registerMainThread( 18, NetworkDirection.PLAY_TO_CLIENT, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new );
|
||||
registerMainThread( 19, NetworkDirection.PLAY_TO_CLIENT, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new );
|
||||
registerMainThread( 20, NetworkDirection.PLAY_TO_CLIENT, UploadResultMessage.class, UploadResultMessage::new );
|
||||
registerMainThread( 20, NetworkDirection.PLAY_TO_CLIENT, UpgradesLoadedMessage.class, UpgradesLoadedMessage::new );
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,7 @@
|
||||
package dan200.computercraft.shared.network;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -10,7 +10,7 @@ import dan200.computercraft.shared.command.text.TableBuilder;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -7,7 +7,7 @@ package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
public class ComputerDeletedClientMessage extends ComputerClientMessage
|
||||
{
|
||||
|
@@ -6,7 +6,7 @@
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -12,7 +12,7 @@ import net.minecraft.client.player.LocalPlayer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -13,7 +13,7 @@ import net.minecraft.network.chat.TextComponent;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -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.shared.network.client;
|
||||
|
||||
import dan200.computercraft.client.sound.SpeakerManager;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.nio.ByteBuffer;
|
||||
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 SpeakerAudioClientMessage implements NetworkMessage
|
||||
{
|
||||
private final UUID source;
|
||||
private final Vec3 pos;
|
||||
private final ByteBuffer content;
|
||||
private final float volume;
|
||||
|
||||
public SpeakerAudioClientMessage( UUID source, Vec3 pos, float volume, ByteBuffer content )
|
||||
{
|
||||
this.source = source;
|
||||
this.pos = pos;
|
||||
this.content = content;
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
public SpeakerAudioClientMessage( FriendlyByteBuf buf )
|
||||
{
|
||||
source = buf.readUUID();
|
||||
pos = new Vec3( buf.readDouble(), buf.readDouble(), buf.readDouble() );
|
||||
volume = buf.readFloat();
|
||||
|
||||
SpeakerManager.getSound( source ).pushAudio( buf );
|
||||
content = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes( @Nonnull FriendlyByteBuf buf )
|
||||
{
|
||||
buf.writeUUID( source );
|
||||
buf.writeDouble( pos.x() );
|
||||
buf.writeDouble( pos.y() );
|
||||
buf.writeDouble( pos.z() );
|
||||
buf.writeFloat( volume );
|
||||
buf.writeBytes( content.duplicate() );
|
||||
}
|
||||
|
||||
@Override
|
||||
@OnlyIn( Dist.CLIENT )
|
||||
public void handle( NetworkEvent.Context context )
|
||||
{
|
||||
SpeakerManager.getSound( source ).playAudio( pos, volume );
|
||||
}
|
||||
}
|
@@ -5,13 +5,13 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.client.SoundManager;
|
||||
import dan200.computercraft.client.sound.SpeakerManager;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
@@ -53,6 +53,6 @@ public class SpeakerMoveClientMessage implements NetworkMessage
|
||||
@OnlyIn( Dist.CLIENT )
|
||||
public void handle( NetworkEvent.Context context )
|
||||
{
|
||||
SoundManager.moveSound( source, pos );
|
||||
SpeakerManager.moveSound( source, pos );
|
||||
}
|
||||
}
|
||||
|
@@ -5,14 +5,14 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.client.SoundManager;
|
||||
import dan200.computercraft.client.sound.SpeakerManager;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
@@ -66,6 +66,6 @@ public class SpeakerPlayClientMessage implements NetworkMessage
|
||||
@OnlyIn( Dist.CLIENT )
|
||||
public void handle( NetworkEvent.Context context )
|
||||
{
|
||||
SoundManager.playSound( source, pos, sound, volume, pitch );
|
||||
SpeakerManager.getSound( source ).playSound( pos, sound, volume, pitch );
|
||||
}
|
||||
}
|
||||
|
@@ -5,12 +5,12 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.client.SoundManager;
|
||||
import dan200.computercraft.client.sound.SpeakerManager;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
@@ -46,6 +46,6 @@ public class SpeakerStopClientMessage implements NetworkMessage
|
||||
@OnlyIn( Dist.CLIENT )
|
||||
public void handle( NetworkEvent.Context context )
|
||||
{
|
||||
SoundManager.stopSound( source );
|
||||
SpeakerManager.stopSound( source );
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ import dan200.computercraft.shared.UpgradeManager;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.minecraftforge.registries.IForgeRegistry;
|
||||
import net.minecraftforge.registries.RegistryManager;
|
||||
|
||||
|
@@ -13,7 +13,7 @@ import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -12,16 +12,16 @@ import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraftforge.common.extensions.IForgeContainerType;
|
||||
import net.minecraftforge.fmllegacy.network.IContainerFactory;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkHooks;
|
||||
import net.minecraftforge.common.extensions.IForgeMenuType;
|
||||
import net.minecraftforge.network.IContainerFactory;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An extension over the basic {@link IForgeContainerType}/{@link NetworkHooks#openGui(ServerPlayer, MenuProvider, Consumer)}
|
||||
* An extension over the basic {@link IForgeMenuType}/{@link NetworkHooks#openGui(ServerPlayer, MenuProvider, Consumer)}
|
||||
* hooks, with a more convenient way of reading and writing data.
|
||||
*/
|
||||
public interface ContainerData
|
||||
@@ -35,7 +35,7 @@ public interface ContainerData
|
||||
|
||||
static <C extends AbstractContainerMenu, T extends ContainerData> MenuType<C> toType( Function<FriendlyByteBuf, T> reader, Factory<C, T> factory )
|
||||
{
|
||||
return IForgeContainerType.create( ( id, player, data ) -> factory.create( id, player, reader.apply( data ) ) );
|
||||
return IForgeMenuType.create( ( id, player, data ) -> factory.create( id, player, reader.apply( data ) ) );
|
||||
}
|
||||
|
||||
static <C extends AbstractContainerMenu, T extends ContainerData> MenuType<C> toType( Function<FriendlyByteBuf, T> reader, FixedFactory<C, T> factory )
|
||||
@@ -60,7 +60,7 @@ public interface ContainerData
|
||||
|
||||
private FixedPointContainerFactory( Function<FriendlyByteBuf, T> reader, FixedFactory<C, T> factory )
|
||||
{
|
||||
MenuType<C> type = this.type = IForgeContainerType.create( this );
|
||||
MenuType<C> type = this.type = IForgeMenuType.create( this );
|
||||
impl = ( id, player, data ) -> factory.create( type, id, player, reader.apply( data ) );
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,7 @@ package dan200.computercraft.shared.network.server;
|
||||
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -10,7 +10,7 @@ import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||
import dan200.computercraft.shared.computer.core.InputState;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||
import dan200.computercraft.shared.computer.core.InputState;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -10,7 +10,7 @@ import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.util.NBTUtil;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
@@ -9,7 +9,7 @@ import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.network.server;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.computer.core.IContainerComputer;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.upload.FileSlice;
|
||||
@@ -14,7 +13,7 @@ import dan200.computercraft.shared.network.NetworkHandler;
|
||||
import io.netty.handler.codec.DecoderException;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkEvent;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -122,9 +121,9 @@ public class UploadFileMessage extends ComputerServerMessage
|
||||
buf.writeByte( slice.getFileId() );
|
||||
buf.writeVarInt( slice.getOffset() );
|
||||
|
||||
slice.getBytes().rewind();
|
||||
buf.writeShort( slice.getBytes().remaining() );
|
||||
buf.writeBytes( slice.getBytes() );
|
||||
ByteBuffer bytes = slice.getBytes().duplicate();
|
||||
buf.writeShort( bytes.remaining() );
|
||||
buf.writeBytes( bytes );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +157,6 @@ public class UploadFileMessage extends ComputerServerMessage
|
||||
|
||||
int canWrite = Math.min( remaining, capacity - currentOffset );
|
||||
|
||||
ComputerCraft.log.info( "Adding slice from {} to {}", currentOffset, currentOffset + canWrite - 1 );
|
||||
contents.position( currentOffset ).limit( currentOffset + canWrite );
|
||||
slices.add( new FileSlice( fileId, currentOffset, contents.slice() ) );
|
||||
currentOffset += canWrite;
|
||||
|
@@ -29,7 +29,7 @@ import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
|
||||
/**
|
||||
* This peripheral allows you to interact with command blocks.
|
||||
*
|
||||
* Command blocks are only wrapped as peripherals if the {@literal enable_command_block} option is true within the
|
||||
* Command blocks are only wrapped as peripherals if the {@code enable_command_block} option is true within the
|
||||
* config.
|
||||
*
|
||||
* This API is <em>not</em> the same as the {@link CommandAPI} API, which is exposed on command computers.
|
||||
|
@@ -37,9 +37,9 @@ import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkHooks;
|
||||
import net.minecraftforge.items.IItemHandlerModifiable;
|
||||
import net.minecraftforge.items.wrapper.InvWrapper;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -137,9 +137,8 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public CompoundTag save( @Nonnull CompoundTag nbt )
|
||||
public void saveAdditional( @Nonnull CompoundTag nbt )
|
||||
{
|
||||
if( customName != null ) nbt.putString( NBT_NAME, Component.Serializer.toJson( customName ) );
|
||||
|
||||
@@ -149,7 +148,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
|
||||
diskStack.save( item );
|
||||
nbt.put( NBT_ITEM, item );
|
||||
}
|
||||
return super.save( nbt );
|
||||
super.saveAdditional( nbt );
|
||||
}
|
||||
|
||||
void serverTick()
|
||||
|
@@ -60,9 +60,9 @@ public class ItemData
|
||||
data.put( "maxDamage", stack.getMaxDamage() );
|
||||
}
|
||||
|
||||
if( stack.getItem().showDurabilityBar( stack ) )
|
||||
if( stack.getItem().isBarVisible( stack ) )
|
||||
{
|
||||
data.put( "durability", stack.getItem().getDurabilityForDisplay( stack ) );
|
||||
data.put( "durability", stack.getItem().getBarWidth( stack ) / 13.0 );
|
||||
}
|
||||
|
||||
data.put( "tags", DataHelpers.getTags( stack.getItem().getTags() ) );
|
||||
|
@@ -19,10 +19,10 @@ import javax.annotation.Nonnull;
|
||||
*
|
||||
* This works with energy storage blocks, as well as generators and machines which consume energy.
|
||||
*
|
||||
* <blockquote>
|
||||
* <strong>Note:</strong> Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF
|
||||
* :::note
|
||||
* Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF
|
||||
* used/generated per tick).
|
||||
* </blockquote>
|
||||
* :::
|
||||
*
|
||||
* @cc.module energy_storage
|
||||
*/
|
||||
|
@@ -21,9 +21,60 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The modem peripheral allows you to send messages between computers.
|
||||
* Modems allow you to send messages between computers over long distances.
|
||||
*
|
||||
* :::tip
|
||||
* Modems provide a fairly basic set of methods, which makes them very flexible but often hard to work with. The
|
||||
* {@literal @}{rednet} API is built on top of modems, and provides a more user-friendly interface.
|
||||
* :::
|
||||
*
|
||||
* ## Sending and receiving messages
|
||||
* Modems operate on a series of channels, a bit like frequencies on a radio. Any modem can send a message on a
|
||||
* particular channel, but only those which have {@link #open opened} the channel and are "listening in" can receive
|
||||
* messages.
|
||||
*
|
||||
* Channels are represented as an integer between 0 and 65535 inclusive. These channels don't have any defined meaning,
|
||||
* though some APIs or programs will assign a meaning to them. For instance, the @{gps} module sends all its messages on
|
||||
* channel 65534 (@{gps.CHANNEL_GPS}), while @{rednet} uses channels equal to the computer's ID.
|
||||
*
|
||||
* - Sending messages is done with the {@link #transmit(int, int, Object)} message.
|
||||
* - Receiving messages is done by listening to the @{modem_message} event.
|
||||
*
|
||||
* ## Types of modem
|
||||
* CC: Tweaked comes with three kinds of modem, with different capabilities.
|
||||
*
|
||||
* <ul>
|
||||
* <li><strong>Wireless modems:</strong> Wireless modems can send messages to any other wireless modem. They can be placed next to a
|
||||
* computer, or equipped as a pocket computer or turtle upgrade.
|
||||
*
|
||||
* Wireless modems have a limited range, only sending messages to modems within 64 blocks. This range increases
|
||||
* linearly once the modem is above y=96, to a maximum of 384 at world height.</li>
|
||||
* <li><strong>Ender modems:</strong> These are upgraded versions of normal wireless modems. They do not have a distance
|
||||
* limit, and can send messages between dimensions.</li>
|
||||
* <li><strong>Wired modems:</strong> These send messages to other any other wired modems connected to the same network
|
||||
* (using <em>Networking Cable</em>). They also can be used to attach additional peripherals to a computer.</li></ul>
|
||||
*
|
||||
* @cc.module modem
|
||||
* @cc.see modem_message Queued when a modem receives a message on an {@link #open(int) open channel}.
|
||||
* @cc.see rednet A networking API built on top of the modem peripheral.
|
||||
* @cc.usage Wrap a modem and a message on channel 15, requesting a response on channel 43. Then wait for a message to
|
||||
* arrive on channel 43 and print it.
|
||||
*
|
||||
* <pre>{@code
|
||||
* local modem = peripheral.find("modem") or error("No modem attached", 0)
|
||||
* modem.open(43) -- Open 43 so we can receive replies
|
||||
*
|
||||
* -- Send our message
|
||||
* modem.transmit(15, 43, "Hello, world!")
|
||||
*
|
||||
* -- And wait for a reply
|
||||
* local event, side, channel, replyChannel, message, distance
|
||||
* repeat
|
||||
* event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
|
||||
* until channel == 43
|
||||
*
|
||||
* print("Received a reply: " .. tostring(message))
|
||||
* }</pre>
|
||||
*/
|
||||
public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPacketReceiver
|
||||
{
|
||||
@@ -157,12 +208,23 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa
|
||||
* Sends a modem message on a certain channel. Modems listening on the channel will queue a {@code modem_message}
|
||||
* event on adjacent computers.
|
||||
*
|
||||
* <blockquote><strong>Note:</strong> The channel does not need be open to send a message.</blockquote>
|
||||
* :::note
|
||||
* The channel does not need be open to send a message.
|
||||
* :::
|
||||
*
|
||||
* @param channel The channel to send messages on.
|
||||
* @param replyChannel The channel that responses to this message should be sent on.
|
||||
* @param payload The object to send. This can be a boolean, string, number, or table.
|
||||
* @param replyChannel The channel that responses to this message should be sent on. This can be the same as
|
||||
* {@code channel} or entirely different. The channel must have been {@link #open opened} on
|
||||
* the sending computer in order to receive the replies.
|
||||
* @param payload The object to send. This can be any primitive type (boolean, number, string) as well as
|
||||
* tables. Other types (like functions), as well as metatables, will not be transmitted.
|
||||
* @throws LuaException If the channel is out of range.
|
||||
* @cc.usage Wrap a modem and a message on channel 15, requesting a response on channel 43.
|
||||
*
|
||||
* <pre>{@code
|
||||
* local modem = peripheral.find("modem") or error("No modem attached", 0)
|
||||
* modem.transmit(15, 43, "Hello, world!")
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction
|
||||
public final void transmit( int channel, int replyChannel, Object payload ) throws LuaException
|
||||
|
@@ -98,7 +98,7 @@ public class BlockCable extends BlockGeneric implements SimpleWaterloggedBlock
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removedByPlayer( BlockState state, Level world, BlockPos pos, Player player, boolean willHarvest, FluidState fluid )
|
||||
public boolean onDestroyedByPlayer( BlockState state, Level world, BlockPos pos, Player player, boolean willHarvest, FluidState fluid )
|
||||
{
|
||||
if( state.getValue( CABLE ) && state.getValue( MODEM ).getFacing() != null )
|
||||
{
|
||||
@@ -140,12 +140,12 @@ public class BlockCable extends BlockGeneric implements SimpleWaterloggedBlock
|
||||
}
|
||||
}
|
||||
|
||||
return super.removedByPlayer( state, world, pos, player, willHarvest, fluid );
|
||||
return super.onDestroyedByPlayer( state, world, pos, player, willHarvest, fluid );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ItemStack getPickBlock( BlockState state, HitResult hit, BlockGetter world, BlockPos pos, Player player )
|
||||
public ItemStack getCloneItemStack( BlockState state, HitResult hit, BlockGetter world, BlockPos pos, Player player )
|
||||
{
|
||||
Direction modem = state.getValue( MODEM ).getFacing();
|
||||
boolean cable = state.getValue( CABLE );
|
||||
|
@@ -292,13 +292,12 @@ public class TileCable extends TileGeneric
|
||||
peripheral.read( nbt, "" );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public CompoundTag save( CompoundTag nbt )
|
||||
public void saveAdditional( CompoundTag nbt )
|
||||
{
|
||||
nbt.putBoolean( NBT_PERIPHERAL_ENABLED, peripheralAccessAllowed );
|
||||
peripheral.write( nbt, "" );
|
||||
return super.save( nbt );
|
||||
super.saveAdditional( nbt );
|
||||
}
|
||||
|
||||
private void updateBlockState()
|
||||
@@ -346,7 +345,7 @@ public class TileCable extends TileGeneric
|
||||
for( Direction facing : DirectionUtil.FACINGS )
|
||||
{
|
||||
BlockPos offset = current.relative( facing );
|
||||
if( !world.isAreaLoaded( offset, 0 ) ) continue;
|
||||
if( !world.isLoaded( offset ) ) continue;
|
||||
|
||||
LazyOptional<IWiredElement> element = ComputerCraftAPI.getWiredElementAt( world, offset, facing.getOpposite() );
|
||||
if( !element.isPresent() ) continue;
|
||||
|
@@ -241,13 +241,12 @@ public class TileWiredModemFull extends TileGeneric
|
||||
for( int i = 0; i < peripherals.length; i++ ) peripherals[i].read( nbt, Integer.toString( i ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public CompoundTag save( CompoundTag nbt )
|
||||
public void saveAdditional( CompoundTag nbt )
|
||||
{
|
||||
nbt.putBoolean( NBT_PERIPHERAL_ENABLED, peripheralAccessAllowed );
|
||||
for( int i = 0; i < peripherals.length; i++ ) peripherals[i].write( nbt, Integer.toString( i ) );
|
||||
return super.save( nbt );
|
||||
super.saveAdditional( nbt );
|
||||
}
|
||||
|
||||
private void updateBlockState()
|
||||
@@ -306,7 +305,7 @@ public class TileWiredModemFull extends TileGeneric
|
||||
for( Direction facing : DirectionUtil.FACINGS )
|
||||
{
|
||||
BlockPos offset = current.relative( facing );
|
||||
if( !world.isAreaLoaded( offset, 0 ) ) continue;
|
||||
if( !world.isLoaded( offset ) ) continue;
|
||||
|
||||
LazyOptional<IWiredElement> element = ComputerCraftAPI.getWiredElementAt( world, offset, facing.getOpposite() );
|
||||
if( !element.isPresent() ) continue;
|
||||
|
@@ -80,8 +80,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
* If this computer is attached to the network, it _will not_ be included in
|
||||
* this list.
|
||||
*
|
||||
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless}
|
||||
* returns false before calling it.</blockquote>
|
||||
* :::note
|
||||
* This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
|
||||
* :::
|
||||
*
|
||||
* @param computer The calling computer.
|
||||
* @return Remote peripheral names on the network.
|
||||
@@ -95,8 +96,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
/**
|
||||
* Determine if a peripheral is available on this wired network.
|
||||
*
|
||||
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless}
|
||||
* returns false before calling it.</blockquote>
|
||||
* :::note
|
||||
* This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
|
||||
* :::
|
||||
*
|
||||
* @param computer The calling computer.
|
||||
* @param name The peripheral's name.
|
||||
@@ -112,8 +114,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
/**
|
||||
* Get the type of a peripheral is available on this wired network.
|
||||
*
|
||||
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless}
|
||||
* returns false before calling it.</blockquote>
|
||||
* :::note
|
||||
* This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
|
||||
* :::
|
||||
*
|
||||
* @param computer The calling computer.
|
||||
* @param name The peripheral's name.
|
||||
@@ -132,8 +135,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
/**
|
||||
* Check a peripheral is of a particular type.
|
||||
*
|
||||
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless}
|
||||
* returns false before calling it.</blockquote>
|
||||
* :::note
|
||||
* This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
|
||||
* :::
|
||||
*
|
||||
* @param computer The calling computer.
|
||||
* @param name The peripheral's name.
|
||||
@@ -153,8 +157,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
/**
|
||||
* Get all available methods for the remote peripheral with the given name.
|
||||
*
|
||||
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless}
|
||||
* returns false before calling it.</blockquote>
|
||||
* :::note
|
||||
* This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
|
||||
* :::
|
||||
*
|
||||
* @param computer The calling computer.
|
||||
* @param name The peripheral's name.
|
||||
@@ -174,8 +179,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
/**
|
||||
* Call a method on a peripheral on this wired network.
|
||||
*
|
||||
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless}
|
||||
* returns false before calling it.</blockquote>
|
||||
* :::note
|
||||
* This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
|
||||
* :::
|
||||
*
|
||||
* @param computer The calling computer.
|
||||
* @param context The Lua context we're executing in.
|
||||
@@ -204,8 +210,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW
|
||||
* may be used by other computers on the network to wrap this computer as a
|
||||
* peripheral.
|
||||
*
|
||||
* <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless}
|
||||
* returns false before calling it.</blockquote>
|
||||
* :::note
|
||||
* This function only appears on wired modems. Check {@link #isWireless} returns false before calling it.
|
||||
* :::
|
||||
*
|
||||
* @return The current computer's name.
|
||||
* @cc.treturn string|nil The current computer's name on the wired network.
|
||||
|
@@ -25,7 +25,7 @@ import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import net.minecraftforge.fmllegacy.RegistryObject;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
@@ -21,7 +21,7 @@ import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||
import net.minecraft.world.level.block.state.properties.EnumProperty;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import net.minecraftforge.fmllegacy.RegistryObject;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
@@ -101,7 +101,7 @@ public final class MonitorWatcher
|
||||
if( !(world instanceof ServerLevel) ) continue;
|
||||
|
||||
LevelChunk chunk = world.getChunkAt( pos );
|
||||
if( ((ServerLevel) world).getChunkSource().chunkMap.getPlayers( chunk.getPos(), false ).findAny().isEmpty() )
|
||||
if( ((ServerLevel) world).getChunkSource().chunkMap.getPlayers( chunk.getPos(), false ).isEmpty() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@@ -127,15 +127,14 @@ public class TileMonitor extends TileGeneric
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public CompoundTag save( CompoundTag tag )
|
||||
public void saveAdditional( CompoundTag tag )
|
||||
{
|
||||
tag.putInt( NBT_X, xIndex );
|
||||
tag.putInt( NBT_Y, yIndex );
|
||||
tag.putInt( NBT_WIDTH, width );
|
||||
tag.putInt( NBT_HEIGHT, height );
|
||||
return super.save( tag );
|
||||
super.saveAdditional( tag );
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -261,7 +260,7 @@ public class TileMonitor extends TileGeneric
|
||||
@Override
|
||||
public final ClientboundBlockEntityDataPacket getUpdatePacket()
|
||||
{
|
||||
return new ClientboundBlockEntityDataPacket( worldPosition, 0, getUpdateTag() );
|
||||
return ClientboundBlockEntityDataPacket.create( this );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@@ -398,7 +397,7 @@ public class TileMonitor extends TileGeneric
|
||||
BlockPos pos = toWorldPos( x, y );
|
||||
|
||||
Level world = getLevel();
|
||||
if( world == null || !world.isAreaLoaded( pos, 0 ) ) return MonitorState.UNLOADED;
|
||||
if( world == null || !world.isLoaded( pos ) ) return MonitorState.UNLOADED;
|
||||
|
||||
BlockEntity tile = world.getBlockEntity( pos );
|
||||
if( !(tile instanceof TileMonitor monitor) ) return MonitorState.MISSING;
|
||||
|
@@ -31,10 +31,10 @@ import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.fmllegacy.network.NetworkHooks;
|
||||
import net.minecraftforge.items.IItemHandler;
|
||||
import net.minecraftforge.items.wrapper.InvWrapper;
|
||||
import net.minecraftforge.items.wrapper.SidedInvWrapper;
|
||||
import net.minecraftforge.network.NetworkHooks;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -113,9 +113,8 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
ContainerHelper.loadAllItems( nbt, inventory );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public CompoundTag save( @Nonnull CompoundTag nbt )
|
||||
public void saveAdditional( @Nonnull CompoundTag nbt )
|
||||
{
|
||||
if( customName != null ) nbt.putString( NBT_NAME, Component.Serializer.toJson( customName ) );
|
||||
|
||||
@@ -130,7 +129,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
// Write inventory
|
||||
ContainerHelper.saveAllItems( nbt, inventory );
|
||||
|
||||
return super.save( nbt );
|
||||
super.saveAdditional( nbt );
|
||||
}
|
||||
|
||||
boolean isPrinting()
|
||||
|
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaTable;
|
||||
import dan200.computercraft.shared.util.PauseAwareTimer;
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral.SAMPLE_RATE;
|
||||
|
||||
/**
|
||||
* Internal state of the DFPWM decoder and the state of playback.
|
||||
*/
|
||||
class DfpwmState
|
||||
{
|
||||
private static final long SECOND = TimeUnit.SECONDS.toNanos( 1 );
|
||||
|
||||
/**
|
||||
* The minimum size of the client's audio buffer. Once we have less than this on the client, we should send another
|
||||
* batch of audio.
|
||||
*/
|
||||
private static final long CLIENT_BUFFER = (long) (SECOND * 0.5);
|
||||
|
||||
private static final int PREC = 10;
|
||||
|
||||
private int charge = 0; // q
|
||||
private int strength = 0; // s
|
||||
private boolean previousBit = false;
|
||||
|
||||
private boolean unplayed = true;
|
||||
private long clientEndTime = PauseAwareTimer.getTime();
|
||||
private float pendingVolume = 1.0f;
|
||||
private ByteBuffer pendingAudio;
|
||||
|
||||
synchronized boolean pushBuffer( LuaTable<?, ?> table, int size, @Nonnull Optional<Double> volume ) throws LuaException
|
||||
{
|
||||
if( pendingAudio != null ) return false;
|
||||
|
||||
int outSize = size / 8;
|
||||
ByteBuffer buffer = ByteBuffer.allocate( outSize );
|
||||
|
||||
for( int i = 0; i < outSize; i++ )
|
||||
{
|
||||
int thisByte = 0;
|
||||
for( int j = 1; j <= 8; j++ )
|
||||
{
|
||||
int level = table.getInt( i * 8 + j );
|
||||
if( level < -128 || level > 127 )
|
||||
{
|
||||
throw new LuaException( "table item #" + (i * 8 + j) + " must be between -128 and 127" );
|
||||
}
|
||||
|
||||
boolean currentBit = level > charge || (level == charge && charge == 127);
|
||||
|
||||
// Identical to DfpwmStream. Not happy with this, but saves some inheritance.
|
||||
int target = currentBit ? 127 : -128;
|
||||
|
||||
// q' <- q + (s * (t - q) + 128)/256
|
||||
int nextCharge = charge + ((strength * (target - charge) + (1 << (PREC - 1))) >> PREC);
|
||||
if( nextCharge == charge && nextCharge != target ) nextCharge += currentBit ? 1 : -1;
|
||||
|
||||
int z = currentBit == previousBit ? (1 << PREC) - 1 : 0;
|
||||
|
||||
int nextStrength = strength;
|
||||
if( strength != z ) nextStrength += currentBit == previousBit ? 1 : -1;
|
||||
if( nextStrength < 2 << (PREC - 8) ) nextStrength = 2 << (PREC - 8);
|
||||
|
||||
charge = nextCharge;
|
||||
strength = nextStrength;
|
||||
previousBit = currentBit;
|
||||
|
||||
thisByte = (thisByte >> 1) + (currentBit ? 128 : 0);
|
||||
}
|
||||
|
||||
buffer.put( (byte) thisByte );
|
||||
}
|
||||
|
||||
buffer.flip();
|
||||
|
||||
pendingAudio = buffer;
|
||||
pendingVolume = Mth.clamp( volume.orElse( (double) pendingVolume ).floatValue(), 0.0f, 3.0f );
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean shouldSendPending( long now )
|
||||
{
|
||||
return pendingAudio != null && now >= clientEndTime - CLIENT_BUFFER;
|
||||
}
|
||||
|
||||
ByteBuffer pullPending( long now )
|
||||
{
|
||||
ByteBuffer audio = pendingAudio;
|
||||
pendingAudio = null;
|
||||
// Compute when we should consider sending the next packet.
|
||||
clientEndTime = Math.max( now, clientEndTime ) + (audio.remaining() * SECOND * 8 / SAMPLE_RATE);
|
||||
unplayed = false;
|
||||
return audio;
|
||||
}
|
||||
|
||||
boolean isPlaying()
|
||||
{
|
||||
return unplayed || clientEndTime >= PauseAwareTimer.getTime();
|
||||
}
|
||||
|
||||
float getVolume()
|
||||
{
|
||||
return pendingVolume;
|
||||
}
|
||||
}
|
@@ -9,77 +9,177 @@ import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.lua.LuaTable;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.network.NetworkHandler;
|
||||
import dan200.computercraft.shared.network.client.SpeakerAudioClientMessage;
|
||||
import dan200.computercraft.shared.network.client.SpeakerMoveClientMessage;
|
||||
import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage;
|
||||
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
|
||||
import dan200.computercraft.shared.util.PauseAwareTimer;
|
||||
import net.minecraft.ResourceLocationException;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
|
||||
import static dan200.computercraft.api.lua.LuaValues.checkFinite;
|
||||
|
||||
/**
|
||||
* Speakers allow playing notes and other sounds.
|
||||
* The speaker peirpheral allow your computer to play notes and other sounds.
|
||||
*
|
||||
* The speaker can play three kinds of sound, in increasing orders of complexity:
|
||||
* - {@link #playNote} allows you to play noteblock note.
|
||||
* - {@link #playSound} plays any built-in Minecraft sound, such as block sounds or mob noises.
|
||||
* - {@link #playAudio} can play arbitrary audio.
|
||||
*
|
||||
* @cc.module speaker
|
||||
* @cc.since 1.80pr1
|
||||
*/
|
||||
public abstract class SpeakerPeripheral implements IPeripheral
|
||||
{
|
||||
private static final int MIN_TICKS_BETWEEN_SOUNDS = 1;
|
||||
/**
|
||||
* Number of samples/s in a dfpwm1a audio track.
|
||||
*/
|
||||
public static final int SAMPLE_RATE = 48000;
|
||||
|
||||
private final UUID source = UUID.randomUUID();
|
||||
private final Set<IComputerAccess> computers = Collections.newSetFromMap( new HashMap<>() );
|
||||
|
||||
private long clock = 0;
|
||||
private long lastPlayTime = 0;
|
||||
private final AtomicInteger notesThisTick = new AtomicInteger();
|
||||
|
||||
private long lastPositionTime;
|
||||
private Vec3 lastPosition;
|
||||
|
||||
private long lastPlayTime;
|
||||
|
||||
private final List<PendingSound> pendingNotes = new ArrayList<>();
|
||||
|
||||
private final Object lock = new Object();
|
||||
private boolean shouldStop;
|
||||
private PendingSound pendingSound = null;
|
||||
private DfpwmState dfpwmState;
|
||||
|
||||
public void update()
|
||||
{
|
||||
clock++;
|
||||
notesThisTick.set( 0 );
|
||||
|
||||
Vec3 pos = getPosition();
|
||||
Level level = getLevel();
|
||||
if( level == null ) return;
|
||||
MinecraftServer server = level.getServer();
|
||||
|
||||
synchronized( pendingNotes )
|
||||
{
|
||||
for( PendingSound sound : pendingNotes )
|
||||
{
|
||||
lastPlayTime = clock;
|
||||
server.getPlayerList().broadcast(
|
||||
null, pos.x, pos.y, pos.z, sound.volume * 16, level.dimension(),
|
||||
new ClientboundCustomSoundPacket( sound.location, SoundSource.RECORDS, pos, sound.volume, sound.pitch )
|
||||
);
|
||||
}
|
||||
pendingNotes.clear();
|
||||
}
|
||||
|
||||
// The audio dispatch logic here is pretty messy, which I'm not proud of. The general logic here is that we hold
|
||||
// the main "lock" when modifying the dfpwmState/pendingSound variables and no other time.
|
||||
// dfpwmState will only ever transition from having a buffer to not having a buffer on the main thread (so this
|
||||
// method), so we don't need to bother locking that.
|
||||
boolean shouldStop;
|
||||
PendingSound sound;
|
||||
DfpwmState dfpwmState;
|
||||
synchronized( lock )
|
||||
{
|
||||
sound = pendingSound;
|
||||
dfpwmState = this.dfpwmState;
|
||||
pendingSound = null;
|
||||
|
||||
shouldStop = this.shouldStop;
|
||||
if( shouldStop )
|
||||
{
|
||||
dfpwmState = this.dfpwmState = null;
|
||||
sound = null;
|
||||
this.shouldStop = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the speaker and nuke the position, so we don't update it again.
|
||||
if( shouldStop && lastPosition != null )
|
||||
{
|
||||
lastPosition = null;
|
||||
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( getSource() ) );
|
||||
return;
|
||||
}
|
||||
|
||||
long now = PauseAwareTimer.getTime();
|
||||
if( sound != null )
|
||||
{
|
||||
lastPlayTime = clock;
|
||||
NetworkHandler.sendToAllAround(
|
||||
new SpeakerPlayClientMessage( getSource(), pos, sound.location, sound.volume, sound.pitch ),
|
||||
level, pos, sound.volume * 16
|
||||
);
|
||||
syncedPosition( pos );
|
||||
}
|
||||
else if( dfpwmState != null && dfpwmState.shouldSendPending( now ) )
|
||||
{
|
||||
// If clients need to receive another batch of audio, send it and then notify computers our internal buffer is
|
||||
// free again.
|
||||
NetworkHandler.sendToAllTracking(
|
||||
new SpeakerAudioClientMessage( getSource(), pos, dfpwmState.getVolume(), dfpwmState.pullPending( now ) ),
|
||||
getLevel().getChunkAt( new BlockPos( pos ) )
|
||||
);
|
||||
syncedPosition( pos );
|
||||
|
||||
// And notify computers that we have space for more audio.
|
||||
for( IComputerAccess computer : computers )
|
||||
{
|
||||
computer.queueEvent( "speaker_audio_empty", computer.getAttachmentName() );
|
||||
}
|
||||
}
|
||||
|
||||
// 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 )
|
||||
if( lastPosition != null && (clock - lastPositionTime) >= 20 )
|
||||
{
|
||||
Vec3 position = getPosition();
|
||||
if( lastPosition == null || lastPosition.distanceToSqr( position ) >= 0.1 )
|
||||
if( lastPosition.distanceToSqr( position ) >= 0.1 )
|
||||
{
|
||||
lastPosition = position;
|
||||
lastPositionTime = clock;
|
||||
NetworkHandler.sendToAllTracking(
|
||||
new SpeakerMoveClientMessage( getSource(), position ),
|
||||
getLevel().getChunkAt( new BlockPos( position ) )
|
||||
);
|
||||
syncedPosition( position );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public abstract Level getLevel();
|
||||
|
||||
@Nonnull
|
||||
public abstract Vec3 getPosition();
|
||||
|
||||
protected abstract UUID getSource();
|
||||
|
||||
public boolean madeSound( long ticks )
|
||||
@Nonnull
|
||||
public UUID getSource()
|
||||
{
|
||||
return clock - lastPlayTime <= ticks;
|
||||
return source;
|
||||
}
|
||||
|
||||
public boolean madeSound()
|
||||
{
|
||||
DfpwmState state = dfpwmState;
|
||||
return clock - lastPlayTime <= 20 || (state != null && state.isPlaying());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@@ -90,18 +190,81 @@ public abstract class SpeakerPeripheral implements IPeripheral
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays a sound through the speaker.
|
||||
* Plays a note block note through the speaker.
|
||||
*
|
||||
* This plays sounds similar to the {@code /playsound} command in Minecraft.
|
||||
* It takes the namespaced path of a sound (e.g. {@code minecraft:block.note_block.harp})
|
||||
* with an optional volume and speed multiplier, and plays it through the speaker.
|
||||
* This takes the name of a note to play, as well as optionally the volume
|
||||
* and pitch to play the note at.
|
||||
*
|
||||
* The pitch argument uses semitones as the unit. This directly maps to the
|
||||
* number of clicks on a note block. For reference, 0, 12, and 24 map to F#,
|
||||
* and 6 and 18 map to C.
|
||||
*
|
||||
* A maximum of 8 notes can be played in a single tick. If this limit is hit, this function will return
|
||||
* {@literal false}.
|
||||
*
|
||||
* ### Valid instruments
|
||||
* The speaker supports [all of Minecraft's noteblock instruments](https://minecraft.fandom.com/wiki/Note_Block#Instruments).
|
||||
* These are:
|
||||
*
|
||||
* {@code "harp"}, {@code "basedrum"}, {@code "snare"}, {@code "hat"}, {@code "bass"}, @code "flute"},
|
||||
* {@code "bell"}, {@code "guitar"}, {@code "chime"}, {@code "xylophone"}, {@code "iron_xylophone"},
|
||||
* {@code "cow_bell"}, {@code "didgeridoo"}, {@code "bit"}, {@code "banjo"} and {@code "pling"}.
|
||||
*
|
||||
* @param context The Lua context
|
||||
* @param instrumentA The instrument to use to play this note.
|
||||
* @param volumeA The volume to play the note at, from 0.0 to 3.0. Defaults to 1.0.
|
||||
* @param pitchA The pitch to play the note at in semitones, from 0 to 24. Defaults to 12.
|
||||
* @return Whether the note could be played as the limit was reached.
|
||||
* @throws LuaException If the instrument doesn't exist.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final boolean playNote( ILuaContext context, String instrumentA, Optional<Double> volumeA, Optional<Double> pitchA ) throws LuaException
|
||||
{
|
||||
float volume = (float) checkFinite( 1, volumeA.orElse( 1.0 ) );
|
||||
float pitch = (float) checkFinite( 2, pitchA.orElse( 1.0 ) );
|
||||
|
||||
NoteBlockInstrument instrument = null;
|
||||
for( NoteBlockInstrument testInstrument : NoteBlockInstrument.values() )
|
||||
{
|
||||
if( testInstrument.getSerializedName().equalsIgnoreCase( instrumentA ) )
|
||||
{
|
||||
instrument = testInstrument;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the note exists
|
||||
if( instrument == null ) throw new LuaException( "Invalid instrument, \"" + instrument + "\"!" );
|
||||
|
||||
synchronized( pendingNotes )
|
||||
{
|
||||
if( pendingNotes.size() >= ComputerCraft.maxNotesPerTick ) return false;
|
||||
pendingNotes.add( new PendingSound( instrument.getSoundEvent().getRegistryName(), volume, (float) Math.pow( 2.0, (pitch - 12.0) / 12.0 ) ) );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays a Minecraft sound through the speaker.
|
||||
*
|
||||
* This takes the [name of a Minecraft sound](https://minecraft.fandom.com/wiki/Sounds.json), such as
|
||||
* {@code "minecraft:block.note_block.harp"}, as well as an optional volume and pitch.
|
||||
*
|
||||
* Only one sound can be played at once. This function will return {@literal false} if another sound was started
|
||||
* this tick, or if some {@link #playAudio audio} is still playing.
|
||||
*
|
||||
* @param context The Lua context
|
||||
* @param name The name of the sound to play.
|
||||
* @param volumeA The volume to play the sound at, from 0.0 to 3.0. Defaults to 1.0.
|
||||
* @param pitchA The speed to play the sound at, from 0.5 to 2.0. Defaults to 1.0.
|
||||
* @return Whether the sound could be played.
|
||||
* @throws LuaException If the sound name couldn't be decoded.
|
||||
* @throws LuaException If the sound name was invalid.
|
||||
* @cc.usage Play a creeper hiss with the speaker.
|
||||
*
|
||||
* <pre>{@code
|
||||
* local speaker = peripheral.find("speaker")
|
||||
* speaker.playSound("entity.creeper.primed")
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction
|
||||
public final boolean playSound( ILuaContext context, String name, Optional<Double> volumeA, Optional<Double> pitchA ) throws LuaException
|
||||
@@ -119,89 +282,113 @@ public abstract class SpeakerPeripheral implements IPeripheral
|
||||
throw new LuaException( "Malformed sound name '" + name + "' " );
|
||||
}
|
||||
|
||||
return playSound( context, identifier, volume, pitch, false );
|
||||
synchronized( lock )
|
||||
{
|
||||
if( dfpwmState != null && dfpwmState.isPlaying() ) return false;
|
||||
dfpwmState = null;
|
||||
pendingSound = new PendingSound( identifier, volume, pitch );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays a note block note through the speaker.
|
||||
* Attempt to stream some audio data to the speaker.
|
||||
*
|
||||
* This takes the name of a note to play, as well as optionally the volume
|
||||
* and pitch to play the note at.
|
||||
* This accepts a list of audio samples as amplitudes between -128 and 127. These are stored in an internal buffer
|
||||
* and played back at 48kHz. If this buffer is full, this function will return {@literal false}. You should wait for
|
||||
* a @{speaker_audio_empty} event before trying again.
|
||||
*
|
||||
* The pitch argument uses semitones as the unit. This directly maps to the
|
||||
* number of clicks on a note block. For reference, 0, 12, and 24 map to F#,
|
||||
* and 6 and 18 map to C.
|
||||
* :::note
|
||||
* The speaker only buffers a single call to {@link #playAudio} at once. This means if you try to play a small
|
||||
* number of samples, you'll have a lot of stutter. You should try to play as many samples in one call as possible
|
||||
* (up to 128×1024), as this reduces the chances of audio stuttering or halting, especially when the server or
|
||||
* computer is lagging.
|
||||
* :::
|
||||
*
|
||||
* @param context The Lua context
|
||||
* @param name The name of the note to play.
|
||||
* @param volumeA The volume to play the note at, from 0.0 to 3.0. Defaults to 1.0.
|
||||
* @param pitchA The pitch to play the note at in semitones, from 0 to 24. Defaults to 12.
|
||||
* @return Whether the note could be played.
|
||||
* @throws LuaException If the instrument doesn't exist.
|
||||
* {@literal @}{speaker_audio} provides a more complete guide in to using speakers
|
||||
*
|
||||
* @param context The Lua context.
|
||||
* @param audio The audio data to play.
|
||||
* @param volume The volume to play this audio at.
|
||||
* @return If there was room to accept this audio data.
|
||||
* @throws LuaException If the audio data is malformed.
|
||||
* @cc.tparam {number...} audio A list of amplitudes.
|
||||
* @cc.tparam [opt] number volume The volume to play this audio at. If not given, defaults to the previous volume
|
||||
* given to {@link #playAudio}.
|
||||
* @cc.since 1.100
|
||||
* @cc.usage Read an audio file, decode it using @{cc.audio.dfpwm}, and play it using the speaker.
|
||||
*
|
||||
* <pre>{@code
|
||||
* local dfpwm = require("cc.audio.dfpwm")
|
||||
* local speaker = peripheral.find("speaker")
|
||||
*
|
||||
* local decoder = dfpwm.make_decoder()
|
||||
* for chunk in io.lines("data/example.dfpwm", 16 * 1024) do
|
||||
* local buffer = decoder(chunk)
|
||||
*
|
||||
* while not speaker.playAudio(buffer) do
|
||||
* os.pullEvent("speaker_audio_empty")
|
||||
* end
|
||||
* end
|
||||
* }</pre>
|
||||
* @cc.see cc.audio.dfpwm Provides utilities for decoding DFPWM audio files into a format which can be played by
|
||||
* the speaker.
|
||||
* @cc.see speaker_audio For a more complete introduction to the {@link #playAudio} function.
|
||||
*/
|
||||
@LuaFunction( unsafe = true )
|
||||
public final boolean playAudio( ILuaContext context, LuaTable<?, ?> audio, Optional<Double> volume ) throws LuaException
|
||||
{
|
||||
checkFinite( 1, volume.orElse( 0.0 ) );
|
||||
|
||||
// TODO: Use ArgumentHelpers instead?
|
||||
int length = audio.length();
|
||||
if( length <= 0 ) throw new LuaException( "Cannot play empty audio" );
|
||||
if( length > 128 * 1024 ) throw new LuaException( "Audio data is too large" );
|
||||
|
||||
DfpwmState state;
|
||||
synchronized( lock )
|
||||
{
|
||||
if( dfpwmState == null || !dfpwmState.isPlaying() ) dfpwmState = new DfpwmState();
|
||||
state = dfpwmState;
|
||||
|
||||
pendingSound = null;
|
||||
}
|
||||
|
||||
return state.pushBuffer( audio, length, volume );
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop all audio being played by this speaker.
|
||||
*
|
||||
* This clears any audio that {@link #playAudio} had queued and stops the latest sound played by {@link #playSound}.
|
||||
*
|
||||
* @cc.since 1.100
|
||||
*/
|
||||
@LuaFunction
|
||||
public final synchronized boolean playNote( ILuaContext context, String name, Optional<Double> volumeA, Optional<Double> pitchA ) throws LuaException
|
||||
public final void stop()
|
||||
{
|
||||
float volume = (float) checkFinite( 1, volumeA.orElse( 1.0 ) );
|
||||
float pitch = (float) checkFinite( 2, pitchA.orElse( 1.0 ) );
|
||||
|
||||
NoteBlockInstrument instrument = null;
|
||||
for( NoteBlockInstrument testInstrument : NoteBlockInstrument.values() )
|
||||
{
|
||||
if( testInstrument.getSerializedName().equalsIgnoreCase( name ) )
|
||||
{
|
||||
instrument = testInstrument;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the note exists
|
||||
if( instrument == null ) throw new LuaException( "Invalid instrument, \"" + name + "\"!" );
|
||||
|
||||
// If the resource location for note block notes changes, this method call will need to be updated
|
||||
boolean success = playSound( context, instrument.getSoundEvent().getRegistryName(), volume, (float) Math.pow( 2.0, (pitch - 12.0) / 12.0 ), true );
|
||||
if( success ) notesThisTick.incrementAndGet();
|
||||
return success;
|
||||
shouldStop = true;
|
||||
}
|
||||
|
||||
private synchronized boolean playSound( ILuaContext context, ResourceLocation name, float volume, float pitch, boolean isNote ) throws LuaException
|
||||
private void syncedPosition( Vec3 position )
|
||||
{
|
||||
if( clock - lastPlayTime < MIN_TICKS_BETWEEN_SOUNDS )
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
lastPosition = position;
|
||||
lastPositionTime = clock;
|
||||
}
|
||||
|
||||
Level world = getLevel();
|
||||
Vec3 pos = getPosition();
|
||||
@Override
|
||||
public void attach( @Nonnull IComputerAccess computer )
|
||||
{
|
||||
computers.add( computer );
|
||||
}
|
||||
|
||||
float actualVolume = Mth.clamp( volume, 0.0f, 3.0f );
|
||||
float range = actualVolume * 16;
|
||||
@Override
|
||||
public void detach( @Nonnull IComputerAccess computer )
|
||||
{
|
||||
computers.remove( computer );
|
||||
}
|
||||
|
||||
context.issueMainThreadTask( () -> {
|
||||
MinecraftServer server = world.getServer();
|
||||
if( server == null ) return null;
|
||||
|
||||
if( isNote )
|
||||
{
|
||||
server.getPlayerList().broadcast(
|
||||
null, pos.x, pos.y, pos.z, range, world.dimension(),
|
||||
new ClientboundCustomSoundPacket( name, SoundSource.RECORDS, pos, actualVolume, pitch )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkHandler.sendToAllAround(
|
||||
new SpeakerPlayClientMessage( getSource(), pos, name, actualVolume, pitch ),
|
||||
world, pos, range
|
||||
);
|
||||
}
|
||||
return null;
|
||||
} );
|
||||
|
||||
lastPlayTime = clock;
|
||||
return true;
|
||||
private record PendingSound(ResourceLocation location, float volume, float pitch)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,6 @@ 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;
|
||||
|
||||
@@ -29,7 +28,6 @@ public class TileSpeaker extends TileGeneric
|
||||
{
|
||||
private final SpeakerPeripheral peripheral;
|
||||
private LazyOptional<IPeripheral> peripheralCap;
|
||||
private final UUID source = UUID.randomUUID();
|
||||
|
||||
public TileSpeaker( BlockEntityType<TileSpeaker> type, BlockPos pos, BlockState state )
|
||||
{
|
||||
@@ -48,7 +46,7 @@ public class TileSpeaker extends TileGeneric
|
||||
super.setRemoved();
|
||||
if( level != null && !level.isClientSide )
|
||||
{
|
||||
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) );
|
||||
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( peripheral.getSource() ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +85,7 @@ public class TileSpeaker extends TileGeneric
|
||||
return speaker.getLevel();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Vec3 getPosition()
|
||||
{
|
||||
@@ -94,12 +93,6 @@ public class TileSpeaker extends TileGeneric
|
||||
return new Vec3( pos.getX(), pos.getY(), pos.getZ() );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getSource()
|
||||
{
|
||||
return speaker.source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( @Nullable IPeripheral other )
|
||||
{
|
||||
|
@@ -9,11 +9,9 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.shared.network.NetworkHandler;
|
||||
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
import net.minecraftforge.fmllegacy.LogicalSidedProvider;
|
||||
import net.minecraftforge.server.ServerLifecycleHooks;
|
||||
|
||||
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.
|
||||
@@ -22,21 +20,13 @@ public abstract class UpgradeSpeakerPeripheral extends SpeakerPeripheral
|
||||
{
|
||||
public static final String ADJECTIVE = "upgrade.computercraft.speaker.adjective";
|
||||
|
||||
private final UUID source = UUID.randomUUID();
|
||||
|
||||
@Override
|
||||
protected final UUID getSource()
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach( @Nonnull IComputerAccess computer )
|
||||
{
|
||||
// We could be in the process of shutting down the server, so we can't send packets in this case.
|
||||
MinecraftServer server = LogicalSidedProvider.INSTANCE.get( LogicalSide.SERVER );
|
||||
MinecraftServer server = ServerLifecycleHooks.getCurrentServer();
|
||||
if( server == null || server.isStopped() ) return;
|
||||
|
||||
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) );
|
||||
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( getSource() ) );
|
||||
}
|
||||
}
|
||||
|
@@ -17,7 +17,6 @@ import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.items.IComputerItem;
|
||||
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
||||
import dan200.computercraft.shared.pocket.apis.PocketAPI;
|
||||
@@ -34,6 +33,7 @@ import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.CreativeModeTab;
|
||||
import net.minecraft.world.item.Item;
|
||||
@@ -80,53 +80,65 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
|
||||
PocketUpgrades.getVanillaUpgrades().map( x -> create( -1, null, -1, x ) ).forEach( stacks::add );
|
||||
}
|
||||
|
||||
private boolean tick( @Nonnull ItemStack stack, @Nonnull Level world, @Nonnull Entity entity, @Nonnull PocketServerComputer computer )
|
||||
{
|
||||
IPocketUpgrade upgrade = getUpgrade( stack );
|
||||
|
||||
computer.setLevel( world );
|
||||
computer.updateValues( entity, stack, upgrade );
|
||||
|
||||
boolean changed = false;
|
||||
|
||||
// Sync ID
|
||||
int id = computer.getID();
|
||||
if( id != getComputerID( stack ) )
|
||||
{
|
||||
changed = true;
|
||||
setComputerID( stack, id );
|
||||
}
|
||||
|
||||
// Sync label
|
||||
String label = computer.getLabel();
|
||||
if( !Objects.equal( label, getLabel( stack ) ) )
|
||||
{
|
||||
changed = true;
|
||||
setLabel( stack, label );
|
||||
}
|
||||
|
||||
// Update pocket upgrade
|
||||
if( upgrade != null ) upgrade.update( computer, computer.getPeripheral( ComputerSide.BACK ) );
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inventoryTick( @Nonnull ItemStack stack, Level world, @Nonnull Entity entity, int slotNum, boolean selected )
|
||||
{
|
||||
if( !world.isClientSide )
|
||||
{
|
||||
// Server side
|
||||
Container inventory = entity instanceof Player ? ((Player) entity).getInventory() : null;
|
||||
PocketServerComputer computer = createServerComputer( world, inventory, entity, stack );
|
||||
if( computer != null )
|
||||
{
|
||||
IPocketUpgrade upgrade = getUpgrade( stack );
|
||||
Container inventory = entity instanceof Player player ? player.getInventory() : null;
|
||||
PocketServerComputer computer = createServerComputer( world, entity, inventory, stack );
|
||||
computer.keepAlive();
|
||||
|
||||
// Ping computer
|
||||
computer.keepAlive();
|
||||
computer.setLevel( world );
|
||||
computer.updateValues( entity, stack, upgrade );
|
||||
|
||||
// Sync ID
|
||||
int id = computer.getID();
|
||||
if( id != getComputerID( stack ) )
|
||||
{
|
||||
setComputerID( stack, id );
|
||||
if( inventory != null ) inventory.setChanged();
|
||||
}
|
||||
|
||||
// Sync label
|
||||
String label = computer.getLabel();
|
||||
if( !Objects.equal( label, getLabel( stack ) ) )
|
||||
{
|
||||
setLabel( stack, label );
|
||||
if( inventory != null ) inventory.setChanged();
|
||||
}
|
||||
|
||||
// Update pocket upgrade
|
||||
if( upgrade != null )
|
||||
{
|
||||
upgrade.update( computer, computer.getPeripheral( ComputerSide.BACK ) );
|
||||
}
|
||||
}
|
||||
boolean changed = tick( stack, world, entity, computer );
|
||||
if( changed && inventory != null ) inventory.setChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Client side
|
||||
createClientComputer( stack );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEntityItemUpdate( ItemStack stack, ItemEntity entity )
|
||||
{
|
||||
if( entity.level.isClientSide ) return false;
|
||||
|
||||
PocketServerComputer computer = getServerComputer( stack );
|
||||
if( computer != null && tick( stack, entity.level, entity, computer ) ) entity.setItem( stack.copy() );
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InteractionResultHolder<ItemStack> use( Level world, Player player, @Nonnull InteractionHand hand )
|
||||
@@ -134,22 +146,18 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
|
||||
ItemStack stack = player.getItemInHand( hand );
|
||||
if( !world.isClientSide )
|
||||
{
|
||||
PocketServerComputer computer = createServerComputer( world, player.getInventory(), player, stack );
|
||||
PocketServerComputer computer = createServerComputer( world, player, player.getInventory(), stack );
|
||||
computer.turnOn();
|
||||
|
||||
boolean stop = false;
|
||||
if( computer != null )
|
||||
IPocketUpgrade upgrade = getUpgrade( stack );
|
||||
if( upgrade != null )
|
||||
{
|
||||
computer.turnOn();
|
||||
|
||||
IPocketUpgrade upgrade = getUpgrade( stack );
|
||||
if( upgrade != null )
|
||||
{
|
||||
computer.updateValues( player, stack, upgrade );
|
||||
stop = upgrade.onRightClick( world, computer, computer.getPeripheral( ComputerSide.BACK ) );
|
||||
}
|
||||
computer.updateValues( player, stack, upgrade );
|
||||
stop = upgrade.onRightClick( world, computer, computer.getPeripheral( ComputerSide.BACK ) );
|
||||
}
|
||||
|
||||
if( !stop && computer != null )
|
||||
if( !stop )
|
||||
{
|
||||
boolean isTypingOnly = hand == InteractionHand.OFF_HAND;
|
||||
new ComputerContainerData( computer ).open( player, new PocketComputerMenuProvider( computer, stack, this, hand, isTypingOnly ) );
|
||||
@@ -207,17 +215,17 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
|
||||
return super.getCreatorModId( stack );
|
||||
}
|
||||
|
||||
public PocketServerComputer createServerComputer( final Level world, Container inventory, Entity entity, @Nonnull ItemStack stack )
|
||||
@Nonnull
|
||||
public PocketServerComputer createServerComputer( Level world, Entity entity, @Nullable Container inventory, @Nonnull ItemStack stack )
|
||||
{
|
||||
if( world.isClientSide ) return null;
|
||||
if( world.isClientSide ) throw new IllegalStateException( "Cannot call createServerComputer on the client" );
|
||||
|
||||
PocketServerComputer computer;
|
||||
int instanceID = getInstanceID( stack );
|
||||
int sessionID = getSessionID( stack );
|
||||
int correctSessionID = ComputerCraft.serverComputerRegistry.getSessionID();
|
||||
|
||||
if( instanceID >= 0 && sessionID == correctSessionID &&
|
||||
ComputerCraft.serverComputerRegistry.contains( instanceID ) )
|
||||
if( instanceID >= 0 && sessionID == correctSessionID && ComputerCraft.serverComputerRegistry.contains( instanceID ) )
|
||||
{
|
||||
computer = (PocketServerComputer) ComputerCraft.serverComputerRegistry.get( instanceID );
|
||||
}
|
||||
@@ -235,13 +243,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
|
||||
computerID = ComputerCraftAPI.createUniqueNumberedSaveDir( world, "computer" );
|
||||
setComputerID( stack, computerID );
|
||||
}
|
||||
computer = new PocketServerComputer(
|
||||
world,
|
||||
computerID,
|
||||
getLabel( stack ),
|
||||
instanceID,
|
||||
getFamily()
|
||||
);
|
||||
computer = new PocketServerComputer( world, computerID, getLabel( stack ), instanceID, getFamily() );
|
||||
computer.updateValues( entity, stack, getUpgrade( stack ) );
|
||||
computer.addAPI( new PocketAPI( computer ) );
|
||||
ComputerCraft.serverComputerRegistry.add( instanceID, computer );
|
||||
@@ -251,15 +253,17 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
|
||||
return computer;
|
||||
}
|
||||
|
||||
public static ServerComputer getServerComputer( @Nonnull ItemStack stack )
|
||||
@Nullable
|
||||
public static PocketServerComputer getServerComputer( @Nonnull ItemStack stack )
|
||||
{
|
||||
int session = getSessionID( stack );
|
||||
if( session != ComputerCraft.serverComputerRegistry.getSessionID() ) return null;
|
||||
|
||||
int instanceID = getInstanceID( stack );
|
||||
return instanceID >= 0 ? ComputerCraft.serverComputerRegistry.get( instanceID ) : null;
|
||||
return instanceID >= 0 ? (PocketServerComputer) ComputerCraft.serverComputerRegistry.get( instanceID ) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ClientComputer createClientComputer( @Nonnull ItemStack stack )
|
||||
{
|
||||
int instanceID = getInstanceID( stack );
|
||||
@@ -274,6 +278,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ClientComputer getClientComputer( @Nonnull ItemStack stack )
|
||||
{
|
||||
int instanceID = getInstanceID( stack );
|
||||
|
@@ -42,6 +42,6 @@ public class PocketSpeaker extends AbstractPocketUpgrade
|
||||
}
|
||||
|
||||
speaker.update();
|
||||
access.setLight( speaker.madeSound( 20 ) ? 0x3320fc : -1 );
|
||||
access.setLight( speaker.madeSound() ? 0x3320fc : -1 );
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,8 @@ import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral
|
||||
{
|
||||
private Level world = null;
|
||||
@@ -27,6 +29,7 @@ public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral
|
||||
return world;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Vec3 getPosition()
|
||||
{
|
||||
|
@@ -22,8 +22,50 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* The turtle API allows you to control your turtle.
|
||||
* Turtles are a robotic device, which can break and place blocks, attack mobs, and move about the world. They have
|
||||
* an internal inventory of 16 slots, allowing them to store blocks they have broken or would like to place.
|
||||
*
|
||||
* ## Movement
|
||||
* Turtles are capable of moving throug the world. As turtles are blocks themselves, they are confined to Minecraft's
|
||||
* grid, moving a single block at a time.
|
||||
*
|
||||
* {@literal @}{turtle.forward} and @{turtle.back} move the turtle in the direction it is facing, while @{turtle.up} and
|
||||
* {@literal @}{turtle.down} move it up and down (as one might expect!). In order to move left or right, you first need
|
||||
* to turn the turtle using @{turtle.turnLeft}/@{turtle.turnRight} and then move forward or backwards.
|
||||
*
|
||||
* :::info
|
||||
* The name "turtle" comes from [Turtle graphics], which originated from the Logo programming language. Here you'd move
|
||||
* a turtle with various commands like "move 10" and "turn left", much like ComputerCraft's turtles!
|
||||
* :::
|
||||
*
|
||||
* Moving a turtle (though not turning it) consumes *fuel*. If a turtle does not have any @{turtle.refuel|fuel}, it
|
||||
* won't move, and the movement functions will return @{false}. If your turtle isn't going anywhere, the first thing to
|
||||
* check is if you've fuelled your turtle.
|
||||
*
|
||||
* :::tip Handling errors
|
||||
* Many turtle functions can fail in various ways. For instance, a turtle cannot move forward if there's already a block
|
||||
* there. Instead of erroring, functions which can fail either return @{true} if they succeed, or @{false} and some
|
||||
* error message if they fail.
|
||||
*
|
||||
* Unexpected failures can often lead to strange behaviour. It's often a good idea to check the return values of these
|
||||
* functions, or wrap them in @{assert} (for instance, use `assert(turtle.forward())` rather than `turtle.forward()`),
|
||||
* so the program doesn't misbehave.
|
||||
* :::
|
||||
*
|
||||
* ## Turtle upgrades
|
||||
* While a normal turtle can move about the world and place blocks, its functionality is limited. Thankfully, turtles
|
||||
* can be upgraded with *tools* and @{peripheral|peripherals}. Turtles have two upgrade slots, one on the left and right
|
||||
* sides. Upgrades can be equipped by crafting a turtle with the upgrade, or calling the @{turtle.equipLeft}/@{turtle.equipRight}
|
||||
* functions.
|
||||
*
|
||||
* Turtle tools allow you to break blocks (@{turtle.dig}) and attack entities (@{turtle.attack}). Some tools are more
|
||||
* suitable to a task than others. For instance, a diamond pickaxe can break every block, while a sword does more
|
||||
* damage. Other tools have more niche use-cases, for instance hoes can til dirt.
|
||||
*
|
||||
* Peripherals (such as the @{modem|wireless modem} or @{speaker}) can also be equipped as upgrades. These are then
|
||||
* accessible by accessing the `"left"` or `"right"` peripheral.
|
||||
*
|
||||
* [Turtle Graphics]: https://en.wikipedia.org/wiki/Turtle_graphics "Turtle graphics"
|
||||
* @cc.module turtle
|
||||
* @cc.since 1.3
|
||||
*/
|
||||
|
@@ -42,7 +42,7 @@ import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
import net.minecraftforge.fmllegacy.RegistryObject;
|
||||
import net.minecraftforge.registries.RegistryObject;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
@@ -289,9 +289,8 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
|
||||
brain.readFromNBT( nbt );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public CompoundTag save( @Nonnull CompoundTag nbt )
|
||||
public void saveAdditional( @Nonnull CompoundTag nbt )
|
||||
{
|
||||
// Write inventory
|
||||
ListTag nbttaglist = new ListTag();
|
||||
@@ -310,7 +309,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
|
||||
// Write brain
|
||||
nbt = brain.writeToNBT( nbt );
|
||||
|
||||
return super.save( nbt );
|
||||
super.saveAdditional( nbt );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -31,7 +31,6 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.FluidTags;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntitySelector;
|
||||
import net.minecraft.world.entity.MoverType;
|
||||
import net.minecraft.world.item.DyeColor;
|
||||
import net.minecraft.world.level.Level;
|
||||
@@ -39,6 +38,7 @@ import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.level.material.PushReaction;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.items.IItemHandlerModifiable;
|
||||
@@ -48,6 +48,7 @@ import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static dan200.computercraft.shared.common.IColouredItem.NBT_COLOUR;
|
||||
import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED;
|
||||
@@ -65,6 +66,8 @@ public class TurtleBrain implements ITurtleAccess
|
||||
|
||||
private static final int ANIM_DURATION = 8;
|
||||
|
||||
public static final Predicate<Entity> PUSHABLE_ENTITY = entity -> !entity.isSpectator() && entity.getPistonPushReaction() != PushReaction.IGNORE;
|
||||
|
||||
private TileTurtle owner;
|
||||
private ComputerProxy proxy;
|
||||
private GameProfile owningPlayer;
|
||||
@@ -304,7 +307,7 @@ public class TurtleBrain implements ITurtleAccess
|
||||
}
|
||||
|
||||
// Ensure the chunk is loaded
|
||||
if( !world.isAreaLoaded( pos, 0 ) ) return false;
|
||||
if( !world.isLoaded( pos ) ) return false;
|
||||
|
||||
// Ensure we're inside the world border
|
||||
if( !world.getWorldBorder().isWithinBounds( pos ) ) return false;
|
||||
@@ -886,7 +889,7 @@ public class TurtleBrain implements ITurtleAccess
|
||||
}
|
||||
|
||||
AABB aabb = new AABB( minX, minY, minZ, maxX, maxY, maxZ );
|
||||
List<Entity> list = world.getEntitiesOfClass( Entity.class, aabb, EntitySelector.NO_SPECTATORS );
|
||||
List<Entity> list = world.getEntitiesOfClass( Entity.class, aabb, PUSHABLE_ENTITY );
|
||||
if( !list.isEmpty() )
|
||||
{
|
||||
double pushStep = 1.0f / ANIM_DURATION;
|
||||
|
@@ -137,7 +137,7 @@ public class TurtleMoveCommand implements ITurtleCommand
|
||||
return TurtleCommandResult.failure( "Cannot enter protected area" );
|
||||
}
|
||||
|
||||
if( !world.isAreaLoaded( position, 0 ) ) return TurtleCommandResult.failure( "Cannot leave loaded world" );
|
||||
if( !world.isLoaded( position ) ) return TurtleCommandResult.failure( "Cannot leave loaded world" );
|
||||
if( !world.getWorldBorder().isWithinBounds( position ) )
|
||||
{
|
||||
return TurtleCommandResult.failure( "Cannot pass the world border" );
|
||||
|
@@ -27,13 +27,13 @@ import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.SignBlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||
import net.minecraftforge.eventbus.api.Event.Result;
|
||||
import net.minecraftforge.items.IItemHandler;
|
||||
@@ -321,7 +321,7 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
||||
}
|
||||
}
|
||||
signTile.setChanged();
|
||||
world.sendBlockUpdated( tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), Constants.BlockFlags.DEFAULT );
|
||||
world.sendBlockUpdated( tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), Block.UPDATE_ALL );
|
||||
}
|
||||
|
||||
private static class ErrorMessage
|
||||
|
@@ -17,7 +17,7 @@ import net.minecraft.world.item.crafting.Recipe;
|
||||
import net.minecraft.world.item.crafting.RecipeType;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.fmllegacy.hooks.BasicEventHooks;
|
||||
import net.minecraftforge.event.ForgeEventFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -91,7 +91,7 @@ public class TurtleInventoryCrafting extends CraftingContainer
|
||||
results.add( result );
|
||||
|
||||
result.onCraftedBy( world, player, result.getCount() );
|
||||
BasicEventHooks.firePlayerCraftingEvent( player, result, this );
|
||||
ForgeEventFactory.firePlayerCraftingEvent( player, result, this );
|
||||
|
||||
ForgeHooks.setCraftingPlayer( player );
|
||||
NonNullList<ItemStack> remainders = recipe.getRemainingItems( this );
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user