mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-08-30 00:57:55 +00:00
Merge remote-tracking branch 'CC-Tweaked/mc-1.18.x' into mc-1.18.x/dev
This commit is contained in:
@@ -52,5 +52,6 @@ exclude: |
|
||||
src/test/resources/test-rom/data/json-parsing/|
|
||||
src/test/server-files/|
|
||||
config/idea/|
|
||||
.vscode/
|
||||
.vscode/|
|
||||
.*\.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)))
|
||||
|
@@ -12,7 +12,7 @@ see: speaker.playAudio To play audio using the speaker
|
||||
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
|
||||
```lua {data-peripheral=speaker}
|
||||
local dfpwm = require("cc.audio.dfpwm")
|
||||
local speaker = peripheral.find("speaker")
|
||||
|
||||
|
@@ -145,7 +145,7 @@ 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
|
||||
```lua {data-peripheral=speaker}
|
||||
local dfpwm = require("cc.audio.dfpwm")
|
||||
local speaker = peripheral.find("speaker")
|
||||
|
||||
@@ -157,14 +157,14 @@ 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(input)
|
||||
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.7 + samples[samples_i] * 0.3
|
||||
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
|
||||
|
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,8 +1,8 @@
|
||||
# Done to increase the memory available to gradle.
|
||||
org.gradle.jvmargs=-Xmx1G
|
||||
org.gradle.jvmargs=-Xmx3G
|
||||
|
||||
# Mod properties
|
||||
mod_version=1.99.2-alpha1
|
||||
mod_version=1.100.0
|
||||
|
||||
# Minecraft properties
|
||||
mc_version=1.18.1
|
||||
|
@@ -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.*;
|
||||
|
@@ -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 )
|
||||
|
@@ -29,6 +29,7 @@ import dan200.computercraft.shared.peripheral.printer.ContainerPrinter;
|
||||
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
|
||||
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
|
||||
import dan200.computercraft.shared.util.Config;
|
||||
import dan200.computercraft.shared.util.PauseAwareTimer;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
@@ -40,6 +41,7 @@ import net.fabricmc.fabric.api.client.rendering.v1.BlockEntityRendererRegistry;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.EntityRendererRegistry;
|
||||
import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry;
|
||||
import net.fabricmc.fabric.api.event.client.ClientSpriteRegistryCallback;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||
import net.fabricmc.fabric.api.object.builder.v1.client.model.FabricModelPredicateProviderRegistry;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
|
||||
@@ -62,11 +64,15 @@ public final class ComputerCraftProxyClient implements ClientModInitializer
|
||||
}
|
||||
} );
|
||||
|
||||
ServerTickEvents.START_SERVER_TICK.register( server -> PauseAwareTimer.tick() );
|
||||
|
||||
ComputerCraftCustomEvents.CLIENT_UNLOAD_WORLD_EVENT.register( () -> {
|
||||
SpeakerManager.reset();
|
||||
ClientMonitor.destroyAll();
|
||||
} );
|
||||
|
||||
ComputerCraftCustomEvents.PLAY_STREAMING_AUDIO_EVENT.register( SpeakerManager::playStreaming );
|
||||
|
||||
// Config
|
||||
ClientLifecycleEvents.CLIENT_STARTED.register( Config::clientStarted );
|
||||
}
|
||||
|
@@ -106,7 +106,8 @@ class DfpwmStream implements AudioStream
|
||||
if( head == null ) break;
|
||||
|
||||
int toRead = Math.min( head.remaining(), result.remaining() );
|
||||
result.put( head.array(), head.position(), toRead ); // TODO: In 1.17 convert this to a ByteBuffer override
|
||||
result.put( result.position(), head, head.position(), toRead );
|
||||
result.position( result.position() + toRead );
|
||||
head.position( head.position() + toRead );
|
||||
|
||||
if( head.hasRemaining() ) break;
|
||||
@@ -114,7 +115,9 @@ class DfpwmStream implements AudioStream
|
||||
}
|
||||
|
||||
result.flip();
|
||||
return result;
|
||||
|
||||
// This is naughty, but ensures we're not enqueuing empty buffers when the stream is exhausted.
|
||||
return result.remaining() == 0 ? null : result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,4 +125,9 @@ class DfpwmStream implements AudioStream
|
||||
{
|
||||
buffers.clear();
|
||||
}
|
||||
|
||||
public boolean isEmpty()
|
||||
{
|
||||
return buffers.isEmpty();
|
||||
}
|
||||
}
|
||||
|
@@ -27,8 +27,20 @@ public class SpeakerInstance
|
||||
|
||||
public synchronized void pushAudio( ByteBuf buffer )
|
||||
{
|
||||
if( currentStream == null ) currentStream = new DfpwmStream();
|
||||
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 )
|
||||
|
@@ -7,6 +7,7 @@ package dan200.computercraft.client.sound;
|
||||
|
||||
import com.mojang.blaze3d.audio.Channel;
|
||||
import net.minecraft.client.resources.sounds.SoundInstance;
|
||||
import net.minecraft.client.sounds.SoundEngine;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -21,14 +22,16 @@ public class SpeakerManager
|
||||
private static final Map<UUID, SpeakerInstance> sounds = new ConcurrentHashMap<>();
|
||||
|
||||
// A return value of true cancels the event
|
||||
public static boolean playStreaming( SoundInstance soundInstance, Channel channel )
|
||||
public static boolean playStreaming( SoundEngine engine, SoundInstance soundInstance, Channel channel )
|
||||
{
|
||||
if( !(soundInstance instanceof SpeakerSound) ) return false;
|
||||
SpeakerSound sound = (SpeakerSound) soundInstance;
|
||||
if( !(soundInstance instanceof SpeakerSound sound) ) return false;
|
||||
if( sound.stream == null ) return false;
|
||||
|
||||
channel.attachBufferStream( sound.stream );
|
||||
channel.play();
|
||||
|
||||
sound.channel = channel;
|
||||
sound.executor = engine.executor;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
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;
|
||||
@@ -13,9 +14,12 @@ 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 )
|
||||
|
@@ -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
|
||||
|
@@ -5,8 +5,11 @@
|
||||
*/
|
||||
package dan200.computercraft.fabric.events;
|
||||
|
||||
import com.mojang.blaze3d.audio.Channel;
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
import net.minecraft.client.resources.sounds.SoundInstance;
|
||||
import net.minecraft.client.sounds.SoundEngine;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
|
||||
@@ -28,6 +31,15 @@ public final class ComputerCraftCustomEvents
|
||||
}
|
||||
} );
|
||||
|
||||
public static final Event<PlayStreamingAudio> PLAY_STREAMING_AUDIO_EVENT = EventFactory.createArrayBacked( PlayStreamingAudio.class,
|
||||
callbacks -> ( engine, soundInstance, channel ) -> {
|
||||
for( PlayStreamingAudio callback : callbacks )
|
||||
{
|
||||
if( callback.onPlayStreamingAudio( engine, soundInstance, channel ) ) return true;
|
||||
}
|
||||
return false;
|
||||
} );
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ClientUnloadWorld
|
||||
{
|
||||
@@ -39,4 +51,10 @@ public final class ComputerCraftCustomEvents
|
||||
{
|
||||
void onServerPlayerLoadedChunk( ServerPlayer player, ChunkPos chunkPos );
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PlayStreamingAudio
|
||||
{
|
||||
boolean onPlayStreamingAudio( SoundEngine engine, SoundInstance soundInstance, Channel channel );
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.fabric.mixin;
|
||||
|
||||
import dan200.computercraft.shared.computer.items.IComputerItem;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
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;
|
||||
|
||||
@Mixin( ItemEntity.class )
|
||||
public abstract class MixinItemEntity
|
||||
{
|
||||
@Shadow
|
||||
public abstract ItemStack getItem();
|
||||
|
||||
@Inject(
|
||||
method = "tick",
|
||||
at = @At( "HEAD" ),
|
||||
cancellable = true
|
||||
)
|
||||
private void onTick( CallbackInfo ci )
|
||||
{
|
||||
ItemStack stack = getItem();
|
||||
if( stack.getItem() instanceof IComputerItem item )
|
||||
{
|
||||
if( item.onEntityItemUpdate( stack, (ItemEntity) (Object) this ) ) ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,7 +6,7 @@
|
||||
package dan200.computercraft.fabric.mixin;
|
||||
|
||||
import com.mojang.blaze3d.audio.Channel;
|
||||
import dan200.computercraft.client.sound.SpeakerManager;
|
||||
import dan200.computercraft.fabric.events.ComputerCraftCustomEvents;
|
||||
import net.minecraft.client.resources.sounds.SoundInstance;
|
||||
import net.minecraft.client.sounds.AudioStream;
|
||||
import net.minecraft.client.sounds.SoundEngine;
|
||||
@@ -19,19 +19,22 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
@Mixin( SoundEngine.class )
|
||||
public class MixinSoundEngine
|
||||
{
|
||||
// Used to capture the SoundInstance argument passed to SoundEngine#play. Not a thread-safe way to do it but
|
||||
// this code is only called from the render thread as far as I can tell.
|
||||
@Unique
|
||||
private static SoundInstance onPlaySoundInstanceCapture;
|
||||
// Used to capture the SoundInstance argument passed to SoundEngine#play and the SoundEngine instance.
|
||||
// Not a thread-safe way to do it but this code is only called from the render thread as far as I can tell.
|
||||
@Unique private static SoundInstance soundInstanceCapture;
|
||||
@Unique private static SoundEngine thisCapture;
|
||||
|
||||
@Inject(
|
||||
method = "lambda$play$8",
|
||||
method = "lambda$play$8(Lnet/minecraft/client/sounds/AudioStream;Lcom/mojang/blaze3d/audio/Channel;)V",
|
||||
at = @At( "HEAD" ),
|
||||
cancellable = true
|
||||
)
|
||||
private static void onStreamingSourcePlay( AudioStream audioStream, Channel channel, CallbackInfo ci )
|
||||
{
|
||||
if( SpeakerManager.playStreaming( onPlaySoundInstanceCapture, channel ) ) ci.cancel();
|
||||
if( ComputerCraftCustomEvents.PLAY_STREAMING_AUDIO_EVENT.invoker().onPlayStreamingAudio( thisCapture, soundInstanceCapture, channel ) )
|
||||
{
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
@@ -40,6 +43,7 @@ public class MixinSoundEngine
|
||||
)
|
||||
void onPlay( SoundInstance soundInstance, CallbackInfo ci )
|
||||
{
|
||||
onPlaySoundInstanceCapture = soundInstance;
|
||||
soundInstanceCapture = soundInstance;
|
||||
thisCapture = (SoundEngine) (Object) this;
|
||||
}
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -7,6 +7,7 @@ package dan200.computercraft.shared.computer.items;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@@ -26,6 +27,11 @@ public interface IComputerItem
|
||||
return stack.hasCustomHoverName() ? stack.getHoverName().getString() : null;
|
||||
}
|
||||
|
||||
default boolean onEntityItemUpdate( ItemStack stack, ItemEntity entity )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ComputerFamily getFamily();
|
||||
|
||||
ItemStack withFamily( @Nonnull ItemStack stack, @Nonnull ComputerFamily family );
|
||||
|
@@ -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 )
|
||||
|
@@ -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;
|
||||
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -7,6 +7,7 @@ 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;
|
||||
@@ -27,7 +28,7 @@ class DfpwmState
|
||||
* 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 * 1.5);
|
||||
private static final long CLIENT_BUFFER = (long) (SECOND * 0.5);
|
||||
|
||||
private static final int PREC = 10;
|
||||
|
||||
@@ -36,7 +37,7 @@ class DfpwmState
|
||||
private boolean previousBit = false;
|
||||
|
||||
private boolean unplayed = true;
|
||||
private long clientEndTime = System.nanoTime();
|
||||
private long clientEndTime = PauseAwareTimer.getTime();
|
||||
private float pendingVolume = 1.0f;
|
||||
private ByteBuffer pendingAudio;
|
||||
|
||||
@@ -107,7 +108,7 @@ class DfpwmState
|
||||
|
||||
boolean isPlaying()
|
||||
{
|
||||
return unplayed || clientEndTime >= System.nanoTime();
|
||||
return unplayed || clientEndTime >= PauseAwareTimer.getTime();
|
||||
}
|
||||
|
||||
float getVolume()
|
||||
|
@@ -17,6 +17,7 @@ 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;
|
||||
@@ -119,7 +120,7 @@ public abstract class SpeakerPeripheral implements IPeripheral
|
||||
return;
|
||||
}
|
||||
|
||||
long now = System.nanoTime();
|
||||
long now = PauseAwareTimer.getTime();
|
||||
if( sound != null )
|
||||
{
|
||||
lastPlayTime = clock;
|
||||
@@ -342,7 +343,7 @@ public abstract class SpeakerPeripheral implements IPeripheral
|
||||
// TODO: Use ArgumentHelpers instead?
|
||||
int length = audio.length();
|
||||
if( length <= 0 ) throw new LuaException( "Cannot play empty audio" );
|
||||
if( length > 1024 * 16 * 8 ) throw new LuaException( "Audio data is too large" );
|
||||
if( length > 128 * 1024 ) throw new LuaException( "Audio data is too large" );
|
||||
|
||||
DfpwmState state;
|
||||
synchronized( lock )
|
||||
@@ -387,17 +388,7 @@ public abstract class SpeakerPeripheral implements IPeripheral
|
||||
computers.remove( computer );
|
||||
}
|
||||
|
||||
private static final class PendingSound
|
||||
private record PendingSound(ResourceLocation location, float volume, float pitch)
|
||||
{
|
||||
final ResourceLocation location;
|
||||
final float volume;
|
||||
final float pitch;
|
||||
|
||||
private PendingSound( ResourceLocation location, float volume, float pitch )
|
||||
{
|
||||
this.location = location;
|
||||
this.volume = volume;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
Container inventory = entity instanceof Player player ? player.getInventory() : null;
|
||||
PocketServerComputer computer = createServerComputer( world, inventory, entity, stack );
|
||||
if( computer != null )
|
||||
{
|
||||
IPocketUpgrade upgrade = getUpgrade( 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 )
|
||||
@@ -135,21 +147,17 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
|
||||
if( !world.isClientSide )
|
||||
{
|
||||
PocketServerComputer computer = createServerComputer( world, player.getInventory(), player, 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 ) );
|
||||
@@ -193,15 +201,14 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
|
||||
|
||||
public PocketServerComputer createServerComputer( final Level world, Container inventory, Entity entity, @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 );
|
||||
}
|
||||
@@ -219,13 +226,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 );
|
||||
@@ -235,15 +236,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 );
|
||||
@@ -258,6 +261,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ClientComputer getClientComputer( @Nonnull ItemStack stack )
|
||||
{
|
||||
int instanceID = getInstanceID( stack );
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -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;
|
||||
|
||||
@@ -46,6 +46,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;
|
||||
@@ -63,6 +64,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;
|
||||
@@ -876,7 +879,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;
|
||||
|
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
/**
|
||||
* A monotonically increasing clock which accounts for the game being paused.
|
||||
*/
|
||||
public final class PauseAwareTimer
|
||||
{
|
||||
private static boolean paused;
|
||||
private static long pauseTime;
|
||||
private static long pauseOffset;
|
||||
|
||||
private PauseAwareTimer()
|
||||
{
|
||||
}
|
||||
|
||||
public static long getTime()
|
||||
{
|
||||
return (paused ? pauseTime : Util.getNanos()) - pauseOffset;
|
||||
}
|
||||
|
||||
public static void tick()
|
||||
{
|
||||
boolean isPaused = Minecraft.getInstance().isPaused();
|
||||
if( isPaused == paused ) return;
|
||||
|
||||
if( isPaused )
|
||||
{
|
||||
pauseTime = Util.getNanos();
|
||||
paused = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pauseOffset += Util.getNanos() - pauseTime;
|
||||
paused = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,3 +2,7 @@ accessWidener v1 named
|
||||
|
||||
accessible class net/minecraft/client/renderer/RenderType$CompositeState
|
||||
accessible method net/minecraft/client/renderer/RenderType create (Ljava/lang/String;Lcom/mojang/blaze3d/vertex/VertexFormat;Lcom/mojang/blaze3d/vertex/VertexFormat$Mode;IZZLnet/minecraft/client/renderer/RenderType$CompositeState;)Lnet/minecraft/client/renderer/RenderType$CompositeRenderType;
|
||||
|
||||
# SpeakerInstance/SpeakerManager
|
||||
accessible method com/mojang/blaze3d/audio/Channel pumpBuffers (I)V
|
||||
accessible field net/minecraft/client/sounds/SoundEngine executor Lnet/minecraft/client/sounds/SoundEngineExecutor;
|
||||
|
@@ -9,6 +9,7 @@
|
||||
"MixinBlock",
|
||||
"MixinChunkMap",
|
||||
"MixinEntity",
|
||||
"MixinItemEntity",
|
||||
"MixinMatrix4f",
|
||||
"MixinServerLevel",
|
||||
"MixinServerPlayerGameMode"
|
||||
|
@@ -1,16 +1,18 @@
|
||||
--- The Disk API allows you to interact with disk drives.
|
||||
--
|
||||
-- These functions can operate on locally attached or remote disk drives. To use
|
||||
-- a locally attached drive, specify “side” as one of the six sides
|
||||
-- (e.g. `left`); to use a remote disk drive, specify its name as printed when
|
||||
-- enabling its modem (e.g. `drive_0`).
|
||||
--
|
||||
-- **Note:** All computers (except command computers), turtles and pocket
|
||||
-- computers can be placed within a disk drive to access it's internal storage
|
||||
-- like a disk.
|
||||
--
|
||||
-- @module disk
|
||||
-- @since 1.2
|
||||
--[[- The Disk API allows you to interact with disk drives.
|
||||
|
||||
These functions can operate on locally attached or remote disk drives. To use a
|
||||
locally attached drive, specify “side” as one of the six sides (e.g. `left`); to
|
||||
use a remote disk drive, specify its name as printed when enabling its modem
|
||||
(e.g. `drive_0`).
|
||||
|
||||
:::tip
|
||||
All computers (except command computers), turtles and pocket computers can be
|
||||
placed within a disk drive to access it's internal storage like a disk.
|
||||
:::
|
||||
|
||||
@module disk
|
||||
@since 1.2
|
||||
]]
|
||||
|
||||
local function isDrive(name)
|
||||
if type(name) ~= "string" then
|
||||
|
@@ -1,27 +1,30 @@
|
||||
--- The GPS API provides a method for turtles and computers to retrieve their
|
||||
-- own locations.
|
||||
--
|
||||
-- It broadcasts a PING message over @{rednet} and wait for responses. In order
|
||||
-- for this system to work, there must be at least 4 computers used as gps hosts
|
||||
-- which will respond and allow trilateration. Three of these hosts should be in
|
||||
-- a plane, and the fourth should be either above or below the other three. The
|
||||
-- three in a plane should not be in a line with each other. You can set up
|
||||
-- hosts using the gps program.
|
||||
--
|
||||
-- **Note**: When entering in the coordinates for the host you need to put in
|
||||
-- the `x`, `y`, and `z` coordinates of the computer, not the modem, as all
|
||||
-- rednet distances are measured from the block the computer is in.
|
||||
--
|
||||
-- Also note that you may choose which axes x, y, or z refers to - so long as
|
||||
-- your systems have the same definition as any GPS servers that're in range, it
|
||||
-- works just the same. For example, you might build a GPS cluster according to
|
||||
-- [this tutorial][1], using z to account for height, or you might use y to
|
||||
-- account for height in the way that Minecraft's debug screen displays.
|
||||
--
|
||||
-- [1]: http://www.computercraft.info/forums2/index.php?/topic/3088-how-to-guide-gps-global-position-system/
|
||||
--
|
||||
-- @module gps
|
||||
-- @since 1.31
|
||||
--[[- The GPS API provides a method for turtles and computers to retrieve their
|
||||
own locations.
|
||||
|
||||
It broadcasts a PING message over @{rednet} and wait for responses. In order for
|
||||
this system to work, there must be at least 4 computers used as gps hosts which
|
||||
will respond and allow trilateration. Three of these hosts should be in a plane,
|
||||
and the fourth should be either above or below the other three. The three in a
|
||||
plane should not be in a line with each other. You can set up hosts using the
|
||||
gps program.
|
||||
|
||||
:::note
|
||||
When entering in the coordinates for the host you need to put in the `x`, `y`,
|
||||
and `z` coordinates of the computer, not the modem, as all modem distances are
|
||||
measured from the block the computer is in.
|
||||
:::
|
||||
|
||||
Also note that you may choose which axes x, y, or z refers to - so long as your
|
||||
systems have the same definition as any GPS servers that're in range, it works
|
||||
just the same. For example, you might build a GPS cluster according to [this
|
||||
tutorial][1], using z to account for height, or you might use y to account for
|
||||
height in the way that Minecraft's debug screen displays.
|
||||
|
||||
[1]: http://www.computercraft.info/forums2/index.php?/topic/3088-how-to-guide-gps-global-position-system/
|
||||
|
||||
@module gps
|
||||
@since 1.31
|
||||
]]
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
|
@@ -2,13 +2,13 @@
|
||||
|
||||
Functions are not actually executed simultaniously, but rather this API will
|
||||
automatically switch between them whenever they yield (eg whenever they call
|
||||
@{coroutine.yield}, or functions that call that - eg `os.pullEvent` - or
|
||||
@{coroutine.yield}, or functions that call that - eg @{os.pullEvent} - or
|
||||
functions that call that, etc - basically, anything that causes the function
|
||||
to "pause").
|
||||
|
||||
Each function executed in "parallel" gets its own copy of the event queue,
|
||||
and so "event consuming" functions (again, mostly anything that causes the
|
||||
script to pause - eg `sleep`, `rednet.receive`, most of the `turtle` API,
|
||||
script to pause - eg @{os.sleep}, @{rednet.receive}, most of the @{turtle} API,
|
||||
etc) can safely be used in one without affecting the event queue accessed by
|
||||
the other.
|
||||
|
||||
|
@@ -66,7 +66,7 @@ type.
|
||||
What is a peripheral type though? This is a string which describes what a
|
||||
peripheral is, and so what functions are available on it. For instance, speakers
|
||||
are just called `"speaker"`, and monitors `"monitor"`. Some peripherals might
|
||||
have more than one type; a Minecraft chest is both a `"minecraft:chest"` and
|
||||
have more than one type - a Minecraft chest is both a `"minecraft:chest"` and
|
||||
`"inventory"`.
|
||||
|
||||
You can get all the types a peripheral has with @{peripheral.getType}, and check
|
||||
|
@@ -1,21 +1,48 @@
|
||||
--- The Rednet API allows systems to communicate between each other without
|
||||
-- using redstone. It serves as a wrapper for the modem API, offering ease of
|
||||
-- functionality (particularly in regards to repeating signals) with some
|
||||
-- expense of fine control.
|
||||
--
|
||||
-- In order to send and receive data, a modem (either wired, wireless, or ender)
|
||||
-- is required. The data reaches any possible destinations immediately after
|
||||
-- sending it, but is range limited.
|
||||
--
|
||||
-- Rednet also allows you to use a "protocol" - simple string names indicating
|
||||
-- what messages are about. Receiving systems may filter messages according to
|
||||
-- their protocols, thereby automatically ignoring incoming messages which don't
|
||||
-- specify an identical string. It's also possible to @{rednet.lookup|lookup}
|
||||
-- which systems in the area use certain protocols, hence making it easier to
|
||||
-- determine where given messages should be sent in the first place.
|
||||
--
|
||||
-- @module rednet
|
||||
-- @since 1.2
|
||||
--[[- The Rednet API allows computers to communicate between each other by using
|
||||
@{modem|modems}. It provides a layer of abstraction on top of the main @{modem}
|
||||
peripheral, making it slightly easier to use.
|
||||
|
||||
## Basic usage
|
||||
In order to send a message between two computers, each computer must have a
|
||||
modem on one of its sides (or in the case of pocket computers and turtles, the
|
||||
modem must be equipped as an upgrade). The two computers should then call
|
||||
@{rednet.open}, which sets up the modems ready to send and receive messages.
|
||||
|
||||
Once rednet is opened, you can send messages using @{rednet.send} and receive
|
||||
them using @{rednet.receive}. It's also possible to send a message to _every_
|
||||
rednet-using computer using @{rednet.broadcast}.
|
||||
|
||||
:::caution Network security
|
||||
|
||||
While rednet provides a friendly way to send messages to specific computers, it
|
||||
doesn't provide any guarantees about security. Other computers could be
|
||||
listening in to your messages, or even pretending to send messages from other computers!
|
||||
|
||||
If you're playing on a multi-player server (or at least one where you don't
|
||||
trust other players), it's worth encrypting or signing your rednet messages.
|
||||
:::
|
||||
|
||||
## Protocols and hostnames
|
||||
Several rednet messages accept "protocol"s - simple string names describing what
|
||||
a message is about. When sending messages using @{rednet.send} and
|
||||
@{rednet.broadcast}, you can optionally specify a protocol for the message. This
|
||||
same protocol can then be given to @{rednet.receive}, to ignore all messages not
|
||||
using this protocol.
|
||||
|
||||
It's also possible to look-up computers based on protocols, providing a basic
|
||||
system for service discovery and [DNS]. A computer can advertise that it
|
||||
supports a particular protocol with @{rednet.host}, also providing a friendly
|
||||
"hostname". Other computers may then find all computers which support this
|
||||
protocol using @{rednet.lookup}.
|
||||
|
||||
[DNS]: https://en.wikipedia.org/wiki/Domain_Name_System "Domain Name System"
|
||||
|
||||
@module rednet
|
||||
@since 1.2
|
||||
@see rednet_message Queued when a rednet message is received.
|
||||
@see modem Rednet is built on top of the modem peripheral. Modems provide a more
|
||||
bare-bones but flexible interface.
|
||||
]]
|
||||
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
@@ -46,9 +73,17 @@ This will open the modem on two channels: one which has the same
|
||||
|
||||
@tparam string modem The name of the modem to open.
|
||||
@throws If there is no such modem with the given name
|
||||
@usage Open a wireless modem on the back of the computer.
|
||||
@usage Open rednet on the back of the computer, allowing you to send and receive
|
||||
rednet messages using it.
|
||||
|
||||
rednet.open("back")
|
||||
|
||||
@usage Open rednet on all attached modems. This abuses the "filter" argument to
|
||||
@{peripheral.find}.
|
||||
|
||||
peripheral.find("modem", rednet.open)
|
||||
@see rednet.close
|
||||
@see rednet.isOpen
|
||||
]]
|
||||
function open(modem)
|
||||
expect(1, modem, "string")
|
||||
@@ -65,6 +100,7 @@ end
|
||||
-- @tparam[opt] string modem The side the modem exists on. If not given, all
|
||||
-- open modems will be closed.
|
||||
-- @throws If there is no such modem with the given name
|
||||
-- @see rednet.open
|
||||
function close(modem)
|
||||
expect(1, modem, "string", "nil")
|
||||
if modem then
|
||||
@@ -90,6 +126,7 @@ end
|
||||
-- modems will be checked.
|
||||
-- @treturn boolean If the given modem is open.
|
||||
-- @since 1.31
|
||||
-- @see rednet.open
|
||||
function isOpen(modem)
|
||||
expect(1, modem, "string", "nil")
|
||||
if modem then
|
||||
@@ -109,15 +146,17 @@ function isOpen(modem)
|
||||
end
|
||||
|
||||
--[[- Allows a computer or turtle with an attached modem to send a message
|
||||
intended for a system with a specific ID. At least one such modem must first
|
||||
intended for a sycomputer with a specific ID. At least one such modem must first
|
||||
be @{rednet.open|opened} before sending is possible.
|
||||
|
||||
Assuming the target was in range and also had a correctly opened modem, it
|
||||
may then use @{rednet.receive} to collect the message.
|
||||
Assuming the target was in range and also had a correctly opened modem, the
|
||||
target computer may then use @{rednet.receive} to collect the message.
|
||||
|
||||
@tparam number recipient The ID of the receiving computer.
|
||||
@param message The message to send. This should not contain coroutines or
|
||||
functions, as they will be converted to @{nil}.
|
||||
@param message The message to send. Like with @{modem.transmit}, this can
|
||||
contain any primitive type (numbers, booleans and strings) as well as
|
||||
tables. Other types (like functions), as well as metatables, will not be
|
||||
transmitted.
|
||||
@tparam[opt] string protocol The "protocol" to send this message under. When
|
||||
using @{rednet.receive} one can filter to only receive messages sent under a
|
||||
particular protocol.
|
||||
@@ -174,16 +213,19 @@ function send(recipient, message, protocol)
|
||||
return sent
|
||||
end
|
||||
|
||||
--- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST}
|
||||
-- channel. The message will be received by every device listening to rednet.
|
||||
--
|
||||
-- @param message The message to send. This should not contain coroutines or
|
||||
-- functions, as they will be converted to @{nil}.
|
||||
-- @tparam[opt] string protocol The "protocol" to send this message under. When
|
||||
-- using @{rednet.receive} one can filter to only receive messages sent under a
|
||||
-- particular protocol.
|
||||
-- @see rednet.receive
|
||||
-- @changed 1.6 Added protocol parameter.
|
||||
--[[- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST}
|
||||
channel. The message will be received by every device listening to rednet.
|
||||
|
||||
@param message The message to send. This should not contain coroutines or
|
||||
functions, as they will be converted to @{nil}. @tparam[opt] string protocol
|
||||
The "protocol" to send this message under. When using @{rednet.receive} one can
|
||||
filter to only receive messages sent under a particular protocol.
|
||||
@see rednet.receive
|
||||
@changed 1.6 Added protocol parameter.
|
||||
@usage Broadcast the words "Hello, world!" to every computer using rednet.
|
||||
|
||||
rednet.broadcast("Hello, world!")
|
||||
]]
|
||||
function broadcast(message, protocol)
|
||||
expect(2, protocol, "string", "nil")
|
||||
send(CHANNEL_BROADCAST, message, protocol)
|
||||
@@ -263,25 +305,25 @@ function receive(protocol_filter, timeout)
|
||||
end
|
||||
end
|
||||
|
||||
--- Register the system as "hosting" the desired protocol under the specified
|
||||
-- name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and
|
||||
-- maybe name) on the same network, the registered system will automatically
|
||||
-- respond via a background process, hence providing the system performing the
|
||||
-- lookup with its ID number.
|
||||
--
|
||||
-- Multiple computers may not register themselves on the same network as having
|
||||
-- the same names against the same protocols, and the title `localhost` is
|
||||
-- specifically reserved. They may, however, share names as long as their hosted
|
||||
-- protocols are different, or if they only join a given network after
|
||||
-- "registering" themselves before doing so (eg while offline or part of a
|
||||
-- different network).
|
||||
--
|
||||
-- @tparam string protocol The protocol this computer provides.
|
||||
-- @tparam string hostname The name this protocol exposes for the given protocol.
|
||||
-- @throws If trying to register a hostname which is reserved, or currently in use.
|
||||
-- @see rednet.unhost
|
||||
-- @see rednet.lookup
|
||||
-- @since 1.6
|
||||
--[[- Register the system as "hosting" the desired protocol under the specified
|
||||
name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and
|
||||
maybe name) on the same network, the registered system will automatically
|
||||
respond via a background process, hence providing the system performing the
|
||||
lookup with its ID number.
|
||||
|
||||
Multiple computers may not register themselves on the same network as having the
|
||||
same names against the same protocols, and the title `localhost` is specifically
|
||||
reserved. They may, however, share names as long as their hosted protocols are
|
||||
different, or if they only join a given network after "registering" themselves
|
||||
before doing so (eg while offline or part of a different network).
|
||||
|
||||
@tparam string protocol The protocol this computer provides.
|
||||
@tparam string hostname The name this protocol exposes for the given protocol.
|
||||
@throws If trying to register a hostname which is reserved, or currently in use.
|
||||
@see rednet.unhost
|
||||
@see rednet.lookup
|
||||
@since 1.6
|
||||
]]
|
||||
function host(protocol, hostname)
|
||||
expect(1, protocol, "string")
|
||||
expect(2, hostname, "string")
|
||||
@@ -306,21 +348,38 @@ function unhost(protocol)
|
||||
hostnames[protocol] = nil
|
||||
end
|
||||
|
||||
--- Search the local rednet network for systems @{rednet.host|hosting} the
|
||||
-- desired protocol and returns any computer IDs that respond as "registered"
|
||||
-- against it.
|
||||
--
|
||||
-- If a hostname is specified, only one ID will be returned (assuming an exact
|
||||
-- match is found).
|
||||
--
|
||||
-- @tparam string protocol The protocol to search for.
|
||||
-- @tparam[opt] string hostname The hostname to search for.
|
||||
--
|
||||
-- @treturn[1] { number }|nil A list of computer IDs hosting the given
|
||||
-- protocol, or @{nil} if none exist.
|
||||
-- @treturn[2] number|nil The computer ID with the provided hostname and protocol,
|
||||
-- or @{nil} if none exists.
|
||||
-- @since 1.6
|
||||
--[[- Search the local rednet network for systems @{rednet.host|hosting} the
|
||||
desired protocol and returns any computer IDs that respond as "registered"
|
||||
against it.
|
||||
|
||||
If a hostname is specified, only one ID will be returned (assuming an exact
|
||||
match is found).
|
||||
|
||||
@tparam string protocol The protocol to search for.
|
||||
@tparam[opt] string hostname The hostname to search for.
|
||||
|
||||
@treturn[1] number... A list of computer IDs hosting the given protocol.
|
||||
@treturn[2] number|nil The computer ID with the provided hostname and protocol,
|
||||
or @{nil} if none exists.
|
||||
@since 1.6
|
||||
@usage Find all computers which are hosting the `"chat"` protocol.
|
||||
|
||||
local computers = {rednet.lookup("chat")}
|
||||
print(#computers .. " computers available to chat")
|
||||
for _, computer in pairs(computers) do
|
||||
print("Computer #" .. computer)
|
||||
end
|
||||
|
||||
@usage Find a computer hosting the `"chat"` protocol with a hostname of `"my_host"`.
|
||||
|
||||
local id = rednet.lookup("chat", "my_host")
|
||||
if id then
|
||||
print("Found my_host at computer #" .. id)
|
||||
else
|
||||
printError("Cannot find my_host")
|
||||
end
|
||||
|
||||
]]
|
||||
function lookup(protocol, hostname)
|
||||
expect(1, protocol, "string")
|
||||
expect(2, hostname, "string", "nil")
|
||||
|
@@ -108,39 +108,48 @@ local function makePagedScroll(_term, _nFreeLines)
|
||||
end
|
||||
end
|
||||
|
||||
--- Prints a given string to the display.
|
||||
--
|
||||
-- If the action can be completed without scrolling, it acts much the same as
|
||||
-- @{print}; otherwise, it will throw up a "Press any key to continue" prompt at
|
||||
-- the bottom of the display. Each press will cause it to scroll down and write
|
||||
-- a single line more before prompting again, if need be.
|
||||
--
|
||||
-- @tparam string _sText The text to print to the screen.
|
||||
-- @tparam[opt] number _nFreeLines The number of lines which will be
|
||||
-- automatically scrolled before the first prompt appears (meaning _nFreeLines +
|
||||
-- 1 lines will be printed). This can be set to the terminal's height - 2 to
|
||||
-- always try to fill the screen. Defaults to 0, meaning only one line is
|
||||
-- displayed before prompting.
|
||||
-- @treturn number The number of lines printed.
|
||||
-- @usage
|
||||
-- local width, height = term.getSize()
|
||||
-- textutils.pagedPrint(("This is a rather verbose dose of repetition.\n"):rep(30), height - 2)
|
||||
function pagedPrint(_sText, _nFreeLines)
|
||||
expect(2, _nFreeLines, "number", "nil")
|
||||
--[[- Prints a given string to the display.
|
||||
|
||||
If the action can be completed without scrolling, it acts much the same as
|
||||
@{print}; otherwise, it will throw up a "Press any key to continue" prompt at
|
||||
the bottom of the display. Each press will cause it to scroll down and write a
|
||||
single line more before prompting again, if need be.
|
||||
|
||||
@tparam string text The text to print to the screen.
|
||||
@tparam[opt] number free_lines The number of lines which will be
|
||||
automatically scrolled before the first prompt appears (meaning free_lines +
|
||||
1 lines will be printed). This can be set to the cursor's y position - 2 to
|
||||
always try to fill the screen. Defaults to 0, meaning only one line is
|
||||
displayed before prompting.
|
||||
@treturn number The number of lines printed.
|
||||
|
||||
@usage Generates several lines of text and then prints it, paging once the
|
||||
bottom of the terminal is reached.
|
||||
|
||||
local lines = {}
|
||||
for i = 1, 30 do lines[i] = ("This is line #%d"):format(i) end
|
||||
local message = table.concat(lines, "\n")
|
||||
|
||||
local width, height = term.getCursorPos()
|
||||
textutils.pagedPrint(message, height - 2)
|
||||
]]
|
||||
function pagedPrint(text, free_lines)
|
||||
expect(2, free_lines, "number", "nil")
|
||||
-- Setup a redirector
|
||||
local oldTerm = term.current()
|
||||
local newTerm = {}
|
||||
for k, v in pairs(oldTerm) do
|
||||
newTerm[k] = v
|
||||
end
|
||||
newTerm.scroll = makePagedScroll(oldTerm, _nFreeLines)
|
||||
|
||||
newTerm.scroll = makePagedScroll(oldTerm, free_lines)
|
||||
term.redirect(newTerm)
|
||||
|
||||
-- Print the text
|
||||
local result
|
||||
local ok, err = pcall(function()
|
||||
if _sText ~= nil then
|
||||
result = print(_sText)
|
||||
if text ~= nil then
|
||||
result = print(text)
|
||||
else
|
||||
result = print()
|
||||
end
|
||||
@@ -214,32 +223,45 @@ local function tabulateCommon(bPaged, ...)
|
||||
end
|
||||
end
|
||||
|
||||
--- Prints tables in a structured form.
|
||||
--
|
||||
-- This accepts multiple arguments, either a table or a number. When
|
||||
-- encountering a table, this will be treated as a table row, with each column
|
||||
-- width being auto-adjusted.
|
||||
--
|
||||
-- When encountering a number, this sets the text color of the subsequent rows to it.
|
||||
--
|
||||
-- @tparam {string...}|number ... The rows and text colors to display.
|
||||
-- @usage textutils.tabulate(colors.orange, { "1", "2", "3" }, colors.lightBlue, { "A", "B", "C" })
|
||||
-- @since 1.3
|
||||
--[[- Prints tables in a structured form.
|
||||
|
||||
This accepts multiple arguments, either a table or a number. When
|
||||
encountering a table, this will be treated as a table row, with each column
|
||||
width being auto-adjusted.
|
||||
|
||||
When encountering a number, this sets the text color of the subsequent rows to it.
|
||||
|
||||
@tparam {string...}|number ... The rows and text colors to display.
|
||||
@since 1.3
|
||||
@usage
|
||||
|
||||
textutils.tabulate(
|
||||
colors.orange, { "1", "2", "3" },
|
||||
colors.lightBlue, { "A", "B", "C" }
|
||||
)
|
||||
]]
|
||||
function tabulate(...)
|
||||
return tabulateCommon(false, ...)
|
||||
end
|
||||
|
||||
--- Prints tables in a structured form, stopping and prompting for input should
|
||||
-- the result not fit on the terminal.
|
||||
--
|
||||
-- This functions identically to @{textutils.tabulate}, but will prompt for user
|
||||
-- input should the whole output not fit on the display.
|
||||
--
|
||||
-- @tparam {string...}|number ... The rows and text colors to display.
|
||||
-- @usage textutils.tabulate(colors.orange, { "1", "2", "3" }, colors.lightBlue, { "A", "B", "C" })
|
||||
-- @see textutils.tabulate
|
||||
-- @see textutils.pagedPrint
|
||||
-- @since 1.3
|
||||
--[[- Prints tables in a structured form, stopping and prompting for input should
|
||||
the result not fit on the terminal.
|
||||
|
||||
This functions identically to @{textutils.tabulate}, but will prompt for user
|
||||
input should the whole output not fit on the display.
|
||||
|
||||
@tparam {string...}|number ... The rows and text colors to display.
|
||||
@see textutils.tabulate
|
||||
@see textutils.pagedPrint
|
||||
@since 1.3
|
||||
|
||||
@usage Generates a long table, tabulates it, and prints it to the screen.
|
||||
|
||||
local rows = {}
|
||||
for i = 1, 30 do rows[i] = {("Row #%d"):format(i), math.random(1, 400)} end
|
||||
|
||||
textutils.tabulate(colors.orange, {"Column", "Value"}, colors.lightBlue, table.unpack(rows))
|
||||
]]
|
||||
function pagedTabulate(...)
|
||||
return tabulateCommon(true, ...)
|
||||
end
|
||||
@@ -692,11 +714,11 @@ saving in a file or pretty-printing.
|
||||
@throws If the object contains a value which cannot be
|
||||
serialised. This includes functions and tables which appear multiple
|
||||
times.
|
||||
@see cc.pretty.pretty An alternative way to display a table, often more suitable for
|
||||
pretty printing.
|
||||
@see cc.pretty.pretty_print An alternative way to display a table, often more
|
||||
suitable for pretty printing.
|
||||
@since 1.3
|
||||
@changed 1.97.0 Added `opts` argument.
|
||||
@usage Pretty print a basic table.
|
||||
@usage Serialise a basic table.
|
||||
|
||||
textutils.serialise({ 1, 2, 3, a = 1, ["another key"] = { true } })
|
||||
|
||||
|
@@ -1,6 +1,4 @@
|
||||
--- The turtle API allows you to control your turtle.
|
||||
--
|
||||
-- @module turtle
|
||||
--- @module turtle
|
||||
|
||||
if not turtle then
|
||||
error("Cannot load turtle API on computer", 2)
|
||||
@@ -8,9 +6,8 @@ end
|
||||
|
||||
--- The builtin turtle API, without any generated helper functions.
|
||||
--
|
||||
-- Generally you should not need to use this table - it only exists for
|
||||
-- backwards compatibility reasons.
|
||||
-- @deprecated
|
||||
-- @deprecated Historically this table behaved differently to the main turtle API, but this is no longer the base. You
|
||||
-- should not need to use it.
|
||||
native = turtle.native or turtle
|
||||
|
||||
local function addCraftMethod(object)
|
||||
|
@@ -1,3 +1,15 @@
|
||||
# New features in CC: Tweaked 1.100.0
|
||||
|
||||
* Speakers can now play arbitrary PCM audio.
|
||||
* Add support for encoding and decoding DFPWM streams, with the `cc.audio.dfpwm` module.
|
||||
* Wired modems now only render breaking progress for the part which is being broken.
|
||||
* Various documentation improvements.
|
||||
|
||||
Several bug fixes:
|
||||
* Fix the "repeat" program not repeating broadcast rednet messages.
|
||||
* Fix the drag-and-drop upload functionality writing empty files.
|
||||
* Prevent turtles from pushing non-pushable entities.
|
||||
|
||||
# New features in CC: Tweaked 1.99.1
|
||||
|
||||
* Add package.searchpath to the cc.require API. (MCJack123)
|
||||
|
@@ -6,7 +6,7 @@ Uses LuaJ from http://luaj.sourceforge.net/
|
||||
The ComputerCraft 1.76 update was sponsored by MinecraftU and Deep Space.
|
||||
Visit http://www.minecraftu.org and http://www.deepspace.me/space-cadets to find out more.
|
||||
|
||||
Join the ComputerCraft community online at http://www.computercraft.info
|
||||
Join the ComputerCraft community online at https://computercraft.cc
|
||||
Follow @DanTwoHundred on Twitter!
|
||||
|
||||
To help contribute to ComputerCraft, browse the source code at https://github.com/dan200/ComputerCraft.
|
||||
|
@@ -1,12 +1,13 @@
|
||||
New features in CC: Tweaked 1.99.1
|
||||
New features in CC: Tweaked 1.100.0
|
||||
|
||||
* Add package.searchpath to the cc.require API. (MCJack123)
|
||||
* Provide a more efficient way for the Java API to consume Lua tables in certain restricted cases.
|
||||
* Speakers can now play arbitrary PCM audio.
|
||||
* Add support for encoding and decoding DFPWM streams, with the `cc.audio.dfpwm` module.
|
||||
* Wired modems now only render breaking progress for the part which is being broken.
|
||||
* Various documentation improvements.
|
||||
|
||||
Several bug fixes:
|
||||
* Fix keys being "sticky" when opening the off-hand pocket computer GUI.
|
||||
* Correctly handle broken coroutine managers resuming Java code with a `nil` event.
|
||||
* Prevent computer buttons stealing focus from the terminal.
|
||||
* Fix a class cast exception when a monitor is malformed in ways I do not quite understand.
|
||||
* Fix the "repeat" program not repeating broadcast rednet messages.
|
||||
* Fix the drag-and-drop upload functionality writing empty files.
|
||||
* Prevent turtles from pushing non-pushable entities.
|
||||
|
||||
Type "help changelog" to see the full version history.
|
||||
|
@@ -36,7 +36,7 @@ local encoder = dfpwm.make_encoder()
|
||||
local decoder = dfpwm.make_decoder()
|
||||
|
||||
local out = fs.open("speedy.dfpwm", "wb")
|
||||
for input in io.lines("my_audio_track.dfpwm", 16 * 1024 * 2) do
|
||||
for input in io.lines("data/example.dfpwm", 16 * 1024 * 2) do
|
||||
local decoded = decoder(input)
|
||||
local output = {}
|
||||
|
||||
@@ -143,14 +143,14 @@ streams, or use different decoders for the same stream, the resulting audio may
|
||||
@usage Reads "data/example.dfpwm" in blocks of 16KiB (the speaker can accept a maximum of 128×1024 samples), decodes
|
||||
them and then plays them through the speaker.
|
||||
|
||||
```lua
|
||||
```lua {data-peripheral=speaker}
|
||||
local dfpwm = require "cc.audio.dfpwm"
|
||||
local speaker = peripheral.find("speaker")
|
||||
|
||||
local decoder = dfpwm.make_decoder()
|
||||
for input in io.lines("data/example.dfpwm", 16 * 1024 * 2) do
|
||||
for input in io.lines("data/example.dfpwm", 16 * 1024) do
|
||||
local decoded = decoder(input)
|
||||
while not speaker.playAudio(output) do
|
||||
while not speaker.playAudio(decoded) do
|
||||
os.pullEvent("speaker_audio_empty")
|
||||
end
|
||||
end
|
||||
|
@@ -224,8 +224,8 @@ end
|
||||
|
||||
--- Display a document on the terminal.
|
||||
--
|
||||
-- @tparam Doc doc The document to render
|
||||
-- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
-- @tparam Doc doc The document to render
|
||||
-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
local function write(doc, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
expect(2, ribbon_frac, "number", "nil")
|
||||
@@ -286,8 +286,8 @@ end
|
||||
|
||||
--- Display a document on the terminal with a trailing new line.
|
||||
--
|
||||
-- @tparam Doc doc The document to render.
|
||||
-- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
-- @tparam Doc doc The document to render.
|
||||
-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
local function print(doc, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
expect(2, ribbon_frac, "number", "nil")
|
||||
@@ -297,10 +297,10 @@ end
|
||||
|
||||
--- Render a document, converting it into a string.
|
||||
--
|
||||
-- @tparam Doc doc The document to render.
|
||||
-- @tparam[opt] number width The maximum width of this document. Note that long strings will not be wrapped to
|
||||
-- fit this width - it is only used for finding the best layout.
|
||||
-- @tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
-- @tparam Doc doc The document to render.
|
||||
-- @tparam[opt] number width The maximum width of this document. Note that long strings will not be wrapped to fit
|
||||
-- this width - it is only used for finding the best layout.
|
||||
-- @tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
-- @treturn string The rendered document as a string.
|
||||
local function render(doc, width, ribbon_frac)
|
||||
if getmetatable(doc) ~= Doc then expect(1, doc, "document") end
|
||||
@@ -483,7 +483,7 @@ Controls how various properties are displayed.
|
||||
- `function_args`: Show the arguments to a function if known (`false` by default).
|
||||
- `function_source`: Show where the function was defined, instead of
|
||||
`function: xxxxxxxx` (`false` by default).
|
||||
@tparam[opt] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
@tparam[opt=0.6] number ribbon_frac The maximum fraction of the width that we should write in.
|
||||
|
||||
@usage Display a table on the screen.
|
||||
|
||||
|
@@ -1,22 +1,24 @@
|
||||
--- This provides a pure Lua implementation of the builtin @{require} function
|
||||
-- and @{package} library.
|
||||
--
|
||||
-- Generally you do not need to use this module - it is injected into the
|
||||
-- every program's environment. However, it may be useful when building a
|
||||
-- custom shell or when running programs yourself.
|
||||
--
|
||||
-- @module cc.require
|
||||
-- @since 1.88.0
|
||||
-- @usage Construct the package and require function, and insert them into a
|
||||
-- custom environment.
|
||||
--
|
||||
-- local r = require "cc.require"
|
||||
-- local env = setmetatable({}, { __index = _ENV })
|
||||
-- env.require, env.package = r.make(env, "/")
|
||||
--
|
||||
-- -- Now we have our own require function, separate to the original.
|
||||
-- local r2 = env.require "cc.require"
|
||||
-- print(r, r2)
|
||||
--[[- This provides a pure Lua implementation of the builtin @{require} function
|
||||
and @{package} library.
|
||||
|
||||
Generally you do not need to use this module - it is injected into the every
|
||||
program's environment. However, it may be useful when building a custom shell or
|
||||
when running programs yourself.
|
||||
|
||||
@module cc.require
|
||||
@since 1.88.0
|
||||
@see using_require For an introduction on how to use @{require}.
|
||||
@usage Construct the package and require function, and insert them into a
|
||||
custom environment.
|
||||
|
||||
local r = require "cc.require"
|
||||
local env = setmetatable({}, { __index = _ENV })
|
||||
env.require, env.package = r.make(env, "/")
|
||||
|
||||
-- Now we have our own require function, separate to the original.
|
||||
local r2 = env.require "cc.require"
|
||||
print(r, r2)
|
||||
]]
|
||||
|
||||
local expect = require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua")
|
||||
local expect = expect.expect
|
||||
|
@@ -171,7 +171,7 @@ end
|
||||
local current_section = nil
|
||||
local offset = 0
|
||||
|
||||
--- Find the currently visible seciton, or nil if this document has no sections.
|
||||
--- Find the currently visible section, or nil if this document has no sections.
|
||||
--
|
||||
-- This could potentially be a binary search, but right now it's not worth it.
|
||||
local function find_section()
|
||||
|
Reference in New Issue
Block a user